Springboot的原理

springboot自动装配的原理

spring条件装配

这里举个例子,我有两个组件 MyLog 和 MyAspect

而 MyLog 是依赖于MyAspect的,只有容器中有MyAspect才会加载MyLog

  1. 创建两个组件
public class MyAspect {
}

public class MyLog {
}
  1. 配置生效条件
public class MyCondition implements Condition {
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        //容器中包含MyAspect组件才返回true,条件上下文可以获取到BeanFactory
        return Objects.requireNonNull(conditionContext.getBeanFactory()).containsBean("myAspect");
    }
}
  1. 配置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 注解

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xuTW0GfZ-1617966842662)(8DBB669FFAD44D3E84DEEDE6E38FE1DE)]

@Springboot 注解是一个组合注解,其中与自动装配关系最大的是这两个类

  • AutoConfigurationImportSelector.class
  • AutoConfigurationPackages.Registrar.class

首先看看他们都干了什么事

AutoConfigurationImportSelector.class

  1. 选择导入配置,调用到获取自动配置的方法

    //1.选择导入配置
	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}
	


  1. 获取自动配置的时候,获取所有候选的配置,然后移除重复的,移除排除的,然后返回
	//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);
	}
  1. 候选的配置类是怎么获取,在 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;
	}
  1. 加载 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());
	}
  1. 真正的加载
    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来看看配置类是如何工作的

  1. 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;
	}
}
  1. 引入的组件,这里以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;
		}
	}
}
  1. 配置文件类 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自动装配的原理

  1. AutoConfigurationImportSelector向容器中注册了哪些组件
  2. 通过maven依赖引入的jar包和spring的条件装配来确定哪些组件其作用

总结

  1. @Springboot注解是一个组合注解,其中的@EnableAutoConfiguration注解通过@Import的ImportSelector方式引入了AutoConfigurationImportSelector组件
  2. AutoConfigurationImportSelectorspring-boot-autoconfiure下的META-INF/factories文件中找到@EnableAutoConfiguration配置的那些自动配置类,向容器中注册组件
  3. 而引入的这些组件通过maven引入的jar包和spring的条件装配来确定哪些组件在什么情况下是生效的
  4. 当引入对应jar包,配置对应的配置文件,即可使用那些生效的bean(组件)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值