HandlerMethodArgumentResolver用法

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. 获取当前登陆人(当然用户)的基本信息
  2. 调整(兼容)数据结构

1.源码解读

public interface HandlerMethodArgumentResolver {
    boolean supportsParameter(MethodParameter parameter);

    @Nullable
    Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
  1. boolean supportsParameter(MethodParameter parameter);
    • 这个方法用于判断当前的解析器是否支持给定的方法参数(MethodParameter)。如果支持,返回 true;否则返回 false
    • 参数 parameter 表示需要解析的方法参数。
  2. @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 类的常用方法及其描述:

  1. getParameterType()

    • 描述:获取方法参数的类型。

    • 示例:

      Class<?> parameterType = methodParameter.getParameterType();
      
  2. getParameterIndex()

    • 描述:获取方法参数在方法参数列表中的索引位置。

    • 示例:

      int parameterIndex = methodParameter.getParameterIndex();
      
  3. getGenericParameterType()

    • 描述:获取方法参数的泛型类型。

    • 示例:

      Type genericParameterType = methodParameter.getGenericParameterType();
      
  4. getMethod()

    • 描述:获取包含此方法参数的方法。

    • 示例:

      Method method = methodParameter.getMethod();
      
  5. hasParameterAnnotation(Class<? extends Annotation> annotationType)

    • 描述:判断方法参数是否有指定类型的注解。

    • 示例:

      boolean hasAnnotation = methodParameter.hasParameterAnnotation(RequestParam.class);
      
  6. getParameterAnnotations()

    • 描述:获取方法参数上的所有注解。

    • 示例:

      Annotation[] annotations = methodParameter.getParameterAnnotations();
      
  7. getParameterName()

    • 描述:获取方法参数的名称(需要编译时开启 -parameters 选项)。

    • 示例:

      String parameterName = methodParameter.getParameterName();
      
  8. 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());
    }
}
  1. @Configuration 注解标识了这是一个配置类,Spring会在应用程序启动时读取并加载这个配置类,并根据其中的配置信息来完成相应的初始化操作。
  2. @EnableWebMvc 注解用于启用Spring MVC框架的基本功能,它会自动配置一些默认的Bean,例如处理器映射器(HandlerMapping)、处理器适配器(HandlerAdapter)、视图解析器(ViewResolver)等。
  3. WebMvcConfig 类中定义了一个名为 currUserArgumentResolver 的Bean,它是 UserHandlerMethodArgumentResolver 的实例化对象,并被注解为 @Bean。通过 @Bean 注解,我们告诉Spring容器应该如何创建这个Bean,并将其纳入到Spring的应用上下文中。
  4. 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);
	}
}

2.5.结果

image-20240512130919694

  • 21
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
HandlerMethodArgumentResolver是Spring MVC中的一个接口,用于解决方法参数代入参数值在给定请求的上下文中的问题。它可以将请求中的参数值转换为方法参数类型,并将其注入到方法中。以下是HandlerMethodArgumentResolver的使用方法: 1.创建一个类实现HandlerMethodArgumentResolver接口,并实现supportsParameter和resolveArgument方法。supportsParameter方法用于判断该解析器是否支持该参数类型,resolveArgument方法用于将请求中的参数值转换为方法参数类型。 2.在Spring MVC配置文件中配置HandlerMethodArgumentResolver,将其添加到RequestMappingHandlerAdapter中。 下面是一个简单的例子,演示如何使用HandlerMethodArgumentResolver: ```java // 自定义参数解析器 public class UserArgumentResolver 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 { HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(); String username = request.getParameter("username"); String password = request.getParameter("password"); User user = new User(username, password); return user; } } // 在Spring MVC配置文件中配置HandlerMethodArgumentResolver @Configuration @EnableWebMvc public class AppConfig implements WebMvcConfigurer { @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { argumentResolvers.add(new UserArgumentResolver()); } } // 控制器方法 @RequestMapping("/login") public String login(User user) { // 处理登录逻辑 return "success"; } ``` 在上面的例子中,我们创建了一个UserArgumentResolver类,用于将请求中的参数值转换为User对象,并将其注入到login方法中。然后在Spring MVC配置文件中配置了UserArgumentResolver,并将其添加到RequestMappingHandlerAdapter中。最后,在login方法中,我们可以直接使用User对象,而不需要手动解析请求参数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值