SpringBoot 之AOP实现过滤器、拦截器、切面

SpringBoot中的Interceptor(拦截器)Filter(过虑器)AOP(面向切面编程)用法小结 Interceptor(拦截器) 传统拦截器的写法: SpringBoot实现自定义拦截器方法: 相比springmvc,springboot中拦截器不需要在xml中配置,只需定义拦截器类implements HandlerInterceptor 和拦截器拦截路径的配置类extends WebMvcConfigurerAdapter。 第一步创建一个类实现Handler... 阅读详情
AI摘要

本文深入讲解面向切面编程(AOP)的基本概念与实践应用。涵盖了AOP的核心思想、底层实现机制,以及过滤器、拦截器、切面的区别,并通过具体案例展示了如何利用AOP打印HTTP请求的入参、返回值及接口耗时。

AOP概述

AOP Aspect Oriented Programing 面向切面编程。
AOP 是一种编程思想,不是一项技术。
AOP 使用横向抽取机制实现代码复用,继承方式是纵向的重复性代码。
AOP 思想: 基于代理,对原来目标对象,创建代理对象,在代理对象中,对原有业务方法进行增强。
AOP 实现方式: 过滤器、拦截器、切面。
AOP实际企业应用 : 打印方法运行时间、统一打印入参和出参、事务管理、日志记录、 权限控制、 缓存。

AOP在项目中的应用: 判断用户是否登录、统一打印入参和出参、记录请求运行时间。

AOP底层实现机制

AOP 需要对目标进行代理对象创建, Spring AOP采用哪种技术对目标进行代理对象的创建: JDK动态代理、CGLIB动态代理 。

JDK 动态代理: 针对接口生成代理。
CGLIB 动态代理 直接对目标对象(可以没有接口)生成代理对象,原理: 对目标类创建子类对象。

Spring AOP的代理小结:

  • 如果目标对象有接口,Spring优先使用 JDK动态代理。
  • 如果目标对象没有接口,Spring 采用Cglib 动态代理。
  • 被代理增强的方法,不能为final修饰。

过滤器、拦截器、切面区别

过滤器拦截器Aspect
关注的点所有web请求部分web请求偏向于业务层面的拦截
实现原理函数回调JAVA反射机制(动态代理)动态代理
Servlet提供的支持Filter接口无无
Spring提供的支持无HandlerInterceptorAdapter类、HandlerInterceptor接口@Aspect
可能需要实现的方法void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)Object around(ProceedingJoinPoint joinPoint)

过滤器 Filter

简单来讲就是用来过滤东西的,比如:统一设置编码,统一设置所有请求头信息。

实现: 实现 javax.Servlet.Filter 接口就可以创建一个过滤器。

使用过滤器统一请求耗时

/**
 * 统一记录请求耗时
 */
@Slf4j
@WebFilter(filterName = "ApiAccessFilter", urlPatterns = "/*")
public class ApiAccessFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
                         FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        long start = System.currentTimeMillis();
        filterChain.doFilter(servletRequest, servletResponse);
        long duration = System.currentTimeMillis() - start;
        if (duration > 200) {
            log.info("Api Access uri: {}, method: {}, client: {}, duration: {}ms , time is too long",
                    request.getRequestURI(), request.getMethod(), getIP(request), System.currentTimeMillis() - start);
        }else{
            log.info("Api Access uri: {}, method: {}, client: {}, duration: {}ms",
                    request.getRequestURI(), request.getMethod(), getIP(request), System.currentTimeMillis() - start);
        }

    }

    private String getIP(HttpServletRequest request) {
        if (request == null) {
            return "0.0.0.0";
        }
        String Xip = request.getHeader("X-Real-IP");
        String XFor = request.getHeader("X-Forwarded-For");
        String UNKNOWN_IP = "unknown";
        if (StringUtils.isNotEmpty(XFor) && !UNKNOWN_IP.equalsIgnoreCase(XFor)) {
            //多次反向代理后会有多个ip值,第一个ip才是真实ip
            int index = XFor.indexOf(",");
            if (index != -1) {
                return XFor.substring(0, index);
            } else {
                return XFor;
            }
        }
        XFor = Xip;
        if (StringUtils.isNotEmpty(XFor) && !UNKNOWN_IP.equalsIgnoreCase(XFor)) {
            return XFor;
        }
        if (StringUtils.isBlank(XFor) || UNKNOWN_IP.equalsIgnoreCase(XFor)) {
            XFor = request.getHeader("Proxy-Client-IP");
        }
        if (StringUtils.isBlank(XFor) || UNKNOWN_IP.equalsIgnoreCase(XFor)) {
            XFor = request.getHeader("WL-Proxy-Client-IP");
        }
        if (StringUtils.isBlank(XFor) || UNKNOWN_IP.equalsIgnoreCase(XFor)) {
            XFor = request.getHeader("HTTP_CLIENT_IP");
        }
        if (StringUtils.isBlank(XFor) || UNKNOWN_IP.equalsIgnoreCase(XFor)) {
            XFor = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (StringUtils.isBlank(XFor) || UNKNOWN_IP.equalsIgnoreCase(XFor)) {
            XFor = request.getRemoteAddr();
        }
        return XFor;
    }
}

