睁眼写BUG,闭眼改BUG。

Spring Boot_开发

2020.03.02

上一篇 >> Spring Boot_日志

下一篇 >> Spring Boot_docker

fighting!fighting!

Spring Boot 开发

使用Spring Boot:

  1. 创建SpringBoot应用,选中需要的模块
  2. SpringBoot已经默认将这些场景配置好了,只需要在配置文件中指定少量配置就可以运行起来
  3. 自己编写业务代码

1. SpringBoot 对静态资源映射规则

@ConfigurationProperties(
    prefix = "spring.resources",
    ignoreUnknownFields = false
)
public class ResourceProperties implements ResourceLoaderAware, InitializingBean {
    //可以设置和静态资源有关的参数, 缓存时间等
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    if (!this.resourceProperties.isAddMappings()) {
        logger.debug("Default resource handling disabled");
    } else {
        Integer cachePeriod = this.resourceProperties.getCachePeriod();
        if (!registry.hasMappingForPattern("/webjars/**")) {
            this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{"/webjars/**"}).addResourceLocations(new String[]{"classpath:/META-INF/resources/webjars/"}).setCachePeriod(cachePeriod));
        }

        String staticPathPattern = this.mvcProperties.getStaticPathPattern();
        if (!registry.hasMappingForPattern(staticPathPattern)) {
            this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{staticPathPattern}).addResourceLocations(this.resourceProperties.getStaticLocations()).setCachePeriod(cachePeriod));
        }

    }
}

//配置欢迎页映射
@Bean
public WebMvcAutoConfiguration.WelcomePageHandlerMapping welcomePageHandlerMapping(ResourceProperties resourceProperties) {
    return new WebMvcAutoConfiguration.WelcomePageHandlerMapping(resourceProperties.getWelcomePage(), this.mvcProperties.getStaticPathPattern());
}

//配置喜欢的图标
@Configuration
@ConditionalOnProperty(
    value = {"spring.mvc.favicon.enabled"},
    matchIfMissing = true
)
public static class FaviconConfiguration {
    private final ResourceProperties resourceProperties;

    public FaviconConfiguration(ResourceProperties resourceProperties) {
        this.resourceProperties = resourceProperties;
    }

    @Bean
    public SimpleUrlHandlerMapping faviconHandlerMapping() {
        SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
        mapping.setOrder(-2147483647);
 //所有的 "**/favicon.ico"    
        mapping.setUrlMap(Collections.singletonMap("**/favicon.ico", this.faviconRequestHandler()));
        return mapping;
    }

