随笔记录-spring.factories

在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虚拟机中。

引用地址:ClassLoader——JAVA成长之路

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类的方式实现自己的类加载器,以满足一些特殊的需求。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值