filter、interceptor、aspect
- Filter过滤器
- Interceptor拦截器
- Aspect切片
Filter过滤器
-
过滤器依赖于servlet容器。在实现上,基于函数回调,它可以对几乎所有请求进行过滤,一个过滤器实例只能在容器初始化时调用一次。
-
过滤器可以拦截到方法的请求和响应(ServletRequest request, ServletResponse response),并对请求响应做出过滤操作。
-
使用过滤器的目的是用来做一些过滤操作,对请求的数据进行获取,或者替换请求数据,或者权限拦截检查,比如:在过滤器中修改字符编码;在过滤器中修改HttpServletRequest的一些参数,过滤关键字:过滤低俗文字、危险字符等。
切面的方法说明
-
@Aspect 作用是把当前类标识为一个切面供容器读取
-
@Before 标识一个前置增强方法,相当于BeforeAdvice的功能
-
@AfterReturning 后置增强,相当于AfterReturningAdvice,方法退出时执行
-
@AfterThrowing 异常抛出增强,相当于ThrowsAdvice
-
@After final增强,不管是抛出异常或者正常退出都会执行
-
@Around 环绕增强,相当于MethodInterceptor
-
除了@Around外,每个方法里都可以加或者不加参数JoinPoint,如果有用JoinPoint的地方就加,不加也可以,JoinPoint里包含了类名、被切面的方法名,参数等属性,可供读取使用。@Around参数必须为ProceedingJoinPoint,pjp.proceed相应于执行被切面的方法。@AfterReturning方法里,可以加returning = “XXX”,XXX即为在controller里方法的返回值,本例中的返回值是“first controller”。@AfterThrowing方法里,可以加throwing = "XXX"
切面PointCut的切入点
execution切点函数
- execution函数用于匹配方法执行的连接点,语法为:
execution(方法修饰符(可选) 返回类型 方法名 参数 异常模式(可选))
例如:
execution(* com.imooc.controller.UserController.*(..))
第一个 * 代表的是所有的返回值类型,
com.imooc.controller.UserController.*代表的是com.imooc.controller包下UserController类的所有方法,
(..)代表的是所有的参数
Filter代码实例
@Component
public class TimeFilter implements Filter {
private static final Logger LOGGER = LoggerFactory.getLogger(TimeFilter.class);
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
String uri = request.getRequestURI();
long start = System.currentTimeMillis();
filterChain.doFilter(servletRequest,servletResponse);
long end = System.currentTimeMillis();
LOGGER.info("请求地址 : [{}], 耗时 : [{}]",uri, (end-start)/3600);
}
@Override
public void destroy() {
}
}
- 实现javax.servlet.Filter接口,并重写接口的三个方法,其中doFilter方法是必须实现的,其它两个方法是接口的默认方法;
- doFilter方法 提供请求的ServletRequest和响应的ServletResponse对象
- Filter中无法获取其它对象,例如Controller 即Filter中不支持对象注入(@AutoWired). 在Spring中 web容器的加载顺序是:Listener -> Filter -> Servlet, 先初始化Listener,然后进行Filter初始化,再接口进行Servlet的初始化过程。然后我们的Controller对象即在Servlet容器初始化后进行初始化的,所以在Filter初始化时,注解获取Bean的还没有进行初始化,即没法注入。
public interface Filter {
default void init(FilterConfig filterConfig) throws ServletException {
}
void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;
default void destroy() {
}
}
Filter配置
Filter过滤器实例已经书写完毕,但是使其生效需要进行配置,一般有两种方式进行配置:
- 第一个方案在Filter上面加上@Component
@Component
public class TimeFilter implements Filter {
...
}
-
第二个方案配置化注册过滤器
该方案的特点就是可以细化到过滤哪些规则的URL
@Configuration
public class FilterConfig {
@Bean
FilterRegistrationBean<TimeFilter> filterRegistrationBean() {
FilterRegistrationBean<TimeFilter> registrationBean = new FilterRegistrationBean();
TimeFilter timeFilter = new TimeFilter();
registrationBean.setFilter(timeFilter);
registrationBean.setUrlPatterns(Arrays.asList("/*"));
return registrationBean;
}
}
容器启动输出:
com.ams.admin.filters.TimeFilter : init...
调用请求输出
2019-09-04 14:11:27.424 INFO 66854 --- [io-10001-exec-2] com.ams.admin.filters.TimeFilter : 请求地址 : [/group/single], 耗时 : [10334] ms
2019-09-04 14:11:34.236 INFO 66854 --- [io-10001-exec-3] com.ams.admin.filters.TimeFilter : 请求地址 : [/group/single], 耗时 : [14] ms
2019-09-04 14:11:35.442 INFO 66854 --- [io-10001-exec-4] com.ams.admin.filters.TimeFilter : 请求地址 : [/group/single], 耗时 : [14] ms
容器销毁输出
2019-09-04 14:19:16.148 INFO 66854 --- [ Thread-31] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
2019-09-04 14:19:16.149 INFO 66854 --- [ Thread-31] com.alibaba.druid.pool.DruidDataSource : {dataSource-3} closed
2019-09-04 14:19:16.158 INFO 66854 --- [ Thread-31] com.ams.admin.filters.TimeFilter : destroy...
Filter 总结
- 过滤器(Filter)是基于函数回调;
- Filter随web应用的启动而启动,只初始化一次,随web应用的停止而销毁;
- 启动服务器时加载过滤器的实例,并调用init()方法来初始化实例;
- 每一次请求时都只调用方法doFilter()进行处理;
- 停止服务器时调用destroy()方法,销毁实例。
Interceptor拦截器
Interceptor基于Java的反射机制,属于面向切面编程(AOP)的一种运用,就是在一个方法前,调用一个方法,或者在方法后,调用一个方法。其实我们所了解的代理机制其实就是一种拦截器的实现。而我们此处讨论的拦截器是针对web框架所说的,所以依赖于web框架。
- 在web开发中,拦截器是经常用到的功能。它可以帮我们验证是否登陆、预先设置数据以及统计方法的执行效率等。在spring中拦截器有两种,第一种是HandlerInterceptor,第二种是MethodInterceptor。HandlerInterceptor是SpringMVC中的拦截器,它拦截的是Http请求的信息,优先于MethodInterceptor。而MethodInterceptor是springAOP的。前者拦截的是请求的地址,而后者是拦截controller中的方法,因为下面要将Aspect,就不详细讲述MethodInterceptor
代码实例
public class AmsInterceptor implements HandlerInterceptor {
private static final Logger LOGGER = LoggerFactory.getLogger(AmsInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HandlerMethod method = (HandlerMethod) handler;
String cn = method.getBean().getClass().getName();
String mn = method.getMethod().getName();
LOGGER.info("preHandle -> [{}]#[{}]#[{}]", cn, mn, readRaw(request.getInputStream()));
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
LOGGER.info("postHandle -> [{}]#[{}]", request.getRequestURI(), request.getPathInfo());
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
LOGGER.info("afterCompletion -> [{}]#[{}]", request.getRequestURI(), request.getPathInfo());
}
public static String readRaw(InputStream inputStream) {
String result = "";
try {
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = inputStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, len);
}
outSteam.close();
inputStream.close();
result = new String(outSteam.toByteArray(), "UTF-8");
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
}
Interceptor配置
- 继承WebMvcConfigurationSupport
@Configuration
public class InterceptorConfig extends WebMvcConfigurationSupport {
@Autowired
AmsInterceptor amsInterceptor;
/**
* Override this method to add Spring MVC interceptors for
* pre- and post-processing of controller invocation.
*
* @param registry
* @see InterceptorRegistry
*/
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(amsInterceptor).addPathPatterns(Arrays.asList("/**"));
}
}
输出
2019-09-05 11:27:29.585 INFO 77598 --- [io-10001-exec-4] com.ams.admin.filters.TimeFilter : 请求地址 : [/group/single]
2019-09-05 11:27:29.586 INFO 77598 --- [io-10001-exec-4] c.ams.admin.interceptor.AmsInterceptor : preHandle : [com.ams.admin.controller.AmsGroupController]#[operateEntity]#[{ "name":"三部", "groupAmsid":"0003", "remark":"三部", "systemId": 1, "operator":"maozw"}]
2019-09-05 11:27:29.586 INFO 77598 --- [io-10001-exec-4] c.a.admin.controller.AmsGroupController : controller
2019-09-05 11:27:29.603 INFO 77598 --- [io-10001-exec-4] c.ams.admin.interceptor.AmsInterceptor : postHandle : [/group/single]
2019-09-05 11:27:29.603 INFO 77598 --- [io-10001-exec-4] c.ams.admin.interceptor.AmsInterceptor : afterCompletion : [/group/single]
2019-09-05 11:27:29.603 INFO 77598 --- [io-10001-exec-4] com.ams.admin.filters.TimeFilter : 请求地址 : [/group/single], 耗时 : [18] ms
执行流程解释
- 简要说明一下Interceptor执行流程,
- 我们通过日志可以看出,执行流程 TimeFilter -> preHandle -> controller -> postHandle -> afterCompletion
我们看下SpringMVC的核心类 org.springframework.web.servlet.DispatcherServlet
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
try {
...
try {
...
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
...
}
finally {
...
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
}
}
以上是删除一些无关代码之后的:
-
mappedHandler.applyPreHandle(processedRequest, response) 这个方法执行,就是执行的拦截器的preHandler方法
-
mappedHandler.applyPostHandle(processedRequest, response, mv); postHandler方法的执行,当controller内部有异常,posthandler方法是不会执行的。
-
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); ****afterCompletion方法,不管controller内部是否有异常,都会执行此方法;此方法还会有个Exception ex这个参数;如果有异常,ex会有异常值;没有异常 此值为null
-
根据上面的流程我们知道ha.handle(processedRequest, response, mappedHandler.getHandler());方法才会对请求参数进行组装方法参数,所以我们是在拦截器中可以拿到Http原始请求和响应信息,也可以获取到ioc的bean(controller)但是这个这个方法的参数是没法通过haner对象去获取。
-
值得一提的事情,因为我在Interceptor中获取了ServletRequest的InputStream 这个操作会导致后续在Controller中获取不到流,解决办法我会在另一篇幅中进行说明:详见【】
Aspect切片
AOP操作可以对操作进行横向的拦截,最大的优势在于他可以获取执行方法的参数,对方法进行统一的处理。常见使用日志,事务,请求参数安全验证等。
- 可以拿得到方法响应中参数的值,但是拿不到原始的Http请求和相对应响应的方法,基于Controller层。对于统一异常处理和日志记录非常方便,有效地减少了代码的重复率。
代码实例
@Aspect
@Component
@Slf4j
public class ControllerAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(ControllerAspect.class);
@Around("execution(* com.ams.admin.controller.*.*(..))")
public Object handlerControlerMethod(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
LOGGER.info("aop before... [{}]", args);
Object proceed = joinPoint.proceed();
LOGGER.info("aop after...");
return proceed;
}
}
配置使其生效
@EnableTransactionManagement(proxyTargetClass = true)
输出
2019-09-05 15:18:06.748 INFO 80313 --- [io-10001-exec-2] com.ams.admin.filters.TimeFilter : 请求地址 : [/group/single]
2019-09-05 15:18:06.750 INFO 80313 --- [io-10001-exec-2] c.ams.admin.interceptor.AmsInterceptor : preHandle : [com.ams.admin.controller.AmsGroupController$$EnhancerBySpringCGLIB$$7f16fb78]#[operateEntity]#[{ "name":"三部", "groupAmsid":"0003", "remark":"三部", "systemId": 1, "operator":"maozw"}]
2019-09-05 15:18:06.752 INFO 80313 --- [io-10001-exec-2] com.ams.admin.aspect.ControllerAspect : aop before... [AmsGroup(id=null, groupAmsid=0003, name=三部, systemId=1, remark=三部, createDate=null, modifyDate=null, operator=maozw)]
2019-09-05 15:18:06.755 INFO 80313 --- [io-10001-exec-2] c.a.admin.controller.AmsGroupController : controller
2019-09-05 15:18:16.909 INFO 80313 --- [io-10001-exec-2] com.ams.admin.aspect.ControllerAspect : aop after...
2019-09-05 15:18:16.910 INFO 80313 --- [io-10001-exec-2] c.ams.admin.interceptor.AmsInterceptor : postHandle : [/group/single]
2019-09-05 15:18:16.910 INFO 80313 --- [io-10001-exec-2] c.ams.admin.interceptor.AmsInterceptor : afterCompletion : [/group/single]
2019-09-05 15:18:16.911 INFO 80313 --- [io-10001-exec-2] com.ams.admin.filters.TimeFilter : 请求地址 : [/group/single], 耗时 : [10163] ms
小结
Filter
参数:ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain
作用:可以获取原始的http请求和响应对象,无法获取请求控制器
Interceptor
参数:HttpServletRequest request, HttpServletResponse response, Object handler
作用:可以获取原始的http请求和响应对象,可以获取到控制器和方法,但获取不到方法参数
Aspect
参数:ProceedingJoinPoint joinPoint
作用:无法获取原始的http请求和响应对象,可以获取到控制器和方法,也可以获取方法参数
网友评论