栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

SpringBoot自动装配源码解析

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

SpringBoot自动装配源码解析

Spring Boot 自动装配原理

使用Spring Boot最方便的一点体验在于我们可以几零配置的搭建一个Spring Web项目,那么他是怎么做到不通过配置来对Bean完成注入的呢。这就要归功于Spring Boot的自动装配实现,他也是Spring Boot中各个Starter的实现基础,Spring Boot的核心。自动装配,就是Spring Boot会自动的寻找Bean并且装配到IOC容器中,如下,我们通过一个Spring Boot项目说明,案例如下:添加pom.xml文件依赖



    4.0.0

    com.ljm
    spring-cloud-alibaba-learn
    1.0-SNAPSHOT

    
        8
        UTF-8
    

    
        
        
            org.springframework.boot
            spring-boot-starter-data-redis
            2.2.2.RELEASE
        
        
            org.springframework.boot
            spring-boot-starter-web
            2.2.2.RELEASE
        
    


    
        
            
                org.apache.maven.plugins
                maven-compiler-plugin
                3.8.1
                
                    true
                    ${java.version}
                    ${java.version}
                    ${java.version}
                    ${project.build.sourceEncoding}
                
            
        
    

application.properties

spring.redis.host=localhost
spring.redis.port=6379

HelloController.java

package com.springcloud.mystart;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
public class HelloController {
    @Autowired
    private RedisTemplate redisTemplate;

    @GetMapping("helloRedis")
    public String helloRedis(){
        redisTemplate.opsForValue().set("helloRedis", "RedisTemplateAutoConfig");
        return "helloRedis";
    }
}

SpringBoot启动类 Application.java

package com.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;


@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Redis操作界面结果

如上案例中,我们并没有通过XML文件的形式注入Redis Template到IOC容器中,但是HelloController中却可以直接用@Autowired注入Redis Template实例,说明,IOC容器中已经存在了Redis Template,这个是Spring Boot自动装配实现的自动加载机制。在针对Redis的配置以及jar来说,我们只添加了一个Start依赖,就完成了依赖组件相关的Bean自动注入, 自实现自动装配标签

自动装配在Spring Boot中通过@EnableAutoConfiguration 注解来开启的,这个注解我们没有在项目中显示的声明,他是包含在@SpringBootApplication注解中如下我们可以看到@SpringBootApplication注解的声明

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {......}

在理解EnableAutoConfiguration之前,我们能想到之前在用SpringMvc的时候用到过一个@Enable注解,他的主要作用就是吧相关的组件Bean装配到IOC容器中。@Enable注解对JavaConfig的进一步的优化,目的是为了减少配置,其实Spring从3.X开始就一直在做优化,减少配置,降低上手的难度,比如常见的有@EnableWebMvc,@EnableScheduling,如下代码

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@documented
@import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}

如果我们通过JavaConfig的方式来注入Bean的时候,离不来@Configuration和@Bean,@Enable就是对这两个注解的封装,比如在上面的@EnableWebMvc注解代码中我们能看到@import,Spring会解析@import倒入的配置类,通过对这个配置类的实现来找到需要装配的Bean。 EnableAutoConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@documented
@Inherited
@AutoConfigurationPackage
@import(AutoConfigurationimportSelector.class)
public @interface EnableAutoConfiguration {

	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

	
	Class[] exclude() default {};

	
	String[] excludeName() default {};

}

以上是@EnableAutoConfiguration的注解,在除了@import之外还有一个@AutoConfigurationPackage

注解,作用在于用了该注解的类所在的包以及子包下所有的组件扫描到Spring IOC容器中

@import注解倒入类一个Configuration结尾的配置类,和上面不同,AutoConfigurationimportSelector类就是本注解的特殊地方

public class AutoConfigurationimportSelector implements DeferredimportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {......}

AutoConfigurationimportSelect 实现了DeferredimportSelector,他是importSelector的子类,其中有一个selectimports方法返回类一个String数组,这个数组中就是我们需要制定装配到IOC容器中的所有类也就是说他在importSelector 的实现类之后,将实现类中返回的class名称都装配进IOC容器里与@Configuration不同的是,importSelector是批量的,并且还可以通过逻辑处理来选择对于的Bean,那么我们用一个Demo来验证创建两个类:


public class FilterFirstObj {
}

public class FilterSecondObj {
}

创建一个importSelect的实现类,直接返回我们新建的类的名字,如下:

public class GpimportSelector implements importSelector {
    @Override
    public String[] selectimports(Annotationmetadata annotationmetadata) {
        return new String[]{FilterFirstObj.class.getName(), FilterSecondObj.class.getName()};
    }
}

定义我们自己的注解,这个可以直接抄@EnableAutoConfiguration

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@documented
@Inherited
@AutoConfigurationPackage
@import({GpimportSelector.class})
public @interface EnableAutoimport {
}

在之前Demo的启动类中从IOC容器中获取我们定义的类:

@SpringBootApplication
@EnableAutoimport
public class Application {
    public static void main(String[] args) {
        ConfigurableApplicationContext ca = SpringApplication.run(Application.class, args);
        System.out.println(ca.getBean(FilterFirstObj.class));
    }
}

以上的实现方式相比@import(*Configuration.class)来说,好处在于灵活性更高,还可以实现批量的注入,我们还有在以上的Demo中GpimportSelector添加N个,由于一个Configuration表示某一个技术组件中的一批Bean,所以自动装配的过程只需要扫描置顶路径对于的配置类即可。 自动装配源码分析

