關於SpringBoot的自動配置和啓動過程

1、簡介

Spring Boot簡化了Spring應用的開發,採用約定大於配置的思想,去繁從簡,很方便就能構建一個獨立的、產品級別的應用。html

1.傳統J2EE開發的缺點

開發笨重、配置繁多複雜、開發效率低下、部署流程複雜、第三方技術集成難度大。java

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.自動配置

自動配置xxxAutoConfigurationreact

  • SpringBoot中存現大量的這些類,這些類的做用就是幫咱們進行自動裝配
  • 它會將這個場景須要的全部組件都註冊到容器中,並配置好
  • 他們在類路徑下的META-INF/spring.factories文件中
  • spring-boot-autoconfigure.jar中包含了全部場景的字段配置類代碼
  • 這些自動配置類是SpringBoot進行自動裝配的關鍵。

2、SpringBoot配置

1.配置文件

  • SpringBoot使用一個全局的配置文件。配置文件名是固定的。
    -application.properties或者application.ymlweb

  • 配置文件放在src/main/resources目錄或者類路徑/config下。
  • 全局配置文件的做用是對一些默認配置進行修改spring

2.配置文件值注入

  • @Value和@ConfigurationProperties爲屬性注入值進行對比
對比點 @ConfigurationProperties @Value
功能 批量注入配置文件中的屬性 一個個指定
鬆散綁定(鬆散語法) 支持 不支持
SpEL 不支持 支持
JSR303數據校驗 支持 不支持
複雜類型封裝 支持 不支持
  • 屬性名匹配規則
    -person.firstName 使用標準方式
    -person.first-name 使用-
    -person.first_name 使用_
    -PERSON_FIRST_NAME 推薦系統屬性使用這種寫法json

  • @PropertySource
    加載指定的配置文件瀏覽器

  • ConfigurationProperties
    -與@Bean結合爲屬性賦值
    -與@PropertySource(只能用於properties文件)結合讀取指定文件。緩存

  • ConfigurationProperties Validation
    -支持JSR303進行配置文件值校驗。tomcat

@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]}springboot

  • 屬性配置佔用符
    -能夠在配置文件中引用前面配置過的屬性(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.外部配置加載順序

  1. 命令行參數
  2. 來自java:comp/env的JNDI屬性
  3. Java系統屬性(System.getProperties())
  4. 操做系統環境變量
  5. RandomValuePropertySource配置的random.*屬性值
  6. jar包外部的application-{profile}.properties或application.yml(帶spring.profile)配置文件
  7. jar包內部的application-{profile}.properties或application.yml(帶spring.profile)配置文件
  8. jar包外部的application.properties或application.yml(不帶spring.profile)配置文件
  9. jar包內部的application.properties或application.yml(不帶spring.profile)配置文件
  10. @Configuration註解類上的@PropertySource。
  11. 經過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指定的條件成立,纔給容器中添加組件,配置配裏面的全部內容才生效。

3、SpringBoot與日誌

1.日誌框架

市場上存在很是多的日誌框架,JUL(java.util.logging)、JCL(Apache Commons Logging)、Log4J、Log4J二、Logback、SLF4j、jboss-logging等。

  • SpringBoot早框架內部使用JCL。spring-boot-starter-logging採用了slf4j+logback的形式,SpringBoot也能自動配置(jul、log4j二、logback)並簡化配置。
日誌門面 日誌實現
JCL、SLF4J、jboss-logging log4j、JUL、Log4j二、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依賴。

4、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");
    }
}

原理

  1. WebMvcAutoConfiguration是SpringMVC的自動配置類。
  2. 在作其餘自動配置時會導入。
  3. 容器中全部的WebMvcConfigurer都會一塊兒被註冊。
  4. 咱們自定義的配置類也會被調用。

3.全面接管SpringMVC

若是想要使SpringMVC的自動配置失效,只須要在咱們自定義的配置類中添加@EnableWebMvc註解便可。

@EnableWebMvc
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/desperado").setViewName("success");
    }
}

原理

  1. @EnableWebMvc的註解
@Import(DelegatingWebMvcConfiguation.class)
public @interface EnableWebMvc{}
  1. DelegatingWebMvcConfiguation
@Configuration
public class DelegatingWebMvcConfiguation extend WebMvcConfigurationSupport{}
  1. 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{}
  1. @EnableWebMvc會將WebMvcConfigurationSupport組件導入進來。
  2. 導入的WebMvcConfigurationSupport只是SpringMVC最基本的功能。

4.修改默認配置

  1. SpringBoot在自動配置不少組件的時候,顯卡容器中有沒有用戶本身配置的(@Bean 、@Component),若是有就用用戶配置的,若是沒有,纔會進行自動配置;若是某些組件能夠有多個,將用戶配置的和本身默認的組合起來。
  2. 在SpringBoot中有許多的xxxConfigurer幫助咱們進行擴展配置
  3. 在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等。

  1. 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進行國際化。