    @Bean
    public ResourceHttpRequestHandler faviconRequestHandler() {
        ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler();
        requestHandler.setLocations(this.resourceProperties.getFaviconLocations());
        return requestHandler;
    }
}
  1. 所有/webjars/**"都去classpath:/METAINF/resources/webjars/找资源;webjars : 以jar包的方式引入静态资源;

    localhost:8080/webjars/jquery/3.3.1/jquery.js

    <!--引入jQuery-webjar-->
    <!--在访问的时候只需要写webjars下面的名称即可-->
    <dependency>
        <groupId>org.webjars</groupId>
        <artifactId>jquery</artifactId>
        <version>3.3.1</version>
    </dependency>
    
  2. "/" 访问当前项目的任何资源,(静态资源的文件夹)**

    "classpath:/META-INF/resources/",
    "classpath:/resources/",
    "classpath:/static/",
    "classpath:/public/"
    "/": 当前项目得到根路径
    

    localhost:8080/abc ==> 去静态资源文件夹下

  3. 欢迎页; 静态文件夹下的所有index.html页面; 被"/"映射;**

    localhost:8080/ 找index页面

  4. 所有的 "/favicon.ico" 都是在静态资源文件下找;

2. 模板引擎

JSP, Velocity, Freemarker, Thymeleaf

SpringBoot推荐的Thymeleaf:

语法简单, 功能更强大;

2.1 引入 thymeleaf

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

切换thymeleaf版本
<thymeleaf.version>3.0.11.RELEASE</thymeleaf.version>
<!--布局功能的支持程序 thymeleaf3主程序 layout2以上版本 3.0.2==2.1.1-->
<!--thymeleaf2 layout1-->
<thymeleaf-layout-dialect.version>2.3.0</thymeleaf-layout-dialect.version>

2.2 Thymeleaf 使用&语法

只要我们把HTML页面放在classpath:/templates/ ,thymeleaf就能自动渲染;

使用:

  1. 导入thymeleaf的名称空间

    <html lang="en" xmlns:th="https://www.thymeleaf.org">
    
  2. 使用thymeleaf语法

    <!DOCTYPE html>
    <html lang="en" xmlns:th="https://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <h1>成功!</h1>
        <!--th:text 将div里面的文本内容设置为-->
        <div th:text="${hello}">
            这是显示欢迎信息
        </div>
    </body>
    </html>
    

2.3 语法规则

  1. th:text :改变当前元素里面的文本内容;

    ​ th:任意html属性 来替换原生属性的值

  1. 表达式?
Simple expressions:(表达式语法)
    Variable Expressions: ${...} # 获取变量值:OGNL
    	1.获取对象的属性,调用方法
    	2.使用内置的基本对象
    		#ctx : the context object.
            #vars: the context variables.
            #locale : the context locale.
            #request : (only in Web Contexts) the HttpServletRequest object.
            #response : (only in Web Contexts) the HttpServletResponse object.
            #session : (only in Web Contexts) the HttpSession object.
            #servletContext : (only in Web Contexts) the ServletContext object.
            ${param.foo}
   		3.内置的工具对象
   			#execInfo : information about the template being processed.
            #messages : methods for obtaining externalized messages inside variables expressions, in the same way as they
            would be obtained using #{…} syntax.
            #uris : methods for escaping parts of URLs/URIs
            Page 20 of 106
            #conversions : methods for executing the configured conversion service (if any).
            #dates : methods for java.util.Date objects: formatting, component extraction, etc.
            #calendars : analogous to #dates , but for java.util.Calendar objects.
            #numbers : methods for formatting numeric objects.
            #strings : methods for String objects: contains, startsWith, prepending/appending, etc.
            #objects : methods for objects in general.
            #bools : methods for boolean evaluation.
            #arrays : methods for arrays.
            #lists : methods for lists.
            #sets : methods for sets.
            #maps : methods for maps.
            #aggregates : methods for creating aggregates on arrays or collections.
            #ids : methods for dealing with id attributes that might be repeated (for example, as a result of an iteration).

    Selection Variable Expressions: *{...}  # 选择表达式和${}功能上是一样的
    	补充:配合 th:object="${session.user}使用 :
    	<div th:object="${session.user}">
            <p>Name: <span th:text="*{firstName}">Sebastian</span>.</p>
            <p>Surname: <span th:text="*{lastName}">Pepper</span>.</p>
            <p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p>
        </div>
        
    Message Expressions: #{...} :获取国际化内容的
    Link URL Expressions: @{...} :定义url链接的
    	@{/order/process(execId=${execId},execType='FAST')}
    
    Fragment Expressions: ~{...} :片段引用表达式
    
# 一下各种奇葩用法
Literals(字面量)
    Text literals: 'one text' , 'Another one!' ,…
    Number literals: 0 , 34 , 3.0 , 12.3 ,…
    Boolean literals: true , false
    Null literal: null
    Literal tokens: one , sometext , main ,…
    
Text operations:(文本操作)
    String concatenation: +
    Literal substitutions: |The name is ${name}|
    
Arithmetic operations:(数学运算)
    Binary operators: + , - , * , / , %
    Minus sign (unary operator): -
    
Boolean operations:(布尔运算)
    Binary operators: and , or
    Boolean negation (unary operator): ! , not

Comparisons and equality:(比较运算)
    Comparators: > , < , >= , <= ( gt , lt , ge , le )
    Equality operators: == , != ( eq , ne )

Conditional operators:(条件运算)
    If-then: (if) ? (then)
    If-then-else: (if) ? (then) : (else) (三元运算)
    Default: (value) ?: (defaultvalue)

Special tokens:(特殊操作)
    No-Operation: _

3. SpringMVC自动配置

官方文档,chrome自带翻译了解一下

Spring Boot provides auto-configuration for Spring MVC that works well with most applications.

The auto-configuration adds the following features on top of Spring’s defaults:

  • Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
  • Support for serving static resources, including support for WebJars (see below).
  • Automatic registration of Converter, GenericConverter, Formatter beans.
  • Support for HttpMessageConverters (see below).
  • Automatic registration of MessageCodesResolver (see below).
  • Static index.html support.
  • Custom Favicon support (see below).
  • Automatic use of a ConfigurableWebBindingInitializer bean (see below).

If you want to keep Spring Boot MVC features, and you just want to add additional MVC configuration (interceptors, formatters, view controllers etc.) you can add your own @Configuration class of type WebMvcConfigurerAdapter, but without @EnableWebMvc. If you wish to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter or ExceptionHandlerExceptionResolver you can declare a WebMvcRegistrationsAdapter instance providing such components.

If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc.

3.1 扩展SpringMVC

    <mvc:view-controller path="/hello" view-name="success"/>
    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/hello"/>
            <bean></bean>
        </mvc:interceptor>
    </mvc:interceptors>

编写一个配置类(@Configuration), 是WebMvcConfigurerAdapter类型;不能标注@EnableWebMvc

既保留了所有的自动配置,也能用我们的扩展配置

//使用WebMvcAutoConfigurationAdapter可以来扩展SpringMVC的功能
@Configuration
public class MyMVCConfig extends WebMvcConfigurationAdapter {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        //super.addViewControllers(registry);
        //浏览器发送 /idler 请求来到 success
        registry.addViewController("/idler").setViewName("success");
    }
}

3.2 全面接管SpringMVC

SpringBoot对SpringMVC的自动配置都不需要了,所有都是我们自己配置;所有的SpringMVC的自动配置都失效了.我们需要在配置类中添加@EnableWebMvc即可

4. 如何修改SpringBoot的默认配置

模式:

  1. SpringBoot在自动配置很多组件的时候, 先看容器中有没有用户自己配置的(@Bean, @Component) 如果有就用用户配置, 如果没有,才自动配置;如果有些组件可以有多个(ViewResolver) 将用户配置的和自己默认的组合起来;
  2. 在SpringBoot中会有非常多的xxConfiguration帮助我们进行扩展配置
  3. 在SpringBoot中会有很多的xxxCustomizer帮助我们进行定制配置

5. 小项目:RestfulCRUD

5.1 默认访问首页

//所有的WebMvcConfigureAdapter组件都会一起起作用
@Bean //将组件注册在容器
public WebMvcConfigurerAdapter webMvcConfigurerAdapter(){
    WebMvcConfigurerAdapter adapter = new WebMvcConfigurerAdapter() {
        @Override
        public void addViewControllers(ViewControllerRegistry registry) {
            //super.addViewControllers(registry);
            registry.addViewController("/").setViewName("login");
            registry.addViewController("/index.html").setViewName("login");
        }
    };
    return adapter;
}

5.2 国际化

  1. 编写国际化配置文件
  2. 使用ResourceBundleMessageSource管理国际化资源文件
  3. 在页面使用fmt:message取出国际化内容

步骤:

1.编写国际化配置文件,抽取页面需要显示的国际化消息

2.SpringBoot自动配置好了管理国际化资源文件的组件

3.去页面获取国际化的值;

<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org">
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
		<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
		<meta name="description" content="">
		<meta name="author" content="">
		<title>Signin Template for Bootstrap</title>
		<!-- Bootstrap core CSS -->
		<link href="asserts/css/bootstrap.min.css" th:href="@{/webjars/bootstrap/4.0.0/css/bootstrap.css}" rel="stylesheet">
		<!-- Custom styles for this template -->
		<link href="asserts/css/signin.css" th:href="@{/asserts/css/signin.css}" rel="stylesheet">
	</head>
	<body class="text-center">
		<form class="form-signin" action="dashboard.html" th:action="@{/user/login}" method="post">
			<img class="mb-4" th:src="@{/asserts/img/bootstrap-solid.svg}" src="asserts/img/bootstrap-solid.svg" alt="" width="72" height="72">
			<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
			<!--判断-->
			<!--<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>-->
			<label class="sr-only" th:text="#{login.username}">Username</label>
			<input type="text"  name="username" class="form-control" placeholder="Username" th:placeholder="#{login.username}" required="" autofocus="">
			<label class="sr-only" th:text="#{login.password}">Password</label>
			<input type="password" name="password" class="form-control" placeholder="Password" th:placeholder="#{login.password}" required="">
			<div class="checkbox mb-3">
				<label>
          			<input type="checkbox" value="remember-me"/> [[#{login.remember}]]
        		</label>
			</div>
			<button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}">Sign in</button>
			<p class="mt-5 mb-3 text-muted">© 2017-2018</p>
			<a class="btn btn-sm" th:href="@{/index.html(l='zh_CN')}">中文</a>
			<a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">English</a>
		</form>
	</body>

</html>

效果 :根据浏览器语言设置的信息切换了国际化

原理:

​ 国际化Locale(区域信息对象); LocaleResplver(获取区域信息对象)

4.点击链接切换国际化

配置新的locale

package com.atguigu.springboot.component;

import org.springframework.util.StringUtils;
import org.springframework.web.servlet.LocaleResolver;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;

/**
 * 可以在连接上携带区域信息
 */