拦截器 Interceptor

在执行方法之前,进行拦截,然后在之前或之后加入一些操作。

使用场景:

  • 日志记录: 记录请求信息的日志,以便进行信息监控、信息统计、计算PV(Page View)等。
  • 权限检查: 如登录检测,进入处理器检测检测是否登录。
  • 性能监控: 通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间。
  • 通用行为: 读取cookie得到用户信息并将用户对象放入请求,从而方便后续流程使用,只要是多个处理器都需要的即可使用拦截器实现。

实现拦截器: 实现 HandlerInterceptor 接口就可以创建一个拦截器。

HandlerInterceptor 接口的源码如下:

public interface HandlerInterceptor {
  	// 在请求处理之前进行调用(Controller方法调用之前)
	default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		return true;
	}
  	// 请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后),如果异常发生,则该方法不会被调用
	default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable ModelAndView modelAndView) throws Exception {
	}
  	// 在整个请求结束之后被调用,也就是在 DispatcherServlet 渲染了对应的视图之后执行(主要是用于进行资源清理工作
	default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable Exception ex) throws Exception {
	}
}

运行流程:
1、拦截器是一个列表,按顺序执行拦截器的 preHandle() 方法。
2、如果 preHandle() 方法返回false,不再往下执行。
3、执行主方法,即 controller 里的接口。
4、逆序执行 postHandle() 方法。
5、view页面渲染
6、逆序执行 afterCompletion() 方法。

使用demo:

import cn.hutool.core.net.URLDecoder;
import cn.hutool.json.JSONUtil;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import sca.pro.common.contants.Contants;
import sca.pro.common.jwt.MapInfo;
import sca.pro.common.response.HttpCode;
import sca.pro.common.response.HttpResult;
import sca.pro.system.common.util.ServletUtils;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;

public class RequestContextInterceptor extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        Method method = ((HandlerMethod) handler).getMethod();
        if (!method.isAnnotationPresent(AuthInfoRequired.class)) {
            return true;
        }
        AuthInfoRequired annotation = method.getAnnotation(AuthInfoRequired.class);
        if (!annotation.required()) {
            return true;
        }
        if (initHeaderContext(request)) {
            return super.preHandle(request, response, handler);
        } else {
            returnJson(response, JSONUtil.toJsonStr(new HttpResult().builder()
                    .code(HttpCode.UNAUTHORIZED).build()));
            return false;
        }
    }
 
    private boolean initHeaderContext(HttpServletRequest request) {
        String mapInfoStr = ServletUtils.URLDecoderString(request.getHeader(Contants.JWT_MAP_KEY));
        if (mapInfoStr != null) {
            try {
                MapInfo mapInfo = JSONUtil.toBean(mapInfoStr, MapInfo.class);
                new RequestContext.RequestContextBuild()
                        .mapInfo(mapInfo)
                        .bulid();
                if (mapInfo.getUsername() == null) {
                    return false;
                }
                return true;
            } catch (Exception e) {
                return false;
            }
        } else {
            return false;
        }
    }
 
    private void returnJson(HttpServletResponse response, String json) throws Exception {
        PrintWriter writer = null;
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html; charset=utf-8");
        try {
            writer = response.getWriter();
            writer.print(json);
        } catch (IOException e) {
        } finally {
            if (writer != null)
                writer.close();
        }
    }
 
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        RequestContext.clean();
        super.postHandle(request, response, handler, modelAndView);
    }
}

使用拦截器判断是否登录

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NotCheckToken {
}

@Component
public class AuthenticationIntercept extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        if (handler instanceof HandlerMethod) {
            HandlerMethod method = (HandlerMethod) handler;
            NotCheckToken methodAnnotation = method.getMethodAnnotation(NotCheckToken.class);
            if (Objects.nonNull(methodAnnotation)) {
                return true;
            }
        }
        String userId = request.getHeader("userId");
        if (StringUtils.isBlank(userId)) {
            throw new Exception("未登录");
        }
        request.setAttribute("userId", userId);
        return super.preHandle(request, response, handler);
    }

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

拦截器注册:

@Configuration
public class WebAppConfigurer implements WebMvcConfigurer {
    @Autowired
    private AuthenticationIntercept authenticationIntercept;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        InterceptorRegistration registration = registry.addInterceptor(authenticationIntercept);
        // 拦截所有请求
        registration.addPathPatterns("/**");
        // 添加不拦截路径
        registration.excludePathPatterns("/login", "/error", "/logout","/login.html");
    }
}

AspectJ 简介

AspectJ 是第三方 AOP 框架, 现阶段企业 AOP 开发,使用 AspectJ 。

AspectJ 提供的几种Advice类型 :

  • before : 在目标方法运行前增强, 前置通知。应用场景: 日志记录、权限控制。
  • afterReturing : 在目标方法返回值获取后进行增强,后置通知。应用场景:和业务相关, 网上营业厅查询余额后,自动下发短信。
  • around : 在目标方法运行前后进行增强,环绕通知。应用场景:日志、缓存、权限、性能监控、 事务管理。
  • afterThrowing: 在目标方法抛出异常后进行增强,抛出通知。应用场景: 处理异常 ,记录日志。
  • After (新增): 不管是否发生异常,该通知都执行,类似 finally。应用场景 : 关闭和释放一些资源

