咱們知道,spring boot自動配置功能能夠根據不一樣狀況來決定spring配置應該用哪一個,不該該用哪一個,舉個例子:linux
那個這個是怎麼實現的呢?緣由就在於它利用了Spring的條件化配置,條件化配置容許配置存在於應用中,可是在知足某些特定條件前會忽略這些配置。spring
要實現條件化配置咱們要用到@Conditional條件化註解。apache
本篇隨便講從以下三個方面進行展開:windows
1、@Conditional小例子app
咱們知道在windows下顯示列表的命令是dir,而在linux系統下顯示列表的命令是ls,基於條件配置,咱們能夠實如今不一樣的操做系統下返回不一樣的值。maven
/** * 實現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"); } }
/** * 實現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"); } }
public interface ListService { public String showListLine(); }
public class WindowsListService implements ListService{ @Override public String showListLine() { return "dir"; } }
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()); } }
Windows 7 系統下的列表命令爲: dir
若是你的是linux系統,則結果就會是ide
Linux 系統下的列表命令爲: ls
2、spring boot 的條件化配置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。
基於以上內容,咱們就能夠閱讀自動配置相關的源碼了。
3、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自動配置類就在裏面。
4、編寫本身的spring boot starter pom
接下來,咱們就來寫一個簡單的spring boot starter 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
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.sam.spring_boot_starter_hello.HelloServiceAutoConfiguration
<dependency> <groupId>com.sam</groupId> <artifactId>spring-boot-starter-hello</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
@RestController public class HelloController { //代碼中沒有配置這個helloService Bean,可是自動配置可以幫忙實例化,所以能夠直接注入 @Autowired HelloService helloService; @RequestMapping(value="/helloService") public String sayHello() { return helloService.sayHello(); } }