微服务系列之SpringBoot基础:过滤器、拦截器、Aop切面

SpringBoot过滤器、拦截器、Aop切面


一、过滤器(Filter)

启动类添加如下注解:@ServletComponentScan。
Filter过滤器是Servlet容器层面的,在实现上基于函数回调,可以对几乎所有请求进行过滤。Filter完整的流程:Filter对用户请求进行预处理,接着将请求交给Servlet进行预处理并生成响应,最后Filter再对服务器响应进行后处理。过滤器实现示例如下:

package com.example.demo01.filter;

import org.springframework.util.StringUtils;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.IOException;

@WebFilter(filterName = "SessionFilter",
        displayName = "SessionFilter",
        urlPatterns = {"/*"},
        initParams = @WebInitParam(
                name = "SessionFilterInitParam",
                value = "SessionFilter")
)
public class SessionFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequestWrapper requestWrapper = new HttpServletRequestWrapper((HttpServletRequest) servletRequest) {

            @Override
            public String getHeader(String name) {
                String superHeader = super.getHeader(name);
                if ("test".equals(name) && StringUtils.isEmpty(superHeader)) {
                    String session = servletRequest.getParameter("session");
                    if (!StringUtils.isEmpty(session)) {
                        return session;
                    }
                }
                return superHeader;
            }

        };
        filterChain.doFilter(requestWrapper, servletResponse);
    }
}

二、拦截器(Interceptor)

Interceptor拦截器和Filter有本质上的不同,Filter依赖于Servlet容器,而Interceptor依赖于Spring框架,是aop的一种表现,基于Java的动态代理实现的。拦截器在AOP中用于在某个方法或字段被访问之前,进行拦截,然后在之前或之后加入某些操作。拦截是AOP的一种实现策略。

拦截器作用:
日志记录:记录请求信息的日志;
权限检查:如登录检测,进入处理器检测检测是否登录;
性能监控:通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间。

拦截器实现
通过实现HandlerInterceptor接口,并重写该接口的方法来实现自定义拦截。在SpringBoot中实现拦截器的方式:
声明拦截器的类:
通过实现HandlerInterceptor接口,实现preHandle、postHandle和afterCompletion方法。
配置类配置拦截器:
通过实现WebMvcConfigurer接口,实现addInterceptors方法。如下示例代码:

@Slf4j
public class SignAuthInterceptor implements HandlerInterceptor {
    /**
     * 5分钟有效期
     */
    private final static long MAX_EXPIRE = 5 * 60;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("request URI = " + request.getRequestURI());
        HttpServletRequest requestWrapper = new BodyReaderHttpServletRequestWrapper(request);
        //获取全部参数(包括URL和body上的)
        SortedMap<String, String> allParams = HttpUtils.getAllParams(requestWrapper);
        //对参数进行签名验证
        String headerSign = request.getHeader(CommonConstant.X_SIGN);
        String timesTamp = request.getHeader(CommonConstant.X_TIMESTAMP);

        //1.校验时间有消息
        try {
            DateUtils.parseDate(timesTamp, "yyyyMMddHHmmss");
        } catch (Exception e) {
            throw new IllegalArgumentException("签名验证失败:X-TIMESTAMP格式必须为:yyyyMMddHHmmss");
        }
        Long clientTimestamp = Long.parseLong(timesTamp);
        //判断时间戳 timestamp=201808091113
        if ((DateUtils.getCurrentTimestamp() - clientTimestamp) > MAX_EXPIRE) {
            throw new IllegalArgumentException("签名验证失败:X-TIMESTAMP已过期");
        }

        //2.校验签名
        boolean isSigned = SignUtil.verifySign(allParams,headerSign);

        if (isSigned) {
            log.debug("Sign 签名通过!Header Sign : {}",headerSign);
            return true;
        } else {
            log.error("request URI = " + request.getRequestURI());
            log.error("Sign 签名校验失败!Header Sign : {}",headerSign);
            //校验失败返回前端
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8");
            PrintWriter out = response.getWriter();
            Result<?> result = Result.error("Sign签名校验失败!");
            out.print(JSON.toJSON(result));
            return false;
        }
    }
/**
 * 签名 拦截器配置
 */
@Configuration
public class SignAuthConfiguration implements WebMvcConfigurer {
    public static String[] urlList = new String[] {"/sys/dict/getDictItems/*", "/sys/dict/loadDict/*","/sys/api/translateDictFromTable", "/sys/api/translateDictFromTableByKeys"};
    @Bean
    public SignAuthInterceptor signAuthInterceptor() {
        return new SignAuthInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(signAuthInterceptor()).addPathPatterns(urlList);
    }
}

