Spring Boot的自动配置原理解析

生活中,最使人疲惫的往往不是道路的遥远,而是心中的郁闷;最使人痛苦的往往不是生活的不幸,而是希望的破灭;最使人颓废的往往不是前途的坎坷,而是自信的丧失;最使人绝望的往往不是挫折的打击,而是心灵的死亡。所以我们要有自己的梦想,让梦想的星光指引着我们走出落漠,走出惆怅,带着我们走进自己的理想。

导读:本篇文章讲解 Spring Boot的自动配置原理解析,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

一、Spring Boot概述

SpringBoot是整合Spring技术栈的一站式框架,是简化Spring技术栈的快速开发脚手架。

Spring Boot官网:https://spring.io/

Spring的生态:https://spring.io/projects/spring-boot

查看版本新特性:https://github.com/spring-projects/spring-boot/wiki#release-notes

Spring Boot文档:https://docs.spring.io/spring-boot/docs/current/reference/html/
在这里插入图片描述

二、注解说明

介绍Spring Boot自动配置过程中可能使用到的注解,对其有个大概的了解。

@SpringBootApplication(核心启动)

@SpringBootApplication注解是Spring Boot的启动注解,是三个注解的总和

@Configuration: 用于定义一个配置类

@EnableAutoConfigurationSpring Boot会自动根据jar包的依赖来自动配置项目。

@ComponentScan: 告诉Spring哪个packages用注解标识的类会被spring自动扫描并且装入bean容器。

@ComponentScan(包扫描)

扫描被@Component 、@Controller、@Service、@Repository等注解的类,注解默认会扫描该@ComponentScan注解类所在的包下所有的类

@Import(导入组件)

给容器中自动创建出导入类型的组件、默认组件的名字就是全类名(com.example.demo.model.User)

@Import({User.class, XXX.class})
@Configuration
public class MyConfig {

}

@Conditional(条件装配)

条件装配注解:满足Conditional指定的条件,则进行组件注入。

条件装配注解既可以使用在类上也可以使用在方法上。

有如下注解:

在这里插入图片描述
@ConditionalOnBean(name = "myUser")时,myCar方法执行,向容器添加car组件

@ConditionalOnBean(name = "myUser22")时,myCar方法不执行

@Configuration
public class MyConfig {

    /**
     * 向容器添组件,方法名作为组件id
     * 返回类型就是组件类型
     * 返回值:组件在容器中的实例
     */
    @Bean
    public User myUser() {
        User user = new User();
        return user;
    }

    /**
     * 在@Bean注解指定值:向容器添组件, id为`car`
     */
    @Bean("car")
    @ConditionalOnBean(name = "myUser")
    public Car myCar() {
        return new Car();
    }

    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(DemoApplication.class, args);
        System.out.println("容器中是否存在 myUser: " + run.containsBean("myUser"));
        System.out.println("容器中是否存在 car: " + run.containsBean("car"));
    }
}

@Configuration(申明配置文件)

@Configuration申明一个类是配置类,代替传统的配置文件

@Configuration具体两种模式:Full模式(proxyBeanMethods = true)与Lite模式(proxyBeanMethods = false)

Full模式:

保证每个@Bean方法被调用多少次返回的组件都是单实例的

配置类组件之间无依赖关系用Lite模式加速容器启动过程,减少判断

Lite模式:

每个@Bean方法被调用多少次返回的组件都是新创建的

配置类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式
/**
 * 配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
 * 配置类本身也是组件
 * proxyBeanMethods:代理bean的方法
 * 组件依赖必须使用Full模式默认。其他默认是否Lite模式
 */
@Configuration(proxyBeanMethods = true)
public class MyConfig {

    /**
     * 向容器添组件,方法名作为组件id
     * 返回类型就是组件类型
     * 返回值:组件在容器中的实例
     */
    @Bean
    public User myUser() {
        User user= new User("zhangsan", 18);
        //user组件依赖了Car组件,当proxyBeanMethods = false时,多次调用myUser方法产生的依赖组件Car与调用myCar方法产生的Car不是同一个,反之是同一个。
        user.setCar(myCar());
        return user;
    }

    /**
     * 在@Bean注解指定值:向容器添组件, id为`car`
     */
    @Bean("car")
    public Car myCar() {
        return new Car("car");
    }
}

