Spring Aop 和 自定义注解的使用的详解

什么是AOP

        AOP,Spring框架的两大核心之一,又称面向切面编程,通过代理模式,对原有的类进行增强。在Spring框架中,AOP有两种动态代理方式,其一是基于JDK的动态代理,需要代理的类实现某一个接口;其二是基于CGLIB的方式,该方式不需要类实现接口就能进行代理。AOP的应用场景,常见的就是事务的处理和日志的记录,还有权限的认证。(笔者使用AOP的场景:保存所有用户对数据进行的增删改内容等,比如,张三修改了一个表格数据的值,就需要记录谁,什么时候,修改or添加or删除,哪项数据,数据的旧值和新值是什么。因为涉及到的接口很多,也很分散,所以笔者使用aop和自定义注解,让所有涉及到增删改的接口添加自定义注解,以达到在保存记录之后进行操作日志记录。)

什么是注解

Annotation,自定义注解,基于Java六大元注解的注解(target、document、retention、inherited、repeatable和类型注解)。一般创建自定义注解,至少会在该注解上添加@target(注解的位置,如添加到方法上或者是类上)和@retention(注解使用的时机,编译期间或者运行时等)俩个注解。
 

当AOP和注解结合的时候就是比较方便,下面利用AOP+自定义注解来详细讲解两个的作用

对于AOP

Aspect(切面)

aspect 由 pointcount 和 advice 组成, 它既包含了横切逻辑的定义, 也包括了连接点的定义. Spring AOP就是负责实施切面的框架, 它将切面所定义的横切逻辑织入到切面所指定的连接点中.
AOP的工作重心在于如何将增强织入目标对象的连接点上, 这里包含两个工作:

  1. 如何通过 pointcut 和 advice 定位到特定的 joinpoint 上
  2. 如何在 advice 中编写切面代码.

可以简单地认为, 使用 @Aspect 注解的类就是切面

advice(增强)

由 aspect 添加到特定的 join point(即满足 point cut 规则的 join point) 的一段代码.
许多 AOP框架, 包括 Spring AOP, 会将 advice 模拟为一个拦截器(interceptor), 并且在 join point 上维护多个 advice, 进行层层拦截.
例如 HTTP 鉴权的实现, 我们可以为每个使用 RequestMapping 标注的方法织入 advice, 当 HTTP 请求到来时, 首先进入到 advice 代码中, 在这里我们可以分析这个 HTTP 请求是否有相应的权限, 如果有, 则执行 Controller, 如果没有, 则抛出异常. 这里的 advice 就扮演着鉴权拦截器的角色了.

切点(point cut)

匹配 join point 的谓词(a predicate that matches join points).
Advice 是和特定的 point cut 关联的, 并且在 point cut 相匹配的 join point 中执行.
在 Spring 中, 所有的方法都可以认为是 joinpoint, 但是我们并不希望在所有的方法上都添加 Advice, 而 pointcut 的作用就是提供一组规则(使用 AspectJ pointcut expression language 来描述) 来匹配joinpoint, 给满足规则的 joinpoint 添加 Advice.

关于join point 和 point cut 的区别

在 Spring AOP 中, 所有的方法执行都是 join point. 而 point cut 是一个描述信息, 它修饰的是 join point, 通过 point cut, 我们就可以确定哪些 join point 可以被织入 Advice. 因此 join point 和 point cut 本质上就是两个不同纬度上的东西.
advice 是在 join point 上执行的, 而 point cut 规定了哪些 join point 可以执行哪些 advice

introduction

为一个类型添加额外的方法或字段. Spring AOP 允许我们为 目标对象 引入新的接口(和对应的实现). 例如我们可以使用 introduction 来为一个 bean 实现 IsModified 接口, 并以此来简化 caching 的实现.

目标对象(Target)

织入 advice 的目标对象. 目标对象也被称为 advised object.
因为 Spring AOP 使用运行时代理的方式来实现 aspect, 因此 adviced object 总是一个代理对象(proxied object)
注意, adviced object 指的不是原来的类, 而是织入 advice 后所产生的代理类.

AOP proxy

一个类被 AOP 织入 advice, 就会产生一个结果类, 它是融合了原类和增强逻辑的代理类.
在 Spring AOP 中, 一个 AOP 代理是一个 JDK 动态代理对象或 CGLIB 代理对象.

Advice 的类型

before advice, 在 join point 前被执行的 advice. 虽然 before advice 是在 join point 前被执行, 但是它并不能够阻止 join point 的执行, 除非发生了异常(即我们在 before advice 代码中, 不能人为地决定是否继续执行 join point 中的代码)

