文章目录
HandlerMethodArgumentResolver
在 Spring MVC 中,处理器方法(handler method)是控制器(Controller)中的函数,负责处理客户端请求。这些请求可能包含各种不同类型的数据,例如表单数据、JSON 数据、路径变量等。为了使得开发者能够方便地访问和处理这些请求数据,Spring MVC 提供了
HandlerMethodArgumentResolver
接口。
HandlerMethodArgumentResolver
在 Spring MVC 框架中充当了一个关键角色,它负责将请求中的信息转换成处理器方法所需的参数类型。换句话说,它允许开发者将请求参数直接映射到处理器方法的参数上,而无需手动解析请求。
使用场景
HandlerMethodArgumentResolver 是Spring MVC框架中的一个核心接口,它扮演着数据绑定和类型转换的重要角色,负责处理控制器方法参数的解析与注入。这一机制极大地增强了Spring框架处理HTTP请求的灵活性和便捷性,允许开发者自定义参数解析逻辑,以支持复杂类型、自定义注解或从请求中提取特定信息。
在Spring MVC处理请求的过程中,接收到HTTP请求后,DispatcherServlet会找到对应的处理器(HandlerMethod)。接着,系统会遍历注册的所有HandlerMethodArgumentResolver实例,寻找能够支持当前处理器方法参数的解析器。这些解析器可以基于参数类型、注解或其他条件进行匹配,实现如从请求体自动绑定JSON数据到Java对象、解析请求参数、处理请求头信息或从Session中获取数据等功能。
开发者可以通过实现HandlerMethodArgumentResolver接口来自定义参数解析逻辑,满足特定业务需求,如权限验证、多语言处理或是自定义的数据过滤等。此外,Spring框架本身也提供了多种内置的实现,如RequestParamMethodArgumentResolver处理@RequestParam注解参数,PathVariableMethodArgumentResolver处理路径变量,以及用于处理请求体的RequestBodyArgumentResolver等,覆盖了大部分常见场景。
关于它的应用场景可以非常多,本文我总结出最为常见、好理解的两个应用场景作为举例说明:
- 获取当前登陆人(当然用户)的基本信息
- 调整(兼容)数据结构
1.源码解读
public interface HandlerMethodArgumentResolver {
boolean supportsParameter(MethodParameter parameter);
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
boolean supportsParameter(MethodParameter parameter);
- 这个方法用于判断当前的解析器是否支持给定的方法参数(MethodParameter)。如果支持,返回
true
;否则返回false
。 - 参数
parameter
表示需要解析的方法参数。
- 这个方法用于判断当前的解析器是否支持给定的方法参数(MethodParameter)。如果支持,返回
@Nullable Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
- 这个方法用于解析处理器方法的参数,并返回解析后的结果。
- 参数
parameter
表示需要解析的方法参数。 - 参数
mavContainer
是一个可空的ModelAndViewContainer
对象,用于在解析期间存储模型和视图的相关信息。 - 参数
webRequest
表示当前的 Web 请求对象,类型为NativeWebRequest
。 - 参数
binderFactory
是一个可空的WebDataBinderFactory
对象,用于创建WebDataBinder
对象,用于数据绑定和验证。 - 方法返回一个
@Nullable
注解修饰的 Object 类型的对象,表示解析后的方法参数。如果解析失败或者不适用,可以返回null
。- 方法可能会抛出异常
Exception
,表示解析过程中出现的异常情况。
- 方法可能会抛出异常
MethodParameter
是 Spring Framework 提供的一个类,用于描述方法参数的元数据信息。它提供了丰富的方法,用于获取和操作方法参数的各种信息。下面是一些 MethodParameter
类的常用方法及其描述:
-
getParameterType()
:-
描述:获取方法参数的类型。
-
示例:
Class<?> parameterType = methodParameter.getParameterType();
-
-
getParameterIndex()
:-
描述:获取方法参数在方法参数列表中的索引位置。
-
示例:
int parameterIndex = methodParameter.getParameterIndex();
-
-
getGenericParameterType()
:-
描述:获取方法参数的泛型类型。
-
示例:
Type genericParameterType = methodParameter.getGenericParameterType();
-
-
getMethod()
:-
描述:获取包含此方法参数的方法。
-
示例:
Method method = methodParameter.getMethod();
-
-
hasParameterAnnotation(Class<? extends Annotation> annotationType)
:-
描述:判断方法参数是否有指定类型的注解。
-
示例:
boolean hasAnnotation = methodParameter.hasParameterAnnotation(RequestParam.class);
-
-
getParameterAnnotations()
:-
描述:获取方法参数上的所有注解。
-
示例:
Annotation[] annotations = methodParameter.getParameterAnnotations();
-
-
getParameterName()
:-
描述:获取方法参数的名称(需要编译时开启 -parameters 选项)。
-
示例:
String parameterName = methodParameter.getParameterName();
-
-
initParameterNameDiscovery(ParameterNameDiscoverer parameterNameDiscoverer)
:-
描述:初始化参数名称发现器,用于在运行时获取方法参数的名称。
-
示例:
methodParameter.initParameterNameDiscovery(parameterNameDiscoverer);
-
2.场景1
下面我们针对场景1来实现一个简单的案例
在Controller
层获取当前登陆人的基本信息(如id、名字…)是一个必须的、频繁的功能需求,这个时候如果团队内没有提供相关封装好的方法来调用,你便可看到大量的、重复的获取当前用户的代码,这是非常繁琐的一个过程
一般团队的做法是:提供BaseController
,在基类里面提供获取当前用户的功能方法,这样业务控制器Controller
只需要继承它就有这能力了,使用起来确实也还挺方便的。但是是否还思考过这种通过继承的方式它是有弊端的–>我只想获取当前登陆人我就得继承一个父类?这是不是设计太重了点?更坏的情况是如果此时我已经有父类了呢?
可以通过自定义HandlerMethodArgumentResolver
来实现获取当前登录用户的解决方案。
当客户端发送POST请求时,通常会将身份验证所需的token
包含在请求的Authorization
请求头中。在后端,我们需要根据这个token
进行身份验证和用户信息的获取。这个过程可能涉及到解析token
、验证token
的有效性、获取用户相关信息等步骤。一旦认证通过,我们希望将用户信息传递给Controller
层,以便Controller
的方法能够直接访问用户信息,并在业务逻辑中使用它。通过这种方式,我们可以实现一个统一的身份验证和用户信息传递机制,使得Controller
层的方法更加简洁、可读性更高,同时也提高了代码的可维护性和可扩展性。
这时,HandlerMethodArgumentResolver
就发挥了重要作用。我们可以通过实现自定义的HandlerMethodArgumentResolver
来实现这一功能。具体来说,我们可以创建一个名为UserResolver
的解析器,它负责从请求的Authorization头中提取token,并根据token获取用户信息。一旦用户信息被成功解析,UserResolver
会将用户信息封装到Controller方法的参数中,使得控制器方法可以直接获取到用户信息。
在UserResolver
中,我们可以编写逻辑 根据token从数据库或者其他存储中获取用户信息,并将其封装成一个User对象。
接着,当Spring MVC框架调用控制器的处理器方法时,它会自动调用UserResolver
解析器,并将解析得到的用户信息传递给处理器方法。这样一来,控制器方法的参数就会默认包含用户信息,无需额外的代码来获取。
通过这种方式,我们可以实现一种统一的用户信息赋值,并且使得控制器方法更加简洁和可读。
2.1.创建用户实体类
/**
* @author 13723
* @version 1.0
* 2024/5/12 11:03
*/
@Getter @Setter
public class UserInfo {
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
/**
* 用户姓名
*/
private String userName;
/**
* 用户年龄
*/
private Integer age;
/**
* 用户地址
*/
private String address;
}
2.2.HandlerMethodArgumentResolver实现类
import com.swx.service.test.UserInfo;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.core.MethodParameter;
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.servlet.http.HttpServletRequest;
import java.lang.invoke.MethodHandles;
import java.util.HashMap;
import java.util.Map;
/**
* @author 13723
* @version 1.0
* 2024/5/12 11:05
*/
public class UserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@Override
public boolean supportsParameter(MethodParameter parameter) {
// 设置当前容器支持的解析的方法参数类型
// 如果有注解,可以追加 @CurrUser UserInfo userInfo
// CurrUser ann = parameter.getParameterAnnotation(CurrUser.class);
// ann != null &&
Class<?> parameterType = parameter.getParameterType();
return (
// 如果方法参数的类型是 UserInfo 类型或者其子类,则返回 true。
// 如果方法参数的类型是 Map 类型或者其子类,则返回 true。
// 如果方法参数的类型是 Object 类型或者其子类,则返回 true。
(UserInfo.class.isAssignableFrom(parameterType)
|| Map.class.isAssignableFrom(parameterType)
|| Object.class.isAssignableFrom(parameterType)));
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
// 获取request
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
// 从请求头中获取 token
String token = request.getHeader("Authorization");
if (StringUtils.isBlank(token)){
// 此处不建议抛出异常,因为校验token是Filter那边处理的
return null;
}
// 这里就模拟从后端获取数据
UserInfo userInfo = new UserInfo() {{
setUserName("喜羊羊");
setAge(123);
setAddress("羊村!");
}};
// 判断参数类型进行返回
Class<?> parameterType = parameter.getParameterType();
if (Map.class.isAssignableFrom(parameterType)) {
Map<String, Object> map = new HashMap<>();
BeanUtils.copyProperties(userInfo, map);
return map;
} else {
return userInfo;
}
}
}
2.3.注入Spring容器
如果我们在 解释器内部使用 @Autowired的时候,我们会发现注入的是空的,因为此时
helloService
值为null@Autowired HelloService helloService;
2.3.1.第一种注入方式(推荐新手)
@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
// 这里如果不单独声明成bean 那么我们就无法在 UserHandlerMethodArgumentResolver 进入注入其他对象
@Bean
public UserHandlerMethodArgumentResolver currUserArgumentResolver(){
return new UserHandlerMethodArgumentResolver();
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(currUserArgumentResolver());
}
}
@Configuration
注解标识了这是一个配置类,Spring会在应用程序启动时读取并加载这个配置类,并根据其中的配置信息来完成相应的初始化操作。@EnableWebMvc
注解用于启用Spring MVC框架的基本功能,它会自动配置一些默认的Bean,例如处理器映射器(HandlerMapping)、处理器适配器(HandlerAdapter)、视图解析器(ViewResolver)等。- 在
WebMvcConfig
类中定义了一个名为currUserArgumentResolver
的Bean,它是UserHandlerMethodArgumentResolver
的实例化对象,并被注解为@Bean
。通过@Bean
注解,我们告诉Spring容器应该如何创建这个Bean,并将其纳入到Spring的应用上下文中。 addArgumentResolvers
方法是WebMvcConfigurer
接口的一个方法,它用于配置Spring MVC中的参数解析器。在这里,通过调用currUserArgumentResolver()
方法,将currUserArgumentResolver
添加到了参数解析器列表中
这里有一个地方是需要注意的,因为Spring框架通过容器来管理应用程序中的组件,Bean的生命周期和依赖关系由Spring容器来管理。因此,如果我们希望在Spring容器中使用某个对象,并且希望Spring能够对其进行管理,我们需要将其声明为一个Bean。
在这个例子中,UserHandlerMethodArgumentResolver
是一个自定义的参数解析器,我们希望Spring MVC能够识别并使用它来解析方法参数。为了达到这个目的,我们需要将 UserHandlerMethodArgumentResolver
声明为一个Bean,并且在 WebMvcConfig
配置类中通过 @Bean
注解将其注册到Spring容器中。
2.3.2.第二种注入方式(启动类注入)
/**
* @author 13723
* @version 1.0
* 2024/5/10 10:46
*/
@SpringBootApplication
public class SWXWeChatApplication implements WebMvcConfigurer {
public static void main(String[] args) {
SpringApplication.run(SWXWeChatApplication.class, args);
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
// 通过在 addArgumentResolvers 方法中创建 UserHandlerMethodArgumentResolver 实例,并将其添加到参数解析器列表中,
// 实际上就是将 UserHandlerMethodArgumentResolver 声明为一个Bean,并交给了Spring容器管理。
resolvers.add(new UserHandlerMethodArgumentResolver());
}
}
这里我们在启动类上,实现了 WebMvcConfigurer
接口,表明它还是一个Web MVC配置类。
在 addArgumentResolvers
方法中,我们创建了一个 UserHandlerMethodArgumentResolver
的实例,并将其添加到了 resolvers
参数中。在Spring MVC中,参数解析器(HandlerMethodArgumentResolver
)是一种特殊类型的Bean,它用于解析控制器方法的参数。
虽然我们没有使用 @Bean
注解来显式声明 UserHandlerMethodArgumentResolver
为一个Bean,但通过将其实例添加到了 resolvers
列表中,Spring MVC会自动识别 UserHandlerMethodArgumentResolver
并将其注册为一个Bean。
换句话说,当Spring Boot应用程序启动时,它会扫描所有的配置类和组件,并将它们注册到Spring的应用上下文中。在这个过程中,Spring MVC会自动检测到 UserHandlerMethodArgumentResolver
并将其识别为一个参数解析器的Bean。因此,它可以被Spring容器所管理,并在需要时被Spring MVC框架使用。
这种方式相对于第一种方式的优势在于,它更加简洁和直观。通过重写addArgumentResolvers
方法,直接在WebMvcConfigurer
的实现类中添加参数解析器,省去了定义额外的Bean的步骤,使得代码结构更加清晰。
2.4.Controller层
/**
* @author 13723
* @version 1.0
* 2024/5/10 10:57
*/
@RestController
@RequestMapping("/test")
public class TestController {
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@GetMapping("get")
public String get(UserInfo userInfoToken){
return JsonObjectMapper.getInstance().toJson(userInfoToken);
}
}