@AspectJ 注解开发 AOP

AOP 注解说明

  • @Before 前置通知 (方法前)
  • @AfterReturning 后置通知 (方法返回后)
  • @Around 环绕通知 (方法前后)
  • @AfterThrowing 抛出通知 (异常发生后)
  • @After 最终通知 (一定在方法后执行)

@Aspect: 标识为切面类,被容器识别。

@Pointcut: 配置切入点:

  • 拦截 com.demo.controller 包下的所有类型所有方法:@Pointcut(value = "execution(* com.demo.controller.*.*(..))")
  • 拦截 com.demo.controller 包和子包下的所有类型所有方法:@Pointcut(value = "execution(* com.demo.controller..*.*(..))")
  • 拦截 com.demo.controller 包下 DemoController 类型下的所有方法:@Pointcut(value = "execution(* com.demo.controller.DemoController.*.*(..))")
  • 方法上标注了Inter注解就会拦截:@Pointcut(value = "@annotation(com.demo.Inter)")
  • 类上标注了Inter注解就会拦截:@Pointcut(value = "@within(com.demo.Inter)")
  • 且的关系,类和方法上同时标注这个注解才会拦截:@Pointcut(value = "@within(com.demo.Inter) && @annotation(com.demo.Inter)")
  • 或的关系,类或者方法上有标注这个注解就会拦截,类上标注拦截所有方法,方法上标注类上没有只拦截标注的方法:@Pointcut(value = "@within(com.demo.Inter) || @annotation(com.demo.Inter)")

案例代码

1、将业务逻辑组件和切面类都加入到容器中;告诉Spring哪个是切面类(@Aspect)。
2、在切面类上的每一个通知方法上标注通知注解,告诉Spring何时何地运行(切入点表达式)。
3、开启基于注解的aop模式;@EnableAspectJAutoProxy

1、导入依赖

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

2、编写切面类

@Aspect
@Component
@Slf4j
public class WebLogAspect {

    //切入点描述 这个是controller包的切入点
    @Pointcut("execution(public * com.example.demo.controller..*.*(..))")
    //签名,可以理解成这个切入点的一个名称
    public void controllerLog(){}

    @Pointcut("execution(public * com.example.demo.service..*.*(..))")
    public void serviceLog(){}

    //在切入点的方法run之前要干的
    @Before("controllerLog()||serviceLog()")
    public void logBeforeController(JoinPoint joinPoint) {
        //这个RequestContextHolder是SpringMvc提供来获得请求的东西
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = ((ServletRequestAttributes)requestAttributes).getRequest();
        // 记录下请求内容
        log.info("################URL : " + request.getRequestURL().toString());
    }

    //在切入点的方法run之后要干的
    @After("controllerLog()||serviceLog()")
    public void logAfterController(JoinPoint joinPoint) {
        System.out.println("after");
    }
}

切面类示例

@Aspect  
@Component  
public class LogAspect {  
    @Pointcut("execution(public * com.example.controller.*.*(..))")  
    public void webLog(){}  
  
    @Before("webLog()")  
    public void deBefore(JoinPoint joinPoint) throws Throwable {  
        // 接收到请求,记录请求内容  
        ServletRequestAttributes attributes = 
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();  
        HttpServletRequest request = attributes.getRequest();  
        // 记录下请求内容  
        System.out.println("URL : " + request.getRequestURL().toString());  
        System.out.println("HTTP_METHOD : " + request.getMethod());  
        System.out.println("IP : " + request.getRemoteAddr());  
        System.out.println("CLASS_METHOD : " +
         joinPoint.getSignature().getDeclaringTypeName() + 
        "." + joinPoint.getSignature().getName());  
        System.out.println("ARGS : " + Arrays.toString(joinPoint.getArgs()));  
  
    }  
  
    @AfterReturning(returning = "ret", pointcut = "webLog()")  
    public void doAfterReturning(Object ret) throws Throwable {  
        // 处理完请求,返回内容  
        System.out.println("方法的返回值 : " + ret);  
    }  
  
    //后置异常通知  
    @AfterThrowing("webLog()")  
    public void throwss(JoinPoint jp){  
        System.out.println("方法异常时执行.....");  
    }  
  
    //后置最终通知,final增强,不管是抛出异常或者正常退出都会执行  
    @After("webLog()")  
    public void after(JoinPoint jp){  
        System.out.println("方法最后执行.....");  
    }  
  
    //环绕通知,环绕增强,相当于MethodInterceptor  
    @Around("webLog()")  
    public Object arround(ProceedingJoinPoint pjp) {  
        System.out.println("方法环绕start.....");  
        try {  
            Object o =  pjp.proceed();  
            System.out.println("方法环绕proceed,结果是 :" + o);  
            return o;  
        } catch (Throwable e) {  
            e.printStackTrace();  
            return null;  
        }  
    }  
}  

JoinPoint 对象

JoinPoint 对象封装了SpringAop中切面方法的信息,在切面方法中添加 JoinPoint 参数,就可以获取到封装了该方法信息的JoinPoint对象。
常用API:

方法名功能
Signature getSignature()获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息
Object[] getArgs()获取传入目标方法的参数对象
Object getTarget()获取被代理的对象
Object getThis()获取代理对象
System.out.println("目标方法名为:" + joinPoint.getSignature().getName());
System.out.println("目标方法所属类的简单类名:" +        joinPoint.getSignature().getDeclaringType().getSimpleName());
System.out.println("目标方法所属类的类名:" + joinPoint.getSignature().getDeclaringTypeName());
System.out.println("目标方法声明类型:" + Modifier.toString(joinPoint.getSignature().getModifiers()));
//获取传入目标方法的参数
Object[] args = joinPoint.getArgs();
for (int i = 0; i < args.length; i++) {
    System.out.println("第" + (i+1) + "个参数为:" + args[i]);
}
System.out.println("被代理的对象:" + joinPoint.getTarget());
System.out.println("代理对象自己:" + joinPoint.getThis());

ProceedingJoinPoint 获取方法上的注解

Class<?> classTarget = joinPoint.getTarget().getClass();
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method objMethod = classTarget.getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
GpDbSetAppAnnotation annotation = objMethod.getAnnotation(GpDbSetAppAnnotation.class);

使用AOP打印Http请求入参、返回值、接口耗时

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@Aspect
@Component
public class WebAspect {

    @Pointcut("execution(public * com.example.controller.*.*(..))")
    public void pointCut() {
    }

    @AfterReturning(value = "pointCut()", returning = "returnVal")
    public void afterReturning(JoinPoint joinPoint, Object returnVal) {
        log.info("{} after return, returnVal: {}", joinPoint.getSignature().getName(), returnVal);
    }

    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) {
        long startTime = System.currentTimeMillis();
        String methodName = joinPoint.getSignature().getName();
        String className = joinPoint.getTarget().getClass().getName();
        Object[] args = joinPoint.getArgs();
        String[] parameterNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        Map<String, Object> paramMap = new HashMap<>();
        for (int i = 0; i < parameterNames.length; i++) {
            paramMap.put(parameterNames[i], args[i]);
        }
        log.info("path:{} {}.{} start, param:{}", request.getServletPath(), className, methodName, paramMap.toString());
        Object result = null;
        try {
            result = joinPoint.proceed();
        } catch (Throwable e) {
            log.error("around error", e);
        }
        long endTime = System.currentTimeMillis();
        log.info("{}.{} end execute time:{} ms", className, methodName, endTime - startTime);
        return result;
    }
}
import cn.hutool.json.JSONUtil;
import com.evcas.charge.pile.platform.common.annotation.LogNoRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
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 org.springframework.util.StopWatch;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

@Slf4j
@Aspect
@Component
public class WebAspect {

    @Pointcut("execution(* com.evcas.charge.pile.platform.app.controller.*.*(..))")
    public void pointCut() {
    }

    @Before("pointCut()")
    public void doBefore(JoinPoint joinPoint) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        assert attributes != null;
        HttpServletRequest request = attributes.getRequest();
        try {
            MethodSignature methodSignature = (MethodSignature) (joinPoint.getSignature());
            String methodName = methodSignature.getName();
            //Annotation[] annotations = methodSignature.getMethod().getAnnotations();
            LogNoRequest logNoRequest = methodSignature.getMethod().getAnnotation(LogNoRequest.class);
            if (Objects.isNull(logNoRequest)) {
                Map<String, Object> params = this.getRequestParams(joinPoint);
                log.info("request start》》》|methodName:{}|IP:{}|URL:{}|param:{}", methodName, request.getRemoteAddr(),
                        request.getRequestURL().toString(), JSONUtil.toJsonStr(params));
            }
        } catch (Exception e) {
            log.info("全局请求日志打印异常:", e);
        }
    }

    @Around("pointCut()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        Object result = proceedingJoinPoint.proceed();
        stopWatch.stop();
        try {
            MethodSignature methodSignature = (MethodSignature) (proceedingJoinPoint.getSignature());
            String methodName = methodSignature.getName();
            //Annotation[] annotations = methodSignature.getMethod().getAnnotations();
            LogNoRequest logNoRequest = methodSignature.getMethod().getAnnotation(LogNoRequest.class);
            if (Objects.isNull(logNoRequest)) {
                log.info("response end》》》|methodName:{}|result:{}|elapsedTime:{}ms", methodName, result, stopWatch.getTotalTimeMillis());
            }
        } catch (Exception e) {
            log.info("全局响应日志打印异常:", e);
        }
        return result;
    }

    /**
     * 获取入参
     */
    private Map<String, Object> getRequestParams(JoinPoint joinPoint) {
        Map<String, Object> requestParams = new HashMap<>();
        //参数名
        String[] paramNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
        //参数值
        Object[] paramValues = joinPoint.getArgs();
        for (int i = 0; i < paramNames.length; i++) {
            Object value = paramValues[i];
            //如果是文件对象
            if (value instanceof MultipartFile) {
                MultipartFile file = (MultipartFile) value;
                value = file.getOriginalFilename();
            }
            requestParams.put(paramNames[i], value);
        }
        return requestParams;
    }

    private Map<String, Object> getHeader(HttpServletRequest request) {
        Map<String, Object> headerParams = new HashMap<>(1);
        // 获取所有请求头名称并遍历
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            String headerValue = request.getHeader(headerName);
            headerParams.put(headerName, headerValue);
        }
        return headerParams;
    }
}


