SpringBoot运作原理【原创】

作者: elijah777 | 来源:发表于2019-07-11 11:23 被阅读8次

运作原理

调整配置可以打印出日志

通过启用 debug=true属性;来让控制台打印自动配置报告


原作原理-consol-未启用的自动配置.png 原作原理-consol-已启用的自动配置.png

SpringBoot启动的时候加载主配置类,开启了自动配置功能 @EnableAutoConfiguration

@EnableAutoConfiguration注解的源码

一旦加上此注解,那么将会开启自动装配功能,简单点讲,Spring会试图在你的classpath下找到所有配置的Bean然后进行装配。当然装配Bean时,会根据若干个(Conditional)定制规则来进行初始化。

​
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
​
@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 {};
}
AutoConfigurationImportSelector
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

}
​
public interface DeferredImportSelector extends ImportSelector {}

该类实现了DeferredImportSelector接口,这个接口继承了ImportSelector:

该接口主要是为了导入@Configuration的配置项,而DeferredImportSelector是延期导入,当所有的@Configuration都处理过后才会执行。

EnableAutoConfiguration中的AutoConfigurationImportSelector类扫描spring.factorise文件

AutoConfigurationImportSelector的selectImports方法
 public String[] selectImports(AnnotationMetadata annotationMetadata) {
 if (!this.isEnabled(annotationMetadata)) {
 return NO_IMPORTS;
 } else {
 AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
 AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
 return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
 }
 }

该方法刚开始会先判断是否进行自动装配,而后会从META-INF/spring-autoconfigure-metadata.properties读取元数据与元数据的相关属性,紧接着会调用getCandidateConfigurations方法:

原作原理-自动装配-spring.factories.png
 protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
 if (!this.isEnabled(annotationMetadata)) {
 return EMPTY_ENTRY;
 } else {
 AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
 List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
 configurations = this.removeDuplicates(configurations);
 Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
 this.checkExcludedClasses(configurations, exclusions);
 configurations.removeAll(exclusions);
 configurations = this.filter(configurations, autoConfigurationMetadata);
 this.fireAutoConfigurationImportEvents(configurations, exclusions);
 return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
 }
 }
​
ConditionalOnWebApplication类分析
package org.springframework.boot.autoconfigure.condition;
​
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Conditional;
​
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnWebApplicationCondition.class})
public @interface ConditionalOnWebApplication {
 ConditionalOnWebApplication.Type type() default ConditionalOnWebApplication.Type.ANY;
​
 public static enum Type {
 ANY,
 SERVLET,
 REACTIVE;
​
 private Type() {
 }
 }
}
OnWebApplicationCondition.class
 private ConditionOutcome isWebApplication(ConditionContext context, AnnotatedTypeMetadata metadata, boolean required) {
 switch(this.deduceType(metadata)) {
 case SERVLET:
 return this.isServletWebApplication(context);
 case REACTIVE:
 return this.isReactiveWebApplication(context);
 default:
 return this.isAnyWebApplication(context, required);
 }
 }

isWebApplication方法可以看出,判断条件是:

springboot内置的自动装配功能
http的编码配置

常规web项目的web.xml里配置一个filter

<filter>
 <filter-name>encodingFilter</filter-name>
 <filter-class>org.springframework.web.filter.CharacterEncodingFilter
 </filter-class>
 <async-supported>true</async-supported>
 <init-param>
 <param-name>encoding</param-name>
 <param-value>UTF-8</param-value>
 </init-param>
 <init-param>
 <param-name>forceEncoding</param-name>
 <param-value>true</param-value>
 </init-param>
 </filter>

自动配置需要满足两个条件

1、能配置CharacterEncodingFilter这个bean

2、能配置encoding和forceEncoding这两个参数

HttpEncodingAutoConfiguration 类

根据条件配置characterEncodingFilter的bean

