HandlerMethodArgumentResolver(四):自定参数解析器处理特定场景需求,介绍PropertyNamingStrategy的使用【享学Spring MVC】(上)

简介: HandlerMethodArgumentResolver(四):自定参数解析器处理特定场景需求,介绍PropertyNamingStrategy的使用【享学Spring MVC】(上)

前言


前面通过三篇文章介绍了HandlerMethodArgumentResolver这个参数解析器以及它的所有内置实现,相信看过的小伙伴对它的加载、初始化、处理原理等等已能够做到了心中有数了。

Spring MVC内置注册了灰常多的处理器给我们的使用,不客气说几乎100%的case我们都是足够用了的。但既然我们已经理解到了HandlerMethodArgumentResolver它深层的作用原理,那么本文就通过自定义参数处理器,来做到屏蔽(隔离)基础实现、更高效的编写业务编码(提效是本文的关注点)。


使用场景

关于它的应用场景可以非常多,本文我总结出最为常见、好理解的两个应用场景作为举例说明:


  1. 获取当前登陆人(当然用户)的基本信息
  2. 调整(兼容)数据结构


场景一:


在Controller层获取当前登陆人的基本信息(如id、名字…)是一个必须的、频繁的功能需求,这个时候如果团队内没有提供相关封装好的方法来调用,你便可看到大量的、重复的获取当前用户的代码,这就是各位经常吐槽的垃圾代码~


一般团队的做法是:提供BaseController,在基类里面提供获取当前用户的功能方法,这样业务控制器Controller只需要继承它就有这能力了,使用起来确实也还挺方便的。但是是否还思考过这种通过继承的方式它是有弊端的–>我只想获取当前登陆人我就得继承一个父类?这是不是设计太重了点?更坏的情况是如果此时我已经有父类了呢?


面对我提出的问题,本文针对性的提供一个新的、更加轻量的解决思路:自定义HandlerMethodArgumentResolver来实现获取当前登录用户的解决方案。实施步骤如下:


1、自定义一个参数注解(注解并不是100%必须的,可完全根据类型来决策)


/**
 * 用于获取当前登陆人信息的注解,配合自定义的参数处理器使用
 *
 * @see CurrUserArgumentResolver
 */
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CurrUser {
}
// 待封装的Vo
@Getter
@Setter
@ToString
public class CurrUserVo {
    private Long id;
    private String name;
}


2、自定义参数解析器CurrUserArgumentResolver并完成注册

public class CurrUserArgumentResolver implements HandlerMethodArgumentResolver {
    // 只有标注有CurrUser注解,并且数据类型是CurrUserVo/Map/Object的才给与处理
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        CurrUser ann = parameter.getParameterAnnotation(CurrUser.class);
        Class<?> parameterType = parameter.getParameterType();
        return (ann != null &&
                (CurrUserVo.class.isAssignableFrom(parameterType)
                        || Map.class.isAssignableFrom(parameterType)
                        || Object.class.isAssignableFrom(parameterType)));
    }
    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer container, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        // 从请求头中拿到token
        String token = request.getHeader("Authorization");
        if (StringUtils.isEmpty(token)) {
            return null; // 此处不建议做异常处理,因为校验token的事不应该属于它来做,别好管闲事
        }
        // 此处作为测试:new一个处理(写死的)
        CurrUserVo userVo = new CurrUserVo();
        userVo.setId(1L);
        userVo.setName("fsx");
        // 判断参数类型进行返回
        Class<?> parameterType = parameter.getParameterType();
        if (Map.class.isAssignableFrom(parameterType)) {
            Map<String, Object> map = new HashMap<>();
            BeanUtils.copyProperties(userVo, map);
            return map;
        } else {
            return userVo;
        }
    }
}
// 注册进Spring组件内
@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(new CurrUserArgumentResolver());
    }
}


3、书写测试例子


@Controller
@RequestMapping
public class HelloController {
    @ResponseBody
    @GetMapping("/test/curruser")
    public Object testCurrUser(@CurrUser CurrUserVo currUser) {
        return currUser;
    }
    @ResponseBody
    @GetMapping("/test/curruser/map")
    public Object testCurrUserMap(@CurrUser Map<String,Object> currUser) {
        return currUser;
    }
    @ResponseBody
    @GetMapping("/test/curruser/object")
    public Object testCurrUserObject(@CurrUser Object currUser) {
        return currUser;
    }
}


请求:/test/curruser或者/test/curruser/object 这两个请求得到的答案是一致的且符合预期,结果如下截图:


image.png


但是,但是,但是若访问/test/curruser/map,它的结果如下:


image.png



so参数类型是Map类型,自定义的参数解析器CurrUserArgumentResolver并没有生效,为什么呢???

带着这个疑问,接下来我说说对此非常重要的使用细节:


如何使用Spring容器内的Bean?


在本例中,为了方便,我在CurrUserArgumentResolver里写死的自己new的一个CurrUserVo作为返回。实际应用场景中,此部分肯定是需要根据token去访问DB/Redis的,因此就需要使用到Spring容器内的Bean。


有的小伙伴就想当然了,在本例上直接使用@Autowired HelloService helloService;来使用,经测试发现这是注入不进来的,helloService值为null。那么本文就教你正确的使用姿势:


  1. 姿势一:把自定义的参数解析器也放进容器

     这是一种十分快捷、见效的解决方案。

@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
    @Bean
    public CurrUserArgumentResolver currUserArgumentResolver(){
        return new CurrUserArgumentResolver();
    }
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(currUserArgumentResolver());
    }


这样,你在CurrUserArgumentResolver就可以顺理成章的注入想要的组件了,形如这样:


public class CurrUserArgumentResolver implements HandlerMethodArgumentResolver {
    @Autowired
    HelloService helloService;
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    ...
}


这种方案的优点是:在Spring容器内它几乎能解决大部分类似问题,在组件不是很多的情况下,推荐新手使用,因为无需过多的理解Spring内部机制便可轻松使用。


  1. 姿势二:借助AutowireCapableBeanFactory给对象赋能
    本着"减轻"Spring容器"负担"的目的,"手动"精细化控制Spring内的Bean组件。像本文的这种解析器其实是完全没必要放进容器内的,需要什么组件让容器帮你完成注入即可,自己本文就没必要放进去喽:
@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
    @Autowired
    private ApplicationContext applicationContext;
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        CurrUserArgumentResolver resolver = new CurrUserArgumentResolver();
        // 利用工厂给容器外的对象注入所需组件
        applicationContext.getAutowireCapableBeanFactory().autowireBean(resolver);
        argumentResolvers.add(resolver);
    }
}

本姿势的技巧是利用了AutowireCapableBeanFactory巧妙完成了给外部对象赋能,从而即使自己并不是容器内的Bean,也能自由注入、使用容器内Bean的能力(同样可以随意使用@Autowired注解了~)。

这种方式是侵入性最弱的,是我推荐的方式。当然这需要你对Spring容器有一定的了解才能运用自如,做到心中有数才行,否则不建议你使用~


可以和内置的一些注解/类型一起使用吗?(参数类型是Map类型?)

相关文章
|
25天前
|
Java 关系型数据库 数据库
京东面试:聊聊Spring事务?Spring事务的10种失效场景?加入型传播和嵌套型传播有什么区别?
45岁老架构师尼恩分享了Spring事务的核心知识点,包括事务的两种管理方式(编程式和声明式)、@Transactional注解的五大属性(transactionManager、propagation、isolation、timeout、readOnly、rollbackFor)、事务的七种传播行为、事务隔离级别及其与数据库隔离级别的关系,以及Spring事务的10种失效场景。尼恩还强调了面试中如何给出高质量答案,推荐阅读《尼恩Java面试宝典PDF》以提升面试表现。更多技术资料可在公众号【技术自由圈】获取。
|
1月前
|
JavaScript Java 关系型数据库
Spring事务失效的8种场景
本文总结了使用 @Transactional 注解时事务可能失效的几种情况,包括数据库引擎不支持事务、类未被 Spring 管理、方法非 public、自身调用、未配置事务管理器、设置为不支持事务、异常未抛出及异常类型不匹配等。针对这些情况,文章提供了相应的解决建议,帮助开发者排查和解决事务不生效的问题。
|
1月前
|
监控 网络协议 算法
OSPFv2与OSPFv3的区别:全面解析与应用场景
OSPFv2与OSPFv3的区别:全面解析与应用场景
37 0
|
2月前
|
JSON 前端开发 Java
Spring MVC——获取参数和响应
本文介绍了如何在Spring框架中通过不同的注解和方法获取URL参数、上传文件、处理cookie和session、以及响应不同类型的数据。具体内容包括使用`@PathVariable`获取URL中的参数,使用`MultipartFile`上传文件,通过`HttpServletRequest`和`@CookieValue`获取cookie,通过`HttpSession`和`@SessionAttribute`获取session,以及如何返回静态页面、HTML代码片段、JSON数据,并设置HTTP状态码和响应头。
68 1
Spring MVC——获取参数和响应
|
1月前
|
JavaScript API 开发工具
<大厂实战场景> ~ Flutter&鸿蒙next 解析后端返回的 HTML 数据详解
本文介绍了如何在 Flutter 中解析后端返回的 HTML 数据。首先解释了 HTML 解析的概念,然后详细介绍了使用 `http` 和 `html` 库的步骤,包括添加依赖、获取 HTML 数据、解析 HTML 内容和在 Flutter UI 中显示解析结果。通过具体的代码示例,展示了如何从 URL 获取 HTML 并提取特定信息,如链接列表。希望本文能帮助你在 Flutter 应用中更好地处理 HTML 数据。
122 1
|
1月前
|
负载均衡 网络协议 算法
OSPF与其他IGP协议的比较:全面解析与应用场景
OSPF与其他IGP协议的比较:全面解析与应用场景
44 0
|
2月前
|
存储
让星星⭐月亮告诉你,HashMap的put方法源码解析及其中两种会触发扩容的场景(足够详尽,有问题欢迎指正~)
`HashMap`的`put`方法通过调用`putVal`实现,主要涉及两个场景下的扩容操作:1. 初始化时,链表数组的初始容量设为16,阈值设为12;2. 当存储的元素个数超过阈值时,链表数组的容量和阈值均翻倍。`putVal`方法处理键值对的插入,包括链表和红黑树的转换,确保高效的数据存取。
64 5
|
2月前
|
Java API Spring
springboot学习七:Spring Boot2.x 拦截器基础入门&实战项目场景实现
这篇文章是关于Spring Boot 2.x中拦截器的入门教程和实战项目场景实现的详细指南。
35 0
springboot学习七:Spring Boot2.x 拦截器基础入门&实战项目场景实现
|
2月前
|
Java API Spring
springboot学习六:Spring Boot2.x 过滤器基础入门&实战项目场景实现
这篇文章是关于Spring Boot 2.x中过滤器的基础知识和实战项目应用的教程。
40 0
springboot学习六:Spring Boot2.x 过滤器基础入门&实战项目场景实现
|
4月前
|
运维 监控 数据可视化
Elasticsearch全观测技术解析问题之面对客户不同的场景化如何解决
Elasticsearch全观测技术解析问题之面对客户不同的场景化如何解决

推荐镜像

更多