1. 程式人生 > >SpringBoot內部的一些自動化配置原理

SpringBoot內部的一些自動化配置原理

springboot用來簡化Spring框架帶來的大量XML配置以及複雜的依賴管理,讓開發人員可以更加關注業務邏輯的開發。

比如不使用springboot而使用SpringMVC作為web框架進行開發的時候,需要配置相關的SpringMVC配置以及對應的依賴,比較繁瑣;而使用springboot的話只需要以下短短的幾行程式碼就可以使用SpringMVC,可謂相當地方便:

@RestController
class App {
  @RequestMapping("/")
  String home() {
    "hello"
  }
}

其中maven配置如下:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.3.5.RELEASE</version>
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

我們以使用SpringMVC並且檢視使用freemarker為例,分析springboot內部是如何解析freemarker檢視的。

如果要在springboot中使用freemarker檢視框架,並且使用maven構建專案的時候,還需要加入以下依賴:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
    <version>1.3.5.RELEASE</version>
</dependency>

這個spring-boot-starter-freemarker依賴對應的jar包裡的檔案如下:

META-INF
├── MANIFEST.MF
├── maven
│   └── org.springframework.boot
│       └── spring-boot-starter-freemarker
│           ├── pom.properties
│           └── pom.xml
└── spring.provides

內部的pom.xml裡需要的依賴如下:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
  <groupId>org.freemarker</groupId>
  <artifactId>freemarker</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context-support</artifactId>
</dependency>

我們可以看到這個spring-boot-starter-freemarker依賴內部並沒有freemarker的ViewResolver,而是僅僅加入了freemarker的依賴,還有3個依賴,分別是spring-boot-starter、spring-boot-starter-web和spring-context-support。

接下來我們來分析一下為什麼在springboot中加入了freemarker的依賴spring-boot-starter-freemarker後,SpringMVC自動地構造了一個freemarker的ViewResolver?

在分析之前,首先我們先看下maven配置,看到了一個parent配置:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.3.5.RELEASE</version>
</parent>

它內部也有一個parent:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>1.3.5.RELEASE</version>
    <relativePath>../../spring-boot-dependencies</relativePath>
</parent>

比如spring-boot-starter-web、spring-boot-starter-websocket、spring-boot-starter-data-solrspring-boot-starter-freemarker等等,基本上所有的依賴都在這個parent裡。

我們的例子中使用了parent依賴裡的兩個依賴,分別是spring-boot-starter-web和spring-boot-starter-freemarker。

其中spring-boot-starter-web內部依賴了spring的兩個spring web依賴:spring-web和spring-webmvc。

spring-boot-starter-web內部還依賴spring-boot-starter,這個spring-boot-starter依賴了spring核心依賴spring-core;還依賴了spring-bootspring-boot-autoconfigure這兩個。

spring-boot定義了很多基礎功能類,像執行程式的SpringApplication,Logging系統,一些tomcat或者jetty這些EmbeddedServlet容器,配置屬性loader等等。

包括了這些包:

image

spring-boot-autoconfigure定義了很多自動配置的類,比如jpa,solr,redis,elasticsearch、mongo、freemarker、velocity,thymeleaf等等自動配置的類。

以freemarker為例,看一下它的自動化配置類:

@Configuration // 使用Configuration註解,自動構造一些內部定義的bean
@ConditionalOnClass({ freemarker.template.Configuration.class,
        FreeMarkerConfigurationFactory.class }) // 需要freemarker.template.Configuration和FreeMarkerConfigurationFactory這兩個類存在在classpath中才會進行自動配置
@AutoConfigureAfter(WebMvcAutoConfiguration.class) // 本次自動配置需要依賴WebMvcAutoConfiguration這個配置類配置之後觸發。這個WebMvcAutoConfiguration內部會配置很多Wen基礎性的東西,比如RequestMappingHandlerMapping、RequestMappingHandlerAdapter等
@EnableConfigurationProperties(FreeMarkerProperties.class) // 使用FreeMarkerProperties類中的配置
public class FreeMarkerAutoConfiguration {

