什么是参数解析器?
我们在使用Spring MVC(包括Spring Boot)的时候,在控制层(Controller)中经常接收参数,Spring MVC基于参数解析器提供了很多注解:
ServletRequestMethodArgumentResolver
和
ServletResponseMethodArgumentResolver处理了
@HttpServletRequest和**@HttpServletResponse**RequestParamMapMethodArgumentResolver
处理了
@RequestParamRequestHeaderMapMethodArgumentResolver
处理了
@RequestHeaderPathVariableMapMethodArgumentResolver
处理了
@PathVariableModelAttributeMethodProcessor
处理了
@ModelAttributeRequestResponseBodyMethodProcessor
处理了
@RequestBody
为什么是自定义参数解析器?
需求:前端发起请求只携带token,后端接收到请求后,在redis中查询到token对应的JSON值,通过JSON.parseObject()将其封装到UserInfo对象中,并修改此UserInfo对象的updateTime字段为当前访问时间,再将此UserInfo对象新增到操作日志的MySQL表中。(此操作在每个update操作都要进行)
不使用自定义参数解析器的解决方案:
@RestController
public class TestController{
@Autowired
private IUserInfoService userInfoService;
@Autowired
private RedisService redisService;
@RequestMappint("/update")
public void update(HttpServletRequest req){
// 获取请求中携带的token
String token = TokenUtil.getToken(req);
// 通过token查询到redis中存储的JSON字符串
String value = redisService.get(token);
// 将JSON字符串转为对象
UserInfo userInfo = JSON.parseObject(value,UserInfo.class);
// 修改参数
userInfo.setUpdateTime(new Date());
// 执行操作
userInfoService.updateById(userInfo);
}
}
优点:实现简单、定制化程度高。
缺点:
1、重复代码量大。
2、强依赖了HttpServletRequest。
3、在Controller中进行了过多的业务操作和对象初始化操作。
在Controller中我们应该是直接使用我们需要的对象,而不是初始化后再使用
现在新的需求是:我们如何在Controller中接收参数的时候就接收应该完整的对象,而不是我们自己去初始化它?
如何实现自定义参数解析器?
使用自定义参数解析器的解决方案:
首先自定义一个在参数处使用的注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface GetUserByToken {
}
创建类MyResolver,实现HandlerMethodArgumentResolver接口,且通过@Component交由Spring管理
@Component
public class MyResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
return false;
}
@Override
public Object resolveArgument(
MethodParameter methodParameter,
ModelAndViewContainer modelAndViewContainer,
NativeWebRequest nativeWebRequest,
WebDataBinderFactory webDataBinderFactory) throws Exception {
return null;
}
}
对自定义参数解析器的supportsParameter进行完善:
@Component
public class MyResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
// 如果目标参数是UserInfo
return methodParameter.getParameterType() == UserInfo.class
&&
// 且必须贴自定义的@GetUserByToken注解(注意此处是hasParameterAnnotation,不是hasMethodAnnotation)
methodParameter.hasParameterAnnotation(GetUserByToken.class);
}
@Override
public Object resolveArgument(
MethodParameter methodParameter,
ModelAndViewContainer modelAndViewContainer,
NativeWebRequest nativeWebRequest,
WebDataBinderFactory webDataBinderFactory) throws Exception {
return null;
}
}
对自定义参数解析器的resolveArgument进行完善:
@Component
public class MyResolver implements HandlerMethodArgumentResolver {
@Autowired
private RedisService redisService;
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
// 如果访问的目标方法的参数是UserInfo
return methodParameter.getParameterType() == UserInfo.class
&&
// 且参数贴了自定义的@GetUserByToken注解(注意此处是hasParameterAnnotation,不是hasMethodAnnotation)
methodParameter.hasParameterAnnotation(GetUserByToken.class);
}
@Override
public Object resolveArgument(
MethodParameter methodParameter,
ModelAndViewContainer modelAndViewContainer,
NativeWebRequest nativeWebRequest,
WebDataBinderFactory webDataBinderFactory) throws Exception {
// 获取请求
HttpServletRequest request = (HttpServletRequest) nativeWebRequest.getNativeRequest();
// 通过请求获取token
String token = TokenUtil.getToken(request);
// 通过token获取userInfo对象
UserInfo userInfo = redisService.getUserByToken(token);
return userInfo;
}
}
在
supportsParameter
中判断是否需要启用解析参数的功能,这里判断形参类型是否为UserInfo类且目标方法的参数必须贴有@GetUserByToken
注解,如果都满足则supportsParameter
方法返回true,走到resolveArgument
方法。在
resolveArgument
中处理UserInfo对象的初始化,并将初始化完成的结果返回。注意:supportsParameter方法和resolveArgument方法之间存在缓存,如果监测到本次请求携带的参数和目标方法请求过的完全一致,则会跳过supportsParameter方法,直接去往resolveArgument方法。
创建一个配置类,在配置类中注册自定义的参数解析器:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private MyResolver myResolver;
// 注册
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(myResolver);
}
}
传统XML配置方式:
<mvc:annotation-driven> <mvc:argument-resolvers> <bean class="com.xx.xxx.xxx.MyResolver"/> </mvc:argument-resolvers> </mvc:annotation-driven>
或
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"> <property name="myResolver"> <bean class="com.xx.xxx.xxx.MyResolver"/> </property> </bean>
此时的TestController就可以改为如下:
@RestController
public class TestController{
@Autowired
private IUserInfoService userInfoService;
@RequestMappint("/update")
public void update(@GetUserByToken UserInfo userInfo){
// 修改参数
userInfo.setUpdateTime(new Date());
// 执行操作
userInfoService.updateById(userInfo);
}
}
参考资料: