文章目录
springboot自动装配的原理
spring条件装配
这里举个例子,我有两个组件 MyLog 和 MyAspect
而 MyLog 是依赖于MyAspect的,只有容器中有MyAspect才会加载MyLog
- 创建两个组件
public class MyAspect {
}
public class MyLog {
}
- 配置生效条件
public class MyCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
//容器中包含MyAspect组件才返回true,条件上下文可以获取到BeanFactory
return Objects.requireNonNull(conditionContext.getBeanFactory()).containsBean("myAspect");
}
}
- 配置bean
@Configuration
public class AutoConfig {
@Bean
public MyAspect myAspect() {
System.out.println("myAspect 组件装配到容器中");
return new MyAspect();
}
@Bean
@Conditional(value = MyCondition.class)
public MyLog myLog() {
System.out.println("myLog 组件装配到容器中");
return new MyLog();
}
}
这样配置两个两个bean都会被加载到容器中
但是如果按照下面这样配置,两个bean都不会加载到容器中
@Configuration
public class AutoConfig {
// @Bean
public MyAspect myAspect() {
System.out.println("myAspect 组件装配到容器中");
return new MyAspect();
}
@Bean
@Conditional(value = MyCondition.class)
public MyLog myLog() {
System.out.println("myLog 组件装配到容器中");
return new MyLog();
}
}
springboot 的自动装配
@Springboot 注解
@Springboot 注解是一个组合注解,其中与自动装配关系最大的是这两个类
- AutoConfigurationImportSelector.class
- AutoConfigurationPackages.Registrar.class
首先看看他们都干了什么事
AutoConfigurationImportSelector.class
- 选择导入配置,调用到获取自动配置的方法
//1.选择导入配置
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
- 获取自动配置的时候,获取所有候选的配置,然后移除重复的,移除排除的,然后返回
//2.调用到这个方法,获取自动装配的配置
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//1.获取候选的配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
//2.移除重复的
configurations = removeDuplicates(configurations);
//3.获取移除排除的
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
- 候选的配置类是怎么获取,在 META-INF/spring.factories 这个文件里找到并加载
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;
}
- 加载 META-INF/spring.factories 中的配置
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
- 真正的加载
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 {
//1.去类路径下加载/META-INF/spring.factories文件中的EnableConfiguration.class
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
//2.解析遍历获取的内容
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
//3.构建成配置文件
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;
}
所以这个AutoConfigurationImportSelector类实际上是
扫描spring-boot-autoconfigure的META-INF/spring.factories中EnableAutoConfiguration对应的全路径类名
这些都是一些自动配置类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Oh6TeX68-1617966842666)(399BDA4AD83B49D68BA2A06F39C9FE8E)]
自动配置类
这里拿org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
来看看配置类是如何工作的
- RedisAutoConfiguration自动配置类,定义和引入了一些组件
@Configuration(proxyBeanMethods = false) //1.标志是一个配置类
@ConditionalOnClass(RedisOperations.class)//2.判断是否导入了redis的jar包
@EnableConfigurationProperties(RedisProperties.class)//3.允许将配置文件中配置绑定到对象并加载到容器中
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })//4.导入组件的方式
public class RedisAutoConfiguration {
//配置了一个RedisTemplate,只有当容器中没有配置自定义的RedisTemplate时才生效,有自定义的就会被替代
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
//同上,操作字符串的
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
- 引入的组件,这里以
lettuce
客户端连接配置为例
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisClient.class)
@ConditionalOnProperty(name = "spring.redis.client-type", havingValue = "lettuce", matchIfMissing = true)//配置文件中这样配置 配置类才会生效
class LettuceConnectionConfiguration extends RedisConnectionConfiguration {
//通过RedisProperties生成对象的构造方法
LettuceConnectionConfiguration(RedisProperties properties,
ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider,
ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider) {
super(properties, sentinelConfigurationProvider, clusterConfigurationProvider);
}
//这里去掉了一些无关的代码和方法
//配置了一个连接工厂,只有在容器中没有自定义的的时候才会加载到容器中,而这个bean依赖于 RedisProperties
@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class)
LettuceConnectionFactory redisConnectionFactory(
ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,
ClientResources clientResources) {
LettuceClientConfiguration clientConfig = getLettuceClientConfiguration(builderCustomizers, clientResources,
getProperties().getLettuce().getPool());
return createLettuceConnectionFactory(clientConfig);
}
/**
* Inner class to allow optional commons-pool2 dependency.
*/
private static class PoolBuilderFactory {
LettuceClientConfigurationBuilder createBuilder(Pool properties) {
return LettucePoolingClientConfiguration.builder().poolConfig(getPoolConfig(properties));
}
private GenericObjectPoolConfig<?> getPoolConfig(Pool properties) {
GenericObjectPoolConfig<?> config = new GenericObjectPoolConfig<>();
config.setMaxTotal(properties.getMaxActive());
config.setMaxIdle(properties.getMaxIdle());
config.setMinIdle(properties.getMinIdle());
if (properties.getTimeBetweenEvictionRuns() != null) {
config.setTimeBetweenEvictionRunsMillis(properties.getTimeBetweenEvictionRuns().toMillis());
}
if (properties.getMaxWait() != null) {
config.setMaxWaitMillis(properties.getMaxWait().toMillis());
}
return config;
}
}
}
- 配置文件类 RedisProperties,省去了一些不影响分析的内容
@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {
/**
* Database index used by the connection factory.
*/
private int database = 0;
/**
* Connection URL. Overrides host, port, and password. User is ignored. Example:
* redis://user:password@example.com:6379
*/
private String url;
/**
* Redis server host.
*/
private String host = "localhost";
/**
* Login username of the redis server.
*/
private String username;
/**
* Login password of the redis server.
*/
private String password;
/**
* Redis server port.
*/
private int port = 6379;
/**
* Whether to enable SSL support.
*/
private boolean ssl;
/**
* Read timeout.
*/
private Duration timeout;
/**
* Connection timeout.
*/
private Duration connectTimeout;
/**
* Client name to be set on connections with CLIENT SETNAME.
*/
private String clientName;
/**
* Type of client to use. By default, auto-detected according to the classpath.
*/
private ClientType clientType;
private Sentinel sentinel;
private Cluster cluster;
private final Jedis jedis = new Jedis();
private final Lettuce lettuce = new Lettuce();
/**
* Type of Redis client to use.
*/
public enum ClientType {
/**
* Use the Lettuce redis client.
*/
LETTUCE,
/**
* Use the Jedis redis client.
*/
JEDIS
}
/**
* Pool properties.
*/
public static class Pool {
/**
* Maximum number of "idle" connections in the pool. Use a negative value to
* indicate an unlimited number of idle connections.
*/
private int maxIdle = 8;
/**
* Target for the minimum number of idle connections to maintain in the pool. This
* setting only has an effect if both it and time between eviction runs are
* positive.
*/
private int minIdle = 0;
/**
* Maximum number of connections that can be allocated by the pool at a given
* time. Use a negative value for no limit.
*/
private int maxActive = 8;
/**
* Maximum amount of time a connection allocation should block before throwing an
* exception when the pool is exhausted. Use a negative value to block
* indefinitely.
*/
private Duration maxWait = Duration.ofMillis(-1);
/**
* Time between runs of the idle object evictor thread. When positive, the idle
* object evictor thread starts, otherwise no idle object eviction is performed.
*/
private Duration timeBetweenEvictionRuns;
}
/**
* Cluster properties.
*/
public static class Cluster {
/**
* Comma-separated list of "host:port" pairs to bootstrap from. This represents an
* "initial" list of cluster nodes and is required to have at least one entry.
*/
private List<String> nodes;
/**
* Maximum number of redirects to follow when executing commands across the
* cluster.
*/
private Integer maxRedirects;
}
/**
* Redis sentinel properties.
*/
public static class Sentinel {
/**
* Name of the Redis server.
*/
private String master;
/**
* Comma-separated list of "host:port" pairs.
*/
private List<String> nodes;
/**
* Password for authenticating with sentinel(s).
*/
private String password;
}
/**
* Jedis client properties.
*/
public static class Jedis {
/**
* Jedis pool configuration.
*/
private Pool pool;
}
/**
* Lettuce client properties.
*/
public static class Lettuce {
/**
* Shutdown timeout.
*/
private Duration shutdownTimeout = Duration.ofMillis(100);
/**
* Lettuce pool configuration.
*/
private Pool pool;
private final Cluster cluster = new Cluster();
public static class Cluster {
private final Refresh refresh = new Refresh();
public static class Refresh {
/**
* Whether to discover and query all cluster nodes for obtaining the
* cluster topology. When set to false, only the initial seed nodes are
* used as sources for topology discovery.
*/
private boolean dynamicRefreshSources = true;
/**
* Cluster topology refresh period.
*/
private Duration period;
/**
* Whether adaptive topology refreshing using all available refresh
* triggers should be used.
*/
private boolean adaptive;
}
}
}
}
对应yml
配置文件中的redis配置,包含了,单击,哨兵,集群的配置
以上就是springboot自动装配的原理
AutoConfigurationImportSelector
向容器中注册了哪些组件- 通过maven依赖引入的jar包和spring的条件装配来确定哪些组件其作用
总结
@Springboot
注解是一个组合注解,其中的@EnableAutoConfiguration注解通过@Import的ImportSelector方式引入了AutoConfigurationImportSelector组件AutoConfigurationImportSelector
去spring-boot-autoconfiure
下的META-INF/factories文件中找到@EnableAutoConfiguration配置的那些自动配置类,向容器中注册组件- 而引入的这些组件通过maven引入的jar包和spring的条件装配来确定哪些组件在什么情况下是生效的
- 当引入对应jar包,配置对应的配置文件,即可使用那些生效的bean(组件)