package org.springframework.boot.autoconfigure.web.servlet;
​
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.http.HttpProperties;
import org.springframework.boot.autoconfigure.http.HttpProperties.Encoding;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.filter.OrderedCharacterEncodingFilter;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.filter.CharacterEncodingFilter;
​
@Configuration
​
//  开启属性注入  通过 @EnableConfigurationProperties 生命,
@EnableConfigurationProperties({HttpProperties.class})
@ConditionalOnWebApplication(
 type = Type.SERVLET
)
​
// 当CharacterEncodingFilter在类路径的条件下
@ConditionalOnClass({CharacterEncodingFilter.class})
​
// 当设置spring.http.encoding=enabled的情况下,如果没有设置则默认为true,既条件符合
@ConditionalOnProperty(
 prefix = "spring.http.encoding",
 value = {"enabled"},
 matchIfMissing = true
)
public class HttpEncodingAutoConfiguration {
 private final Encoding properties;
​
 public HttpEncodingAutoConfiguration(HttpProperties properties) {
 this.properties = properties.getEncoding();
 }
​
 @Bean
 @ConditionalOnMissingBean
 public CharacterEncodingFilter characterEncodingFilter() {
 CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
 filter.setEncoding(this.properties.getCharset().name());
 filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));
 filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));
 return filter;
 }
​
 @Bean
 public HttpEncodingAutoConfiguration.LocaleCharsetMappingsCustomizer localeCharsetMappingsCustomizer() {
 return new HttpEncodingAutoConfiguration.LocaleCharsetMappingsCustomizer(this.properties);
 }
​
 private static class LocaleCharsetMappingsCustomizer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {
 private final Encoding properties;
​
 LocaleCharsetMappingsCustomizer(Encoding properties) {
 this.properties = properties;
 }
​
 public void customize(ConfigurableServletWebServerFactory factory) {
 if (this.properties.getMapping() != null) {
 factory.setLocaleCharsetMappings(this.properties.getMapping());
 }
​
 }
​
 public int getOrder() {
 return 0;
 }
 }
}
相关注释
@ConfigurationPropertie注解
@Component
@ConfigurationProperties(prefix = "author")
@Data
public class AuthorSettings {
​
 private String name;
 private Long age;
}
author:
 name: SSH
 age: 18

使用该注解,可以将配置文件数据引入到类中。把配置文件的信息,读取并自动封装成实体类

@ConditionalOnClass

被引用时@ConditionalOnClass(HelloService.class)

ConditionalOnClass源码类

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Conditional;
​
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnClassCondition.class})
public @interface ConditionalOnClass {
 Class<?>[] value() default {};
​
 String[] name() default {};
}

@Conditional指定的条件成立,才给容器中添加组件,配置的所有内容才生效;

点开该注解 @Conditional({OnClassCondition.class})

OnClassCondition类有继承了FilteringSpringBootCondition

最终还是实现Condition 接口

即根据条件返回是否为true来决定支付注册这个bean

类似的:@ConditionalOnWebApplication、@ConditionalOnProperty 满足一定条件才做什么

1、
@Conditional({OnClassCondition.class})
2、
OnClassCondition extends FilteringSpringBootCondition
3、
FilteringSpringBootCondition extends SpringBootCondition
4、
SpringBootCondition implements Condition
@ConditionalOnProperty

@ConditionalOnProperty(prefix = "hello", value = "enable", matchIfMissing = true)

前缀 hello吗,matchIfMissing 为true 没有配置时也会正常加载,在下边的demo中会被引用

package org.springframework.boot.autoconfigure.condition;
​
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Conditional;
​
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional({OnPropertyCondition.class})
public @interface ConditionalOnProperty {
 /**
 * 数组,获取对应property名称的值,与name不可同时使用
 *
 * @return
 */
 String[] value() default {};
​
 /**
 *  property名称的前缀,可有可无
 * @return
 */
 String prefix() default "";
​
 /**
 * 数组,property完整名称或部分名称(可与prefix组合使用,组成完整的property名称),与value不可同时使用
 * @return
 */
 String[] name() default {};
​
 /**
 * 可与name组合使用,比较获取到的属性值与havingValue给定的值是否相同,相同才加载配置
 * @return
 */
 String havingValue() default "";
​
 /**
 * 缺少该property时是否可以加载。如果为true,没有该property也会正常加载;反之报错
 * @return
 */
 boolean matchIfMissing() default false;
}
​

打开OnPropertyCondition.class类

OnPropertyCondition 类继承 SpringBootCondition

