在 java web 开发中,我们可以通过三种方式拦截请求,分别是:Filter、Interceptor、Aspect,下面我们来介绍一下这三种方式之间的异同点。
以一个简单的 spring-boot 项目为例,记录 Controller 方法执行的时间。
项目结构
HelloController
@RestController
public class HelloController {
@GetMapping("/hello")
public Object sayHello(String name) {
System.out.println("name: " + name);
return "hello " + name;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
单元测试
先写一个单元测试,验证 HelloController.sayHello() 方法的正确性。
@RunWith(SpringRunner.class)
@SpringBootTest
public class HelloControllerTest {
@Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
@Before
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
@Test
public void sayHello() throws Exception {
// 模拟 get 请求
mockMvc.perform(
get("/hello") // get 请求 url /hello
.param("name", "tom") // 参数 name=tom
.contentType(MediaType.APPLICATION_JSON_UTF8)) // 设置请求头 Content-Type = application/json;charset=UTF-8
.andExpect(status().isOk()) // 期望返回的状态码为200
.andExpect(content().string("hello tom")); // 期望返回值为"hello tom"
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
经测试,HelloController.sayHello() 没有问题。
Filter
新建 TimeFilter
@Component
public class TimeFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("time filter init");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("time filter start");
long startTime = System.currentTimeMillis();
filterChain.doFilter(servletRequest, servletResponse);
long endTime = System.currentTimeMillis();
System.out.println("time filter consume " + (endTime - startTime) + " ms");
System.out.println("time filter end");
}
@Override
public void destroy() {
System.out.println("time filter init");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
启动服务器,在浏览器输入:http://localhost:8080/hello?name=tom
可以在控制台输出如下结果:
time filter start
name: tom
time filter consume 3 ms
time filter end
- 1
- 2
- 3
- 4
可以看到,filter 先执行,再到真正执行 HelloController.sayHello() 方法。
通过 TimeFilter.doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) 方法的参数可以看出,我们只能得到原始的 request 和 response 对象,不能得到这个请求被哪个 Controller 以及哪个方法处理了,使用Interceptor 就可以获得这些信息。
Interceptor
新建 TimeInterceptor
@Component
public class TimeInterceptor extends HandlerInterceptorAdapter {
private final NamedThreadLocal<Long> startTimeThreadLocal = new NamedThreadLocal<>("startTimeThreadLocal");
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("time interceptor preHandle");
HandlerMethod handlerMethod = (HandlerMethod) handler;
// 获取处理当前请求的 handler 信息
System.out.println("handler 类:" + handlerMethod.getBeanType().getName());
System.out.println("handler 方法:" + handlerMethod.getMethod().getName());
MethodParameter[] methodParameters = handlerMethod.getMethodParameters();
for (MethodParameter methodParameter : methodParameters) {
String parameterName = methodParameter.getParameterName();
// 只能获取参数的名称,不能获取到参数的值
//System.out.println("parameterName: " + parameterName);
}
// 把当前时间放入 threadLocal
startTimeThreadLocal.set(System.currentTimeMillis());
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("time interceptor postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 从 threadLocal 取出刚才存入的 startTime
Long startTime = startTimeThreadLocal.get();
long endTime = System.currentTimeMillis();
System.out.println("time interceptor consume " + (endTime - startTime) + " ms");
System.out.println("time interceptor afterCompletion");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
注册 TimeInterceptor
把 TimeInterceptor 注入 spring 容器
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
@Autowired
private TimeInterceptor timeInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(timeInterceptor);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
启动服务器,在浏览器输入:http://localhost:8080/hello?name=tom
可以在控制台输出如下结果:
time filter start
time interceptor preHandle
handler 类:com.nextyu.demo.web.controller.HelloController
handler 方法:sayHello
name: tom
time interceptor postHandle
time interceptor consume 40 ms
time interceptor afterCompletion
time filter consume 51 ms
time filter end
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
可以看到,filter 先于 interceptor 执行,再到真正执行 HelloController.sayHello() 方法。
通过 interceptor 方法上的 handler 参数,我们就可以得到这个请求被哪个 Controller 以及哪个方法处理了。但是不能直接获取到这个方法上的参数值(在这里就是 HelloController.sayHello(String name) 方法参数 name 的值),通过 Aspect 就可以获取到。
Aspcet
新建 TimeAspect
@Aspect
@Component
public class TimeAspect {
@Around("execution(* com.nextyu.demo.web.controller.*.*(..))")
public Object handleControllerMethod(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("time aspect start");
Object[] args = pjp.getArgs();
for (Object arg : args) {
System.out.println("arg is " + arg);
}
long startTime = System.currentTimeMillis();
Object object = pjp.proceed();
long endTime = System.currentTimeMillis();
System.out.println("time aspect consume " + (endTime - startTime) + " ms");
System.out.println("time aspect end");
return object;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
启动服务器,在浏览器输入:http://localhost:8080/hello?name=tom
可以在控制台输出如下结果:
time filter start
time interceptor preHandle
handler 类:com.nextyu.demo.web.controller.HelloController
handler 方法:sayHello
time aspect start
arg is tom
name: tom
time aspect consume 0 ms
time aspect end
time interceptor postHandle
time interceptor consume 2 ms
time interceptor afterCompletion
time filter consume 4 ms
time filter end
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
可以看到,filter 先执行,再到 interceptor 执行,再到 aspect 执行,再到真正执行 HelloController.sayHello() 方法。
我们也获取到了 HelloController.sayHello(String name) 方法参数 name 的值。
请求拦截过程图
总结
- Filter 是 java web 里面的,肯定获取不到 spring 里面 Controller 的信息。
- Interceptor、Aspect 是和 spring 相关的,所以能获取到 Controller 的信息。