Springboot 之 自定義方法參數(shù)解析器

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

昨天(2018-08-01)我同事Damon在分享Spring AOP注解失效的主題上,順帶著介紹了下《自定義方法參數(shù)解析器》的使用,個(gè)人覺得還挺有用的,再次記錄一下,以后用的時(shí)候好查。

場景

一個(gè)業(yè)務(wù)場景,最近微信小程序如日中天,大有一發(fā)不可收拾之局勢,我司新接的一些業(yè)務(wù)也是趁熱打鐵以微信小程序的形式公諸于世,因此也有了這個(gè)場景。

與之前玩APP不一樣了,之前的app用戶注冊,基本上都是手機(jī)號+驗(yàn)證碼的形式,也就是說用戶上來都有手機(jī)號登錄的過程,登陸以后放回token,訪問替他頁面會帶著這個(gè)token,后臺也就通過token識別出這個(gè)用戶是誰了。

現(xiàn)在小程序,自然是微信登錄最方便,而且不用麻煩用戶再綁定手機(jī)號,那么就會有一個(gè)用戶唯一標(biāo)識的問題,這就是微信提供的openId,我們現(xiàn)在的做法就是在所有要請求的接口上在head里都帶著這個(gè)openId,后臺通過這個(gè)openId檢查是新用戶還是老用戶,用戶的信息也就相應(yīng)的拿到了。

業(yè)務(wù)處理

舉個(gè)例子,前端請求是這樣的:


image.png

1、各自接口分別處理
在需要判讀當(dāng)前用戶是誰的接口邏輯里分別加上,本次實(shí)驗(yàn),放在controller層,實(shí)際寫代碼時(shí)下面的邏輯應(yīng)該在service代碼里實(shí)現(xiàn)。

 @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);
    }

結(jié)果:


image.png

2、自定義方法參數(shù)解析器實(shí)現(xiàn)
先定義一個(gè)方法參數(shù)解析器


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 馬海強(qiáng)
 * @create 2018-08-03 14:38
 * @desc 方法參數(shù)解析器
 * 使用
 **/
@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;
    }
}

別忘了要將自定義的參數(shù)解析器配置到spring容器中,新建一個(gè)config類,繼承WebMvcConfigurationSupport,,內(nèi)容如下:


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 馬海強(qiáng)
 * @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的參數(shù)分解器
        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這個(gè)參數(shù),將變得如此簡單:

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

檢測結(jié)果


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;

    /**
     * 手機(jī)號
     */
    private String mobile;

    /**
     * 郵箱
     */
    private String mail;

    /**
     * 用戶工牌號
     */
    private String cardNo;


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

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

}

注意事項(xiàng)

1、為什么不用攔截器或者過濾器?
攔截器或者過濾器都能實(shí)現(xiàn)獲取request的header的openId,也能校驗(yàn)redis和數(shù)據(jù)庫中有沒有該數(shù)據(jù),沒有的話新建到數(shù)據(jù)庫或者redis中,但是就是返回一個(gè)對象實(shí)現(xiàn)起來麻煩一些。上重點(diǎn)了,最主要是不是每個(gè)接口都需要根據(jù)openId獲取用戶信息,這樣的話攔截器就有點(diǎn)多余了,配置哪些需要哪些不需要又太麻煩。所以使用方法參數(shù)解析器最合適了。

2、自定的方法參數(shù)解析器要創(chuàng)建一個(gè)繼承WebMvcConfigurationSupport的配置類,然后添加到解析器中,否則是不會生效的。

3、在使用spring-data-jpa的框架里,因?yàn)槭褂昧俗远x解析器,導(dǎo)致了自帶分分頁不好使的問題,其原因是自定義解析器以后,往 HandlerMethodArgumentResolver里面添加了什么解析器,容器就有了什么解析器,沒添加的不會默認(rèn)添加了,因此需要手動添加,就是下面這樣的意思


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的參數(shù)分解器
        argumentResolvers.add(new PageableHandlerMethodArgumentResolver());
    }
    
    @Override
    protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
        ObjectMapper objectMapper = new ObjectMapper();
        /**
         * 序列換成json時(shí),將所有的long變成string
         * 因?yàn)閖s中得數(shù)字類型不能包含所有的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);
    }
}

該配置文件增加了兩個(gè)json轉(zhuǎn)換處理,順便貼出來,知道在哪加怎么加就行,跟本文無關(guān)。

4、此處是以手機(jī)號舉例的,實(shí)際生產(chǎn)中,視情況而定。

5、關(guān)于注解不生效的問題,Damon主要以Asyc和Transactional為例說明的,但是就其不生效的原因,
玩轉(zhuǎn)Spring —— 消失的事務(wù)基本上講明白了。

完美~~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,569評論 19 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,275評論 6 342
  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架,建立于...
    Hsinwong閱讀 22,942評論 1 92
  • 什么是習(xí)慣? 1.每個(gè)習(xí)慣有三個(gè)組成部分:一個(gè)觸機(jī)(cue),讓你的行動開展;一個(gè)跟該觸機(jī)直接相關(guān)聯(lián)的“獎勵”(r...
    鐵馬閱讀 821評論 0 1
  • 注:本文寫于3月2日。 今天去了昨天未能去成的昭陵。 清朝皇帝的陵墓。 昨天入園免費(fèi),今天去,門票要6塊錢。 清昭...
    80天旅行閱讀 388評論 1 0

友情鏈接更多精彩內(nèi)容