java 过滤器、拦截器和切面

过滤器(Filter)         :可以拿到原始的http请求,但是拿不到你请求的控制器和请求控制器中的方法的信息。

拦截器(Interceptor):可以拿到你请求的控制器和方法,却拿不到请求方法的参数。

切片   (Aspect)       :  可以拿到方法的参数,但是却拿不到http请求和响应的对象

Filter过滤器:拦截web访问url地址。
Interceptor拦截器:拦截以 .action结尾的url,拦截Action的访问。

Spring AOP拦截器:只能拦截Spring管理Bean的访问(业务层Service)

Filter与Interceptor联系与区别
1. 拦截器是基于java的反射机制,使用代理模式,而过滤器是基于函数回调。
2. 拦截器不依赖servlet容器,过滤器依赖于servlet容器。
3. 拦截器只能对action起作用,而过滤器可以对几乎所有的请求起作用(可以保护资源)。
4. 拦截器可以访问action上下文,堆栈里面的对象,而过滤器不可以。

5. 执行顺序:过滤前-拦截前-Action处理-拦截后-过滤后。

例:

一:过滤器

过滤器能够对目标资源的请求和相应进行截取。

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 初始化
{
    "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 : 执行顺序   @Around 》@Before》@Around 》@After
  • 在切面中,可以获取到请求的参数,也可以获取到响应值,并且我们可以对参数和响应值做出一定修改并生效

 

    @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'}

总结 

 è¿æ»¤å¨ãæ¦æªå¨ãåé¢çæ§è¡é¡ºåº

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值