SpringMVC对自定义controller入参预处理方式

网友投稿 468 2022-09-27


SpringMVC对自定义controller入参预处理方式

目录Spring Mvc对自定义controller入参预处理HandlerMethodArgumentResolver接口说明初学者一般喜欢类似下面的代码我们需要定义如下的一个参数分解器注册自定义分解器SpringMVC技巧之通用Controller1. 前言2. 问题3. 解决方案4. 使用5. 特殊需求6. 完善

Spring Mvc对自定义controller入参预处理

在初学springmvc框架时,我就一直有一个疑问,为什么controller方法上竟然可以放这么多的参数,而且都能得到想要的对象,比如HttpServletRequest或HttpServletResponse,各种注解@RequestParam、@RequestHeader、@RequestBody、@PathVariable、@ModelAttribute等。相信很多初学者都曾经感慨过。

这篇文章就是讲解处理这方面内容的

我们可以模仿springmvc的源码,实现一些我们自己的实现类,而方便我们的代码开发。

HandlerMethodArgumentResolver接口说明

package org.springframework.web.method.support;

import org.springframework.core.MethodParameter;

import org.springframework.web.bind.WebDataBinder;

import org.springframework.web.bind.support.WebDataBinderFactory;

import org.springframework.web.context.request.NativeWebRequest;

public interface HandlerMethodArgumentResolver {

//用于判定是否需要处理该参数分解,返回true为需要,并会去调用下面的方法resolveArgument。

boolean supportsParameter(MethodParameter parameter);

//真正用于处理参数分解的方法,返回的Object就是controller方法上的形参对象。

Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,

NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;

}

示例

本示例显示如何 优雅地将传入的信息转化成自定义的实体传入controller方法。

post 数据:

first_name = Bill

last_name = Gates

初学者一般喜欢类似下面的代码

package com.demo.controller;

import javax.servlet.http.HttpServletRequest;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;

import org.springframework.web.bind.annotation.RestController;

import com.demo.domain.Person;

import com.demo.mvc.annotation.MultiPerson;

import lombok.extern.slf4j.Slf4j;

@Slf4j

@Controller

@RequestMapping("demo1")

public class HandlerMethodArgumentResolverDemoController {

@ResponseBody

@RequestMapping(method = RequestMethod.POST)

public String addPerson(HttpServletRequest request) {

String firstName = request.getParameter("first_name");

String lastName = request.getParameter("last_name");

Person person = new Person(firstName, lastName);

log.info(person.toStringMyWUq());

return person.toString();

}

}

这样的代码强依赖了javax.servlet-api的HttpServletRequest对象,并且把初始化Person对象这“活儿”加塞给了controller。代码显得累赘不优雅。在controller里我只想使用person而不想组装person,想要类似下面的代码:

@RequestMapping(method = RequestMethod.POST)

public String addPerson(Person person) {

log.info(person.toString());

return person.toString();

}

直接在形参列表中获得person。那么这该如实现呢?

我们需要定义如下的一个参数分解器

package com.demo.mvc.component;

import org.springframework.core.MethodParameter;

import org.springframework.web.bind.support.WebDataBinderFactory;

import org.springframework.web.context.request.NativeWebRequest;

import org.springframework.web.method.support.HandlerMethodArgumentResolver;

import org.springframework.web.method.support.ModelAndViewContainer;

import com.demo.domain.Person;

public class PersonArgumentResolver implements HandlerMethodArgumentResolver {

@Override

public boolean supportsParameter(MethodParameter parameter) {

return parameter.getParameterType().equals(Person.class);

}

@Override

public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

String firstName = webRequest.getParameter("first_name");

String lastName = webRequest.getParameter("last_name");

return new Person(firstName, lastName);

}

}

在supportsParameter中判断是否需要启用分解功能,这里判断形参类型是否为Person类,也就是说当形参遇到Person类时始终会执行该分解流程resolveArgument,也可以基于paramter上是否有我们指定的自定义注解判断是否需要流程分解。在resolveArgument中处理person的初始化工作。

注册自定义分解器

传统XML配置:

spring boot java代码配置:

public class WebConfig extends WebMvcConfigurerAdapter{

@Override

public void addArgumentResolvers(List argumentResolvers) {

argumentResolvers.add(new CustomeArgumentResolver());

}

}

SpringMVC技巧之通用Controller

一个通用Controller。大多数情况下不再需要编写任何Controller层代码,将开发人员的关注点全部集中到Service层。

1. 前言

平时在进行传统的MVC开发时,为了完成某个特定的功能,我们通常需要同时编写Controller,Service,Dao层的代码。代码模式大概是这样的。

这里只贴出Controller层的代码,Service层也不是本次我们的关注点。

// ----------------------------------------- Controller层

@RestController

@RequestMapping("/a")

public class AController {

@Resource(name = "aService")

private AService aService;

@PostMapping(value = "/a")

public ResponseBean a(HttpServletRequest request, HttpServletResponse response) {

final String name = WebUtils.findParameterValue(request, "name");

return ResponseBean.of(aService.invoke(name));

}

}

// ----------------------------------------- 前端访问路径

// {{rootPath}}/a/a.do

2. 问题

只要有过几个月Java Web开发经验的,应该对这样的代码非常熟悉,熟悉到恶心。我们稍微注意下就会发现:上面的Controller代码中,大致做了如下事情:

收集前端传递过来的参数。

将第一步收集来的参数传递给相应的Service层的某个方法执行。

将Service层执行后的结果使用Controller层特有的ResponseBean进行封装后返回给前台。

所以我们在排除掉少有的特殊情况之后,就会发现在一般情况下这个所谓的Controller层的存在感实在有点稀薄。因此本文尝试去除掉这部分枯燥的重复性代码。

3. 解决方案

