这里记录回顾一些知识,不然就快忘记啦。
这是一个常见的业务需求,为实现这个需求,有以下几种解决方案:
即,每个Controller开始,先调用tokenUtils.getUserByToken(token),不够优雅。
AOP可以解决很多切面类问题,思路同Spring AOP来自定义注解实现审计或日志记录,将currentUser放到request里;比起拦截器稍重。
使用mvc拦截器HandlerInterceptor+方法参数解析器HandlerMethodArgumentResolver最合适。
SpringMVC提供了mvc拦截器HandlerInterceptor,包含以下3个方法:
HandlerInterceptor经常被用来解决拦截事件,如用户鉴权等。另外,Spring也向我们提供了多种解析器Resolver,如用来统一处理异常的HandlerExceptionResolver,以及今天的主角 HandlerMethodArgumentResolver。HandlerMethodArgumentResolver是用来处理方法参数的解析器,包含以下2个方法:
知识储备已到位,接下来着手实现,主要分为三步走:
1. 自定义权限拦截器AuthenticationInterceptor拦截所有request请求,并将token解析为currentUser,最终放到request中;
2. 自定义参数注解@CurrentUser,添加至controller的方法参数user之上;
3. 自定义方法参数解析器CurrentUserMethodArgumentResolver,取出request中的user,并赋值给添加了@CurrentUser注解的参数user。
要使用 HandlerMethodArgumentResolver
,需要遵循以下步骤:
HandlerMethodArgumentResolver
实现类。supportsParameter()
方法和 resolveArgument()
方法。 为了将 HTTP 请求参数转换为 Java 对象,我们需要创建一个自定义的 HandlerMethodArgumentResolver
实现类。在这个类中,我们需要实现 supportsParameter()
方法和 resolveArgument()
方法。
在 supportsParameter()
方法中,我们需要检查方法参数是否与我们要转换的 Java 类型相同。如果是,返回 true
,否则返回 false
。
在 resolveArgument()
方法中,我们需要将 HTTP 请求参数转换为 Java 对象。为此,我们可以使用 NativeWebRequest
对象来获取请求参数,然后将其转换为 Java 对象。
要在 Spring Boot 应用程序中使用自定义的 HandlerMethodArgumentResolver
实现类,我们需要将其注册到应用程序上下文中。为此,我们可以创建一个 @Configuration
类,并实现 WebMvcConfigurer
接口。在这个类中,我们需要重写 addArgumentResolvers()
方法,并将自定义的 HandlerMethodArgumentResolver
实现类添加到参数解析器列表中。
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List
resolvers.add(new CustomHandlerMethodArgumentResolver());
}
}
自定义权限拦截器AuthenticationInterceptor,需实现HandlerInterceptor。在preHandle中调用tokenUtils.getUserByToken(token),获取到当前用户,最后塞进request中,如下:
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import edp.core.utils.TokenUtils;
import edp.davinci.core.common.Constants;
import edp.davinci.model.User;
public class AuthenticationInterceptor implements HandlerInterceptor {
@Autowired
private TokenUtils tokenUtils;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("Authorization");
User user = tokenUtils.getUserByToken(token);
request.setAttribute(Constants.CURRENT_USER, user);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
自定义方法参数上使用的注解@CurrentUser,代表被它注解过的参数的值都需要由方法参数解析器CurrentUserMethodArgumentResolver来“注入”,如下:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义 当前用户 注解
* 注解 参数
* 此注解在验证token通过后,获取当前token包含用户
*/
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface CurrentUser {
}
自定义方法参数解析器CurrentUserMethodArgumentResolver,需实现HandlerMethodArgumentResolver。
import org.springframework.core.MethodParameter; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; import edp.core.annotation.CurrentUser; import edp.core.consts.Consts; import edp.davinci.model.User; /** * @CurrentUser 注解 解析器 */ public class CurrentUserMethodArgumentResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.getParameterType().isAssignableFrom(User.class) && parameter.hasParameterAnnotation(CurrentUser.class); } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) { return (User) webRequest.getAttribute(Consts.CURRENT_USER, RequestAttributes.SCOPE_REQUEST); } } As we all know,拦截器定义好以后,在SpringMVC项目中,需要去SpringMVC的配置文件springmvc.xml添加该拦截器;但是在SpringBoot中,省去了很多配置文件,取而代之的是被注解@Configuration标识的配置类,SpringMVC配置文件对应的配置类需继承WebMvcConfigurationSupport。同理,解析器定义好以后,也需被添加到SpringMVC的配置文件或配置类中。最后,额外的一步,配置mvc。
定义MVC配置类,需继承WebMvcConfigurationSupport。分别在addInterceptors和addArgumentResolvers方法中,添加自定义的拦截器和参数解析器,如下:
import static edp.core.consts.Consts.EMPTY; import java.math.BigInteger; import java.util.ArrayList; import java.util.List; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.fastjson.serializer.ValueFilter; import com.alibaba.fastjson.support.config.FastJsonConfig; import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; import edp.davinci.core.common.Constants; import edp.davinci.core.inteceptor.AuthenticationInterceptor; import edp.davinci.core.inteceptor.CurrentUserMethodArgumentResolver; @Configuration public class WebMvcConfig extends WebMvcConfigurationSupport { @Value("${file.userfiles-path}") private String filePath; /** * 登录校验拦截器 * * @return */ @Bean public AuthenticationInterceptor loginRequiredInterceptor() { return new AuthenticationInterceptor(); } /** * CurrentUser 注解参数解析器 * * @return */ @Bean public CurrentUserMethodArgumentResolver currentUserMethodArgumentResolver() { return new CurrentUserMethodArgumentResolver(); } /** * 参数解析器 * * @param argumentResolvers */ @Override protected void addArgumentResolvers(ListargumentResolvers) { argumentResolvers.add(currentUserMethodArgumentResolver()); super.addArgumentResolvers(argumentResolvers); } @Override protected void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginRequiredInterceptor()) .addPathPatterns(Constants.BASE_API_PATH + "/**") .excludePathPatterns(Constants.BASE_API_PATH + "/login"); super.addInterceptors(registry); } @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/**") .addResourceLocations("classpath:/META-INF/resources/") .addResourceLocations("classpath:/static/page/") .addResourceLocations("classpath:/static/templates/") .addResourceLocations("file:" + filePath); } @Override protected void configureMessageConverters(List > converters) { FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter(); FastJsonConfig fastJsonConfig = new FastJsonConfig(); fastJsonConfig.setSerializerFeatures(SerializerFeature.QuoteFieldNames, SerializerFeature.WriteEnumUsingToString, SerializerFeature.WriteMapNullValue, SerializerFeature.WriteDateUseDateFormat, SerializerFeature.DisableCircularReferenceDetect); fastJsonConfig.setSerializeFilters((ValueFilter) (o, s, source) -> { if (null != source && (source instanceof Long || source instanceof BigInteger) && source.toString().length() > 15) { return source.toString(); } else { return null == source ? EMPTY : source; } }); //处理中文乱码问题 List fastMediaTypes = new ArrayList<>(); fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8); fastConverter.setSupportedMediaTypes(fastMediaTypes); fastConverter.setFastJsonConfig(fastJsonConfig); converters.add(fastConverter); } }
以下是一些场景案例,演示了如何使用 HandlerMethodArgumentResolver
解析不同类型的请求参数。
假设有一个 POST 请求,其中包含以下 JSON 请求体:
{ "name": "John", "age": 30}
我们可以创建一个自定义的 HandlerMethodArgumentResolver
实现类来将这些参数转换为 Java 对象。
public class JsonHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver
{
@Override
public boolean supportsParameter(MethodParameter parameter)
{
return parameter.hasParameterAnnotation(RequestBody.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception
{
String json = webRequest.getNativeRequest(HttpServletRequest.class).getReader().lines().collect(Collectors.joining(System.lineSeparator()));
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(json, parameter.getParameterType());
}
}
在这个示例中,我们创建了一个名为 JsonHandlerMethodArgumentResolver
的自定义 HandlerMethodArgumentResolver
实现类,并在其中解析 JSON 请求体。在 supportsParameter()
方法中,我们检查方法参数是否带有 @RequestBody
注解。在 resolveArgument()
方法中,我们从请求体中读取 JSON 数据,并使用 ObjectMapper
将其转换为 Java 对象。
假设有一个 POST 请求,其中包含以下 XML 请求体:
我们可以创建一个自定义的 HandlerMethodArgumentResolver
实现类来将这些参数转换为 Java 对象。
public class XmlHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver
{
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(RequestBody.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception
{
String xml = webRequest.getNativeRequest(HttpServletRequest.class).getReader().lines().collect(Collectors.joining(System.lineSeparator()));
JAXBContext jaxbContext = JAXBContext.newInstance(parameter.getParameterType());
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
StringReader reader = new StringReader(xml);
return unmarshaller.unmarshal(reader);
}
}
在这个示例中,我们创建了一个名为 XmlHandlerMethodArgumentResolver
的自定义 HandlerMethodArgumentResolver
实现类,并在其中解析 XML 请求体。在 supportsParameter()
方法中,我们检查方法参数是否带有 @RequestBody
注解。在 resolveArgument()
方法中,我们从请求体中读取 XML 数据,并使用 JAXBContext
和 Unmarshaller
将其转换为 Java 对象。
假设有一个 GET 请求,其中包含以下请求参数:
name=John&age=30
我们可以创建一个自定义的 HandlerMethodArgumentResolver
实现类来将这些参数转换为 Java 对象。
public class UserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().equals(User.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
String name = webRequest.getParameter("name");
int age = Integer.parseInt(webRequest.getParameter("age"));
User user = new User();
user.setName(name);
user.setAge(age);
return user;
}
}
在这个示例中,我们创建了一个名为 UserHandlerMethodArgumentResolver
的自定义 HandlerMethodArgumentResolver
实现类,并在其中解析多个请求参数。在 supportsParameter()
方法中,我们检查方法参数是否与 User
类型相同。在 resolveArgument()
方法中,我们从请求参数中获取数据,并将其转换为 User
对象。
要在 Spring Boot 应用程序中使用自定义的 HandlerMethodArgumentResolver
实现类,我们需要将其注册到应用程序上下文中。为此,我们可以创建一个 @Configuration
类,并实现 WebMvcConfigurer
接口。在这个类中,我们需要重写 addArgumentResolvers()
方法,并将自定义的 HandlerMethodArgumentResolver
实现类添加到参数解析器列表中。
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List
resolvers.add(new JsonHandlerMethodArgumentResolver());
resolvers.add(new XmlHandlerMethodArgumentResolver());
resolvers.add(new UserHandlerMethodArgumentResolver());
}
}
现在,我们已经成功地创建了多个自定义的 HandlerMethodArgumentResolver
实现类,并将它们注册到 Spring Boot 应用程序中。这样,我们就能够轻松地处理不同类型的请求参数,并将它们转换为 Java 对象。
例如,假设您有一个 @UserParam
注解,您可以使用以下代码来解析它:
public class UserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterAnnotation(UserParam.class) != null;
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
UserParam userParam = parameter.getParameterAnnotation(UserParam.class);
String name = webRequest.getParameter(userParam.name());
int age = Integer.parseInt(webRequest.getParameter(userParam.age()));
User user = new User();
user.setName(name);
user.setAge(age);
return user;
}
}
在这个示例中,我们创建了一个 @UserParam
注解,并将其应用于方法参数上。然后,我们可以在 supportsParameter()
方法中检查方法参数是否带有 @UserParam
注解。在 resolveArgument()
方法中,我们从注解中获取参数名,并从请求参数中获取相应的值,然后将其转换为 User
对象。
要在 Spring Boot 应用程序中使用自定义注解,您需要将您的注解类添加到 @Configuration
类中,并将其添加到 addArgumentResolvers()
方法中:
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List
resolvers.add(new UserHandlerMethodArgumentResolver());
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface UserParam {
String name() default "name";
String age() default "age";
}
}
现在,您就可以在 Spring Boot 应用程序中使用 @UserParam
注解来简化
HandlerMethodArgumentResolver用于统一获取当前登录用户_metaobjecthandler获取token-CSDN博客
Spring Boot HandlerMethodArgumentResolver 使用和场景-Spring专区论坛-技术-SpringForAll社区
HandlerMethodArgumentResolver(四):自定参数解析器处理特定应用场景,介绍PropertyNamingStrategy的使用【享学Spring MVC】-腾讯云开发者社区-腾讯云