淺談Spring boot自動配置運作原理

常規(guī)的Java項目,很多都是基于SSH/SSM三大框架搭建,開發(fā)起來顯得格外的笨重:繁多的配置、低下的開發(fā)效率、復(fù)雜的部署流程以及第三方技術(shù)集成難度大。在上述環(huán)境下,Sping boot應(yīng)運而生,它使用“習(xí)慣優(yōu)于配置”(項目中存在大量的配置,此外還內(nèi)置一個習(xí)慣性的配置,自動配置機制可以讀取默認(rèn)的配置,讓你無需手動配置)的理念讓你的項目快速運行起來,Spring boot可以不用或者只用很少的spring配置。但是由于Spring boot將配置封裝了起來,就喪失了SSH配置的靈活性,造成維護(hù)的困難。可是如果我們熟悉Spring boot的自動配置運作原理,修改Spring boot參數(shù)配置將和SSH一樣。

在講解Spring boot自動配置運作原理之前,有必要先溫習(xí)一下Spring的知識

Spring boot常用注解

  • @configuation
    聲明當(dāng)前類是一個配置類
  • @ComponentScan
    自動掃描包名下所使用的@Controller、@Service、@Repository、@Component的類,并注冊為Bean
  • @PropertySource
    注入配置文件
  • @Conditional
    根據(jù)特定條件控制Bean的創(chuàng)建行為,這樣我們可以利用這個特性進(jìn)行一些自動的配置(敲黑板,Spring的自動配置就是利用了這個注解)

Java配置

Java配置是Java 4.x推薦的配置方式,可以完全替代xml配置,Java配置也是Spring boot推薦的配置方式。
Java是通過@Configuation和@Bean來實現(xiàn)

  • @Configuation聲明當(dāng)前類是一個配置文件,相當(dāng)于一個spring配置的xml文件
  • @Bean注解在方法上,聲明當(dāng)前方法的返回值為一個Bean

關(guān)于何時使用Java配置或者注解配置呢?一般全局配置使用Java配置(如數(shù)據(jù)庫相關(guān)配置),業(yè)務(wù)Bean配置使用注解(如@Controller、@Service、@Component)

@Enable*注解的工作原理

如@EnableScheduling、@EnableWebMvc注解,來開啟一項功能的支持,從而避免自己配置大量的代碼,大大降低使用難度。那么這個神奇功能的原理是什么呢?通過觀察@Enable*注解的源碼,這些注解里面都有一個@Import注解,@Import注解是用來導(dǎo)入配置類的,這也意味著這些自動開啟的實現(xiàn)其實是導(dǎo)入了一些自動配置的Bean。

組合注解

隨著注解的大量使用,尤其相同的注解越來越多,會顯得很啰嗦,這就所謂的模板代碼,在Spring設(shè)計原則中是要消除的代碼。把注解注解到別的注解上形成的新的注解,就叫做組合注解。

用一個例子結(jié)束上面的內(nèi)容,例子盡可以覆蓋以上的內(nèi)容

#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,為了能夠繼續(xù)調(diào)試,先寫死
        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)

在常規(guī)spring環(huán)境下,注入properties文件里值的方式,通過@propertiesSource指明值的位置,然后通過@Value注入值,在Spring boot中只需要把值寫到application.properties, 直接用@Value注入值即可。

盡管方便了一點,可是在實際項目中,配置有很多,就要配置很多的@Value,顯得格外麻煩,所以Spring boot提供了基于類型安全的配置方式,通過@ConfiguationProperties將propertis屬性和一個bean及其屬性關(guān)聯(lián)。

#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文件內(nèi)的配置,通過prefix屬性指定properties內(nèi)配置的前綴,通過locations屬性指定properties文件的位置,將AuthorSettings這個Bean注入到其他內(nèi)即可使用,方便吧

Spring boot運作原理

關(guān)于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 {};
}

這里的關(guān)鍵功能是@Import注解導(dǎo)入的配置功能,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:當(dāng)前容器有指定Bean的條件下。
@ConditionalOnClass:當(dāng)前類路徑下有指定的類的條件下。
@ConditionalOnExpression:基于SpEL表達(dá)式作為判斷條件。
@ConditionalOnJava:基于JVM版本作為判斷條件。
@ConditionalOnJndi:在JNDI存在的條件下查找指定的位置。
@ConditionalOnMissingBean:當(dāng)容器里沒有指定Bean的情況下。
@ConditionalOnMissingClass:當(dāng)類路徑下沒有指定的類的條件下。
@ConditionalOnNotWebApplication:當(dāng)前項目不是WEB項目的條件下。
@ConditionalOnProperty:指定屬性是否有指定的值。
@ConditionalOnResource:類路徑是否有指定的值。
@ConditionalOnSingleCandidate:當(dāng)指定Bean在容器中只有一個,或者雖然有多個但 是指定首選的Bean。
@ConditionalOnWebApplication:當(dāng)前項目是WEB項目的條件下。
這些注解都組合了@Conditional元注解,只是使用了不同的條件(Conditional),Spring 條件注解(@Conditional)我們介紹過根據(jù)不同條件創(chuàng)建不同Bean

實戰(zhàn)

最后,寫一個例子,實現(xiàn)自動裝載,而且寫一個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();
    }
}
測試結(jié)果

參考文獻(xiàn)《JavaEE開發(fā)的顛覆者 Spring Boot實戰(zhàn)》汪云飛編著

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,256評論 6 342
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,506評論 19 139
  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架,建立于...
    Hsinwong閱讀 22,931評論 1 92
  • 《說文》:“剝?nèi)~F革者謂之皮?!毕蟊粍?nèi)〉谜麎K獸皮。 所有從“皮”的字都有一個特點──像獸皮那樣成一攤開的平面: ...
    求其_ae23閱讀 601評論 0 0
  • 寶媽敘述:寶寶下周就過一歲生日了,這幾天剛會走路,走的還很不穩(wěn),踉踉蹌蹌就會摔倒。今天寶寶在外面玩的時候有比她大的...
    育兒顧問愉悅媽媽閱讀 360評論 0 0

友情鏈接更多精彩內(nèi)容