直接上代码。talk is cheap, show me the code。

// 这里之所以是 /lq , 而不是 /* ; 是因为 AntPathMatcher.combine 方法中进行合并时的处理, 导致 前一个 /* 丢失

/**

*

直接以前端传递来的Serivce名+方法名去调用Service层的同名方法; Controller层不再需要写任何代码

*

例子

*

* 前端: /lq/thirdService/queryTaskList.do

* Service层相应的方法签名: Object queryTaskList(Map parameterMap)

* 相应的Service注册到Spring容器中的id : thirdServiceService

*

* @author LQ

*

*/

@RestController

@RequestMapping("/lq")

public class CommonController {

private static final Logger LOG = LoggerFactory.getLogger(ThirdServiceController.class);

@PostMapping(value = "/{serviceName}/{serviceMethodName}")

public void common(@PathVariable String serviceName, @PathVariable final String serviceMethodName, HttpServletRequest request, HttpServletResponse response) {

// 收集前台传递来的参数, 并作预处理

final Map parameterMap = HtmlUtils.getParameterMap(request);

final Map paramsCopy = preDealOutParam(parameterMap);

// 获取本次的调度服务名和相应的方法名

//final List serviceAndMethod = parseServiceAndMethod(request);

//final String serviceName = serviceAndMethod.get(0) + "Service";

//final String serivceMethodName = serviceAndMethod.get(1);

// 直接使用Spring3.x新加入的@PathVariable注解; 代替上面的自定义操作

serviceName = serviceName + "Service";

finhttp://al String fullServiceMethodName = StringUtil.format("{}.{}", serviceName, serivceMethodName);

// 输出日志, 方便回溯

LOG.debug("### current request method is [ {} ] , parameters is [ {} ]", fullServiceMethodName, parameterMap);

// 获取Spring中注册的Service Bean

final Object serviceBean = SpringBeanFactory.getBean(serviceName);

Object rv;

try {

// 调用Service层的方法

rv = ReflectUtil.invoke(serviceBean, serivceMethodName, paramsCopy);

// 若用户返回一个主动构建的FriendlyException

if (rv instanceof FriendlyException) {

rv = handlerException(fullServiceMethodName, (FriendlyException) rv);

} else {

rv = returnVal(rv);

}

} catch (Exception e) {

rv = handlerException(fullServiceMethodName, e);

}

LOG.debug("### current request method [ {} ] has dealed, rv is [ {} ]", fullServiceMethodName, rv);

HtmlUtils.writerjson(response, rv);

}

/**

* 解析出Service和相应的方法名

* @param request

* @return

*/

private List parseServiceAndMethod(HttpServletRequest request) {

// /lq/thirdService/queryTaskList.do 解析出 [ thirdService, queryTaskList ]

final String serviceAndMethod = StringUtil.subBefore(request.getServletPath(), ".", false);

List split = StringUtil.split(serviceAndMethod, '/', true, true);

return split.subList(1, split.size());

}

// 将传递来的JSON字符串转换为相应的Map, List等

private Map preDealOutParam(final Map parameterMap) {

final Map outParams = new HashMap(parameterMap.size());

for (Map.Entry entry : parameterMap.entrySet()) {

outParams.put(entry.getKey(), entry.getValue());

}

for (Map.Entry entry : outParams.entrySet()) {

final String value = (String) entry.getValue();

if (StringUtil.isEmpty(value)) {

entry.setValue("");

continue;

}

Object parsedObj = JSONUtil.tryParse(value);

// 不是JSON字符串格式

if (null == parsedObj) {

continue;

}

entry.setValue(parsedObj);

}

return outParams;

}

// 构建成功执行后的返回值

private Object returnVal(Object data) {

return MapUtil.newMapBuilder().put("data", data).put("status", 200).put("msg", "success").build();

}

// 构建执行失败后的返回值

private Object handlerException(String distributeMethod, Throwable e) {

final String logInfo = StringUtil.format("[ {} ] fail", distributeMethod);

LOG.error(logInfo, ExceptionUtil.getRootCause(e));

return MapUtil.newMapBuilder().put("data", "").put("status", 500)

.put("msg", ExceptionUtil.getRootCause(e).getMessage()).build();

}

}

4. 使用

到此为止,Controller层的代码就算是完成了。之后的开发工作中,在绝大多数情况下,我们将不再需要编写任何Controller层的代码。只要遵循如下的约定,前端将会直接调取到Service层的相应方法,并获取到约定格式的响应值。

前端请求路径 : {{rootPath}}/lq/serviceName/serviceMethodName.do

{{rootPath}} : 访问地址的根路径

lq :自定义的固定名称,用于满足SpringMVC的映射规则。

serviceName : 用于获取Spring容器中的Service Bean。这里的规则是 该名称后附加上Service字符来作为Bean Id来从Spring容器中获取相应 Service Bean。

serviceMethodName : 第三步中找到的Service Bean中的名为serviceMethodName的方法。签名为Object serviceMethodName(Map param)。

5. 特殊需求

对于有额外需要的特殊Controller,可以完全按照之前的Controller层写法。没有任何额外需要注意的地方。

6. 完善

上面的Service层的方法签名中,其参数使用的是固定的Map param。对Map和Bean的争论由来已久,经久不衰,这里不搅和这趟浑水。

对于希望使用Bean作为方法参数的,可以参考SpringMVC中对Controller层方法调用的实现,来达到想要的效果。具体的实现就不在这里献丑了,有兴趣的同学可以参考下源码ServletInvocableHandlerMethod.invokeAndHandle。


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:聊聊Cisco 3650交换机系统升级那点事儿!(聊聊此生)
下一篇:#哆啦A梦
相关文章

 发表评论

暂时没有评论,来抢沙发吧~