public class MyLocaleResolver implements LocaleResolver {
    
    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        String l = request.getParameter("l");
        Locale locale = Locale.getDefault();
        if(!StringUtils.isEmpty(l)){
            String[] split = l.split("_");
            locale = new Locale(split[0],split[1]);
        }
        return locale;
    }

    @Override
    public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {

    }
}

配置到容器中

@Bean
public LocaleResolver localeResolver(){

    return new MyLocaleResolver();
}

5.3 登陆

开发期间模板引擎页面修改以后, 要实时生效

  1. 禁用缓存 spring.thymeleaf.cache=false
  2. 修改完成后 Ctrl+F9 :重新编译

登陆错误消息的显示

<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>

5.4 拦截器进行登陆检查

package work.idler.springboot.component;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @Auther:https://www.idler.work
 * @Date:2019/2/9
 * @Description:work.idler.springboot.component
 * @version:1.0
 */

/**
 * 登陆检查
 */
public class LoginHandlerinterceptor implements HandlerInterceptor {

    //目标方法执行之前
    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
        Object user = httpServletRequest.getSession().getAttribute("loginUser");
        if(user == null){
            //未登录,返回登陆页面
            httpServletRequest.setAttribute("msg","没有权限请先登陆");
            httpServletRequest.getRequestDispatcher("/index.html").forward(httpServletRequest,httpServletResponse);
            return false;
        }else{
            //已登陆,放行请求
            return true;
        }
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {

    }
}
//注册拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
    //super.addInterceptors(registry);
    //静态资源: css js
    //SpringBoot已经做好了静态资源映射
    registry.addInterceptor(new LoginHandlerinterceptor()).addPathPatterns("/**")
        .excludePathPatterns("/index.html","/","/user/login");
}

