Hi I’m Shendi
SpringBoot接口传递自定义参数,参数解析器
简介
我的需求:编写了一个日志微服务,使用方式是 创建日志对象 - 日志流程 - 完成日志对象,这样的方式使用时就需要在每个接口都去创建和完成一下,多出了一点代码。
在 SpringBoot 中,我们接收接口的参数都是直接写在函数参数上,例如传递了一个name
@GetMapping("/test")
public String test(String name) {
return name;
}
于是就想到能不能像上面这种方式将创建和完成封装起来,就开始寻找解决方案。
之前一直使用的是过滤器,但我的需求过滤器是没有办法实现的,过滤器可以给请求注入字符串,但不能注入对象
例如 User 类,想要的效果如下
@GetMapping("/test")
public String test(User user) {
return user.toString();
}
后面学了下拦截器,发现拦截器也不行…
后面发现可以使用addArgumentResolvers来实现接口增加参数,自定义参数解析器
编写实现类
可以继承以下两个接口
- WebArgumentResolver
- HandlerMethodArgumentResolver
这两个接口都是用来处理控制器方法参数的接口。不同之处在于:
- HandlerMethodArgumentResolver是在Spring 3.1之后引入的,用于处理注解控制器方法参数。它是一个更加灵活、更加强大的解决方案,可以处理更多的操作,例如类型转换、注入依赖、权限验证等等。它可以用于处理@RequestParam、@PathVariable、@RequestBody、@RequestHeader等注解。
- WebArgumentResolver是在Spring 3.0和3.1版本中都有,但在3.1版本中已经被HandlerMethodArgumentResolver所取代。它主要用于处理旧版的控制器方法参数,例如Servlet API中的HttpServletRequest和HttpServletResponse对象。但是它缺乏HandlerMethodArgumentResolver的灵活性,不能处理更为复杂的场景。
对于新项目而言,应该优先使用HandlerMethodArgumentResolver,可以获得更加灵活、强大、可扩展的参数处理能力。
本文就使用 HandlerMethodArgumentResolver
来实现给所有接口注入参数
实现HandlerMethodArgumentResolver接口,需要实现两个函数
public class TestResolve implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
// TODO Auto-generated method stub
return false;
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
// TODO Auto-generated method stub
return null;
}
}
supportsParameter方法用于判断参数是否符合特定的类型,符合则执行resolveArgument
而resolveArgument方法则用于将从请求中获取的参数值转换为特定的参数类型,从而将其作为参数传递给方法。(返回什么对象那么接口中使用的就是什么对象,所以需要先判断对象类型再做操作,类型不同则报错)
在配置类注册
编写一个类实现 WebMvcConfigurer,这个在使用拦截器时也是用这个配置
WebMvcConfigurer是一个Spring框架中的配置接口,用于配置Spring MVC的默认行为和定制化处理程序。它定义了多个方法,包括添加资源处理器、拦截器和视图控制器等。
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import shendi.resolve.TestResolve;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new TestResolve());
}
}
测试使用
给Resolver类改一下,输出类型
public class TestResolve implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
System.out.println("supportsParameter:" + parameter.getParameterType());
return true;
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
System.out.println(parameter.getParameterType());
return null;
}
}
可以看到 supportsParameter
返回 true,那么就会执行 resolveArgument
随便写个接口,函数接收两个参数(类型需要是当前项目的类,不然无效或者报错)
例如测试接口如下
public static class User {
public String account;
public String pwd;
public String name;
}
@GetMapping("/")
public String get(String test, User user) {
System.out.println("接口执行");
return "test";
}
函数有两个参数,一个String一个User,运行,请求接口,控制台输出如下
将 supportsParameter
返回值改为 false,测试结果如下
可以很明显的看到区别。
参数注入
根据上面的部分,已经实现了参数的注入了,只不过注入的是null
我们可以在 supportsParameter
判断是否是我们需要注入的类,是则返回 true,否则false
@Override
public boolean supportsParameter(MethodParameter parameter) {
// 判断相等可以直接用equals或者==,不过isAssignableFrom是本地(native)函数,可以判断是否有继承关系
return parameter.getParameterType().isAssignableFrom(User.class);
}
然后在 resolveArgument
创建实例返回
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
// 如果当前有多个类支持的话,就需要用if判断是哪个类,没有则可直接创建对象返回
if (parameter.getParameterType().isAssignableFrom(User.class)) {
User user = new User();
user.account = "shendi";
return user;
}
return null;
}
这样,可以在接口直接使用 user 对象了
@GetMapping("/")
public String get(String test, User user) {
System.out.println("接口执行: " + user.account);
return "test";
}
配合过滤器实现我的需求
在这里再说下我的需求
我的需求:编写了一个日志微服务,使用方式是 创建日志对象 - 日志流程 - 完成日志对象,这样的方式使用时就需要在每个接口都去创建和完成一下,多出了一点代码。
根据上面那部分,已经可以给接口注入参数了,因为我的需求是日志使用,在过滤器中也需要使用,所以配合过滤器
我的理解,过滤器 - 接口,接口中解析参数时才用参数解析器,于是直接将对象放入请求的attribute,然后在解析器拿到返回就可以了
代码如下
@Override
protected void doFilter(HttpServletRequest req, HttpServletResponse resp, FilterChain chain) throws IOException, ServletException {
OLog olog = null;
try {
String id = ReqUtil.getUserIdStr(req);
if (id == null) {
olog = new OLog("接口", uri);
} else {
olog = new OLog("接口", uri, Integer.parseInt(id));
}
req.setAttribute("olog", olog);
chain.doFilter(req, resp);
olog.finish();
} catch (Exception e) {
e.printStackTrace();
if (olog != null) olog.finish(OLog.RESULT_NOOK);
}
}
public class ArgumentResolve implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().isAssignableFrom(OLog.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
// 如果当前有多个类支持的话,就需要判断是哪个类
if (parameter.getParameterType().isAssignableFrom(OLog.class)) {
return webRequest.getAttribute("olog", NativeWebRequest.SCOPE_REQUEST);
}
return null;
}
}
END