一则面试经历
多年前,楼主之前在面试时,架构师问了一个问题,当时有点没想明白,这里分享下:
如何在Spring中,引用外部容器的Bean对象(好像是这么问的)
或者说Spring如何来管理外部容器的Bean
举个例子:
假设你的工程是SpringBoot启动,启动类包名是 com.springboot.test
所以你的Spring容器里的管理的Bean默认是 com.springboot.test 这个包扫描下的
假设你引用了第三方jar包,这个是外部容器,它的命名例如是 demo.test,不是在com.springboot.test 这个包下的,也就是不会被扫描到的
但是我们在使用第三方jar里面的方法时,是直接拿来用的,使用姿势如下:
@Autowired
private TestUtil testUtil;
testUtil.methodOne()
这里就有一个问题,既然我第三方包名是demo.test,不是在com.springboot.test下的,那么三方包下面的TestUtil类也不会被Spring容器扫描管理,所以我testUtil肯定是空指针,会失败的
那么这个问题是怎么解决的??
从面试经历谈SpringBoot自动装配
何为自动装配
假设我们一个项目要连接Mysql,使用Mybatis,使用Redis等
如果纯手动自己写代码,要搞很多花里胡哨的东西,配置数据库连接啊,连接池,Redis方法封装啊各自
但是我们真正在开发过程中,就直接导入了一个 ***stater**包完事,里面封装了各种方式
例如: springboot-redis-starter
springboot-mybatis-starter
springboot-mybatis-starter
然后直接用里面的方法完事:
例如想redis存储数据,使用姿势如下
@Autowired
private RedisUtil redisUtil
redisUtil.set(key,value)
也就是我们通过注解或者一些简单的配置就能在SpringBoot的帮助下实现某块功能,这就是自动装配,就像我引入了springboot-redis-starter,配置下redis连接信息,整点注解,直接用里面的方法即可
再谈面试经历
上面说了自动装配过程中,我们引入了一些springboot-redis-starter 这种包,里面的RedisUtil我们是毫不客气拿来即用,
再结合上述面试经历看
springboot-redis-starter包名是 *srpingboot.redis**,那它里面的RedisUtil
又是怎么被我的Spring容器管理的?为什么我拿来即用,没有空指针?
专业点讲即是:文件跨模块实例化
META-INF/spring.factories庐山真面目
如果你是一个爱看源码的同学,你应该对这个文件有一定印象
它一般会出现在各种**springboot-*-starter* 源码包中,或者公司架构师封装的开箱即用工具包中
目录src/main/resources/META-INF/**下
也简单总结下基本特点
spring.factories 的文件内容就是接口对应其实现类,实现类可以有多个**
文件内容必须是kv形式,即properties类型**
一个接口可以有多个实现类,注意格式
META-INF/spring.factories原理解析
我们自身搭建一个SpringBoot工程,因为一个 **springboot-starter**,然后Debug看下源码流程
关于SpringBoot源码启动流程这里不多逼逼了,这里直接上干货,进入主流程
Debug顺序:
启动类@SpringBootApplication-->
复合注解@EnableAutoConfiguration-->
@Import(EnableAutoConfigurationImportSelector.class)-->
AutoConfigurationImportSelector.selectImports()-->
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);-->
!!! 风暴已经出现!!! 源码看到了这里马上就出现一个重要的类了SpringFactoriesLoader
以及其对应方法,我们继续深入看看这个类,和这个方法做什么了
SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
// spring.factories文件的扫描位置
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
try {
// 通过类加载器,获取spring.factories文件
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List<String> result = new ArrayList<String>();
// 开始对spring.factories文件读了
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
//获取里面的配置信息
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
"] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
这里可以看我的Debug数据部分截图
这里可以看出扫描了我**druid** 包下的spring.factories文件,并且获取了org.springframework.boot.autoconfigure.EnableAutoConfiguration 这个key对应的配置值,也就是需要加载的bean
或者我直白点:公司架构师写了一个第三方包,com.demo.TestUtil
现在他想把这个TestUtil给我Spring加载进来
就会把TestUtil的信息 放入spring.factories文件里
这样就会被spring里面的SpringFactoriesLoader文件给读取到了,之后******
至于后续Spring又是如何加载进去的,那就是另一个点了,有兴趣的同学可以去了解一下Spring bean的几种注入方法,关键词(这里不多逼逼了)
@Import注解,
ImportBeanDefinitionRegistrar
ImportSelector
此处的注入亦是核心入口类AutoConfigurationImportSelector.selectImports()进来的
spring.factories 总结
作用总结
SpringBoot在包扫描时,并不会扫描子模块下的内容,这样就使得我们的其他模块中的Bean无法注入到Spring容器中,SpringBoot为我们提供了spring.factories这个文件,这个文件可以帮助我们将其他模块的Bean注入到我们的Spring容器中
举例:我们引入第三方jar包时,如 druid-starter,redis-starter,将我们需要注入的bean 写入spring.factories文件中,这样SpringBoot会去扫描spring.factories中的信息,将bean进行加载
这就就回答了,上述的问题,即使我们的模块例如是com.test.demo,Spring是不会自动扫描注入第三方模块如demo.util包下的bean的,可以通过此实现跨模块实例化
原理总结
1 Springboot启动时,启动类上的@SpringBootApplication复合注解EnableAutoConfiguration继承了EnableAutoConfigurationImportSelector,AutoConfigurationImportSelector
2 AutoConfigurationImportSelector类中核心方法selectImports()中,会去扫描当前Spring需要加载的Bean对象信息
3 Spring提供了核心类SpringFactoriesLoader,
这个类的主要作用是去扫描jar下META-INF/spring.factories文件,并加载spring.factories文件中,key为EnableAutoConfiguration对应的value值,即外部三方需要加载的bean类信息
4 SpringFactoriesLoader将扫描到的信息返回给AutoConfigurationImportSelector之后通过Spring进行实例化
写在后面
资质有限,难免有误,还望靓仔不吝赐教