5.5 CRUD-员工列表

实验要求:

  1. RestfulCRUD:CRUD满足Rest风格

    URI: /资源名称/资源标识 HTTP请求方式区分对资源CRUD操作

    普通CRUD(uri来区分操作)ResultCRUD
    查阅getEmpemp---GET
    添加addEmp?xxxemp---POST
    修改updateEmp?id=xxx&xxx=xxxemp/---PUT
    删除deleteEmp?id=xxxemp/---DELETE
  2. 实验的请求架构

    请求URI请求方式
    查询所有员工empsGET
    查询某个员工(来到修改页面)emp/1GET
    来到添加页面empGET
    添加员工empPOST
    来到修改页面(查出员工进行信息回显)emp/1GET
    修改员工empPUT
    删除员工emp/1DELETE
  3. 员工列表

    thymeleaf公共页面元素抽取

    1.抽取公共片段
    <div th:fragment="copy">
        &copy;
    </div>
    
    2.引入公共片段
    <div th:insert="~{footer :: copy}"></div>
    ~{templatename::selector} :模板名::选择器
    ~{templatename::fragmentname} :模板名::片段名
    
    3.默认效果
    insert的功能片段在div标签中
    如果使用th:insert等属性进行引入,可以用写~{}
    行内写法可以加上: [[~{}]];[(~{})]
    

    三种引入功能片段的th属性:

    th:insert :将公共片段整个插入到声明引入元素

    th:replace :将声明引入的元素替换为公共片段

    th:include :将被引入的片段的内容包含进这个标签中

    <div th:fragment="copy">
        &copy;
    </div
    
    引入方式
    <div th:insert="footer :: copy"></div>
    <div th:replacce="footer :: copy"></div>
    <div th:include="footer :: copy"></div>
    
    效果
    <div>
        <footer>
            &copy;
        </footer>
    </div>
    
    <footer>
       	&copy;
    </footer>
    
    <div>
        &copy;
    </div>
    

    引入片段的时候传入参数:(实现点谁,谁高亮)

    ...

  4. CRUD-员工添加

    提交的数据格式不对:生日:日期;

    2017-12-12;2017/12/12;2017.12.12

    日期的格式化; SpringMVC将页面提交的值需要转换为指定的类型

    2017-12-12 Date: 类型转换,格式化:

    默认日期是按照/方式,换成-方式:修改配置文件:spring.mvc.date-format=yyyy-MM-d

  5. 员工修改

    <!--发送put请求修改员工数据-->
    <!--
    1、SpringMVC中配置HiddenHttpMethodFilter;(SpringBoot自动配置好的)
    2、页面创建一个post表单
    3、创建一个input项,name="_method";值就是我们指定的请求方式
    -->
    <input type="hidden" name="_method" value="put" th:if="${emp!=null}"/>
    <input type="hidden" name="id" th:if="${emp!=null}" th:value="${emp.id}">
    
  6. 员工删除

    <script>
        $(".deleteBtn").click(function(){
        //删除当前员工的
        $("#deleteEmpForm").attr("action",$(this).attr("del_uri")).submit();
        return false;
    });
    </script>
    
    <button th:attr="del_uri=@{/emp/}+${emp.id}" class="btn btn-sm btn-danger deleteBtn">删除</button>
    
    <form id="deleteEmpForm"  method="post">
    					<input type="hidden" name="_method" value="delete"/>
    				</form>
    