import java.lang.annotation.*;

/**
 * 不需要打印请求日志
 */
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogNoRequest {

}

多个切面的执行顺序

自己实现一个AOP

业务类。

public interface HelloService {
	public void sayHello(String name);
}
//////////////////////////////////////////
import com.demo.service.HelloService;
import org.springframework.stereotype.Service;
@Service
public class HelloServiceImpl implements HelloService {
	@Override
	public void sayHello(String name) {
		if (name == null || name.trim() == "") {
			throw new RuntimeException ("parameter is null!!");
		}
		System.out.println("hello " + name);
	}
}

定义一个以反射的形式去调用原有的方法工具。

import lombok.Getter;
import lombok.Setter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@Getter
@Setter
public class Invocation {
	private Object[] params;
	private Method method;
	private Object target;
	public Invocation(Object target, Method method, Object[] params) {
		this.target = target;
		this.method = method;
		this.params = params;
	}
	public Object proceed() throws InvocationTargetException, IllegalAccessException {
		return method.invoke(target, params);
	}
}

定义一个自己的拦截器。

import com.demo.invoke.Invocation;
import java.lang.reflect.InvocationTargetException;
public interface Interceptor {
	//事前方法
	public boolean before();
	//事后方法
	public void after();
	/**
	 * 取代原有事件方法
	 * @param invocation -- 回调参数,可以通过它的proceed方法,回调原有事件
	 * @return 原有事件返回对象
	 * @throws InvocationTargetException
	 * @throws IllegalAccessException
	 */
	public Object around(Invocation invocation) throws InvocationTargetException, IllegalAccessException;	
	//是否返回方法。事件没有发生异常执行
	public void afterReturning();	
	//事后异常方法,当事件发生异常后执行
	public void afterThrowing();
	//是否使用around方法取代原有方法
	boolean useAround();	
}
///////////////////////////////////////////////
import com.demo.invoke.Invocation;
import java.lang.reflect.InvocationTargetException;
public class MyInterceptor implements Interceptor {
	@Override
	public boolean before() {
		System.out.println("before ......");
		return true;
	}
	@Override
	public boolean useAround() {
		return true;
	}
	@Override
	public void after() {
		System.out.println("after ......");
	}
	@Override
	public Object around(Invocation invocation) throws InvocationTargetException, IllegalAccessException {
		System.out.println("around before ......");
		Object obj = invocation.proceed();
		System.out.println("around after ......");
		return obj;
	}
	@Override
	public void afterReturning() {
		System.out.println("afterReturning......");

	}
	@Override
	public void afterThrowing() {
		System.out.println("afterThrowing 。。。。。。");
	}
}

编写生成代理业务类。

import com.demo.intercept.Interceptor;
import com.demo.invoke.Invocation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyBean implements InvocationHandler {
	private Object target = null;
	private Interceptor interceptor = null;
	/**
	 * 绑定代理对象
	 * @param target 被代理对象
	 * @param interceptor 拦截器
	 * @return 代理对象
	 */
	public static Object getProxyBean(Object target, Interceptor interceptor) {
		ProxyBean proxyBean = new ProxyBean();
		// 保存被代理对象
		proxyBean.target = target;
		// 保存拦截器
		proxyBean.interceptor = interceptor;
		// 生成代理对象
		Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
				proxyBean);
		// 返回代理对象
		return proxy;
	}
	/**
	 * 处理代理对象方法逻辑
	 * @param proxy 代理对象
	 * @param method 当前方法
	 * @param args  运行参数
	 * @return 方法调用结果
	 * @throws Throwable 异常
	 */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)  {
		//异常标识
		boolean exceptionFlag = false;
		Invocation invocation = new Invocation(target, method, args);
		Object retObj = null; 
		try {
			if (this.interceptor.before()) {
				retObj = this.interceptor.around(invocation);
			} else {
				retObj = method.invoke(target, args);
			}
		} catch (Exception ex) {
			//产生异常
			exceptionFlag = true;
		}
		this.interceptor.after();
		if (exceptionFlag) {
			this.interceptor.afterThrowing();
		} else {
			this.interceptor.afterReturning();
			return retObj;
		}
		return null;
	}
}

测试类。

import com.demo.intercept.MyInterceptor;
import com.demo.proxy.ProxyBean;
import com.demo.service.HelloService;
import com.demo.service.impl.HelloServiceImpl;

public class AopMain {
	public static void main(String[] args) {
		HelloService helloService = new HelloServiceImpl();
		HelloService proxy = (HelloService) ProxyBean.getProxyBean(helloService, new MyInterceptor());
		proxy.sayHello("Tom");
	}
}

测试结果。

before ......
around before ......
hello Tom
around after ......
after ......
afterReturning......

使用AOP解决接口防刷

