Filter、Interceptor、Aspect的设计及区别
1) Filter
定义
基于Servlet
架构
public class TimeFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
long start = new Date().getTime();
chain.doFilter(request, response);
System.out.println("time filter" + (new Date().getTime() - start));
}
}
原理: 函数回调
Spring Boot引入第三方Filter
-
使用
application.xml
的形式 -
Spring boot
的纯注解模式,外部的Filter可能不带Component- 新增配置类
- 定义
FilterRegistrationBean
为返回值的bean
@Configuration public class FilterRegister { @Bean public FilterRegistrationBean timeFilter(){ FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); TimeFilter timeFilter = new TimeFilter(); List<String> urls = new ArrayList<>(); urls.add("/testFilter"); filterRegistrationBean.setFilter(timeFilter); filterRegistrationBean.setUrlPatterns(urls); return filterRegistrationBean; } }
Filter的不足
- 只拥有
ServletRequest
和ServletResponse
对象,无法获取Controller相关的方法信息
2) Interceptor
定义
来自Spring MVC
,能够对Spring
管理的Controller
进行方法粒度的拦截,能够获得方法名
和对应的controller的bean
,request.getParam可以获取基于Servlet
的参数,@RequestBody
等spring
管理的参数无法解析获得
public class TimeInterceptor implements HandlerInterceptor {
/**
* @param handler
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return false;
}
@Override
public void postHandle(...){}
@Override
public void afterCompletion(...){}
}
原理: 反射
HandlerInterceptor
preHandle
返回布尔值,若返回false
调用链断开preHandle
返回true
,postHandle
按照preHandle
的执行顺序倒叙执行
WebRequestInterceptor
-
preHandle
无返回值,常常用作资源准备 -
WebRequest
的setAttribute(name, value, scope)
scope
SCOPE_REQUEST
,表示只有在request中可以访问。SCOPE_SESSION
,如果环境允许的话,它表示的是一个局部的隔离的session,否则就代表普通的session,并且在该session范围内可以访问。SCOPE_GLOBAL_SESSION
,如果环境允许的话,它表示的是一个全局共享的session,否则就代表普通的session,并且在该session范围内可以访问。
更详细的资料:详述 Spring MVC 框架中拦截器 Interceptor 的使用方法
MVC中的Handler
指的是RequestMapping
标记的方法
从DispatcherServlet
源码看handler的获取
DispatcherServlet.getrHandler()
源码看 Interceptor
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
HandlerExecutionChain
内部是有Interceptor
的容器,也即是Spring 拦截器作用的类
使用
- 继承
HandlerInterceptorAdapter
或实现HandlerInterceptor
- 同理,还可以继承
WebRequestInterceptorAdapter
或实现WebRequestInterceptor
注入拦截器-XML
<mvc:interceptors>
<!-- 使用 bean 定义一个 Interceptor,这个位置的bean将将拦截所有的请求 -->
<bean class="com.hit.interceptor.WrongCodeInterceptor"/>
<mvc:interceptor>
<mvc:mapping path="/demo/hello.do"/>
<!-- 定义在 mvc:interceptor 下面的 Interceptor,表示对特定的请求进行拦截 -->
<bean class="com.hit.interceptor.LoginInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
注入拦截器-Spring Boot
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Autowired
private TimeInterceptor timeInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
InterceptorRegistration interceptorRegistration = registry.addInterceptor(timeInterceptor);
interceptorRegistration.addPathPatterns("/**");
}
}
Interceptor 的不足
无法获取请求参数拼装后的结果,@RequestBody
反序列化成的参数将不会出现在拦截器里。
3) Aspect
定义
面向切面编程,进过Spring MVC
的 Interceptor
后再进入ControllerAdvice
(一般用作异常处理)
再进入到Controller
,Contrller
需要用到动态代理的技术实现,Spring
使用两种技术实现AOP
- JDK 动态代理 - 实现类
- CGLib 动态代理 - 非实现类强制使用
两种代理技术的区别
JDK 只能代理接口,通过字节码底层继承要代理类来实现除了final 类 都可以被代理。
原理: 动态代理
名词解释
JoinPoint: 连接点
类里面哪些方法可以被增强,这些方法称为连接点. 在spring的AOP中,指的是所有现有的方法。
Pointcut: 切入点
在类里面可以有很多方法被增强,但是实际开发中,我们只对具体的某几个方法而已,那么这些实际增强的方法就称之为切入点
Advice: 通知/增强
增强的逻辑、称为增强,比如给某个切入点(方法) 扩展了校验权限的功能,那么这个校验权限即可称之为增强 或者是通知
通知分为:
- Before 前置通知: 在原来方法之前执行.
- AfterReturning 后置通知: 在原来方法之后执行. 特点: 可以得到被增强方法的返回值
- Around 环绕通知:在方法之前和方法之后执行. 特点:可以阻止目标方法执行
- AfterThrowing 异常通知: 目标方法出现异常执行. 如果方法没有异常,不会执行. 特点:可以获得异常的信息
- After 最终通知: 指的是无论是否有异常,总是被执行的。
Aspect: 切面
切入点和通知的结合。
使用
配置切面-XML
<bean id="myAscept" class="com.itheima.aspect.MyAscept"/>
<aop:config>
<aop:pointcut id="pointCut01" expression="execution(* com.itheima.dao.impl.AccountDaoImpl.save(..))"/>
<!--配置切面(切入点和通知的结合)-->
<aop:aspect ref="myAscept">
<!--前置通知-->
<aop:before method="checkPrivilege" pointcut-ref="pointCut01"> </aop:before>
</aop:aspect>
</aop:config>
配置切面-Spring Boot
@Aspect
@Component
public class TimeAspect {
/**
* 环绕通知, 代理包下的所有方法
* @param joinPoint 切入点(被增强的方法)
* @return 被增强的返回值
*/
@Around("execution(* com.changgou.aspect.TimeController.*(..))")
public Object handleControllerMethod(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("time aspect start");
for (Object arg : joinPoint.getArgs()) {
System.out.println(arg);
}
// 调用原来的方法
Object proceed = joinPoint.proceed();
System.out.println("time aspect end");
return proceed;
}
}
Aspect 的不足
获取原生的ServletRequest
需要借助 RequestContextHolder
,
对于分布式应用,基于ThreadLocal
的实现是有很大瓶颈的