基于以上Demo的实现,我们找到AutoConfigurationimportSelector的实现,找到其中的selectimports方法,他是importSelector接口的实现,如下:

@Override
	public String[] selectimports(Annotationmetadata annotationmetadata) {
		if (!isEnabled(annotationmetadata)) {
			return NO_importS;
		}
		AutoConfigurationmetadata autoConfigurationmetadata = AutoConfigurationmetadataLoader
				.loadmetadata(this.beanClassLoader);
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationmetadata,
				annotationmetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}

如上源码中有两个功能:

从meta-INF/spring-autoconfigure-metadata.properties中加载自动装配的条件元数据手机所有符合条件的配置类,autoConfigurationEntry.getConfigurations(),完成自动装配

在AutoConfigurationimportSelector 类中并不直接执行对呀selectimport方法,其中有一个process方法,这个方法最中还是会调用getAutoConfigurationEntry方法获取自动装配的所有配置类,我们可以看如下源码

process方法

@Override
		public void process(Annotationmetadata annotationmetadata, DeferredimportSelector deferredimportSelector) {
			Assert.state(deferredimportSelector instanceof AutoConfigurationimportSelector,
					() -> String.format("only %s implementations are supported, got %s",
							AutoConfigurationimportSelector.class.getSimpleName(),
							deferredimportSelector.getClass().getName()));
			AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationimportSelector) deferredimportSelector)
					.getAutoConfigurationEntry(getAutoConfigurationmetadata(), annotationmetadata);
			this.autoConfigurationEntries.add(autoConfigurationEntry);
			for (String importClassName : autoConfigurationEntry.getConfigurations()) {
				this.entries.putIfAbsent(importClassName, annotationmetadata);
			}
		}

getAutoConfigurationEntry方法

	protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationmetadata autoConfigurationmetadata,
			Annotationmetadata annotationmetadata) {
		if (!isEnabled(annotationmetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationmetadata);
		List configurations = getCandidateConfigurations(annotationmetadata, attributes);
		configurations = removeDuplicates(configurations);
		Set exclusions = getExclusions(annotationmetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
		configurations = filter(configurations, autoConfigurationmetadata);
		fireAutoConfigurationimportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

通过Debug代码可以看每个步骤得到的返回值,简单分析代码的作用

getAttributes 获取@EnableAutoCOnfiguration注解中的属性exclude, excludeNamegetCandidateConfigurations获得所有自动装配的配置类。removeDuplicates 用linkedHashSet实现去重getExclusions更具@EnableAutoConfiguration注解中配置的exclude等属性吧不需要自动装配的配置类移除fireAutoConfigurationimportEvents广播事件最后返回经过过滤之后的配置类集合

以上步骤中,核心在于获取自动装配的配置类getCandidateConfigurations,其他只是在最筛选等其他步骤,我们看该方法实现

protected List getCandidateConfigurations(Annotationmetadata metadata, AnnotationAttributes attributes) {
		List 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;
	}

如上代码用到了SpringFactoriesLoader,他是Spring内部提供的一种类加载方式,类似java的SPI,他会扫描classpath目录下的meta-INF/spring.factories文件,spring.factories文件中的数据以 key=value 的形式存储,也就是getCandidateConfigurations 方法的返回值

如下图是spring-boot-autoconfiguration对应的jar包中文件,我们可以在jar中找到对应的spring.factories

内容如下

......
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,
......

里面包含了多种Spring支持的组件的加载,我们这以Redis为案例,通过Debug,我们查看getCandidateConfigurations所扫描到的所有类,如下图所示,其中就包括我们上图中找到的Redis的支持:

我们打开RedisAutoConfiguration可以看到,他是一个基于JavaConfig形式的配置类,如下:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {

	@Bean
	@ConditionalOnMissingBean(name = "redisTemplate")
	public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory)
			throws UnknownHostException {
		RedisTemplate 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;
	}

}

除了基本的Configuration 和Bean两个注解,还有一个COnditionalOnClass,这个条件控制机制在这里用途是判断classpath下是否存在RedisOperations这个类,我们找一下这个类属于那个jar中,如下图

如上图,所示,他在spring-data-redis中,而我们只引入了一个有关redis的jar就是那个redis-start,那么结论显而易见了,在Spring Boot自动装配的时候,他能扫描到所有支持的组件,但是他实际加载到IOC中的会依据每个组件的condition进行第一次筛选,只有找到对应的资源文件他才会去加载。@EnableConfigurationProperties注解也是我们需要关注的,他说有关属性配置的,也就是我们按照约定在application.properties中配置的Redis的参数,如下Redis.properties

@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {

	
	private int database = 0;

	
	private String url;

	
	private String host = "localhost";

	
	private String password;

	
	private int port = 6379;

	
	private boolean ssl;

	
	private Duration timeout;

	
	private String clientName;

	private Sentinel sentinel;

	private Cluster cluster;
	......
	}

我们的properties中的配置样式的由来就是由此得出的

spring.redis.host=127.0.0.1
spring.redis.port=6379

由此自动装配的基本原理就完结了,总结过程如下:

通过@import(AutoConfigurationimportSelector)实现配置类的导入,但是这里并不是传统意义上的单个配置类装配AutoConfigurationimportSelector实现了importSelector接口,重写了selectimports,他用来实现选择性批量配置类的装配通过Spring提供的SpringFactoriesLoader机制,扫描classpath路径下的meta-INF/spring.factories读取自动装配的配置类通过筛选条件,吧不符合的配置类移除,最中完成自动装配

下一篇:SpringBoot中Bean按条件装配

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/759317.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号