一、背景

今天在做项目时遇到了一个有关参数解析 HandlerMethodArgumentResolver 的使用疑惑。因此去 csdn 学习了一下,现在记录一下。

二、参数解析器

想要自定义参数解析器,就要实现HandlerMethodArgumentResolver接口,而它是springMvc下的一个接口,引入spring-web的starter就可见了。

接口内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package org.springframework.web.method.support;

import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;

/**
* Strategy interface for resolving method parameters into argument values in
* the context of a given request.
*
* @author Arjen Poutsma
* @since 3.1
* @see HandlerMethodReturnValueHandler
*/
public interface HandlerMethodArgumentResolver {

boolean supportsParameter(MethodParameter parameter);

@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;

}

可以看到这个接口有两个方法,分别为supportsParameterresolveArgument

supportsParameter()

它很好理解,返回值是boolean类型,它的作用是判断Controller层中的参数,是否满足条件,满足条件则执行resolveArgument方法,不满足则跳过。

resolveArgument()

它只有在supportsParameter方法返回true的情况下才会被调用。用于处理一些业务,将返回值赋值给Controller层中的对应的参数。

三、实战

未使用参数解析器时的Controller控制器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
* @author Blue 2113438464@qq.com
* @ClassName TestController
* @Description 测试
* @date 2022/7/14 19:23
* @Version 1.0
*/
@Controller
@RequestMapping("/test")
@RequiredArgsConstructor
public class TestController {

// 日志打印,如导入了lombok也可使用@Slf4j注解
private static Logger logger = LoggerFactory.getLogger(GoodsController.class);

// 构造注入
private final UserService userService;

/**
* 根据用户id获取 内外销,供应商编号
* @param Model 模型
* @param response 响应
* @param cookieToken Token
* @param paramToken Token
* @return 页面路径
*/
@RequestMapping("/toList")
public String toList(Model model,HttpServletResponse response,
@CookieValue(value = UserService.TOKEN,required = false)String cookieToken,
@RequestParam(value = UserService.TOKEN,required = false) String paramToken) {
// 参数安全比较,是否存在Token
if(StringUtils.isEmpty(cookieToken) && StringUtils.isEmpty(paramToken)){
return "login";
}
// 拿取token
String token = StringUtils.isEmpty(cookieToken) ? paramToken : cookieToken;
// 业务
User user = userService.getByToken(response,token);
// 日志
logger.info(user.toString());
// 将数据添加到模型对象中
model.addAttribute("user",user);
// 返回
return "goodsList";
}

}

上面类的toList()方法有两个参数分别为 cookieTokenparamToken ,再仔细看一下方法体,不难看出,这两个参数和方法体中的代码就是为了,通过cookie或request其中的token这一变量来查询用户user的信息。

但是大家发没发现,这一个方法,就为了简简单单的这一个小功能,就写了这么多代码,是不是有点过分臃肿呢?而且假如还有其他方法会用到用户user这一信息变量呢,难道也写这么多的代码吗?

好的,这个时候我们就可以使用 HandlerMethodArgumentResolver 接口来进行一下小小的优化。


使用 HandlerMethodArgumentResolver 解析器后:

写一个类实现 HandlerMethodArgumentResolver 接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// 自定义参数解析器
@Component
@RequiredArgsConstructor
public class UserArgumentResolver implements HandlerMethodArgumentResolver {

// 构造注入
private final MiaoshaUserService userService;

// 满足条件则执行resolveArgument方法,不满足则跳过
public boolean supportsParameter(MethodParameter parameter) {
// MethodParameter类是专门用来获取方法参数、参数名等功能的类
Class<?> clazz = parameter.getParameterType();
// 判断参数类型
return clazz == MiaoshaUser.class;
}

// 处理Controller层中的对应的参数
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
// 请求
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
// 响应
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
// 获取参数
String paramToken = request.getParameter(MiaoshaUserService.COOKI_NAME_TOKEN);
// MiaoshaUserService.COOKI_NAME_TOKEN为我自定义的常量,就是token名
String cookieToken = getCookieValue(request, MiaoshaUserService.COOKI_NAME_TOKEN);
// 参数安全比较,是否存在Token
if (StringUtils.isEmpty(cookieToken) && StringUtils.isEmpty(paramToken)) {
return null;
}
// 拿取token
String token = StringUtils.isEmpty(paramToken) ? cookieToken : paramToken;
// 返回User对象
return userService.getByToken(response, token);
}

// 获取Cookie
private String getCookieValue(HttpServletRequest request, String cookiName) {
// 拿到所有Cookie信息
Cookie[] cookies = request.getCookies();
// 循环比较
for(Cookie cookie : cookies) {
if(cookie.getName().equals(cookiName)) {
// 有就返回
return cookie.getValue();
}
}
// 返回为空
return null;
}
}

把我们编写的这个类,注册到配置文件中去:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
@RequiredArgsConstructor
public class WebConfig extends WebMvcConfigurerAdapter{

// 构造注入
private final UserArgumentResolver userArgumentResolver;

@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
// 注册自定义参数解析器
argumentResolvers.add(userArgumentResolver);
}

}

最后,我们的控制类就可以这样写了

1
2
3
4
5
6
7
8
9
10
11
@RequestMapping("/toList")
public String toList(Model model,MiaoshaUser user) {
// 直接拿User对象
model.addAttribute("user", user);
// 查询商品列表
List<GoodsVo> goodsList = goodsService.listGoodsVo();
// 添加
model.addAttribute("goodsList", goodsList);
// 返回
return "goodsList";
}

以此类推,可以做很多有用的代码优化