SpringBootCondition 实现Condition 接口

自动装配Demo

不仅是自动装配,更是低耦合的配置

新建项目:

spring-boot-starter-hello

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <parent>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-parent</artifactId>
 <version>2.1.6.RELEASE</version>
 <relativePath/> <!-- lookup parent from repository -->
 </parent>
 <groupId>hand.shen</groupId>
 <artifactId>spring-boot-starter-hello</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 <name>starter-hello</name>
 <description>starter-hello project for Spring Boot</description>
​
 <properties>
 <java.version>1.8</java.version>
 </properties>
​
 <dependencies>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-autoconfigure</artifactId>
 </dependency>
 <dependency>
 <groupId>org.projectlombok</groupId>
 <artifactId>lombok</artifactId>
 <version>1.18.0</version>
 </dependency>
 </dependencies>
 <build>
 <plugins>
 <plugin>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-maven-plugin</artifactId>
 <configuration>
 <skip>true</skip>
 </configuration>
 </plugin>
 </plugins>
 </build>
​
</project>
​
HelloServiceProperties.java
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
​
/**
 * @description: spring-boot 自动配置作为依赖
 *               类型安全的属性获取。
 *               在yml文件中 通过 hello.msg = 来设置,如果不设置默认 hello.meg = word
 *
 * @author: Shenshuaihu
 * @version: 1.0
 * @data: 2019-07-10 23:26
 */
@ConfigurationProperties(prefix = "hello")
@Data
public class HelloServiceProperties {
​
 private static final String MSG = "word";
​
 private String msg = MSG;
}
​
HelloService.java
import lombok.Data;
​
/**
 * @description: 判断依据
 *
 * @author: Shenshuaihu
 * @version: 1.0
 * @data: 2019-07-10 23:30
 */
@Data
public class HelloService {
​
 private String msg;
​
 public String sayHello() {
 return "Hello" + msg;
 }
}
HelloServiceAutoConfiguration.java

@ConditionalXXX注释上文有

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
​
/**
 * @description: 自动配置类
 *
 *      注释 @ConditionalOnClass 会判断HelloService这个类的路径是否存在,
 *          如果没有了 则会自动创建
 *
 * @author: Shenshuaihu
 * @version: 1.0
 * @data: 2019-07-10 23:35
 */
​
@Configuration
@EnableConfigurationProperties(HelloServiceProperties.class)
@ConditionalOnClass(HelloService.class)
@ConditionalOnProperty(prefix = "hello", value = "enable", matchIfMissing = true)
public class HelloServiceAutoConfiguration {
​
 @Autowired
 private HelloServiceProperties helloServiceProperties;
​
 @Bean
 @ConditionalOnMissingBean(HelloService.class)
 public HelloService helloService() {
 HelloService helloService = new HelloService();
 helloService.setMsg(helloServiceProperties.getMsg());
 return helloService;
 }
}
spring.factories

文件路径 resource/MEFA-INF/spring.factories,指定文件装配的类

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.starterhello.HelloServiceAutoConfiguration

mvn install 安装到本地库

在另外一个项目中开启

在pom.xml 中引入jar

 <dependency>
 <groupId>hand.shen</groupId>
 <artifactId>spring-boot-starter-hello</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 </dependency>
​

直接注入helloservice

import com.starterhello.HelloService;
​
 @Autowired
 private HelloService helloService;
​
 @RequestMapping("/")
 String index() {
 return "Hello Spring Boot," +
 " \n 自动装配:" + helloService.sayHello();
 }
​

yml配置

# 自动装配        
hello:
 msg: shenshauihu

如何使用了,如果yml文件上没有配置hello:msg中,会使用默认是helloword,没有配置了hello:msg为shenshuaihu时,则会使用了helloshenshuaihu

原作原理-自动装配-hello.png
参考文档:

https://www.cnblogs.com/niechen/p/9027804.html?utm_source=tuicool&utm_medium=referral

https://www.jianshu.com/p/83693d3d0a65
https://www.jianshu.com/p/1d96508085ff

相关文章

网友评论

    本文标题:SpringBoot运作原理【原创】

    本文链接:https://www.haomeiwen.com/subject/hqerkctx.html