具有多个类加载器的Java ServiceLoader [英] Java ServiceLoader with multiple Classloaders

查看:135
本文介绍了具有多个类加载器的Java ServiceLoader的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

使用 ServiceLoader <的最佳做法是什么? / a>在具有多个ClassLoader的环境中?文档建议在初始化时创建并保存单个服务实例:

  private static ServiceLoader< CodecSet> codecSetLoader = ServiceLoader.load(CodecSet.class); 

这将使用当前的上下文类加载器初始化ServiceLoader。现在假设这个片段包含在使用Web容器中的共享类加载器加载的类中,并且多个Web应用程序想要定义自己的服务实现。这些不会在上面的代码中被提取,甚至可能使用第一个webapps上下文类加载器初始化加载器并向其他用户提供错误的实现。



始终创建新的ServiceLoader似乎是浪费性能,因为它必须每次枚举和解析服务文件。 编辑:这甚至可能是一个很大的性能问题,如关于java的XPath实现的这个答案



其他库如何处理这个问题?他们是否为每个类加载器缓存实现,他们是否每次都重新分析他们的配置,或者他们只是忽略这个问题而只适用于一个类加载器?

解决方案

在任何情况下,我个人都不喜欢 ServiceLoader 。这是缓慢且不必要的浪费,你几乎无法做到优化它。



我也发现它有点受限 - 你真的不得不走开路你想做的不仅仅是按类型搜索。



xbean-finder的ResourceFinder




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



在我们的注意力跨度太短之前,以下是它如何取代ServiceLoader

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

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



注意它实际上并没有实例化所有实例。选择你想要的那个,你就是一个 newInstance()打电话给一个实例。



为什么这很好?




  • 调用 newInstance()有多难有适当的异常处理?并不难。

  • 可以自由地仅实例化你想要的那些。

  • 现在你可以支持构造函数args了!



缩小搜索范围



如果您只想查看特定网址,您可以轻松完成:

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

此处,只有在使用此ResourceFinder实例时才会搜索some.jar。 / p>

还有一个名为 UrlSet 的便利类,可以很容易地从类路径中选择URL。

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

列表< 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<?扩展URLStreamHandler>> handlers = finder.mapAllImplementations(URLStreamHandler.class);
等级<?扩展URLStreamHandler> fooHandler = handlers.get(foo);



备用服务样式2



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



所以这里红色是属性文件




  • 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());
属性redDefinition = plugins.get(red);

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

  ObjectRecipe recipe = new ObjectRecipe(redDefinition卸下摆臂( 类名)的toString())。 
recipe.setAllProperties(redDefinition);

插件red =(插件)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);
插件red =(插件)recipe.create();
red.start();

xbean-reflect 库是一步超出内置的JavaBeans API,但更好一点,而不需要你一直到像Guice或Spring这样的全功能IoC框架。它支持工厂方法和构造函数args和setter / field injection。



为什么ServiceLoader如此受限?



JVM中不推荐使用的代码会破坏Java语言本身。在添加到JVM之前,很多东西都会修剪到骨骼中,因为之后无法修剪它们。 ServiceLoader 就是一个很好的例子。 API是有限的,OpenJDK实现大约500行,包括javadoc。



没有什么花哨的东西,替换它很容易。如果它不适合你,请不要使用它。



类路径范围



除了API,纯粹的实用性缩小搜索到的URL的范围是这个问题的真正解决方案。应用服务器本身拥有相当多的URL,不包括应用程序中的jar。例如,OSX上的Tomcat 7仅在StandardClassLoader中有大约40个URL(这是所有webapp类加载器的父级)。



应用服务器越大,即使是简单搜索将采用。



如果您打算搜索多个条目,缓存无效。同样,它可以添加一些不良泄漏。可能是一个真正的双输局面。



将您真正关心的网址缩小到5或12,您可以进行各种服务加载而不会注意到点击。


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?

解决方案

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.

这篇关于具有多个类加载器的Java ServiceLoader的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