@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Prevent {
    int seconds() default 60;
    int maxCount() default 1;
    String message() default "";
}
import cn.hutool.json.JSONUtil;
import com.boot.common.BusinessException;
import org.aspectj.lang.JoinPoint;
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.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.concurrent.TimeUnit;

/**
 * 防刷切面实现类
 */
@Aspect
@Component
public class PreventAop {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Pointcut("@annotation(com.boot.aop.Prevent)")
    public void pointcut() {
    }

    @Before("pointcut()")
    public void joinPoint(JoinPoint joinPoint) throws Exception {
        String requestStr = JSONUtil.toJsonStr(joinPoint.getArgs()[0]);
        if (StringUtils.isEmpty(requestStr) || requestStr.equalsIgnoreCase("{}")) {
            throw new BusinessException("[防刷]入参不允许为空");
        }

        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = joinPoint.getTarget().getClass().getMethod(methodSignature.getName(),
                methodSignature.getParameterTypes());

        Prevent preventAnnotation = method.getAnnotation(Prevent.class);
        String methodFullName = method.getDeclaringClass().getName() + method.getName();

        defaultHandle(requestStr, preventAnnotation, methodFullName);
    }

    private void defaultHandle(String requestStr, Prevent prevent, String methodFullName) throws Exception {
        String base64Str = toBase64String(requestStr);
        int expire = prevent.seconds();
        int maxCount = prevent.maxCount();
        String resp = redisTemplate.opsForValue().get(methodFullName + base64Str);
        int count = StringUtils.isEmpty(resp) ? 0 : Integer.parseInt(resp);
        if (StringUtils.isEmpty(resp)) {
            redisTemplate.opsForValue().set(methodFullName + base64Str, "1", expire, TimeUnit.SECONDS);
        } else if (count < maxCount) {
            redisTemplate.opsForValue().increment(methodFullName + base64Str);
        } else {
            String message = !StringUtils.isEmpty(prevent.message()) ? prevent.message() :
                    expire + "超出访问次数";
            throw new BusinessException(message);
        }
    }

    /**
     * 对象转换为base64字符串
     *
     * @param obj 对象值
     * @return base64字符串
     */
    private String toBase64String(String obj) throws Exception {
        if (StringUtils.isEmpty(obj)) {
            return null;
        }
        Base64.Encoder encoder = Base64.getEncoder();
        byte[] bytes = obj.getBytes(StandardCharsets.UTF_8);
        return encoder.encodeToString(bytes);
    }

}
springboot-监听器,过滤器,拦截器,aop,自定义注解 springboot基础-监听器,过滤器,拦截器,aop,自定义注解 环境 idea2018,jdk1.8, springboot版本:1.5.9.RELEASE 代码下载: https://github.com/2010yhh/springBoot-demos.git 测试:启动项目后,访问: http://localhost:8080/springboot-demo2/test1 ... 阅读详情

相关推荐

spring boot如何使用spring AOP实现拦截器

本篇文章主要介绍了spring boot如何使用spring AOP实现拦截器,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

Java Spring Boot 的过滤器(Filter),拦截器(Interceptor),切面(Aspect)

在 Java Spring Boot 中,过滤器(Filter),拦截器(Interceptor)和切面(Aspect)是用于处理请求和响应的关键组件。它们都提供了在请求处理过程中进行自定义处理的能力,但在使用方式和功能上有一些区别。

weixin_47847063的博客 939

abaqus运行报错 WindowsError:[Error 2]

