在具有多个ClassLoader的环境中使用ServiceLoader的最佳做法是什么?文档建议在初始化时创建并保存单个服务实例:
private static ServiceLoader<CodecSet> codecSetLoader = ServiceLoader.load(CodecSet.class);
这将使用当前上下文类加载器初始化ServiceLoader。现在假设此片段包含在使用Web容器中的共享类加载器加载的类中,并且多个Web应用程序想要定义自己的服务实现。这些不会在上面的代码中被提取,甚至可能使用第一个webapps上下文类加载器初始化加载器并向其他用户提供错误的实现。
始终创建新的ServiceLoader似乎是浪费性能,因为它必须每次枚举和解析服务文件。 修改:这可能是一个很大的性能问题,如this answer regarding java's XPath implementation所示。
其他图书馆如何处理这个问题?他们是否缓存每个类加载器的实现,他们是否每次都重新分析它们的配置,还是只是忽略了这个问题而只适用于一个类加载器?
答案 0 :(得分:58)
在任何情况下,我个人都不喜欢ServiceLoader
。这是缓慢且不必要的浪费,你几乎无法做到优化它。
我也发现它有点受限 - 如果你想做的不仅仅是按类型搜索,你真的不得不走开。
在我们的注意力范围太短之前,以下是它如何取代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 = 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
类型概念来重新设计网址处理,并为特定协议查找/加载java.net.URLStreamHandler
。
以下是在类路径中布局服务的方法:
META-INF/java.net.URLStreamHandler/foo
META-INF/java.net.URLStreamHandler/bar
META-INF/java.net.URLStreamHandler/baz
其中foo
是一个纯文本文件,其中包含与以前一样的服务实现的名称。现在说某人创建了一个foo://...
网址。我们可以通过以下方式快速找到实施方案:
ResourceFinder finder = new ResourceFinder("META-INF/");
Map<String, Class<? extends URLStreamHandler>> handlers = finder.mapAllImplementations(URLStreamHandler.class);
Class<? extends URLStreamHandler> fooHandler = handlers.get("foo");
假设您想在服务文件中放置一些配置信息,因此它不仅包含一个类名。这是一种将服务解析为属性文件的替代样式。按照惯例,一个键是类名,其他键是可注入的属性。
所以这里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
一起使用,这是另一个可以为您提供无框架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,但更好一点,不需要你一直到像Guice或Spring这样的全面的IoC框架。它支持工厂方法和构造函数args以及setter / field injection。
JVM中不推荐使用的代码会破坏Java语言本身。在添加到JVM之前,很多东西都会修剪到骨骼中,因为之后无法修剪它们。 ServiceLoader
就是一个很好的例子。 API是有限的,OpenJDK实现大约500行,包括javadoc。
那里没有什么花哨的东西,取而代之的很容易。如果它不适合您,请不要使用它。
即使是简单的搜索,您的应用服务器也越大。
如果您打算搜索多个条目,缓存无济于事。同样,它可以添加一些不良泄漏。可能是一个真正的双输局面。
将您真正关心的网址缩小到5或12,您可以进行各种服务加载,从不会注意到该网页。
答案 1 :(得分:5)
您是否尝试过使用这两个参数版本,以便指定要使用的类加载器?即,java.util.ServiceLoader.load(Class, ClassLoader)
答案 2 :(得分:4)
亩。
在1x WebContainer&lt; - &gt;中在Nx WebApplication系统中,在WebContainer中实例化的ServiceLoader将不会获取WebApplications中定义的任何类,只是容器中的那些类。在WebApplication中实例化的ServiceLoader将检测应用程序中定义的类以及容器中定义的类。
请记住,WebApplications需要保持独立,按照这种方式设计,如果你试图绕过它, 会破坏它们,并且它们不是扩展容器的方法和系统 - 如果您的库是一个简单的Jar,只需将其放入容器的相应扩展文件夹中即可。
答案 3 :(得分:3)
我非常喜欢Neil在我评论中添加的链接中的答案。由于我在最近的项目中有相同的经历。
“ServiceLoader要记住的另一件事是尝试抽象查找机制。发布机制非常好,干净且声明性。但查找(通过java.util.ServiceLoader)和地狱一样丑陋,实现作为一个类路径扫描程序,如果你把代码放到没有全局可见性的任何环境(如OSGi或Java EE)中,可能会破坏性。如果你的代码与之纠缠在一起,那么你将很难在OSGi上运行它后来。最好写一个抽象,你可以在时机成熟时取而代之。“
我实际上在OSGi环境中遇到了这个问题,实际上它只是我们项目中的eclipse。但我很幸运地及时修复了它。我的解决方法是使用我想要加载的插件中的一个类,并从中获取classLoader。这将是一个有效的解决方案。我没有使用标准的ServiceLoader,但我的过程非常相似,使用属性来定义我需要加载的插件类。我知道还有另一种方法可以了解每个插件的类加载器。但至少我不需要使用它。
老实说,我不喜欢ServiceLoader中使用的泛型。因为它限制了一个ServiceLoader只能处理一个接口的类。它真的有用吗?在我的实现中,它不会强迫您受此限制。我只是使用一个加载器实现来加载所有的插件类。我没有看到使用两个或更多的原因。由于消费者可以从配置文件中了解接口和实现之间的关系。
答案 4 :(得分:1)
这个问题似乎比我最初预料的要复杂得多。我看到了 它,有3种可能的策略来处理ServiceLoaders。
使用静态ServiceLoader实例,仅支持从中加载类 与持有ServiceLoader引用的类加载器相同的类加载器。这个
服务配置和实现位于共享的类加载器中 并且所有子类加载器都使用相同的实现。这个例子 在文档中面向着用例。
或
将配置和实现放入每个子类加载器中
在WEB-INF/lib
。
在此方案中,无法在共享类加载器中部署服务 并让每个webapp选择自己的服务实现。
在每次访问时初始化ServiceLoader,传递上下文类加载器
当前线程作为第二个参数。这种方法是JAXP
和JAXB apis,虽然他们使用自己的FactoryFinder实现
而不是ServiceLoader。因此可以将xml解析器与webapp捆绑在一起
并自动获取它,例如DocumentBuilderFactory#newInstance
。
此查找具有performance impact,但是在xml解析的情况下 与需要的时间相比,查找实施的时间很短 实际上解析一个xml文档。在图书馆,我正在设想工厂 本身非常简单,因此查找时间将主导性能。
以某种方式使用上下文类加载器作为键来缓存实现类。 我不完全确定在没有这些情况下是否有可能 导致任何内存泄漏。
总之,我可能会忽略这个问题并要求库 部署在每个webapp中,即上面的选项1b。