java注解初步学习和使用--02(Spring AOP)

引言:
AOP( 面向切面编程)是OOP(面向对象编程)的延续,是软件开发中的一个热点。它所面对的是处理过程中的某个步骤或阶段,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP包括切面(Aspect)、切入点(pointCut)、通知(advice) 、连接点(joinpoint),其中会用到的注解有:
(注:本文中采用注解的方式进行aop操作,没有配置文件。)

1.切面(aspect)

切面类 @Aspect: 定义切面类,在类前面加上@Aspect、@Component注解,标明该类是作为切面。

@Aspect
@Component
public class LimitAspect{
        ...
}

2.切入点(pointCut)

@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() 匹配指定注解为切入点的方法,表示标注了某个注解的所有方法。)

3.通知(advice)

通知(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){
	...
}

4.连接点(joinpoint)

JoinPoint对象封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,就可以获取到封装了该方法信息的JoinPoint对象。
接口JoinPoint中封装了代理对象的相关信息:

//获取代理对象
Object getThis();
//获取被代理的对象
Object getTarget();
//获取传入目标方法的参数对象
Object[] getArgs();
//获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息
Signature getSignature();

5.代码

注解层:

@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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值