上文中簡單介紹了intellij環境下Spring Boot的入門示例,從而見識到了Spring Boot的強大,幾乎不用做什麼配置,就能運行一個Spring mvc的示例,要知道,Spring之前都是以繁瑣的配置而爲人詬病,Spring Boot的自動裝配,可以根據pom的依賴配置,自動生成相應的bean,並加載到Spring Context中,簡化了Spring項目搭建的複雜度,本節主要介紹Spring Boot自動裝配的流程,並最終提供了自定義自動裝配的示例代碼。
一、在這之前,首先要介紹一下Spring4中的條件註解:@Conditional,Spring會根據獨立的註解條件來創建類,Spring條件註解示例如下:
1、首先創建ch2_1工程和condmodule模塊,項目結構如下所示:
2、創建對應的條件類,其中,MatchCondition表示匹配的條件類,NotMatchCondition表示不匹配的條件類,MatchCondition代碼如下:
- package com.flagship.condition;
- import org.springframework.context.annotation.Condition;
- import org.springframework.context.annotation.ConditionContext;
- import org.springframework.core.type.AnnotatedTypeMetadata;
- public class MatchCondition implements Condition {
- @Override
- public boolean matches(ConditionContext conditionContext,
- AnnotatedTypeMetadata annotatedTypeMetadata) {
- return true;
- }
- }
NotMatchCondition代碼如下:
- package com.flagship.condition;
- import org.springframework.context.annotation.Condition;
- import org.springframework.context.annotation.ConditionContext;
- import org.springframework.core.type.AnnotatedTypeMetadata;
- public class NotMatchCondition implements Condition {
- @Override
- public boolean matches(ConditionContext conditionContext,AnnotatedTypeMetadata annotatedTypeMetadata) {
- return false;
- }
- }
3、創建2個類,MatchBean和NotMatchBean,實現同一接口BeanInterface,後續Java配置類中,會根據不同的註解條件,生成相應的類對象,我們用類中的description()來打印信息,以此識別不同的對象信息,BeanInterface代碼如下:
- package com.flagship.bean;
- public interface BeanInterface {
- public void description();
- }
MatchBean代碼如下:
- package com.flagship.bean;
- public class MatchBean implements BeanInterface {
- @Override
- public void description() {
- System.out.println("this is MatchBean's method!");
- }
- }
NotMatchBean代碼如下:
- package com.flagship.bean;
- public class NotMatchBean implements BeanInterface {
- @Override
- public void description() {
- System.out.println("this is NotMatchBean's method!");
- }
- }
4、Java配置類代碼如下,此處採用@Configuration註解聲明配置類,類似以前的xml配置文件,@Bean註解聲明當前方法的返回值是一個Bean對象:
- package com.flagship.config;
- import com.flagship.bean.BeanInterface;
- import com.flagship.bean.MatchBean;
- import com.flagship.bean.NotMatchBean;
- import com.flagship.condition.MatchCondition;
- import com.flagship.condition.NotMatchCondition;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Conditional;
- import org.springframework.context.annotation.Configuration;
- @Configuration
- public class ConditionalCfg {
- @Bean
- @Conditional(MatchCondition.class)
- public BeanInterface getMatchBeanObject(){
- return new MatchBean();
- }
- @Bean
- @Conditional(NotMatchCondition.class)
- public BeanInterface getNotMatchBeanObject(){
- return new NotMatchBean();
- }
- }
5、運行類代碼如下,根據條件類的matches方法的返回值,最終會調用MatchBean類的description方法:
- package com.flagship.condmodule;
- import com.flagship.bean.BeanInterface;
- import com.flagship.config.ConditionalCfg;
- import org.springframework.context.annotation.AnnotationConfigApplicationContext;
- public class Application {
- public static void main(String[] args) {
- AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ConditionalCfg.class);
- BeanInterface bean = ctx.getBean(BeanInterface.class);
- bean.description();
- }
- }
最終控制檯打印信息爲:this is MatchBean's method!
二、跟蹤官網文檔說明,Spring Boot條件註解大致分了如下幾類如下:
Class conditions:@ConditionalOnClass和@ConditionalOnMissingClass,表示類是否在類路徑下的條件註解
Bean conditions:@ConditionalOnBean和@ConditionalOnMissingBean,表示Bean是否被定義的條件註解
Property conditions:@ConditionalOnProperty,使用prefix和name屬性用來表示是否有值,默認的話,只要該屬性存在值,且不爲false,即可匹配
Resource conditions:@ConditionalOnResource表示是否存在指定的resouce的條件註解
Web application conditions:@ConditionalOnWebApplication和@ConditionalOnNotWebApplication,當項目是web項目,或者不是web項目的條件註解
SpEL expression conditions:@ConditionalOnExpression,根據SPEL表達式執行結果作爲條件
自動裝配代碼跟蹤:
我們從上一章節的@SpringBootApplication開始,由於@SpringBootApplication是由@EnableAutoConfiguration組成的,我們觀察@EnableAutoConfiguration註解的源碼如下:
- //
- // Source code recreated from a .class file by IntelliJ IDEA
- // (powered by Fernflower decompiler)
- //
- package org.springframework.boot.autoconfigure;
- import java.lang.annotation.Documented;
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Inherited;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
- import org.springframework.context.annotation.Import;
- @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 {};
- }
EnableAutoConfiguration使用@Import註解將EnableAutoConfigurationImportSelector導入並聲明爲一個Bean,跟蹤EnableAutoConfigurationImportSelector的源碼,發現其繼承AutoConfigurationImportSelector類,而其中有這麼一個方法getCandidateConfigurations
- protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,AnnotationAttributes attributes) {
- List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
- //...省略其餘代碼
- }
- public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
- String factoryClassName = factoryClass.getName();
- try {
- Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") :ClassLoader.getSystemResources("META-INF/spring.factories");
- ArrayList result = new ArrayList();
- while(urls.hasMoreElements()) {
- URL url = (URL)urls.nextElement();
- Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
- String factoryClassNames = properties.getProperty(factoryClassName);
- result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
- }
發現最終讀取的就是META-INF/spring.factories文件,點擊intellij中【External Libraries】中spring-boot-autoconfigure-1.5.7.RELEASE.jar,打開META-INF/spring.factories文件,查看裏面內容
此文件中提供了Spring Boot的默認自動配置,隨便點開啓動一個配置類:org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,代碼如下:
- @Configuration
- @ConditionalOnWebApplication
- @ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurerAdapter.class})
- @ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
- @AutoConfigureOrder(-2147483638)
- @AutoConfigureAfter({DispatcherServletAutoConfiguration.class,
- ValidationAutoConfiguration.class})
- public class WebMvcAutoConfiguration {
- public static final String DEFAULT_PREFIX = "";
- public static final String DEFAULT_SUFFIX = "";
- public WebMvcAutoConfiguration() {
- }
可以看到,這裏使用了上述介紹的條件註解來實現自動裝配功能
三、本節我們根據上面介紹的原理,開始自定義實現一個自動裝配,實現根據項目properties文件的配置打印當前環境信息日誌的功能
1、首先,新建一個maven-archetype-quickstart模版的maven模塊,並在src\main目錄下建好resources\META-INF\spring.factories文件,架構如下圖所示:
2、pom文件中加入autofigure的依賴,代碼如下:
- <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">
- <parent>
- <artifactId>ch2_1</artifactId>
- <groupId>com.flagship</groupId>
- <version>0.0.1-SNAPSHOT</version>
- </parent>
- <modelVersion>4.0.0</modelVersion>
- <artifactId>autocfg</artifactId>
- <packaging>jar</packaging>
- <name>envLog</name>
- <url>http://maven.apache.org</url>
- <properties>
- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
- </properties>
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-autoconfigure</artifactId>
- <version>1.5.7.RELEASE</version>
- </dependency>
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>3.8.1</version>
- <scope>test</scope>
- </dependency>
- </dependencies>
- </project>
3、新建Java配置類:LogServiceProp,會讀取項目配置文件中"env.log"開頭的屬性值
- package com.flagship.autocfg;
- import org.springframework.boot.context.properties.ConfigurationProperties;
- /**
- * Java配置類
- */
- @ConfigurationProperties(prefix="env.log")
- public class LogServiceProp {
- private String runPattern = "run";
- public String getRunPattern() {
- return runPattern;
- }
- public void setRunPattern(String runPattern) {
- this.runPattern = runPattern;
- }
- }
4、新建業務實體類,用於打印日誌,其中,printEnv方法用來在控制檯打印當前的運行模式日誌,代碼如下:
- package com.flagship.autocfg;
- public class LogService {
- private String runPatternLog;
- public String printEnv(){
- return "current env is in:" + runPatternLog + " pattern!";
- }
- public String getRunPatternLog() {
- return runPatternLog;
- }
- public void setRunPatternLog(String runPatternLog) {
- this.runPatternLog = runPatternLog;
- }
- }
5、建立自動配置類:LogConfiguration,其中@Configuration註解標識的類,表明作爲一個配置類,類似於之前的xml配置文件,@EnableConfigurationProperties告訴Spring Boot 任何被@ConfigurationProperties註解的beans將自動被屬性配置,@ConditionalOnClass用來條件註解,當LogService.class存在類路徑的時候起效,@ConditionalOnMissingBean當容器中沒有這個Bean對象的時候,自動配置這個Bean對象,代碼如下:
- package com.flagship.autocfg;
- 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.ConfigurationProperties;
- import org.springframework.boot.context.properties.EnableConfigurationProperties;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- @Configuration
- @EnableConfigurationProperties(LogServiceProp.class)
- @ConditionalOnClass(LogService.class)
- public class LogConfiguration {
- @Autowired
- private LogServiceProp logServiceProp;
- @Bean
- @ConditionalOnMissingBean(LogService.class)
- public LogService getLogService(){
- LogService service = new LogService();
- service.setRunPatternLog(logServiceProp.getRunPattern());
- return service;
- }
- }
6、spring.factories文件中加入自動配置類,代碼如下:
- # Auto Configure
- org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
- com.flagship.autocfg.LogConfiguration
7、回到第一節的condmodule模塊,pom加入對envLog模塊的依賴,代碼如下:
- <dependency>
- <groupId>com.flagship</groupId>
- <artifactId>autocfg</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- </dependency>
8、src\main\resources\application.properties中加入如下配置:
env.log.runPattern=debug
9、新建mvc入口測試類,代碼如下:
- package com.flagship.condmodule;
- import com.flagship.autocfg.LogService;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RestController;
- @RestController
- @SpringBootApplication
- public class AutoCfgApplication {
- @Autowired
- LogService service;
- @RequestMapping("/")
- public String getEnvLog(){
- return service.printEnv();
- }
- public static void main(String[] args) {
- SpringApplication.run(AutoCfgApplication.class,args);
- }
- }
10、運行後結果如下,此時神奇的效果出現了,工程中並沒有配置LogService這個對象,但是卻可以通過@Autowired註解進行注入,這就是Spring Boot自動配置的威力: