Springboot 之 自定义方法参数解析器

120495-16c6f8d2dc1aeb9b.jpg
>>>我的博客<<<

昨天(2018-08-01)我同事Damon在分享Spring AOP注解失效的主题上,顺带着介绍了下《自定义方法参数解析器》的使用,个人觉得还挺有用的,再次记录一下,以后用的时候好查。

场景

一个业务场景,最近微信小程序如日中天,大有一发不可收拾之局势,我司新接的一些业务也是趁热打铁以微信小程序的形式公诸于世,因此也有了这个场景。

与之前玩APP不一样了,之前的app用户注册,基本上都是手机号+验证码的形式,也就是说用户上来都有手机号登录的过程,登陆以后放回token,访问替他页面会带着这个token,后台也就通过token识别出这个用户是谁了。

现在小程序,自然是微信登录最方便,而且不用麻烦用户再绑定手机号,那么就会有一个用户唯一标识的问题,这就是微信提供的openId,我们现在的做法就是在所有要请求的接口上在head里都带着这个openId,后台通过这个openId检查是新用户还是老用户,用户的信息也就相应的拿到了。

业务处理

举个例子,前端请求是这样的:


image.png

1、各自接口分别处理
在需要判读当前用户是谁的接口逻辑里分别加上,本次实验,放在controller层,实际写代码时下面的逻辑应该在service代码里实现。

 @GetMapping("/public/api/demo1")
    public Object getUser1(HttpServletRequest request) {
        String mobile = request.getHeader("openId");
        CurrentUser currentUser = (CurrentUser) redisService.find(mobile);
        if(currentUser == null){
            List<HaierUser> userList = userRepository.findByMobileAndUserState(mobile, "1");
            if(userList.size() > 0 ){
                currentUser = new CurrentUser();
                BeanUtils.copyProperties(userList.get(0), currentUser);
                redisService.add(mobile, currentUser);
            }
        }
        return new RtnResult<>(currentUser);
    }

结果:


image.png

2、自定义方法参数解析器实现
先定义一个方法参数解析器


import com.hczt.haier.db.user.entity.HaierUser;
import com.hczt.haier.db.user.repository.HaierUserRepository;
import com.hczt.haier.mobileoffice.usercenter.service.api.RedisService;
import org.springframework.beans.BeanUtils;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
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 javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.List;

/**
 * 精诚所至,金石为开。
 * 石の上にも三年;陽気の発する所金石亦透る。
 * Faith moves mountains.
 *
 * @author 马海强
 * @create 2018-08-03 14:38
 * @desc 方法参数解析器
 * 使用
 **/
@Component
public class CurrentUserArgumentResolver implements HandlerMethodArgumentResolver {
    @Resource
    private RedisService redisService;

    @Resource
    private HaierUserRepository userRepository;

    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
        return methodParameter.getParameterType().equals(CurrentUser.class);
    }

    @Override
    public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
        HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);
        String openId = request.getHeader("openId");
        CurrentUser currentUser = (CurrentUser) redisService.find(openId);
        if(currentUser == null){
            List<HaierUser> userList = userRepository.findByMobileAndUserState(openId, "1");
            if(userList.size() > 0 ){
                currentUser = new CurrentUser();
                BeanUtils.copyProperties(userList.get(0), currentUser);
                redisService.add(openId, currentUser);
            }
        }
        return currentUser;
    }
}

别忘了要将自定义的参数解析器配置到spring容器中,新建一个config类,继承WebMvcConfigurationSupport,,内容如下:


import org.springframework.context.annotation.Configuration;
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

import javax.annotation.Resource;
import java.util.List;

/**
 * 精诚所至,金石为开。
 * 石の上にも三年;陽気の発する所金石亦透る。
 * Faith moves mountains.
 *
 * @author 马海强
 * @create 2018-08-03 16:23
 * @desc ${DESCRIPTION}
 **/
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {
    @Resource
    private CurrentUserArgumentResolver currentUserArgumentResolver;

    @Override
    protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(currentUserArgumentResolver);

        // 注册Spring data jpa pageable的参数分解器
        argumentResolvers.add(new PageableHandlerMethodArgumentResolver());
    }

    // swagger2 访问
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("swagger-ui.html")
                .addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
        registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/");
        super.addResourceHandlers(registry);
    }
}

