springboot的web源码分析

我们把静态资源放到static目录下

浏览器器访问就能展示图片的信息,如果显示不出来,把target删除掉,重新启动项目

当我们想要配置一些访问静态资源的规则,可以设置

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

默认不需要配置,访问的都是/**的资源

我们直接访问http://localhost:8080/aaa.webp 就会报错

需要http://localhost:8080/res/aaa.webp 这样访问

我们也可以自定义一个目录tp放入图片

在yml配置web静态资源路径

spring:
  mvc:
    static-path-pattern: /res/**
  web:
    resources:
      static-locations: classpath:tp

我们在tp这个目录下配置一个index.html看看能不能访问

发现也可以

但是我们想直接访问http://localhost:8080/ 就直接展示index.html就会报错

需要把mvc静态拦截资源注释掉 才能访问

不用输入index.html,直接访问就能显示了

我们把yml文件的配置注释掉,把资源都放在static目录下

创建一个favicon.ico的图标文件,必须是这个名字

可以看到图标就有了,如果不显示,清空缓存 关闭浏览器 重新打开

spring mvc的自动配置类就是WebMvcAutoConfiguration

在@ConditionalOnWebApplication(type = Type.SERVLET)

这里只有基于servlet的web应用程序才能匹配

如果你的项目不是web项目,那么使用不了mvc的功能

我们在看下WebMvcAutoConfigurationAdapter webmvc自动配置适配器

我们点进去WebMvcProperties,webmvc属性和前缀spring.mvc绑定的

就是我们在yml文件输入spring.mvc的时候这些字段会自动显示出来

在来看下WebProperties,他是和前缀spring.web绑定的

在看下Resources,这里对应者静态资源的目录

我们在看下webmvc字段配置适配器的构造方法,所有的参数值都会从容器中确定

ListableBeanFactory beanFactory 就是spring的bean工厂

HttpMessageConverters 就是http消息转换器

ResourceHandlerRegistrationCustomizer 资源处理程序注册自定义器

ServletRegistrationBean 给应用注册servlet ,filter

在来看下addResourceHandlers,添加资源处理程序,资源处理的默认规则

我们进入this.resourceProperties.isAddMappings

是否启用默认静态资源处理,如果为true,可以访问静态资源,如果为false,静态资源不能访问

可以看到默认是true

我们在yml文件设置为false

spring:
  web:
    resources:
      add-mappings: false

添加资源处理程序这里 直接禁止了

我们在浏览器访问静态资源就是404,默认是true 不需要配置

还可以在yml文件设置缓存时间

spring:
  web:
    resources:
      cache:
        period: 100000

在this.mvcProperties.getStaticPathPattern()这里mv属性的静态资源规则是/**

在Yml文件配置

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

这里获取的时候就变成了res/**

在this.resourceProperties.getStaticLocations() 静态资源路径 就是读取的下面的路径信息

当我们在Yml文件改了tp这个文件夹路径

spring:
  web:
    resources:
      static-locations: classpath:tp

这里读取的就是tp这个目录

我们来看下欢迎页的处理规则

WelcomePageHandlerMapping 欢迎页处理程序映射

HandlerMapping处理器映射 ,保存了每一个handler能处理那些请求

可以看到如果配置了静态资源规则和静态资源拦截,那么不会走index.html界面

我们把这些yml配置都去掉,可以看到访问到了static目录下的index.html

然后访问localhost:8080 就自动去找index.html界面了

  @DeleteMapping("/bb")
    public String bb(){
        return "bb";
    }

    @PutMapping("/cc")
    public String cc(){
        return "cc";
    }

当我们在index.html界面直接写PUT和DELETE的表单方式是不行的


<form action="/cc" method="PUT">
    <input type="submit" value="修改">
</form>

<form action="/bb" method="DELETE">
    <input type="submit" value="删除">
</form>

我们需要改成post并且在加一行隐藏的列,name变成_method,value输入DELETE和PUT

<form action="/cc" method="post">
    <input type="hidden" name="_method" value="put">
    <input type="submit" value="修改">
</form>

<form action="/bb" method="post">
    <input type="hidden" name="_method" value="delete">
    <input type="submit" value="删除">
</form>

我们来看这里,有序的隐藏http方法过滤器

在yml配置mvc隐藏方法过滤设置为true

spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true

在进入HiddenHttpMethodFilter这里隐藏http方法过滤器

我们在index.html界面点击修改或删除方法,进入这里

看到DEFAULT_METHOD_PARAM = "_method"

默认方法参数就是_method,就是我们在html设置的那个_method

在这里会判断action是不是post请求,并且参数是不是带着_method

并且存在DELETE,PUT,PATCH中

如果存在进入HttpMethodRequestWrapper把方法换成delete或者put

直接访问就不报错了

我们也可以改成自定义的名称,为_aaa

在配置类,我们重写HiddenHttpMethodFilter 隐藏http方法过滤器

在setMethodParam 重新设置方法参数就可以了

@Configuration
public class TestConfig {

    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
        HiddenHttpMethodFilter hiddenHttpMethodFilter=new HiddenHttpMethodFilter();
        hiddenHttpMethodFilter.setMethodParam("_aaa");
        return hiddenHttpMethodFilter;
    }
}

我们来看下请求映射原理

DispatcherServlet是所有请求的开始

进入这个类

然后进入FrameworkServlet

在这里我们就看到,他有doGet和doPost方法

最终继承的还是HttpServlet

我们可以看到doget和dopost都有一个processRequest 处理请求方法 我们点进去

他的核心是doService方法我们点进去,然后再进入方法的子类

在访问下走找到doDispatch方法,每个请求都会调用, 点进去

我们随便请求一个接口,然后进入getHandler方法

这里有多个handlerMappings 处理程序映射

比如说这个欢迎页的配置规则是/,那么就进入index.html界面

比如说RequestMappingHandlerMapping就是对应@RequestMapping这个注解和handler的映射规则

我们可以看下,在这个registry这里对应的这些方法,就是我们在控制层写的那些方法

我们进入getHandler方法

在进入getHandlerInternal方法

在进入lookupHandlerMethod方法

当我们只有一个路径的时候,这时候匹配上了我们的接口

如果size大于1,那么会报错

所有的请求映射都在HandlerMapping中

springboot自动配置欢迎页的HandlerMapping,访问/就能访问到index.html

springboot自动配置了默认的RequestMappingHandlerMapping

请求进来,挨个尝试所有的HandlerMapping看是否有请求信息

如果有就找到这个请求对应的handler

如果没有就是下一个HandlerMapping

我们需要一些自定义的映射出来,我们也可以自己给容器中放HandlerMapping,自定义HandlerMapping

@GetMapping("/a")
public String a(@RequestParam("name")String name,@RequestParam("pwd")String pwd){
    return name+pwd;
}

浏览器访问

http://localhost:8080/a?name=aa&pwd=asda

我们进入RequestMappingHandlerAdapter类的this.argumentResolvers != null这里

argumentResolvers参数解析器,可以看到我们请求的参数,在这里出现了

在这里有一堆参数解析器,每一个不同的注解对应的参数,就是在这里解析的

然后进入ServletInvocableHandlerMethod的invokeAndHandle方法

在这一行Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);

会跳转回我们的控制层

执行完之后拿到返回结果

我们在进入invokeForRequest方法

在进入getMethodArgumentValues方法

所有的参数在下面遍历

我们直接这样写name="actor",获取的数据就是null

@Data
public class Actor {


    private String name;

    private Integer age;
}
@PostMapping("/bb")
    public String bb(Actor actor){
        System.out.println("===================="+actor);
        return "bb";
    }
<form action="/bb" method="post">
    <input type="text" name="actor" value="你好,12">
    <input type="submit" value="修改">
</form>

所以我们需要自定义转换器

package com.example.client.config;

import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.example.client.entity.Actor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;


@Configuration
public class TestConfig {

    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
        HiddenHttpMethodFilter hiddenHttpMethodFilter=new HiddenHttpMethodFilter();
        hiddenHttpMethodFilter.setMethodParam("_aaa");
        return hiddenHttpMethodFilter;
    }

    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
            @Override
            public void addFormatters(FormatterRegistry registry) {
                registry.addConverter(new Converter<String, Actor>() {

                    @Override
                    public Actor convert(String source) {
                        if(StringUtils.isNotEmpty(source)){
                            Actor actor=new Actor();
                            String[] split = source.split(",");
                            actor.setName(split[0]);
                            actor.setAge(Integer.parseInt(split[1]));
                            return actor;
                        }
                        return null;
                    }
                });
            }
        };
    }
}

当我们配置自定义转换器之后,这个对象就有数据了

进入ModelAttributeMethodProcessor类 模型属性方法处理器

WebDataBinder web数据绑定这里会看到String已经转换成了实体类

我们在来看下响应处理

在spring-boot-starter-web里面引入了spring-boot-starter-json

在spring-boot-starter-json里面引入了jackson

所以我们在控制层加入@RestController或者在方法上面@ResponseBody 就返回了json类型的数据

@Controller
public class BlueController {

   
    @ResponseBody
    @GetMapping("/bb")
    public Actor bb(){
        Actor actor=new Actor();
        actor.setAge(100);
        actor.setName("aaaaaaaa");
        return actor;
    }

}

我们来分析下源码,进入RequestMappingHandlerAdapter

找到this.returnValueHandlers != null 返回值处理程序

可以看到有这么多的返回值处理程序

我们可以看到在这里,他就拿到的返回结果就是对象

然后进入HandlerMethodReturnValueHandlerComposite类的handleReturnValue方法

然后进入selectHandler方法

找到合适的处理程序返回,因为我们的方法上面加了ResponseBody注解

所以这里匹配到了RequestResponseBodyMethodProcessor

请求响应主体方法处理器

我们在进入RequestResponseBodyMethodProcessor类的handleReturnValue方法

我们在进入writeWithMessageConverters方法 使用消息转换器写入

我们首先看下内容协商,就是这一部分数据

可以看到这个getAcceptableMediaTypes就是获取内容协商的数据

获取可接受的媒体类型

getProducibleMediaTypes 获取可生产媒体类型

最终过滤在mediaTypesToUse 要使用的媒体类型

然后我们找到HttpMessageConverter http消息转换器

HttpMessageConverter 有canRead 要读取的媒体类型

和canWrite要写入的媒体类型

就是看我们那个对象能不能转成json,或者json转成对象

我们可以看到MappingJackson2HttpMessageConverter

映射json http消息转换器,这里获取到了body数据

在这个转换器中可以把对象转换成json,也可以把json转成对象

在这一行写入数据

内容协商就是根据客户端接收能力不同,返回不同媒体类型的数据

比如说返回Json和xml

在pom.xml引入

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

我们可以看到在次查询的时候,内容协商优先返回xml的数据

我们在postman看到却返回的是json,为什么呢?

因为Accept 返回的*

我们可以手动修改内容协商Accept的数据

当我修改为xml的时候他就变成了xml的数据

当我们变成json的时候 返回的就是json的数据

进入HeaderContentNegotiationStrategy类 请求头内容谈判策略

在这里获取的就是请求头传过来的Accept

得到媒体类型

循环遍历所有的MessageConverter,看谁支持操作这个对象

找到支持操作对象的converter,把converter支持的媒体类型统计出来

客户端需要application/xml,服务端就支持json/xml的

在这个for循环里面匹配xml需要的,最终返回

我们可以在yml文件开启内容协商,在format后面输入不同的类型,就返回不同的类型

spring:
  mvc:
    contentnegotiation:
      favor-parameter: true

http://localhost:8080/bb?format=xml

http://localhost:8080/bb?format=json

我们进入ContentNegotiationManager 内容协商管理器这里 可以看到strategy 策略中

拿到了json的媒体类型

我们可以自定义转换器

package com.example.client.config;

import com.example.client.entity.Actor;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;

import java.io.IOException;
import java.io.OutputStream;
import java.util.List;

public class MyConverter implements HttpMessageConverter<Actor> {
    @Override
    public boolean canRead(Class<?> clazz, MediaType mediaType) {
        return false;
    }

    @Override
    public boolean canWrite(Class<?> clazz, MediaType mediaType) {
        //如果是这个对象 那么可以写入
        return clazz.isAssignableFrom(Actor.class);
    }

    //服务器要统计所有messageconverter都能写出那些内容类型
    @Override
    public List<MediaType> getSupportedMediaTypes() {
        //自定义abc媒体类型
        return MediaType.parseMediaTypes("application/abc");
    }

    @Override
    public Actor read(Class<? extends Actor> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        return null;
    }

    @Override
    public void write(Actor actor, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        //自定义协议数据的写出
        String data="123456";
        OutputStream outputStream=outputMessage.getBody();
        outputStream.write(data.getBytes());
    }
}
@Configuration
public class TestConfig {

    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
            //扩展消息转换器
            @Override
            public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
                //添加自定义的转换器
                converters.add(new MyConverter());
            }
        };
    }
}

输入内容协商application/abc,返回我们自定义转换器的123456

我们也可以在浏览器自定义内容协商的参数和内容

package com.example.client.config;

import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.example.client.entity.Actor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.convert.converter.Converter;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.accept.HeaderContentNegotiationStrategy;
import org.springframework.web.accept.ParameterContentNegotiationStrategy;
import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.*;


@Configuration
public class TestConfig {

    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
        HiddenHttpMethodFilter hiddenHttpMethodFilter=new HiddenHttpMethodFilter();
        hiddenHttpMethodFilter.setMethodParam("_aaa");
        return hiddenHttpMethodFilter;
    }

    @Bean
    public WebMvcConfigurer webMvcConfigurer(){

        return new WebMvcConfigurer() {
            @Override
            public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
                Map<String, MediaType> mediaTypes=new HashMap<>();
                mediaTypes.put("json",MediaType.APPLICATION_JSON);
                mediaTypes.put("xml",MediaType.APPLICATION_XML);
                mediaTypes.put("abc",MediaType.parseMediaType("application/abc"));
                //指定支持解析那些参数对应的那些媒体类型
                ParameterContentNegotiationStrategy strategy=new ParameterContentNegotiationStrategy(mediaTypes);
                //自定义参数类型
                strategy.setParameterName("my");
                //HeaderContentNegotiationStrategy strategy1=new HeaderContentNegotiationStrategy();
                configurer.strategies(Arrays.asList(strategy));
            }
            //扩展消息转换器
            @Override
            public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
                //添加自定义的转换器
                converters.add(new MyConverter());
            }

        };



    }
}

http://localhost:8080/bb?my=abc

但是我们在postman 输入xml,却没有返回xml信息

需要添加请求头的内容协商策略

可以看到xml出来了

自定义参数my对应的就是这里的ParameterContentNegotiationStrategy类的format

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第1章 Spring Boot 简介 讲解Spring Boot的项目背景,已经与其他技术框架(比如,Spring、SpringMVC、SpringCloud等)的关系。简单介绍下Spring Boot 整个生态系统 第2章 开启 Spring Boot 的第一个 Web 项目 通过 Spring Initializr 来快速初始化一个 Spring Boot 原型,方便学员来极速体验Spring Boot。本课程也将会采用Gradle作为项目管理工具,让学员掌握最前瞻的构建工具。通过探索项目让学员了解项目的结构,已经相关的配置原理。 第3章 一个Hello World项目 本章是正式开始动手敲代码了。依照惯例,会先编写一个最简单的Hello World程序。从项目配置,应用的编写,再到测试用例,最后运行项目。方面学员了解整个编码的流程。 第4章 开发环境的搭建 为了让实战过程更顺利,避免不要的问题,这里会先将课程所要求的环境进行一个讲解,并要求学员最好跟随课程的环境配置。本节也会讲解如何将项目导入IDE 来运行。 第5章 集成Thymeleaf模版引擎 Thymeleaf 方面的内容,知识点会讲解的相对全面点。Thymeleaf作为界面的模版引擎,对于界面的布局和实现起着非常关键的作用。本章节也会讲解Thymeleaf 如何与 Spring Boot 来进行集成。最后通过一个实战,来让学员更加深刻的理解Thymeleaf。… 第6章 数据持久化Spring Data JPA 本章节涉及数据的持久化。从JPA规范讲起,到Spring对于JPA的用法以及与Hibernate集成实现。本课程的数据库采用MySQL,但也可以方便切换到其他数据库。最后通过一个实战内容,来帮助学员理解掌握。 第7章 全文搜索ElasticSearch 企业级应用中,难免会涉及到全文搜素。对于Java应用来说,ElasticSearch在全文搜索方面是一把“利器”。本章节会将带领学员了解全文搜索的概念,并熟悉如何用ElasticSearch来实现全文搜索。 第8章 架构设计与分层 本章节讲解了系统的整体架构设计思路,包括如何来组织项目结构。让学员理解系统的数据流程。 第9章 集成 Bootstrap Bootsrap最大的好处是,可以让整个系统界面实现响应式布局。本节先从Bootstrap 的基本原理讲起,并将常用的前端框架比如 JQuery等进行集成。最后通过一个实战内容,来帮助学员理解掌握。 第10章 博客系统的需求分析与原型设计 本章节是对博客系统的需求分析与设计。对于企业级应用的完整流程来说,需求的分析与设计是必不可少的环节。本章节设计部分包含了原型设计、数据库设计及接口设计。 第11章 权限管理Spring Security Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架,在企业级应用中被广泛使用。本章节不会对该框架做深入探讨,仅从基于角色的权限管理角度,来实现对系统的权限管理。 第12章 博客系统的整体框架实现 先对系统的整个界面、结构、布局、API进行实现,这样方便每个模块进行划分及实现。 第13章 博客系统的用户管理实现 对用户管理模块进行前后台的实现。 第14章 博客系统的角色管理实现 对用户角色理模块进行前后台的实现。 第15章 博客系统的权限管理实现 对用权限理模块进行前后台的实现。 第16章 博客系统的博客管理实现 对博客管理模块进行前后台的实现。 第17章 博客系统的评论管理实现 对评论管理模块进行前后台的实现。 第18章 博客系统的点赞管理实现 对用户点赞理模块进行前后台的实现。 第19章 博客系统的分类管理实现 对分类管理模块进行前后台的实现。 第20章 博客系统的标签管理实现 对标签管理模块进行前后台的实现。 第21章 博客系统的搜索实现 对搜索模块进行前后台的实现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值