after return advice, 在一个 join point 正常返回后执行的 advice

after throwing advice, 当一个 join point 抛出异常后执行的 advice

after(final) advice, 无论一个 join point 是正常退出还是发生了异常, 都会被执行的 advice.

around advice, 在 join point 前和 joint point 退出后都执行的 advice. 这个是最常用的 advice.

introduction,introduction可以为原有的对象增加新的属性和方法。

对于代码的使用

在自定义注解上面添加@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME)

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SignerCheck {
}

两个注解

@Target表明是要在哪一种方法或者类上面添加注解,具体的类型有(可以选择多个,逗号隔开)

public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,

    /** Field declaration (includes enum constants) */
    FIELD,

    /** Method declaration */
    METHOD,

    /** Formal parameter declaration */
    PARAMETER,

    /** Constructor declaration */
    CONSTRUCTOR,

    /** Local variable declaration */
    LOCAL_VARIABLE,

    /** Annotation type declaration */
    ANNOTATION_TYPE,

    /** Package declaration */
    PACKAGE,

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE
}

@Retention表明当前声明的自定义注解是在什么时期进行生效

如果自定义注解中有需要的参数,就像定义变量类似进行定义就行,可以给默认值,后续在注解中添加相应的遍历

注解定义完毕之后,定义当前注解AOP切面类

@Aspect
@Component
public class SignerCheckAspect {

    @Pointcut("@within(com.zs.authorization.aksk.annotations.SignerCheck)")
    private void signerCheckannotations() {
    }

    @Around("signerCheckannotations()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {

        Signature signature = pjp.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method targetMethod = methodSignature.getMethod();
        Class<?> clazz = targetMethod.getDeclaringClass();
        if (clazz.isAnnotationPresent(SignerCheck.class)) {
            if (!targetMethod.isAnnotationPresent(SignerCheckIgnore.class)) {
                // 进入签名检查
                signerCheck();
            }
        }
        return pjp.proceed();
    }

    /**
     * 签名检查
     */
    private void signerCheck() {
        // 业务逻辑中需要进行增强的操作
       
    }
}

其中有一些注解

@Aspect就是定义当前类为切面类
@Pointcut注解 规定了增强的操作在哪些作用域上生效(相当于拦截器)

其中的匹配规则有很多

匹配方法签名

// 匹配指定包中的所有的方法

execution(* com.xys.service.*(..))

// 匹配当前包中的指定类的所有方法

execution(* UserService.*(..))

// 匹配指定包中的所有 public 方法

execution(public * com.xys.service.*(..))

// 匹配指定包中的所有 public 方法, 并且返回值是 int 类型的方法

execution(public int com.xys.service.*(..))

// 匹配指定包中的所有 public 方法, 并且第一个参数是 String, 返回值是 int 类型的方法

execution(public int com.xys.service.*(String name, ..))

匹配类型签名

// 匹配指定包中的所有的方法, 但不包括子包

within(com.xys.service.*)

// 匹配指定包中的所有的方法, 包括子包

within(com.xys.service..*)

// 匹配当前包中的指定类中的方法

within(UserService)

// 匹配一个接口的所有实现类中的实现的方法

within(UserDao+)

匹配 Bean 名字

// 匹配以指定名字结尾的 Bean 中的所有方法

bean(*Service)

切点表达式组合

// 匹配以 Service 或 ServiceImpl 结尾的 bean

bean(*Service || *ServiceImpl)

// 匹配名字以 Service 开头, 并且在包 com.xys.service 中的 bean

bean(*Service) && within(com.xys.service.*)

Advice

advice 是和一个 pointcut 表达式关联在一起的, 并且会在匹配的 join point 的方法执行的前/后/周围 运行. pointcut 表达式可以是简单的一个 pointcut 名字的引用, 或者是完整的 pointcut 表达式.

@Around注解与之对应的还有@Before注解,@After都可以在其中引用对应的Pointcut

around advice 比较特别, 它可以在一个方法的之前之前和之后添加不同的操作, 并且甚至可以决定何时, 如何, 是否调用匹配到的方法

    @Around("signerCheckannotations()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {

        Signature signature = pjp.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method targetMethod = methodSignature.getMethod();
        Class<?> clazz = targetMethod.getDeclaringClass();
        if (clazz.isAnnotationPresent(SignerCheck.class)) {
            if (!targetMethod.isAnnotationPresent(SignerCheckIgnore.class)) {
                // 进入签名检查
                signerCheck();
            }
        }
        return pjp.proceed();
    }

以上就是Spring APO结合自定义注解的使用。

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值