截取自:Spring Security OAuth2.0 认证协议【4】

过滤、拦截、切片

非常重要的三个概念,后面用三方类的时候需要有的概念。

  • 过滤器(Filter)
    JEE的规范,处于最外层,<mark>能拿到请求(request)和响应(response)</mark>,但也只能拿到这两个东西。

    doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)

    由于是JEE规范的东西,因此无法操作spring,即 <mark>无法知道请求被哪个controller处理</mark>
    <mark>但可以通过FilterRegistrationBean把它注册到spring中,被spring管理</mark>
    第三方Filter插件的运行原理,下面演示)

  • ***(Interceptor)
    和Filter相反,***是Spring提供的类,天生就能让spring管理(当然,也需要注册),
    <mark>相比起 Filter ,Interceptor 除了能拿到 request 和 response,还能拿到 handler (Controller 的处理器)</mark>
    postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler, ModelAndView modelAndView)

  • 切片(Aspect)
    上面讲了 <mark>***(interceptor)可以获得request 、response 和 handler ,但他无法获取 handler 上的参数</mark>
    这时候就需要用到 切面(Aspect)了,

<mark>下面各自创一个类</mark>


# Filter

filter应该都懂,两个重点

  • JEE的规范,处于最外层
  • SpringBoot 环境不能直接创建,需要通过 FilterRegistrationBean

创建 filter 类

package cn.vshop.security.web.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StopWatch;

import javax.servlet.*;
import java.io.IOException;

/** * @author alan smith * @version 1.0 * @date 2020/3/31 18:00 */
 // JEE 标准,默认不收spring管理
// 不要直接使用@Component,Spring无法管理,必须借助FilterRegistrationBean创建
//@Component
@Slf4j
public class TimeFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("time filter init");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        log.info("time filter start");
        StopWatch watch = new StopWatch();
        watch.start("filter");
        filterChain.doFilter(servletRequest, servletResponse);
        watch.stop();
        System.out.println(watch.prettyPrint());
        log.info("time filter finish");
    }

    @Override
    public void destroy() {
        log.info("time filter destroy");
    }
}

创建 webConfig ,执行web相关注册

package cn.vshop.security.web.config;

import cn.vshop.security.web.filter.TimeFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.ArrayList;

/** * 把第三方的filter加入到spring的容器中 * * @author alan smith * @version 1.0 * @date 2020/3/31 18:06 */
@Configuration
public class WebConfig {

    /** * 使用 FilterRegistrationBean 而不是直接 @Component 一个Filter的好处是: * + 可以通过setUrlPatterns,指定哪些路径通过***,哪些路径不通过 */
    @Bean
    public FilterRegistrationBean timeFilter() {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        Filter filter = new TimeFilter();
        registrationBean.setFilter(filter);

        ArrayList<String> urls = new ArrayList<>();
        urls.add("/filter") ;
        registrationBean.setUrlPatterns(urls);

        return registrationBean ;
    }
}

运行结果

访问: http://localhost:8080/filter

<mark>所有URL符合过滤规则的请求,都会经过过滤器</mark>

DELETE  http://{{host}}/filter
###
PUT http://{{host}}/filter
### 
# 等等

# Interceptor

实现接口 HandlerInterceptor

package cn.vshop.security.web.interceptor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/** * @author alan smith * @version 1.0 * @date 2020/3/31 19:21 */
@Slf4j
// spring 提供的类,默认被spring管理
@Component
public class TimeInterceptor implements HandlerInterceptor {

