一、簡介
Spring Boot簡化了Spring應用的開發,采用約定大於配置的思想,去繁從簡,很方便就能構建一個獨立的、產品級別的應用。
1.傳統J2EE開發的缺點
開發笨重、配置繁多復雜、開發效率低下、部署流程復雜、第三方技術集成難度大。
2.SpringBoot的優點
- 快速重建獨立運行的Spring項目以及與主流框架集成。
- 使用嵌入式的Servlet容器,應用無需打成WAR包
- starters自動依賴與版本控制
- 大量的自動配置、簡化開發,也可以修改其默認值
- 無需配置XML,無代碼生成
- 准生產環境的運行時應用監控
- 與雲計算的天然繼承
3.SpringBoot helloworld說明
1.starters
- SpringBoot為我們提供了簡化企業級開發絕大多數場景的starters pom(啟動器),只要引入了相應場景的starters pom,相關技術的絕大部分配置將會消除(字段配置),從而簡化我們的開發。業務中我們就會使用到SpringBoot為我們字段配置的Bean。
- 這些starters幾乎涵蓋了javaee所有常用場景,SpringBoot對這些場景依賴的jar也做了嚴格的測試和版本控制。
- spring-boot-dependencies里面定義了jar包的版本。
2.入口類和@SpringBootApplication
- 程序從main方法開始運行。
- 使用SpringApplication.run()加載主程序類
- 主程序類需標注@SpringBootApplication
- @EnableAutoConfiguration是核心注解
- @Import導入所有的自動配置場景
- @AutoConfigurationPackage定義默認的包掃描規則。
- 程序啟動掃描主程序類所在的包以及下面所有子包的組件
3.自動配置
自動配置xxxAutoConfiguration
- SpringBoot中存現大量的這些類,這些類的作用就是幫我們進行自動裝配
- 它會將這個場景需要的所有組件都注冊到容器中,並配置好
- 他們在類路徑下的META-INF/spring.factories文件中
- spring-boot-autoconfigure.jar中包含了所有場景的字段配置類代碼
- 這些自動配置類是SpringBoot進行自動裝配的關鍵。
二、SpringBoot配置
1.配置文件
-
SpringBoot使用一個全局的配置文件。配置文件名是固定的。
-application.properties或者application.yml -
配置文件放在src/main/resources目錄或者類路徑/config下。
-
全局配置文件的作用是對一些默認配置進行修改
2.配置文件值注入
- @Value和@ConfigurationProperties為屬性注入值進行對比
| 對比點 | @ConfigurationProperties | @Value |
|---|---|---|
| 功能 | 批量注入配置文件中的屬性 | 一個個指定 |
| 松散綁定(松散語法) | 支持 | 不支持 |
| SpEL | 不支持 | 支持 |
| JSR303數據校驗 | 支持 | 不支持 |
| 復雜類型封裝 | 支持 | 不支持 |
-
屬性名匹配規則
-person.firstName 使用標准方式
-person.first-name 使用-
-person.first_name 使用_
-PERSON_FIRST_NAME 推薦系統屬性使用這種寫法 -
@PropertySource
加載指定的配置文件 -
ConfigurationProperties
-與@Bean結合為屬性賦值
-與@PropertySource(只能用於properties文件)結合讀取指定文件。 -
ConfigurationProperties Validation
-支持JSR303進行配置文件值校驗。
@Component
@PropertySource(value={"classpath:person.properties"})
@ConfigurationProperties(prefix="person")
@Validated
public class Person{
@Email
@Value("${person.email}")
private String email;
}
- ImportResource讀取外部配置文件
3.配置文件占位符
-
RandomValuePropertySource
配置文件中可以使用隨機數
-${random.value}
-${random.int}
-${random.long}
-${random.int(10)}
-${random.int[1024,65536]} -
屬性配置占用符
-可以在配置文件中引用前面配置過的屬性(Y優先級前面配置過的這里都可以使用)。
-${app.name:默認值}來指定找不到屬性時的默認值。
app.name=MyApp
app.description=${app.name} is a SpringBoot Application
4.profile
profile是Spring對不同環境提供不同配置功能的支持,可以通過激活指定參數的方式快速切換環境。
1.多profile文件形式
- 格式:application-{profile}.properties/yml
application-dev.properties、application-prod.properties
2.多profile文檔塊模式
spring.profiles.active=prod #激活指定配置
spring.profiles=prod
server.port=80
# default表示未指定時的默認配置
spring.profiles=default
server.port=8080
3.激活方式
- 命令行:--spring.profiles.active=dev
- 配置文件:spring.profiles.active=dev
- jvm參數:-Dspring.profiles.active=dev
5.配置文件加載位置
SpringBoot啟動會掃描一下位置的application.properties或者application.yml文件作為SpringBoot的默認配置文件。
- file:./config/
- file:./
- classpath:/config/
-classpath:/
-以上是按照優先級從高到低的順序,所有位置的文件都會被加載,高優先級配置內容會覆蓋低優先級配置內容。
-可以通過配置spring.config.location來改變默認配置。
6.外部配置加載順序
- 命令行參數
- 來自java:comp/env的JNDI屬性
- Java系統屬性(System.getProperties())
- 操作系統環境變量
- RandomValuePropertySource配置的random.*屬性值
- jar包外部的application-{profile}.properties或application.yml(帶spring.profile)配置文件
- jar包內部的application-{profile}.properties或application.yml(帶spring.profile)配置文件
- jar包外部的application.properties或application.yml(不帶spring.profile)配置文件
- jar包內部的application.properties或application.yml(不帶spring.profile)配置文件
- @Configuration注解類上的@PropertySource。
- 通過SpringApplication.setDefaultproperties指定的默認屬性。
7.自動配置原理
1.SpringBoot啟動的時候加載主配置類,開啟了自動配置功能@EnableAutoConfiguration
2.@EnableAutoConfiguration作用
- 利用EnableAutoConfigurationImportSelector給容器中導入一些組件。
- 將類路徑小META-INF/spring.factories里面配置的所有EnableAutoConfiguration的值加入到了容器中。
3.@Conditional派生注解
| @Conditional擴展注解 | 作用(判斷是否滿足當期指定條件) |
|---|---|
| @ConditionalOnJava | 系統的java版本是否符合要求 |
| @ConditionalOnBean | 容器中存在指定Bean |
| @ConditionalOnMissingBean | 容器中不存在指定Bean |
| @ConditionalOnExpression | 滿足SpEL表達式指定 |
| @ConditionalOnClass | 系統中有指定的類 |
| @ConditionalOnMissingClass | 容器中沒有指定類 |
| @ConditionalOnSingleCandidate | 容器中只有一個指定的Bean,或者這個Bean是首選Bean |
| @ConditionalOnProperty | 系統中指定的屬性是否有指定的值 |
| @ConditionalOnResource | 類路徑下是否存在指定資源文件 |
| @ConditionalOnWebApplication | 當前是web環境 |
| @ConditionalOnNotWebApplication | 當前不是web環境 |
| @ConditionalOnJndi | JNDI存在指定項 |
- 作用:必須是@Conditional指定的條件成立,才給容器中添加組件,配置配里面的所有內容才生效。
三、SpringBoot與日志
1.日志框架
市場上存在非常多的日志框架,JUL(java.util.logging)、JCL(Apache Commons Logging)、Log4J、Log4J2、Logback、SLF4j、jboss-logging等。
- SpringBoot早框架內部使用JCL。spring-boot-starter-logging采用了slf4j+logback的形式,SpringBoot也能自動配置(jul、log4j2、logback)並簡化配置。
| 日志門面 | 日志實現 |
|---|---|
| JCL、SLF4J、jboss-logging | log4j、JUL、Log4j2、Logback |
| 日志系統 | 配置文件 |
|---|---|
| Logback | logback-spring.xml、logback-spring.groovy、logback.xml或logback.groovy |
| Log4j2 | log4j2-spring.xml、log4j2.xml |
| JUL | logging.properties |

