IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 前端json+@RequestBody的参数接收,不用ViewObject模型接收如何处理 -> 正文阅读

[Java知识库]前端json+@RequestBody的参数接收,不用ViewObject模型接收如何处理

痛点

@RequestBody接收的参数只能是一个对象,可以通过创建ViewObject对象来接收,但是对于各种不确定参数就需要非常多的ViewObject。
如果不用ViewObject,而是直接用原生Map<String,Object> map接收,一样需要一个个取出来:

    @PostMapping("login")
    public Object login(@RequestBody Map<String,Object> map) {
        String account = (String) map.get("account");
        String pwd = (String) map.get("pwd");
        ...
    }

目标方案

最终想要的效果是即使前端传json,后台先解析出来,再塞到方法参数中,这个过程是隐式完成,最终业务代码就可以非常简洁:

    @PostMapping("login")
    public Object login(String account,String pwd) {
        String account = (String) map.get("account");
        String pwd = (String) map.get("pwd");
        ...
    }

个别问题Q&A

Q:如何区分POST的json和form形式的参数
A:先判断前端传的是json(通过contentType或者request.getInputStream),确认json后,解析json,然后赋值给方法的参数。同时,可以使用注解来指定(或排除)某些参数使用(或不使用)json对象赋值。


想要实现以上效果, 需要理解springmvc是哪一步获取请求体数据封装成@RequestBody参数对象,
以及其他表单参数是如何赋值,所以:

第一步:理解springmvc参数接收原理

因为springmvc本身是个庞大的架构体系,可以预知它对参数值的封装这个过程是通过某种设计模式完成的,而不是按代码顺序一步步写下来,这是读spring源码的一个方向。

从整个应用服务层面来看,请求体数据先进入tomcat(或其他),被封装形成基础request请求对象,然后经过filter过滤器,经过interceptor拦截器等,进入统一的DispatcherServlet(spring默认servlet),然后在DispatcherServlet中根据HandlerMapping找到匹配的Handler(也就是Controller中某一个方法),最后request请求对象交由匹配的这个Handler处理。

DispatcherServlet

public class DispatcherServlet extends FrameworkServlet {
    --1、servlet的doService方法
    @Override
	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
		
		try {
			doDispatch(request, response);
		}
    	....
	}
    
    --2、进入doDispatch方法
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
			try {
				HttpServletRequest processedRequest = request;
				
				--3、 为当前request匹配对应的Handler
				HandlerExecutionChain mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					return;
				}

				--4、 拿到HandlerAdapter 适配器
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				--5、 先经过多个拦截器
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}
				
				--6、 实际调用handle处理(--7、 进入RequestMappingHandlerAdapter.handle()方法)
				ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				...
				applyDefaultViewName(processedRequest, mv);
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
				Exception  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.
				Exception  dispatchException = new NestedServletException("Handler dispatch failed", err);
			}
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		
	}
	
}

RequestMappingHandlerAdapter

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
		implements BeanFactoryAware, InitializingBean {
		@Override

	--7RequestMappingHandlerAdapter.handle()方法实际调用handleInternal()如下:
	protected ModelAndView handleInternal(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

		checkRequest(request);
		...
		--8、 实际调用invokeHandlerMethod()
		ModelAndView mav = invokeHandlerMethod(request, response, handlerMethod);
		...
		return mav;
	}

	--8、 实际调用invokeHandlerMethod()
	protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

		ServletWebRequest webRequest = new ServletWebRequest(request, response);
		try {
			WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
			ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

			--9、 创建ServletInvocableHandlerMethod,并调用invokeAndHandle()方法
			ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
			
			
			//配置argumentResolvers和returnValueHandlers
			if (this.argumentResolvers != null) {
				invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
			}
			if (this.returnValueHandlers != null) {
				invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
			}
			invocableMethod.setDataBinderFactory(binderFactory);
			invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

			ModelAndViewContainer mavContainer = new ModelAndViewContainer();
			mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
			modelFactory.initModel(webRequest, mavContainer, invocableMethod);
			mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

			...

			--9、 调用ServletInvocableHandlerMethod.invokeAndHandle()方法
			invocableMethod.invokeAndHandle(webRequest, mavContainer);

			return getModelAndView(mavContainer, modelFactory, webRequest);
		}
		finally {
			webRequest.requestCompleted();
		}
	}
}

ServletInvocableHandlerMethod

public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {

	--9、 调用ServletInvocableHandlerMethod.invokeAndHandle()方法
	public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {

		--10、 真正的调用Controller方法并获取返回值
		Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
		setResponseStatus(webRequest);

		...
		
		try {
			this.returnValueHandlers.handleReturnValue(
					returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
		}
	}

}

小结:

上面三个类的代码,从DispatcherServlet找到真正的RequestMappingHandlerAdapter直到构造ServletInvocableHandlerMethod来最终调用到Controller方法,这个过程都还没涉及到方法入参的封装、方法调用。直到ServletInvocableHandlerMethod.invokeForRequest()才解析request参数封装进方法参数并调用。


	public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {

		Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);

		return doInvoke(args);
	}
	

从invokeForRequest方法可见,它完成了关键的两步:

  1. getMethodArgumentValues获取方法的参数
  2. 用方法参数去调用方法

getMethodArgumentValues方法如下:

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
		//获取该方法有哪些参数类型
		MethodParameter[] parameters = getMethodParameters();
		//args 用来存从request获取到的参数值
		Object[] args = new Object[parameters.length];
		
		for (int i = 0; i < parameters.length; i++) {
		
			MethodParameter parameter = parameters[i];
			
			//是否支持该参数类型
			if (!this.resolvers.supportsParameter(parameter)) {
				throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
			}
			
			try {
				//使用resolvers去解析获取参数
				//实际这里拿到的resolvers是RequestResponseBodyMethodProcessor
				args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
			}
		}
		
		return args;
	}

