SpringBoot的自动装配原理解析(Starter)

这里用的是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

希望大佬们提出宝贵意见。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值