SpringBoot2核心技术-核心功能【第一篇】

该文档是:SpringBoot教程(文章部分内容转自尚硅谷笔记)

博客连接:https://www.loveuluo.cn

日期:2021-01-15

1️⃣配置文件

1、文件类型

1.1、properties

同以前的properties用法。

1.2、yaml

image-20210114214156522

如果properties中配置了同样的,优先使用properties的。

1.2.1、简介

YAML 是 "YAML Ain't Markup Language"(YAML 不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:"Yet Another Markup Language"(仍是一种标记语言)。

非常适合用来做以数据为中心的配置文件

1.2.2、基本语法

  • key: value;kv之间有空格
  • 大小写敏感
  • 使用缩进表示层级关系
  • 缩进不允许使用tab,只允许空格
  • 缩进的空格数不重要,只要相同层级的元素左对齐即可
  • '#'表示注释
  • 字符串无需加引号,如果要加,''与""表示字符串内容 会被 转义/不转义

1.2.3、数据

  • 字面量:单个的、不可再分的值。date、boolean、string、number、null
k: v
  • 对象:键值对的集合。map、hash、set、object
行内写法:  k: {k1:v1,k2:v2,k3:v3}
#或
k: 
  k1: v1
  k2: v2
  k3: v3
  • 数组:一组按次序排列的值。array、list、queue
行内写法:  k: [v1,v2,v3]
#或者
k:
 - v1
 - v2
 - v3

1.2.4、示例

@ConfigurationProperties(prefix = "person")
@Component
@ToString
@Data
public class Person {
    private String userName;
    private Boolean boss;
    private Date birth;
    private Integer age;
    private Pet pet;
    private String[] interests;
    private List<String> animal;
    private Map<String, Object> score;
    private Set<Double> salarys;
    private Map<String, List<Pet>> allPets;
}


@Data
public class Pet {
    private String name;
    private Double weight;
}

用yaml表示以上对象:

person:
#  单引号会将 \n作为字符串输出   双引号会将\n 作为换行输出
#  双引号不会转义,单引号会转义
  user-name: zhangsan #-n代表n是大写 userName
  boss: true
  birth: 2019/12/9
  age: 18
#  interests: [篮球,足球]
  interests:
    - 篮球
    - 足球
    - 18
  animal: [阿猫,阿狗]
#  score:
#    english: 80
#    math: 90
  score: {english:80,math:90}
  salarys:
    - 9999.98
    - 9999.99
  pet:
    name: 阿狗
    weight: 99.99
  allPets:
    sick:
      - {name: 阿狗,weight: 99.99}
      - name: 阿猫
        weight: 88.88
      - name: 阿虫
        weight: 77.77
    health:
      - {name: 阿花,weight: 199.99}
      - {name: 阿明,weight: 199.99}

2、ymal配置提示

自定义的类和配置文件绑定一般没有提示。

加上依赖后会有提示。

image-20210114215653662

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>


 <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.springframework.boot</groupId>
                            <artifactId>spring-boot-configuration-processor</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

2️⃣Web开发

1、SpringMVC自动配置概览

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.

    • 内容协商视图解析器和BeanName视图解析器
  • Support for serving static resources, including support for WebJars (covered later in this document)).

    • 静态资源(包括webjars)
  • Automatic registration of Converter, GenericConverter, and Formatter beans.

    • 自动注册 Converter,GenericConverter,Formatter
  • Support for HttpMessageConverters (covered later in this document).

    • 支持 HttpMessageConverters (后来我们配合内容协商理解原理)
  • Automatic registration of MessageCodesResolver (covered later in this document).

    • 自动注册 MessageCodesResolver (国际化用)
  • Static index.html support.

    • 静态index.html 页支持
  • Custom Favicon support (covered later in this document).

    • 自定义 Favicon
  • Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).

    • 自动使用 ConfigurableWebBindingInitializer ,(DataBinder负责将请求数据绑定到JavaBean上)

If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc.

不用@EnableWebMvc注解。使用 @Configuration + WebMvcConfigurer 自定义规则

If you want to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, and still keep the Spring Boot MVC customizations, you can declare a bean of type WebMvcRegistrations and use it to provide custom instances of those components.

声明 WebMvcRegistrations 改变默认底层组件

If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc, or alternatively add your own @Configuration-annotated DelegatingWebMvcConfiguration as described in the Javadoc of @EnableWebMvc.

使用 @EnableWebMvc+@Configuration+DelegatingWebMvcConfiguration 全面接管SpringMVC

2、简单功能分析

2.1、静态资源访问

(1)静态资源目录

