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

常规的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

  4.0.0
  org.springwork.boot
  spring-boot-starter-hello
  0.0.1-SNAPSHOT
   
    
        org.springframework.boot
        spring-boot-autoconfigure
        1.3.0.M1
    
  



# 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

    org.springwork.boot
    spring-boot-starter-hello
    0.0.1-SNAPSHOT


#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自动配置运作原理)