abaqus提交job总是提示“WindowsError [Error 2”,报这个错误,可能是由于安装其他软件改变了abaqus软件的路径导致,总结了看到的教程,按照下面的方法可以解决这个问题。 步骤一:找到你的abaqus安装路径(我是按照一个安装视频安装的,安装路径为SIMULI1),然后找到SIMULIA文件夹,点击进入后找到commands文件夹,将这个路径复制下来。 步骤二:我的电脑右键属性,在左侧中找到高级系统设置并...

az101za的博客 1万+

SpringBoot使用过滤器、拦截器、切面(AOP),及其之间的区别和执行顺序

(拦截器的preHandle方法在进入controller前执行,而拦截器的postHandle方法在执行完controller业务流程后,在视图解析器解析ModelAndView之前执行,可以操控Controller的ModelAndView内容。(关于这句话的解读是:我们知道拦截器是SprinMVC自带的,而SpringMVC存在Controller层的,而controller层可以访问到service层,service层是不能访问service层的,而过滤器是客户端和服务端之间请求与响应的过滤)

qq_43842093的博客 811

逆向Shopee签名机制:从参数分析到稳定采集方案

本文深入解析了Shopee签名机制的逆向过程,从参数分析到稳定采集方案的实现。通过抓包定位关键参数`sap-ri`和`x-sap-sec`,还原了签名生成算法,并提供了Python实现代码。文章还分享了反反爬策略、性能优化技巧及长期维护建议,帮助开发者构建稳定的Shopee数据采集方案。

weixin_27722377的博客 672

STM32F103用软件I2C读取IP2366电量数据,实测避坑指南(附完整源码)

本文详细介绍了如何使用STM32F103的软件I2C驱动读取IP2366电池管理芯片的电量数据,包括GPIO初始化、I2C时序实现、关键延时优化及常见问题解决方案。通过实测避坑指南和完整源码,帮助开发者快速实现稳定的电量数据读取,适用于嵌入式电池管理系统开发。

weixin_33725722的博客 513

SpringBoot 使用过滤器、拦截器、切面(AOP),及其之间的区别和执行顺序

Servlet(Server Applet),全称是Java Servlet,是提供基于协议请求/响应服务的Java类。在JavaEE中是Servlet规范,即是指Java语言实现的一个接口,广义的Servlet是指任何实现了这个Servlet接口的Java类,一般人们理解是后者。是什么。

qq_43842093的博客 3022

【智能制造】- APS系列|05 APS 排产的五大底层算法逻辑

很多人误以为APS只是给生产工单排先后顺序,实则整套系统依托成熟算法体系,完成多维度约束下的最优生产决策在制造业数字化排产普及的当下,依旧有不少制造管理者对APS存在认知误区,将其等同于简易工单排序工具。事实上,专业级高级计划与排程系统,早已脱离单纯按时间排序的基础功能,而是依托一整套成熟完善的底层算法逻辑,整合现场各类生产限制条件,在海量变量中自动推演、筛选、输出最贴合企业经营目标的生产方案。

qq_40821260的博客 433

Spring Boot使用AOP实现拦截器

新建GlobalException异常类,用于捕捉全局异常。super();@Override@Override自定义AOP,对接口入参进行非空校验和验签;并对接口返回参数进行签名。@Aspect@Component/*** 声明一个切入点,范围为controller包下所有的类* 注:作为切入点签名的方法必须返回void类型*//*** 前置通知:在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常)

null 1万+

【项目管理】第8章 项目整合管理 --知识点整理

制定项目章程: 第一步,旨在明确项目的目标、范围、关键里程碑等关键信息,为项目的后续工作奠定基础。制订项目管理计划: 在项目章程的基础上,进一步详细规划项目的执行过程,包括时间、成本、质量、风险等方面的管理计划。指导与管理项目执行: 按照项目管理计划执行项目,同时监控项目的进展,确保项目按照计划进行。监控项目工作: 对项目的执行情况进行持续监控,及时发现并解决问题,确保项目目标的实现。

weixin_42081167的博客 1594

Mac安装Weblogic教程

1.Weblogic的下载 这次安装的是最新版本12 官网下载地址: http://www.oracle.com/technetwork/middleware/weblogic/downloads/index.html 下载需要登录账号,如没有账号要注册一个 下载后的文件如下图,其中fmw_12.2.1.3.0_wls_quick.jar文件就是安装包了,注意现在不要对它进行解压 2.安装前的确...

weixin_40102675的博客 5063

springAOP,过滤器、拦截器、切面的使用

Slf4j@Override// 包装Request 和 Response// 打印请求参数// 执行过滤器链//打印返回参数// 打印请求参数// 打印request种关键信息= null) {");= null) {// 判断是否文件上传,图片上传,视频等if (!// 打印返回参数// 判断是否请求静态资源或者文件等}else {if(!

小野爸爸的博客 1728

37、基于铁电场效应晶体管(FeFET)的片上深度神经网络(DNN)直接反馈对齐训练

本文提出了一种基于铁电场效应晶体管(FeFET)的深度神经网络(DNN)训练加速器架构,结合直接反馈对齐(DFA)算法和FeFET的独特物理特性,以解决当前DNN训练中反向传播(BP)算法带来的高计算开销、复杂数据依赖和高能耗问题。通过设计基于FeFET的MVM引擎、低功耗ADC和随机数生成器(RNG),该架构在速度和功率效率方面相比现有基于ReRAM的加速器分别提升了1.3倍和2.5倍。研究为未来高效能AI训练硬件的发展提供了新的思路和实现路径。

wine的专栏 162

SpringBoot自定义servlet、注册自定义的servlet、过滤器、监听器、拦截器、切面

在springboot应用中添加servlet springboot的主servlet是DispacherServlet,它默认的url-pattern是“/”,如果我们还需要其他的servlet就需要开发人员自己进行定义和注册 代码注册 通过ServletRegistrationBean获得控制 注解注册 (1) 在启动类上标注@ServletComponentScan (2) 在自定义的servlet类上标注@WebServlet即可 (3) 自定义Servlet 继承HttpServlet,再根据

qq_40813329的博客 1330

Matplotlib之bar3d画3D柱状图

3D柱状图通常用于展示数据集中不同分类项的数据大小或数量。与普通的柱状图相比,3D柱状图更适合展示数据的三个维度 3D 柱状图 按行/列涂颜色 柱加阴影、描黑边 自定义座标轴名、刻度标签、范围

我走的每一步都算数 1121

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

spring boot 过滤器&拦截器与aop

spring boot 拦截器&过滤器 与aop

喵酱 4314

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

springboot出入参参数日志打印方案以及实现

背景 开发项目的时候,对出入参可以通过idea 的debug模式实现。但是项目一旦发布到线上如果发现有数据存在问题,那么究竟是哪一个环节出现的问题呢。有些情况就会不好分析。或者在系统间互相调用的时候,自己作为被调用方,如何证明调用方的参数正确与否呢。当然是通过日志实现,可以通过如果log日志输出实现。但是更建议写成一个通用的,要么放到基础组件中,也可以放到各自微服务中选择是否启用。 分析 思考一番个人觉得可以采用三种方式实现 拦截器 过滤器 aop切面(个人更推荐使用) 拦截器和过滤器实现方式差不多,因此

ruo_yuan 7765

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

SpringBoot 之多个过滤器(Filter) ,监听器(Listener),切面(AOP),拦截器(Interceptor)的指定排序问题总结篇

相关文章: SpringBoot 之AOP切面的使用 SpringBoot之Listener注册到Spring容器中的多种方法 SpringBoot之Interceptor拦截器注入使用 SpringBoot之Filter过滤器的实现及排序问题 SpringBoot 之多个过滤器(Filter) ,监听器(Listener),切面(AOP),拦截器(Interceptor)的指定排序问题总结篇 过滤器(Filter) ,监听器(Listener) 这两个实现排序是一样的。 无法通过Ordered接口,@.

Jarvis.y的博客 711

SpringBoot如何设计业务操作日志功能?

很久以前都想写这篇文章,一直没有空,但直到现在我对当时的情景还有印象,之所以有印象是因为需求很简单,业务操作日志的记录与查询的功能,但是具体实现真的很烂,具体的烂法会在反面示例里细说,领导以及客户层面很认可,一系列迷之操作,让我印象深刻。业务操作日志记录中包含了用户操作的功能名称、功能描述、操作人、操作时间和操作的参数报文,参数报文之所以选择存储在文件中,是因为正常情况下,是不需要知道具体的参数报文,只有在回滚操作的时候才会用到,可以根据上一次的参数报文逆向操作。

hui_ss的博客 328

springboot的切面应用(注解Aspect )

spring boot 拦截的方式 过滤器filter: 可以获取http、http请求和响应,但无法获取与spring框架相关的信息,如哪个control处理,哪个方法处理,有哪些参数,这些都是无法获取的。主要用于内容上的过滤,敏感字替换成*等,也可用于非登入状态的非法请求过滤。 拦截器interceptor: 除了获取http、http请求和响应对象,还可以获取请求的类名、方法名,但拦截器无法获取请求参数的值,从DispatcherServlet类源码分析。主要用于对公共的一些拦截获取,例如请求

weixin_44632065的博客 6239

springboot切片测试_springboot 过滤器、拦截器、切片(aspect)

本文的环境基础:springboot web模块,aop模块,rest风格本文的测试功能:对UserController进行拦截,输出每个方法执行的耗时spring boot过滤器:过滤器只能取到request,response等信息,不能获取正在过滤的类的信息和方法的信息,不能获取请求参数过滤器的实现有两种方式:通过@Component注解,直接将filter交给spring管理,但是这种方式时...

weixin_31008279的博客 406

Spring Boot 实现用户行为记录的四种方式

目录 一、需求背景 二、需要的pom依赖 三、AOP切面编程实现 四、Interceptor拦截器实现 五、Filter过滤器实现 六、ArgumentResolver 七、拓展部分 八、参考文献 一、需求背景 根据产品经理要求,需要对用户的行为进行记录。重点记录用户的姓名、IP、操作行为、请求参数和返回参数。先采用暴力解决问题,每个行为增加记录行为,当然这个是最不恰当的方案,工作量大不说,还不具有扩展性。因此参考他人博客,使用4种方案来实现该需求。 二、需要的pom依赖

无痕之剑的书橱 4877

Spring boot实战:拦截器interceptor与过滤器Filter

用途 AOP——面向切面编程。在面向对象编程的过程中,我们很容易通过继承、多态来解决纵向扩展。但是对于横向的功能,比如统一记录日志或者统一的加密规则校验等功能,面向对象的是无法解决的。所以就需要使用AOP——面向切面编程的方式进行补充。过滤器和拦截器都属于面向切面编程的具体实现。 两者区别   1、Filter是依赖于Servlet容器,属于Servlet规范的一部分,而拦截器则是独立存在...

nights07的专栏 417

使用Redis实现记录访问次数(三种方案)

准备一个Controller,用来测试。@Tag(name = "测试",description = "测试springMVC拦截器实现记录访问次数")case 1:return "1";

BlackPudding_的博客 2051

SpringBoot 项目鉴权的 4 种方式

点击下方“Java编程鸭”关注并标星更多精彩 第一时间直达文章介绍了spring-boot中实现通用auth的四种方式,包括 传统AOP、拦截器、参数解析器和过滤器,并提供了对应的实例代码,最后简单总结了下他们的执行顺序。前言最近一直被无尽的业务需求淹没,没时间喘息,终于接到一个能让我突破代码舒适区的活儿,解决它的过程非常曲折,一度让我怀疑人生,不过收获也很大,代码方面不...

民工哥的博客 902

Spring Boot 项目鉴权的 4 种方式

鉴权的4种实现方式

weixin_43805579的博客 2214
上一篇: Spring Cloud Gateway+Oauth2 实现统一认证和鉴权
下一篇: Redis 之缓存和DB一致性解决
yyangqqian
博客等级 码龄15年 406粉丝 406原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值