然后访问的controller里接收current这个参数,将变得如此简单:

    @GetMapping("/public/api/demo2")
    public Object getUser2(CurrentUser currentUser) {
        System.out.println(currentUser);
        return new RtnResult<>(currentUser);
    }

检测结果


image.png

贴上Current类:


import lombok.Data;
import lombok.ToString;

import java.io.Serializable;

/**
 * 
 * @Auther: marvin.mma
 * @Date: 2018/8/3 14:38
 * @return   
 */
@Data
@ToString
public class CurrentUser implements Serializable{
    private static final long serialVersionUID = 1L;
    private String userId;

    /**
     * 手机号
     */
    private String mobile;

    /**
     * 邮箱
     */
    private String mail;

    /**
     * 用户工牌号
     */
    private String cardNo;


    /**
     * 用户姓名
     */
    private String fullName;

    /**
     * 用户姓名拼音
     */
    private String fullNamePinyin;

}

注意事项

1、为什么不用拦截器或者过滤器?
拦截器或者过滤器都能实现获取request的header的openId,也能校验redis和数据库中有没有该数据,没有的话新建到数据库或者redis中,但是就是返回一个对象实现起来麻烦一些。上重点了,最主要是不是每个接口都需要根据openId获取用户信息,这样的话拦截器就有点多余了,配置哪些需要哪些不需要又太麻烦。所以使用方法参数解析器最合适了。

2、自定的方法参数解析器要创建一个继承WebMvcConfigurationSupport的配置类,然后添加到解析器中,否则是不会生效的。

3、在使用spring-data-jpa的框架里,因为使用了自定义解析器,导致了自带分分页不好使的问题,其原因是自定义解析器以后,往 HandlerMethodArgumentResolver里面添加了什么解析器,容器就有了什么解析器,没添加的不会默认添加了,因此需要手动添加,就是下面这样的意思


import java.io.IOException;
import java.util.List;

import org.springframework.context.annotation.Configuration;
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;

/**
 * 
 * @Auther: marvin.mma
 * @Date: 2018/8/3 16:38
 * @return   
 */
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {
    @Override
    protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        // 注册Spring data jpa pageable的参数分解器
        argumentResolvers.add(new PageableHandlerMethodArgumentResolver());
    }
    
    @Override
    protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
        ObjectMapper objectMapper = new ObjectMapper();
        /**
         * 序列换成json时,将所有的long变成string
         * 因为js中得数字类型不能包含所有的java long值
         */
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
        simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
        objectMapper.registerModule(simpleModule);
        
        // 将返回值null处理成空值
        SerializerProvider serializerProvider = objectMapper.getSerializerProvider();
        serializerProvider.setNullValueSerializer(new JsonSerializer<Object>() {
            @Override
            public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
                    jsonGenerator.writeString("");
            }
        });
        
        jackson2HttpMessageConverter.setObjectMapper(objectMapper);
        converters.add(jackson2HttpMessageConverter);
        converters.add(new StringHttpMessageConverter());
//      super.configureMessageConverters(converters);
    }
}

该配置文件增加了两个json转换处理,顺便贴出来,知道在哪加怎么加就行,跟本文无关。

4、此处是以手机号举例的,实际生产中,视情况而定。

5、关于注解不生效的问题,Damon主要以Asyc和Transactional为例说明的,但是就其不生效的原因,
玩转Spring —— 消失的事务基本上讲明白了。

完美~~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,175评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,674评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,151评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,597评论 1 269
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,505评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,969评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,455评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,118评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,227评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,213评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,214评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,928评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,512评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,616评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,848评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,228评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,772评论 2 339

推荐阅读更多精彩内容