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


使用spring的自定义方法参数解析器获取当前用户的demo。
简书地址

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

场景

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

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

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

业务处理

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

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 )&#123;
                currentUser = new CurrentUser();
                BeanUtils.copyProperties(userList.get(0), currentUser);
                redisService.add(mobile, currentUser);
            &#125;
        &#125;
        return new RtnResult<>(currentUser);
    &#125;

结果:
postman api 得到response

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 &#123;
    @Resource
    private RedisService redisService;

    @Resource
    private HaierUserRepository userRepository;

    @Override
    public boolean supportsParameter(MethodParameter methodParameter) &#123;
        return methodParameter.getParameterType().equals(CurrentUser.class);
    &#125;

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

别忘了要将自定义的参数解析器配置到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 $&#123;DESCRIPTION&#125;
 **/
@Configuration
public class WebConfig extends WebMvcConfigurationSupport &#123;
    @Resource
    private CurrentUserArgumentResolver currentUserArgumentResolver;

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

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

    // swagger2 访问
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) &#123;
        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);
    &#125;
&#125;

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

    @GetMapping("/public/api/demo2")
    public Object getUser2(CurrentUser currentUser) &#123;
        System.out.println(currentUser);
        return new RtnResult<>(currentUser);
    &#125;

检测结果
api

贴上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&#123;
    private static final long serialVersionUID = 1L;
    private String userId;

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

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

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


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

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

&#125;

注意事项

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 &#123;
    @Override
    protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) &#123;
        // 注册Spring data jpa pageable的参数分解器
        argumentResolvers.add(new PageableHandlerMethodArgumentResolver());
    &#125;
    
    @Override
    protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) &#123;
        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>() &#123;
            @Override
            public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException &#123;
                    jsonGenerator.writeString("");
            &#125;
        &#125;);
        
        jackson2HttpMessageConverter.setObjectMapper(objectMapper);
        converters.add(jackson2HttpMessageConverter);
        converters.add(new StringHttpMessageConverter());
//        super.configureMessageConverters(converters);
    &#125;
&#125;

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

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

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

完美~~~


评论
  目录