一、什么是自动装配
在使用SpringBoot的时候,会自动将Bean装配到IoC容器中。例如我们在使用Redis数据库的时候,会引入依赖spring-boot-starter-data-redis。在引入这个依赖后,服务初始化的时候,会将操作Redis需要的组件注入到IoC容器中进行后续使用
自动装配大致过程如下:
- 1)通过注解@SpringBootApplication=>@EnableAutoConfiguration=>@Import({AutoConfigurationImportSelector.class})实现自动装配
- 2)AutoConfigurationImportSelector类中重写了ImportSelector中selectImports方法,批量返回需要装配的配置类
- 3)通过Spring提供的SpringFactoriesLoader机制,扫描classpath下的META-INF/spring.factories文件,读取需要自动装配的配置类
- 4)依据条件筛选的方式,把不符合的配置类移除掉,最终完成自动装配
具体细节可以参考以下两篇文章:
https://baijiahao.baidu.com/s?id=1725265949551075777&wfr=spider&for=pc
https://blog.youkuaiyun.com/weixin_41979002/article/details/119378858
二、自动装配的应用
我们看了springboot的源码后,我们知道了什么是自动装配及其原理。但光知道原理还不行,还需要知道如何使用。
2.1 直接使用springboot自动装配好的配置类
springboot为我们装配好的配置类有哪些呢?答案就在spring.factories文件里:
可以看到,这个文件写了很多配置类,springboot为了方便我们使用,已经事先把许多常用的配置类准备好,当我们具体需要使用哪个功能配置时,再具体将其依赖引入即可。
下面以RedisAutoConfiguration这个自动配置类为例讲解,看下图:
这个配置类是springboot事先提供的,也放在spring-boot-autoconfigure-xxx.RELEASE.jar这个依赖下,这个配置类为我们装备两个bean,分别为redisTemplate和stringRedisTemplate,原代码如下:
package org.springframework.boot.autoconfigure.data.redis;
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
那么问题来了,这个配置类已经存在了,那么项目里是否就可以直接使用Redis相关的功能了吗?
答案是,不能。
为啥呢?且看下面,慢慢道来…
我们可以看到,每个自动配置类都会有下面这行代码:
@ConditionalOnClass(RedisOperations.class)
其意思,就是如果这个RedisOperations类存在,则这个配置类生效。
细心的同学看过源码都会发现,看上面源码截图,有些类是显示红色的(有些idea不会显示红色),如RedisOperations、RedisTemplate…
而且点击不进去,也看不到源码,说明我们的项目里这些类是不存在的,所以我们这个自动配置类是无效的,简言之,就是一个空壳。springboot为我们提前准备好的一系列自动配置类,大多数都是空壳,不生效的。
那么我们如何能使得RedisAutoConfiguration 这个自动配置类生效呢?(这就是应用了)
答案是,将相关的自动配置类引入即可,在pom文件里加入:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
我们再从头看RedisAutoConfiguration 的源码:
且看,之前飘红的类已经不红了,而且也能点进去查看源码,说明这个配置类生效了。redis的相关功能,我们也可以正常使用了,这就是自动装配的应用。
2.2 自定义的配置类手动装配
有些时候,由于业务需求的原因,我们会产生许多公共的组件,最具代表性的,如系统操作记录的日志收集。
做日志收集时,我们一般都会应用aop切面编程。如果你这部分aop的代码不是很复杂,也许不需要抽取为公共的。
但如果这个日志业务及其复杂,而且很多个微服务项目也要用到,如果我们不想将这部分代码在每个微服务项目都拷贝一份,那我们就可以将这部分代码抽出为一个独立的项目组件,并将其打包成为一个jar依赖,放到我们的仓库,供其他项目引入并使用。
但我们完成这部分切面代码的打包后,并在其他项目引入后,你会发现,这些aop切面代码是不生效的
如这篇文章:https://blog.youkuaiyun.com/whj826069748/article/details/84068201 也说到了这个问题,并有相应的解决方案。需要在引用模块的启动类中加入扫描的代码:
@ComponentScan(basePackages = {"xxx.xxx.*"})
为什么要这么做呢?
原因是,我们的切面代码,有一些类是需要装配到ioc容器中才能使用的,如下代码:
/**
* @ClassName MyAspect
* @Description TODO
* @Author Oneby
* @Date 2021/1/22 11:27
* @Version 1.0
*/
@Aspect
@Component
public class MyAspect {
@Before("execution(public int com.heygo.spring.aop.CalcServiceImpl.*(..))")
public void beforeNotify() {
System.out.println("******** @Before我是前置通知MyAspect");
}
@After("execution(public int com.heygo.spring.aop.CalcServiceImpl.*(..))")
public void afterNotify() {
System.out.println("******** @After我是后置通知");
}
@AfterReturning("execution(public int com.heygo.spring.aop.CalcServiceImpl.*(..))")
public void afterReturningNotify() {
System.out.println("********@AfterReturning我是返回后通知");
}
@AfterThrowing("execution(public int com.heygo.spring.aop.CalcServiceImpl.*(..))")
public void afterThrowingNotify() {
System.out.println("********@AfterThrowing我是异常通知");
}
@Around("execution(public int com.heygo.spring.aop.CalcServiceImpl.*(..))")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
Object retValue = null;
System.out.println("我是环绕通知之前AAA");
retValue = proceedingJoinPoint.proceed();
System.out.println("我是环绕通知之后BBB");
return retValue;
}
}
这是一个简单的切面类,它需要装配到ioc容器里才能生效,但由于其是外部依赖jar引入的,所以我们的启动类是扫描不到它的, 因此,我们可以通过@ComponentScan的方式指定包扫描,这样我们的切面代码就能装配到容器中了。
上述这个解决方案,有些人成功了,有些人失败了,博主就是失败的那个…
失败的也没关系,这个方案并不是最好的,假设这种公共组件越来越多,每个你都需要亲自去加一下这行代码,依然是挺麻烦的。
我们可以通过自动配置原理来实现自动装配:
1、先创建一个配置类:
package com.junjie.configure;
@Configuration
@ComponentScan(basePackages = "com.work.*")
public class LogConfigure {
}
2、在aop项目组件创建META-INF文件夹,并在该文件夹下创建spring.factories文件
这个文件内容要怎么写呢?
我们在看一下springboot自带的spring.factories文件
看到这里,我们就知道了,只要参考这个写法,我们就能将我们定义的组件自动装配到容器里去。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.junjie.configure.LogConfigure
到这里,我们的自定义组件,就已经加入到自动配置里去了。
后续使用时,只需要将我们的组件打包到maven仓库,在其他项目添加此依赖即可。springboot会自动扫描项目里所有META-INF文件夹下的spring.factories文件,并将这些文件下所配置好的自动配置类装配到容器中。如此就实现了自动装配,采用自动装配的优点就是,客户端想要使用我们的组件,只需要引入依赖即可,无需其他操作。