只要静态资源放在类路径下: 只要文件名叫做/static (or /public or /resources or /META-INF/resources

image-20210115140425431

访问 : 当前项目根路径/ + 静态资源名 (例如在static包下有个111.jpg,直接localhost:8080/111.jgp,就能访问到)

原理: 静态映射 /** , 代表处理所有请求。

请求进来,先去找Controller看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面

改变默认的静态资源路径:

这样改了之后,只有放在haha文件名下的静态资源才会被访问到。

image-20210115141554301

resources:
  static-locations: [classpath:/haha/]

(2)静态资源访问前缀(常用)

默认无前缀,这样改了之后例如在static包下有个111.jpg,localhost:8080/res/111.jgp进行访问

spring:
  mvc:
    static-path-pattern: /res/**

(3)webjar

把我们常见的BootStrap、jQuery之类的弄成了jar包,只需要引入jar包就能使用。

自动映射 /webjars/**

https://www.webjars.org/

<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>jquery</artifactId>
    <version>3.5.1</version>
</dependency>

访问地址:http://localhost:8080/webjars/jquery/3.5.1/jquery.js 后面地址要按照依赖里面的包路径

2.2、欢迎页支持

  • 静态资源路径下 index.html
    • 可以配置静态资源路径
    • 但是不可以配置静态资源的访问前缀。否则导致 index.html不能被默认访问
spring:
#  mvc:
#    static-path-pattern: /res/**   这个会导致 欢迎页 功能失效
  • controller能处理/index

2.3、自定义 Favicon

favicon.ico 放在静态资源目录下即可,不可以配置静态资源的访问前缀,否则会导致失效。

spring:
#  mvc:
#    static-path-pattern: /res/**   这个会导致 欢迎页 功能失效

2.4、静态资源配置原理

  • SpringBoot启动默认加载 xxxAutoConfiguration 类(自动配置类)
  • SpringMVC功能的自动配置类 WebMvcAutoConfiguration,生效
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
        ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {}
  • 给容器中配了什么。
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {}
  • 配置文件的相关属性和xxx进行了绑定。WebMvcProperties==spring.mvc、ResourceProperties==spring.resources

(1)当配置类只有一个有参构造器:

    //有参构造器所有参数的值都会从容器中确定
//ResourceProperties resourceProperties;获取和spring.resources绑定的所有的值的对象
//WebMvcProperties mvcProperties 获取和spring.mvc绑定的所有的值的对象
//ListableBeanFactory beanFactory Spring的beanFactory
//HttpMessageConverters 找到所有的HttpMessageConverters
//ResourceHandlerRegistrationCustomizer 找到 资源处理器的自定义器。=========
//DispatcherServletPath  
//ServletRegistrationBean   给应用注册Servlet、Filter....
    public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties,
                ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
                ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
                ObjectProvider<DispatcherServletPath> dispatcherServletPath,
                ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
            this.resourceProperties = resourceProperties;
            this.mvcProperties = mvcProperties;
            this.beanFactory = beanFactory;
            this.messageConvertersProvider = messageConvertersProvider;
            this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
            this.dispatcherServletPath = dispatcherServletPath;
            this.servletRegistrations = servletRegistrations;
        }

(2)资源处理的默认规则:

@Override
        public void addResourceHandlers(ResourceHandlerRegistry registry) {
            //如果 add-mappings 值是 false,静态资源的规则不再执行,直接return
            if (!this.resourceProperties.isAddMappings()) {
                logger.debug("Default resource handling disabled");
                return;
            }
            Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
            CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
            //webjars的规则
            if (!registry.hasMappingForPattern("/webjars/**")) {
                customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
                        .addResourceLocations("classpath:/META-INF/resources/webjars/")
                        .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
            }
            
            //
            String staticPathPattern = this.mvcProperties.getStaticPathPattern();
            if (!registry.hasMappingForPattern(staticPathPattern)) {
                customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
                        .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
                        .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
            }
        }
spring:
#  mvc:
#    static-path-pattern: /res/**

  resources:
    add-mappings: false # 代表禁用所有静态资源规则
//解释了为什么这些静态资源放在这些路径下可以直接访问
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties {

    private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
            "classpath:/resources/", "classpath:/static/", "classpath:/public/" };

    /**
     * Locations of static resources. Defaults to classpath:[/META-INF/resources/,
     * /resources/, /static/, /public/].
     */
    private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;

(3)欢迎页的处理规则:

    HandlerMapping:处理器映射。保存了每一个Handler能处理哪些请求。    

    @Bean
        public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
                FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
            WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
                    new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
                    this.mvcProperties.getStaticPathPattern());
            welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
            welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
            return welcomePageHandlerMapping;
        }

    WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,
            ApplicationContext applicationContext, Optional<Resource> welcomePage, String staticPathPattern) {
        if (welcomePage.isPresent() && "/**".equals(staticPathPattern)) {
            //要用欢迎页功能,必须是/**
            logger.info("Adding welcome page: " + welcomePage.get());
            setRootViewName("forward:index.html");
        }
        else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
            // 调用Controller  /index
            logger.info("Adding welcome page template: index");
            setRootViewName("index");
        }
    }

3、请求参数处理

3.1、请求映射

(1)restful使用与原理

Restful风格支持(使用HTTP请求方式动词来表示对资源的操作

以前:/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 保存用户

现在: /user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户
核心FilterHiddenHttpMethodFilter

用法: 表单method=post,隐藏域 _method=put,rest风格需要在SpringBoot配置文件中手动开启

扩展:如何把_method 这个名字换成我们自己喜欢的

html页面:

测试REST风格;
<form action="/user" method="get">
    <input value="REST-GET 提交" type="submit"/>
</form>
<form action="/user" method="post">
    <input value="REST-POST 提交" type="submit"/>
</form>
<form action="/user" method="post">
    <input name="_method" type="hidden" value="delete"/>
    <input name="_m" type="hidden" value="delete"/>
    <input value="REST-DELETE 提交" type="submit"/>
</form>
<form action="/user" method="post">
    <input name="_method" type="hidden" value="PUT"/>
    <input value="REST-PUT 提交" type="submit"/>
</form>

Controller:

@RequestMapping(value = "/user",method = RequestMethod.GET)
    public String getUser(){
        return "GET-张三";
    }

    @RequestMapping(value = "/user",method = RequestMethod.POST)
    public String saveUser(){
        return "POST-张三";
    }


    @RequestMapping(value = "/user",method = RequestMethod.PUT)
    public String putUser(){
        return "PUT-张三";
    }

    @RequestMapping(value = "/user",method = RequestMethod.DELETE)
    public String deleteUser(){
        return "DELETE-张三";
    }

如何开启Rest风格:

spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true   #开启页面表单的Rest功能

原因是:

@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
//matchIfMissing = false 如果没有填写默认是false
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
    return new OrderedHiddenHttpMethodFilter();
}

Rest原理(表单提交要使用REST的时候,表单只能写POST和GET):

