Spring拦截器、过滤器、AOP

拦截器

  • 什么是拦截器
    Java里的拦截器是动态拦截Action调用的对象,它提供了一种机制可以使开发者在一个Action执行的前后执行一段代码,也可以在一个Action执行前阻止其执行,同时也提供了一种可以提取Action中可重用部分代码的方式。
    拦截器的拦截,是基于Java反射机制实现的,拦截的对象只能是实现了接口的类,而不能拦截url这种链接。

  • 拦截器作用
    当请求到达DispatcherServlet时,它会根据HandlingMapping的机制找到处理器,这样就会返回一个HandlerExecutionChain对象,这个对象包含处理器和拦截器。这里的拦截器会对处理器进行拦截,这样通过拦截器就可以增强处理器的功能。

  • 如何应用拦截器

PreAuthorize.java (伪代码)
注解,作为拦截器的"连接点"

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Documented
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface PreAuthorize {

  String [] perms() default "";

  String [] roles() default "";

}

PreAuthorizeInterceptor.java (伪代码)
拦截器实现,通过反射指定了要拦截的"切点"PreAuthorize

import ****.annotation.PreAuthorize;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

@Slf4j
public class PreAuthorizeInterceptor extends HandlerInterceptorAdapter {

  public PreAuthorizeInterceptor() {
    super();
  }

  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

    if (!(handler instanceof HandlerMethod)) {
      return true;
    }

    HandlerMethod handlerMethod = (HandlerMethod) handler;
    Method method = handlerMethod.getMethod();
    PreAuthorize annotation = method.getAnnotation(PreAuthorize.class);
    
    try {
      if (annotation != null) {
        // 获取注解内的参数
        String perms [] = annotation.perms();
        String roles [] = annotation.roles();
        /***process:想要在请求进入接口前做的处理工作***/
      }
    } catch (Exception e) {
      log.error("@PreAuthorize error: {}", Throwables.getStackTraceAsString(Throwables.getRootCause(e)));
      return false;
    }

    // TODO 增加过滤日志,对重要接口的调用打印日志:log.info("当前用户:" + userInfo.getUserName() + ",成功进入接口:" + "");
    return true;
  }

  @Override
  public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    super.postHandle(request, response, handler, modelAndView);
  }

  @Override
  public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    super.afterCompletion(request, response, handler, ex);
  }

  @Override
  public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    super.afterConcurrentHandlingStarted(request, response, handler);
  }
}

WebConfig.java (伪代码)
实现了自己的拦截器后,要在spring中配置,将该拦截器加入到spring容器中

import *****.interceptor.PreAuthorizeInterceptor;
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.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(preAuthorizeInterceptor());
  }

  @Bean
  public PreAuthorizeInterceptor preAuthorizeInterceptor() {
    return new PreAuthorizeInterceptor();
  }

}

最后应用拦截器时,只需要在接口前添加@PreAuthorize注解及相应的参数即可。

过滤器

  • 什么是过滤器
    Java的过滤器能够为我们提供系统级别的过滤,即过滤所有的web请求。(这一点是拦截器无法做到的,Java Web中传入的request, response提前过滤掉一些信息等等)

  • 过滤器作用

  1. 用户访问权限过滤;
  2. 日志过滤,可以记录特殊用户的特殊请求的记录等;
  3. 图像、数据压缩、编码格式等过滤;
  • 如何应用过滤器
    实现一个简单的过滤器处理逻辑:
    TimeFilter.java
package com.spring.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

@Component
@Slf4j
public class TimeFilter implements Filter {


  @Override
  public void init(FilterConfig filterConfig) throws ServletException {
    log.info("初始化过滤器:{}", filterConfig.getFilterName());
  }

  @Override
  public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    log.info("start to doFilter");
    long startTime = System.currentTimeMillis();

    filterChain.doFilter(servletRequest, servletResponse);

    long endTime = System.currentTimeMillis();

    String url;
    if (servletRequest instanceof HttpServletRequest) {
      url = ((HttpServletRequest) servletRequest).getRequestURI();
    } else {
      url = "";
    }

    log.info("the request of {} consums {} ms", url, endTime - startTime);
    log.info("end doFilter.");

  }

  @Override
  public void destroy() {
    log.info("销毁过滤器.");
  }
}