5、錯誤處理機制

1.默認的錯誤處理機制

  1. 瀏覽器,默認返回一個默認的錯誤頁面。
  2. 其餘客戶端,默認響應一個json數據。

原理

  1. 在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;
    }
}
  1. 在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);
    }
}
  1. ErrorPageCustomizer進行錯誤配置
public class ErrorProperties {
    @Value("${error.path:/error}")
    private String path = "/error";
    private boolean includeException;
    private ErrorProperties.IncludeStacktrace includeStacktrace;
    private final ErrorProperties.Whitelabel whitelabel;
}
  1. 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.錯誤頁面的優先級(自定義錯誤頁面)

  1. 在模板引擎的狀況下,error/狀態碼(將錯誤頁面命名爲 錯誤狀態碼.html 放在模板引擎文件夾裏面的error文件夾下),發生此狀態碼的錯誤就會來到對應的頁面;
  2. 沒有模板引擎(模板引擎找不到錯誤頁面),就在靜態資源文件夾下找。
  3. 以上都沒有錯誤頁面,就是默認來到SpringBoot默認的錯誤提示頁面。

3.如何定製錯誤的json數據

  1. 自定義異常處理&返回定製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獲得的。

  1. 編寫一個ErrorController的實現類(或者是編寫AbstractErrorController的子類),放到容器中。
  2. 頁面上能用的數據,或者是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;
    }
}

6、配置嵌入式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容器自動配置原理

  1. 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();
        }
    }
}
  1. 嵌入式Servlet容器工廠

  1. 嵌入式的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);
    }
  1. 容器中導入 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();
    }
}

總結

  1. SpringBoot根據導入的依賴狀況,給容器中添加相應的WebServerFactory【TomcatServletWebServerFactory】。
  2. 容器中某個組件要建立對象就會使用WebServerFactoryCustomizerBeanPostProcessor後置處理器,只要是嵌入式的Servlet工廠,後置處理器就會進行處理。
  3. 後置處理器從容器中獲取全部的WebServerFactoryCustomizer,調用定製器的定製方法

6.嵌入式Servlet容器啓動過程

  1. SpringBoot啓動運行run方法。
  2. 調用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);
    }
  1. 調用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();
            }

        }
    }
  1. 調用onRefresh();web的IOC容器重寫了onRefresh方法。
  2. 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();
    }
  1. 獲取嵌入式的Servlet容器工廠: ServletWebServerFactory factory = this.getWebServerFactory();從IOC容器中獲取ServletWebServerFactory組件;

  2. 使用容器工廠獲取嵌入式的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);
    }
  1. 嵌入式的Servlet容器建立並啓動Servlet容器。

9.先啓動嵌入式的Servlet容器,再將IOC容器中剩餘的沒有建立出來的對象獲取出來、IOC容器啓動就會建立嵌入式的Servlet容器。

7.使用外置的Servlet容器

1. 嵌入式Servlet容器優缺點

優勢:簡單、便捷。
缺點:默認不支持JSP,優化定製比較複雜。

2.使用外部Servlet容器步驟

  1. 必須建立一個war項目。
  2. 將嵌入式的Tomcat指定爲provided。
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring‐boot‐starter‐tomcat</artifactId>
    <scope>provided</scope>
</dependency>
  1. 必須編寫一個SpringBootServletInitializer的子類,並調用configure方法。
public class ServletInitializer extends SpringBootServletInitializer {
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        //傳入SpringBoot應用的主程序
        return application.sources(SpringBoot04WebJspApplication.class);
  }
}
  1. 啓動服務器就能夠了。

3.原理與規則

原理
啓動服務器,服務器啓動SpringBoot應用[SpringBootServletInitializer],啓動IOC容器。

規則

  1. 服務器啓動會建立當前web應用裏面每個jar包裏面的ServletContainerInitializer實例。
  2. ServletContainerInitializer的實現放在jar包的META-INF/services文件下,有一個名爲javax.servlet.ServletContainerInitializer的文件,內容就是ServletContainerInitializer實現類的全類名。
  3. 還可使用@HandlerType,在啓動應用時加載指定的類。

4. 啓動流程

  1. 啓動tomcat。
  2. 加載spring-web包下META-INF/services下面的javax.servlet.ServletContainerInitializer文件。

  1. 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);
            }

        }
    }
}
  1. 每一個WebApplicationInitializer到調用本身的onStartup()方法。

image.png

  1. 至關於SpringBootServletInitializer的類會被建立對象,並執行onStartup()方法。

  2. 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);
    }
  1. Spring的應用啓動而且建立IOC容器。

  2. 先啓動Servlet容器,再啓動SpringBoot應用。

相關文章
相關標籤/搜索