美文网首页Java 杂谈程序员
浅谈Spring boot自动配置运作原理

浅谈Spring boot自动配置运作原理

作者: whoami2019 | 来源:发表于2018-11-21 11:31 被阅读0次

    常规的Java项目,很多都是基于SSH/SSM三大框架搭建,开发起来显得格外的笨重:繁多的配置、低下的开发效率、复杂的部署流程以及第三方技术集成难度大。在上述环境下,Sping boot应运而生,它使用“习惯优于配置”(项目中存在大量的配置,此外还内置一个习惯性的配置,自动配置机制可以读取默认的配置,让你无需手动配置)的理念让你的项目快速运行起来,Spring boot可以不用或者只用很少的spring配置。但是由于Spring boot将配置封装了起来,就丧失了SSH配置的灵活性,造成维护的困难。可是如果我们熟悉Spring boot的自动配置运作原理,修改Spring boot参数配置将和SSH一样。

    在讲解Spring boot自动配置运作原理之前,有必要先温习一下Spring的知识

    Spring boot常用注解

    • @configuation
      声明当前类是一个配置类
    • @ComponentScan
      自动扫描包名下所使用的@Controller、@Service、@Repository、@Component的类,并注册为Bean
    • @PropertySource
      注入配置文件
    • @Conditional
      根据特定条件控制Bean的创建行为,这样我们可以利用这个特性进行一些自动的配置(敲黑板,Spring的自动配置就是利用了这个注解)

    Java配置

    Java配置是Java 4.x推荐的配置方式,可以完全替代xml配置,Java配置也是Spring boot推荐的配置方式。
    Java是通过@Configuation和@Bean来实现

    • @Configuation声明当前类是一个配置文件,相当于一个spring配置的xml文件
    • @Bean注解在方法上,声明当前方法的返回值为一个Bean

    关于何时使用Java配置或者注解配置呢?一般全局配置使用Java配置(如数据库相关配置),业务Bean配置使用注解(如@Controller、@Service、@Component)

    @Enable*注解的工作原理

    如@EnableScheduling、@EnableWebMvc注解,来开启一项功能的支持,从而避免自己配置大量的代码,大大降低使用难度。那么这个神奇功能的原理是什么呢?通过观察@Enable*注解的源码,这些注解里面都有一个@Import注解,@Import注解是用来导入配置类的,这也意味着这些自动开启的实现其实是导入了一些自动配置的Bean。

    组合注解

    随着注解的大量使用,尤其相同的注解越来越多,会显得很啰嗦,这就所谓的模板代码,在Spring设计原则中是要消除的代码。把注解注解到别的注解上形成的新的注解,就叫做组合注解。

    用一个例子结束上面的内容,例子尽可以覆盖以上的内容

    #annotation.properties
    program.type=python
    
    #JavaConditional.java
    @PropertySource("classpath:annotation.properties")
    public class JavaConditional implements Condition{
        @Value("${program.type}")
        private String programType;
        
        @Override
        public boolean matches(ConditionContext arg0, AnnotatedTypeMetadata arg1) {
            //return "java".equals(programType);
            //不知道为什么获取不到programType,为了能够继续调试,先写死
            return true;
        }
    }
    
    #PythonConditional .java
    @PropertySource("classpath:demo.properties")
    public class PythonConditional implements Condition{
    
        @Value("${program.type}")
        private String programType;
        
        @Override
        public boolean matches(ConditionContext arg0, AnnotatedTypeMetadata arg1) {
            return "python".equals(programType);
        }
    }
    
    #JavaConfiguation.java
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Bean
    @Conditional(JavaConditional.class)
    public @interface JavaConfiguation {
        
    }
    
    #PythonConfiguation.java
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Bean
    @Conditional(PythonConditional.class)
    public @interface PythonConfiguation {
        
    }
    
    #ConditionConfig.java
    @Configuration
    public class ConditionConfig {
        @JavaConfiguation
        public ProgramLearnService javaLearnService() {
            return new JavaLearnServiceImpl();
        }
        
        @PythonConfiguation
        public ProgramLearnService pythonLearnService() {
            return new PythonLearnServiceImpl();
        }
    }
    
    #ProgramLearnService.java
    public interface ProgramLearnService {
        public String learn();
    }
    
    #JavaLearnServiceImpl.java
    public class JavaLearnServiceImpl implements ProgramLearnService {
        @Override
        public String learn() {
            return "I learn java";
        }
    }
    
    #PythonLearnServiceImpl.java
    public class PythonLearnServiceImpl implements ProgramLearnService {
        @Override
        public String learn() {
            return "I learn python";
        }
    }
    
    #App.java
    public class App {
        public static void main(String[] args) {
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
                    ConditionConfig.class);
            
            ProgramLearnService pl = context.getBean(ProgramLearnService.class);
            System.out.println(pl.learn());
            context.close();
        }
    }
    

    分界线 -----------,以下开始Spring boot部分

    类型安全的配置(基于properties)

    在常规spring环境下,注入properties文件里值的方式,通过@propertiesSource指明值的位置,然后通过@Value注入值,在Spring boot中只需要把值写到application.properties, 直接用@Value注入值即可。

    尽管方便了一点,可是在实际项目中,配置有很多,就要配置很多的@Value,显得格外麻烦,所以Spring boot提供了基于类型安全的配置方式,通过@ConfiguationProperties将propertis属性和一个bean及其属性关联。

    #application.properties
    author.name=jack
    author.age=18
    #AuthorSettings.java
    @Component
    @ConfigurationProperties(prefix="author")
    public class AuthorSettings {
         private String name;
         private Integer age;
         //setter、getter
    }
    
    #通过@ConfigurationProperties加载Propertis文件内的配置,通过prefix属性指定properties内配置的前缀,通过locations属性指定properties文件的位置,将AuthorSettings这个Bean注入到其他内即可使用,方便吧
    

    Spring boot运作原理

    关于Spring boot的运作原理,得从@SpringBootApplication讲起,这个注解是一个组合注解,它的核心功能是由@EnableAutoConfiguration注解提供的。来看看@EnableAutoConfiguration源码

    @SuppressWarnings("deprecation")
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @AutoConfigurationPackage
    @Import(EnableAutoConfigurationImportSelector.class)
    public @interface EnableAutoConfiguration {
        String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
        Class<?>[] exclude() default {};
        String[] excludeName() default {};
    }
    

    这里的关键功能是@Import注解导入的配置功能,EnableAutoConfigurationImportSelector使用SpringFactoriesLoader.loadFactoryNames方法来扫描具有MEAT-INF/spring.factories文件的jar包(1.5版本以前使用EnableAutoConfigurationImportSelector类,1.5以后这个类废弃了使用的是AutoConfigurationImportSelector类),而我们的spring-boot-autoconfigure-1.5.3.RELEASE.jar下面就有spring.factories文件,此文件中声明了有哪些自动配置。
    spring.factories文件:

    # Initializers
    org.springframework.context.ApplicationContextInitializer=\
    org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer
     
    # Application Listeners
    org.springframework.context.ApplicationListener=\
    org.springframework.boot.autoconfigure.BackgroundPreinitializer
     
    # Auto Configure
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
    org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
    org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
    org.springframework.boot.autoconfigure.MessageSourceAutoConfiguration,\
    org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration,\
    org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
    org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
    org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
    org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
    

    核心注解

    打开任意*AutoConfiguration文件,一般都有下面的条件注解,在spring-boot-autoconfigure-1.5.3.RELEASE.jar的org.springframework.boot.autoconfigure.condition包下条件注解如下:

    @ConditionalOnBean:当前容器有指定Bean的条件下。
    @ConditionalOnClass:当前类路径下有指定的类的条件下。
    @ConditionalOnExpression:基于SpEL表达式作为判断条件。
    @ConditionalOnJava:基于JVM版本作为判断条件。
    @ConditionalOnJndi:在JNDI存在的条件下查找指定的位置。
    @ConditionalOnMissingBean:当容器里没有指定Bean的情况下。
    @ConditionalOnMissingClass:当类路径下没有指定的类的条件下。
    @ConditionalOnNotWebApplication:当前项目不是WEB项目的条件下。
    @ConditionalOnProperty:指定属性是否有指定的值。
    @ConditionalOnResource:类路径是否有指定的值。
    @ConditionalOnSingleCandidate:当指定Bean在容器中只有一个,或者虽然有多个但 是指定首选的Bean。
    @ConditionalOnWebApplication:当前项目是WEB项目的条件下。
    这些注解都组合了@Conditional元注解,只是使用了不同的条件(Conditional),Spring 条件注解(@Conditional)我们介绍过根据不同条件创建不同Bean

    实战

    最后,写一个例子,实现自动装载,而且写一个starter pom

    spring-boot-starter-hello工程

    # pom.xml
    <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>
      <groupId>org.springwork.boot</groupId>
      <artifactId>spring-boot-starter-hello</artifactId>
      <version>0.0.1-SNAPSHOT</version>
       <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
            <version>1.3.0.M1</version>
        </dependency>
      </dependencies>
    </project>
    
    
    # HelloServiceProperties.java
    package hello;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    @ConfigurationProperties(prefix="hello")
    public class HelloServiceProperties {
        private String msg = "world";
        //setter、getter
    }
    
    
    # HelloService.java
    package hello;
    public class HelloService {
        private String msg;
        
        public String sayHello() {
            return "hello " + msg;
        }
        //setter、getter
    }
    
    
    # HelloServiceAutoConfiguation.java
    package hello;
    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.context.properties.EnableConfigurationProperties;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    @EnableConfigurationProperties(HelloServiceProperties.class)
    @ConditionalOnClass(HelloService.class)
    //@ConditionalOnProperty(prefix="hello", value="enabled", matchIfMissing=true)
    public class HelloServiceAutoConfiguation {
        @Autowired
        private HelloServiceProperties helloServiceProperties;
        @Bean
        @ConditionalOnMissingBean(HelloService.class)
        public HelloService helloService() {
            HelloService helloService = new HelloService();
            helloService.setMsg(helloServiceProperties.getMsg());
            return helloService;
        }
    }
    
    
    # spring.factories(放在src.main.resource/META-INF路径下)
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    hello.HelloServiceAutoConfiguation
    

    其他工程,依赖spring-boot-starter-hello工程

    # pom.xml
    <dependency>
        <groupId>org.springwork.boot</groupId>
        <artifactId>spring-boot-starter-hello</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
    
    #SpringbootController.java
    package base.controller;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import hello.HelloService;
    
    @RestController
    @RequestMapping("springboot")
    public class SpringbootController {
        @Autowired
        HelloService helloService;
        @RequestMapping("autoConfigTest")
        public String autoConfigTest() {
            return helloService.sayHello();
        }
    }
    
    测试结果

    参考文献《JavaEE开发的颠覆者 Spring Boot实战》汪云飞编著

    相关文章

      网友评论

        本文标题:浅谈Spring boot自动配置运作原理

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