SpringBoot web开发源码深入分析

目录
  • 一、MVC自动配置
    • 1、默认支持的功能
    • 2、静态资源与首页相关源码解析
    • 3、Rest映射及源码分析
    • 4、请求映射原理

一、MVC自动配置

1、默认支持的功能

Spring Boot为Spring MVC提供了自动配置,默认支持以下功能

  • ContentNegotiatingViewResolver和BeanNameViewResolver视图解析器
  • 支持静态资源,包括webjars
  • 转换器的自动注册、自定义转换器GenericConverter与格式化
  • 支持http消息转换(请求与响应)
  • MessageCodesResolver错误消息
  • 首页映射
  • 图标自定义
  • 自动使用ConfigurableWebBindingInitializer,博主百度了一下,它的主要作用就是初始化WebDataBinder,将请求的参数转化为对应的JavaBean,并且会结合类型、格式转换等API一起使用

2、静态资源与首页相关源码解析

SpringBoot启动时会默认加载 xxxAutoConfiguration 类(自动配置类),SpringMVC功能的自动配置类为 WebMvcAutoConfiguration

@AutoConfiguration(
    after = {DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class}
)
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
@AutoConfigureOrder(-2147483638)
public class WebMvcAutoConfiguration {
	...
}

然后我们可以看到他有一个静态内部类WebMvcAutoConfigurationAdapter,可以看到这是一个配置类

@Configuration(
    proxyBeanMethods = false
)
@Import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class})
// 将配置文件的相关属性和括号中的两个类进行了绑定,然后注册到容器中。WebMvcProperties和spring.mvc开头的配置、WebProperties和spring.web开头的配置
@EnableConfigurationProperties({WebMvcProperties.class, WebProperties.class})
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {
	...
}

然后我们发现,这个配置类只有一个有参构造器,在这种情况下,我们默认有参构造器所有参数的值都会从容器中获取

// 这里的WebProperties 和WebMvcProperties都在上面和配置进行绑定过了,如果我们没有配置该配置项,那就去类中取默认配置的值
// ListableBeanFactory beanFactory Spring的beanFactory
// 其他的可以自己去了解下,博主这里没有特地去搜了
public WebMvcAutoConfigurationAdapter(WebProperties webProperties, WebMvcProperties mvcProperties, ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider, ObjectProvider<WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider, ObjectProvider<DispatcherServletPath> dispatcherServletPath, ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
	this.resourceProperties = webProperties.getResources();
	this.mvcProperties = mvcProperties;
	this.beanFactory = beanFactory;
	this.messageConvertersProvider = messageConvertersProvider;
	this.resourceHandlerRegistrationCustomizer = (WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer)resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
	this.dispatcherServletPath = dispatcherServletPath;
	this.servletRegistrations = servletRegistrations;
	this.mvcProperties.checkConfiguration();
}

那么我们的静态资源映射以及webjars都是在哪里进行配置的呢,我们往下看找到一个方法

public void addResourceHandlers(ResourceHandlerRegistry registry) {
	// 判断这个isAddMappings属性是否为false,默认值是true,如果我们在yaml文件或者properties中改为false,那么就会进这个条件语句,后面的静态资源路径以及webjars都不会生效了
    if (!this.resourceProperties.isAddMappings()) {
        logger.debug("Default resource handling disabled");
    } else {
    	// webjars的规则
        this.addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
        // 默认静态资源地址的处理规则
        this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
            registration.addResourceLocations(this.resourceProperties.getStaticLocations());
            if (this.servletContext != null) {
                ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
                registration.addResourceLocations(new Resource[]{resource});
            }
        });
    }
}

那我们欢迎页是在哪里配置的呢?

我们发现,在这个WebMvcAutoConfiguration下面还有一个静态内部类EnableWebMvcConfiguration,它也是一个配置类

这里面有一个方法welcomePageHandlerMapping()

HandlerMapping(处理映射器):根据URL找到对应的处理器

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