表单提交会带上_method=PUT

请求过来被HiddenHttpMethodFilter拦截。

请求是否正常,并且是POST。

获取到_method的值。兼容以下请求:

PUT.DELETE.PATCH

原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。

过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。

image-20210115160237930

Rest使用客户端工具(和Filter无关)

如PostMan直接发送Put、delete等方式请求,无需Filter。

如何把_method 这个名字换成我们自己喜欢的:

//自定义filter
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
    HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
    methodFilter.setMethodParam("_m");
    return methodFilter;
}

注解小技巧:

image-20210115161139525

(2)请求映射原理

image.png

SpringMVC功能分析都从 org.springframework.web.servlet.DispatcherServlet-》doDispatch()

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            ModelAndView mv = null;
            Exception dispatchException = null;

            try {
                processedRequest = checkMultipart(request);
                multipartRequestParsed = (processedRequest != request);

                // 找到当前请求使用哪个Handler(Controller的方法)处理
                mappedHandler = getHandler(processedRequest);
                
                //HandlerMapping:处理器映射。/xxx->>xxxx

image.png

RequestMappingHandlerMapping:保存了所有@RequestMapping 和handler的映射规则。

image.png

所有的请求映射都在HandlerMapping中。

  • SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html;
  • SpringBoot自动配置了默认 的 RequestMappingHandlerMapping
  • 请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。

    • 如果有就找到这个请求对应的handler
    • 如果没有就是下一个 HandlerMapping
  • 我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    if (this.handlerMappings != null) {
        for (HandlerMapping mapping : this.handlerMappings) {
            HandlerExecutionChain handler = mapping.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
    }
    return null;
}

3.2、普通参数与基本注解

(1)注解

测试基本注解:
@PathVariable(路径变量)
@RequestHeader(获取请求头)
@RequestParam(获取请求参数)
@CookieValue(获取cookie值)
@RequestBody(获取请求体[POST])
@RequestAttribute(获取request域属性)
@MatrixVariable(矩阵变量)

@PathVariable,@RequestHeader,@RequestParam测试:

请求地址:

 <a href="car/3/owner/lisi?age=18&inters=basketball&inters=game">car/{id}/owner/{username}</a>

Controller:

    //<a href="car/3/owner/lisi?age=18&inters=basketball&inters=game">car/{id}/owner/{username}</a>
    @GetMapping("/car/{id}/owner/{username}")
    public Map<String,Object> getCar(@PathVariable("id") Integer id,
                                     @PathVariable("username") String name,
                                     @PathVariable Map<String,String> pv,
                                     @RequestHeader("User-Agent") String userAgent,
//                                     @RequestHeader Map<String,String> header,获得请求头里边不写参数,默认获得所有请求头
                                     @RequestParam("age") Integer age,
                                     @RequestParam("inters") List<String> inters,
                                     @RequestParam Map<String,String> params){

        Map<String,Object> map = new HashMap<>();
        map.put("id",id);
        map.put("name",name);
        map.put("pv",pv);
        map.put("userAgent",userAgent);
//        map.put("headers",header);
        map.put("age",age);
        map.put("inters",inters);
        map.put("params",params);
        return map;
    }

页面结果:

image-20210115191428389

@CookieValue测试:

@CookieValue("_ga") String _ga, //获取单独某一个Cookie的值
@CookieValue("_ga") Cookie cookie //把_ga的整个Cookie信息全部拿过来

@RequestBody测试:

表单:

<form action="/save" method="post">
    测试@RequestBody获取数据 <br/>
    用户名:<input name="userName"/> <br>
    邮箱:<input name="email"/>
    <input type="submit" value="提交"/>
</form>

Controller:

@PostMapping("/save")
public Map postMethod(@RequestBody String content){
    Map<String,Object> map = new HashMap<>();
    map.put("content",content);
    return map;
}

页面结果:

image-20210115192639994

image-20210115192616631

@RequestAttribute测试:

image-20210115193333810

@MatrixVariable测试:

什么是矩阵变量:

  • 注意一定就是,矩阵变量可以出现在任何路径片段中,每一个矩阵变量都用分号;隔开。比如 “/color=red;year=2012”。多个值可以用逗号隔开,比如 “color=red,green,blue”,或者分开写 “color=red;color=green;color=blue”
<a href="/cars/sell;low=34;brand=byd,audi,yd">@MatrixVariable(矩阵变量)</a>
<a href="/cars/sell;low=34;brand=byd;brand=audi;brand=yd">@MatrixVariable(矩阵变量)</a>

后端接收前端的请求,并解析矩阵变量:

  • 方法参数增加@MatrixVariable注解,parhVar的名称与占位符的名称保持一致,value的名称与前端的Key值名称保持一致.由于矩阵变量中含有多个数据,用List<String>的变量,对其进行接收
//1、语法: 请求路径:/cars/sell;low=34;brand=byd,audi,yd
//2、SpringBoot默认是禁用了矩阵变量的功能
//      手动开启:原理。对于路径的处理。UrlPathHelper进行解析。
//              removeSemicolonContent(移除分号内容)支持矩阵变量的
//3、矩阵变量必须有url路径变量才能被解析
@GetMapping("/cars/{path}")
public Map carsSell(@MatrixVariable("low") Integer low,
                    @MatrixVariable("brand") List<String> brand,
                    @PathVariable("path") String path){ //也可以得到路劲变量, 值:sell,不会带上矩阵变量
    Map<String,Object> map = new HashMap<>();

    map.put("low",low);
    map.put("brand",brand);
    map.put("path",path);
    return map;
}

面试题:

  • 页面开发,cookie禁用了,session里面的内容怎么使用;
    session.set(a,b)---> jsessionid ---> cookie ----> 每次发请求携带。

url重写:/abc;jsesssionid=xxxx 把cookie的值使用矩阵变量的方式进行传递.

(2)Servlet API

WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId

ServletRequestMethodArgumentResolver 以上的部分参数

@Override
    public boolean supportsParameter(MethodParameter parameter) {
        Class<?> paramType = parameter.getParameterType();
        return (WebRequest.class.isAssignableFrom(paramType) ||
                ServletRequest.class.isAssignableFrom(paramType) ||
                MultipartRequest.class.isAssignableFrom(paramType) ||
                HttpSession.class.isAssignableFrom(paramType) ||
                (pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
                Principal.class.isAssignableFrom(paramType) ||
                InputStream.class.isAssignableFrom(paramType) ||
                Reader.class.isAssignableFrom(paramType) ||
                HttpMethod.class == paramType ||
                Locale.class == paramType ||
                TimeZone.class == paramType ||
                ZoneId.class == paramType);
    }

(3)复杂参数

Model model, HttpServletRequest request 都是可以给request域中放数据

MapModel(map、model里面的数据会被放在request的请求域 request.setAttribute)、Errors/BindingResult、RedirectAttributes( 重定向携带数据)ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder

image-20210115204031272

(4)自定义对象参数

表单:

姓名: <input name="userName"/> <br/>
年龄: <input name="age"/> <br/>
生日: <input name="birth"/> <br/>
宠物姓名:<input name="pet.name"/><br/>
宠物年龄:<input name="pet.age"/>

可以直接绑定bean:

@Data
public class Person {
    
    private String userName;
    private Integer age;
    private Date birth;
    private Pet pet;
    
}

@Data
public class Pet {

    private String name;
    private String age;

}

4、数据响应与内容协商

4.1、响应JSON

(1)Jackson.jar+@ResponseBody

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
web场景自动引入了json场景
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-json</artifactId>
      <version>2.3.4.RELEASE</version>
      <scope>compile</scope>
    </dependency>

image.png

能给前端自动返回json数据:

image-20210116094740689

image-20210116094800235

(2)SpringMVC到底支持哪些返回值

ModelAndView
Model
View
ResponseEntity 
ResponseBodyEmitter
StreamingResponseBody
HttpEntity
HttpHeaders
Callable
DeferredResult
ListenableFuture
CompletionStage
WebAsyncTask
有 @ModelAttribute 且为对象类型的
@ResponseBody 注解 ---> RequestResponseBodyMethodProcessor;

4.2、内容协商

根据客户端接收能力不同,返回不同媒体类型的数据。

引入xml依赖:

 <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
</dependency>

postman分别测试返回json和xml:

只需要改变请求头中Accept字段。Http协议中规定的,告诉服务器本客户端可以接收的数据类型。

image-20210116104036455

开启浏览器参数方式内容协商功能:

为了方便内容协商,开启基于请求参数的内容协商功能。

spring:
    contentnegotiation:
      favor-parameter: true  #开启请求参数内容协商模式

发请求:

  • http://localhost:8080/test/person?format=json
  • http://localhost:8080/test/person?format=xml

image.png

确定客户端接收什么样的内容类型:

1、Parameter策略优先确定是要返回json数据(获取请求头中的format的值)

image.png

2、最终进行内容协商返回给客户端json即可。

5、视图解析与模板引擎

5.1、视图解析

image-20210116132157853

5.2、模板引擎-Thymeleaf

(1)thymeleaf简介

Thymeleaf is a modern server-side Java template engine for both web and standalone environments, capable of processing HTML, XML, JavaScript, CSS and even plain text.

现代化、服务端Java模板引擎

(2)基本语法

1、表达式

表达式名字语法用途
变量取值${...}获取请求域、session域、对象等值
选择变量*{...}获取上下文对象值
消息#{...}获取国际化等值
链接@{...}生成链接,@{/} 可以动态获取项目名
片段表达式~{...}jsp:include 作用,引入公共页面片段

2、字面量

文本值: 'one text' , 'Another one!' ,…数字: 0 , 34 , 3.0 , 12.3 ,…布尔值: true , false

空值: null

变量: one,two,.... 变量不能有空格

3、文本操作

字符串拼接: +

变量替换: |The name is ${name}|

4、数学运算

运算符: + , - , * , / , %

5、布尔运算

运算符: and , or

一元运算: ! , not

6、比较运算

比较: > , < , >= , <= ( gt , lt , ge , le )等式: == , != ( eq , ne )

7、条件运算

If-then: (if) ? (then)

If-then-else: (if) ? (then) : (else)

Default: (value) ?: (defaultvalue)

8、特殊操作

无操作: _

(3)设置属性值-th:attr

设置单个值

<form action="subscribe.html" th:attr="action=@{/subscribe}">
  <fieldset>
    <input type="text" name="email" />
    <input type="submit" value="Subscribe!" th:attr="value=#{subscribe.submit}"/>
  </fieldset>
</form>

设置多个值

<img src="../../images/gtvglogo.png"  th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />

以上两个的代替写法 th:xxxx

<input type="submit" value="Subscribe!" th:value="#{subscribe.submit}"/>
<form action="subscribe.html" th:action="@{/subscribe}">

所有h5兼容的标签写法

https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#setting-value-to-specific-attributes

(4)遍历

<tr th:each="prod : ${prods}">
        <td th:text="${prod.name}">Onions</td>
        <td th:text="${prod.price}">2.41</td>
        <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>
<!-- iterStat是遍历状态,里边有索引计数等等 -->
<tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'">
  <!-- iterStat.count 可以拿到计数也就是1,2,3.... 索引则是0开始 -->
  <td th:text="${iterStat.count}">Onions</td>
  <td th:text="${prod.name}">Onions</td>
  <td th:text="${prod.price}">2.41</td>
  <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>

(5)条件运算

<a href="comments.html"
th:href="@{/product/comments(prodId=${prod.id})}"
th:if="${not #lists.isEmpty(prod.comments)}">view</a>
<div th:switch="${user.role}">
  <p th:case="'admin'">User is an administrator</p>
  <p th:case="#{roles.manager}">User is a manager</p>
  <p th:case="*">User is some other thing</p>
</div>

(6)属性优先级

image.png

(7)声明片段

用于抽取公共部分。

th:fragment声明成为一个片段可以引用:

image-20210116183743197

如何引用,可以使用th:insert(footer代表的是在template文件夹下的footer.html页面):

image-20210116183958708

也支持使用选择:

image-20210116184039414

th:insert,th:replace,th:include的区别,th:include已不推荐使用:

公共部分:

image-20210116184452344

三种引入:

image-20210116184530597

三种引入的效果:

image-20210116184611969

结论:

th:insert:是插入到标签中

th:replace:是整个带标签都替换掉

th:include:是更换标签中的文本内容

5.3、thymeleaf使用

(1)引入Starter:

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

(2)自动配置好了thymeleaf:

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ThymeleafProperties.class)
@ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class })
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })
public class ThymeleafAutoConfiguration { }

