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

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);
    }

}
  • 6
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring拦截器过滤器AOP是Spring框架中常用的三种技术,用于实现对请求的处理和控制。它们的作用和使用方式有一些不同,我会分别对它们进行简单解释。 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主要用于将与业务逻辑无关的横切关注点进行解耦和管理。 希望以上解释能对你有所帮助!如果有其他问题,请随时提问。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值