Python 官方文档:入门教程 => 点击学习
目录springMVC对自定义controller入参预处理HandlerMethodArgumentResolver接口说明初学者一般喜欢类似下面的代码我们需要定义如下的一个参数分
在初学springmvc框架时,我就一直有一个疑问,为什么controller方法上竟然可以放这么多的参数,而且都能得到想要的对象,比如httpservletRequest或HttpServletResponse,各种注解@RequestParam、@RequestHeader、@RequestBody、@PathVariable、@ModelAttribute等。相信很多初学者都曾经感慨过。
这篇文章就是讲解处理这方面内容的
我们可以模仿springmvc的源码,实现一些我们自己的实现类,而方便我们的代码开发。
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.toString());
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配置:
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean class="com.demo.mvc.component.PersonArgumentResolver"/>
</mvc:argument-resolvers>
</mvc:annotation-driven>
或
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="customArgumentResolvers">
<bean class="com.demo.mvc.component.PersonArgumentResolver"/>
</property>
</bean>
Spring Boot java代码配置:
public class WebConfig extends WebMvcConfigurerAdapter{
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new CustomeArgumentResolver());
}
}
一个通用Controller。大多数情况下不再需要编写任何Controller层代码,将开发人员的关注点全部集中到Service层。
平时在进行传统的MVC开发时,为了完成某个特定的功能,我们通常需要同时编写Controller,Service,Dao层的代码。代码模式大概是这样的。
这里只贴出Controller层的代码,Service层也不是本次我们的关注点。
// ----------------------------------------- Controller层
@RestController
@RequestMapping("/a")
public class AController {
@Resource(name = "aService")
private AService aService;
@PostMapping(value = "/a")
public ResponseBean<String> a(HttpServletRequest request, HttpServletResponse response) {
final String name = WebUtils.findParameterValue(request, "name");
return ResponseBean.of(aService.invoke(name));
}
}
// ----------------------------------------- 前端访问路径
// {{rootPath}}/a/a.do
只要有过几个月Java web开发经验的,应该对这样的代码非常熟悉,熟悉到恶心。我们稍微注意下就会发现:上面的Controller代码中,大致做了如下事情:
收集前端传递过来的参数。
将第一步收集来的参数传递给相应的Service层的某个方法执行。
将Service层执行后的结果使用Controller层特有的ResponseBean进行封装后返回给前台。
所以我们在排除掉少有的特殊情况之后,就会发现在一般情况下这个所谓的Controller层的存在感实在有点稀薄。因此本文尝试去除掉这部分枯燥的重复性代码。
直接上代码。talk is cheap, show me the code。
// 这里之所以是 /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<String, String> parameterMap = htmlUtils.getParameterMap(request);
final Map<String, Object> paramsCopy = preDealOutParam(parameterMap);
// 获取本次的调度服务名和相应的方法名
//final List<String> serviceAndMethod = parseServiceAndMethod(request);
//final String serviceName = serviceAndMethod.get(0) + "Service";
//final String serivceMethodName = serviceAndMethod.get(1);
// 直接使用Spring3.x新加入的@PathVariable注解; 代替上面的自定义操作
serviceName = serviceName + "Service";
final 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);
}
private List<String> parseServiceAndMethod(HttpServletRequest request) {
// /lq/thirdService/queryTaskList.do 解析出 [ thirdService, queryTaskList ]
final String serviceAndMethod = StringUtil.subBefore(request.getServletPath(), ".", false);
List<String> split = StringUtil.split(serviceAndMethod, '/', true, true);
return split.subList(1, split.size());
}
// 将传递来的jsON字符串转换为相应的Map, List等
private Map<String, Object> preDealOutParam(final Map<String, String> parameterMap) {
final Map<String, Object> outParams = new HashMap<String, Object>(parameterMap.size());
for (Map.Entry<String, String> entry : parameterMap.entrySet()) {
outParams.put(entry.geTKEy(), entry.getValue());
}
for (Map.Entry<String, Object> 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();
}
}
到此为止,Controller层的代码就算是完成了。之后的开发工作中,在绝大多数情况下,我们将不再需要编写任何Controller层的代码。只要遵循如下的约定,前端将会直接调取到Service层的相应方法,并获取到约定格式的响应值。
对于有额外需要的特殊Controller,可以完全按照之前的Controller层写法。没有任何额外需要注意的地方。
上面的Service层的方法签名中,其参数使用的是固定的Map<String,Object> param。对Map和Bean的争论由来已久,经久不衰,这里不搅和这趟浑水。
对于希望使用Bean作为方法参数的,可以参考SpringMVC中对Controller层方法调用的实现,来达到想要的效果。具体的实现就不在这里献丑了,有兴趣的同学可以参考下源码ServletInvocableHandlerMethod.invokeAndHandle。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。
--结束END--
本文标题: SpringMVC对自定义controller入参预处理方式
本文链接: https://lsjlt.com/news/135633.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
2024-03-01
2024-03-01
2024-03-01
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0