官术网_书友最值得收藏!

  • 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)
        }

完整的項目代碼參考示例工程源代碼。

提示

本小節的實例工程源碼:https://github.com/KotlinSpringBoot/demo_conditional_bean

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。

主站蜘蛛池模板: 新泰市| 三河市| 监利县| 扶余县| 安顺市| 榆树市| 湖北省| 长沙县| 马公市| 沭阳县| 江油市| 昆明市| 商水县| 江门市| 平果县| 印江| 格尔木市| 新民市| 呼伦贝尔市| 贵港市| 斗六市| 江阴市| 潞城市| 忻州市| 德令哈市| 平南县| 北安市| 兰坪| 西峡县| 霍林郭勒市| 沙湾县| 鹰潭市| 潞城市| 海南省| 灵丘县| 阳新县| 华阴市| 北票市| 张北县| 福州市| 慈利县|