zoukankan      html  css  js  c++  java
  • 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文件中声名了一些自动配置,如:

            

        打开上面任意一个类,一般都有下面的条件注解,在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注解。

     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,下面我们来看看这个条件是如何构造的:

     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类中,源码如下:

     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,源码如下:

     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编码配置的例子自己写一个自动配置。代码如下:

     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 }
    属性配置
     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 }
    判断依据类
     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,并添加代码:

        

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

        

        最后运行看效果。

     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 }
    运行
  • 相关阅读:
    C++使用静态类成员时出现的一个问题
    C++中的const_cast
    【位运算与嵌入式编程】
    电压取反电路
    bzoj4769
    初赛
    noip2011day2
    uva1252
    codeforces 703d
    poj[1734]
  • 原文地址:https://www.cnblogs.com/gaofei-1/p/8976781.html
Copyright © 2011-2022 走看看