    private static final Log logger = LogFactory
            .getLog(FreeMarkerAutoConfiguration.class);

    @Autowired
    private ApplicationContext applicationContext;

    @Autowired
    private FreeMarkerProperties properties;

    @PostConstruct // 構造之後呼叫的方法,組要檢查模板位置是否存在
    public void checkTemplateLocationExists() {
        if (this.properties.isCheckTemplateLocation()) {
            TemplateLocation templatePathLocation = null;
            List<TemplateLocation> locations = new ArrayList<TemplateLocation>();
            for (String templateLoaderPath : this.properties.getTemplateLoaderPath()) {
                TemplateLocation location = new TemplateLocation(templateLoaderPath);
                locations.add(location);
                if (location.exists(this.applicationContext)) {
                    templatePathLocation = location;
                    break;
                }
            }
            if (templatePathLocation == null) {
                logger.warn("Cannot find template location(s): " + locations
                        + " (please add some templates, "
                        + "check your FreeMarker configuration, or set "
                        + "spring.freemarker.checkTemplateLocation=false)");
            }
        }
    }

    protected static class FreeMarkerConfiguration {

        @Autowired
        protected FreeMarkerProperties properties;

        protected void applyProperties(FreeMarkerConfigurationFactory factory) {
            factory.setTemplateLoaderPaths(this.properties.getTemplateLoaderPath());
            factory.setPreferFileSystemAccess(this.properties.isPreferFileSystemAccess());
            factory.setDefaultEncoding(this.properties.getCharsetName());
            Properties settings = new Properties();
            settings.putAll(this.properties.getSettings());
            factory.setFreemarkerSettings(settings);
        }

    }

    @Configuration
    @ConditionalOnNotWebApplication // 非Web專案的自動配置
    public static class FreeMarkerNonWebConfiguration extends FreeMarkerConfiguration {

        @Bean
        @ConditionalOnMissingBean
        public FreeMarkerConfigurationFactoryBean freeMarkerConfiguration() {
            FreeMarkerConfigurationFactoryBean freeMarkerFactoryBean = new FreeMarkerConfigurationFactoryBean();
            applyProperties(freeMarkerFactoryBean);
            return freeMarkerFactoryBean;
        }

    }

    @Configuration // 自動配置的類
    @ConditionalOnClass(Servlet.class) // 需要執行在Servlet容器下
    @ConditionalOnWebApplication // 需要在Web專案下
    public static class FreeMarkerWebConfiguration extends FreeMarkerConfiguration {

        @Bean
        @ConditionalOnMissingBean(FreeMarkerConfig.class)
        public FreeMarkerConfigurer freeMarkerConfigurer() {
            FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
            applyProperties(configurer);
            return configurer;
        }

        @Bean
        public freemarker.template.Configuration freeMarkerConfiguration(
                FreeMarkerConfig configurer) {
            return configurer.getConfiguration();
        }

        @Bean
        @ConditionalOnMissingBean(name = "freeMarkerViewResolver") // 沒有配置freeMarkerViewResolver這個bean的話,會自動構造一個freeMarkerViewResolver
        @ConditionalOnProperty(name = "spring.freemarker.enabled", matchIfMissing = true) // 配置檔案中開關開啟的話,才會構造
        public FreeMarkerViewResolver freeMarkerViewResolver() {
            // 構造了freemarker的ViewSolver,這就是一開始我們分析的為什麼沒有設定ViewResolver,但是最後卻還是存在的原因
            FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();
            this.properties.applyToViewResolver(resolver);
            return resolver;
        }

    }
}

freemarker對應的配置類:

@ConfigurationProperties(prefix = "spring.freemarker") // 使用配置檔案中以spring.freemarker開頭的配置
public class FreeMarkerProperties extends AbstractTemplateViewResolverProperties {
    public static final String DEFAULT_TEMPLATE_LOADER_PATH = "classpath:/templates/"; // 預設路徑

    public static final String DEFAULT_PREFIX = ""; // 預設字首

    public static final String DEFAULT_SUFFIX = ".ftl"; // 預設字尾

    ...

}

下面是官網上的freemarker配置:

# FREEMARKER (FreeMarkerAutoConfiguration)
spring.freemarker.allow-request-override=false # Set whether HttpServletRequest attributes are allowed to override (hide) controller generated model attributes of the same name.
spring.freemarker.allow-session-override=false # Set whether HttpSession attributes are allowed to override (hide) controller generated model attributes of the same name.
spring.freemarker.cache=false # Enable template caching.
spring.freemarker.charset=UTF-8 # Template encoding.
spring.freemarker.check-template-location=true # Check that the templates location exists.
spring.freemarker.content-type=text/html # Content-Type value.
spring.freemarker.enabled=true # Enable MVC view resolution for this technology.
spring.freemarker.expose-request-attributes=false # Set whether all request attributes should be added to the model prior to merging with the template.
spring.freemarker.expose-session-attributes=false # Set whether all HttpSession attributes should be added to the model prior to merging with the template.
spring.freemarker.expose-spring-macro-helpers=true # Set whether to expose a RequestContext for use by Spring's macro library, under the name "springMacroRequestContext".
spring.freemarker.prefer-file-system-access=true # Prefer file system access for template loading. File system access enables hot detection of template changes.
spring.freemarker.prefix= # Prefix that gets prepended to view names when building a URL.
spring.freemarker.request-context-attribute= # Name of the RequestContext attribute for all views.
spring.freemarker.settings.*= # Well-known FreeMarker keys which will be passed to FreeMarker's Configuration.
spring.freemarker.suffix= # Suffix that gets appended to view names when building a URL.
spring.freemarker.template-loader-path=classpath:/templates/ # Comma-separated list of template paths.
spring.freemarker.view-names= # White list of view names that can be resolved.

所以說一開始我們加入了一個spring-boot-starter-freemarker依賴,這個依賴中存在freemarker的lib,滿足了FreeMarkerAutoConfiguration中的ConditionalOnClass裡寫的freemarker.template.Configuration.class這個類存在於classpath中。

所以就構造了FreeMarkerAutoConfiguration裡的ViewResolver,這個ViewResolver被自動加入到SpringMVC中。

同樣地,如果我們要使用velocity模板,springboot內部也有velocity的自動配置類VelocityAutoConfiguration,原理是跟freemarker一樣的。

其他:

Mybatis的autoconfigure是Mybatis提供的springboot的自動配置模組,由於springboot官方沒有提供mybatis的自動化配置模組,所以mybatis自己寫了這麼一個模組,觀察它的原始碼,發現基本上跟freemarker的autoconfigure模組一樣,只需要構造對應的例項即可。

總結:

springboot內部提供了很多自動化配置的類,這些類會判斷classpath中是否存在自己需要的那個類,如果存在則會自動配置相關的配置,否則就不會自動配置。

如果我們需要使用一些框架,只需要加入依賴即可,這些依賴內部是沒有程式碼的,只是一些對應框架需要的lib,有了這些lib就會觸發自動化配置,於是就能使用框架了。

這一點跟當時看springmvc的時候對response進行json或xml渲染的原理相同。springmvc中的requestmapping註解加上responsebody註解後會返回xml或者json,如果依賴中加入jackson依賴就會轉換成json,如果依賴中加入xstream依賴就會轉換成xml。當然,前提是springmvc中有了這兩種依賴的HttpMessageConverter程式碼,這個HttpMessageConverter程式碼就相當於springboot中的各種AutoConfiguration。

轉 http://fangjian0423.github.io/2016/06/12/springboot-autoconfig-analysis/