测试

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        // 得到IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(DemoApplication.class, args);

        // 所有容器里的组件
        String[] names = run.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }

        // 从容器中获取组件
        User user1 = run.getBean("myUser", User.class);
        User user2 = run.getBean("myUser", User.class);
        System.out.println("user1 == user2 :" + (user1 == user2));


        MyConfig bean = run.getBean(MyConfig.class);
        // com.example.demo.MyConfig$$EnhancerBySpringCGLIB$$c0fab96c@62102051
        System.out.println(bean);

        //如果@Configuration(proxyBeanMethods = true)代理对象调用方法:SpringBoot总会检查这个组件是否在容器中存在
        User user3 = bean.myUser();
        User user4 = bean.myUser();
        System.out.println("user3 == user4 :" + (user3 == user4));

        // proxyBeanMethods = true :保证方法被调用返回的组件都是单实例,包括依赖组件。
        // proxyBeanMethods = false: 保证方法被调用返回的组件都是新创建实例
        User user5 = run.getBean("myUser", User.class);
        Car car = run.getBean("car", Car.class);
        System.out.println("user5 == car :" + (user5.getCar() == car));
    }
}

@ImportResource(原生配置文件引入)

@ImportResource支持将原生配置文件引入

创建spring原生配置文件beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="xml_user" class="com.example.demo.model.User"/>

    <bean id="xml_car" class="com.example.demo.model.Car"/>

</beans>

使用@ImportResource注解引入

@Configuration
@ImportResource("classpath:/beans.xml")
public class MyConfig {

    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(DemoApplication.class, args);
        System.out.println("容器中是否存在 xml_user: " + run.containsBean("xml_user"));
        System.out.println("容器中是否存在 xml_car: " + run.containsBean("xml_car"));
    }
}

启动main方法

容器中是否存在 xml_user: true
容器中是否存在 xml_car: true

@ConfigurationProperties(配置绑定)

1.使用Java读取到properties文件中的内容,并且把它封装到JavaBean中

test.properties

name:"userName"
age:20
public class getProperties {
    public static void main(String[] args) throws IOException {
        Properties pps = new Properties();
        Class<getProperties> getPropertiesClass = getProperties.class;
        InputStream inputStream = getPropertiesClass.getClassLoader().getResource("test.properties").openStream();
        pps.load(inputStream);
        //得到配置文件的名字
        Enumeration<?> enums = pps.propertyNames();
        while (enums.hasMoreElements()) {
            String name = pps.getProperty("name");
            String age = pps.getProperty("age");
            //封装到JavaBean
            User user = new User();
            user.setName(name);
            user.setAge(Integer.valueOf(age));
        }
    }
}

2.使用@Component+@ConfigurationProperties

application.properties

user.name="userName"
user.age=20
@Data
@Component
@ConfigurationProperties(prefix = "user")
public class User {
    private String name;
    private Integer age;
}

3.使用@EnableConfigurationProperties+@ConfigurationProperties

@Data
@ConfigurationProperties(prefix = "user")
public class User {
    private String name;
    private Integer age;
}
// 开启User配置绑定功能,把这个User组件自动注册到容器中
@EnableConfigurationProperties(User.class)
@SpringBootApplication
public class DemoApplication {

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

执行测试

@RestController
public class TestController {
    @Qualifier("user")
    @Autowired
    User user;

    @RequestMapping("/test")
    public User test() {
        return user;
    }
}

三、自动配置过程解析

@SpringBootApplication注解包含如下3个注解

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {}

1.@SpringBootConfiguration

@SpringBootConfiguration注解包含@Configuration注解(代表当前是一个配置类)

@Configuration
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

2.@EnableAutoConfiguration

@EnableAutoConfiguration注解包含@AutoConfigurationPackage@Import

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}

@AutoConfigurationPackage

@AutoConfigurationPackage注解直译:自动导入包,其指定了默认的包规则

// 向容器添加Registrar组件
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {}

使用Registrar类向容器添加一系列组件

	static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

		@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
			// 将指定的一个包(DemoApplication所在包)下的所有组件导入容器中
			register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
		}

		@Override
		public Set<Object> determineImports(AnnotationMetadata metadata) {
			return Collections.singleton(new PackageImports(metadata));
		}

	}

@Import(AutoConfigurationImportSelector.class)

@Import注解向容器添加AutoConfigurationImportSelector组件,同时导入相关依赖组件。

1.使用getAutoConfigurationEntry(annotationMetadata)向容器批量导入组件

	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}

