springboot实现Rest风格方式和实现请求映射源码入门分析

SpringBoot实现Rest风格方式及其源码分析

请求映射就是我们的@RequestMapping

我们以前使用如下风格表示请求

  1. /getUser表示获取用户
  2. /deleteUser表示删除用户
  3. /editUser表示修改用户
  4. /saveUser表示保存用户

而我们现在则使用Rest风格的请求方式
都是用/user这个请求,然后使用不同的请求方式表示请求不同的资源

  1. GET表示获取用户
  2. DELETE表示删除用户
  3. PUT表示修改用户
  4. POST表示保存用户

下面是我们的测试代码

@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-张三";
    }

但是这里有一个问题,就是我们的表单只能提交get和post的方式,并没有put和delete的方式,那我们怎么才能访问到呢?

1、SpringMVC实现Rest风格

我们再springMVC中的解决方案是,使用一个过滤器叫做HiddenHttpMethodFilter,通过使用一个隐藏域的input来实现,name必须为_method,value就是请求方式
在这里插入图片描述

当然我们在使用这个之前需要配置过滤器
在这里插入图片描述

2、SpringBoot实现Rest风格

springboot实现Rest风格的方式很简单,只需要在配置文件中配置这个属性即可
在这里插入图片描述
然后注意我们的表单只有是post请求,springboot才能将请求转换为对应的请求
在这里插入图片描述

我们测试访问在这里插入图片描述
访问成功

我们来查看源码分析这个配置实现的原理

Rest映射源码分析

首先我们还是看我们的web场景的自动配置组件的核心类
就是这个类WebMvcConfiguration。

我们能看到有一个方法是有关HiddenHttpMethodFilter的
在这里插入图片描述
然后我们点到这个类的源码查看
在这里插入图片描述
这个类继承了我们的HiddenHttpMethodFilter这个类,这个类正式我们springmvc实现时使用的那个过滤器类。
然后我们查看这个HiddenHttpMethodFilter类
可以看到默认的method的参数就是在这里插入图片描述
然后我们再看它的doFilterInternal方法
在这里插入图片描述
我们能够看到
在这里插入图片描述
当请求为post的时候并且这个请求灭有什么错误的时候才会进行请求方式的转化

这就是为什么我们需要将请求方式设置为post并且name为_method才能识别

至于为什么需要配置这个属性,是因为这个组件时按条件装配的
在这里插入图片描述
第一个条件是当容器中没有这个组件的时候自动装配
第二个条件是前缀为spring.mvc.hiddenmethod.filter 并且name属性为enabled,这个enabled的默认值为false。所以默认虽然有这个组件但是并没有启动。所以我们需要设置这个属性

3、Rest映射原理(表单提交使用Rest风格)

首先表单提交会带上_method=PUT

然后发送请求后会被HiddenHttpFilter拦截:然后判断请求是否是post请求,并且判断请求是否正常,满足条件之后,获取到key为_method的请求参数的值,这个值就是我们隐藏的input想要真正提交的请求方式
然后判断这个请求的参数是否有值,如果有值的话,则无论你的这个值是大写还是小写全部转换为大写。然后接着判断他们允许的请求方式中包不包含你的这个请求参数的值,springboot兼容put、delete、patch这三个请求。然后使用了一个包装模式,使用了一个包装模式的requestWrapper重写了原生的request的接口,覆盖了getMethod方法,然后将我们传进来的值覆盖掉原来的值,然后放行的时候放行的是wrapper包装了的request对象

这个原理是根据拦截的源码分析的
在这里插入图片描述
包装类的wrapper源码如下
在这里插入图片描述
Rest使用客户端工具就不是这样了。比如使用postman发请求,这是因为表单只能写get和post这两种请求

Rest风格还能使用这些注解写

  1. @PostMapping
  2. @GetMapping
  3. @PutMapping
  4. @DeleteMapping

这个和上面的写法的作用是相同的

扩展

我们在表单提交的时候可不可以不使用_method而是使用自定义的name

解决方案

因为设置的这个能识别的input的name默认为_method,这时因为他帮我们创建这个HiddenHttpMethodFilter这个组件时默认使用的就是它

我们如果想要自定义这个可被识别的name的话,那么我们只需要自定义一个HiddenHttpMethodFilter组件即可

在这里插入图片描述
通过setMethodParame这个方法来设置我们自定义的识别的name即可

请求映射源码分析

