Spring Boot 核心(二)
springboot 執行原理:
springboot 的神奇是基於 spring4.x 條件配置 來實現的。我們可以藉助這一特性來理解 springboot 執行自動配置的原理,並實現自己的自動配置。
springboot 關於自動配置的原始碼在 spring-boot-autoconfigure-.*.jar 內,主要包含了如下:
可通過如下三種方式檢視當前專案中已啟用和未啟用的自動配置報告:
①:執行 jar 時增加--debug 引數:
java -jar xx.jar --debug
②:在 application.properties 中設定屬性:
debug=true
③:在 STS 中設定:
此時啟動可在控制檯輸出已啟用的自動配置:
已啟用:
未啟用:
運作原理:
關於 springboot 的運作原理,我們還是迴歸到 @SpringBootApplication 註解上來,這個註解是一個組合註解,它的核心功能是由@EnableAutoConfiguration 註解提供的。
下面我們來看 @EnableAutoConfiguration 註解的原始碼:
這裡的關鍵功能是 @Import 註解匯入的功能配置,EnableAutoConfigurationImportSelector 使用 SpringFactoriesLoaderFactoryNames 方法來掃描 META-INF/spring.factories 檔案的 jar 包,而我們的 spring-boot-autofigure-*.jar 裡就有一個 spring.factories 檔案,此檔案就宣告瞭有哪些自動配置:
核心註解:
開啟上面任意一個 AutoConfiguration 檔案,一般都有下面的條件註解,在 spring-boot-autoconfigure-*.jar 的 org.springframework.boot.autoconfigure.conditon 包下,條件註解如下:
@ConditionalOnBean: 當容器裡有指定的 Beab 的條件下。
@ConditionalOnClass: 當類路徑下有指定的條件下。
@ConditionalOnExpression: 基於 SpEL 表示式作為判斷條件。
@ConditionalOnjava: 基於 JVM 版本作為判斷條件。
@ConditionalOnJndi: 在 JNDI 存在的條件下查詢指定的位置。
@ConditionalOnMissingBean: 當容器中沒有指定的 bean 的情況下。
@ConditonalOnMissingClass: 當類路徑中沒有指定的類的條件下。
@ConditionalOnNotWebApplication: 當專案下不是 web 專案的條件下。
@ConditionalOnProperty: 指定的屬性是否有指定的值。
@ConditionalOnResource: 類路徑是否有指定額值。
@ConditionalOnSingleCandidate: 當指定 bean 在容器中只有一個,或者雖然有多個但是指定是首選的 bean。
@ConditionalOnWebApplication: 當前專案是 web 專案的條件下。
這些註解都是組合了 @Conditonal 元註解,只是使用了不同的條件(Condition)。
@ConditionalOnWebApplication 註解:
從原始碼可以看出使用的條件是 OnWebApplicationCondition,下面我們看看這個條件是如何構造的:
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.condition;
import java.util.Map;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.web.reactive.context.ConfigurableReactiveWebEnvironment;
import org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.ConfigurableWebEnvironment;
import org.springframework.web.context.WebApplicationContext;
/**
* {@link Condition} that checks for the presence or absence of
* {@link WebApplicationContext}.
*
* @author Dave Syer
* @see ConditionalOnWebApplication
* @see ConditionalOnNotWebApplication
*/
@Order(Ordered.HIGHEST_PRECEDENCE + 20)
class OnWebApplicationCondition extends SpringBootCondition {
private static final String WEB_CONTEXT_CLASS = "org.springframework.web.context."
+ "support.GenericWebApplicationContext";
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
boolean required = metadata
.isAnnotated(ConditionalOnWebApplication.class.getName());
ConditionOutcome outcome = isWebApplication(context, metadata, required);
if (required && !outcome.isMatch()) {
return ConditionOutcome.noMatch(outcome.getConditionMessage());
}
if (!required && outcome.isMatch()) {
return ConditionOutcome.noMatch(outcome.getConditionMessage());
}
return ConditionOutcome.match(outcome.getConditionMessage());
}
private ConditionOutcome isWebApplication(ConditionContext context,
AnnotatedTypeMetadata metadata, boolean required) {
ConditionMessage.Builder message = ConditionMessage.forCondition(
ConditionalOnWebApplication.class, required ? "(required)" : "");
Type type = deduceType(metadata);
if (Type.SERVLET == type) {
return isServletWebApplication(context);
}
else if (Type.REACTIVE == type) {
return isReactiveWebApplication(context);
}
else {
ConditionOutcome servletOutcome = isServletWebApplication(context);
if (servletOutcome.isMatch() && required) {
return new ConditionOutcome(servletOutcome.isMatch(),
message.because(servletOutcome.getMessage()));
}
ConditionOutcome reactiveOutcome = isReactiveWebApplication(context);
if (reactiveOutcome.isMatch() && required) {
return new ConditionOutcome(reactiveOutcome.isMatch(),
message.because(reactiveOutcome.getMessage()));
}
boolean finalOutcome = (required
? servletOutcome.isMatch() && reactiveOutcome.isMatch()
: servletOutcome.isMatch() || reactiveOutcome.isMatch());
return new ConditionOutcome(finalOutcome,
message.because(servletOutcome.getMessage()).append("and")
.append(reactiveOutcome.getMessage()));
}
}
private ConditionOutcome isServletWebApplication(ConditionContext context) {
ConditionMessage.Builder message = ConditionMessage.forCondition("");
if (!ClassUtils.isPresent(WEB_CONTEXT_CLASS, context.getClassLoader())) {
return ConditionOutcome
.noMatch(message.didNotFind("web application classes").atAll());
}
if (context.getBeanFactory() != null) {
String[] scopes = context.getBeanFactory().getRegisteredScopeNames();
if (ObjectUtils.containsElement(scopes, "session")) {
return ConditionOutcome.match(message.foundExactly("'session' scope"));
}
}
if (context.getEnvironment() instanceof ConfigurableWebEnvironment) {
return ConditionOutcome
.match(message.foundExactly("ConfigurableWebEnvironment"));
}
if (context.getResourceLoader() instanceof WebApplicationContext) {
return ConditionOutcome.match(message.foundExactly("WebApplicationContext"));
}
return ConditionOutcome.noMatch(message.because("not a servlet web application"));
}
private ConditionOutcome isReactiveWebApplication(ConditionContext context) {
ConditionMessage.Builder message = ConditionMessage.forCondition("");
if (context.getEnvironment() instanceof ConfigurableReactiveWebEnvironment) {
return ConditionOutcome
.match(message.foundExactly("ConfigurableReactiveWebEnvironment"));
}
if (context.getResourceLoader() instanceof ReactiveWebApplicationContext) {
return ConditionOutcome
.match(message.foundExactly("ReactiveWebApplicationContext"));
}
return ConditionOutcome
.noMatch(message.because("not a reactive web application"));
}
private Type deduceType(AnnotatedTypeMetadata metadata) {
Map<String, Object> attributes = metadata
.getAnnotationAttributes(ConditionalOnWebApplication.class.getName());
if (attributes != null) {
return (Type) attributes.get("type");
}
return Type.ANY;
}
}
從 isServletWebApplication 方法可以看出,判斷條件是:
①:GenericWebApplicationContext 是否在類路徑中。
②:容器是否有名為 session 的 scope。
③:當前容器的 Enviroment 是否為 ConfigurableWebEnvironment
④:當前 ResourceLoader 是否為 WebApplicationContext(ResourceLoader 是 ApplicationContext 的頂級介面之一);
======================================================================================================
例項分析:
在瞭解 springboot 的運作原理和主要的條件註解後,現在來分析一個簡單的 springboot 內建的自動配置功能:HTTP 的編碼配置。
我們在常規的配置 HTTP 編碼的時候是在 web.xml 中配置一個 filter。
<filter>....</filter>
HTTP 編碼配置的自動配置需要兩個條件:
①:能配置 CharacterEncodingFilter 這個 Bean
②:能配置 encoding 和 forceEncoding 這兩個引數。
配置引數:
springboot 是基於型別安全的配置,這裡的配置類可以在 application.properties 中直接設定。
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.http;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Configuration properties for http encoding.
*
* @author Stephane Nicoll
* @author Brian Clozel
* @since 1.2.0
*/
@ConfigurationProperties(prefix = "spring.http.encoding") //1
public class HttpEncodingProperties {
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;//2
/**
* Charset of HTTP requests and responses. Added to the "Content-Type" header if not
* set explicitly.
*/
private Charset charset = DEFAULT_CHARSET;//2
/**
* Whether to force the encoding to the configured charset on HTTP requests and
* responses.
*/
private Boolean force;//3
/**
* Whether to force the encoding to the configured charset on HTTP requests. Defaults
* to true when "force" has not been specified.
*/
private Boolean forceRequest;
/**
* Whether to force the encoding to the configured charset on HTTP responses.
*/
private Boolean forceResponse;
/**
* Locale in which to encode mapping.
*/
private Map<Locale, Charset> mapping;
public Charset getCharset() {
return this.charset;
}
public void setCharset(Charset charset) {
this.charset = charset;
}
public boolean isForce() {
return Boolean.TRUE.equals(this.force);
}
public void setForce(boolean force) {
this.force = force;
}
public boolean isForceRequest() {
return Boolean.TRUE.equals(this.forceRequest);
}
public void setForceRequest(boolean forceRequest) {
this.forceRequest = forceRequest;
}
public boolean isForceResponse() {
return Boolean.TRUE.equals(this.forceResponse);
}
public void setForceResponse(boolean forceResponse) {
this.forceResponse = forceResponse;
}
public Map<Locale, Charset> getMapping() {
return this.mapping;
}
public void setMapping(Map<Locale, Charset> mapping) {
this.mapping = mapping;
}
public boolean shouldForce(Type type) {
Boolean force = (type == Type.REQUEST ? this.forceRequest : this.forceResponse);
if (force == null) {
force = this.force;
}
if (force == null) {
force = (type == Type.REQUEST);
}
return force;
}
public enum Type {
REQUEST, RESPONSE
}
}
①:在 application.properties 配置的時候字首是 spring.http.encoding;
②:預設編碼方式是 UTF-8,若修改可使用 spring.http.charset= 編碼;
③:設定 forceEncoding,預設為 true,若修改可使用 spring.http.encoding.force = false;
配置 bean
通過上述配置,並根據條件配置 CharacterEcodingFilter 的 bean ,我們來看看原始碼:
/*
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.web.servlet;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.http.HttpEncodingProperties;
import org.springframework.boot.autoconfigure.http.HttpEncodingProperties.Type;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.filter.OrderedCharacterEncodingFilter;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.filter.CharacterEncodingFilter;
/**
* {@link EnableAutoConfiguration Auto-configuration} for configuring the encoding to use
* in web applications.
*
* @author Stephane Nicoll
* @author Brian Clozel
* @since 1.2.0
*/
@Configuration
@EnableConfigurationProperties(HttpEncodingProperties.class) //1
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(CharacterEncodingFilter.class)//2
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)//3
public class HttpEncodingAutoConfiguration {
private final HttpEncodingProperties properties;//3
public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) {
this.properties = properties;
}
@Bean//4
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
return filter;
}
@Bean
public LocaleCharsetMappingsCustomizer localeCharsetMappingsCustomizer() {
return new LocaleCharsetMappingsCustomizer(this.properties);
}
private static class LocaleCharsetMappingsCustomizer implements
WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {
private final HttpEncodingProperties properties;
LocaleCharsetMappingsCustomizer(HttpEncodingProperties properties) {
this.properties = properties;
}
@Override
public void customize(ConfigurableServletWebServerFactory factory) {
if (this.properties.getMapping() != null) {
factory.setLocaleCharsetMappings(this.properties.getMapping());
}
}
@Override
public int getOrder() {
return 0;
}
}
}
①:開啟屬性注入,通過 @EnableConfigurationProperties 宣告,使用 @AutoWired 注入;
②:當 CharacterEncodingFilter 在類路徑的條件下;
③:當設定 spring.http.encoding=enabled 的情況下,如果沒有設定則預設為 true,即條件符合;
④:像使用 Java 配置的方式配置 CharacterEncodingFilter 這個 Bean;
⑤:當容器中沒有這個 Bean 的時候新建 Bean;
實戰!!!!
package com.pangu.springboot_autoconfig;
/**
* 本例使用這個類的存在與否來建立這個類的 bean,這個類可以是第三方類庫
* @author etfox
*
*/
public class HelloService {
private String msg;
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
package com.pangu.springboot_autoconfig;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 在 application.properties 中使用 hello.msg=xxx 來設定值,不設定預設是 world
* @author etfox
*
*/
@ConfigurationProperties(prefix="hello")
public class HelloServiceProperties {
private static final String MSG = "world";
private String msg = MSG;
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
package com.pangu.springboot_autoconfig; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * 根據 HelloServiceProperties 提供的引數,並通過 @ConditionalOnClass 判斷 * HelloService 這個類在類路徑中是否存在,且當容器中沒有這個 bean 的情況下自動配置這個 Bean. * @author etfox * */ @Configuration @EnableConfigurationProperties(HelloServiceProperties.class) @ConditionalOnClass(HelloService.class) @ConditionalOnProperty(prefix = "hello", value = "enable", matchIfMissing = true) public class HelloServiceAutoConfiguration { @Autowired private HelloServiceProperties helloServiceProperties; @Bean @ConditionalOnMissingBean(HelloService.class) public HelloService helloService(){ HelloService helloService = new HelloService(); helloService.setMsg(helloServiceProperties.getMsg()); return helloService; } }我們知道,若想要配置生效,需要註冊自動配置類,在 src\main\resources 下新建 META-INF/spring.factories, 此處”\“是為了換行後仍然能讀到屬性。
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.pangu.springboot_autoconfig.HelloServiceAutoConfiguration
結構如下:
在專案中引用自定義的 starter pom:
<dependency>
<groupId>com.pangu</groupId>
<artifactId>springboot-autoconfig</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
application 設定值,預設 world.
run,訪問....
相關文章
- SpringBoot詳解(二)-Spring Boot的核心Spring Boot
- Spring Boot核心配置Spring Boot
- Spring Boot 核心(一)Spring Boot
- Spring Boot學習筆記:Spring Boot核心配置Spring Boot筆記
- Spring Boot核心技術Spring Boot
- Spring boot學習(二) Spring boot基礎配置Spring Boot
- Spring Boot核心原理-自動配置Spring Boot
- Spring Boot 2.0(二):Spring Boot 2.0嚐鮮-動態 BannerSpring Boot
- spring boot 二 整合 FastJsonSpring BootASTJSON
- Spring boot入門(二):Spring boot整合MySql,Mybatis和PageHelper外掛Spring BootMySqlMyBatis
- Spring Boot(二):Web 綜合開發Spring BootWeb
- Spring Boot (二):Web 綜合開發Spring BootWeb
- spring boot(二)配置資訊的讀取Spring Boot
- Spring核心系列之AOP(二)Spring
- Spring Boot 2.0深度實踐之核心技術篇Spring Boot
- (二)Spring Boot 起步入門(翻譯自Spring Boot官方教程文件)1.5.9.RELEASESpring Boot
- Spring Boot:Spring Boot配置MybatisSpring BootMyBatis
- spring boot redis做mybatis二級快取Spring BootRedisMyBatis快取
- Spring Boot + Mybatis + Spring MVC環境配置(二):Mybatis Generator配置Spring BootMyBatisMVC
- spring微服務實戰(二):使用Spring Boot建立微服務微服務Spring Boot
- Spring Boot 最核心的 25 個註解,都是乾貨!Spring Boot
- Spring Boot (十三): Spring Boot 小技巧Spring Boot
- 備忘錄二:Spring Boot Actuator+Prometheus+GrafanaSpring BootPrometheusGrafana
- Spring Boot自動配置原理與實踐(二)Spring Boot
- Spring Boot 2.0 新特性(二):新增事件ApplicationStartedEventSpring Boot事件APPdev
- Spring Boot + Mybatis + Redis二級快取例項Spring BootMyBatisRedis快取
- Spring Boot:Spring Boot配置SwaggerSpring BootSwagger
- Spring Boot 2.0(八):Spring Boot 整合 MemcachedSpring Boot
- Spring Boot 參考指南(Spring Boot文件)Spring Boot
- Spring Boot學習6:Spring Boot JDBCSpring BootJDBC
- Spring Boot(十八):使用 Spring Boot 整合 FastDFSSpring BootAST
- Spring Boot(五):Spring Boot Jpa 的使用Spring Boot
- Jeecg-Boot Spring BootSpring Boot
- Spring boot 微服務核心元件集 mica v1.0.1 釋出Spring Boot微服務元件
- 深入理解SpringBoot核心機制《spring-boot-starter》Spring Boot
- Spring BootSpring Boot
- Spring Boot系列(四):Spring Boot原始碼解析Spring Boot原始碼
- Spring Boot系列(一):Spring Boot快速開始Spring Boot