在src/main/resource文件下,可以新建META-INF文件,并新建spring.factories文件,可以实现让spring扫描包扫描不到的bean对象实例化。
准备工作
新建了一个父级项目test_factories,下面新建了两个module,分别是use_factory,provider_factory,在use_factory的pom.xml中引入provider_factory的依赖。
在provider_factory中写一个监听器:
@Slf4j
public class SelfDefinedListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
public SelfDefinedListener() {
log.info("自定义的监听器实例化");
}
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
Environment env = event.getEnvironment();
log.info("env= {}",env);
}
}
use_factory中直接调用SelfDefinedListener
然后在use_factory中随便写一个访问方法:
@RestController
@RequestMapping("/test")
public class TestWeb {
private SelfDefinedListener self;
@GetMapping("/test1")
public String test(){
self.test();
return "调用结束";
}
}
可以明显是为self对象是null的。因为没有任何地方给这个对象有实例化的过程。
SelfDefinedListener使用@Component注解
如果直接在use_factory中直接使用@Autowired注解是没用的,因为SelfDefinedListener不在bean管理器中,会找不到类型报错。需要在provider_factory使用@Component注解,并启动use_factory(只会启动这个项目,不会启动provider_factory)
可以看到在控制台中有打印信息,说明调用了构造函数开始了实例化,再次调用user_factory中的访问方法。
页面显示正常。
使用spring.factories
把@Compoent和@Autowired注解去掉,在use_factory中新建META-INF/spring.factories文件,如下图:
然后启动use_factory:
可以看到不仅打印了构造函数的日志,而且还打印了onApplicationEvent方法的内容。如果构造函数的日志打印了两边,可能是因为用了spring-boot-devtools热部署,直接在pom.xml中注释掉就行。
使用@Component和spring.factories的区别
明显可以看到的主要区别就是加载顺序问题,@Component打印的构造函数的日志是在启动use_factory之后打印的;但是使用spring.factories则是在打印use_factory启动日志之前就出来了。
SpringFactoriesLoader读取spring.factories
SpringFactoriesLoader类就是专门去读取META-INF/spring.factories文件的, 截取一段源码:
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
return result;
}
result = new HashMap<>();
try {
Enumeration<URL> urls = classLoader.getResources('META-INF/spring.factories');
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
for (String factoryImplementationName : factoryImplementationNames) {
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}
// Replace all lists with unmodifiable lists containing unique elements
result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
cache.put(classLoader, result);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
return result;
}
仿照这段代码自己试了下,确实读取到了:
test.factories中的内容是:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=/com.example.TestProject.test2022.day0723.TestConfigurationBean
SpringFactoriesLoader类的loadSpringFactories方法中的ClassLoader参数
ClassLoader就是类加载器,作用就是将java类加载到jvm虚拟机中。
Java 中的类加载器大致可以分成两类,一类是系统提供的,另外一类则是由 Java 应用开发人员编写的。系统提供的类加载器主要有下面三个:
引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自
java.lang.ClassLoader。
扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java
虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java
类。一般来说,Java 应用的类都是由它来完成加载的。可以通过
ClassLoader.getSystemClassLoader()来获取它。
除了系统提供的类加载器以外,开发人员可以通过继承 java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。