首先我们先分析DispatcherServlet这个类。这个类本质上是一个servlet
我们可以使用idea来看到它的继承关系
在这里插入图片描述
因为它本质上是一个servlet,那么一定覆盖重写了doGet或者doPost方法

按理说它上面继承了HttpServlet的时候这个HttpServletBean应该覆盖重写doGet和doPost方法,但是我们经过查看,它并没有覆盖重写这两个方法
而是HttpServletBean的子类FrameworkServlet覆盖重写了doGet和doPost方法在这里插入图片描述
我们发现无论是调用doGet还是doPost他都会使用这么一个方法processRequest(request, response);

然后我们跟入到这个方法看看他都干了些什么

在这里插入图片描述
然后看到一个doService方法,我们跟进去看这个doService方法是什么
发现是个本类的抽象方法
在这里插入图片描述
然后我们去看它的子类,如何实现的这个方法,也就是DispatcherServlet这个类
有点多直接复制源码,不截图了

@Override
	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
		logRequest(request);

		// Keep a snapshot of the request attributes in case of an include,
		// to be able to restore the original attributes after the include.
		Map<String, Object> attributesSnapshot = null;
		if (WebUtils.isIncludeRequest(request)) {
			attributesSnapshot = new HashMap<>();
			Enumeration<?> attrNames = request.getAttributeNames();
			while (attrNames.hasMoreElements()) {
				String attrName = (String) attrNames.nextElement();
				if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
					attributesSnapshot.put(attrName, request.getAttribute(attrName));
				}
			}
		}

		// Make framework objects available to handlers and view objects.
		request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
		request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
		request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
		request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

		if (this.flashMapManager != null) {
			FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
			if (inputFlashMap != null) {
				request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
			}
			request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
			request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
		}

		RequestPath previousRequestPath = null;
		if (this.parseRequestPath) {
			previousRequestPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
			ServletRequestPathUtils.parseAndCache(request);
		}

		try {
			doDispatch(request, response);
		}
		finally {
			if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
				// Restore the original attribute snapshot, in case of an include.
				if (attributesSnapshot != null) {
					restoreAttributesAfterInclude(request, attributesSnapshot);
				}
			}
			ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
		}
	}

这里面的get呀set什么的方法都是初始化一些东西,然后我们往下看看到了一个doDIspatch方法,其实这个方法才是我们最终要研究的方法

每一个请求进来之后会去走doGet或者doPost方法,然后发现doGet或者doPost方法中是一个processRequest方法,而这个方法是抽象方法,它的子类实现了之后执行doService方法,然后再doService方法中执行doDispatch方法

分析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);

				// Determine handler for the current request.
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.
				String method = request.getMethod();
				boolean isGet = "GET".equals(method);
				if (isGet || "HEAD".equals(method)) {
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}

				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// Actually invoke the handler.
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}

				applyDefaultViewName(processedRequest, mv);
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
				dispatchException = ex;
			}
			catch (Throwable err) {
				// As of 4.3, we're processing Errors thrown from handler methods as well,
				// making them available for @ExceptionHandler methods and other scenarios.
				dispatchException = new NestedServletException("Handler dispatch failed", err);
			}
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Throwable err) {
			triggerAfterCompletion(processedRequest, response, mappedHandler,
					new NestedServletException("Handler processing failed", err));
		}
		finally {
			if (asyncManager.isConcurrentHandlingStarted()) {
				// Instead of postHandle and afterCompletion
				if (mappedHandler != null) {
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				}
			}
			else {
				// Clean up any resources used by a multipart request.
				if (multipartRequestParsed) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
使用debug发下
在这里插入图片描述
执行图中这一行请求后handler直接锁定了我们的要执行的对应的controller,所以我们给这个方法打一个断点进行查看,查看他是怎么知道我们要请求的是哪一个handler

然后进入到这个断点的方法
发现有一个handlerMapping
在这里插入图片描述
而且这个handlerMapper有5个
然后我们看看都是哪五个
在这里插入图片描述
这里面的WelcomePageHanderMapping这个是我们之前说过的

我们要关注的自然是这个
在这里插入图片描述
其实,在getHandler这个方法中,它会使用增强for循环来遍历这5个HandlerMapping,然后分别看这个五个HandlerMapping哪一个可以处理我们访问的这个请求
在这里插入图片描述
它就是使用这个方法来判断我们的请求到底映射的是哪一个controller方法
其实是因为这个RequestHandlerMapping中包含了我们每一个controller和controler对应的请求,我们就可以根据这个请求找到这个对应的controller

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值