通过HandlerMethodArgumentResolver自定义参数解析器

本文介绍了如何在SpringMVC中使用自定义参数解析器来简化Controller层的代码,减少重复性工作。通过创建一个@GetUserByToken注解和对应的MyResolver类,实现了从请求中自动获取并初始化UserInfo对象,降低了Controller层的业务耦合。最后,展示了如何在配置类中注册自定义参数解析器,使得在更新操作中可以直接使用初始化好的UserInfo对象。

什么是参数解析器?

我们在使用Spring MVC(包括Spring Boot)的时候,在控制层(Controller)中经常接收参数,Spring MVC基于参数解析器提供了很多注解:

ServletRequestMethodArgumentResolverServletResponseMethodArgumentResolver 处理了@HttpServletRequest和**@HttpServletResponse**

RequestParamMapMethodArgumentResolver处理了@RequestParam

RequestHeaderMapMethodArgumentResolver处理了@RequestHeader

PathVariableMapMethodArgumentResolver处理了@PathVariable

ModelAttributeMethodProcessor处理了@ModelAttribute

RequestResponseBodyMethodProcessor处理了@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);
    }
}

参考资料:

利用SpringMvc-HandlerMethodArgumentResolver 实现自定义参数解析器

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

千早爱音Official

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值