- 總結:
1.SpringBoot底層也是使用slf4j+logback的方式進行日志記錄。
2.SpringBoot也把其他的日志都替換成了slf4j。
3.如果要引入其他日志框架,要排除Spring框架的commons-logging依賴。
四、Web開發
1.SpringBoot對靜態資源的映射規則
- 所有/webjars/**,都去classpath:/META-INF/resources/webjars/ 下面找資源。
- "/**" 訪問當前項目的任何資源,都去(靜態資源的文件夾)找映射。
- 歡迎頁;靜態資源文件夾下的所有index.html頁面,被"/**" 映射。
- 所有的 **/favicon.ico都是在靜態資源文件下找。
2.SpringMVC自動配置
1.SpringMVC auto-configuration
SpringBoot對SpringMVC的默認配置(WebMvcAutoConfiguration)如下:
- 包含了ContentNegotiatingViewResolver和BeanNameViewResolver。
- 自動配置了ViewResolver
- ContentNegotiatingViewResolver:組合所有的視圖解析器
- 支持靜態資源,包括支持Wenjars
- 靜態首頁訪問
- 支持favicon.ico
- 自動注冊了Converter、GenericConverter、Formatter。
- Converter:轉換器。
- Formatter:格式化器
-
支持HttpMessageConverters
- HttpMessageConverter:SpringMVC用來轉換Http請求和響應。
- HttpMessageConverters:從容器中確定,獲取所有的HttpMessageConverter;
-
自動注入MessageCodesResolver,定義錯誤碼生成規則。
-
自動使用ConfigurableWebBindingInitializer。
2.擴展SpringMVC
編寫一個配置類(@Configuration),是WebMvcConfigurerAdapter類型,不能標注@EnableWebMvc注解
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/desperado").setViewName("success");
}
}
原理
- WebMvcAutoConfiguration是SpringMVC的自動配置類。
- 在做其他自動配置時會導入。
- 容器中所有的WebMvcConfigurer都會一起被注冊。
- 我們自定義的配置類也會被調用。
3.全面接管SpringMVC
如果想要使SpringMVC的自動配置失效,只需要在我們自定義的配置類中添加@EnableWebMvc注解即可。
@EnableWebMvc
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/desperado").setViewName("success");
}
}
原理
- @EnableWebMvc的注解
@Import(DelegatingWebMvcConfiguation.class)
public @interface EnableWebMvc{}
- DelegatingWebMvcConfiguation
@Configuration
public class DelegatingWebMvcConfiguation extend WebMvcConfigurationSupport{}
- WebMvcAutoConfiguration
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({Servlet.class,DispatcherServlet.class,
WebMvcConfigurerAdapter.class})
//容器中沒有這個組件,這個自動配置類才會生效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({DispatcherServletAutoConfiguration.class,
ValidationAutoConfiguration.class})
public class WebMvcAutoConfiguration{}
- @EnableWebMvc會將WebMvcConfigurationSupport組件導入進來。
- 導入的WebMvcConfigurationSupport只是SpringMVC最基本的功能。
4.修改默認配置
- SpringBoot在自動配置很多組件的時候,顯卡容器中有沒有用戶自己配置的(@Bean 、@Component),如果有就用用戶配置的,如果沒有,才會進行自動配置;如果某些組件可以有多個,將用戶配置的和自己默認的組合起來。
- 在SpringBoot中有許多的xxxConfigurer幫助我們進行擴展配置
- 在SpringBoot中有許多的xxxCustomizer幫助我們進行定制配置
5.默認訪問首頁
使用自定義WebMvcConfigurationAdapter進行配置
//使用WebMvcConfigurationAdapter可以擴展SpringMVC的功能
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//瀏覽器發送/desperado 請求來到success
registry.addViewController("/desperado").setViewName("success");
}
//所有的webMvcConfigurerAdapter組件都會一起起作用
@Bean //將組件注冊到容器
public WebMvcConfigurerAdapter webMvcConfigurerAdapter(){
WebMvcConfigurerAdapter adapter = new WebMvcConfigurerAdapter() {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//配置默認路徑的頁面
registry.addViewController("/").setViewName("login");
registry.addViewController("/index.html").setViewName("login");
}
};
return adapter;
}
}
6.國際化
1.編寫國際化配置文件
編寫不同語言的配置文件,比如login.properties、login_en_US.properties、login_zh_CN.properties等。
2. SpringBoot自動配置好了管理國際化資源文件的組件。
@EnableConfigurationProperties
public class MessageSourceAutoConfiguration {
private static final Resource[] NO_RESOURCES = new Resource[0];
public MessageSourceAutoConfiguration() {
}
@Bean
@ConfigurationProperties(
prefix = "spring.messages"
)
public MessageSourceProperties messageSourceProperties() {
return new MessageSourceProperties();
}
@Bean
public MessageSource messageSource(MessageSourceProperties properties) {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
if (StringUtils.hasText(properties.getBasename())) {
//設置國際化資源文件的基礎名(去掉語言國家代碼)
messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename())));
}
if (properties.getEncoding() != null) {
messageSource.setDefaultEncoding(properties.getEncoding().name());
}
messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
Duration cacheDuration = properties.getCacheDuration();
if (cacheDuration != null) {
messageSource.setCacheMillis(cacheDuration.toMillis());
}
messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
return messageSource;
}
原理
根據請求頭帶來的區域信息獲取Locale進行國際化。
五、錯誤處理機制
1.默認的錯誤處理機制
- 瀏覽器,默認返回一個默認的錯誤頁面。
- 其他客戶端,默認響應一個json數據。
原理
- 在DefaultErrorAttributes中獲取錯誤頁面的信息
public class DefaultErrorAttributes implements ErrorAttributes {
//獲取錯誤頁面的信息
public Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap();
errorAttributes.put("timestamp", new Date());
errorAttributes.put("path", request.path());
Throwable error = this.getError(request);
HttpStatus errorStatus = this.determineHttpStatus(error);
errorAttributes.put("status", errorStatus.value());
errorAttributes.put("error", errorStatus.getReasonPhrase());
errorAttributes.put("message", this.determineMessage(error));
this.handleException(errorAttributes, this.determineException(error), includeStackTrace);
return errorAttributes;
}
}
- 在BasicErrorController中處理/error請求
@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"})
public class BasicErrorController extends AbstractErrorController {
//產生html類型的數據,瀏覽器發送的請求來打這個方法處理
@RequestMapping(
produces = {"text/html"}
)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = this.getStatus(request);
Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
//去哪個頁面作為錯誤頁面。包含頁面地址和頁面內容
ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
return modelAndView != null ? modelAndView : new ModelAndView("error", model);
}
//產生json數據,其他客戶端來到這個方法處理
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = this.getStatus(request);
return new ResponseEntity(body, status);
}
}
- ErrorPageCustomizer進行錯誤配置
public class ErrorProperties {
@Value("${error.path:/error}")
private String path = "/error";
private boolean includeException;
private ErrorProperties.IncludeStacktrace includeStacktrace;
private final ErrorProperties.Whitelabel whitelabel;
}
- ErrorMvcAutoConfiguration生成錯誤頁面
public class ErrorMvcAutoConfiguration {
private static class StaticView implements View {
private static final Log logger = LogFactory.getLog(ErrorMvcAutoConfiguration.StaticView.class);
private StaticView() {
}
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
if (response.isCommitted()) {
String message = this.getMessage(model);
logger.error(message);
} else {
StringBuilder builder = new StringBuilder();
Date timestamp = (Date)model.get("timestamp");
Object message = model.get("message");
Object trace = model.get("trace");
if (response.getContentType() == null) {
response.setContentType(this.getContentType());
}
builder.append("<html><body><h1>Whitelabel Error Page</h1>").append("<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>").append("<div id='created'>").append(timestamp).append("</div>").append("<div>There was an unexpected error (type=").append(this.htmlEscape(model.get("error"))).append(", status=").append(this.htmlEscape(model.get("status"))).append(").</div>");
if (message != null) {
builder.append("<div>").append(this.htmlEscape(message)).append("</div>");
}
if (trace != null) {
builder.append("<div style='white-space:pre-wrap;'>").append(this.htmlEscape(trace)).append("</div>");
}
builder.append("</body></html>");
response.getWriter().append(builder.toString());
}
}
private String htmlEscape(Object input) {
return input != null ? HtmlUtils.htmlEscape(input.toString()) : null;
}
private String getMessage(Map<String, ?> model) {
Object path = model.get("path");
String message = "Cannot render error page for request [" + path + "]";
if (model.get("message") != null) {
message = message + " and exception [" + model.get("message") + "]";
}
message = message + " as the response has already been committed.";
message = message + " As a result, the response may have the wrong status code.";
return message;
}
public String getContentType() {
return "text/html";
}
}
5.DefaultErrorViewResolver解析頁面
public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
ModelAndView modelAndView = this.resolve(String.valueOf(status.value()), model);
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()), model);
}
return modelAndView;
}
private ModelAndView resolve(String viewName, Map<String, Object> model) {
//默認去找一個頁, error/404
String errorViewName = "error/" + viewName;
//模板引擎可以解析這個頁面地址就要模板引擎解析
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
//模板引擎可用的情況下返回到errorViewName指定的視圖地址
//模板引擎不可以,就在靜態資源文件夾下找對應的頁面
return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);
}
2.錯誤頁面的優先級(自定義錯誤頁面)
- 在模板引擎的情況下,error/狀態碼(將錯誤頁面命名為 錯誤狀態碼.html 放在模板引擎文件夾里面的error文件夾下),發生此狀態碼的錯誤就會來到對應的頁面;
- 沒有模板引擎(模板引擎找不到錯誤頁面),就在靜態資源文件夾下找。
- 以上都沒有錯誤頁面,就是默認來到SpringBoot默認的錯誤提示頁面。
3.如何定制錯誤的json數據
- 自定義異常處理&返回定制json數據(沒有自適應效果)
@ControllerAdvice
public class MyExceptionHandler {
@ResponseBody
@ExceptionHandler(CustomException.class)
public Map<String,Object> handleException(Exception e){
HashMap<String, Object> map = new HashMap<>();
map.put("code","錯誤信息");
map.put("message",e.getMessage());
return map;
}
}
2.轉發到/error進行自適應響應效果處理。
@ControllerAdvice
public class MyExceptionHandler {
@ResponseBody
@ExceptionHandler(CustomException.class)
public String handleException(Exception e, HttpServletRequest request){
HashMap<String, Object> map = new HashMap<>();
//傳入我們自己的錯誤狀態碼,否則就不會進入定制錯誤頁面的解析流程
request.setAttribute("java.servlet.error.status_code","500");
map.put("code","錯誤信息");
map.put("message",e.getMessage());
//轉發到/error
return "forward:/error";
}
}
4.將定制的數據發送出去
出現錯誤之后,回來到/error請求,會被BasicErrorController處理,響應出去可以獲取的數據是由getErrorAttributes得到的。
- 編寫一個ErrorController的實現類(或者是編寫AbstractErrorController的子類),放到容器中。
- 頁面上能用的數據,或者是json返回能用的數據都是通過errorAttributes.getErrorAttributes得到;容器中DefaultErrorAttributes.getErrorAttributes()默認進行數據處理.
//給容器中加入自定義的ErrorAttributes
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
//獲取ErrorAttributes的map
Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
//加入自己屬性字段
map.put("name","desperado");
return map;
}
}
六、配置嵌入式Servlet容器
SpringBoot默認使用Tomcat作為內嵌的Servlet容器

1.修改Servlet容器的配置
在配置文件application文件中修改和server有關的配置。
server.port=8081
server.context_path=/crud
server.tomcat.uri-encoding=utf-8
2. 定制Servlet容器的相關配置
編寫一個EmbeddedServletContainerCustomizer(2.x中使用WebServerFactoryCustomizer),來修改Servlet容器的配置。
@Bean
public WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer(){
return new WebServerFactoryCustomizer<ConfigurableWebServerFactory>(){
@Override
public void customize(ConfigurableWebServerFactory factory) {
factory.setPort(8081);
}
};
}
3.注冊Servlet三大組件
由於SpringBoot是默認以jar包的方式啟動內嵌的Servlet容器來啟動SpringBoot的web應用,沒有web.xml文件。所以注冊Servlet、Filter、Listener的方式也不同
1. 注入Servlet
@Bean
public ServletRegistrationBean<MyServlet> myServlet(){
ServletRegistrationBean<MyServlet> registrationBean =
new ServletRegistrationBean<>(new MyServlet(), "/myServlet");
return registrationBean;
}
2. 注入Filter
@Bean
public FilterRegistrationBean<MyFilter> myFilter(){
FilterRegistrationBean<MyFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new MyFilter());
registrationBean.setUrlPatterns(Arrays.asList("/hello","/myServlet"));
return registrationBean;
}
3. 注入Listener
@Bean
public ServletListenerRegistrationBean<MyListener> myListener(){
ServletListenerRegistrationBean<MyListener> registrationBean =
new ServletListenerRegistrationBean<>(new MyListener());
return registrationBean;
}
4.替換為其他嵌入式Servlet容器
替換為其他的Servlet非常簡單,只需要在pom中引入其依賴,然后排除tomcat的依賴即可.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
5. 嵌入式Servlet容器自動配置原理
- EmbeddedServletContainerAutoConfiguration(2.x對應ServletWebServerFactoryConfiguration):嵌入式容器的自動配置
@Configuration
class ServletWebServerFactoryConfiguration {
ServletWebServerFactoryConfiguration() {
}
@Configuration
@ConditionalOnClass({Servlet.class, Undertow.class, SslClientAuthMode.class})
@ConditionalOnMissingBean(
value = {ServletWebServerFactory.class},
search = SearchStrategy.CURRENT
)
public static class EmbeddedUndertow {
public EmbeddedUndertow() {
}
@Bean
public UndertowServletWebServerFactory undertowServletWebServerFactory() {
return new UndertowServletWebServerFactory();
}
}
@Configuration
@ConditionalOnClass({Servlet.class, Server.class, Loader.class, WebAppContext.class})
@ConditionalOnMissingBean(
value = {ServletWebServerFactory.class},
search = SearchStrategy.CURRENT
)
public static class EmbeddedJetty {
public EmbeddedJetty() {
}
@Bean
public JettyServletWebServerFactory JettyServletWebServerFactory() {
return new JettyServletWebServerFactory();
}
}
@Configuration
//判斷當前是否引入了tomcat依賴
@ConditionalOnClass({Servlet.class, Tomcat.class, UpgradeProtocol.class})
///判斷當前容器沒有用戶自己定義ServletWebServerFactory:
//嵌入式的Servlet容器工廠;作用:創建嵌入式的Servlet容器
@ConditionalOnMissingBean(
value = {ServletWebServerFactory.class},
search = SearchStrategy.CURRENT
)
public static class EmbeddedTomcat {
public EmbeddedTomcat() {
}
@Bean
public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
return new TomcatServletWebServerFactory();
}
}
}
- 嵌入式Servlet容器工廠

