1.问题
接上回研究全局异常处理器的过程中,发现spring是通过参数解析器(HandlerMethodArgumentResolver)解析全局异常处理器的参数的。本文稍微研究一下参数解析器;
2.调用过程
调用全局异常处理方法的过程分为如下三步。本文主要研究方法处理器(HandlerMethod),后续两篇研究参数解析器(HandlerMethodArgumentResolver)和返回值处理器(HandlerMethodReturnValueHandler)
- 创建方法处理器(HandlerMethod),解析方法的参数,并处理返回结果;
- 方法处理器委托参数解析器(HandlerMethodArgumentResolver)解析方法的参数;
- 方法处理器委托返回值处理器(HandlerMethodReturnValueHandler)处理方法的返回结果;
3.使用示例(重点)
在全局全局异常处理器中使用各种类型的参数;
3.1.默认参数解析器
- request由ServletRequestMethodArgumentResolver解析,默认可以使用;
- @RequestParam由RequestParamMapMethodArgumentResolver解析,默认不可以使用。需要通过WebMvcConfigurationSupport的addArgumentResolvers方法添加;
/**
* 测试全局异常处理器定义参数
*/
@RestControllerAdvice
public class GoldjetInterfacePlatformExceptionHander extends AppExceptionHandler {
//**request由ServletRequestMethodArgumentResolver解析,默认可以使用
//**@RequestParam由RequestParamMapMethodArgumentResolver解析,默认不可以使用,需要手动添加
@ExceptionHandler({MyException.class})
public ResultDTO handleRRException(AppException e, ServletRequest request, @RequestParam Map map) {
return ResultDTO.error(e.getCode(), e.getMsg());
}
}
/**
* 测试全局异常处理器添加参数解析器
* spring的大部分配置都是由WebMvcConfigurationSupport处理。重写此类,应用自定义的配置
*/
@Configuration
public class MyConfig extends WebMvcConfigurationSupport {
//**全局异常处理器添加@RequestParam参数解析器
@Override
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
val resolver = new RequestParamMapMethodArgumentResolver();
argumentResolvers.add(resolver);
}
}
3.2.自定义参数解析器
- 研究了一下参数解析器(HandlerMethodArgumentResolver)后,发现实现WebArgumentResolver接口是实现自定义参数解析器更好的选择,具体可以看看下一篇;
- 本文实现功能:在全局异常处理方法中,如果参数是自定义类(Test)则调用spring的service组件,并获取Test对象的功能;
A.全局异常处理器
增加一个自定义参数类型(Test);
//**全局异常处理器
@RestControllerAdvice
class MyExceptionHandler {
@ExceptionHandler({MyException.class})
public Map handleMyException(MyException e, ServletRequest request, @RequestParam Map map, Test test) {
val rst = new HashMap<>();
rst.put("code", "500");
rst.put("msg", e.getMessage());
rst.put("request", request.getServerName());
rst.put("map", map);
rst.put("test", test);
return rst;
}
}
B.自定义WebArgumentResolver
如果目标参数类型为Test,则调用TestService获取Test对象;
/**
* 自定义WebArgumentResolver
* WebArgumentResolver是一个函数式接口,是spring预留用于解析用户自定义参数类型的SPI。
* 使用该接口可以很方便的自定义参数解析器,解析自定义的参数类型。
* 我们应该优先考虑该接口扩展自定义参数解析器;
*
* 本文实现调用其它spring组件,获取内存中数据的功能
*/
public class MyWebArgumentResolver implements WebArgumentResolver {
//**如果目标参数类型为Test,则调用TestService获取Test对象
@Nullable
@Override
public Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest) throws Exception {
if(methodParameter.getParameterType().equals(Test.class)) {
TestService service = (TestService)ApplicationContextUtil.getBean("testService");
return service.getTest(methodParameter.getParameterName());
}
return null;
}
}
//**自定类型
@Data
class Test {
private String name;
private int age;
}
@Slf4j
@Service
class TestService {
//**自定义参数解析器调用该方法,获取Test实例
public Test getTest(String parameterName) {
log.info("参数名:{}", parameterName);
val rst = new Test();
rst.setName("张三");
rst.setAge(33);
return rst;
}
}
C.注册MyWebArgumentResolver
ServletWebArgumentResolverAdapter和自定义WebArgumentResolver配合使用,实现自定义参数解析;
/**
* 测试全局异常处理器添加参数解析器
* spring的大部分配置都是由WebMvcConfigurationSupport处理。重写此类,应用自定义的配置
*/
@Configuration
public class MyConfig extends WebMvcConfigurationSupport {
//**全局异常处理器添加@RequestParam参数解析器
@Override
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
//**解析目标方法@RequestParam标注的参数
val resolver1 = new RequestParamMapMethodArgumentResolver();
//**自定义WebArgumentResolver
val myResolver = new MyWebArgumentResolver();
//**ServletWebArgumentResolverAdapter和自定义WebArgumentResolver配合使用,实现自定义参数解析
val resolver2 = new ServletWebArgumentResolverAdapter(myResolver);
argumentResolvers.add(resolver1);
argumentResolvers.add(resolver2);
}
}
4.方法处理器简介
4.1.类结构
4.2.运行过程
本文以调用全局异常处理器的方法为例,讲解调用过程(参数解析器和返回值处理器都使用组合模式):
- ExceptionHandlerExceptionResolver异常处理器为异常处理方法创建方法处理器ServletInvocableHandlerMethod(HandlerMethod的子类),并为方法处理器设置参数解析器(HandlerMethodArgumentResolver)和返回值处理器(HandlerMethodReturnValueHandler);
- 调用ServletInvocableHandlerMethod的invokeAndHandle();
- invokeAndHandle()调用组合的参数解析器(HandlerMethodArgumentResolverComposite)解析异常处理方法的参数;
- invokeAndHandle()调用异常处理方法并获取返回值;
- invokeAndHandle()把http响应状态写入到响应中(如果异常处理方法配置了@ResponseStatus注解);
- invokeAndHandle()调用组合返回值处理器(HandlerMethodReturnValueHandlerComposite)处理返回结果;
5.方法处理器源码
5.1.HandlerMethod方法处理器
spring对方法的封装,包含方法及所属的bean,参数,参数的注解等;
//**方法处理器(spring对方法的封装,包含方法及所属的bean,参数,参数的注解等)
public class HandlerMethod {
//**方法所在的bean,可以是bean对象/bean名称
private final Object bean;
//**如果是bean名称,使用beanFactory获取bean实例
@Nullable
private final BeanFactory beanFactory;
//**bean的类对象
private final Class<?> beanType;
//**方法
private final Method method;
//**桥接方法,如果是普通的java方法,则和上面一样
private final Method bridgedMethod;
//**方法参数
private final MethodParameter[] parameters;
//**响应状态
@Nullable
private HttpStatus responseStatus;
//**响应状态描述(异常状态原因)
@Nullable
private String responseStatusReason;
@Nullable
private HandlerMethod resolvedFromHandlerMethod;
//**该方法实现的接口抽象方法中的参数注解
@Nullable
private volatile List<Annotation[][]> interfaceParameterAnnotations;
private MethodParameter[] initMethodParameters() {
int count = this.bridgedMethod.getParameterCount();
MethodParameter[] result = new MethodParameter[count];
for (int i = 0; i < count; i++) {
HandlerMethodParameter parameter = new HandlerMethodParameter(i);
GenericTypeResolver.resolveParameterType(parameter, this.beanType);
result[i] = parameter;
}
return result;
}
//**获取方法上@ResponseStatus配置的响应状态及异常状态原因(包括父类中的配置)
private void evaluateResponseStatus() {
ResponseStatus annotation = getMethodAnnotation(ResponseStatus.class);
if (annotation == null) {
annotation = AnnotatedElementUtils.findMergedAnnotation(getBeanType(), ResponseStatus.class);
}
if (annotation != null) {
this.responseStatus = annotation.code();
this.responseStatusReason = annotation.reason();
}
}
//**根据bean创建新的HandlerMethod对象
public HandlerMethod createWithResolvedBean() {
Object handler = this.bean;
//**如果是bean名称,使用beanFactory获取bean实例
if (this.bean instanceof String) {
Assert.state(this.beanFactory != null, "Cannot resolve bean name without BeanFactory");
String beanName = (String) this.bean;
handler = this.beanFactory.getBean(beanName);
}
return new HandlerMethod(this, handler);
}
//**获取方法实现的接口抽象方法中的参数注解
private List<Annotation[][]> getInterfaceParameterAnnotations() {
List<Annotation[][]> parameterAnnotations = this.interfaceParameterAnnotations;
if (parameterAnnotations == null) {
parameterAnnotations = new ArrayList<>();
for (Class<?> ifc : this.method.getDeclaringClass().getInterfaces()) {
for (Method candidate : ifc.getMethods()) {
if (isOverrideFor(candidate)) {
parameterAnnotations.add(candidate.getParameterAnnotations());
}
}
}
this.interfaceParameterAnnotations = parameterAnnotations;
}
return parameterAnnotations;
}
}
5.2.InvocableHandlerMethod方法处理器
扩展解析方法参数并调用方法的功能,具体过程如下;
- 获取目标方法的参数列表对象(参数类型等);
- 调用invokeForRequest()时如果传入了目标方法的参数值(规则:传入的参数对象是目标接口某个参数的实例),则以传入的参数值为准;
- 如果没有传入目标方法的参数值,则调用参数解析器解析目标接口的参数值;
public class InvocableHandlerMethod extends HandlerMethod {
//**空参数
private static final Object[] EMPTY_ARGS = new Object[0];
//**数据绑定工厂(获取数据绑定器)
@Nullable
private WebDataBinderFactory dataBinderFactory;
//**参数解析器(组合了其它的参数解析器)
private HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite();
//**获取方法参数名
private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
//**根据request对象解析方法参数并调用该方法
@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//**获取目标方法的参数列表
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
...
//**调用目标方法
return doInvoke(args);
}
//**解析方法参数
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//**获取目标方法的参数对象
MethodParameter[] parameters = getMethodParameters();
//**目标方法没有参数,则返回空参数对象
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
}
Object[] args = new Object[parameters.length];
//**遍历参数对象
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
//**如果providedArgs中有与目标接口参数类型相同的参数,则直接当成目标接口的参数
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
//**如果参数解析器不支持目标接口的参数,则抛异常
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try { //**使用参数解析器解析目标接口的参数值
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
...
}
return args;
}
//**调用方法
@Nullable
protected Object doInvoke(Object... args) throws Exception {
//**设置可见性
ReflectionUtils.makeAccessible(getBridgedMethod());
try { //**根据参数列表,调用该方法,并把结果返回
return getBridgedMethod().invoke(getBean(), args);
}
...
}
}
5.3.ServletInvocableHandlerMethod方法处理器
扩展处理方法返回值的功能,具体过程如下:
- 调用父类的invokeForRequest()方法,解析目标方法的参数值,并调用目标方法,获取返回值;
- 如果目标方法及其父类的方法上配置了@ResponseStatus注解,则把http响应状态写入到响应中;
- 如果目标方法有返回值,则调用返回值处理器处理返回值;
- 如果目标方法是异步方法,可以使用返回值调用wrapConcurrentResult方法,获取新的ConcurrentResultHandlerMethod异步方法处理器。以支持调用异步方法并获取返回值;
public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
//**返回值处理器(组合了其它的返回值处理器)
@Nullable
private HandlerMethodReturnValueHandlerComposite returnValueHandlers;
//**
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//**调用父类的invokeForRequest方法,根据request对象解析方法参数并调用该方法
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
//**写入响应状态
setResponseStatus(webRequest);
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {//**调用返回值处理器处理目标方法的返回值
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
...
}
//**把响应状态和异常状态描述写入到HttpServletResponse中,并记录到request对象中
private void setResponseStatus(ServletWebRequest webRequest) throws IOException {
HttpStatus status = getResponseStatus();
if (status == null) {
return;
}
HttpServletResponse response = webRequest.getResponse();
if (response != null) {
String reason = getResponseStatusReason();
if (StringUtils.hasText(reason)) {
response.sendError(status.value(), reason);
}
else {
response.setStatus(status.value());
}
}
// To be picked up by RedirectView
webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, status);
}
//**获取新的ConcurrentResultHandlerMethod异步方法处理器。支持异步方法调用
ServletInvocableHandlerMethod wrapConcurrentResult(Object result) {
return new ConcurrentResultHandlerMethod(result, new ConcurrentResultMethodParameter(result));
}
}
5.4.ConcurrentResultHandlerMethod异步方法处理器
扩展调用异步方法的功能。ConcurrentResultHandlerMethod是ServletInvocableHandlerMethod的内部类,且是私有的。实际是为ServletInvocableHandlerMethod扩展调用异步方法的功能。具体过程如下:
- 把ServletInvocableHandlerMethod调用目标方法获取的返回值(通常是异步方法的返回值)封装成一个Callable;
- 根据Callable创建ConcurrentResultHandlerMethod异步方法处理器,该方法处理器的目标方法即为Callable方法;
private class ConcurrentResultHandlerMethod extends ServletInvocableHandlerMethod {
private static final Method CALLABLE_METHOD = ClassUtils.getMethod(Callable.class, "call");
private final MethodParameter returnType;
/*
* 把ServletInvocableHandlerMethod调用目标方法获取的返回值封装成一个Callable
* 根据Callable创建ConcurrentResultHandlerMethod异步方法处理器
*/
public ConcurrentResultHandlerMethod(final Object result, ConcurrentResultMethodParameter returnType) {
super((Callable<Object>) () -> {
if (result instanceof Exception) {
throw (Exception) result;
}
else if (result instanceof Throwable) {
throw new NestedServletException("Async processing failed", (Throwable) result);
}
return result;
}, CALLABLE_METHOD);
if (ServletInvocableHandlerMethod.this.returnValueHandlers != null) {
setHandlerMethodReturnValueHandlers(ServletInvocableHandlerMethod.this.returnValueHandlers);
}
this.returnType = returnType;
}
}