
>>>我的博客<<<
昨天(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è)例子,前端請求是這樣的:

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é)果:

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é)果

貼上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ù)基本上講明白了。
完美~~~