aop

什么是 AOP
AOP(Aspect-Oriented Programming), 即 面向切面编程, 它与 OOP( Object-Oriented Programming, 面向对象编程) 相辅相成, 提供了与 OOP 不同的抽象软件结构的视角.
在 OOP 中, 我们以类(class)作为我们的基本单元, 而 AOP 中的基本单元是 Aspect(切面)

  • Aspect 切面
    Aspectadvicepointcut 组成,它既包含了横切逻辑的定义,又包含了连接点的定义,Spring AOP 就是负责实施横切的框架,它将切面所定义的横切逻辑织入到切面所定义的连接点中。
    aop 的工作重心在于如何将增强织入目标对象的连接点中,这里包含 2 个工作

    1. 如何通过 pointcut 定位 和 advice 定位到 join point
    2. advice 中实现增强逻辑
      可以简单的认为 带有@Aspect的类 就是一个切面。
  • join point 连接点
    在程序运行过程中的一些时间点,例如一个方法的执行,一个异常的处理
    在 spring aop 中 join point 总是方法的执行点。即只有方法连接点

  • point cut 切点
    在 spring 中,所有的方法都可以认为是 join point,但是我们不希望所有的 方法都被 advice (增强),所有就有了 pointcut 对想要被 advice的地方进行修饰标注,告诉 spring aop,被point cut修饰的 join point 需要 advice

  • advice 增强
    aspect 添加到特定的 join point (即满足 poin cut 规则)的一段代码

Join point 与 point cut 的区别
其实没啥可比性, point cut 只是对 需要 advicejoin point 做修饰的。
advice 是在 join point 上执行的,point cut 规定了 哪些 join point 可以执行哪些 advice

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

aop proxy
一个类 被 aop 织入 advice ,就会产生一个结果类,它是融入了原类和增强逻辑的代理类。在spring apop 中,一个 aop 代理是一个 jdk 动态代理对象或 cglib 代理对象
织入(Weaving)
aspect 和其他对象连接起来,并创建 adviced object 的过程
根据不同的实现技术, aop 织入有三种方式

  • 编译器织入,这里要求有特殊的 Java编译器
  • 类装载期织入,这需要有特殊的类装载器
  • 动态代理织入,在运行期为目标类添加增强(advice)生成子类的方式

advice 类型

  • before advicejoin 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 advicejoin point 前和 join point 退出后都执行的 advice ,这个是最常用的 advice

关于 aop proxy
spring aop 默认使用标准的 jdk 动态代理技术实现 aop 代理,通过它,我们可以为任意的接口实现代理,如果需要为一个类实现代理,那么可以使用 CGLIB 代理 。当一个业务逻辑对象没有实现接口时,那么spring aop 就默认使用 CGLIB作为 aop 代理了,即如果我们需要未一个方法织入 advice,但是这个方法不是一个接口锁提供的方法,则此时 spring aop 会使用 CGLIB 来实现动态代理,鉴于此,spring aop 建议基于接口编程,对接口进行 aop而不是 类

@AspectJ

@AspectJ 是一个使用 Java 注解来实现 aop 的编码风格
@AspectJ 风格的 aop 是 AspectJ Project 在 AspectJ 5 中引入的,并且 Spring 也支持@AspectJ 的 aop 风格

使能 @AspectJ 支持
@AspectJ 可以以 XML 的方式或以注解的方式来使能,并且不论以哪种方式使能@AspectJ,我们都能保证 aspectjweaver.jar 在 classpath 中。
使用 Java Configuration 方式使能 @AspectJ

@Configuration
@EnableAspectJAutoProxy
public class AppConfig{

}

使用 XML方式使能@AspectJ


定义切面
使用 注解 @AspectJ 标注一个Bean 后,那么 sping 会自动收集这些 Bean,并添加到 spring aop 中 eg.

@Component
@Aspect
public class LogAspect{
}

注意,仅仅使用 @Aspect 注解,并不能使一个 Java对象 转换为 Bean。因此我们还需要使用 类似 @Component 注解,如果一个 类被 @Aspect 注解标注,这个类就不能是其他 Aspect 的 **adviced object**了,因为使用 @Aspect 后,这个类就会被排除咋 auto-proxying 机制之外

声明 poincut
一个 poincut的声明包含 2 部分

  • 一个方法签名,包括方法名和相关参数
  • 一个poincut 表达式,用来指定哪些方法执行是需要织入 advice
    @Aspect 风格的 aop 中,我们使用一个方法来描述 poincut,即
@Pointcut("execution(* com.xys.service.UserService.*(..))") // 切点表达式
private void dataAccessOperation(){

}

这个方法必须无返回值
这个方法本身就是 pointcut signature,poincut 表达式使用 @Pointcut 注解指定
这个 pointcut 所描述的是匹配 所有在包 com.xys.service.UserService下的所有方法的执行