    // 控制器(controller)方法处理前调用
    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
        log.info("@@@@ interceptor preHandle");
        long start = System.currentTimeMillis();
        // 为了在方法间传递信息,类似可用ThreadLocal
        httpServletRequest.setAttribute("start", start);
        // 返回false将不继续执行下面方法
        return true;
    }

    // 控制器(controller)方法处理后调用
    // 但是,如果controler执行期间出现异常,postHandle将不被调用
    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
        log.info("@@@@ interceptor postHandle");
        long start = (long) httpServletRequest.getAttribute("start");
        log.info("@@@@ 用时:{}", System.currentTimeMillis() - start);
    }

    // postHandle方法处理后调用
    // 无论,controler执行期间是否出现异常,afterCompletion都将被调用
    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
        log.info("@@@@ interceptor afterCompletion");
        long start = (long) httpServletRequest.getAttribute("start");
        log.info("@@@@ 用时:{}", System.currentTimeMillis() - start);
        log.info("@@@@ exception:{}", e);
    }
}

在 webMVCConfig 配置类中注册 interceptor

package cn.vshop.security.web.config;

import cn.vshop.security.web.filter.TimeFilter;
import cn.vshop.security.web.interceptor.TimeInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import javax.servlet.Filter;
import java.util.ArrayList;

/** * 把第三方的filter加入到spring的容器中 * * @author alan smith * @version 1.0 * @date 2020/3/31 18:06 */
@Configuration
// 继承 WebMvc注册类的适配:WebMvcConfigurerAdapter
public class WebConfig extends WebMvcConfigurerAdapter {

    @Autowired
    private TimeInterceptor timeInterceptor;

    // 将***注册进springmvc
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(timeInterceptor);
    }

    /** * 使用 FilterRegistrationBean 而不是直接 @Component 一个Filter的好处是: * + 可以通过setUrlPatterns,指定哪些路径通过***,哪些路径不通过 */
    @Bean
    public FilterRegistrationBean timeFilter() {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        Filter filter = new TimeFilter();
        registrationBean.setFilter(filter);

        ArrayList<String> urls = new ArrayList<>();
        urls.add("/filter");
        registrationBean.setUrlPatterns(urls);

        return registrationBean;
    }
}

创建 Filtercontroller

package cn.vshop.security.web.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/** * @author alan smith * @version 1.0 * @date 2020/3/31 23:24 */
@Slf4j
@RequestMapping("/filter")
@RestController
public class FilterController {
    @RequestMapping
    public String ok() {
        log.info("ok !");
        return "filter ok !";
    }
}

运行测试脚本

GET  http://{{host}}/filter
Content-Type: application/json
###

测试结果

(点击放大)和这图还差个切面(aspect)

注意,如果中间controller了异常(如:没有加 FilterController 这个类),
<mark>postHandle 方法将不执行</mark>
并且最终的执行顺序也非常让人头疼(下图)

# Aspect

官方文档:切点表达式 Examples

面向切面编程三要素:

  • 切片(切谁)
  • 切点(在哪切)
  • 增强(切了干嘛)

v-security-demo 添加依赖

<!--切面-->
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-aop</artifactId>
 </dependency>

编写aspect类

package cn.vshop.security.web.aspect;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

import java.util.Date;

/** * @author alan smith * @version 1.0 * @date 2020/4/1 0:38 */
@Slf4j
// 定义为切面
@Aspect
// 让spring容器管理
@Component
public class TimeAspect {

    // @Around 包围方式调用切点
    // 切入点表达式: UserController的任何返回值的任何参数的任何方法
    @Around("execution(* cn.vshop.security.web.controller.FilterController.*(..))")
    public Object handControllerMethod(
            // 当前切点
            ProceedingJoinPoint pj
    ) throws Throwable {

        log.info("### ### time aspect start");

        long start = new Date().getTime();

        // aspect相比于interceptor的优点,能获取参数
        Object[] args = pj.getArgs();
        for (Object arg : args) {
            log.info("arg is {}", arg);
        }

        Object result = pj.proceed();

        long end = new Date().getTime();
        log.info("### ### time aspect 耗时:{}", (end - start));

        log.info("### ### time aspect end");

        return result;

    }
}

修改controller

让其随便接收一个参数

运行,调用脚本测试

###
GET http://{{host}}/filter?id=1

# 或者
###
POST  http://{{host}}/filter
# 表单
Content-Type:application/x-www-form-urlencoded

id=1

###

控制台打印