SpringBoot常用统一处理方式

web编程中,常用的统一处理方式有:servlet过滤器、spring的AOP以及spring拦截器。

一、过滤器filter与拦截器Interceptor、aop切面的区别

  • Filter是Servlet规范中定义,只有Servlet容器支持的。
  • 拦截器是是Spring框架支持的,可以使用Spring内的任何资源、对象,Filter则不可;
  • Filter只在Servlet前后起作用,而拦截器精细到方法前后、异常抛出前后等;
  • Filter基于函数回调,拦截器是基于java的反射机制。
  • AOP使用的是动态代理,常和事务结合:Spring声明式事务管理(切面)
  • AOP对操作进行横向的拦截,最大的优势在于获取执行方法参数(ProceedingJoinPoint.getArgs() ),对方法进行统一的处理。如在日常工作中编写方法切面,用来统计每一个方法执行的时间,来确定优化点。

二、适用场景

1、过滤器使用场景

  1. 过滤敏感词汇(防止sql注入)
  2. 设置字符编码
  3. URL级别的权限访问控制
  4. 压缩响应信息

2、拦截器使用场景

  1. 登录验证
  2. 权限验证,判断用户是否有权限访问资源,如校验token
  3. 日志记录,记录请求操作日志(用户ip,访问时间等),以便统计请求访问量
  4. 性能监控,监控请求处理时长等
  5. 读取cookie中的各种信息、本地化、国际化、主题等

3、切面使用场景

  1. Authentication 权限
  2. Caching 缓存
  3. Context passing 内容传递
  4. Error handling 错误处理
  5. Lazy loading 懒加载
  6. Debugging 调试
  7. logging, tracing, profiling and monitoring 记录跟踪,优化,校准
  8. Performance optimization 性能优化
  9. Persistence 持久化
  10. Resource pooling 资源池
  11. Synchronization 同步
  12. 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

发表评论