点进WelcomePageHandlerMapping的构造方法可以看到,它的逻辑大体上为,如果welcomePage不等于null,而且staticPathPattern是默认的/**,就会去我们的静态资源文件夹找index.html,否则就去找有没有能处理/index接口的映射器

这里的staticPathPattern和spring.mvc.static-path-pattern是绑定在一起的

WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders, ApplicationContext applicationContext, Resource welcomePage, String staticPathPattern) {
    if (welcomePage != null && "/**".equals(staticPathPattern)) {
        logger.info("Adding welcome page: " + welcomePage);
        this.setRootViewName("forward:index.html");
    } else if (this.welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
        logger.info("Adding welcome page template: index");
        this.setRootViewName("index");
    }
}

3、Rest映射及源码分析

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

具体的可以看我之前的 【SpringMVC】Restful风格及中文乱码问题

  • 原来获取用户信息–/getUSer、删除用户–/deleteUser、修改用户–editUser、保存用户/saveUser
  • 使用REST风格获取用户信息–GET、删除用户–DELETE、修改用户–PUT、保存用户POST

核心源码部分:WebMvcAutoConfiguration类下的hiddenHttpMethodFilter()方法

核心配置:如果要从页面发起PUT、DELETE请求,需要在yaml文件中将spring.mvc.hiddenmethod.filter.enabled设置为true,如果是客户端工具如postman发起,则无需开启

@Bean
@ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
@ConditionalOnProperty(
    prefix = "spring.mvc.hiddenmethod.filter",
    name = {"enabled"}
)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
    return new OrderedHiddenHttpMethodFilter();
}

我们点开OrderedHiddenHttpMethodFilter可以看到它继承了HiddenHttpMethodFilter这个类

我们接着跟进去 发现里面有一个doFilterInternal()方法,请求进来都会被这个方法拦截

package org.springframework.web.filter;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpMethod;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
public class HiddenHttpMethodFilter extends OncePerRequestFilter {
    private static final List<String> ALLOWED_METHODS;
    public static final String DEFAULT_METHOD_PARAM = "_method";
    private String methodParam = "_method";
    public HiddenHttpMethodFilter() {
    }
    public void setMethodParam(String methodParam) {
        Assert.hasText(methodParam, "'methodParam' must not be empty");
        this.methodParam = methodParam;
    }
	// 它会对请求方法进行判断
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        HttpServletRequest requestToUse = request;
        // 如果是表单是post请求且请求正常,那么它会判断请求参数里面是否存在_method这个参数
        if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
            String paramValue = request.getParameter(this.methodParam);
            // 判断_method参数是否为空
            if (StringUtils.hasLength(paramValue)) {
            	// 将参数转成大写
                String method = paramValue.toUpperCase(Locale.ENGLISH);
                // 判断_method参数是否是PUT、DELETE、PATCH其中的一个,如果满足就使用requesWrapper重写了HttpServletRequest的getMethod方法,返回的是传入的值
                if (ALLOWED_METHODS.contains(method)) {
                    requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
                }
            }
        }
		// 这里过滤器放行时的request是处理后得到的HttpMethodRequestWrapper
        filterChain.doFilter((ServletRequest)requestToUse, response);
    }
    static {
        ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
    }
	// HttpServletRequestWrapper类还是实现了HttpServletRequest接口
    private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
        private final String method;
        public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
            super(request);
            this.method = method;
        }
        public String getMethod() {
            return this.method;
        }
    }
}

我们可以写一个html页面还有一个控制器测试一下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
Hello World!
<form action="/test/user" method="get">
    <input type="submit" value="REST-GET">
</form>
<form action="/test/user" method="post">
    <input type="submit" value="REST-POST">
</form>
<form action="/test/user" method="post">
    <input name="_method" type="hidden" value="PUT">
    <input type="submit" value="REST-PUT">
</form>
<form action="/test/user" method="post">
    <input name="_method" type="hidden" value="DELETE">
    <input type="submit" value="REST-DELETE">
</form>
</body>
</html>
package com.decade.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping(value = "/test")
public class TestController {
    @RequestMapping(value = "/user", method = RequestMethod.GET)
    @ResponseBody
    public String queryUser() {
        return "get查询用户的信息";
    }
    @RequestMapping(value = "/user", method = RequestMethod.POST)
    @ResponseBody
    public String editUser() {
        return "post保存用户的信息";
    }
    @RequestMapping(value = "/user", method = RequestMethod.DELETE)
    @ResponseBody
    public String deleteUser() {
        return "delete删除用户的信息";
    }
    @RequestMapping(value = "/user", method = RequestMethod.PUT)
    @ResponseBody
    public String saveUser() {
        return "put编辑用户的信息";
    }
}

验证的时候遇到一个问题,那就是如果引入了spring-boot-starter-security这个依赖

那么调用POST、PUT和DELETE接口时就会出错

博主查了一下,这是因为Spring Boot 与 SpringSecurity整合后,为了防御csrf攻击,只有GET|OPTIONS|HEAD|TRACE|CONNECTION可以通过

其他方法请求时,需要有token

我将SpringSecurity的依赖注掉之后,验证就通过了

拓展:如果我们要修改HiddenHttpMethodFilter里过滤方法中判断的参数名称,我们可以自己写一个配置类,例如我们想将它由_method改为_m,那可以这么写

package com.decade.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.HiddenHttpMethodFilter;
@Configuration(proxyBeanMethods = false)
public class MyMvcConfig {
    @Bean
    public HiddenHttpMethodFilter createHiddenHttpMethodFilter() {
        HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
        // 利用setMethodParam设置自己想要的参数名
        hiddenHttpMethodFilter.setMethodParam("_m");
        return hiddenHttpMethodFilter;
    }
}

4、请求映射原理

1)接下来我们研究一下,Spring Boot是怎么将一个个请求匹配到对应的处理器(即controller)的

根据我们之前SpringMVC的学习 我们可以知道 所有的请求都会被DispatcherServlet拦截,我们一直跟下去可以发现,DispatcherServlet实际上也是继承了HttpServlet

我们着重分析一下DispatcherServlet中的doDispatch()方法,我们发现有一个getHandler()方法

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    try {
        try {
            ModelAndView mv = null;
            Object dispatchException = null;
            try {
            	// 检查是否是文件上传请求
                processedRequest = this.checkMultipart(request);
                multipartRequestParsed = processedRequest != request;
                // 决定是哪个handler处理当前请求,HandlerMapping(处理映射器):根据URL找到对应的处理器
                mappedHandler = this.getHandler(processedRequest);
                if (mappedHandler == null) {
                    this.noHandlerFound(processedRequest, response);
                    return;
                }

我们尝试发起一个请求,使用debug模式可以发现,这个getHandler()方法就是对容器中的handlerMapping进行一个遍历,查看哪个处理映射器能处理这个请求

我们可以看到这里有WelcomePageHandlerMapping(与首页相关的)等映射器

通过分析RequestMappingHandlerMapping,我们可以发现,他这里面保存了所有@RequestMapping 路径和与之对应控制器类下的方法的映射规则

我们继续深入到AbstractHandlerMapping这个类下的getHandler()---->getHandlerInternal()方法

然后AbstractHandlerMethodMapping这个类继承了AbstractHandlerMapping,并完成了关于getHandlerInternal()的重写,接着就是lookupHandlerMethod()----->addMatchingMappings()

------>getMatchingMapping()

然后又跟到getMatchingMapping()------->RequestMappingInfo.getMatchingCondition()

最后,我们发现在RequestMappingInfo这个类中,getMatchingCondition()这个方法会对请求类型做一个筛选,这样就能将相同路径不同请求方法的接口区分开来,如果存在相同请求类型且请求路径也相同,那么系统就会报错

同样的,如果我们需要自定义映射处理,我们也可以自己给容器中放HandlerMapping

2)问题:那么我们还可以思考一下,我们最开始遍历的那些handlerMapping是从哪里来的呢?

我们的目光还是回到DispatcherServlet,这里面有一个initHandlerMappings()

这里他会从容器中获取实现了HandlerMapping接口的处理映射器

这样 我们就基本完成了spring boot关于web开发的源码分析

到此这篇关于SpringBoot web开发源码深入分析的文章就介绍到这了,更多相关SpringBoot web开发内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • SpringBoot整合Web之AOP配置详解

    目录 配置AOP AOP简介 Spring Boot 支持 其它 自定义欢迎页 自定义 favicon 除去某个自动配置 配置AOP AOP简介 要介绍面向切面变成(Aspect-Oriented Programming,AOP),需要先考虑一个这样的场景:公司有一个人力资源管理系统目前已经上线,但是系统运行不稳定,有时运行的很慢,为了检测到底是哪个环节出现问题了,开发人员想要监控每一个方法执行的时间,再根据这些执行时间判断出问题所在.当问题解决后,再把这些监控移除掉.系统目前已经运行,如果手动

  • SpringBoot整合Web开发之Json数据返回的实现

    目录 本章概要 返回JSON数据 默认实现 自定义转换器 1. 使用Gson 2. 使用fastjson 静态资源访问 默认策略 自定义策略 1. 在配置文件中定义 2. Java编码定义 本章概要 返回JSON数据 静态资源访问 返回JSON数据 默认实现 JSON 是目前主流的前后端数据传输方式,Spring MVC中使用消息转换器HTTPMessageConverter对JSON的转换提供了很好的支持,在Spring Boot中更进一步,对相关配置做了进一步的简化.默认情况下,创建一个Sp

  • SpringBoot整合Web之CORS支持与配置类和 XML配置及注册拦截器

    目录 本章概要 CORS 支持 1. 创建SpringBoot工程 2. 创建控制器 3. 配置跨域 4. 测试 配置类与 XML 配置 注册拦截器 本章概要 CORS 支持 配置类与 XML 配置 注册拦截器 CORS 支持 CORS (Cross-Origin Resource Sharing)是由 W3C 制定开发的一种跨域资源共享技术标准,其目的就是为了解决前端的跨域请求.在 Java EE 开发中,最常见的前端跨域请求解决方案是 JSONP ,但是 JSONP 只支持 GET 请求,而

  • SpringBoot web静态资源映射实现步骤详解

    目录 静态资源映射规则 自定义静态资源映射规则 静态资源映射规则 “/**” 访问当前项目任何资源,全部找静态资源的文件夹进行映射 静态资源的文件夹包括: "classpath:/META-INF/resources/","classpath:/resources/","classpath:/static/", "classpath:/public/" 静态资源路径下的文件,可以通过地址栏直接访问. 例如:我们在在static

  • SpringBoot整合Web开发之文件上传与@ControllerAdvice

    目录 本章概要 文件上传 单文件上传 多文件上传 @ControllerAdvice 全局异常处理 添加全局数据 请求参数预处理 本章概要 文件上传 @ControllerAdvice 文件上传 Java 中的文件上传一共涉及两个组件,一个是 CommonsMultipartResolver,另一个是 StandardServletMultipartResolver ,其中 CommonsMultipartResolver 使用 commons-fileupload 来处理 multipart

  • SpringBoot web开发源码深入分析

    目录 一.MVC自动配置 1.默认支持的功能 2.静态资源与首页相关源码解析 3.Rest映射及源码分析 4.请求映射原理 一.MVC自动配置 1.默认支持的功能 Spring Boot为Spring MVC提供了自动配置,默认支持以下功能 ContentNegotiatingViewResolver和BeanNameViewResolver视图解析器 支持静态资源,包括webjars 转换器的自动注册.自定义转换器GenericConverter与格式化 支持http消息转换(请求与响应) M

  • 合成大西瓜开发源码手把手教你运行和部署大西瓜游戏项目(附源码)

    最近合成大西瓜非常火,很多编程爱好者将大西瓜改成了各种版本,非常魔性,哈哈. 如果你也想魔改大西瓜,或者想研究一下项目怎么玩的,下面的教程从下载到游戏项目部署一条龙搞定. 步骤一:下载大西瓜源代码 贴心的我已经将各种版本的代码整理到百度网盘了,大家可以按需下载: 链接: https://pan.baidu.com/s/1DfRdj2s2yGW_XbQhhjSM1w 提取码: 4t3d 步骤二:尝试运行大西瓜游戏项目 下载的源码结构如下图 如果你双击打开 index.html 文件可能卡在98%或

  • SpringBoot+Vue开发之Login校验规则、实现登录和重置事件

    一.Login校验规则 1.校验规则写法可参考Element-ui官网文档 https://element.eleme.cn/#/zh-CN/component/form 2.Login.vue页面  <template> <div class="login_container"> <!-- 登录块 --> <div class="login_box"> <!-- 头像 --> <div class=

  • 一篇文章带你了解SpringBoot Web开发

    目录 SpringBoot Web开发 静态资源 定制首页 thymeleaf模板引擎 1.导入依赖 2.controller书写 源码分析 Thymeleaf语法 基本语法: MVC配置原理 总结 SpringBoot Web开发 springboot到底帮我们配置了什么?我们能不能修改?能修改那些东西?能不能扩展? xxxAutoConfiguration: 向容器中自动配置组件 xxxProperties:自动配置类,装配配置文件中自定义的一些内容 要解决的问题: 导入静态资源 首页 js

  • java编程之基于SpringBoot框架实现扫码登录

    目录 项目简介 实现思路 二次认证的原因 实现步骤 用户访问网页端,选择扫码登录 使用手机扫码,二维码状态改变 手机确认登录 效果演示 完整代码已上传到GitHub. Web端体验地址:http://47.116.72.33/(只剩一个月有效期) apk下载地址:https://github.com/zhangjiwei1221/qrscan/releases/tag/0.0.1. 用户名:非空即可,密码:123456,效果见文末,整体实现如有不妥之处,欢迎交流讨论 实现部分参考二维码扫码登录是

  • 基于SpringBoot生成二维码的几种实现方式

    目录 一.基于Google开发工具包ZXing生成二维码 二.基于开源的Hutool工具生成二维码 本文将基于Spring Boot介绍两种生成二维码的实现方式,一种是基于Google开发工具包,另一种是基于Hutool来实现: 为了方便理解二维码的实际应用场景,举一些例子! (1)进销存系统 想必大家都听说过,其系统中的商品二维码承载了许多重要.核心的关键信息,比如商品编码.商品名称.规格.型号.单位.作用/使用说明等信息:操作者可以借助硬件设备,如“扫码枪”,通过扫描该二维码后将该商品录入到

  • SpringBoot Web详解静态资源规则与定制化处理

    目录 1.相关概念 2.静态资源目录 3.静态资源访问前缀 4.欢迎页支持 5.自定义favicon 6.源码分析 1.相关概念 Spring Boot 默认为我们提供了静态资源处理,使用 WebMvcAutoConfiguration 中的配置各种属性. 建议使用Spring Boot的默认配置方式,如果需要特殊处理的再通过配置文件进行修改. 如果想要自己完全控制WebMVC,就需要在@Configuration注解的配置类上增加@EnableWebMvc, 增加该注解以后WebMvcAuto

  • 教你如何使用google.zxing结合springboot生成二维码功能

    目录 Zxing原生方式 添加依赖 二维码生成工具类 添加Controller 添加测试页面 Hutool的方式 添加依赖 创建QRCodeService 添加Controller 效果测试 我们使用两种方式,去生成二维码,但是其实,二维码的生成基础,都是zxing包,这是Google开源的一个包,第一种是使用原始的zxing方式去实现,第二种是使用hutool来实现,hutool其实也是对于zxing的一个封装,但是封装前后,确实比较简单了. Zxing原生方式 添加依赖 <!-- zxing

  • SpringBoot Web开发之系统任务启动与路径映射和框架整合

    目录 本章概要 启动系统任务 CommandLineRunner ApplicationRunner 整合Servlet与Filter和Listener 路径映射 本章概要 启动系统任务 整合 Servlet.Filter 和 Listener 路径映射 启动系统任务 有一些特殊的任务需要在系统启动时执行,例如配置文件加载,数据库初始化等操作.如果没有使用 Spring Boot ,这些问题可以在 Listener 中解决.Spring Boot 对此提供了两种解决方案 CommandLineR

随机推荐