Spring Boot运行原理

概述

    本文主要写了下Spring Boot运行原理,还有一个小例子。

    Spring4.x提供了基于条件来配置Bean的能力,而Spring Boot的实现也是基于这一原理的。

    Spring Boot关于自动配置的源码在spring-boot-autoconfigure-1.3.0.x.jar内。如果想知道Spring Boot为我们做了哪些自动配置,可以查看这里的源码。

    可以通过下面的几种方式查看当前项目中已启用可未启用的自动配置的报告:

        1:运行jar时添加--debug参数:java -jar xx.jar --debug。

        2:在application.properties中设置属性:debug=true。

        3:也可以在开发工具中配置运行时参数,此处就不再截图了。


 

Spring Boot运作原理

    关于Spring Boot的运作原理,还是要看@SpringBootApplication注解,这个注解是一个组合注解,它的核心功能是由@EnableAutoConfiguration注解提供的。

    @EnableAutoConfiguration注解的源码如下:

1 //
 2 // Source code recreated from a .class file by IntelliJ IDEA
 3 // (powered by Fernflower decompiler)
 4 //
 5 
 6 package org.springframework.boot.autoconfigure;
 7 
 8 import java.lang.annotation.Documented;
 9 import java.lang.annotation.ElementType;
10 import java.lang.annotation.Inherited;
11 import java.lang.annotation.Retention;
12 import java.lang.annotation.RetentionPolicy;
13 import java.lang.annotation.Target;
14 import org.springframework.boot.autoconfigure.AutoConfigurationPackages.Registrar;
15 import org.springframework.context.annotation.Import;
16 
17 @Target({ElementType.TYPE})
18 @Retention(RetentionPolicy.RUNTIME)
19 @Documented
20 @Inherited
21 @Import({EnableAutoConfigurationImportSelector.class, Registrar.class})
22 public @interface EnableAutoConfiguration {
23     Class<?>[] exclude() default {};
24 
25     String[] excludeName() default {};
26 }

    这里的关键功能是@Import注解导入的配置功能,EnableAutoConfigurationImportSelector使用SpringFactoriesLoador.loadFactoryNames方法来扫描具有META-INF/spring.factories文件的jar包。

    spring.factories文件中声名了一些自动配置,如:

        

springboot jar包冲突原理 springboot jar运行原理_spring

    打开上面任意一个类,一般都有下面的条件注解,在spring-boot-autoconfigure-1.3.0.x.jar的org.springframwork.boot.autoconfigure.condition包下,条件注解如下。

        @ConditionalOnBean:当容器里有指定的Bean的条件下。

        @ConditionalOnClass:当类路径下有指定的类的条件下。
        @ConditionalOnExpression:基于SpEL表达式作为判断条件。
        @ConditionalOnOnJava:基于JVM版本作为判断条件。
        @ConditionalOnJndi:在JNDI存在的条件下查找指定的位置。
        @ConditionalOnMissingBean:当容器里没有指定Bean的情况下。
        @ConditionalOnMissingClass:当类路径下没有指定的类的条件下。
        @ConditionalOnNotWebApplication:当前项目不是Web项目的条件下。
        @ConditionalOnProperty:指定的属性是否有指定的值。
        @ConditionalOnResource:类路径是否有指定的值。
        @ConditionalOnSingleCandidate:当·指定Bean在容器中只有一个,或者虽然有多个但是指定首选的Bean。
        @ConditionalOnWenApplication:当前项目是Web项目的条件下。

    这些注解都是组合了@Conditional元注解,只是使用了不同的条件。

    下面我们来分析一下@ConditionalOnWebApplication注解。

springboot jar包冲突原理 springboot jar运行原理_springboot jar包冲突原理_02

springboot jar包冲突原理 springboot jar运行原理_springboot jar包冲突原理_03

1 //
 2 // Source code recreated from a .class file by IntelliJ IDEA
 3 // (powered by Fernflower decompiler)
 4 //
 5 
 6 package org.springframework.boot.autoconfigure.condition;
 7 
 8 import java.lang.annotation.Documented;
 9 import java.lang.annotation.ElementType;
