- Spring Boot開發實戰
- 陳光劍
- 2358字
- 2019-01-05 10:00:25
3.2 Spring Boot自動配置原理
那么,有沒有一種方案,可以把上面這些繁雜費時費力的重復性勞動“一鍵打包、開箱即用”?
接下來,我們就逐步展示Spring Boot是怎樣通過自動配置和提供一系列開箱即用的啟動器starter來封裝上面的復雜性使其簡單化的。
3.2.1 Java配置
在整個Spring Boot應用程序中,我們將看不到一個傳統意義上的Spring XMI配置文件。其實,在Spring3.x和Spring4.x中就出現了大量簡化XML配置的解決方案。例如:
?組件掃描(Component Scan):Spring去自動發現應用上下文中創建的Bean。
?自動裝配(Autowired):Spring自動創建Bean之間的依賴。
?通過JavaConfig方式實現Java代碼配置Bean。
下面是一個使用Java Config方式配置Thymeleaf視圖模板引擎的代碼示例:
@Configuration @ComponentScan(basePackages = { "com.easy.Spring Boot"}) @EnableWebMvc // 啟用WebMVC配置(關于WebMVC的自定義配置我們將在后面章節中介紹) public class WebMvcConfig extends WebMvcConfigurerAdapter { @Bean public TemplateResolver templateResolver() {//配置模板解析器 TemplateResolver templateResolver = new ServletContextTemplateResolver(); templateResolver.setPrefix("/WEB-INF/views/"); templateResolver.setSuffix(".html"); templateResolver.setTemplateMode("HTML5"); templateResolver.setCacheable(false); return templateResolver; } @Bean public SpringTemplateEngine templateEngine() {//配置模板引擎 SpringTemplateEngine templateEngine = new SpringTemplateEngine(); templateEngine.setTemplateResolver(templateResolver()); return templateEngine; } @Bean public ThymeleafViewResolver viewResolver() {//配置視圖解析器 ThymeleafViewResolver thymeleafViewResolver = new ThymeleafView Resolver(); thymeleafViewResolver.setTemplateEngine(templateEngine()); thymeleafViewResolver.setCharacterEncoding("UTF-8"); return thymeleafViewResolver; } @Override public void addResourceHandlers(ResourceHandlerRegistry registry)
{//靜態資源處理器配置 registry.addResourceHandler("/resources/**").addResourceLocations ("/resources/"); } … @Bean(name = "messageSource") public MessageSource configureMessageSource(){//消息源配置 ReloadableResourceBundleMessageSource messageSource = new Reloada bleResourceBundleMessageSource(); messageSource.setBasename("classpath:messages"); messageSource.setCacheSeconds(5); messageSource.setDefaultEncoding("UTF-8"); return messageSource; } }
在WebMvcConfig.java配置類中,我們做了如下的配置:
?將它標記為使用@Configuration注釋的Spring配置類。
?啟用基于注釋的Spring MVC配置,使用@EnableWebMvc。
?通過注冊TemplateResolver、SpringTemplateEngine、ThymeleafViewResolver Bean來配置Thymeleaf ViewResolver。
?注冊的ResourceHandlers Bean用來配置URI/resources/**靜態資源的請求映射到/resources/目錄下。
?配置的MessageSource Bean從classpath路徑下的ResourceBundle中的messages-{country-code}.properties消息配置文件中加載i18n消息。
這些樣板化的Java配置代碼比XML要更加簡單些,同時易于管理。而Spring Boot則是引入了一系列的約定規則,將上面的樣板化配置抽象內置到框架中去,用戶連上面的Java配置代碼也將省去。
3.2.2 條件化Bean
Spring Boot除了采用Java、Config方式實現“零XML”配置外,還大量采用了條件化Bean方式來實現自動化配置,本節就介紹這個內容。
1.條件注解@Conditional
假如你想一個或多個Bean只有在應用的路徑下包含特定的庫時才創建,那么使用這節我們所要介紹的@Conditional注解定義條件化的Bean就再適合不過了。
Spring4.0中引入了條件化配置特性。條件化配置通過條件注解@Conditional來標注。條件注解是根據特定的條件來選擇Bean對象的創建。條件注解根據不同的條件來做出不同的事情(簡單說就是if else邏輯)。在Spring中條件注解可以說是設計模式中狀態模式的一種體現方式,同時也是面向對象編程中多態的應用部分。
常用的條件注解如表3-1所示。
表3-1 常用的條件注解

2.條件注解使用實例
下面我們通過實例來說明條件注解@Conditional的具體工作原理。
1)創建示例工程。
為了精簡篇幅,這里只給出關鍵步驟。首先使用Spring Initializr創建一個Spring Boot工程,選擇Web Starter依賴,配置項目名稱和存放路徑,配置Gradle環境,最后導入到IDEA中,完成工程的創建工作。
2)實現Condition接口。
下面我們來實現org.springframework.context.annotation.Condition接口,實現類是MagicCondition。
實現類的“條件”邏輯是:當application.properties配置文件中存在“magic”配置項,同時當值是true的時候:
magic=true #magic=false
就表示條件匹配。
新建MagicCondition類,實現Condition接口。在IDEA中會自動提示我們實現其中的方法,如圖3-1所示。

圖3-1 IDEA會自動提示我們實現其中的方法
選擇要實現的matches函數,如圖3-2所示。

圖3-2 選擇要實現的matches函數
完整的實現代碼如下:
class MagicCondition : Condition { override fun matches(context: ConditionContext, metadata: AnnotatedType Metadata): Boolean { val env = context.getEnvironment() if (env.containsProperty("magic")) // 檢查application.properties配置 文件中是否存在magic屬性key { val b = env["magic"] //獲取magic屬性key的值 return b == "true" //如果是true,返回true } return false // 返回false } }
實現這個Condition接口只需要實現matches方法。如果matches方法返回true就創建該Bean,如果返回false則不創建Bean。這就是否創建MagicService Bean的條件。
matches方法中的第1個參數類型ConditionContext是一個接口,它的定義如下:
public interface ConditionContext { BeanDefinitionRegistry getRegistry(); ConfigurableListableBeanFactory getBeanFactory(); Environment getEnvironment(); ResourceLoader getResourceLoader(); ClassLoader getClassLoader(); }
ConditionContext中的方法API說明如表3-2所示。
表3-2 ConditionContext中的方法API

matches方法中的第2個參數類型AnnotatedTypeMetadata,則能夠讓我們檢查帶有@Bean注解的方法上是否有其他注解。AnnotatedTypeMetadata接口的定義如下:
public interface AnnotatedTypeMetadata { boolean isAnnotated(String annotationType); Map<String, Object> getAnnotationAttributes(String annotationType);
Map<String, Object> getAnnotationAttributes(String annotationType, boolean classValuesAsString); MultiValueMap<String, Object> getAllAnnotationAttributes(String annota tionType); MultiValueMap<String, Object> getAllAnnotationAttributes(String annota tionType, boolean classValuesAsString); }
使用isAnnotated()方法,能夠判斷帶有@Bean注解的方法是不是還有其他特定的注解。使用另外的幾個方法,我們能夠檢查@Bean注解的方法上,所標注的其他注解的屬性。
例如Spring4使用@Conditional對多環境部署配置文件功能實現的ProfileCondition類的代碼如下:
class ProfileCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { MultiValueMap<String, Object> attrs = metadata.getAllAnnotationA ttributes(Profile.class.getName()); if (attrs ! = null) { for (Object value : attrs.get("value")) { if (context.getEnvironment().acceptsProfiles((String[]) value)) { return true; } } return false; } return true; } }
我們可以看到,ProfileCondition通過AnnotatedTypeMetadata得到了用于@Profile注解的所有屬性:
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes (Profile.class.getName());
然后循環遍歷attrs這個Map中的屬性“value”的值(包含了Bean的profile名稱),使用ConditionContext中的Environment來檢查這個value進而決定使用哪個Profile處于激活狀態。
3)條件配置類ConditionalConfig。
Spring4提供了一個通用的基于特定條件創建Bean的方式:@Conditional注解。編寫條件配置類ConditionalConfig代碼如下:
@Configuration @ComponentScan(basePackages = ["com.easy.Spring Boot.demo_conditional_bean"]) class ConditionalConfig { @Bean @Conditional(MagicCondition::class) //指定條件類 fun magicService(): MagicServiceImpl { return MagicServiceImpl() } }
邏輯是當Spring容器中存在MagicCondition Bean,并滿足MagicCondition類的條件時,去實例化magicService這個Bean。否則不注冊這個Bean。
4)MagicServiceImpl邏輯實現。
MagicServiceImpl業務Bean的邏輯很簡單,就是打印一個標識信息。實現代碼如下:
class MagicServiceImpl : MagicService { override fun info(): String { return "THIS IS MAGIC" // 打印一個標識信息 } } interface MagicService { fun info(): String }
5)測試MagicController。
我們使用一個HTTP接口來測試條件化Bean的注冊結果:
@RestController class MagicController { @GetMapping("magic") fun magic(): String { try { val magicService = SpringContextUtil.getBean("magicService") as MagicService // 從Spring容器中獲取magicService Bean return magicService.info() //調用info()方法 } catch (e: Exception) { e.printStackTrace() } return "null" } }
其中SpringContextUtil實現代碼如下:
object SpringContextUtil { lateinit var applicationContext: ApplicationContext fun setGlobalApplicationContext(context: ApplicationContext) { applicationContext = context }
fun getBean(beanId: String): Any { return applicationContext.getBean(beanId) } }
在Spring Boot啟動入口類中,我們把Spring Boot應用的上下文對象放到Spring Context-Util中的這個applicationContext成員變量中:
@Spring BootApplication class DemoConditionalBeanApplication fun main(args: Array<String>) { val context = runApplication<DemoConditionalBeanApplication>(*args) SpringContextUtil.setGlobalApplicationContext(context) }
完整的項目代碼參考示例工程源代碼。
6)運行測試。
我們先來測試magic = true。在application.properties中配置:
magic=true
重新啟動應用程序,瀏覽器輸入:http://127.0.0.1:8080/magic,輸出“THIS IS MAGIC”。
再來測試magic = false. 在application.properties中配置:
magic=false
重新啟動應用程序,瀏覽器輸入:http://127.0.0.1:8080/magic,輸出:“null”。
這個時候,我們看到應用程序后臺日志有如下輸出:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'magicService' available …… com.easy.Spring Boot.demo_conditional_bean.controller.MagicController.magic (MagicController.kt:14)
表明magicService這個Bean沒有注冊到Spring容器中。條件化注冊Bean驗證OK。
3.2.3 組合注解
組合注解就是將現有的注解進行組合,生成一個新的注解。使用這個新的注解就相當于使用了該組合注解中所有的注解。這個特性還是蠻有用的,例如Spring Boot應用程序的入口類注解@Spring BootApplication就是典型的例子:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Spring BootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExclude Filter.class) }) public @interface Spring BootApplication
早期版本的Spring Boot中,用戶需要使用如下三個注解來標注應用入口main類:
?@Configuration
?@EnableAutoConfiguration
?@ComponentScan
在Spring Boot1.2.0中只需用一個統一的注解@Spring BootApplication。
- Android Wearable Programming
- 大學計算機基礎(第二版)
- Vue.js快跑:構建觸手可及的高性能Web應用
- Hands-On Natural Language Processing with Python
- MATLAB 2020從入門到精通
- Swift語言實戰精講
- NGINX Cookbook
- Webpack實戰:入門、進階與調優
- Advanced Express Web Application Development
- Java零基礎實戰
- Java Web從入門到精通(第3版)
- 代替VBA!用Python輕松實現Excel編程
- Modern C++ Programming Cookbook
- Swift語言實戰晉級
- 深入淺出Python數據分析