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-boot和spring-boot-autoconfigure這兩個。
spring-boot定義了很多基礎功能類,像執行程式的SpringApplication,Logging系統,一些tomcat或者jetty這些EmbeddedServlet容器,配置屬性loader等等。
包括了這些包:
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/