三、Spring aop切面(Aspect)

相比较于拦截器,Spring 的aop则功能更强大,封装的更细。
Aop涉及到的注解如下:

@Aspect:将一个 java 类定义为切面类。
@Pointcut:定义一个切入点,可以是一个规则表达式,比如下例中某个 package 下的所有函数,也可以是一个注解等。
@Before:在切入点开始处切入内容。
@After:在切入点结尾处切入内容。
@AfterReturning:在切入点 return 内容之后处理逻辑。
@Around:在切入点前后切入内容,并自己控制何时执行切入点自身的内容。原则上可以替代
@Before和@After。
@AfterThrowing:用来处理当切入内容部分抛出异常之后的处理逻辑。
@Order(100):AOP 切面执行顺序, @Before 数值越小越先执行,@After 和 @AfterReturning
数值越大越先执行。

引入依赖

 <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
 </dependency>

示例代码如下:

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;

@Aspect
@Slf4j
@Component
public class MethodMonitorAspect {

    private long startTime;

    //声明切面类路径,类型必须为final String类型的,注解里要使用的变量只能是静态常量类型的
    //public static final String POINT = "execution(* com.product.service.*.*(..))";
    //也可以使用注解声明切入点,如下
    @Pointcut("execution(* com.liuc.server.api.sechdule.invoice.*.*(..))")
    public void point() {
    }

    @Before("point()")
    public void doBefore(JoinPoint pj) {
        this.startTime = System.currentTimeMillis();

        Object target = pj.getTarget();
        String className = target.getClass().getName();  //当前执行的方法所属的类、包

        MethodSignature signature = (MethodSignature) pj.getSignature();
        String methodName = signature.getName();  //当前执行的方法名称
        SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        log.info("类{}.{}方法开始执行" + (dateformat.format(this.startTime)),className,methodName);
    }
    @After("point()")
    public void doAfter(JoinPoint pj) {
        long endTime = System.currentTimeMillis();

        Object target = pj.getTarget();
        String className = target.getClass().getName();  //当前执行的方法所属的类、包

        MethodSignature signature = (MethodSignature) pj.getSignature();
        String methodName = signature.getName();  //当前执行的方法名称

       log.info("类{}.{}方法执行了" + (endTime - this.startTime) + "ms",className,methodName);
    }
    
    @Around("point()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();
        //执行方法
        Object result = point.proceed();
        //执行时长(毫秒)
        long time = System.currentTimeMillis() - beginTime;

        //保存日志
        saveSysLog(......);

        return result;
    }
}

总结

过滤器、拦截器以及切片的调用顺序
调用顺序Filter->Interceptor->Aspect->Controller
系统中请求被过滤的时机越早对服务的性能影响越小,因此在编写相对比较公用的代码时,优先考虑过滤器,然后是拦截器,最后是aop。比如权限校验,一般情况下,所有的请求都需要做登陆校验,此时就应该使用过滤器在最顶层做校验;比如日志记录,一般日志只针对部分逻辑做日志记录,而且牵扯到业务逻辑完成前后的日志记录,因此使用过滤器不能细致地划分模块,此时应该考虑拦截器,然而拦截器也是依据URL做规则匹配,因此相对来说不够细致,因此我们会考虑到使用AOP实现,AOP可以针对代码的方法级别做拦截,非常适合日志功能。

1.过滤器
过滤器可以拿到原始的http请求,但是拿不到请求的控制器和请求控制器中的方法的信息。过滤器可以拦截到方法的请求和响应(ServletRequest request, ServletResponse response),并对请求响应做出像响应的过滤操作,比如设置字符编码,鉴权操作等。过滤器依赖于Servlet容器,属于Servlet规范的一部分。Filter的执行由Servlet容器回调完成。Filter的生命周期由Servlet容器管理。

2.拦截器
拦截器可以拿到你请求的控制器和方法,却拿不到请求方法的参数。拦截器是独立存在的,可以在任何情况下使用。拦截器通常通过动态代理(反射)的方式来执行,通过IOC容器来管理,因此可以通过注入等方式来获取其他Bean的实例,使用会更方便。

3.Aop切片
AOP可以拿到方法的参数,但是却拿不到http请求和响应的对象。AOP操作可以对操作进行横向的拦截,最大的优势在于他可以获取执行方法的参数,对方法进行统一的处理.常见使用日志,事务,请求参数安全验证等 。

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值