- 嵌入式的Servlet容器

4.以tomcat為例
public WebServer getWebServer(ServletContextInitializer... initializers) {
Tomcat tomcat = new Tomcat();
File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
tomcat.getService().addConnector(connector);
this.customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
this.configureEngine(tomcat.getEngine());
Iterator var5 = this.additionalTomcatConnectors.iterator();
while(var5.hasNext()) {
Connector additionalConnector = (Connector)var5.next();
tomcat.getService().addConnector(additionalConnector);
}
this.prepareContext(tomcat.getHost(), initializers);
return thisb.getTomcatWebServer(tomcat);
}
- 容器中導入 WebServerFactoryCustomizerBeanPostProcessor
public class WebServerFactoryCustomizerBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
//初始化之前
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
//如果當前初始化的是一個WebServerFactory類型的組件
if (bean instanceof WebServerFactory) {
this.postProcessBeforeInitialization((WebServerFactory)bean);
}
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
// 獲取所有的定制器,調用每一個定制器的customize方法來給servlet容器進行屬性賦值
((Callbacks)LambdaSafe.callbacks(WebServerFactoryCustomizer.class, this.getCustomizers(), webServerFactory, new Object[0]).withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)).invoke((customizer) -> {
customizer.customize(webServerFactory);
});
}
private Collection<WebServerFactoryCustomizer<?>> getCustomizers() {
if (this.customizers == null) {
// 定制Servlet容器,給容器中可以添加一個WebServerFactoryCustomizer類型的組件
this.customizers = new ArrayList(this.getWebServerFactoryCustomizerBeans());
this.customizers.sort(AnnotationAwareOrderComparator.INSTANCE);
this.customizers = Collections.unmodifiableList(this.customizers);
}
return this.customizers;
}
private Collection<WebServerFactoryCustomizer<?>> getWebServerFactoryCustomizerBeans() {
//從容器中獲取所有這個類型的組件:WebServerFactoryCustomizer
return this.beanFactory.getBeansOfType(WebServerFactoryCustomizer.class, false, false).values();
}
}
總結
- SpringBoot根據導入的依賴情況,給容器中添加相應的WebServerFactory【TomcatServletWebServerFactory】。
- 容器中某個組件要創建對象就會使用WebServerFactoryCustomizerBeanPostProcessor后置處理器,只要是嵌入式的Servlet工廠,后置處理器就會進行處理。
- 后置處理器從容器中獲取所有的WebServerFactoryCustomizer,調用定制器的定制方法
6.嵌入式Servlet容器啟動過程
- SpringBoot啟動運行run方法。
- 調用refreshContext(context);刷新IOC容器【創建IOC容器,並初始化容器,創建容器中的每一個組件】;如果是web應用創建AnnotationConfigServletWebServerApplicationContext,如果是reactive應用創建AnnotationConfigReactiveWebServerApplicationContext,否則創建AnnotationConfigApplicationContext。
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch(this.webApplicationType) {
case SERVLET:
contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
break;
case REACTIVE:
contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
break;
default:
contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
}
} catch (ClassNotFoundException var3) {
throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
}
}
return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
}
- 調用refresh(context);刷新上面創建好的IOC容器
public void refresh() throws BeansException, IllegalStateException {
Object var1 = this.startupShutdownMonitor;
synchronized(this.startupShutdownMonitor) {
//准備刷新的context
this.prepareRefresh();
//調用子類去刷新內部的實例工廠
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
//准備在這個context中要使用的實例工廠
this.prepareBeanFactory(beanFactory);
try {
// 允許在上下文子類中對bean工廠進行后置處理。
this.postProcessBeanFactory(beanFactory);
//在context中調用注冊為bean的工廠處理器。
this.invokeBeanFactoryPostProcessors(beanFactory);
//注冊攔截bean創建的bean處理器。
this.registerBeanPostProcessors(beanFactory);
//初始化此context的消息源
this.initMessageSource();
//初始化此上下文的事件多播器。
this.initApplicationEventMulticaster();
//在特定的context子類中初始化其他特殊bean。
this.onRefresh();
// 檢查監聽器bean並注冊它們。
this.registerListeners();
// 實例化所有剩余(非延遲初始化)單例。
this.finishBeanFactoryInitialization(beanFactory);
//最后一步:發布相應的事件。
this.finishRefresh();
} catch (BeansException var9) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
}
//摧毀已經創建的單例以避免占用資源。
this.destroyBeans();
//重置 ‘active’ 標志
this.cancelRefresh(var9);
//Propagate exception to caller.
throw var9;
} finally {
//從我們開始,重置Spring核心中的常見內省緩存
//可能不再需要單例bean的元數據了...
this.resetCommonCaches();
}
}
}
- 調用onRefresh();web的IOC容器重寫了onRefresh方法。
- Web IOC容器創建嵌入式的Servlet容器。
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = this.getServletContext();
if (webServer == null && servletContext == null) {
ServletWebServerFactory factory = this.getWebServerFactory();
this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});
} else if (servletContext != null) {
try {
this.getSelfInitializer().onStartup(servletContext);
} catch (ServletException var4) {
throw new ApplicationContextException("Cannot initialize servlet context", var4);
}
}
this.initPropertySources();
}
-
獲取嵌入式的Servlet容器工廠: ServletWebServerFactory factory = this.getWebServerFactory();從IOC容器中獲取ServletWebServerFactory組件;
-
使用容器工廠獲取嵌入式的Servlet容器:his.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});
public WebServer getWebServer(ServletContextInitializer... initializers) {
Tomcat tomcat = new Tomcat();
File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
tomcat.getService().addConnector(connector);
this.customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
this.configureEngine(tomcat.getEngine());
Iterator var5 = this.additionalTomcatConnectors.iterator();
while(var5.hasNext()) {
Connector additionalConnector = (Connector)var5.next();
tomcat.getService().addConnector(additionalConnector);
}
this.prepareContext(tomcat.getHost(), initializers);
return this.getTomcatWebServer(tomcat);
}
- 嵌入式的Servlet容器創建並啟動Servlet容器。
9.先啟動嵌入式的Servlet容器,再將IOC容器中剩余的沒有創建出來的對象獲取出來、IOC容器啟動就會創建嵌入式的Servlet容器。
7.使用外置的Servlet容器
1. 嵌入式Servlet容器優缺點
優點:簡單、便捷。
缺點:默認不支持JSP,優化定制比較復雜。
2.使用外部Servlet容器步驟
- 必須創建一個war項目。
- 將嵌入式的Tomcat指定為provided。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring‐boot‐starter‐tomcat</artifactId>
<scope>provided</scope>
</dependency>
- 必須編寫一個SpringBootServletInitializer的子類,並調用configure方法。
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
//傳入SpringBoot應用的主程序
return application.sources(SpringBoot04WebJspApplication.class);
}
}
- 啟動服務器就可以了。
3.原理與規則
原理
啟動服務器,服務器啟動SpringBoot應用[SpringBootServletInitializer],啟動IOC容器。
規則
- 服務器啟動會創建當前web應用里面每一個jar包里面的ServletContainerInitializer實例。
- ServletContainerInitializer的實現放在jar包的META-INF/services文件下,有一個名為javax.servlet.ServletContainerInitializer的文件,內容就是ServletContainerInitializer實現類的全類名。
- 還可以使用@HandlerType,在啟動應用時加載指定的類。
4. 啟動流程
- 啟動tomcat。
- 加載spring-web包下META-INF/services下面的javax.servlet.ServletContainerInitializer文件。

