具有多个类加载器的 Java ServiceLoader
在以下环境中使用 ServiceLoader 的最佳实践是什么具有多个类加载器的环境?文档建议在初始化时创建并保存单个服务实例:
private static ServiceLoader<CodecSet> codecSetLoader = ServiceLoader.load(CodecSet.class);
这将使用当前上下文类加载器初始化 ServiceLoader。现在假设此代码片段包含在使用 Web 容器中的共享类加载器加载的类中,并且多个 Web 应用程序想要定义自己的服务实现。这些不会在上面的代码中被拾取,甚至有可能加载器使用第一个 webapps 上下文类加载器进行初始化,并向其他用户提供错误的实现。
总是创建一个新的 ServiceLoader 似乎会浪费性能,因为它每次都必须枚举和解析服务文件。 编辑:这甚至可能是一个很大的性能问题,如 这个关于 java 的 XPath 实现的答案。
其他图书馆如何处理这个问题?他们是否缓存每个类加载器的实现,是否每次都重新解析其配置,或者他们是否只是忽略此问题并且仅适用于一个类加载器?
What are the best practices for using ServiceLoader in an Environment with multiple ClassLoaders? The documentation recommends to create and save a single service instance at initialization:
private static ServiceLoader<CodecSet> codecSetLoader = ServiceLoader.load(CodecSet.class);
This would initialize the ServiceLoader using the current context classloader. Now suppose this snippet is contained in a class loaded using a shared classloader in a web container and multiple web applications want to define their own service implementations. These would not get picked up in the above code, it might even be possible that the loader gets initialized using the first webapps context classloader and provide the wrong implementation to other users.
Always creating a new ServiceLoader seems wasteful performance wise since it has to enumerate and parse service files each time. Edit: This can even be a big performance problem as shown in this answer regarding java's XPath implementation.
How do other libraries handle this? Do they cache the implementations per classloader, do they reparse their configuration everytime or do they simply ignore this problem and only work for one classloader?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
我个人在任何情况下都不喜欢
ServiceLoader
。它很慢并且不必要地浪费,而且您几乎无法对其进行优化。我还发现它有点有限——如果你想做的不仅仅是按类型搜索,你真的必须不遗余力。
xbean-finder 的 ResourceFinder
在我们的注意力太短之前,先介绍一下它如何替换 ServiceLoader
这将在类路径中找到所有
META-INF/services/org.acme.Plugin
实现。请注意,它实际上并没有实例化所有实例。选择您想要的实例,只需调用一次
newInstance()
即可获得实例。为什么这很好?
newInstance()
并进行适当的异常处理有多难?不难。缩小搜索范围
如果您只想检查特定的 URL,您可以轻松做到这一点:
在这里,在使用此 ResourceFinder 实例时,只会搜索“some.jar”。
还有一个名为
UrlSet
的便利类,它可以使从类路径中选择 URL 变得非常容易。替代“服务”样式
假设您想要应用
ServiceLoader
类型概念来重新设计 URL 处理并查找/加载特定协议的java.net.URLStreamHandler
。以下是在类路径中布局服务的方法:
META-INF/java.net.URLStreamHandler/foo
META-INF/java.net.URLStreamHandler/bar
META-INF/java.net.URLStreamHandler/baz
其中
foo
是一个纯文本文件,其中包含服务实现的名称,就像以前一样。现在假设有人创建了一个foo://...
URL。我们可以通过以下方式快速找到实现:备用“服务”样式 2
假设您想将一些配置信息放入服务文件中,因此它不仅仅包含类名。这是将服务解析为属性文件的替代样式。按照惯例,一个键是类名,其他键是可注入属性。
所以这里
red
是一个属性文件META-INF/org.acme.Plugin/red
META-INF/org.acme.Plugin/blue
META-INF/org.acme.Plugin/green
您可以像以前一样查找内容。
以下是如何将这些属性与 xbean-reflect 一起使用,xbean-reflect 是另一个可以为您提供无框架 IoC 的小库。您只需给它类名和一些名称值对,它就会构造并注入。
下面是长形式的“拼写”:
xbean-reflect
库比内置 JavaBeans API 更进一步,但更好一点,不需要您一路走到完整的 IoC 框架,如 Guice 或 Spring。它支持工厂方法和构造函数参数以及设置器/字段注入。为什么 ServiceLoader 如此有限?
JVM 中不推荐使用的代码会损害 Java 语言本身。许多东西在添加到 JVM 之前都会被精简到骨干,因为之后就无法再精简它们了。
ServiceLoader
就是一个很好的例子。 API 有限,OpenJDK 实现大约有 500 行,包括 javadoc。那里没有什么花哨的东西,更换它很容易。如果它对您不起作用,请不要使用它。
抛开类路径范围
API 不谈,在纯粹的实用性中,缩小搜索 URL 的范围是这个问题的真正解决方案。应用程序服务器本身就有相当多的 URL,不包括应用程序中的 jar。例如,OSX 上的 Tomcat 7 仅在 StandardClassLoader 中就有大约 40 个 URL(这是所有 Web 应用程序类加载器的父级)。
您的应用程序服务器越大,即使是简单的搜索也会花费更长的时间。
如果您打算搜索多个条目,则缓存没有帮助。此外,它还会增加一些严重的泄漏。可能会出现真正的双输局面。
将 URL 缩小到您真正关心的 5 或 12 个,您就可以执行各种服务加载而永远不会注意到点击。
I personally do not like the
ServiceLoader
under any circumstances. It's slow and needlessly wasteful and there is little you can do to optimize it.I also find it a bit limited -- you really have to go out of your way if you want to do more than search by type alone.
xbean-finder's ResourceFinder
Before our attention spans get too short, here's how it can replace a ServiceLoader
This will find all of the
META-INF/services/org.acme.Plugin
implementations in your classpath.Note it does not actually instantiate all the instances. Pick the one(s) you want and you're one
newInstance()
call away from having an instance.Why is this nice?
newInstance()
with proper exception handling? Not hard.Narrowing search scope
If you want to just check specific URLs you can do so easily:
Here, only the 'some.jar' will be searched on any usage of this ResourceFinder instance.
There's also a convenience class called
UrlSet
which can make selecting URLs from the classpath very easy.Alternate "service" styles
Say you wanted to apply the
ServiceLoader
type concept to redesign URL handling and find/load thejava.net.URLStreamHandler
for a specific protocol.Here's how you might layout the services in your classpath:
META-INF/java.net.URLStreamHandler/foo
META-INF/java.net.URLStreamHandler/bar
META-INF/java.net.URLStreamHandler/baz
Where
foo
is a plain text file that contains the name of the service implementation just as before. Now say someone creates afoo://...
URL. We can find the implementation for that quickly, via:Alternate "service" styles 2
Say you wanted to put some configuration information in your service file, so it contains more than just a classname. Here's an alternate style that resolves services to properties files. By convention one key would be the class names and the other keys would be injectable properties.
So here
red
is a properties fileMETA-INF/org.acme.Plugin/red
META-INF/org.acme.Plugin/blue
META-INF/org.acme.Plugin/green
You can look things up similarly as before.
Here's how you could use those properties with
xbean-reflect
, another little library that can give you framework-free IoC. You just give it the class name and some name value pairs and it will construct and inject.Here's how that might look "spelled" out in long form:
The
xbean-reflect
library is a step beyond the built-in JavaBeans API, but a bit better without requiring you to go all the way to a full-on IoC framework like Guice or Spring. It supports factory methods and constructor args and setter/field injection.Why is the ServiceLoader so limited?
Deprecated code in the JVM damages the Java language itself. Many things are trimmed to the bone before being added to the JVM, because you cannot trim them after. The
ServiceLoader
is a prime example of that. The API is limited and OpenJDK implementation is somewhere around 500 lines including javadoc.There's nothing fancy there and replacing it is easy. If it doesn't work for you, don't use it.
Classpath scope
APIs aside, in pure practicality narrowing the scope of the URLs searched is the true solution to this problem. App Servers have quite a lot of URLs all by themselves, not including the jars in your application. Tomcat 7 on OSX for example has about 40~ URLs in the StandardClassLoader alone (this is the parent to all webapp classloaders).
The bigger your app server the longer even a simple search will take.
Caching doesn't help if you intend to search for more than one entry. As well, it can add some bad leaks. Can be a real lose-lose scenario.
Narrow the URLs down to the 5 or 12 that you really care about and you can do all sorts of service loading and never notice the hit.
您是否尝试过使用两个参数版本以便可以指定要使用的类加载器?即,
java.util.ServiceLoader.load(Class, ClassLoader)
Have you tried using the two argument version so that you can specify which classloader to use? Ie,
java.util.ServiceLoader.load(Class, ClassLoader)
亩。
在 1x WebContainer 中 <-> Nx WebApplication 系统中,WebContainer 中实例化的 ServiceLoader 将不会选取 WebApplications 中定义的任何类,而只会选取容器中的类。除了容器中定义的类之外,WebApplication 中实例化的 ServiceLoader 还将检测应用程序中定义的类。
请记住,Web 应用程序需要保持独立,以这种方式设计,如果您尝试规避这一点,事情将会破坏,并且它们不是可用于扩展容器的方法和系统 - 如果您的库是一个简单的Jar,只需将其放入容器的相应扩展文件夹中即可。
Mu.
In a 1x WebContainer <-> Nx WebApplication system, the ServiceLoader instantiated in the WebContainer will not pick up any classes defined in WebApplications, just those in the container. A ServiceLoader instantiated in a WebApplication will detect classes defined in the application in addition to those defined in the container.
Keep in mind WebApplications will need to be kept separate, are designed that way, things will break if you try and circumvent that, and they are not the method and system available to extend the container - if your library is a simple Jar, just drop it into the appropriate extension folder of the container.
我真的很喜欢尼尔在评论中添加的链接中的回答。由于我在最近的项目中也有同样的经历。
“使用 ServiceLoader 时要记住的另一件事是尝试抽象查找机制。发布机制非常好、干净且具有声明性。但是查找(通过 java.util.ServiceLoader)非常丑陋,作为类路径实现如果您将代码放入任何不具有全局可见性的环境(例如 OSGi 或 Java EE),扫描器就会严重崩溃。如果您的代码与之纠缠在一起,那么您将很难在 OSGi 上运行它。稍后最好编写一个抽象,以便在需要时替换。”
我实际上在 OSGi 环境中遇到了这个问题,实际上它只是我们项目中的 eclipse 。但幸运的是我及时修复了它。我的解决方法是使用我想要加载的插件中的一个类,并从中获取 classLoader。这将是一个有效的修复。我没有使用标准的ServiceLoader,但我的过程非常相似,使用属性来定义我需要加载的插件类。我知道还有另一种方法可以了解每个插件的类加载器。但至少我不需要使用它。
老实说,我不喜欢 ServiceLoader 中使用的泛型。因为它限制了一个ServiceLoader只能处理一个接口的类。那么它真的有用吗?在我的实现中,它不会强迫您受到此限制。我只使用加载器的一种实现来加载所有插件类。我不明白使用两个或更多的理由。由于消费者可以从配置文件中了解接口和实现之间的关系。
I really like Neil's answer in the link I added in my comment. Due to I have same experences in my recent project.
"Another thing to bear in mind with ServiceLoader is to try to abstract the lookup mechanism. The publish mechanism is quite nice and clean and declarative. But the lookup (via java.util.ServiceLoader) is as ugly as hell, implemented as a classpath scanner that breaks horribly if you put the code into any environment (such as OSGi or Java EE) that does not have global visibility. If your code gets tangled up with that then you'll have a hard time running it on OSGi later. Better to write an abstraction that you can replace when the time comes."
I actually met this problem in OSGi environment actually it's just eclipse in our project. But I luckily fixed it in a timely fashion. My workaround is using one class from the plugin I want to load ,and get classLoader from it. That will be a valid fix. I didn't use the standard ServiceLoader, But my process is quite similiar, use a properties to define the plugin classes I need to load. And I know there is another way to know each plugin's classloader. But at least I don't need to use that.
Honest, I don't like the generics used in ServiceLoader. Because it limited that one ServiceLoader can only handle classes for one interface. Well is it really useful? In my implementation, it don't force you by this limitation. I just use one implementation of loader to load all the plugin classes. I don't see the reason to use two or more. Due to the consumer can know from the config files about the relationships between interfaces and implementation.
这个问题似乎比我最初预想的要复杂。据我所知
它有 3 种可能的策略来处理 ServiceLoader。
使用静态ServiceLoader实例,仅支持从以下位置加载类
与持有 ServiceLoader 引用的类加载器相同。这
会工作,当
服务配置和实现位于共享类加载器中
并且所有子类加载器都使用相同的实现。例子
文档中的内容面向该用例。
或者
配置和实现被放入每个子类加载器中,
沿着
WEB-INF/lib
中的每个 Web 应用程序部署。在这种情况下,无法在共享类加载器中部署服务
并让每个webapp选择自己的服务实现。
在每次访问时初始化 ServiceLoader,传递上下文类加载器
当前线程作为第二个参数。 JAXP 采用了这种方法
和 JAXB api,尽管它们使用自己的 FactoryFinder 实现
而不是 ServiceLoader。因此可以将 xml 解析器与 web 应用程序捆绑在一起
并让它自动被
DocumentBuilderFactory#newInstance
拾取。此查找具有性能影响,但在 xml 解析的情况下
与查找实现所需的时间相比,查找实现的时间很短
实际上解析一个xml文档。在图书馆里我想象着工厂
本身非常简单,因此查找时间将主导性能。
以某种方式缓存以上下文类加载器为键的实现类。
我不完全确定在上述所有情况下这是否可能
导致任何内存泄漏。
总之,我可能会忽略这个问题并要求图书馆
部署在每个 Web 应用程序内,即上面的选项 1b。
This question seem to be more complicated than I first anticipated. As I see
it, there are 3 possible strategies for dealing with ServiceLoaders.
Use a static ServiceLoader instance and only support loading classes from
the same classloader as the one holding the ServiceLoader reference. This
would work when
The service configuration and implementation are in a shared classloader
and all child classloaders are using the same implementation. The example
in the documentation is geared towards theis use case.
Or
Configuration and implementation are put into each child classloader and
deployed along each webapp in
WEB-INF/lib
.In this scenario it is not possible to deploy the service in a shared classloader
and let each webapp choose its own service implementation.
Initialize the ServiceLoader on each access passing the context classloader of
the current thread as the second parameter. This approach is taken be the JAXP
and JAXB apis, although they are using their own FactoryFinder implementation
instead of ServiceLoader. So it is possible to bundle a xml parser with a webapp
and have it automatically get picked up for example by
DocumentBuilderFactory#newInstance
.This lookup has a performance impact, but in the case of xml parsing
the time to look up the implementation is small compared to the time needed to
actually parse a xml document. In the library I'm envisioning the factory
itself is pretty simple so the lookup time would dominate the performance.
Somehow cache the implementation class with the context classloader as the key.
I'm not entirely sure if this is possible in all the above cases without
causing any memory leaks.
In conclusion, I will probably be ignoring this problem and require that the library
gets deployed inside each webapp, i.e. option 1b above.