6. 错误处理机制

6.1 SpringBoot默认处理机制

默认效果:

  • 返回一个默认的错误页面
  • 如果是其他客户端,默认响应一个json数据

原理:

  • 参照ErrorMvcAutoConfiguration; 错误处理的自动配置

6.2 如何定制错误响应

如何定制错误的页面

  • 有模板引擎情况下; error/状态码; 将错误页面命名为 错误状态码.html 放在模板引擎文件 error文件夹下,发生此状态码的错误就会来到 对应的页面.

    我们可以使用4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误, 精确优先

    页面能获取的信息:

    • timestamp: 时间戳
    • status: 状态码
    • error: 错误提示
    • exception: 异常对象
    • message: 异常消息
    • errors: JSR303数据校验的错误都在这里
  • **没有模板引擎(模板引擎找不到这个错误页面),**静态资源文件夹下找;

  • 以上都没有,默认来到SpringBoot默认提供的错误页面

如何定制错的的json数据

  • 自定义异常处理&返回定制json数据;

    @ControllerAdvice
    public class MyExceptionHandler {
    
        //1.浏览器客户端返回的都是json
        @ResponseBody
        @ExceptionHandler(UserNotExistException.class)
        public Map<String,Object> handlerException(Exception e){
    
            Map<String,Object> map = new HashMap<>();
            map.put("code","user.notexist");
            map.put("message",e.getMessage());
            return map;
        }
    }
    //没有自适应效果
    
  • 转发到error进行自适应效果处理

    @ExceptionHandler(UserNotExistException.class)
    public String handlerException(Exception e, HttpServletRequest request){
    
        Map<String,Object> map = new HashMap<>();
        //传入我们自己的错误状态码 4xx,5xx,否则不会进入定制错误页面的解析流程
        /**
             * Integer statusCode = (Integer) request
             * getAttribute("javax.servlet.error.status_code")
             */
        request.setAttribute("javax.servlet.error.status_code",500);
        map.put("code","user.notexist");
        map.put("message",e.getMessage());
        //转发到/error
        return "forward:/error";
    }
    
  • 将我们的定制数据携带出去