10 import java.lang.annotation.Retention;
11 import java.lang.annotation.RetentionPolicy;
12 import java.lang.annotation.Target;
13 import org.springframework.context.annotation.Conditional;
14 
15 @Target({ElementType.TYPE, ElementType.METHOD})
16 @Retention(RetentionPolicy.RUNTIME)
17 @Documented
18 @Conditional({OnWebApplicationCondition.class})
19 public @interface ConditionalOnWebApplication {
20 }

ConditionalOnWebApplication源码

 

     从源码可以看出,此注解使用的条件是OnWebApplicationCondition,下面我们来看看这个条件是如何构造的:

springboot jar包冲突原理 springboot jar运行原理_springboot jar包冲突原理_02

springboot jar包冲突原理 springboot jar运行原理_springboot jar包冲突原理_03

1 //
 2 // Source code recreated from a .class file by IntelliJ IDEA
 3 // (powered by Fernflower decompiler)
 4 //
 5 
 6 package org.springframework.boot.autoconfigure.condition;
 7 
 8 import org.springframework.context.annotation.ConditionContext;
 9 import org.springframework.core.annotation.Order;
10 import org.springframework.core.type.AnnotatedTypeMetadata;
11 import org.springframework.util.ClassUtils;
12 import org.springframework.util.ObjectUtils;
13 import org.springframework.web.context.WebApplicationContext;
14 import org.springframework.web.context.support.StandardServletEnvironment;
15 
16 @Order(-2147483628)
17 class OnWebApplicationCondition extends SpringBootCondition {
18     private static final String WEB_CONTEXT_CLASS = "org.springframework.web.context.support.GenericWebApplicationContext";
19 
20     OnWebApplicationCondition() {
21     }
22 
23     public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
24         boolean webApplicationRequired = metadata.isAnnotated(ConditionalOnWebApplication.class.getName());
25         ConditionOutcome webApplication = this.isWebApplication(context, metadata);
26         if (webApplicationRequired && !webApplication.isMatch()) {
27             return ConditionOutcome.noMatch(webApplication.getMessage());
28         } else {
29             return !webApplicationRequired && webApplication.isMatch() ? ConditionOutcome.noMatch(webApplication.getMessage()) : ConditionOutcome.match(webApplication.getMessage());
30         }
31     }
32 
33     private ConditionOutcome isWebApplication(ConditionContext context, AnnotatedTypeMetadata metadata) {
34         if (!ClassUtils.isPresent("org.springframework.web.context.support.GenericWebApplicationContext", context.getClassLoader())) {
35             return ConditionOutcome.noMatch("web application classes not found");
36         } else {
37             if (context.getBeanFactory() != null) {
38                 String[] scopes = context.getBeanFactory().getRegisteredScopeNames();
39                 if (ObjectUtils.containsElement(scopes, "session")) {
40                     return ConditionOutcome.match("found web application 'session' scope");
41                 }
42             }
43 
44             if (context.getEnvironment() instanceof StandardServletEnvironment) {
45                 return ConditionOutcome.match("found web application StandardServletEnvironment");
46             } else {
47                 return context.getResourceLoader() instanceof WebApplicationContext ? ConditionOutcome.match("found web application WebApplicationContext") : ConditionOutcome.noMatch("not a web application");
48             }
49         }
50     }
51 }

OnWebApplicationCondition源码

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

        1:GenericWebApplicationContext是否在类路径中;

        2:容器里是否有名为session的scope;

        3:当前容器的Enviroment是否为StandardServletEnvironment;

        4:当前的ResourceLoader是否为WebApplicationContext(ResourceLoador是ApplicationContext的顶级接口之一);

        5:我们需要构造ConditionOutcome类的对象来帮助我们,最终通过ContitionOutcome.isMatch方法来返回布尔值来确定条件;

实例分析

    通过上面写的,我们初步了解了Spring Boot的运作原理和主要的条件注解,下面来分析一个简单的Spring Boot内置的自动配置功能:http的编码配置。

    在常规项目中,http编码一般是在web.xml中配置一个filter,如下:

1 <filter>
 2     <filter-name>encodingFilter</filter-name>
 3     <filter-class>
 4         org.springframework.web.filter.CharacterEncodingFilter
 5     </filter-class>
 6     <init-param>
 7         <param-name>encoding</param-name>
 8         <param-value>UTF-8</param-value>
 9     </init-param>
10     <init-param>
11         <param-name>forceEncoding</param-name>
12         <param-value>true</param-value>
13     </init-param>
14 </filter>

    由上可见,自动配置要满足两个条件:

        1:能配置CharacterEncodingFilter这个Bean;

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

 

    配置参数:

        Spring Boot的自动配置是基于类型安全的配置,关于http编码的配置在HttpEncodingProperties类中,源码如下:

springboot jar包冲突原理 springboot jar运行原理_springboot jar包冲突原理_02

springboot jar包冲突原理 springboot jar运行原理_springboot jar包冲突原理_03

1 //
 2 // Source code recreated from a .class file by IntelliJ IDEA
 3 // (powered by Fernflower decompiler)
 4 //
 5 
 6 package org.springframework.boot.autoconfigure.web;
 7 
 8 import java.nio.charset.Charset;
 9 import org.springframework.boot.context.properties.ConfigurationProperties;
10 
11 @ConfigurationProperties(
12         prefix = "spring.http.encoding"//在application.properties配置的时候前缀是spring.http.encoding。
13 )
14 public class HttpEncodingProperties {
15     public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");//默认编码方式UTF-8,如果要修改可以使用:spring.http.encoding.charset=编码。
16     private Charset charset;
17     private boolean force;
18 
19     public HttpEncodingProperties() {
20         this.charset = DEFAULT_CHARSET;
21         this.force = true;//设置forceEncoding,默认为true,若果要修改可以使用spring.http.encoding.force=false。
22     }
23 
24     public Charset getCharset() {
25         return this.charset;
26     }
27 
28     public void setCharset(Charset charset) {
29         this.charset = charset;
30     }
31 
32     public boolean isForce() {
33         return this.force;
34     }
35 
36     public void setForce(boolean force) {
37         this.force = force;
38     }
39 }

HttpEncodingProperties源码

 

     配置Bean:

        通过调用上面的配置,并根据条件配置CharacterEncodingFilter的Bean,源码如下:

springboot jar包冲突原理 springboot jar运行原理_springboot jar包冲突原理_02

springboot jar包冲突原理 springboot jar运行原理_springboot jar包冲突原理_03

1 //
 2 // Source code recreated from a .class file by IntelliJ IDEA
 3 // (powered by Fernflower decompiler)
 4 //
 5 
 6 package org.springframework.boot.autoconfigure.web;
 7 
 8 import org.springframework.beans.factory.annotation.Autowired;
 9 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
10 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
11 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
12 import org.springframework.boot.context.properties.EnableConfigurationProperties;
13 import org.springframework.boot.context.web.OrderedCharacterEncodingFilter;
14 import org.springframework.context.annotation.Bean;
15 import org.springframework.context.annotation.Configuration;
16 import org.springframework.web.filter.CharacterEncodingFilter;
17 
18 @Configuration
19 @EnableConfigurationProperties({HttpEncodingProperties.class})//开启属性注入,通过@EnableConfigurationProperties声明,使用@Autowired注入。
20 @ConditionalOnClass({CharacterEncodingFilter.class})//当CharacterEncodingFilter在类路径的条件下
21 @ConditionalOnProperty(
22         prefix = "spring.http.encoding",
23         value = {"enabled"},//当设置spring.http.encoding=enabled的情况下
24         matchIfMissing = true//如果没有设置,则默认为true,即条件符合。
25 )
26 public class HttpEncodingAutoConfiguration {
27     @Autowired
28     private HttpEncodingProperties httpEncodingProperties;
29 
30     public HttpEncodingAutoConfiguration() {
31     }
32 
33     @Bean//使用Java配置的方式配置CharacterEncodingFilter这个bean。
34     @ConditionalOnMissingBean({CharacterEncodingFilter.class})//如果容器中没有这个bean的时候新建bean。
35     public CharacterEncodingFilter characterEncodingFilter() {
36         CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
37         filter.setEncoding(this.httpEncodingProperties.getCharset().name());
38         filter.setForceEncoding(this.httpEncodingProperties.isForce());
39         return filter;
40     }
41 }