自动配好的策略

  • 1、所有thymeleaf的配置值都在 ThymeleafProperties
  • 2、配置好了 SpringTemplateEngine
  • 3、配好了 ThymeleafViewResolver
  • 4、我们只需要直接开发页面
    public static final String DEFAULT_PREFIX = "classpath:/templates/"; //放到templates文件夹里

    public static final String DEFAULT_SUFFIX = ".html";  //xxx.html 后缀为.html

(3)页面开发:

一定记得要引入thymeleaf名称空间,就算只是跳转或者重定向过去也要加上。

只需要文件在templates下有,且后缀名是.html,例如success.html,那么可以直接通过return "success";进行访问。

html页面:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"> <!-- 必须引入名称空间 -->
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
   <h1 th:text="${msg}">哈哈</h1>  <!--text代表要改变文本,${}是变量取值-->
    <a href="www.123456.com" th:href="${link}">去百度</a>  <!--href代表要改变链接,${}是变量取值,结果为:www.baidu.com-->
    <a href="www.123456.com" th:href="@{link}">去百度2</a>  <!--href代表要改变链接,@{}是认为要去的地址,结果为:http://localhost:8080/link  @{/} 可以动态获取项目名--> 
</body>
</html>

Controller:

@Controller
public class ViewTestController {