在代码中配置我们实现的过滤器:
方式一 WebConfig.java

package com.spring.filter;

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;

@Configuration
public class WebConfig {

  @Bean
  public FilterRegistrationBean filterConfig() {
    FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>();
    filterFilterRegistrationBean.setFilter(new TimeFilter());
    ArrayList<String> urls = new ArrayList<>();
    //匹配所有url
    urls.add("/*");
    filterFilterRegistrationBean.setUrlPatterns(urls);

    return filterFilterRegistrationBean;
  }

}

方式二 通过注解的方式

// 在实现的过滤器前加入 @WebFilter注解
@Component
@WebFilter(urlPatterns = "/*", filterName = "timeFilter")
public class TimeFilter implements Filter {
...
}

最后启动SpringBoot,访问服务,发现过滤器成功执行:
在这里插入图片描述

过滤器与拦截器对比

  • 区别
    过滤器
  1. 在servlet规范中定义,是由servlet容器支持的;
  2. 对request和response请求进行过滤;(取你所取)
  3. Filter的链式调用:filterChanin.doFilter()
    拦截器
  4. 在Spring容器内,由Spring框架支持;
  5. 通过Java反射机制来拦截web请求(拒你所拒);
  6. Interceptor也是链式调用;
  7. 拦截器一般可用于(日志记录、权限检查、性能监控、通用行为)
    在这里插入图片描述
  • 总结
  1. 拦截器是基于Java反射机制来实现的,而过滤器是基于函数回调来实现的;
  2. 拦截器不依赖servlet容器,过滤器依赖于servlet容器;
  3. 拦截器只对Action起作用,过滤器可以对所有请求起作用;
  4. 拦截器可以访问Action上下文和值栈中的对象,过滤器不能;
  5. 在Action的生命周期中,拦截器可以多次调用,而过滤器只能在容器初始化时调用一次。

Spring AOP

  • 什么是Spring AOP
    面向切面编程,用于增强指定方法的处理,即将指定方法的处理过程插入到一套规定好的流程中(比如说,执行方法前要做些什么,执行过程中做些什么之类的)。

  • 为什么要使用Spring AOP

  • 如何使用Spring AOP
    AOP中,使用注解@Pointcut来定义切点,其后面采用正则式匹配到连接点(即我们想要进行拦截增强处理的方法)

// 切点正则式
execution(* com.spring.aop.AspectTest.UserServiceImpl.printUser(..))

其中:

  1. execution表示在执行的时候,拦截里面的正则匹配的方法;
  2. *表示任意返回类型的方法;
  3. com.springboot.chapter4.aspect.service.impl.UserServiceImpl指定目标对象的全限定名称;
  4. printUser指定目标对象的方法;
  5. (…)表示任意参数进行匹配;

此时,Spring就可以通过此正则式知道需要对类UserServiceImpl的printUser方法进行AOP增强,它就会将正则式匹配的方法和对应切面的方法织入约定流程中(如下图)
在这里插入图片描述

下面是Spring AOP的一个示例:
第一,我们需要确定连接点,即我们要增强处理的目标对象
UserService.java

package com.spring.aop.AspectTest;

public interface UserService {

  public void printUser(User user);

}

UserServiceImpl.java

package com.spring.aop.AspectTest;

import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {

  @Override
  public void printUser(User user) {
    if (user == null) {
      throw new RuntimeException("检查用户参数是否为空......");
    }
    System.out.println("id = " + user.getId());
    System.out.println("username = " + user.getUserName());
    System.out.println("note = " + user.getNote());
  }
}

第二,定义 切点切面,即它向Spring描述哪些类的哪些方法需要启用AOP编程,以及如何增强这些方法

package com.spring.aop.AspectTest;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class MyAspect {

  @Pointcut("execution(* com.spring.aop.AspectTest.UserServiceImpl.printUser(..))")
  public void pointCut() {

  }

  @Before("pointCut()")
  public void before() {
    System.out.println("before ......");
  }

  @After("pointCut()")
  public void after() {
    System.out.println("after ......");
  }

  @AfterReturning("pointCut()")
  public void afterReturning() {
    System.out.println("afterReturning ......");
  }

  @AfterThrowing("pointCut()")
  public void afterThrowing() {
    System.out.println("afterThrowing ......");
  }

  @Around("pointCut()")
  public void around(ProceedingJoinPoint jp) throws Throwable {
    System.out.println("around before ......");

    jp.proceed();
    System.out.println("around after ......");
  }

}