2.调用getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类

	protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		configurations = removeDuplicates(configurations);
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
		configurations = getConfigurationClassFilter().filter(configurations);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

在这里插入图片描述
3.使用工厂最终加载 Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader)得到的所有组件

	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;
	}
  public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        String factoryTypeName = factoryType.getName();
        return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
    }

4.从META-INF/spring.factories位置来加载一个文件

    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            try {
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                MultiValueMap<String, String> result = new LinkedMultiValueMap();

                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();

                    while(var6.hasNext()) {
                        Map.Entry<?, ?> entry = (Map.Entry)var6.next();
                        String factoryTypeName = ((String)entry.getKey()).trim();
                        String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;

                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryImplementationName = var9[var11];
                            result.add(factoryTypeName, factoryImplementationName.trim());
                        }
                    }
                }

                cache.put(classLoader, result);
                return result;
            } catch (IOException var13) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
            }
        }
    }

默认扫描当前系统里面所有META-INF/spring.factories位置的文件
在这里插入图片描述
这些文件里写死了spring-boot启动就要向容器中加载的所有配置类
在这里插入图片描述

3.@ComponentScan

@ComponentScan注解指定扫描哪些Spring注解

四、按需开启自动配置

自动配置类

各种配置都拥有默认值,默认配置最终都是映射到某个类上,即:配置文件的值最终会绑定每个类上,这个类会在容器中创建对象

自动配置项是按需加载:当引入了某个starter启动器后,自动配置才会开启

虽然文件中写死了所有自动配置启动的时候默认全部加载,但是Spring Boot会按照条件装配(@Conditional)规则,最终实现按需配置。

org.springframework.boot.autoconfigure包下面全是自动配置类
在这里插入图片描述
additional-spring-configuration-metadata.json配置文件存在大量的默认配置,如项目端口默认8080。
在这里插入图片描述
spring.factories文件存在大量以Configuration为结尾的类名称,这些类就是存有自动配置信息的类,而SpringApplication在获取这些类名后再加载。
在这里插入图片描述

自动配置类解析

例如AOP自动配置类:如果不引入Aop相关依赖,则AOP自动配置并不会生效。
在这里插入图片描述
条件装配满足,执行类

@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)

在这里插入图片描述
由于Aop存在默认配置,即使不在application.properties中配置,只要引入Aop相关依赖,AOP功能默认开启。
在这里插入图片描述

五、修改默认配置

修改配置文件默认值

参考官网:https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#appendix.application-properties
在这里插入图片描述

在src/main/resources下创建application.properties,添加配置以此覆盖默认配置

查看组件获取的配置文件是什么值,然后修改即可

过程:*AutoConfiguration ---> 组件 ---> *Properties里面拿值 ----> application.properties

SpringBoot加载所有的自动配置类 ,每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。当配置类生效,就会向容器中装配很多组件,容器中有这些组件,就相当于有了这些功能

RabbitAutoConfiguration自动配置类,从RabbitProperties里面获取相应配置,RabbitProperties又和application.properties配置文件进行了绑定

在这里插入图片描述

@Bean替换底层组件

直接自己@Bean替换底层的组件

SpringBoot默认会在底层配好所有的组件,但是如果用户自己配置了以用户的优先。

如给容器中加入自定义文件上传解析器

@SpringBootApplication
public class DemoApplication {

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

    /**
     * 显示声明自定义文件解析器为mutipartResolver
     */
    @Bean(name = "multipartResolver")
    public MultipartResolver multipartResolver() {
        CommonsMultipartResolver resolver = new CommonsMultipartResolver();
        resolver.setDefaultEncoding("UTF-8");
        resolver.setResolveLazily(true);
        resolver.setMaxInMemorySize(40960);
        resolver.setMaxUploadSize(10 * 1024 * 1024);
        return resolver;
    }
}

再看DispatcherServletAutoConfiguration

		@Bean
		// 容器中有这个类型组件
		@ConditionalOnBean(MultipartResolver.class)
		// 容器中没有名叫 multipartResolver 的组件
		@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
		public MultipartResolver multipartResolver(MultipartResolver resolver) {
			// Detect if the user has created a MultipartResolver but named it incorrectly
			// 给该方法传入对象参数,参数的值就从容器中找,防止用户配置的文件上传解析器不符合规范
			return resolver;
		}

六、开启自动配置报告