    @GetMapping("/luo")
    public String luo(Model model){

        //model中的数据会被放在请求域中 request.setAttribute("a",aa)
        model.addAttribute("msg","你好 Luo");
        model.addAttribute("link","http://www.baidu.com");
        return "success";
    }
}

如果没有经过模板引擎直接访问,就是使用的默认值:

image-20210116154346570

经过模板引擎,就会动态取值:

image-20210116154236045

5.4、thymeleaf使用-构建后台管理系统

(1)项目创建:

选择Spring Initializr构建项目,勾选:thymeleaf、web-starter、devtools、lombok。拷入需要用的静态资源放在static文件中,页面放在templates文件中。

image-20210116165554387

(2)点击登录会进入后台管理页面,同时解决表单重复提交:

login.html:

image-20210116165413674

Controller:

@Controller
public class IndexController {

    /**
     * 来登录页
     * @return
     */
    @GetMapping(value = {"/","/login"}) //必须是GET方式
    public String loginPage(){
        return "login";
    }

    @PostMapping("/login") //必须是POST方式
    public String main(String username,String password){

        //登陆成功,重定向到main.html,解决表单提交
        return "redirect:/main.html";
    }

    /**
     * 去main页面
     * @return
     */
    //temlpates下的所有页面的解析一定都是通过请求处理,最终由模板引擎进行解析的,直接访问是访问不到的。
    @GetMapping("/main.html") //处理了一个请求,请求名字是main.html
    public String mainPage(){

        return "main";
    }
}

(3)模拟登录,未登录不能进入后台管理页面,登录后后台页面显示用户名:

User实体类:

@Data
public class User {
    private String userName;
    private String password;
}

login.html:

image-20210116181005582

main.html:

image-20210116181402877

Controller:

@PostMapping("/login") //必须是POST方式
public String main(User user, HttpSession session, Model model){
    //判断是否登录成功,如果成功放入Session域中,失败则回到登录页面
    if (StringUtils.hasLength(user.getUserName()) && user.getPassword().equals("123465") ){ //如果名字不为 或者 空密码是123456
        //把用户放入Session域
        session.setAttribute("loginUser",user);
        //登陆成功,重定向到main.html,解决表单提交
        return "redirect:/main.html";
    }else {
        model.addAttribute("msg","帐号密码错误");
        //回到登录页面
        return "login";
    }
}

/**
 * 去main页面
 * @return
 */
//temlpates下的所有页面的解析一定都是通过请求处理,最终由模板引擎进行解析的,直接访问是访问不到的。
@GetMapping("/main.html") //处理了一个请求,请求名字是main.html
public String mainPage(HttpSession session,Model model){
    //如果别人直接访问/main.html进入后台,得先进行判断有没有登录
    Object user = session.getAttribute("loginUser");
    //如果有用户代表登录成功进入后台管理页面,登录失败则回到登录页面
    if (user==null){
        model.addAttribute("msg","您还未登录");
        return "login";
    } else {
        return "main";
    }
}

页面结果:

image-20210116181738353

image-20210116181831169

(4)遍历数据:

Controller:

    @GetMapping("/dynamic_table")
    public String dynamic_table(Model model){
        //表格内容的遍历
        List<User> users = Arrays.asList(new User("zhangsan", "123456"),
                new User("lisi", "123444"),
                new User("haha", "aaaaa"),
                new User("hehe ", "aaddd"));
        model.addAttribute("users",users);

        return "table/dynamic_table";
    }

显示表格数据的html页面:

<table class="display table table-bordered" id="hidden-table-info">
        <thead>
        <tr>
            <th>#</th>
            <th>用户名</th>
            <th>密码</th>
        </tr>
        </thead>
        <tbody>
        <tr class="gradeX" th:each="user,stats:${users}"> <!--stats是状态-->
            <td th:text="${stats.count}">Trident</td>
            <td th:text="${user.userName}">Internet</td>
            <td >[[${user.password}]]</td><!--也可以使用这个方法拿到数据-->
        </tr>
        </tbody>
        </table>

6、拦截器

6.1、实现HandlerInterceptor 接口

/**
 * 登录检查
 * 1、配置好拦截器要拦截哪些请求
 * 2、把这些配置放在容器中(编写配置类AdminWebConfig)
 */
@Slf4j //这是日志用的
public class LoginInterceptor implements HandlerInterceptor {

    /**
     * 目标方法执行之前
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        String requestURI = request.getRequestURI();
        log.info("preHandle拦截的请求路径是{}",requestURI);

        //登录检查逻辑
        HttpSession session = request.getSession();

        Object loginUser = session.getAttribute("loginUser");

        if(loginUser != null){
            //放行
            return true;
        }

        //拦截住。未登录。跳转到登录页
//      re.sendRedirect("/");(重定向+请先登录消息放到session无法成功,因为重定向也被拦截了,这里使用requset+转发)
        request.setAttribute("msg","请先登录");
        request.getRequestDispatcher("/").forward(request,response);
        return false;
    }

    /**
     * 目标方法执行完成以后
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("postHandle执行{}",modelAndView);
    }

    /**
     * 页面渲染以后
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("afterCompletion执行异常{}",ex);
    }
}

6.2、配置拦截器

/**
 * 1、编写一个拦截器实现HandlerInterceptor接口
 * 2、拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors)
 * 3、指定拦截规则【如果是拦截所有,静态资源也会被拦截】
 */
@Configuration
public class AdminWebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**")  //所有请求都被拦截包括静态资源
                .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**"); //放行的请求
    }
}

6.3、拦截器原理

1、根据当前请求,找到HandlerExecutionChain【可以处理请求的handler以及handler的所有 拦截器】

2、先来顺序执行 所有拦截器的 preHandle方法

  • 1、如果当前拦截器prehandler返回为true。则执行下一个拦截器的preHandle
  • 2、如果当前拦截器返回为false。直接 倒序执行所有已经执行了的拦截器的 afterCompletion;

3、如果任何一个拦截器返回false。直接跳出不执行目标方法

4、所有拦截器都返回True。执行目标方法

5、倒序执行所有拦截器的postHandle方法。

6、前面的步骤有任何异常都会直接倒序触发 afterCompletion

7、页面成功渲染完成以后,也会倒序触发 afterCompletion

image.png

image.png

7、文件上传

7.1 文件上传代码

表单:

<!--这里其实直接写action="upload"也行 ,method="post"enctype="multipart/form-data"为文件上传固定写法-->
<form th:action="@{/upload}" method="post" enctype="multipart/form-data">
    姓名 <input type="text" name="username">
    邮箱 <input type="text" name="email">
    头像 <input type="file" name="headerImg">
    生活照 <input type="file" name="photos" multiple><!--multiple代表可以上传多个文件-->
    <input type="submit">
</form>

Controller:

@PostMapping("/upload")
public String upload(String email, String username,
                     @RequestPart("headerImg") MultipartFile headerImg,
                     @RequestPart("photos") MultipartFile[] photos) throws IOException {

    //打印一下日志信息
    log.info("上传的信息:email={},username={},headerImg文件大小={},photos一共几个图片={}",
            email,username,headerImg.getSize(),photos.length);

    //如果文件不是空的
    if (!headerImg.isEmpty()){
        //保存文件服务器,OSS服务器
        String originalFilename = headerImg.getOriginalFilename(); //拿到文件原始名字
        headerImg.transferTo(new File("G:\\"+originalFilename)); //将文件直接存到对应的位置
    }

    //同理
    if (photos.length>0){
        for (MultipartFile photo : photos) {
            if (!photo.isEmpty()){
                String originalFilename = photo.getOriginalFilename();
                photo.transferTo(new File("G:\\"+originalFilename));
            }
        }

    }
    return "login";
}

日志的输出结果:

image-20210116211329044

可以设置文件上传的配置:

#单个文件允许的最大大小
spring.servlet.multipart.max-file-size=10MB
#一整个请求也就是所有文件允许的最大大小
spring.servlet.multipart.max-request-size=100MB

7.2 自动配置原理

文件上传自动配置类-MultipartAutoConfiguration-MultipartProperties

  • 自动配置好了 StandardServletMultipartResolver 【文件上传解析器】
  • 原理步骤

    • 1、请求进来使用文件上传解析器判断(isMultipart)并封装(resolveMultipart,返回MultipartHttpServletRequest)文件上传请求
    • 2、参数解析器来解析请求中的文件内容封装成MultipartFile
    • 3、将request中文件信息封装为一个Map;MultiValueMap<String, MultipartFile>

FileCopyUtils。实现文件流的拷贝

    @PostMapping("/upload")
    public String upload(@RequestParam("email") String email,
                         @RequestParam("username") String username,
                         @RequestPart("headerImg") MultipartFile headerImg,
                         @RequestPart("photos") MultipartFile[] photos)

image.png

8、异常处理

8.1、配合Thymeleaf错误处理

