这里用的是SpringBoot 2.0.6版本
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
想要了解SpringBoot的自动装配首先需要了解两个注解的作用 @Import 和 @Conditional (下面用到的@ConditionalOnMissingBean注解就是@Conditional的变种)
@Import注解
有时没有把某个类注入到IOC容器中,但在运用的时候需要获取该类对应的bean,此时就需要用到@Import注解。
源代码:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
/**
*需要注入到容器中的类
*/
Class<?>[] value();
}
示例如下:
首先创建一个需要注入类
public class MyServiceImpl {
public MyServiceImpl() {
System.out.println("MyServiceImpl的构造函数!");
}
public void testService() {
System.out.println("我是通过importSelector导入进来的service");
}
}
然后定义一个MySelectorBean,需要实现 ImportSelector 接口,然后重写 selectImports()方法,把需要注入到容器中的Bean返回
public class MySelectorBean implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
//这里返回需要注入到容器中类的数组
return new String[]{"com.syt.myspringboot.service.MyServiceImpl"};
}
}
然后创建一个配置类,这样在项目启动的时候就会把MyServiceImpl注入到IOC容器中
/**
* 用来演示 @Import可以在springboot启动时自动装配类
* 两种方式
* 1:通过实现ImportSelector接口
* 2:通过实现ImportBeanDefinitionRegistrar接口
*/
@Configuration
@Import(value = {MySelectorBean.class})
public class SelectorConfig {}
启动项目显示: MyServiceImpl的构造函数!
@RestController
public class MyController {
@Autowired
private MyServiceImpl myService;
@GetMapping("/testSelector")
public String getTest() {
myService.testService();
return "success";
}
}
--------------------------------------------------
调用 localhost:8080/testSelector
控制台输出:我是通过importSelector导入进来的service
@Conditional 注解
@Conditional是Spring4新提供的注解,表示仅当所有指定条件都匹配时,组件才能注册bean。
场景:可用于自定义Starter时根据条件判断注入需要的bean
源代码:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
/**
* 参数需要传入一个继承Condition的类,重写matches方法,返回true时表示允许注册,
*/
Class<? extends Condition>[] value();
}
首先创建需要注入的bean的类,创建一个接口,然后创建两个实现该接口的类
public interface ListService {
String showListCmd();
}
------------------------------------------------------------------------------------
@Service
public class LinuxService implements ListService {
@Override
public String showListCmd(){
return "我是linux系统";
}
}
------------------------------------------------------------------------------------
@Service
public class WindowsService implements ListService {
@Override
public String showListCmd() {
return "我是windows系统";
}
}
接下来是实现 继承Condition的类,重写matches方法
/**
* linux环境
* 实现condition接口重新matches方法
* 返回true:注入 ,false:不注入
*/
public class LinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//获取ioc使用的beanFactory
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
//获取类加载器
ClassLoader classLoader = context.getClassLoader();
//获取当前环境信息
Environment environment = context.getEnvironment();
//获取bean定义的注册类
BeanDefinitionRegistry registry = context.getRegistry();
return context.getEnvironment().getProperty("os.name").contains("Linux");
}
}
//------------------------------------------------------------------------------------
public class WindowsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("os.name").contains("Windows");
}
}
用@Conditional注解,选择注入bean的配置类,
/**
* 演示@Conditional注解的作用
* 通过实现Condition接口,根据修改的判断条件来选择性的注入Bean
* 例:WindowsCondition,LinuxCondition
*/
@Configuration
public class MyConditionalConfiguration {
@Bean(name = "listService")
@Conditional(WindowsCondition.class)
public ListService windowsService() {
return new WindowsService();
}
@Bean(name = "listService")
@Conditional(LinuxCondition.class)
public ListService linuxEmailerService() {
return new LinuxService();
}
}
启动项目调用
@RestController
public class MyController {
@Autowired
private ListService listService;
@GetMapping("/testConditional")
public String testConditional() {
//调用 showListCmd方法
return listService.showListCmd();
}
}
返回结果: 我是windows系统
@ConditionalOnMissingBean 注解
@ConditionalOnMissingBean注解作用在@bean定义上,它的作用就是在容器加载它作用的bean时,检查容器中是否存在目标类型(ConditionalOnMissingBean注解的value值)的bean了,如果存在这跳过原始bean的BeanDefinition加载动作。
自动装配原理解析
创建工程首先进入SpringBoot的主类找到@SpringBootApplication注解,进入找到@EnableAutoConfiguration注解 ,进入找到@Import(AutoConfigurationImportSelector.class)注解。
发现@Import注解导入了一个AutoConfigurationImportSelector类,这个类selectImports()方法就是根据Maven依赖导入jar包来筛选配置类然后设置需要创建bean的类。 。
Springboot自动装配的jar包是Spring-Boot-AutoConfigure,项目启动时会到这个jar下寻找spring.factories文件,org.springframework.boot.autoconfigure.EnableAutoConfiguration这个字段就是可以自动配置的key,默认配置总共有110个类,selectImports()方法会根据需要设置创建bean的类。以下是具体源码
org.springframework.boot.autoconfigure.AutoConfigurationImportSelector
org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#selectImports
/**
*自动配置导入
*DeferredImportSelector接口就是继承了ImportSelector
*/
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,BeanFactoryAware, EnvironmentAware, Ordered {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//去mata-info/spring.factories文件中 查询 EnableAutoConfiguration对于值
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
//去除重复的配置类,若我们自己写的starter 可能存主重复的
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
//根据maven 导入的启动器过滤出 需要导入的配置类
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return StringUtils.toStringArray(configurations);
}
/**
* 去mata-info/spring.factories文件中查询 EnableAutoConfiguration对于值
*/
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
}
-----------------------------------------------------
org.springframework.core.io.support.SpringFactoriesLoader
org.springframework.core.io.support.SpringFactoriesLoader#loadFactoryNames
public abstract class SpringFactoriesLoader {
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
//去mata-info/spring.factories文件中查询 EnableAutoConfiguration对于值的具体实现
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
List<String> factoryClassNames = Arrays.asList(
StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
result.addAll((String) entry.getKey(), factoryClassNames);
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
}
以redis为例
上边的selectImports()已经加载了RedisAutoConfiguration类
RedisAutoConfiguration这个类就是在mata-info/spring.factories配置中存在的类Springboot会创建对应的Bean进行管例,接下来是查看这个类看看具体还做了哪些操作 。源码:
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
/**
*创建了redisTemplateBean
*@ConditionalOnMissingBean注解作用在@bean定义上,它的作用就是在容器加载它作用的bean时,检查容器中是否存在目标类型(ConditionalOnMissingBean注解的value值)的bean了,如果存在这跳过原始bean的BeanDefinition加载动作。
*/
@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;
}
}
操作:
1:RedisOperations接口定义redis了的操作
2:@EnableConfigurationProperties注解的作用是开启@ConfigurationProperties,RedisProperties类就是用@ConfigurationProperties修饰的类,会读取yml文件到配置到RedisProperties映射。
@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {
//***省略****
}
3:@Import注解导入了LettuceConnectionConfiguration,JedisConnectionConfiguration两个类,其中JedisConnectionConfiguration是redis连接的具体实现
4:创建了redisTemplate和stringRedisTemplate的bean
接下来主要看一下JedisConnectionConfiguration类
redisConnectionFactory()配置了JedisConnectionFactory的Bean,JedisConnectionFactory配置了redis的连接池。
@Configuration
@ConditionalOnClass({ GenericObjectPool.class, JedisConnection.class, Jedis.class })
class JedisConnectionConfiguration extends RedisConnectionConfiguration {
@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class)
public JedisConnectionFactory redisConnectionFactory() throws UnknownHostException {
return createJedisConnectionFactory();
}
以上就是SpringBoot的自动装配解析。
关于@Import注解和@Conditional注解的使用可以查看项目:https://gitee.com/sytsuccess/myspringboot.git
如果想要自定义一个自己的starter可以查看博客:https://juejin.im/post/5cb880c2f265da03981fc031
希望大佬们提出宝贵意见。