出现错误以后,会来到/error请求,会被BasicErrorController处理,响应出去可以获取数据是由getErrorAttributes得到的(是 AbstractErrorController(ErrorController)规定的方法);

  • 完全来编写一个ErrorController的实现类[或者是编写AbstractErrorController的子类],放在容器中

  • 也面上能用的数据, 或者是json返回能用的数据都是通过errorAttributes.getErrorAttributes得到;

    容器中DefaultErrorAttributes.getAttributes默认进行数据处理的;

自定义ErrorAttributes

//给容器中加入我们自己定义的ErrorAttributes
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {

    @Override
    public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
        Map<String, Object> map = super.getErrorAttributes(requestAttributes, includeStackTrace);
        map.put("company","idler");
        return map;
    }
}

最终的效果 : 响应的是自适应的,可以通过定制ErrorAttributes改变需要返回的内容.

7. 配置嵌入式Servlet容器

SpringBoot默认使用Tomcat作为嵌入式的Servlet容器

问题?

7.1 如何定制和修改Servlet容器相关配置

1.修改和server有关的配置(Server Properties[也是EmbeddedServletContainerCustomizer]);

server.port=8081
server.context-path=/crud

server.tomcat.uri-encoding=utf-8

#通用的Servlet容器设置
server.xxx
# tomcat的设置
server.tomcat.xxx

2.编写一个EmbeddedServletContainerCustomizer: 嵌入式的Servlet容器的定制器;来修改Servlet容器的配置

@Bean
public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){
    return new EmbeddedServletContainerCustomizer() {

        //定制嵌入式的Servlet容器相关的规则
        @Override
        public void customize(ConfigurableEmbeddedServletContainer configurableEmbeddedServletContainer) {
            configurableEmbeddedServletContainer.setPort(8083);
        }
    };
}

7.2 注册Servlet三大组件[Servlet, Filter, Listener]

由于SpringBoot默认是以jar包的方式启动嵌入式的Servlet容器来启动SpringBoot的web应用,没有web.xml文件.

注册三大组件用以下方式

ServletRegistrationBean

//注册三大组件
@Bean
public ServletRegistrationBean servletRegistrationBean(){
    ServletRegistrationBean registrationBean = new ServletRegistrationBean(new MyServlet(), "/myServlet");
    return registrationBean;
}

FilterRegistrationBean

@Bean
public FilterRegistrationBean filterRegistrationBean(){
    FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
    filterRegistrationBean.setFilter(new MyFilter());
    filterRegistrationBean.setUrlPatterns(Arrays.asList("/hello","/myServlet"));
    return  filterRegistrationBean;
}

ServletListenerRegistrationBean

@Bean
public ServletListenerRegistrationBean servletListenerRegistrationBean(){
    ServletListenerRegistrationBean<MyListener> registrationBean = new ServletListenerRegistrationBean<>(new MyListener());
    return registrationBean;
}

SpringBoot帮我们自动配置SpringMVC的时候, 自动的注册SpringMVC的前端控制器; DispatcherServlet;

7.3 替换为其他嵌入式Servlet容器

默认支持

Tomcat(默认使用)

