具有多个类加载器的 Java ServiceLoader

发布于 2024-11-29 15:30:18 字数 788 浏览 1 评论 0原文

在以下环境中使用 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

白衬杉格子梦 2024-12-06 15:30:18

我个人在任何情况下都不喜欢 ServiceLoader。它很慢并且不必要地浪费,而且您几乎无法对其进行优化。

我还发现它有点有限——如果你想做的不仅仅是按类型搜索,你真的必须不遗余力。

xbean-finder 的 ResourceFinder

  • ResourceFinder 是一个独立的 java 文件,能够替代 ServiceLoader 的使用。复制/粘贴重复使用没有问题。它是一个 java 文件,获得 ASL 2.0 许可并可从 Apache 获取。

在我们的注意力太短之前,先介绍一下它如何替换 ServiceLoader

ResourceFinder finder = new ResourceFinder("META-INF/services/");
List<Class<? extends Plugin>> impls = finder.findAllImplementations(Plugin.class);

这将在类路径中找到所有 META-INF/services/org.acme.Plugin 实现。

请注意,它实际上并没有实例化所有实例。选择您想要的实例,只需调用一次 newInstance() 即可获得实例。

为什么这很好?

  • 调用 newInstance() 并进行适当的异常处理有多难?不难。
  • 能够自由地仅实例化您想要的实例是很好的。
  • 现在您可以支持构造函数参数了!

缩小搜索范围

如果您只想检查特定的 URL,您可以轻松做到这一点:

URL url = new File("some.jar").toURI().toURL();
ResourceFinder finder = new ResourceFinder("META-INF/services/", url);

在这里,在使用此 ResourceFinder 实例时,只会搜索“some.jar”。

还有一个名为 UrlSet 的便利类,它可以使从类路径中选择 URL 变得非常容易。

ClassLoader webAppClassLoader = Thread.currentThread().getContextClassLoader(); 
UrlSet urlSet = new UrlSet(webAppClassLoader);
urlSet = urlSet.exclude(webAppClassLoader.getParent());
urlSet = urlSet.matching(".*acme-.*.jar");

List<URL> urls = urlSet.getUrls();

替代“服务”样式

假设您想要应用 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。我们可以通过以下方式快速找到实现:

ResourceFinder finder = new ResourceFinder("META-INF/");
Map<String, Class<? extends URLStreamHandler>> handlers = finder.mapAllImplementations(URLStreamHandler.class);
Class<? extends URLStreamHandler> fooHandler = handlers.get("foo");

备用“服务”样式 2

假设您想将一些配置信息放入服务文件中,因此它不仅仅包含类名。这是将服务解析为属性文件的替代样式。按照惯例,一个键是类名,其他键是可注入属性。

所以这里 red 是一个属性文件

  • META-INF/org.acme.Plugin/red
  • META-INF/org.acme.Plugin/blue
  • META-INF/org.acme.Plugin/green

您可以像以前一样查找内容。

ResourceFinder finder = new ResourceFinder("META-INF/");

Map<String,Properties> plugins = finder.mapAllProperties(Plugin.class.getName());
Properties redDefinition = plugins.get("red");

以下是如何将这些属性与 xbean-reflect 一起使用,xbean-reflect 是另一个可以为您提供无框架 IoC 的小库。您只需给它类名和一些名称值对,它就会构造并注入。

ObjectRecipe recipe = new ObjectRecipe(redDefinition.remove("className").toString());
recipe.setAllProperties(redDefinition);

Plugin red = (Plugin) recipe.create();
red.start();

下面是长形式的“拼写”:

ObjectRecipe recipe = new ObjectRecipe("com.example.plugins.RedPlugin");
recipe.setProperty("myDateField","2011-08-29");
recipe.setProperty("myIntField","100");
recipe.setProperty("myBooleanField","true");
recipe.setProperty("myUrlField","http://www.stackoverflow.com");
Plugin red = (Plugin) recipe.create();
red.start();

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

  • ResourceFinder is a self-contained java file capable of replacing ServiceLoader usage. Copy/paste reuse is no problem. It's one java file and is ASL 2.0 licensed and available from Apache.

Before our attention spans get too short, here's how it can replace a ServiceLoader

