SpringMVC 利用注解获取 Session 中的数据
1. HandlerMethodArgumentResolver 的使用
通常来说,我们获取获取 Session 中的数据,比如 登录用户 LoginUserVO,都是在方法中注入 HttpSession,然后执行
LoginUserVO attribute = (LoginUserVO) session.getAttribute(“login_user_key”);
来获取,因为用到的地方比较多,每次都得写重复的代码还得进行数据的转换就会很恶心;
SpringMVC 可以让我们通过 HandlerMethodArgumentResolver 接口实现 利用注解或不需要注解 直接从方法中注入 Session 中的数据,就像 HttpServletRequest 一样,比如下面的效果:
// 首先在登录时候将当前用户的信息放入 session 中;
request.getSession().setAttribute("login_user_key", loginUser);
// 直接通过自定义标签 @LoginUserBody 获取 session 中的 登录用户数据;
@PutMapping(value = "operation")
public boolean testArgument(@LoginUserBody LoginUserVO loginUser) {
System.out.println(loginUser.getTrueName() + " 这里的 loginUser 已经获取");
return true;
}
那么具体怎么实现呢?
首先,先定义自定义注解
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginUserBody {
}
然后实现 HandlerMethodArgumentResolver 接口
public class LoginUserArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
// 判断方法的这个参数是否是否使用了 @LoginUserBody
return parameter.hasParameterAnnotation(LoginUserBody.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory)
throws Exception {
// 返回 session 中的用户数据
return webRequest.getNativeRequest(HttpServletRequest.class).getSession().
getAttribute("login_user_key");
}
}
最后,就是将自定义的 HandlerMethodArgumentResolver 注册到 Spring 中,这里主要有两种方式,一种针对传统的 spring 项目,另一种针对 springboot 项目,springboot 是通过实现 WebMvcConfigurer 的方式来进行配置,
@Configuration
public class CustomWebMvcConfigurer implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new LoginUserArgumentResolver());
}
}
传统 spring 项目则是在 XML 中配置:
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean class="com.xxx.LoginUserArgumentResolver"/>
</mvc:argument-resolvers>
<mvc:annotation-driven>
2. 实现原理
首先我们从 DispatcherServlet 开始进行源码阅读,DispatcherServlet 在接受到 请求之后,会通过 doDispatch() 进行整个业务的处理,所以这里从 doDispatch() 进行代码的跟踪;
首先 DispatcherServlet 会 调用 HandlerAdapter 实现对 MVC 接口的调用,HandlerAdapter 是一个处理接口,MVC 默认使用的是 AbstractHandlerMethodAdapter
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
ModelAndView mv = null;
...
// 调用 HandlerAdapter 实现对 MVC 接口的调用
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
...
}
在 AbstractHandlerMethodAdapter.handle() ,请求被转发到了 handleInternal() 方法
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return handleInternal(request, response, (HandlerMethod) handler);
}
handleInternal() 是一个留给子类实现的函数,默认实现为 RequestMappingHandlerAdapter,其在 handleInternal() 调用了 invokeHandlerMethod() 来实现方法的执行和返回值的处理,流程如下所示:
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
...
// 调用 handlerMethod
mav = invokeHandlerMethod(request, response, handlerMethod);
...
}
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
......
// 执行 invokeAndHandle 调用接口
invocableMethod.invokeAndHandle(webRequest, mavContainer);
......
}
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 调用方法并获取到返回值
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
......
}
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 获取参数,也就是说,参数的处理都是这里完成的
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
...
}
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 获取接口参数
MethodParameter[] parameters = getMethodParameters();
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
...
// 这里的argumentResolvers 实际上是 HandlerMethodArgumentResolverComposite
if (this.argumentResolvers.supportsParameter(parameter)) {
args[i] = this.argumentResolvers.resolveArgument(
parameter, mavContainer, request, this.dataBinderFactory);
}
}
return args;
}
通过上面的流程我们可以知道,Spring 会在 HandlerMethodArgumentResolverComposite 中进行判断和处理参数,其主要代码如下所示:
public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {
private final List<HandlerMethodArgumentResolver> argumentResolvers = new LinkedList<>();
@Override
public boolean supportsParameter(MethodParameter parameter) {
return (getArgumentResolver(parameter) != null);
}
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {
if (methodArgumentResolver.supportsParameter(parameter)) {
result = methodArgumentResolver;
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
}
HandlerMethodArgumentResolverComposite 封装了一个 HandlerMethodArgumentResolver 集合,里面放着所有的 HandlerMethodArgumentResolver,选择 HandlerMethodArgumentResolver 的时候就循环判断,然后选出符合条件的一个,进行执行;