spring boot學習二:Spring Boot自動裝配分析與實戰

上文中簡單介紹了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代碼如下:

 

Java代碼 
  1. package com.flagship.condition;  
  2.   
  3. import org.springframework.context.annotation.Condition;  
  4. import org.springframework.context.annotation.ConditionContext;  
  5. import org.springframework.core.type.AnnotatedTypeMetadata;  
  6.   
  7. public class MatchCondition implements Condition {  
  8.     @Override  
  9.     public boolean matches(ConditionContext conditionContext,   
  10.                     AnnotatedTypeMetadata annotatedTypeMetadata) {  
  11.         return true;  
  12.     }  
  13. }  

 NotMatchCondition代碼如下:

Java代碼 
  1. package com.flagship.condition;  
  2.   
  3. import org.springframework.context.annotation.Condition;  
  4. import org.springframework.context.annotation.ConditionContext;  
  5. import org.springframework.core.type.AnnotatedTypeMetadata;  
  6.   
  7. public class NotMatchCondition implements Condition {  
  8.   
  9.     @Override  
  10.     public boolean matches(ConditionContext conditionContext,AnnotatedTypeMetadata annotatedTypeMetadata) {  
  11.         return false;  
  12.     }  
  13. }  

 3、創建2個類,MatchBean和NotMatchBean,實現同一接口BeanInterface,後續Java配置類中,會根據不同的註解條件,生成相應的類對象,我們用類中的description()來打印信息,以此識別不同的對象信息,BeanInterface代碼如下:

Java代碼 
  1. package com.flagship.bean;  
  2.   
  3. public interface BeanInterface {  
  4.     public void description();  
  5. }  

 MatchBean代碼如下:

Java代碼 
  1. package com.flagship.bean;  
  2.   
  3. public class MatchBean implements BeanInterface {  
  4.     @Override  
  5.     public void description() {  
  6.         System.out.println("this is MatchBean's method!");  
  7.     }  
  8. }  

 NotMatchBean代碼如下:

Java代碼 
  1. package com.flagship.bean;  
  2.   
  3. public class NotMatchBean implements BeanInterface {  
  4.     @Override  
  5.     public void description() {  
  6.         System.out.println("this is NotMatchBean's method!");  
  7.     }  
  8. }  

 4、Java配置類代碼如下,此處採用@Configuration註解聲明配置類,類似以前的xml配置文件,@Bean註解聲明當前方法的返回值是一個Bean對象:

Java代碼 
  1. package com.flagship.config;  
  2.   
  3. import com.flagship.bean.BeanInterface;  
  4. import com.flagship.bean.MatchBean;  
  5. import com.flagship.bean.NotMatchBean;  
  6. import com.flagship.condition.MatchCondition;  
  7. import com.flagship.condition.NotMatchCondition;  
  8. import org.springframework.context.annotation.Bean;  
  9. import org.springframework.context.annotation.Conditional;  
  10. import org.springframework.context.annotation.Configuration;  
  11.   
  12. @Configuration  
  13. public class ConditionalCfg {  
  14.     @Bean  
  15.     @Conditional(MatchCondition.class)  
  16.     public BeanInterface getMatchBeanObject(){  
  17.         return new MatchBean();  
  18.     }  
  19.   
  20.     @Bean  
  21.     @Conditional(NotMatchCondition.class)  
  22.     public BeanInterface getNotMatchBeanObject(){  
  23.         return new NotMatchBean();  
  24.     }  
  25. }  

 5、運行類代碼如下,根據條件類的matches方法的返回值,最終會調用MatchBean類的description方法:

Java代碼 
  1. package com.flagship.condmodule;  
  2.   
  3. import com.flagship.bean.BeanInterface;  
  4. import com.flagship.config.ConditionalCfg;  
  5. import org.springframework.context.annotation.AnnotationConfigApplicationContext;  
  6.   
  7. public class Application {  
  8.      public static void main(String[] args) {  
  9.          AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ConditionalCfg.class);  
  10.      BeanInterface bean = ctx.getBean(BeanInterface.class);  
  11.      bean.description();  
  12.      }  
  13. }  

 最終控制檯打印信息爲: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註解的源碼如下:

 

Java代碼 
  1. //  
  2. // Source code recreated from a .class file by IntelliJ IDEA  
  3. // (powered by Fernflower decompiler)  
  4. //  
  5.   
  6. package org.springframework.boot.autoconfigure;  
  7.   
  8. import java.lang.annotation.Documented;  
  9. import java.lang.annotation.ElementType;  
  10. import java.lang.annotation.Inherited;  
  11. import java.lang.annotation.Retention;  
  12. import java.lang.annotation.RetentionPolicy;  
  13. import java.lang.annotation.Target;  
  14. import org.springframework.context.annotation.Import;  
  15.   
  16. @Target({ElementType.TYPE})  
  17. @Retention(RetentionPolicy.RUNTIME)  
  18. @Documented  
  19. @Inherited  
  20. @AutoConfigurationPackage  
  21. @Import({EnableAutoConfigurationImportSelector.class})  
  22. public @interface EnableAutoConfiguration {  
  23.     String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";  
  24.   
  25.     Class<?>[] exclude() default {};  
  26.   
  27.     String[] excludeName() default {};  
  28. }  

 EnableAutoConfiguration使用@Import註解將EnableAutoConfigurationImportSelector導入並聲明爲一個Bean,跟蹤EnableAutoConfigurationImportSelector的源碼,發現其繼承AutoConfigurationImportSelector類,而其中有這麼一個方法getCandidateConfigurations

 

Java代碼 
  1. protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,AnnotationAttributes attributes) {  
  2.         List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());  
  3.        //...省略其餘代碼  
  4.     }  
繼續跟蹤SpringFactoriesLoader.loadFactoryNames方法,其代碼如下:

 

 

Java代碼 
  1. public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {  
  2.      String factoryClassName = factoryClass.getName();  
  3.      try {  
  4.         Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") :ClassLoader.getSystemResources("META-INF/spring.factories");  
  5.         ArrayList result = new ArrayList();  
  6.         while(urls.hasMoreElements()) {  
  7.             URL url = (URL)urls.nextElement();  
  8.             Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));  
  9.             String factoryClassNames = properties.getProperty(factoryClassName);  
  10.             result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));  
  11.          }  

 發現最終讀取的就是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,代碼如下:

Java代碼 
  1. @Configuration  
  2. @ConditionalOnWebApplication  
  3. @ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurerAdapter.class})  
  4. @ConditionalOnMissingBean({WebMvcConfigurationSupport.class})  
  5. @AutoConfigureOrder(-2147483638)  
  6. @AutoConfigureAfter({DispatcherServletAutoConfiguration.class,   
  7.                                              ValidationAutoConfiguration.class})  
  8. public class WebMvcAutoConfiguration {  
  9.     public static final String DEFAULT_PREFIX = "";  
  10.     public static final String DEFAULT_SUFFIX = "";  
  11.   
  12.     public WebMvcAutoConfiguration() {  
  13.     }  

 可以看到,這裏使用了上述介紹的條件註解來實現自動裝配功能

 三、本節我們根據上面介紹的原理,開始自定義實現一個自動裝配,實現根據項目properties文件的配置打印當前環境信息日誌的功能

1、首先,新建一個maven-archetype-quickstart模版的maven模塊,並在src\main目錄下建好resources\META-INF\spring.factories文件,架構如下圖所示:



 

2、pom文件中加入autofigure的依賴,代碼如下:

Xml代碼 
  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  2.          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  
  3.     <parent>  
  4.         <artifactId>ch2_1</artifactId>  
  5.         <groupId>com.flagship</groupId>  
  6.         <version>0.0.1-SNAPSHOT</version>  
  7.     </parent>  
  8.     <modelVersion>4.0.0</modelVersion>  
  9.   
  10.     <artifactId>autocfg</artifactId>  
  11.     <packaging>jar</packaging>  
  12.   
  13.     <name>envLog</name>  
  14.     <url>http://maven.apache.org</url>  
  15.   
  16.     <properties>  
  17.         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>  
  18.     </properties>  
  19.   
  20.     <dependencies>  
  21.         <dependency>  
  22.             <groupId>org.springframework.boot</groupId>  
  23.             <artifactId>spring-boot-autoconfigure</artifactId>  
  24.             <version>1.5.7.RELEASE</version>  
  25.         </dependency>  
  26.         <dependency>  
  27.             <groupId>junit</groupId>  
  28.             <artifactId>junit</artifactId>  
  29.             <version>3.8.1</version>  
  30.             <scope>test</scope>  
  31.         </dependency>  
  32.     </dependencies>  
  33. </project>  

 3、新建Java配置類:LogServiceProp,會讀取項目配置文件中"env.log"開頭的屬性值

Java代碼 
  1. package com.flagship.autocfg;  
  2. import org.springframework.boot.context.properties.ConfigurationProperties;  
  3. /** 
  4.  * Java配置類 
  5.  */  
  6. @ConfigurationProperties(prefix="env.log")  
  7. public class LogServiceProp {  
  8.   
  9.     private String runPattern = "run";  
  10.       
  11.     public String getRunPattern() {  
  12.         return runPattern;  
  13.     }  
  14.     public void setRunPattern(String runPattern) {  
  15.         this.runPattern = runPattern;  
  16.     }  
  17. }  

 4、新建業務實體類,用於打印日誌,其中,printEnv方法用來在控制檯打印當前的運行模式日誌,代碼如下:

Java代碼 
  1. package com.flagship.autocfg;  
  2.   
  3. public class LogService {  
  4.   
  5.     private String runPatternLog;  
  6.   
  7.     public String printEnv(){  
  8.         return "current env is in:" + runPatternLog + " pattern!";  
  9.     }  
  10.   
  11.     public String getRunPatternLog() {  
  12.         return runPatternLog;  
  13.     }  
  14.     public void setRunPatternLog(String runPatternLog) {  
  15.         this.runPatternLog = runPatternLog;  
  16.     }  
  17. }  

 5、建立自動配置類:LogConfiguration,其中@Configuration註解標識的類,表明作爲一個配置類,類似於之前的xml配置文件,@EnableConfigurationProperties告訴Spring Boot 任何被@ConfigurationProperties註解的beans將自動被屬性配置,@ConditionalOnClass用來條件註解,當LogService.class存在類路徑的時候起效,@ConditionalOnMissingBean當容器中沒有這個Bean對象的時候,自動配置這個Bean對象,代碼如下:

Java代碼 
  1. package com.flagship.autocfg;  
  2.   
  3. import org.springframework.beans.factory.annotation.Autowired;  
  4. import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;  
  5. import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;  
  6. import org.springframework.boot.context.properties.ConfigurationProperties;  
  7. import org.springframework.boot.context.properties.EnableConfigurationProperties;  
  8. import org.springframework.context.annotation.Bean;  
  9. import org.springframework.context.annotation.Configuration;  
  10.   
  11. @Configuration  
  12. @EnableConfigurationProperties(LogServiceProp.class)  
  13. @ConditionalOnClass(LogService.class)  
  14. public class LogConfiguration {  
  15.     @Autowired  
  16.     private LogServiceProp logServiceProp;  
  17.   
  18.     @Bean  
  19.     @ConditionalOnMissingBean(LogService.class)  
  20.     public LogService getLogService(){  
  21.         LogService service = new LogService();  
  22.         service.setRunPatternLog(logServiceProp.getRunPattern());  
  23.         return service;  
  24.     }  
  25. }  

 6、spring.factories文件中加入自動配置類,代碼如下:

Xml代碼 
  1. # Auto Configure  
  2. org.springframework.boot.autoconfigure.EnableAutoConfiguration=\  
  3. com.flagship.autocfg.LogConfiguration  

 7、回到第一節的condmodule模塊,pom加入對envLog模塊的依賴,代碼如下:

Xml代碼 
  1. <dependency>  
  2.     <groupId>com.flagship</groupId>  
  3.     <artifactId>autocfg</artifactId>  
  4.     <version>0.0.1-SNAPSHOT</version>  
  5. </dependency>  

 8、src\main\resources\application.properties中加入如下配置:

      env.log.runPattern=debug

9、新建mvc入口測試類,代碼如下:

Java代碼 
  1. package com.flagship.condmodule;  
  2.   
  3. import com.flagship.autocfg.LogService;  
  4. import org.springframework.beans.factory.annotation.Autowired;  
  5. import org.springframework.boot.SpringApplication;  
  6. import org.springframework.boot.autoconfigure.SpringBootApplication;  
  7. import org.springframework.web.bind.annotation.RequestMapping;  
  8. import org.springframework.web.bind.annotation.RestController;  
  9.   
  10. @RestController  
  11. @SpringBootApplication  
  12. public class AutoCfgApplication {  
  13.   
  14.     @Autowired  
  15.     LogService service;  
  16.   
  17.     @RequestMapping("/")  
  18.     public String getEnvLog(){  
  19.         return service.printEnv();  
  20.     }  
  21.   
  22.     public static void main(String[] args) {  
  23.         SpringApplication.run(AutoCfgApplication.class,args);  
  24.     }  
  25. }  

 10、運行後結果如下,此時神奇的效果出現了,工程中並沒有配置LogService這個對象,但是卻可以通過@Autowired註解進行注入,這就是Spring Boot自動配置的威力:

current env is in:debug pattern!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章