过滤器
过滤器能够对目标资源的请求和相应进行截取。
springboot对过滤器的实现,需要实现 javax.servlet.Filter 接口。
- 可以通过@Component将Filter交给Spring容器处理
@Component的形式是对所有资源都进行拦截,不存在URL的正则匹配 - 也可以不实用此注解直接编写Configuration类来实现。
Configuration的形式可以配置URL正则匹配
编写程序验证
通过 @Component的形式实现
- 首先定义一个Controller对外接口。这个接口的意思是:通过 localhost:8080/user/2 访问资源,id属性必须为数字。在这个接口中通过log4j打印日志,证明请求已经进入controller
@RestController
@RequestMapping("/user")
public class UserController {
private Logger log = LoggerFactory.getLogger(UserController.class);
@GetMapping("{id:\\d+}")
public User getUserById(@PathVariable("id") Integer id) {
log.info("进入UserController,执行getUserById");
User user = new User();
user.setId(id);
user.setUsername("用户" + id);
user.setPassword((id + 1000) + "");
return user;
}
}
- 编写TimeFilter,打印日志计算controller执行的耗时。这里我们首先通过@Component注解的形式实现
@Component
public class TimeFilter implements Filter {
private Logger log = LoggerFactory.getLogger(TimeFilter.class);
@Override
public void init(FilterConfig filterConfig) {
log.info("TimeFilter 初始化");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
log.info("TimeFilter doFilter开始");
long start = System.currentTimeMillis();
filterChain.doFilter(servletRequest, servletResponse);
log.info("TimeFilter 耗时 : " + (System.currentTimeMillis() - start) + "ms");
log.info("TimeFilter doFilter结束");
}
@Override
public void destroy() {
log.info("TimeFilter 销毁");
}
}
- 启动项目,查看控制台日志。可以看到有 “TimeFilter 初始化” 打印出来,说明系统启动的时候,TimeFilter过滤器的init方法已被执行
2019-06-20 14:58:43.501 INFO 1408 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'timeFilter' to: [/*]
2019-06-20 14:58:43.501 INFO 1408 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'applicationContextIdFilter' to: [/*]
2019-06-20 14:58:43.502 INFO 1408 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean : Mapping servlet: 'dispatcherServlet' to [/]
2019-06-20 14:58:43.516 INFO 1408 --- [ost-startStop-1] c.m.s.demo.framework.filter.TimeFilter : TimeFilter 初始化
- 通过浏览器访问 http://localhost:8080/user/2 。浏览器返回数据如下
{
"id": 2,
"username": "用户2",
"password": "1002"
}
- 查看控制台。可以看到首先打印TimeFilter 过滤器的doFilter方法中的"TimeFilter doFilter开始"日志,然后打印UserController中的日志,之后再次进入TimeFilter 过滤器的doFilter方法中打印耗时信息和"TimeFilter doFilter结束"。
2019-06-20 15:18:17.948 INFO 1408 --- [nio-8083-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started
2019-06-20 15:18:17.979 INFO 1408 --- [nio-8083-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 31 ms
2019-06-20 15:18:17.996 INFO 1408 --- [nio-8083-exec-1] c.m.s.demo.framework.filter.TimeFilter : TimeFilter doFilter开始
2019-06-20 15:18:18.017 INFO 1408 --- [nio-8083-exec-1] c.m.s.d.p.controller.UserController : 进入UserController,执行getUserById
2019-06-20 15:18:18.039 INFO 1408 --- [nio-8083-exec-1] c.m.s.demo.framework.filter.TimeFilter : TimeFilter 耗时 : 43ms
2019-06-20 15:18:18.039 INFO 1408 --- [nio-8083-exec-1] c.m.s.demo.framework.filter.TimeFilter : TimeFilter doFilter结束
通过Configuration配置类的形式
- 在上边代码的基础上稍作改动,首先去除TimeFilter过滤器的@Component注解
- 添加配置类 WebConfig。在这个配置类中,我们向Spring容器注入了一个FilterRegistrationBean 的Bean,这里边包含TimeFilter过滤器、指定的过滤路径为/user/下的所有url,对于除此之外的其他URL不做过滤
@Configuration
public class WebConfig {
@Bean
public FilterRegistrationBean timeFilter() {
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new TimeFilter());
bean.addUrlPatterns("/user/*");
return bean;
}
}
拦截器
java里的拦截器是动态拦截Action调用的对象。它提供了一种机制可以使开发者可以定义在一个action执行的前后执行的代码,也可以在一个action执行前阻止其执行,同时也提供了一种可以提取action中可重用部分的方式。在AOP(Aspect-Oriented Programming)中拦截器用于在某个方法或字段被访问之前,进行拦截然后在之前或之后加入某些操作。
需要注意的是:filter只能获取到request和response,然后从这两个类中获取到一些信息,filter并不能获取到过滤到的是哪一个controller和哪一个方法,如果想要知道这些,那么需要拦截器。实现如下
- 同filter、首先编写测试controller接口
@RestController
@RequestMapping("/user")
public class UserController {
private Logger log = LoggerFactory.getLogger(UserController.class);
@GetMapping("{id:\\d+}")
public User getUserById(@PathVariable("id") Integer id) {
log.info("进入UserController,执行getUserById");
User user = new User();
user.setId(id);
user.setUsername("用户" + id);
user.setPassword((id + 1000) + "");
return user;
}
}
- 编写interceptor。这里需要实现HandlerInterceptor 接口,请求进入controller接口之前,会首先进入preHandle方法;然后进入controller接口;如果执行正常,那么会进入TimeInterceptor 的postHandle方法,然后会执行afterCompletion方法;如果执行异常那么不会进入postHandle,会直接执行afterCompletion。
@Component
public class TimeInterceptor implements HandlerInterceptor {
private Logger logger = LoggerFactory.getLogger(TimeInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
request.setAttribute("startTime", System.currentTimeMillis());
logger.info("进入TimeInterceptor preHandle");
HandlerMethod handlerMethod = (HandlerMethod) handler;
System.out.println(handlerMethod.getBean().getClass().getName());
System.out.println(handlerMethod.getMethod().getName());
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
logger.info("进入TimeInterceptor postHandle");
logger.info("耗时 : " + (System.currentTimeMillis() - (long) request.getAttribute("startTime")));
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
logger.info("进入TimeInterceptor afterCompletion");
logger.info("耗时 : " + (System.currentTimeMillis() - (long) request.getAttribute("startTime")));
}
}
- 只是使用@Component并不能让拦截器生效,需要声明Configuration才可以生效。在刚刚的filter基础上、首先继承WebMvcConfigurerAdapter 类,然后重写addInterceptors方法,将TimeInterceptor拦截器加入进来。
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
private final TimeInterceptor timeInterceptor;
public WebConfig(TimeInterceptor timeInterceptor) {
this.timeInterceptor = timeInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(timeInterceptor);
}
@Bean
public FilterRegistrationBean timeFilter() {
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new TimeFilter());
bean.addUrlPatterns("/user/*");
return bean;
}
}
- 页面访问 http://localhost:8080/user/3 。日志打印如下。在preHandle方法中,我打印了handler对象的一些信息,在这个对象中可以获取到被拦截的类和方法的一些信息,这也是filter做不到的事情。
2019-06-20 16:02:22.859 INFO 5976 --- [nio-8083-exec-1] c.m.s.d.f.interceptor.TimeInterceptor : 进入TimeInterceptor preHandle
com.mright.security.demo.platform.controller.UserController
getUserById
2019-06-20 16:02:22.867 INFO 5976 --- [nio-8083-exec-1] c.m.s.d.p.controller.UserController : 进入UserController,执行getUserById
2019-06-20 16:02:22.887 INFO 5976 --- [nio-8083-exec-1] c.m.s.d.f.interceptor.TimeInterceptor : 进入TimeInterceptor postHandle
2019-06-20 16:02:22.887 INFO 5976 --- [nio-8083-exec-1] c.m.s.d.f.interceptor.TimeInterceptor : 耗时 : 28
2019-06-20 16:02:22.887 INFO 5976 --- [nio-8083-exec-1] c.m.s.d.f.interceptor.TimeInterceptor : 进入TimeInterceptor afterCompletion
2019-06-20 16:02:22.887 INFO 5976 --- [nio-8083-exec-1] c.m.s.d.f.interceptor.TimeInterceptor : 耗时 : 28
2019-06-20 16:02:22.888 INFO 5976 --- [nio-8083-exec-1] c.m.s.demo.framework.filter.TimeFilter : TimeFilter 耗时 : 39ms
2019-06-20 16:02:22.888 INFO 5976 --- [nio-8083-exec-1] c.m.s.demo.framework.filter.TimeFilter : TimeFilter doFilter结束
切面
Aspect Oriented Programming(AOP),面向切面编程,是一个比较热门的话题。AOP主要实现的目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。
拦截器只能获取到类和方法的信息,但是对于方法中参数的值、和方法执行完毕后返回的数据,拦截器是得不到的,我们可以通过切面来获取。
- 首先还是编写controller接口
@RestController
@RequestMapping("/user")
public class UserController {
private Logger log = LoggerFactory.getLogger(UserController.class);
@GetMapping("{id:\\d+}")
public User getUserById(@PathVariable("id") Integer id) {
log.info("进入UserController,执行getUserById");
User user = new User();
user.setId(id);
user.setUsername("用户" + id);
user.setPassword((id + 1000) + "");
return user;
}
}
- 然后编写切面类
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Component
@Aspect
public class TimeAspect {
private Logger logger = LoggerFactory.getLogger(getClass());
@Pointcut("execution(* com.mright.security.demo.platform.controller.UserController.*(..))")
public void user() {
}
@Before("user()")
public void beforeControllerMethod(JoinPoint joinPoint) {
logger.info("进入TimeAspect切面,执行之前");
}
@Around("user()")
public Object handleControllerMethod(ProceedingJoinPoint pjp) throws Throwable {
logger.info("进入TimeAspect切面");
Object[] args = pjp.getArgs();
Arrays.asList(args).forEach(arg -> logger.info(arg.toString()));
long start = System.currentTimeMillis();
Object obj = pjp.proceed();
logger.info(obj.toString());
logger.info("执行耗时 : " + (System.currentTimeMillis() - start) + "ms");
return obj;
}
@After("user()")
public void afterControllerMethod(JoinPoint joinPoint) {
logger.info("进入TimeAspect切面,执行之后");
}
@AfterReturning(returning = "result", pointcut = "user()")
public void doAfterReturnint(Object result) {
logger.info("方法返回值:" + result.toString());
}
}
- @Aspect注解是切面注解类
@Pointcut切点定义
@Before是方法执行前调用
@After是方法执行后调用
@AfterReturning方法执行返回值调用 - 在切面中,可以获取到请求的参数,也可以获取到响应值,并且我们可以对参数和响应值做出一定修改并生效
@Around("user()")
public Object handleControllerMethod(ProceedingJoinPoint pjp) throws Throwable {
logger.info("进入TimeAspect切面");
Object[] args = pjp.getArgs();
Arrays.asList(args).forEach(arg -> logger.info(arg.toString()));
long start = System.currentTimeMillis();
Object obj = pjp.proceed();
if (obj instanceof User) {
User user = (User) obj;
user.setUsername(user.getUsername() + " &");
}
logger.info(obj.toString());
logger.info("执行耗时 : " + (System.currentTimeMillis() - start) + "ms");
return obj;
}
- 在上述代码中,我对返回值user对象的username属性进行了添加后缀" &" 操作
- 接下来访问 http://localhost:8080/user/55
- 查看响应值
{
"id": 55,
"username": "用户55 &",
"password": "1055"
}
- 查看日志
2019-06-21 10:12:44.029 INFO 13308 --- [nio-8083-exec-1] c.m.s.demo.framework.aspect.TimeAspect : 进入TimeAspect切面
2019-06-21 10:12:44.030 INFO 13308 --- [nio-8083-exec-1] c.m.s.demo.framework.aspect.TimeAspect : 55
2019-06-21 10:12:44.030 INFO 13308 --- [nio-8083-exec-1] c.m.s.demo.framework.aspect.TimeAspect : 进入TimeAspect切面,执行之前
2019-06-21 10:12:44.032 INFO 13308 --- [nio-8083-exec-1] c.m.s.d.p.controller.UserController : 进入UserController,执行getUserById
2019-06-21 10:12:44.032 INFO 13308 --- [nio-8083-exec-1] c.m.s.demo.framework.aspect.TimeAspect : User{id=55, birthday=null, username='用户55 &', password='1055'}
2019-06-21 10:12:44.033 INFO 13308 --- [nio-8083-exec-1] c.m.s.demo.framework.aspect.TimeAspect : 执行耗时 : 3ms
2019-06-21 10:12:44.033 INFO 13308 --- [nio-8083-exec-1] c.m.s.demo.framework.aspect.TimeAspect : 进入TimeAspect切面,执行之后
2019-06-21 10:12:44.033 INFO 13308 --- [nio-8083-exec-1] c.m.s.demo.framework.aspect.TimeAspect : 方法返回值:User{id=55, birthday=null, username='用户55 &', password='1055'}