介绍
首先什么是Handler?我的理解是Spring MVC中真正用于处理业务逻辑的类,也就是常说的、标注了@Controller注解的Controller类;Controller类中使用@RequestMapping(或GetMapping等变种注解)标注的方法称为处理器方法;处理器方法的参数指的就是该方法的参数。
处理器方法参数解析器HandlerMethodArgumentResolver用于自动向处理器方法参数注入值(自动赋值)。
从代码角度说HandlerMethodArgumentResolver是一个接口,开发者实现该接口可以实现自定义的Controller方法的参数的自动注入。
/**
* Strategy interface for resolving method parameters into argument values in
* the context of a given request.
*
* @author Arjen Poutsma
* @since 3.1
* @see HandlerMethodReturnValueHandler
*/
public interface HandlerMethodArgumentResolver {
/**
* 对应的方法参数是否支持该处理器解析处理
* @param 要检查的方法参数,MethodParameter Spring MVC中对方法参数的包装类
* @return 如果支持该参数则返回true,否则返回false
*/
boolean supportsParameter(MethodParameter parameter);
/**
* 从给定的request中解析出方法参数所需要的值,并返回
* ModelAndViewContainer 提供了request中的model
* WebDataBinderFactory 提供了创建WebDataBinder实例的方法(当需要绑定数据和类型转换时)
* @param parameter 要解析的方法参数。
* @param mavContainer 当前请求的ModelAndViewContainer
* @param webRequest 当前请求对象request
* @param binderFactory 创建WebDataBinder实例的工厂
* @return 返回解析后的参数值,如果不能解析返回null
* @throws Exception 如果解析参数值出错抛出异常
*/
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
需求
很多Controller方法,需要用户信息,以便做后续的用户相关操作。但是我们token中同样是可以获取到用户信息这里我们通过HandlerMethodArgumentResolver注入
注解定义
/**
* 请求的方法参数SysUser上添加该注解,则注入当前登录人信息
* 例1:public void test(@LoginUser SysUser user) //只有username 和 roles
* 例2:public void test(@LoginUser(isFull = true) SysUser user) //能获取SysUser对象的所有信息
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LoginUser {
/**
* 是否查询SysUser对象所有信息
*/
boolean isFull() default false;
}
HandlerMethodArgumentResolver实现
请求是接口参数中满足supportsParameter条件
进入resolveArgument返回user对象
public class UserArgumentResolver implements HandlerMethodArgumentResolver {
private final UserService userService;
public UserArgumentResolver(UserService userService) {
this.userService = userService;
}
/**
* 入参筛选
* @param methodParameter 参数集合
* @return 格式化后的参数
*/
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.hasParameterAnnotation(LoginUser.class) && methodParameter.getParameterType().equals(SysUser.class);
}
/**
* @param methodParameter 入参集合
* @param modelAndViewContainer model 和 view
* @param nativeWebRequest web相关
* @param webDataBinderFactory 入参解析
* @return 包装对象
*/
@Override
public Object resolveArgument(MethodParameter methodParameter,
ModelAndViewContainer modelAndViewContainer,
NativeWebRequest nativeWebRequest,
WebDataBinderFactory webDataBinderFactory) {
LoginUser loginUser = methodParameter.getParameterAnnotation(LoginUser.class);
boolean isFull = loginUser.isFull();
HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);
String username = request.getHeader(SecurityConstants.USER_HEADER);
if (StrUtil.isBlank(username)) {
log.warn("resolveArgument error username is empty");
return null;
}
SysUser user;
if (isFull) {
user = userService.selectByUsername(username);
} else {
user = new SysUser();
user.setUsername(username);
}
return user;
}
}
配置参数解析器
使用WebMvcConfigurer#addArgumentResolvers注册参数解析器
/**
* 默认SpringMVC拦截器
*/
public class WebMvcConfig implements WebMvcConfigurer {
@Lazy
@Autowired
private UserService userService;
/**
* 参数解析
*
* @param argumentResolvers 解析类
*/
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
//注入用户信息
argumentResolvers.add(new UserArgumentResolver(userService));
}
}
controller
结果
@Slf4j
@RestController
@RequestMapping(value = "/sys/user")
public class UserController {
@Autowired
private UserService userService;
/**
* 当前登录用户 user
* */
@ApiOperation(value = "根据access_token当前登录用户")
@GetMapping("/users/current")
public Result<LoginUser> getLoginUser(@LoginUser(isFull = true) SysUser user) {
//...
return loginUser;
}
}
controller 使用@LoginUser注解修饰 且是SysUser对象 前端无需传入参数请求时,UserArgumentResolver会对我们满足条件的接口进行一个用户信息注入。
@Controller
public class TestController {
@GetMapping
@ResponseBody
public Result<Void> test(HttpServletRequest request, HttpServletResponse response){
//...
return Reuslt.ok();
}
}
在test方法中直接可以用request和response,很方便,无需开发者自己去获取即可使用。同样是使用HandlerMethodArgumentResolver进行注入,感兴趣的可以查看ServletRequestMethodArgumentResolver代码