现在Spring boot来到了Jar世界,原来它是管理war世界的王者,所以,他也很可能带来了Jar打包规则的改变。Jar世界从OSGI到Java Service Loader以及Java 9的模块化,折腾了很长时间,现在看看Spring是如何折腾Jar包的动态加载的:
控制反转IOC不仅可以在类级别进行,而且可以在模块级别进行,虽然OSGi技术已经存世了很长时间了,但是,在Java中也可以像在Spring中一般都可以直接使用IoC方法来实现类似OSGI方式。
Java Service Loader
这是来自JDK的开箱即用的Java API,它提供了一种特定方式的控制反转IOC,由Service Loader类实现。它用来定位类路径上的某个接口的实现类。这种方式能够让我们在Java运行时动态发现类路径classpath上指定接口的某个实现,动态工厂加载模式,这样就分离了API模块和它的多个具体实现模块类,也就是说,同一套API接口,我们后面可以给予它不同的实现方式,这种接口和实现的清晰分离一直是Java生态最强的优势。
日志框架SLF4J就选择了这条路径。SLF4J本身就是API,但可以使用不同的实现(如Logback,Log4J等),SLF4J客户端仅与SLF4J API这个抽象接口模块交互,而放置在类路径上的不同的实现模块在运行时能够以不同方式处理细节。
Service Loader有一个约束,需要Jar包里存在一个目录META-INF/services目录,在这个目录里有一个文件,文件的名称完全是接口的完整路径名称,而其内容则是指定接口的实现类完整路径名称。例如,对于接口com.Foo,必须有一个名为META-INF/services/com.Foo文件,文件内容如下所示的文件:
com.FooImpl1
com.FooImpl2
上面的类都必须实现com.Foo接口。
从代码的角度来看,它非常简单:
ServiceLoader<Foo> loader = ServiceLoader.load(Foo.class);
loader.iterator();
上面代码获得loader是一个集合类,需要遍历一次。
Service Loader与Spring集成
我们可以通过工厂bean将上面ServiceLoader与Spring集成,例如以下代码:
@Configuration
public class ServiceConfiguration {
@Bean
public ServiceListFactoryBean serviceListFactoryBean() {
ServiceListFactoryBean serviceListFactoryBean = new ServiceListFactoryBean();
serviceListFactoryBean.setServiceType(Foo.class);
return serviceListFactoryBean;
}
}
Object object = serviceListFactoryBean.getObject();
显然,这需要进一步的操作才能以正确的形式获取数据(提示:它是一个链表)。
Spring Factories Loader
与Java Service Loader一样,Spring提供了另一个反转控制的实现,但是只涉及一个属性文件,它必须被命名spring.factories并位于Jar其下META-INF目录下。从代码的角度来看,该文件是通过SpringFactoriesLoader.loadFactories() 这个静态方法读取的- 是的,对于Spring来说,它非常强悍。
客户端代码无法更简单:
List<Foo> foos = SpringFactoriesLoader.loadFactories(Foo.class, null);
注意第二个参数是可选的类加载器。
与Java Service Loader相比,差异有两方面:
1. 一种文件格式是否比其他文件格式更好?更可读或更易于维护,这也许是个人品味的问题。
2. spring.factories的关键是不需要接口,也不需要实现子类来实现它。Spring Boot就是使用这种方法来处理自动配置bean:关键就只是一个注释,即 org.springframework.boot.autoconfigure.EnableAutoConfiguration,值是在类上面的注解@Configuration中写明的,这能够有更灵活的设计。
以上完整代码: github