切点标志符(designator
execution 就是标志符,圆括号里的就是操作参数
匹配 join point 的执行, 例如 “execution(* hello(…))” 表示匹配所有目标类中的 hello() 方法. 这个是最基本的 pointcut 标志符.

within
匹配特定包下的所有 join point, 例如 within(com.xys.*) 表示 com.xys 包中的所有连接点, 即包中的所有类的所有方法. 而 within(com.xys.service.*Service) 表示在 com.xys.service 包中所有以 Service 结尾的类的所有的连接点.

this 与 target
this 的作用是匹配一个 bean, 这个 bean(Spring AOP proxy) 是一个给定类型的实例(instance of). 而 target 匹配的是一个目标对象(target object, 即需要织入 advice 的原始的类), 此对象是一个给定类型的实例(instance of).

bean
匹配 bean 名字为指定值的 bean 下的所有方法, 例如:

bean(*Service) // 匹配名字后缀为 Service 的 bean 下的所有方法
bean(myService) // 匹配名字为 myService 的 bean 下的所有方法

args
匹配参数满足要求的的方法.
例如:

@Pointcut("within(com.xys.demo2.*)")
public void pointcut2() {
}

@Before(value = "pointcut2()  &&  args(name)")
public void doSomething(String name) {
    logger.info("---page: {}---", name);
}
  @Service
    public class NormalService {
        private Logger logger = LoggerFactory.getLogger(getClass());
    
        public void someMethod() {
            logger.info("---NormalService: someMethod invoked---");
        }
    
    
        public String test(String name) {
            logger.info("---NormalService: test invoked---");
            return "服务一切正常";
        }
    }

当 NormalService.test 执行时, 则 advice doSomething 就会执行, test 方法的参数 name 就会传递到 doSomething 中.

// 匹配只有一个参数 name 的方法
@Before(value = "aspectMethod()  &&  args(name)")
public void doSomething(String name) {
}

// 匹配第一个参数为 name 的方法
@Before(value = "aspectMethod()  &&  args(name, ..)")
public void doSomething(String name) {
}

// 匹配第二个参数为 name 的方法
Before(value = "aspectMethod()  &&  args(*, name, ..)")
public void doSomething(String name) {
}

@annotation
匹配由指定注解所标注的方法, 例如:

@Pointcut("@annotation(com.xys.demo1.AuthChecker)")
public void pointcut() {
}

则匹配由注解 AuthChecker 所标注的方法.

常见的切点表达式
匹配方法签名

// 匹配指定包中的所有的方法
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 表达式.
下面我们以几个简单的 advice 为例子, 来看一下一个 advice 是如何声明的.
Before advice

/**
 * @author xiongyongshun
 * @version 1.0
 * @created 16/9/9 13:13
 */
@Component
@Aspect
public class BeforeAspectTest {
    // 定义一个 Pointcut, 使用 切点表达式函数 来描述对哪些 Join point 使用 advise.
    @Pointcut("execution(* com.xys.service.UserService.*(..))")
    public void dataAccessOperation() {
    }
}
@Component
@Aspect
public class AdviseDefine {
    // 定义 advise
    @Before("com.xys.aspect.PointcutDefine.dataAccessOperation()")
    public void doBeforeAccessCheck(JoinPoint joinPoint) {
        System.out.println("*****Before advise, method: " + joinPoint.getSignature().toShortString() + " *****");
    }
}

这里, @Before 引用了一个 pointcut, 即 “com.xys.aspect.PointcutDefine.dataAccessOperation()” 是一个 pointcut 的名字.
如果我们在 advice 在内置 pointcut, 则可以:

@Component
@Aspect
public class AdviseDefine {
    // 将 pointcut 和 advice 同时定义
    @Before("within(com.xys.service..*)")
    public void doAccessCheck(JoinPoint joinPoint) {
        System.out.println("*****doAccessCheck, Before advise, method: " + joinPoint.getSignature().toShortString() + " *****");
    }
}

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

@Component
@Aspect
public class AdviseDefine {
    // 定义 advise
    @Around("com.xys.aspect.PointcutDefine.dataAccessOperation()")
    public Object doAroundAccessCheck(ProceedingJoinPoint pjp) throws Throwable {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        // 开始
        Object retVal = pjp.proceed();
        stopWatch.stop();
        // 结束
        System.out.println("invoke method: " + pjp.getSignature().getName() + ", elapsed time: " + stopWatch.getTotalTimeMillis());
        return retVal;
    }
}

around advice 和前面的 before advice 差不多, 只是我们把注解 @Before 改为了 @Around 了.

文章原文 https://segmentfault.com/a/1190000007469968

你可能感兴趣的:(aop,spring,spring)