截取自: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
###
控制台打印