完成了上面的连接点、切面和切点等的定义之后,就可以测试AOP,需要先搭建一个Web开发环境,开发一个Controller

package com.spring.aop.AspectTest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

/**
 * spring boot项目中的Controller层
 */
@RestController
@RequestMapping("/user")
public class TestController {

  @Autowired
  private UserService userService;

  @RequestMapping("/print")
  @ResponseBody
  public User printUser(@RequestParam("id") int id, @RequestParam("userName") String userName, @RequestParam("note") String note) {
    User user = new User();
    user.setId(id);
    user.setUserName(userName);
    user.setNote(note);

    userService.printUser(user);

    return user;
  }
}

SpringBoot配置文件

package com.spring.aop.AspectTest;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

/**
 * Spring boot项目中的启动类
 *
 */
@SpringBootApplication(scanBasePackages = {"com.spring.aop.AspectTest"})
public class Application {

  @Bean(name = "myAspect")
  public MyAspect initMyAspect() {
    return new MyAspect();
  }

  public static void main(String [] args) {
    SpringApplication.run(Application.class, args);
  }
}
  • 多个切面
    上面是实现一个完整切面的例子,下面再简单介绍一下支持多个切面运行的一些需要注意的地方:
    最值得关注的就是当我们对于同一个连接点定一个了多个切面的时候,需要知道其运行的顺序:

使用注解@Order指定切面的顺序,例如:

...
@Aspect

@Order(1)
public class MyAspect1 {
  ...
}

使用Ordered接口也可以指定顺序,例如:

...
@Aspect

public class MyAspect1 implements Ordered {
  //指定顺序
  @Override
  public int getOrder() {
    return 1;
  }
  ...
}

Spring interceptor与AOP之间的区别与联系

  • 作用层面
    拦截器只对action负责,作用曾米娜一般位于Controller层;
    Spring AOP主要是拦截对Spring管理的Bean的访问,一般作用于Service层;

  • 联系与区别
    联系:Spring AOP和拦截器一样,都是AOP的实现方式的一种,均使用代理模式实现;
    区别:拦截器和过滤器相似,是链式的处理模式,这样有一个缺点是每次请求都会访问action的上下文,不够灵活;
    Spring AOP的注解有@Before、@After、@AfterReturning、@AfterThrowing、@Around,可以更灵活的配置要监听处理的Bean,即可以对指定的方法等进行拦截处理,而不是每个接口等请求都需要进入注解关联的处理。

参考链接

https://juejin.im/post/5c6901206fb9a049af6dcdcf
https://www.cnblogs.com/paddix/p/8365558.html

Spring拦截器过滤器AOPSpring框架中常用的三种技术,用于实现对请求的处理和控制。它们的作用和使用方式有一些不同,我会分别对它们进行简单解释。 1. 过滤器(Filter): 过滤器是Java Servlet规范提供的一种技术,用于在请求到达Servlet之前或响应返回给客户端之前拦截和处理请求。过滤器可以在Web应用的整个生命周期中起作用,对所有请求都生效。常见的应用场景包括:编码转换、身份认证、日志记录等。 2. 拦截器(Interceptor): 拦截器Spring框架提供的一种技术,用于在请求到达Handler(Controller方法)之前或响应返回给客户端之前对请求进行拦截和处理。拦截器只在Spring MVC中起作用,用于实现对请求的预处理和后处理。常见的应用场景包括:身份认证、权限控制、日志记录等。 3. AOP(面向切面编程): AOP是一种编程思想,也是Spring框架提供的一种技术。通过AOP,可以将与业务逻辑无关的横切关注点(如日志、事务管理等)从业务逻辑中解耦出来,以模块化的方式进行管理。在Spring中,AOP通常通过动态代理实现,可以在方法执行前、后或抛出异常时进行一些额外的处理。AOP常用于事务管理、日志记录、性能监控等方面。 总结: - 过滤器主要用于在Servlet规范中对请求进行拦截和处理。 - 拦截器主要用于在Spring MVC中对请求进行拦截和处理。 - AOP主要用于将与业务逻辑无关的横切关注点进行解耦和管理。 希望以上解释能对你有所帮助!如果有其他问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值