springmvc中常见的请求拦截器
- javax.servlet.Filter 的实现(默认tomcat的实现)
- org.springframework.web.servlet.HandlerInterceptor
那么Filter是在更外层,也就是在请求目标方法之前先执行Filter的拦截器,再执行HandlerInterceptor
那么当这两个拦截器都不满足某些场景,而这个场景又想要更靠近目标方法执行时机的时候,就想要找到一个更靠近请求目标方法的扩展点,下述为寻找思路。
首先从HandlerInterceptor#preHandle入手
直接看这个方法由谁调用,如果没有下载对应源码包,可以通过debug查看
直接找到堆栈 可以看到preHandle和postHandle的执行
// 将要调用目标方法
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
跟踪堆栈后进入AbstractHandlerMethodAdapter#handle ,并且当前实例是RequestMappingHandlerAdapter
AbstractHandlerMethodAdapter#handle
继续跟踪
AbstractHandlerMethodAdapter#handleInternal
继续跟踪
RequestMappingHandlerAdapter#invokeHandlerMethod
然后我们可以看到执行目标方法 又委托给了ServletInvocableHandlerMethod
然后委托给invokeForRequest方法 再到doInvoke,走到这里我们已经看到invokeForRequest中的逻辑,先通过
getMethodArgumentValues获取到目标方法的参数,然后真正的执行doInvoke方法带上了参数,可以预计到doInvoke方法已经是非常靠近执行目标方法了,所有准备工作已经完成,参数也构造完了,并且所有方法public,protect也都没有final,也就是这些方法都可以被重写!
如果是重写的话doInvoke是一个比较好的时机,因为目标方法的参数也构造完毕。并且protect还不带final真的太像是设计为被重写的方法一样。。。
@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
return doInvoke(args);
}
@Nullable
protected Object doInvoke(Object... args) throws Exception {
Method method = getBridgedMethod();
try {
if (KotlinDetector.isSuspendingFunction(method)) {
return CoroutinesUtils.invokeSuspendingFunction(method, getBean(), args);
}
return method.invoke(getBean(), args);
}
catch (IllegalArgumentException ex) {
assertTargetBean(method, getBean(), args);
String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
throw new IllegalStateException(formatInvokeError(text, args), ex);
}
catch (InvocationTargetException ex) {
// Unwrap for HandlerExceptionResolvers ...
Throwable targetException = ex.getTargetException();
if (targetException instanceof RuntimeException) {
throw (RuntimeException) targetException;
}
else if (targetException instanceof Error) {
throw (Error) targetException;
}
else if (targetException instanceof Exception) {
throw (Exception) targetException;
}
else {
throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException);
}
}
}
如上述重写 doInvoke那么我们需要能够继承到当前类InvocableHandlerMethod
突然又想到了上面debug过程中看到了构建InvocableHandlerMethod这一行代码如下
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
// 如果经常阅读源码,上面所描述的 doInvoke是protect非final并且这里又构建了对应实例,这就很明显了写代码的人就是这样设计的,继续进入这个方法看个究竟
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
// 省略代码 ...
那么我们进入到 createInvocableHnadlerMethod方法查看
// 看到这里,继续是protect,非final,方法名,以及这简单粗暴的return一个new 实例,坚定了前面的所有想法!
protected ServletInvocableHandlerMethod createInvocableHandlerMethod(HandlerMethod handlerMethod) {
return new ServletInvocableHandlerMethod(handlerMethod);
}
到这里我们已经非常确定以及肯定,doInvoke 以及ServletInvocableHandlerMethod 实例获取方法createInvocableHandlerMethod就是被设计为让我们可以扩展的一系列操作
当前实例为RequestMappingHandlerAdapter,那么我们就需要继承它来重写RequestMappingHandlerAdapter#createInvocableHandlerMethod就可以返回一个我们自己的ServletInvocableHandlerMethod来重写ServletInvocableHandlerMethod#doInvoke
查找RequestMappingHandlerAdapter实例如果被创建
还是两种方式,如果下载了对应的包源码直接ctrl点击,或者debug到。
// 很明显的一点是这个类是启动时就实例化了,InitializingBean这个接口是spring的bean注入过程中的钩子之一(大部分带有spring钩子的类都是启动时注入spring容器,真的很少很少见动态注入)
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
implements BeanFactoryAware, InitializingBean
我们直接debug构造方法
RequestMappingHandlerAdapter 构造方法
直接来到 WebMvcConfigurationSupport#createRequestMappingHandlerAdapter
和上面的那些方法一样,protect,非final,并且简单粗暴,看起来就是让我们重写的啊,那么我们就先切入这个点是否可以重写
WebMvcConfigurationSupport
上面忘了说一点,最主要的是要看每个类的注释,尤其是类上面的注释,多多少少可以理解作者的设计意图,以及相关联的其他类
WebMvcConfigurationSupport
上图可以看到一个非常熟悉的东西,WebMvcConfigurer springmvc的各种扩展点入口。既然WebMvcConfigurationSupport标准了可能有所关联,先看一眼。结果就是,没啥可以重写WebMvcConfigurationSupport的迹象。那么我们换个思路
如果你有下载了很多包的源码可以看到 WebMvcConfigurationSupport#createRequestMappingHandlerAdapter已经被重写了
image.png
或者找到堆栈的上一个方法调用
WebMvcAutoConfiguration
@Override
protected RequestMappingHandlerAdapter createRequestMappingHandlerAdapter() {
// 这里我们眼前一亮,终于找到了真正的扩展点!
if (this.mvcRegistrations != null) {
RequestMappingHandlerAdapter adapter = this.mvcRegistrations.getRequestMappingHandlerAdapter();
if (adapter != null) {
return adapter;
}
}
// 这里就是WebMvcConfigurationSupport#createRequestMappingHandlerAdapter 目前走的就是这个case
return super.createRequestMappingHandlerAdapter();
}
如下在EnableWebMvcConfiguration的构造方法中可以看到 mvcRegistrations 我们只需要注入这个实现即可
private final WebMvcRegistrations mvcRegistrations;
private ResourceLoader resourceLoader;
public EnableWebMvcConfiguration(WebMvcProperties mvcProperties, WebProperties webProperties,
ObjectProvider<WebMvcRegistrations> mvcRegistrationsProvider,
ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
ListableBeanFactory beanFactory) {
this.resourceProperties = webProperties.getResources();
this.mvcProperties = mvcProperties;
this.webProperties = webProperties;
this.mvcRegistrations = mvcRegistrationsProvider.getIfUnique();
this.beanFactory = beanFactory;
}
我们的重写链路已经浮现!WebMvcRegistrations(子类)#getRequestMappingHandlerAdapter返回RequestMappingHandlerAdapter(子类) --> RequestMappingHandlerAdapter(子类)#createInvocableHandlerMethod返回ServletInvocableHandlerMethod(子类) --> ServletInvocableHandlerMethod(子类)重写 doInovke方法即可
下面看代码
定义一个观察者接口由客户端程序可插入逻辑
import org.springframework.web.method.HandlerMethod;
/**
* @author ASDL3DL00676
*/
public interface ServletInvocableHandlerMethodInterceptor extends Ordered {
/**
* 通过参数来决定是否执行
*
* @param args args
* @param handlerMethod 目标方法
* @return boolean
*/
boolean supports(HandlerMethod handlerMethod, Object[] args);
/**
* 需要目标方法考虑异常
* 前置方法
*
* @param args args
* @param handlerMethod 目标方法
*/
default void beforeInvoke(HandlerMethod handlerMethod, Object[] args) {
}
/**
* 异常时
*
* @param args args
* @param throwable 抛出的异常
* @param handlerMethod 目标方法
*/
default void afterThrows(HandlerMethod handlerMethod, Object[] args, Throwable throwable) {
}
/**
* 成功时需要目标方法考虑异常
*
* @param args args
* @param result 结果值
* @param handlerMethod 目标方法
*/
default void afterSuccess(HandlerMethod handlerMethod, Object[] args, Object result) {
}
/**
* 函数式包裹
*
* @param target 目标方法
* @param handlerMethod controller 代理的方法
* @param args 参数
* @return 结果
*/
default Supplier<Object> wrapperDoInvoke(Supplier<Object> target, HandlerMethod handlerMethod,
Object[] args) {
return target;
}
/**
* 默认排序
*
* @return int
*/
@Override
default int ordered() {
return 0;
}
}
初始化WebMvcRegistrations 注入spring
@Bean
// 同时允许依赖当前jar的业务服务可以继续重写,如果想要保留当前jar的逻辑需要继承这里的WebMvcRegistrationsSimpleImpl
@ConditionalOnMissingBean
public WebMvcRegistrations customWebMvcRegistrations(
@Autowired(required = false) List<ServletInvocableHandlerMethodInterceptor> interceptors) {
if (CollectionUtils.isNotEmpty(interceptors)) {
interceptors.sort(Comparator.comparing(ServletInvocableHandlerMethodInterceptor::ordered));
}
return new WebMvcRegistrationsSimpleImpl(interceptors);
}
重写WebMvcRegistrations 并带上观察者
import java.util.List;
import javax.annotation.Nullable;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
/**
* @author ASDL3DL00676
*/
@RequiredArgsConstructor
public class WebMvcRegistrationsSimpleImpl implements WebMvcRegistrations {
@Nullable
private final List<ServletInvocableHandlerMethodInterceptor> interceptors;
/**
* Return the custom {@link RequestMappingHandlerAdapter} that should be used and
* processed by the MVC configuration.
*
* @return the custom {@link RequestMappingHandlerAdapter} instance
*/
@Override
public RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() {
return new RequestMappingHandlerInterceptorImpl(interceptors);
}
}
重写RequestMappingHandlerAdapter 并带上观察者
import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import lombok.RequiredArgsConstructor;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod;
/**
* @author ASDL3DL00676
*/
@RequiredArgsConstructor
public class RequestMappingHandlerInterceptorImpl extends RequestMappingHandlerAdapter {
@Nullable
private final List<ServletInvocableHandlerMethodInterceptor> interceptors;
@Nonnull
@Override
protected ServletInvocableHandlerMethod createInvocableHandlerMethod(@Nonnull HandlerMethod handlerMethod) {
return new ServletInvocableHandlerMethodWithPlugin(handlerMethod, interceptors);
}
}
重写ServletInvocableHandlerMethod 并带上观察者,对doInvoke方法提供多种拦截,以及函数包裹逻辑
import java.util.Arrays;
import java.util.List;
import javax.annotation.Nonnull;
import lombok.EqualsAndHashCode;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.lang.Nullable;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod;
/**
* @author ASDL3DL00676
*/
@EqualsAndHashCode(callSuper = true)
public class ServletInvocableHandlerMethodWithPlugin extends ServletInvocableHandlerMethod {
@javax.annotation.Nullable
private final List<ServletInvocableHandlerMethodInterceptor> interceptors;
private final HandlerMethod currentMethod;
public ServletInvocableHandlerMethodWithPlugin(
HandlerMethod handlerMethod,
@Nullable List<ServletInvocableHandlerMethodInterceptor> interceptors) {
super(handlerMethod);
this.interceptors = interceptors;
this.currentMethod = handlerMethod;
}
@Nullable
@Override
public Object invokeForRequest(
@Nonnull NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
@Nonnull Object... providedArgs) throws Exception {
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
try {
Supplier<Object> target = () -> doInvoke(args);
if (CollectionUtils.isNotEmpty(interceptors)) {
for (ServletInvocableHandlerMethodInterceptor interceptor : interceptors) {
if (interceptor.supports(currentMethod, args)) {
interceptor.beforeInvoke(currentMethod, args);
target = interceptor.wrapperDoInvoke(target, currentMethod, args);
}
}
}
Object result = target.get();
if (CollectionUtils.isNotEmpty(interceptors)) {
for (ServletInvocableHandlerMethodInterceptor interceptor : interceptors) {
if (interceptor.supports(currentMethod, args)) {
interceptor.afterSuccess(currentMethod, args, result);
}
}
}
return result;
} catch (Throwable throwable) {
if (CollectionUtils.isNotEmpty(interceptors)) {
for (ServletInvocableHandlerMethodInterceptor interceptor : interceptors) {
if (interceptor.supports(currentMethod, args)) {
interceptor.afterThrows(currentMethod, args, throwable);
}
}
}
throw throwable;
}
}
}
到此我们找到了扩展点,并通过重写对doInvoke添加观察者,函数式包裹等,也可以通过其他的扩展方法对
ServletInvocableHandlerMethod ,RequestMappingHandlerAdapter进行扩展
网友评论