在配置文件中开启自动配置报告

debug=true

控制台打印如下信息:

Positive matches: 配置生效
在这里插入图片描述
Negative matches: 配置未生效
在这里插入图片描述

七、Spring Boot应用启动器

查询Spring Boot官方提供的Starter:https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.build-systems.starters

在这里插入图片描述

开发使用启动器

1)spring-boot-starter :这是Spring Boot的核心启动器,包含了自动配置、日志和YAML

2)spring-boot-starter-actuator :帮助监控和管理应用

3)spring-boot-starter-amqp :通过spring-rabbit来支持AMQP协议(Advanced Message Queuing Protocol4)spring-boot-starter-aop :支持面向方面的编程即AOP,包括spring-aop和AspectJ

5)spring-boot-starter-artemis :通过Apache Artemis支持JMS的API(JavaMessage Service API)

6)spring-boot-starter-batch :支持Spring Batch,包括HSQLDB数据库

7)spring-boot-starter-cache :支持SpringCache抽象

8)spring-boot-starter-cloud-connectors :支持Spring Cloud Connectors,简化了在像Cloud FoundryHeroku这样的云平台上连接服务

9)spring-boot-starter-data-elasticsearch :支持ElasticSearch搜索和分析引擎,包括spring-data-elasticsearch

10)spring-boot-starter-data-gemfire :支持GemFire分布式数据存储,包括spring-data-gemfire

11)spring-boot-starter-data-jpa :支持JPA(Java Persistence API),包括spring-data-jpa、spring-orm、hibernate

12)spring-boot-starter-data-MongoDB :支持MongoDB数据,包括spring-data-mongodb

13)spring-boot-starter-data-rest :通过spring-data-rest-webmvc,支持通过REST暴露Spring Data数据仓库

14)spring-boot-starter-data-solr :支持Apache Solr搜索平台,包括spring-data-solr

15)spring-boot-starter-freemarker :支持FreeMarker模板引擎

16)spring-boot-starter-groovy-templates :支持Groovy模板引擎

17)spring-boot-starter-hateoas :通过spring-hateoas支持基于HATEOAS的RESTful Web服务

18)spring-boot-starter-hornetq :通过HornetQ支持JMS

19)spring-boot-starter-integration :支持通用的spring-integration模块

20)spring-boot-starter-jdbc :支持JDBC数据库

21)spring-boot-starter-jersey :支持Jersey RESTful Web服务框架

22)spring-boot-starter-jta-atomikos :通过Atomikos支持JTA分布式事务处理

23)spring-boot-starter-jta-bitronix :通过Bitronix支持JTA分布式事务处理

24)spring-boot-starter-mail :支持javax.mail模块

25)spring-boot-starter-mobile :支持spring-mobile

26)spring-boot-starter-mustache :支持Mustache模板引擎

27)spring-boot-starter-Redis :支持Redis键值存储数据库,包括spring-redis

28)spring-boot-starter-security :支持spring-security

29)spring-boot-starter-social-facebook :支持spring-social-facebook

30)spring-boot-starter-social-linkedin :支持pring-social-linkedin

31)spring-boot-starter-social-twitter :支持pring-social-twitter

32)spring-boot-starter-test :支持常规的测试依赖,包括JUnitHamcrestMockito以及spring-test模块

33)spring-boot-starter-thymeleaf :支持Thymeleaf模板引擎,包括与Spring的集成

34)spring-boot-starter-velocity :支持Velocity模板引擎

35)spring-boot-starter-web :支持全栈式Web开发,包括Tomcat和spring-webmvc

36)spring-boot-starter-websocket :支持WebSocket开发

37)spring-boot-starter-ws :支持Spring Web Services

面向生产环境启动器

1)spring-boot-starter-actuator :增加了面向产品上线相关的功能,比如测量和监控

2)spring-boot-starter-remote-shell :增加了远程ssh shell的支持

替换技术启动器

1)spring-boot-starter-jetty :引入了Jetty HTTP引擎(用于替换Tomcat2)spring-boot-starter-log4j :支持Log4J日志框架

3)spring-boot-starter-logging :引入了Spring Boot默认的日志框架Logback

4)spring-boot-starter-tomcat :引入了Spring Boot默认的HTTP引擎Tomcat

5)spring-boot-starter-undertow :引入了Undertow HTTP引擎(用于替换Tomcat

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/136941.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!