<!-- 引入web模块 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <!--引入web模块默认启用tomcat-->
</dependency>

Jetty

<!-- 引入web模块 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!--引入其他的Servlet容器-->
<dependency>
    <artifactId>spring-boot-starter-jetty</artifactId>
    <groupId>org.springframework.boot</groupId>
</dependency>

Undertow

<!-- 引入web模块 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!--引入其他的Servlet容器-->
<dependency>
    <artifactId>spring-boot-starter-undertow</artifactId>
    <groupId>org.springframework.boot</groupId>
</dependency>

7.4 嵌入式Servlet容器自动配置原理

步骤

  • SpringBoot根据导入的依据情况,给容器中添加相应的EmbeddedServletContainerFactory[TomcatEmbeddedServletContainerFactory]
  • 容器中某个组件要创建对象就会惊动后置处理器;EmbeddedServletContainerCustomizerBeanPostProcessor;只要是嵌入式的Servlet容器工厂,后置处理器就工作
  • 后置处理,从容器中获取所有的EmbeddedServletContainerCustomizer, 调用定制器的定制方法

7.5 嵌入式Servlet容器启动原理

什么时候创建嵌入式Servlet容器工厂?什么时候获取嵌入式的Servlet容器并启动Tomcat;获取嵌入式的Serlvet容器工厂;

  1. SpringBoot应用启动运行run方法;

  2. refreshContext(context);SpringBoot刷新IOC容器[创建IOC容器对象,并初始化容器,创建容器中的每一个组件];如果是web应用创建AnnotationConfigEmbeddedWebApplicationContext. 否则: AnnotationConfigApplicationContext

  3. refresh(context);刷新刚才创建好的IOC容器.

  4. onRefresh(); web的IOC容器重写了onRefresh方法

  5. webioc容器会创建嵌入式的Servlet容器; createEmbeddedServletContainer();

  6. 获取嵌入式的Servlet容器工厂

  7. 使用容器工厂获取嵌入式的Servlet容器

  8. 嵌入式的Servlet容器创建对象并启动Servlet容器;

    先启动嵌入式的Servlet容器,在将ioc容器中剩下没有创建出的对象获取出来;

    IOC容器启动创建嵌入式的Servlet容器

8. 使用外置的Servlet容器

**嵌入式Servlet容器: **应用可执行的jar

优点: 简单、便携

缺点: 默认不支持jsp,优化定制比较复杂

外置的Servlet容器: 外面安装Tomcat >> 应用war包的方式打包;

8.1 步骤:

  • 必须创建war项目(利用idea创建目录结构)

  • 将嵌入式的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);
       }
    
    }
    
  • 启动服务器

8.2 原理:

jar包:执行Springboot主类得main方法,启动IOC容器,创建嵌入式得Servlet容器;

war包:启动服务器,服务器启动SpringBoot应用,启动IOC容器;

servlet3.0(Spring注解版):

8.2.4 Shared libraries / runtimes pluggability;

规则:

  1. 服务器启动(web应用启动)会创建当前web应用里面每个jar包里面ServletContainerinitializer得实例
  2. ServletContainerInitalizer的实现放在jar包的META-INF/services文件夹,有一个名为 javax.servlet.ServletContainerInitalizer的文件,内容就是ServletContainerinitializer的实现类的全类名
  3. 还可以使用@HandlesTypes; 在应用启动的时候加载我们感兴趣的类;

流程:

  1. 启动Tomcat
  2. spring的web模块有个文件
  3. 这个文件将@HandlesTypes(WebApplicationInitializer.class)标注的所有这个类型的类都传入到onStratup方法的Set<class<?>>;为这些WebApplicationInitializer类型的类创建实例;
  4. 每一个WebApplicationinitializer都调用自己的onStartup;
  5. 相当于我们的SpringBootServletInitializer的类会被创建对象, 并执行onstartup方法
  6. SpringBootServletInitializer示列指定onStartup的时候会createRootApplicationContext; 创建容器
  7. Spring的应用就启动,并且创建IOC容器

先启动Servlet容器, 在启动web应用