ResourceFinder finder = new ResourceFinder("META-INF/services/");
List<Class<? extends Plugin>> impls = finder.findAllImplementations(Plugin.class);

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?

  • How hard is it to call newInstance() with proper exception handling? Not hard.
  • Having the freedom to instantiate only the ones you want is nice.
  • Now you can support constructor args!

Narrowing search scope

If you want to just check specific URLs you can do so easily:

URL url = new File("some.jar").toURI().toURL();
ResourceFinder finder = new ResourceFinder("META-INF/services/", url);

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.

ClassLoader webAppClassLoader = Thread.currentThread().getContextClassLoader(); 
UrlSet urlSet = new UrlSet(webAppClassLoader);
urlSet = urlSet.exclude(webAppClassLoader.getParent());
urlSet = urlSet.matching(".*acme-.*.jar");

List<URL> urls = urlSet.getUrls();

Alternate "service" styles

Say you wanted to apply the ServiceLoader type concept to redesign URL handling and find/load the java.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 a foo://... URL. We can find the implementation for that quickly, via:

ResourceFinder finder = new ResourceFinder("META-INF/");
Map<String, Class<? extends URLStreamHandler>> handlers = finder.mapAllImplementations(URLStreamHandler.class);
Class<? extends URLStreamHandler> fooHandler = handlers.get("foo");

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 file

  • META-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.

ResourceFinder finder = new ResourceFinder("META-INF/");

Map<String,Properties> plugins = finder.mapAllProperties(Plugin.class.getName());
Properties redDefinition = plugins.get("red");

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.

ObjectRecipe recipe = new ObjectRecipe(redDefinition.remove("className").toString());
recipe.setAllProperties(redDefinition);

Plugin red = (Plugin) recipe.create();
red.start();

Here's how that might look "spelled" out in long form:

ObjectRecipe recipe = new ObjectRecipe("com.example.plugins.RedPlugin");
recipe.setProperty("myDateField","2011-08-29");
recipe.setProperty("myIntField","100");
recipe.setProperty("myBooleanField","true");
recipe.setProperty("myUrlField","http://www.stackoverflow.com");
Plugin red = (Plugin) recipe.create();
red.start();

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.

九局 2024-12-06 15:30:18

您是否尝试过使用两个参数版本以便可以指定要使用的类加载器?即,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)

当爱已成负担 2024-12-06 15:30:18

亩。

在 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.

阳光下的泡沫是彩色的 2024-12-06 15:30:18

我真的很喜欢尼尔在评论中添加的链接中的回答。由于我在最近的项目中也有同样的经历。

“使用 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.

最好是你 2024-12-06 15:30:18

这个问题似乎比我最初预想的要复杂。据我所知
它有 3 种可能的策略来处理 ServiceLoader。

  1. 使用静态ServiceLoader实例,仅支持从以下位置加载类
    与持有 ServiceLoader 引用的类加载器相同。这
    会工作,当

    • 服务配置和实现位于共享类加载器中
      并且所有子类加载器都使用相同的实现。例子
      文档中的内容面向该用例。

      或者

    • 配置和实现被放入每个子类加载器中,
      沿着 WEB-INF/lib 中的每个 Web 应用程序部署。

    在这种情况下,无法在共享类加载器中部署服务
    并让每个webapp选择自己的服务实现。

  2. 在每次访问时初始化 ServiceLoader,传递上下文类加载器
    当前线程作为第二个参数。 JAXP 采用了这种方法
    和 JAXB api,尽管它们使用自己的 FactoryFinder 实现
    而不是 ServiceLoader。因此可以将 xml 解析器与 web 应用程序捆绑在一起
    并让它自动被 DocumentBuilderFactory#newInstance 拾取。

    此查找具有性能影响,但在 xml 解析的情况下
    与查找实现所需的时间相比,查找实现的时间很短
    实际上解析一个xml文档。在图书馆里我想象着工厂
    本身非常简单,因此查找时间将主导性能。

  3. 以某种方式缓存以上下文类加载器为键的实现类。
    我不完全确定在上述所有情况下这是否可能
    导致任何内存泄漏。

总之,我可能会忽略这个问题并要求图书馆
部署在每个 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.

  1. 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.

  2. 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.

  3. 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文