HttpEncodingAutoConfiguration源码

 

自己写一个starter pom

    我们也可以仿照上面http编码配置的例子自己写一个自动配置。代码如下:

springboot jar包冲突原理 springboot jar运行原理_springboot jar包冲突原理_02

springboot jar包冲突原理 springboot jar运行原理_springboot jar包冲突原理_03

1 package com.wisely.spring_boot_starter_hello;
 2 
 3 import org.springframework.boot.context.properties.ConfigurationProperties;
 4 
 5 /**
 6  * 属性配置
 7  */
 8 @ConfigurationProperties(prefix = "hello")
 9 public class HelloServiceProperties {
10 
11     private static final String MSG = "world";
12     
13     private String msg = MSG;
14 
15     public String getMsg() {
16         return msg;
17     }
18 
19     public void setMsg(String msg) {
20         this.msg = msg;
21     }
22 }

属性配置

springboot jar包冲突原理 springboot jar运行原理_springboot jar包冲突原理_02

springboot jar包冲突原理 springboot jar运行原理_springboot jar包冲突原理_03

1 package com.wisely.spring_boot_starter_hello;
 2 
 3 /**
 4  * 判断依据类
 5  */
 6 public class HelloService {
 7     private String msg;
 8 
 9     public String sayHello() {
10         return "Hello" + msg;
11     }
12 
13     public String getMsg() {
14         return msg;
15     }
16 
17     public void setMsg(String msg) {
18         this.msg = msg;
19     }
20 }

判断依据类

springboot jar包冲突原理 springboot jar运行原理_springboot jar包冲突原理_02

springboot jar包冲突原理 springboot jar运行原理_springboot jar包冲突原理_03

1 package com.wisely.spring_boot_starter_hello;
 2 
 3 import org.springframework.beans.factory.annotation.Autowired;
 4 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
 5 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 6 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 7 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 8 import org.springframework.context.annotation.Bean;
 9 import org.springframework.context.annotation.Configuration;
10 
11 /**
12  * 自动配置类
13  */
14 @Configuration
15 @EnableConfigurationProperties(HelloServiceProperties.class)
16 @ConditionalOnClass(HelloService.class)
17 @ConditionalOnProperty(prefix = "hello", value = "enabled", matchIfMissing = true)
18 public class HelloServiceAutoConfiguration {
19 
20     @Autowired
21     private HelloServiceProperties helloServiceProperties;
22 
23     @Bean
24     @ConditionalOnMissingBean(HelloService.class)
25     public HelloService helloService() {
26         HelloService helloService = new HelloService();
27         helloService.setMsg(helloServiceProperties.getMsg());
28         return helloService;
29     }
30 }

自动配置类

    在src/main/resources下新建META-INF/spring.factories,并添加代码:

    

springboot jar包冲突原理 springboot jar运行原理_java_16

    然后新建一个项目,在pom.xml添加依赖

    

springboot jar包冲突原理 springboot jar运行原理_spring_17

    最后运行看效果。

springboot jar包冲突原理 springboot jar运行原理_springboot jar包冲突原理_02

springboot jar包冲突原理 springboot jar运行原理_springboot jar包冲突原理_03

1 package com.wisely.ch6_5;
 2 
 3 import org.springframework.beans.factory.annotation.Autowired;
 4 import org.springframework.boot.SpringApplication;
 5 import org.springframework.boot.autoconfigure.SpringBootApplication;
 6 import org.springframework.web.bind.annotation.RequestMapping;
 7 import org.springframework.web.bind.annotation.RestController;
 8 
 9 import com.wisely.spring_boot_starter_hello.HelloService;
10 @RestController
11 @SpringBootApplication
12 public class Ch65Application {
13     
14     @Autowired
15     HelloService helloService;
16     
17     @RequestMapping("/")
18     public String index(){
19         return helloService.sayHello();
20     }
21 
22     public static void main(String[] args) {
23         SpringApplication.run(Ch65Application.class, args);
24     }
25 }

运行

 

欢迎提出意见或建议!!!