文章内容
web编程中,常用的统一处理方式有:servlet过滤器、spring的AOP以及spring拦截器。
一、过滤器filter与拦截器Interceptor、aop切面的区别
- Filter是Servlet规范中定义,只有Servlet容器支持的。
- 拦截器是是Spring框架支持的,可以使用Spring内的任何资源、对象,Filter则不可;
- Filter只在Servlet前后起作用,而拦截器精细到方法前后、异常抛出前后等;
- Filter基于函数回调,拦截器是基于java的反射机制。
- AOP使用的是动态代理,常和事务结合:Spring声明式事务管理(切面)
- AOP对操作进行横向的拦截,最大的优势在于获取执行方法参数(ProceedingJoinPoint.getArgs() ),对方法进行统一的处理。如在日常工作中编写方法切面,用来统计每一个方法执行的时间,来确定优化点。
二、适用场景
1、过滤器使用场景
- 过滤敏感词汇(防止sql注入)
- 设置字符编码
- URL级别的权限访问控制
- 压缩响应信息
2、拦截器使用场景
- 登录验证
- 权限验证,判断用户是否有权限访问资源,如校验token
- 日志记录,记录请求操作日志(用户ip,访问时间等),以便统计请求访问量
- 性能监控,监控请求处理时长等
- 读取cookie中的各种信息、本地化、国际化、主题等
3、切面使用场景
- Authentication 权限
- Caching 缓存
- Context passing 内容传递
- Error handling 错误处理
- Lazy loading 懒加载
- Debugging 调试
- logging, tracing, profiling and monitoring 记录跟踪,优化,校准
- Performance optimization 性能优化
- Persistence 持久化
- Resource pooling 资源池
- Synchronization 同步
- Transactions 事务
三、过滤器Filter
1、使用@ServletComponentScan注解
使用@WebFilter注解,需在Springboot启动类上加@ServletComponentScan注解。并且@Order注解并不能控制filter执行顺序。
@Slf4j
@WebFilter(urlPatterns = {"/*"}, filterName = "initFilter")
public class InitFilter implements Filter, Ordered {
@Override
public void init(FilterConfig filterConfig){
log.info("-----come into InitFilter init------");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
log.info("--------come into InitFilter and do processes--------");
// 实际业务处理
// HttpServletRequest HRrequest = (HttpServletRequest) request;
// Cookie[] cookies = HRrequest.getCookies();
// if (cookies != null) {
// for (Cookie cookie : cookies) {
// if (cookie.getName().equals("loginUser")) {
// log.info("find loginUser: " + cookie.getValue());
// break;
// }
// }
// }
// 当前过滤器处理完了交给下一个过滤器处理,一定要写,否则就断了
chain.doFilter(request, response);
log.info("--------InitFilter's process has completed!---------");
}
@Override
public void destroy() {
log.info("--------come into InitFilter destroy-------------");
}
@Override
public int getOrder() {
return 2;
}
}
2、使用@Configuration注解
如果不想增加在springboot 启动类增加@ServletComponentScan注解,也可以通过configuration进行添加,而且可以通过设置order值控制filter的执行顺序。
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
/**
* 注册filter
* 这里可以通过filter的order实现顺序过滤
*/@Configuration
public class filterConfig {
/**
* 注入一个filter,也是一个bean
* @return
*/ @Bean
public Filter initFilter(){
return new InitFilter();
}
@Bean
public Filter secondFilter(){
return new SecondFilter();
}
@Bean
public FilterRegistrationBean setFilter1() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(initFilter());
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.setOrder(2); //order的数值越小,在所有的filter中优先级越高
return filterRegistrationBean;
}
@Bean
public FilterRegistrationBean setFilter2(){
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(secondFilter());
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.setOrder(1); //order的数值越小,在所有的filter中优先级越高
return filterRegistrationBean;
}
}
四、拦截器Interceptor
拦截器可以在一个请求进入controller之前、之后、以及完成后进行拦截,进行统一处理。
1、新建拦截器
@Slf4j
@Component
public class SecondInterceptor implements HandlerInterceptor {
/**
* 进入Controller层之前拦截请求,默认是拦截所有请求
* @param httpServletRequest request
* @param httpServletResponse response
* @param o object
* @return 是否拦截当前请求,true表示拦截当前请求,false表示不拦截当前请求
* @throws Exception 可能出现的异常
*/ @Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
log.info("go into preHandle method -----SecondInterceptor---------- ");
return true;
}
/**
* 处理完请求后但还未渲染试图之前进行的操作
* @param httpServletRequest request
* @param httpServletResponse response
* @param o object
* @param modelAndView mv
* @throws Exception E
*/ @Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
log.info("go into postHandle -----SecondInterceptor---------- ");
}
/**
* 视图渲染后但还未返回到客户端时的操作
* @param httpServletRequest request
* @param httpServletResponse response
* @param o object
* @param e exception
* @throws Exception
*/ @Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
log.info("go into afterCompletion -----SecondInterceptor---------- ");
}
}
2、拦截器注册
创建拦截器后,需要把拦截器注册到spring容器中,注意spring可以有多个拦截器,同样可以在注册的时候通过order来进行顺序控制。
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Autowired
private SecondInterceptor secondInterceptor;
/**
* 注册配置的拦截器
* @param registry 拦截器注册器
*/ @Override
public void addInterceptors(InterceptorRegistry registry) {
//拦截器既可以new出来,也可在Spring框架中可以交给IOC进行依赖注入,直接使用@Autowired注入
//registry.addInterceptor(gatewayInterceptor).addPathPatterns("/**");
//使用自动注入的方式注入拦截器,添加应用、或不应用该拦截器的URI(addPathPatterns/excludePathPatterns)
//addPathPatterns 用于添加拦截的规则,excludePathPatterns 用于排除拦截的规则
//registry.addInterceptor(urlInterceptor).addPathPatterns(new UrlInterceptor()).excludePathPatterns("/login");
registry.addInterceptor(secondInterceptor).order(10);
registry.addInterceptor(new InitInterceptor()).order(1);
}
/**
* 在初始化Servlet服务时(在Servlet构造函数执行之后、init()之前执行),@PostConstruct注解的方法被调用
*/ @PostConstruct
void init() {
System.out.println("Servlet init ... ");
// 添加匹配的规则, /** 表示匹配所有规则,任意路径
myPathPatterns.add("/**");
}
/**
* 在卸载Servlet服务时(在Servlet的destroy()方法之前执行),@PreDestroy注解的方法被调用
*/ @PreDestroy
void destroy() {
System.out.println("Servlet destory ... ");
}
}
五、切面AOP
spring为我们提供了很方便的面向切面编程(AOP)。使用切面时,需要在实体类上添加@Aspect和@Component注解
1、引入依赖
<!--引入AOP依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2、添加切面
@Slf4j
@Aspect
@Component
public class InitAspect {
/**
* 定义切入点,切入点为com.example.demo.aop.AopController中的所有函数
*通过@Pointcut注解声明频繁使用的切点表达式
*/ @Pointcut("execution(public * com.pan.controller..*.*(..)))")
public void BrokerAspect(){
}
/**
* @description 在连接点执行之前执行的通知
*/ @Before("BrokerAspect()")
public void doBefore(){
System.out.println("----------InitAspect---------do doBefore-----------------");
}
/**
* @description 在连接点执行之后执行的通知(返回通知和异常通知的异常)
*/ @After("BrokerAspect()")
public void doAfter(){
System.out.println("----------InitAspect---------do doAfter-----------------");
}
/**
* @description 在连接点执行之后执行的通知(返回通知)
*/ @AfterReturning("BrokerAspect()")
public void doAfterReturn(){
System.out.println("----------InitAspect---------do doAfterReturn-----------------");
}
/**
* @description 在连接点执行之后执行的通知(异常通知)
*/ @AfterThrowing("BrokerAspect()")
public void doAfterThrow(){
System.out.println("----------InitAspect---------do doAfterThrow-----------------");
}
/**
* @description 使用环绕通知
*/ @Around("BrokerAspect()")
public void doAround(ProceedingJoinPoint pjp) throws Throwable {
try{
System.out.println("----------InitAspect--------- doAround start ----");
pjp.proceed();
System.out.println("----------InitAspect--------- doAround end ----");
} catch(Throwable e) {
System.out.println("异常通知:----------InitAspect--------- doAround----");
}
}
}
六、执行顺序
结合上述示例,可以看到执行顺序为:
InitFilter-->secondfilter-->
InitInterceptor-->SecondInterceptor-->
aop-doAround start-->aop-doBefore-->aop-doAfterReturn-->aop-doAfter-->aop-doAround-end-->
SecondInterceptor-->InitInterceptor
-->secondfilter-->InitFilter