(1)默认规则

  • 默认情况下,Spring Boot提供/error处理所有错误的映射
  • 对于机器客户端(例如PostMan),它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据

image-20210116212838512

image-20210116212855276

  • 在templates文件夹下error文件夹下的4xx,5xx页面会被自动解析(所有4带头的错误码会自动找到4xx.html并呈现,例如404错误码。5带头的错误同理);

image.png

  • 并且可以在这些错误页面动态获取错误的信息,例如上边 [JSON响应其中包含错误] 中的所有JOSN信息都能拿到。

image-20210116213704787

image-20210116213944836

8.2、SpringBoot异常处理的五种方式

(1)自定义错误页面

SpringBoot 默认的处理异常的机制:SpringBoot 默认的已经提供了一套处理异常的机制。 一旦程序中出现了异常 SpringBoot 会像/error 的 url 发送请求。在 springBoot 中提供了一个 叫 BasicExceptionController 来处理/error 请求,然后跳转到默认显示异常的页面来展示异常信息。

默认错误页面:

image-20210116212855276

如果我们需要将所有的异常同一跳转到自定义的错误页面, 需要再src/main/resources/templates目录下创建error.html 页面。

注意:名称必须叫 error

error.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
        <!--SpringBoot默认存储异常信息的key为exception-->
    <span th:text="${exception}" />
</body>
</html>
(2)@ExceptionHandle注解处理异常
@Controller
public class DemoController {
    @RequestMapping("/show")
    public String showInfo() {
        String str = null;
        str.length();
        return "index";
    }

    @RequestMapping("/show2")
    public String showInfo2() {
        int a = 10 / 0;
        return "index";
    }

    /**
     * java.lang.ArithmeticException 该方法需要返回一个 ModelAndView:目的是可以让我们封装异常信息以及视
     * 图的指定 参数 Exception e:会将产生异常对象注入到方法中
     */
    @ExceptionHandler(value = { java.lang.ArithmeticException.class })
    public ModelAndView arithmeticExceptionHandler(Exception e) {
        ModelAndView mv = new ModelAndView();
        mv.addObject("error", e.toString());
        mv.setViewName("error1");
        return mv;
    }

    /**
     * java.lang.NullPointerException 该方法需要返回一个 ModelAndView:目的是可以让我们封装异常信息以及视
     * 图的指定 参数 Exception e:会将产生异常对象注入到方法中
     */
    @ExceptionHandler(value = { java.lang.NullPointerException.class })
    public ModelAndView nullPointerExceptionHandler(Exception e) {
        ModelAndView mv = new ModelAndView();
        mv.addObject("error", e.toString());
        mv.setViewName("error2");
        return mv;
    }
}

优点:可以自定义异常信息存储的key,自定义跳转视图的名称

缺点:需要编写大量的异常处理方法,不能跨controller,如果两个controller中出现同样的异常,需要重新编写异常处理的方法

(3)@ControllerAdvice+@ExceptionHandler注解处理异常
/* 全局异常处理类
*
*
*/
@ControllerAdvice
public class GlobalException {
    /**
     * java.lang.ArithmeticException 该方法需要返回一个 ModelAndView:目的是可以让我们封装异常信息以及视
     * 图的指定 参数 Exception e:会将产生异常对象注入到方法中
     */
    @ExceptionHandler(value = { java.lang.ArithmeticException.class })
    public ModelAndView arithmeticExceptionHandler(Exception e) {
        ModelAndView mv = new ModelAndView();
        mv.addObject("error", e.toString());
        mv.setViewName("error1");
        return mv;
    }

    /**
     * java.lang.NullPointerException 该方法需要返回一个 ModelAndView:目的是可以让我们封装异常信息以及视
     * 图的指定 参数 Exception e:会将产生异常对象注入到方法中
     */
    @ExceptionHandler(value = { java.lang.NullPointerException.class })
    public ModelAndView nullPointerExceptionHandler(Exception e) {
        ModelAndView mv = new ModelAndView();
        mv.addObject("error", e.toString());
        mv.setViewName("error2");
        return mv;
    }
}

缺点:编写大量的异常处理方法,代码冗余

(4)配置 SimpleMappingExceptionResolver 处理异常
/**
 * 通过 SimpleMappingExceptionResolver 做全局异常处理
 *
 *
 */
@Configuration//表名该类是一个配置类
public class GlobalException {
    /**
     * 该方法必须要有返回值。返回值类型必须是: SimpleMappingExceptionResolver
     */
    @Bean//完成bean的注册
    public SimpleMappingExceptionResolver getSimpleMappingExceptionResolver() {
        SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
        Properties mappings = new Properties();
        /**
         * 参数一:异常的类型,注意必须是异常类型的全名 参数二:视图名称
         */
        mappings.put("java.lang.ArithmeticException", "error1");
        mappings.put("java.lang.NullPointerException", "error2");
        // 设置异常与视图映射信息的
        resolver.setExceptionMappings(mappings);
        return resolver;
    }
}

缺点:不显示具体异常信息

(5)自定义 HandlerExceptionResolver 类处理异常
/**
 * 通过实现 HandlerExceptionResolver 接口做全局异常处理
 *
 *
 */
@Configuration
public class GlobalException implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
            Exception ex) {
        ModelAndView mv = new ModelAndView();
        // 判断不同异常类型,做不同视图跳转
        if (ex instanceof ArithmeticException) {
            mv.setViewName("error1");
        }
        if (ex instanceof NullPointerException) {
            mv.setViewName("error2");
        }
        mv.addObject("error", ex.toString());
        return mv;
    }
}

9、Web原生组件注入(Servlet、Filter、Listener)

9.1、使用Servlet API(常用)

@ServletComponentScan指定原生Servlet组件都放在那里:

在主程序上配置,必须配置了才能让那些原生servlet组件生效。

