引言: AOP( 面向切面编程)是OOP(面向对象编程)的延续,是软件开发中的一个热点。它所面对的是处理过程中的某个步骤或阶段,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。 AOP包括切面(Aspect)、切入点(pointCut)、通知(advice) 、连接点(joinpoint),其中会用到的注解有: (注:本文中采用注解的方式进行aop操作,没有配置文件。)
切面类 @Aspect: 定义切面类,在类前面加上@Aspect、@Component注解,标明该类是作为切面。
@Aspect @Component public class LimitAspect{ ... }@Pointcut:Pointcut是植入Advice的触发条件,定义一个切入点表达式,用来确定哪些类需要代理。在Spring 2.0中Pointcut的定义包括2部分,一是表达式(expression),二是方法签名 (signature)。方法签名必须是 public及void型,Pointcut中的方法不需要在方法体内编写实际代码。
//Pointcut表示式 @Pointcut("@annotation(xx.xx.xx.Limit)") //Point签名 public void pointcut() {}具体使用格式:
//表示匹配所有方法 @Pointcut("execution(* *(..))") //表示匹配com.savage.server.UserService中所有的公有方法 @Pointcut("execution(public * com.savage.service.UserService.*(..))") //表示匹配com.savage.server包及其子包下的所有方法 @Pointcut("execution(* com.savage.server..*.*(..))") //Limit注解的所有方法 @Pointcut("@annotation(xx.xx.xx.Limit)")(注:@annotation() 匹配指定注解为切入点的方法,表示标注了某个注解的所有方法。)
通知(advice)包括下面五种:
@Before 在切点方法之前执行 @After 在切点方法之后执行 @AfterReturning 切点方法返回后执行 @AfterThrowing 切点方法抛异常执行 @Around 属于环绕增强,能控制切点执行前,执行后这里拿@Around举例,可以改变执行目标方法的参数值,也可以改变执行目标方法之后的返回值; 当需要改变目标方法的返回值时,只能使用Around方法;但是只限线程安全!!! 总而言之:能只用其他四个解决就行,@Around功能强,但是条件也更多。 @Around注解源码:
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface Around { String value(); String argNames() default ""; }注解中可以直接带上方法名,或者写@annotation() 表达式
@Around("pointcut()") @Around(value = "pointcut()") @Around(value = "@annotation(around)") //around 与 下面参数名around对应 public void processAuthority(ProceedingJoinPoint point,MyAnnotation around){ ... }JoinPoint对象封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,就可以获取到封装了该方法信息的JoinPoint对象。 接口JoinPoint中封装了代理对象的相关信息:
//获取代理对象 Object getThis(); //获取被代理的对象 Object getTarget(); //获取传入目标方法的参数对象 Object[] getArgs(); //获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息 Signature getSignature();注解层:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Limit { // 资源名称,用于描述接口功能 String name() default ""; // 资源 key String key() default ""; // key prefix String prefix() default ""; // 时间的,单位秒 int period(); // 限制访问次数 int count(); // 限制类型 LimitType limitType() default LimitType.CUSTOMER; }切面层:
@Aspect @Component public class LimitAspect { private final RedisTemplate<Object,Object> redisTemplate; // 用于日志记录切面中信息 private static final Logger logger = LoggerFactory.getLogger(LimitAspect.class); public LimitAspect(RedisTemplate<Object,Object> redisTemplate) { this.redisTemplate = redisTemplate; } // 标注了注解Limit的所有方法 @Pointcut("@annotation(me.zhengjie.annotation.Limit)") public void pointcut() { } @Around("pointcut()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { // SpringMVC中RequestContextHolder获取请求信息的方法 HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest(); MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method signatureMethod = signature.getMethod(); Limit limit = signatureMethod.getAnnotation(Limit.class); LimitType limitType = limit.limitType(); String key = limit.key(); // key默认是"", if (StringUtils.isEmpty(key)) { if (limitType == LimitType.IP) { key = StringUtils.getIp(request); } else { key = signatureMethod.getName(); } } ImmutableList<Object> keys = ImmutableList.of(StringUtils.join(limit.prefix(), "_", key, "_", request.getRequestURI().replaceAll("/","_"))); String luaScript = buildLuaScript(); RedisScript<Number> redisScript = new DefaultRedisScript<>(luaScript, Number.class); Number count = redisTemplate.execute(redisScript, keys, limit.count(), limit.period()); if (null != count && count.intValue() <= limit.count()) { logger.info("第{}次访问key为 {},描述为 [{}] 的接口", count, keys, limit.name()); return joinPoint.proceed(); } else { throw new BadRequestException("访问次数受限制"); } } /** * 限流脚本 */ private String buildLuaScript() { return "local c" + "\nc = redis.call('get',KEYS[1])" + "\nif c and tonumber(c) > tonumber(ARGV[1]) then" + "\nreturn c;" + "\nend" + "\nc = redis.call('incr',KEYS[1])" + "\nif tonumber(c) == 1 then" + "\nredis.call('expire',KEYS[1],ARGV[2])" + "\nend" + "\nreturn c;"; } }注:ProceedingJoinPoint是JoinPoint的子接口,添加了proceed()和proceed(Object[] var1) 两个方法 . . . . . 参考: 1.@Aspect 注解使用详解 https://blog.csdn.net/fz13768884254/article/details/83538709 2.Spring详解篇之 AOP面向切面编程 https://blog.csdn.net/forezp/article/details/61472038?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase 3.Spring注解 AOP@Aspect的详细介绍 https://www.cnblogs.com/happy2333/p/12936182.html 4.Spring AOP 中@Pointcut的用法 https://www.cnblogs.com/liaojie970/p/7883687.html 5.@Around简单使用示例——SpringAOP增强处理 https://blog.csdn.net/qq_41981107/article/details/85260765