我們知道,spring boot自動配置功能可以根據不同情況來決定spring配置應該用哪個,不應該用哪個,舉個例子:
- Spring的JdbcTemplate是不是在Classpath里面?如果是,並且DataSource也存在,就自動配置一個JdbcTemplate的Bean
- Thymeleaf是不是在Classpath里面?如果是,則自動配置Thymeleaf的模板解析器、視圖解析器、模板引擎
那個這個是怎么實現的呢?原因就在於它利用了Spring的條件化配置,條件化配置允許配置存在於應用中,但是在滿足某些特定條件前會忽略這些配置。
要實現條件化配置我們要用到@Conditional條件化注解。
本篇隨便講從如下三個方面進行展開:
- @Conditional小例子,來說明條件化配置的實現方式
- spring boot 的條件化配置詳解
- spring boot 自動配置源碼分析
- 自己動手實現spring boot starter pom
一、@Conditional小例子
我們知道在windows下顯示列表的命令是dir,而在linux系統下顯示列表的命令是ls,基於條件配置,我們可以實現在不同的操作系統下返回不同的值。
- 判斷條件定義
- )windows下的判定條件
/** * 實現spring 的Condition接口,並且重寫matches()方法,如果操作系統是windows就返回true * */ public class WindowsCondition implements Condition{ @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { return context.getEnvironment().getProperty("os.name").contains("Windows"); } }
- )linux下的判定條件
/** * 實現spring 的Condition接口,並且重寫matches()方法,如果操作系統是linux就返回true * */ public class LinuxCondition implements Condition{ @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { return context.getEnvironment().getProperty("os.name").contains("Linux"); } }
- )windows下的判定條件
- 不同系統下的Bean的類
- )接口
public interface ListService { public String showListLine(); }
- )windows下的Bean類
public class WindowsListService implements ListService{ @Override public String showListLine() { return "dir"; } }
- )linux下的Bean的類
public class LinuxListService implements ListService{ @Override public String showListLine() { return "ls"; } }
- )接口
- 配置類
@Configuration public class ConditionConfig { /** * 通過@Conditional 注解,符合windows條件就返回WindowsListService實例 * */ @Bean @Conditional(WindowsCondition.class) public ListService windonwsListService() { return new WindowsListService(); } /** * 通過@Conditional 注解,符合linux條件就返回LinuxListService實例 * */ @Bean @Conditional(LinuxCondition.class) public ListService linuxListService() { return new LinuxListService(); } }
- 測試類
public class ConditionTest { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConditionConfig.class); ListService listService = context.getBean(ListService.class); System.out .println(context.getEnvironment().getProperty("os.name") + " 系統下的列表命令為: " + listService.showListLine()); } }
- 運行測試類,由於我的是windows7 系統,因此結果是
Windows 7 系統下的列表命令為: dir
如果你的是linux系統,則結果就會是
Linux 系統下的列表命令為: ls
二、spring boot 的條件化配置
在spring boot項目中會存在一個名為spring-boot-autoconfigure的jar包
條件化配置就是在這個jar里面實現的,它用到了如下的條件化注解,這些注解都是以@ConditionalOn開頭的,他們都是應用了@Conditional的組合注解:
接下來我們看個源碼的列子:
以JdbcTemplateAutoConfiguration為例,它里面有這段代碼:
@Bean @Primary @ConditionalOnMissingBean(JdbcOperations.class) public JdbcTemplate jdbcTemplate() { return new JdbcTemplate(this.dataSource); }
只有在不存在JdbcOperations(如果查看JdbcTemplate的源碼,你會發現JdbcTemplate類實現了JdbcOperations接口)實例的時候,才會初始化一個JdbcTemplate 的Bean。
基於以上內容,我們就可以閱讀自動配置相關的源碼了。
三、spring boot 自動配置源碼分析
spring boot項目的啟動類用的注解--@SpringBootApplication是一個組合注解,其中@EnableAutoConfiguration是自動配置相關的。
而這個@EnableAutoConfiguration注解里面有個@Import注解導入了EnableAutoConfigurationImportSelector用來實現具體的功能
(注:由於我本地的spring boot版本不是最新的,這里的EnableAutoConfigurationImportSelector已經不建議使用了,新版本可能已經換成了其他類,但是不影響我們看代碼)
這個類繼承了AutoConfigurationImportSelector
進入父類,里面有個方法selectImports()調用了方法getCandidateConfigurations(),進而調用了SpringFactoriesLoader.loadFactoryNames()方法
在SpringFactoriesLoader.loadFactoryNames()方法里面,我們看到會查詢META-INF/spring.factories這個配置文件
SpringFactoriesLoader.loadFactoryNames方法會掃描具有META-INF/spring.factories文件的jar包,而我們的spring-boot-autoconfigure.jar里面就有一個這樣的文件,此文件中聲明了具體有哪些自動配置:
我們上面提到的JdbcTemplateAutoConfiguration自動配置類就在里面。
四、編寫自己的spring boot starter pom
接下來,我們就來寫一個簡單的spring boot starter pom。
步驟如下:
- 新建starter maven項目spring-boot-starter-hello
- 修改pom文件
<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>com.sam</groupId> <artifactId>spring-boot-starter-hello</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <dependencies> <!-- 這里需要引入spring boot的自動配置作為依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> <version>1.5.1.RELEASE</version> </dependency> </dependencies> </project>
- 屬性配置
/** * @ConfigurationProperties * 自動匹配application.properties文件中hello.msg的值,然后賦值給類屬性msg,這里的msg默認值為“spring boot” * */ @ConfigurationProperties(prefix="hello") public class HelloServiceProperties { private static final String MSG = "spring boot"; private String msg = MSG; public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } }
- 判定依據類
/** * 后面的代碼會依據此類是否存在,來決定是否生產對應的Bean * */ public class HelloService { private String msg; public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public String sayHello() { return "hello " + msg; } }
- 自動配置類
@Configuration @EnableConfigurationProperties(HelloServiceProperties.class) @ConditionalOnClass(HelloService.class) @ConditionalOnProperty(prefix = "hello", matchIfMissing = true, value = "enabled") public class HelloServiceAutoConfiguration { @Autowired HelloServiceProperties helloServiceProperties; @Bean @ConditionalOnMissingBean(HelloService.class) public HelloService helloService() { HelloService service = new HelloService(); service.setMsg(helloServiceProperties.getMsg()); return service; } }
根據HelloServiceProperties提供的參數,並通過@ConditionalOnClass(HelloService.class)判定HelloService這個類在Classpath中是否存在,存在並且還沒有對應的Bean,就生成對應的helloService Bean
- 注冊配置,需要到META-INF/spring.factories文件中注冊改自動配置類:在src/main/source目錄下新建改文件,然后進行配置。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.sam.spring_boot_starter_hello.HelloServiceAutoConfiguration
- 對該工程進行mvn clean install,將jar推送到本地maven倉庫,供后續使用。 使用starter ,使用我們這個starter 需要新建一個或使用既存的一個spring boot工程(這里我用的是既存的),然后
- )修改pom,引入上述的依賴
<dependency> <groupId>com.sam</groupId> <artifactId>spring-boot-starter-hello</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
- )實現controller
@RestController public class HelloController { //代碼中沒有配置這個helloService Bean,但是自動配置能夠幫忙實例化,因此可以直接注入 @Autowired HelloService helloService; @RequestMapping(value="/helloService") public String sayHello() { return helloService.sayHello(); } }
- )頁面訪問/helloService接口
- )在application.properties里面配置hello.msg=sam,然后再次訪問/helloService接口