@ServletComponentScan(basePackages = "com.Luo.servlet") //指定原生的servlet组件都在哪里,并注册进来
@SpringBootApplication
public class HoutaidemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(HoutaidemoApplication.class, args);
    }

}

@WebServlet直接响应,不会经过Spring的拦截器:

@WebServlet(urlPatterns = "/my")
public class MyServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("66666");
    }
}

@WebFilter可以指定要过滤的路劲:

@WebFilter(urlPatterns={"/css/*","/images/*"}) //my
public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("MyFilter初始化完成");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        log.info("MyFilter工作");
        chain.doFilter(request,response);
    }

    @Override
    public void destroy() {
        log.info("MyFilter销毁");
    }
}

@WebListener:

@WebListener
public class MySwervletContextListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        log.info("MySwervletContextListener监听到项目初始化完成");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        log.info("MySwervletContextListener监听到项目销毁");
    }
}

扩展:DispatchServlet 如何注册进来

  • 容器中自动配置了 DispatcherServlet 属性绑定到 WebMvcProperties;对应的配置文件配置项是 spring.mvc。
  • 通过 ServletRegistrationBean<DispatcherServlet> 把 DispatcherServlet 配置进来。
  • 默认映射的是 / 路径。

image.png

Tomcat-Servlet;

多个Servlet都能处理到同一层路径,精确优选原则

A: /my/

B: /my/1

9.2、使用RegistrationBean

ServletRegistrationBean, FilterRegistrationBean, and ServletListenerRegistrationBean

// (proxyBeanMethods = true):保证依赖的组件始终是单实例的
/*如果proxyBeanMethods = false的话,下边的Filter中new FilterRegistrationBean(myFilter)
  的做法就可能会导致创建多个servlet对象,虽然功能上没什么影响,但是会造成对象冗余          */
@Configuration(proxyBeanMethods = true)
public class MyRegistConfig {

    @Bean //放到容器中
    //注册servlet
    public ServletRegistrationBean myServlet(){
        MyServlet myServlet = new MyServlet();

        return new ServletRegistrationBean(myServlet,"/my","/my02");
    }


    @Bean
    //注册filter
    public FilterRegistrationBean myFilter(){

        MyFilter myFilter = new MyFilter();
//      return new FilterRegistrationBean(myFilter,myServlet()); 把myServlet()也传进去这种写法表示和servlet的拦截路径一样
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);
        filterRegistrationBean.setUrlPatterns(Arrays.asList("/my","/css/*"));
        return filterRegistrationBean;
    }

    @Bean
    //注册listener
    public ServletListenerRegistrationBean myListener(){
        MySwervletContextListener mySwervletContextListener = new MySwervletContextListener();
        return new ServletListenerRegistrationBean(mySwervletContextListener);
    }
}

10、嵌入式Servlet容器

10.1、切换嵌入式Servlet容器

  • 默认支持的webServer

    • Tomcat, Jetty, or Undertow
    • ServletWebServerApplicationContext 容器启动寻找ServletWebServerFactory 并引导创建服务器
  • 切换服务器

image.png

<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>
  • 原理

    • SpringBoot应用启动发现当前是Web应用。web场景包-导入tomcat
    • web应用会创建一个web版的ioc容器 ServletWebServerApplicationContext
    • ServletWebServerApplicationContext 启动的时候寻找 ServletWebServerFactory(Servlet 的web服务器工厂---> Servlet 的web服务器)
    • SpringBoot底层默认有很多的WebServer工厂;TomcatServletWebServerFactory, JettyServletWebServerFactory, or UndertowServletWebServerFactory
    • 底层直接会有一个自动配置类。ServletWebServerFactoryAutoConfiguration
    • ServletWebServerFactoryAutoConfiguration导入了ServletWebServerFactoryConfiguration(配置类)
    • ServletWebServerFactoryConfiguration 配置类 根据动态判断系统中到底导入了那个Web服务器的包。(默认是web-starter导入tomcat包),容器中就有 TomcatServletWebServerFactory
    • TomcatServletWebServerFactory 创建出Tomcat服务器并启动;TomcatWebServer 的构造器拥有初始化方法initialize---this.tomcat.start();
    • 内嵌服务器,就是手动把启动服务器的代码调用(tomcat核心jar包存在)

10.2、定制Servlet容器

  • 实现 WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>

    • 把配置文件的值和ServletWebServerFactory 进行绑定
  • 修改配置文件 server.xxx
  • 直接自定义 ConfigurableServletWebServerFactory

xxxxxCustomizer:定制化器,可以改变xxxx的默认规则

import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.stereotype.Component;

@Component
public class CustomizationBean implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {

    @Override
    public void customize(ConfigurableServletWebServerFactory server) {
        server.setPort(9000);
    }
}

11、定制化原理

11.1、定制化的常见方式

  • 修改配置文件;
  • xxxxxCustomizer;
  • 编写自定义的配置类 xxxConfiguration;+ @Bean替换、增加容器中默认组件;视图解析器 (可以使用方法上@Bean放入容器,也可以直接类上@Component整个放入容器)
  • Web应用 编写一个配置类实现 WebMvcConfigurer 即可定制化web功能;+ @Bean给容器中再扩展一些组件(推荐使用)
@Configuration
public class AdminWebConfig implements WebMvcConfigurer
  • 完全控制MVC,可以修改底层的很多规则,谨慎使用!

image-20210117110309238

11.2、原理分析套路

基本就是starter开启,改一改配置文件就能达到使用的目的。

场景starter - xxxxAutoConfiguration - 导入xxx组件 - 绑定xxxProperties -- 绑定配置文件项

最后修改:2021 年 02 月 02 日 07 : 38 PM
如果觉得我的文章对你有用,请随意赞赏