irpas技术客

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

未知 7656

文章目录 AOP概述AOP底层实现机制过滤器、拦截器、切面区别过滤器 Filter使用过滤器统一请求耗时 拦截器 Interceptor使用拦截器判断是否登录 AspectJ 简介@AspectJ 注解开发 AOPAOP 注解说明案例代码 JoinPoint 对象ProceedingJoinPoint 获取方法上的注解使用AOP打印Http请求入参、返回值、接口耗时多个切面的执行顺序自己实现一个AOP使用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 { } } 使用拦截器判断是否登录 @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; } } 多个切面的执行顺序 自己实现一个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); } }


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #springboot #切面 #拦截器 #Programming