- SpringServletContainerInitializer將@HandlerType標注的所有這個類型的類都傳入搭配onStartup方法的Set中,為這些WebApplicationInitializer類型的類創建實例。
@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
public SpringServletContainerInitializer() {
}
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList();
Iterator var4;
if (webAppInitializerClasses != null) {
var4 = webAppInitializerClasses.iterator();
while(var4.hasNext()) {
Class<?> waiClass = (Class)var4.next();
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer)ReflectionUtils.accessibleConstructor(waiClass, new Class[0]).newInstance());
} catch (Throwable var7) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7);
}
}
}
}
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
} else {
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
var4 = initializers.iterator();
while(var4.hasNext()) {
WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next();
initializer.onStartup(servletContext);
}
}
}
}
- 每個WebApplicationInitializer到調用自己的onStartup()方法。

-
相當於SpringBootServletInitializer的類會被創建對象,並執行onStartup()方法。
-
SpringBootServletInitializer實例執行onStartup的時候會crateRootApplicationContext創建容器。
protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
// 1.創建SpringApplicationBuilder
SpringApplicationBuilder builder = this.createSpringApplicationBuilder();
builder.main(this.getClass());
ApplicationContext parent = this.getExistingRootWebApplicationContext(servletContext);
if (parent != null) {
this.logger.info("Root context already created (using as parent).");
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, (Object)null);
builder.initializers(new ApplicationContextInitializer[]{new ParentContextApplicationContextInitializer(parent)});
}
builder.initializers(new ApplicationContextInitializer[]{new ServletContextApplicationContextInitializer(servletContext)});
builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
// 2.調用configure方法,子類重新了這個方法,將SpringBoot的主程序類傳入
builder = this.configure(builder);
builder.listeners(new ApplicationListener[]{new SpringBootServletInitializer.WebEnvironmentPropertySourceInitializer(servletContext)});
// 3.使用builder創建一個Spring應用
SpringApplication application = builder.build();
if (application.getAllSources().isEmpty() && AnnotationUtils.findAnnotation(this.getClass(), Configuration.class) != null) {
application.addPrimarySources(Collections.singleton(this.getClass()));
}
Assert.state(!application.getAllSources().isEmpty(), "No SpringApplication sources have been defined. Either override the configure method or add an @Configuration annotation");
//確保錯誤頁被注冊
if (this.registerErrorPageFilter) {
application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class));
}
// 4. q啟動Spring應用
return this.run(application);
}
-
Spring的應用啟動並且創建IOC容器。
-
先啟動Servlet容器,再啟動SpringBoot應用。