getMethodArgumentValues方法通过RequestResponseBodyMethodProcessor(implements HandlerMethodArgumentResolver)来解析request中的请求体或者请求参数,并封装成Controller方法的参数返回(比如请求体中的json就会匹配到MappingJackson2HttpMessageConverter转换器来解析,对于如何解析请求参数这个是转换器内部实现,不在这里展开说明)。


第二步:实现思路

再来看doDispatch方法,如下:

 		protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
				...

				--5、 先经过多个拦截器
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}
				
				--6、 实际调用handle处理(--7、 进入RequestMappingHandlerAdapter.handle()方法)
				ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

	
				mappedHandler.applyPostHandle(processedRequest, response, mv);

		
	}

ha.handle()做的事情包括:

  1. 通过getMethodParameters()拿到所有参数类型,然后通过特定的MappingJackson2HttpMessageConverter解析封装每一个参数;
  2. 最后返回的参数Object[] argsdoInvoke(args)调用Controller方法。

也就是说在doDispatch方法执行拦截器的前拦截applyPreHandle时还没进入ha.handle()也就还没从request解析出参数,执行后拦截applyPostHandle时方法已经调用完毕。

我们的目标是login(String account,String pwd)也可以接收到@RequestBody的请求体数据,我们需要修改Controller方法的参数值,所有无法通过过滤器或者拦截器来解析json注入参数!

方案一

使用AOP拦截Controller方法,也就是在spring解析出参数调用doInvoke(args)的时候拦截,这个时侯,手动解析出json对象(默认为Map对象),通过参数名称和map对象的key值比对,生产参数集合,替换掉doInvoke(args)中的原来参数。

方案一的实现代码

package com;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.aop.ProxyMethodInvocation;
import org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;


/**
 * 拦截PostMapping方法,如果没检测到@RequestBody注解,则反序列化请求体json为map对象
 */
@Aspect
@Component
public class PostMethodAop {
    private final HandlerMethodArgumentResolverComposite argumentResolvers;//参数解析器

    private final Field proxyMethodInvocationField;
    private final MethodParameter methodParameter;

    //方法名称指定为”method“
    private static void method(@RequestBody(required = false) Object params) {
    }

    @Autowired
    public PostMethodAop(@Qualifier("requestMappingHandlerAdapter") RequestMappingHandlerAdapter requestMappingHandlerAdapter) throws NoSuchFieldException, IllegalAccessException {
        Field argumentResolversField = RequestMappingHandlerAdapter.class.getDeclaredField("argumentResolvers");
        argumentResolversField.setAccessible(true);
        argumentResolvers = (HandlerMethodArgumentResolverComposite) argumentResolversField.get(requestMappingHandlerAdapter);
        argumentResolversField.setAccessible(false);
        //
        try {
            Method method = PostMethodAop.class.getDeclaredMethod("method", Object.class);
            methodParameter = new MethodParameter(method, 0);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
        //
        Field field = null;
        for (Field declaredField : MethodInvocationProceedingJoinPoint.class.getDeclaredFields()) {
            if (ProxyMethodInvocation.class.equals(declaredField.getType())) {
                field = declaredField;
                break;
            }
        }
        proxyMethodInvocationField = field;
        proxyMethodInvocationField.setAccessible(true);
    }

    @Pointcut(value = "@annotation(org.springframework.web.bind.annotation.PostMapping)")
    public void postMapping() {
    }

    @Before("postMapping()")
    public void doBefore(JoinPoint jp) throws Throwable {
        MethodInvocationProceedingJoinPoint joinPoint = (MethodInvocationProceedingJoinPoint) jp;
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        //不是post请求
        if (!HttpMethod.POST.matches(request.getMethod())) {
            return;
        }
        //带有@RequestBody注解(已经解析json),不再处理
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        for (Annotation[] annotations : signature.getMethod().getParameterAnnotations()) {
            for (Annotation annotation : annotations) {
                if (RequestBody.class.equals(annotation.annotationType())) {
                    return;
                }
            }
        }

        String[] parameterNames = signature.getParameterNames();
        if (parameterNames.length == 0) {
            return;//空参数方法,无需装配
        }
        //反序列化前端json数据
        Map<String, Object> jsonBody = getRequestBody(request);
        if (jsonBody == null) {
            return;//无数据
        }
        //构造参数集合
        Object[] args = new Object[parameterNames.length];
        for (int i = 0; i < parameterNames.length; i++) {
            if (jsonBody.containsKey(parameterNames[i])) {
                args[i] = jsonBody.get(parameterNames[i]);
            } else if (joinPoint.getArgs()[i] != null) {
                args[i] = joinPoint.getArgs()[i];
            }
        }
        //替换原有的参数集合
        ProxyMethodInvocation methodInvocation = (ProxyMethodInvocation) proxyMethodInvocationField.get(joinPoint);
        methodInvocation.setArguments(args);
    }

    private Map<String, Object> getRequestBody(HttpServletRequest request) {
        if (!argumentResolvers.supportsParameter(methodParameter)) {
            return null;
        }

        Object jsonBody;
        try {
            jsonBody = argumentResolvers.resolveArgument(methodParameter, null, new ServletWebRequest(request), null);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        if (jsonBody instanceof Map) {
            return (Map<String, Object>) jsonBody;
        }

        return null;
    }

}

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-02-26 11:16:56  更:2022-02-26 11:18:07 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 12:04:18-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码