Spring 三部曲(二):Spring 的 AOP

简介: 软件开发一直在追求,更高效,更易维护,更易扩展的方式。为了提高开发效率,我们对开发语言进行抽象,走过了从汇编到各种高级语言繁盛的时期;为了更容易扩展,我们对各种相同功能进行高度抽象使之模块化。走过了从过程化编程到面向对象(OOP)编程 “短暂而又漫长”的过程。不管如何,我们一直在追求更加完美更加高效的软件开发方式。

Spring 三部曲(二):Spring 的 AOP

[toc]

一起来看 AOP

软件开发一直在追求,更高效,更易维护,更易扩展的方式。为了提高开发效率,我们对开发语言进行抽象,走过了从汇编到各种高级语言繁盛的时期;为了更容易扩展,我们对各种相同功能进行高度抽象使之模块化。走过了从过程化编程到面向对象(OOP)编程 “短暂而又漫长”的过程。不管如何,我们一直在追求更加完美更加高效的软件开发方式。

当 OOP 被提出来以后,许多人认为面向对象(OOP)的软件开发方式可能就是那搞定一切的 “银弹”。但是不得不承认,即使是 OOP ,也不能解决软件开发中的所有问题。

软件开发的目的,最终是为了解决各种需求。包括业务需求和系统需求。使用 OOP ,我们可以对业务需求等普通关注点进行很好的抽象和封装。对于系统需求一类的关注点可能就有所不同。

我们要为上述系统添加 安全检查 和 日志记录功能。这些属于系统需求,而且需求很明确,就是在方法开始进行安全检查和日志记录。如果使用面向对象的方法,我们势必要对每个对象都应用一遍相同的安全检查与日志记录的逻辑。

OOP 已足够优秀,足以解决我们的大部分业务需求,但我们还应该寻找一种方式来助力OOP更上一层楼,或者寻找一种方法来对 OOP 进行补足。好消息是,优秀的大牛们帮我们找到了,那就是 AOP。

AOP 全程为 Aspect-Oriented Programming ,中文通常翻译为 面向切面编程。AOP 引入了 Aspect 的概念,用来以模块化的方式对一些横切关注点进行组织和实现。Aspect 之与 AOP,如同 Class 之于 OOP。AOP仅仅是对 OOP 方法的一种补足,如下图:

加入各种系统需求后的模块关系示意图

加入各种系统需求后的模块关系示意图

在业务系统的各个模块之间加入我们的横切关注点

AOP 走向现实 - AspectJ

AOP 只是一种理念,要实现这种理念,通常需要一种实现方式。与 OOP 需要响应的语言支持一样,AOP 的实现也需要响应的语言帮助来实现 AOP 中的概念实体。我们统称这些 AOP 的实现语言为 AOL。AOL 可以使系统实现语言也可以不是。
AspectJ 是一个面向切面的框架,它扩展了 Java 语言。AspectJ 定义了 AOP 语法,它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。

除了上面提到的针对 Java 语言产生的 AspectJ ,还有 AcpectC,AspectC++,Aspect.Net,AspectPHP 等等。

目前 AOP 实现的技术方式都最终都需要以某种方式集成到系统语言所实现的 OOP 实体组件中去。这个过程就叫做 织入(Weave)

织入(Weave) 的过程是处于 OOP 和 AOP 开发过程之外的。当所有的业务需求系统需求以模块化的开发形式完成之后,就可以通过 织入(Weave) 的过程将整个软件系统集成并付诸使用。

当 AOP 的理论出来之后,Java 业界各种 AOP 框架就如同雨后春笋般涌现。主要分为 静态AOP 和 动态 AOP 两大类。

Java 的 AOP 发展史

静态 AOP 时代

静态 AOP ,即第一代 AOP,以 AspectJ 为代表。特点是使用特殊的编译器,将 Aspect 编译到系统的静态类中,以达到融合 Aspect 和 Class 的目的。静态 AOP 的优点是, Aspect 直接以 Java 字节码的形式编译到 Java 类中。不会对整个系统的运行造成任何性能损失。缺点就是不够灵活,Aspect 修改后,需要使用编译器重新编译 Aspect 并织入到系统中。

动态 AOP 时代

动态 AOP,又称第二代 AOP。大都通过 Java 语言实现的各种动态特性来实现 Aspect 织入到当前系统的过程。值得一提的是, Aspect 在融入了 AspectWerkz 框架之后,也拥有的动态织入的行为。称为唯一一个及支持静态 AOP 和动态 AOP 的产品。

动态 AOP 大都以 Java 语言来实现,AOP 中各种概念和实体也都是普通的 java 类,很容易开发和集成。AOP 的织入过程在系统运行时期进行,而不是预先编译到系统类中。好处是可以在系统运行过程中,动态改变织入逻辑。

动态 AOP 在引入灵活性和易用性的同时,也不避免的带来一些性能上的损失。因为动态 AOP的实现大都在类加载或者系统运行期,采用反射或者操作字节码的方式来完成 Aspect 到系统的织入。但随着 JVM 性能的提升,对反射和字节码操作技术更好的支持,这种性能损失是越来越小的。

Spring AOP 属于第二代 AOP,即动态代理。

AOP 国家的公民

就像 OOP 需要了解, 对象,类,接口 这些一样,AOP 也有一些公民需要我们了解一下。

由于 AOP 的术语在不同框架中存在差异。这里我们以出身正统 AspectJ 来讲解,因为 Spring AOP 中的 AOP 术语也是采用 AspectJ 标准的。

Joinpoint

Aspect 需要织入 OOP 的功能模块中,我们需要知道在系统的哪些执行点上进行织入,而这些执行点就叫做 Joinpoint。

常见的 Joinpoint 类型有 方法调用,方法执行,构造方法调用,构造方法执行,字符获取,异常处理类初始化(静态代码)。基本上程序执行中的必要执行点都可以作为 Joinpoint。但是,对于一些 Joinpoint ,AOP 的实现有些困难,比如循环开始时的执行点也可以作为一种 Joinpoint ,但却难以捕捉,甚至付出很多却收效甚微。所以大部分 AOP 都不支持。

Pointcut

Pointcut 概念代表的是 Joinpoint 的表达方式。常用的 Pointcut 的表达方式是: 正则表达式。可以很方便便的指定某个包下的所有类,某个注解标注的某个类,某个注解标注的某些方法等。

Advice

Advice 是横切关注点的逻辑载体。它表示将会织入到 Jointpoint 的横切逻辑。如果将 Aspect 比作 OOP 中的 Class,那么 Advice 就相当于 Class 中的 Method。

按照 Advice 执行时机的不同,Advice 可以分为如下几种:Before Advice,After Advice,After Returning Advice,After Throwing Advice ,以及 Around Advice 等。

Aspect

Aspect 是对系统中的横切关注点进行模块化封装的 AOP 概念实体。一个AOP 可以包含多个 Pointcut 和 Advice 的相关定义。就如同 一个 Class 里面有多个属性和方法一样。

ASpect 准备完毕,如何 应用到 OOP 的模块中呢?而这个应用就是织入的过程。

织入

古语有云:“一桥飞架南北,天堑变通途”。织入的过程就是飞架 AOP 和 OOP 的那座桥梁。织入有不同的方法,AspectJ 有专门的编译器来完成织入的过程,如 acj 编译器。Spring 使用 ProxyFactoryBean 来完成织入。Java 平台各种 AOP 产品实现织入的方法不一而足,唯一相同的就是他们的职责,即 把 Aspect 织入到 OOP 的模块中去。

一文看懂 AOP 中的各个概念。


一文看懂 AOP 中的各个概念关系

Spring AOP

Spring AOP 采用动态代理字节码技术实现,二者都是在运行期间为目标对象生成一个代理对象。在代理对象中实现横切逻辑。系统最终使用的是织入了横切逻辑的代理对象而不是真正的对象。值得一提的是, Spring AOP 并不像 AspectJ 那样支持 AOP 的所有功能,而是根据二八定律,只实现了其中20%的功能,如果觉得 Spring AOP 无法满足需求,那么还可以求助于 AspectJ,Spring 对 AspectJ 也有很好的支持。Spring AOP 相较于 AspectJ,学习曲线要平滑很多,可以快捷的融入到开发过程中。实际上,大部分业务场景 Spring AOP 提供的功能已完全满足需要。

Spring AOP 的实现机制

我们前面说过,Spring AOP 属于第二代AOP,即动态代理的集大成者。其中主要用到了两种技术,一个是 JDK Dynamic proxy ,另一个是 CGLIB Dynamic proxy。

动态代理:JDK Dynamic Proxy

这主要使用了 JDK 提供的动态代理技术。下面来看一个 JDK 动态代理的示例:

# java.lang.reflect.Proxy  
  
public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h) {...}

demo:

public static void testJdkProxy() {
        // 原对象
        IUserService userService = new UserService();

        // 代理对象
        IUserService userServiceProxy = (IUserService) Proxy.newProxyInstance(JDKProxyTest.class.getClassLoader(),
                userService.getClass().getInterfaces(),
                (proxy, method, arguments) -> {
                    System.out.println("我是 <代理方法执行前> 运行的 。。。");

                    // 原始方法运行
                    Object obj = method.invoke(userService, arguments);

                    System.out.println("我是 <代理方法执行后> 运行的 。。。");

                    return obj;
                });

        userServiceProxy.login("123456", "0000");
    }

JDK 动态代理虽然简单,但是却有实现限制,那就是需要目标对象实现一个接口,否则 JDK Dynamic Proxy 就无法为其生代理对象。

默认情况下,Spring AOP 当发现目标对象实现了某个接口,就会使用 JDK Dynamic Proxy 来为其生成代理对象注入到 Spring IoC 容器中,否则的话,就采用一个叫做 CGLIB 的开源的 动态字节码 生成类库,来为其生成代理对象。

字节码生成:CGLIB

使用动态字节码 CGLIB,可以做到 对目标对象进行继承扩展。为目标对象生成子类,子类可以覆写父类的方法来扩展父类的行为。

使用 CGLIB 对目标对象进行扩展的唯一限制就是无法对 final 进行扩展,因为 final 类无法被继承。

/**
 * Cglib 模拟实现 Spring 动态代理
 */
public class CglibAopPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        
        if (bean 实现了某个接口){ JDK Dynamic Proxy}
        else 

        // 如果 bean 没有实现任何接口,采用 CGLIB 实现
        // enhancer: 增强者
        Enhancer enhancer = new Enhancer();
        enhancer.setClassLoader(bean.getClass().getClassLoader());
        enhancer.setSuperclass(bean.getClass());
        MethodInterceptor methodInterceptor = new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] arguments, MethodProxy methodProxy) throws Throwable {
                System.out.println("Cglib 代理方法前 。。。");
                // 调用代理的对象的方法
                Object ret = method.invoke(bean, arguments);
                System.out.println("Cglib 代理方法后 。。。");
                return ret;
            }
        };
        enhancer.setCallback(methodInterceptor);
        // 创建代理 Bean
        Object proxyBean = enhancer.create();

        // 返回代理对象
        return proxyBean;
    }
}

上面说了那么多,一会 AOP,一会 Spring AOP ,又动态代理又CGLIB 的,那么这几个东西之间到底是个啥关系呢?别急,看了下面这张图,保证你会清晰很多。

一图看懂 AOP,Spring AOP ,Aspectj,CGLIB  的关系

一图看懂 AOP,Spring AOP ,Aspectj,CGLIB 的关系

Spring AOP 的织入

我们上面说到 Spring AOP 的两种实现方式并没有看到 Spring AOP 的公民们,如 Joinpoint,Pointcut, Advice 等。那这些 公民是真的存在的吗?

其实,上面我们只是用简单的例子介绍了 Spring AOP的实现底层用到的技术原理。实际上,Spring AOP 已经加入了开源组织 AOP Aliance ,目标是为了标准 化 AOP的使用,促进AOP 实现产品之间的可交互性。鉴于此,Spring AOP 中也有一一对应的各种 AOP 概念实体。

org.aopalliance.intercept.Joinpoint

org.springframework.aop.Pointcut

org.aopalliance.aop.Advice

org.springframework.aop.Advisor

Adcisor 就是 Spring AOP 中的 Aspect,与正常的 Aspect 不同,Advisor 通常持有一个 Pointcut 和 Advice 。而理论上,一个 Aspect 可以有多个 Pointcut 和 Advice,从这一点上,我们可以认为 Advisor 就是一种特殊的 Aspect。

有了上述 各种 AOP 实体,相当于 万事俱备只欠东风,剩下的工作就是把它们组装为 Aspect (Advisor),织入到 OOP 中去即可。

而要进行织入,不同 AOP 实现产品有不同的方法。AspectJ 采用 acj 编译器进行织入,而在 Spring AOP 中,使用类 org.springframework.aop.framework.ProxyFactory 作为织入器。

ProxyFactory 是个啥?

ProxyFactory 作为一个织入器的作用很简单,那就是接受 目标对象Advisor ,返回一个 最终的 代理对象 。API 使用如下:

目标对象: OOP 的实体对象
Advisor:AOP 的 Aspect (横切逻辑和关注点)
    public static void main(String[] args) {
        // 目标对象
        Object targetObject;
        // Aspect
        Advisor advisor;
        
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.addAdvisor(advisor);
        proxyFactory.setTarget(targetObject);

        // 获取代理对象
        Object proxyObject = proxyFactory.getProxy();
    }

前面说过 Spring AOP 支持两种方式的代理模式,而最终都是委托给 ProxyFactory ,在 ProxyFactory 中使用 JDK动态代理 和 CGLIB 两种模式来生成代理对象的。

看清 ProxyFactory 的本质

认清 ProxyFactory 的本质,不仅可以让我们清楚它的实现,还能帮助我们在以后的系统设计中汲取宝贵的经验。接下来让我们详细看下 ProxyFactory 是如何设计封装两种动态代理实现的。

而要了解 ProxyFactory ,得先从 AopProxy 说起。

public interface AopProxy {

    Object getProxy();

    Object getProxy(@Nullable ClassLoader classLoader);

}

Spring AOP 框架内使用 AopProxy 对使用的不同的代理实现机制进行了适度的抽象,针对不同的代理实现机制提供相应的 AopProxy 子类实现。目前,Spring AOP 框架内提供了针对 JDK 的动态代理和 CGLIB 两种机制的 AopProxy 实现。

当前,AopProxy有 Cglib2AopProxy 和 JdkDynamicAopProxy 两种实现。因为JDK动态代理需要通过 InvocationHandler 提供调用拦截,所以,JdkDynamicAopProxy同时实现了InvocationHandler接口。
不同AopProxy实现的实例化过程采用工厂模式(确切地说是抽象工厂模式)进行封装,即 org.springframework.aop.framework.AopProxyFactory 。AopProxyFactory接口的定义如下所示:

public interface AopProxyFactory {

    AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException;

}

AopProxyFactory根据传入的 AdvisedSupport 实例提供的相关信息,来决定生成什么类型的AopProxy。不过,具体工作会转交给AopProxyFactory 的具体实现类。即 DefaultAopProxyFactory:

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
  
  @Override
    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
        if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
            Class<?> targetClass = config.getTargetClass();
            if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
        // 创建 JDK 动态代理
                return new JdkDynamicAopProxy(config);
            }
      // 创建 CGLIB 动态代理
            return new ObjenesisCglibAopProxy(config);
        }
        else {
            return new JdkDynamicAopProxy(config);
        }
    }
}

AopProxyFactory 需要根据 createAopProxy 方法传入的 AdvisedSupport 实例信息,来构建相应的AopProxy。下面我们要看看这个AdvisedSupport 到底是何方神圣:

其实说白了,AdvisedSupport 就是一个生成代理对象所需要的必要信息的载体。

AdvisedSupport 所承载的信息可以划分为两类,一类以org.springframework.aop.framework.ProxyConfig 为统领,记载生成代理对象的控制信息;一类以 org。springframework.aop.framework.Advised 为旗帜,承载生成代理对象所需要的必要信息,如相关目标类、Advice、Advisor等。

默认情况下,SpringAOP 框架返回的代理对象都可以强制转型为 Advised,以查询代理对象的相关信息。具体信息可以查看 Advised 的接口定义。

Advised 接口定义太长,我们就不在此罗列了,你可以参照它的Javadoc。简单点儿说,我们可以使用Advised接口访问相应代理对象所持有的 Advisor,进行添加Advisor、移除Advisor等相关动作。即使代理对象已经生成完毕,也可对其进行这些操作。直接操作Advised,更多时候用于测试场景,可以帮助我们检查生成的代理对象是否如所期望的那样。(有关Advised的更多信息,请参照Spring的参考文档,因为与我们的主题相关性不大,这里不进行详细讲述。)

现在回到主题 ProxyFactory。AopProxy、AdvisedSupport 与 ProxyFactory 是什么关系呢?先看图:

再看我们刚开始示例:

    public static void main(String[] args) {
        // 目标对象
        Object targetObject;
        // Aspect
        Advisor advisor;
        
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.addAdvisor(advisor);
        proxyFactory.setTarget(targetObject);

        // 获取代理对象
        Object proxyObject = proxyFactory.getProxy();
    }

我们知道 ProxyFactory 就是 AdvisedSupport 的子类,可以为其添加 Adcisor ,目标类等生成代理的必要信息,最后调用 getProxy 方法生成代理类,而 getProxy 方法其实就是创建了一个 AopProxyFactory(默认是 DefaultAopProxyFactory) ,调用其 createAopProxy 方法来生成代理对象的。

    public Object getProxy() {
            return getAopProxyFactory().createAopProxy(this);
    }    

因为 ProxyFactory 实现了 AdvisedSupport 接口,因此本身就是一个 AdvisedSupport ,把自己传给 AopProxyFactory 来获取一个 AopProxy 的具体实现,就说得通了。

为了重用相关逻辑,Spring AOP框架在实现的时候,将一些公用的逻辑抽取到了org.spring-
framework.aop.framework.ProxyCreatorSupport 中,它自身就继承了AdvisedSupport,所以,生成代理对象的必要信息从其自身就可以搞到。为了简化子类生成不同类型AopProxy的工作,ProxyCreatorSupport 内部持有一个AopProxyFactory实例,默认采用的是DefaultAopProxyFactory。

前面已经说过了,ProxyFactory 只是 Spring AOP 中最基本的织入器实现。实际上,ProxyFactory
还有几个“兄弟”,这从 ProxyCreatorSupport 的继承类图(图9-13)中可以看到。


ProxyFactory 的兄弟

说了这么多,为什么我们使用 Spring AOP 的时候都没看到过这个 ProxyFactory 呢?

其实,ProxyFactory 可以让我们独立于 Spring IoC 容器之外来使用 Spring的 AOP 支持。
而其他两种则是结合强大的 Spring IoC 容器来为我们提供半自动与全自动的 Spring AOP 支持。

织入的自动化

AspectJ 形式的 Spring AOP 实现

Spring AOP 2.0 之后集成了 AspectJ,支持 AspectJ5 发布的 @AspectJ 的实现方式。我们可以使用一套标准的注解来描述 POJO,由 Spring AOP 来扫描这些 POJO,组装为相应的 Aspect 定义,并将声明的横切逻辑织入到当前的系统。

虽然2.0之后的Spring AOP集成了AspectJ,但实际上只能说是仅仅拿来AspectJ的“皮大衣”用一下。而底层各种概念的实现以及织入方式,依然使用的是Spring1.x原先的实现体系。这就好像我们可以学习英语,把英语拿过来为我所用,但本质上,我们还是中国人,而不是英国人。换句话说,Spring AOP 还是 Spring AOP,只不过多学了门外语而已。
Spring AOP 只使用 AspectJ 的类库进行 Pointcut 的解析和匹配,最终的实现机制还是Spring AOP 最初的架构,即使用代理模式处理横切逻辑的织入。

@Aspect 形式的 AOP 使用

还记得上面我们讲到 的 ProxyFactory 的几个兄弟吗,其中 AspectJProxyFactory 就是用来使用编程式方式处理 @Aspect 形式的AOP的。

为了了解 AspectJProxyFactory 的使用,我们需要有一个 POJO,来作为我们的 Aspect

public class FooService {
    public String doSomething(String string){
        System.out.println(string);
        return string;
    }
}


@Aspect
public class LogAdvices {

    /**
     * 匹配 com.itguang.service.FooService 类里面的所有方法
     */
    @Pointcut("execution( public String com.itguang.service.FooService.*(..))")
    public void pointcut() {
    }

    @Around("pointcut()")
    public void around(ProceedingJoinPoint joinPoint) {
        try {
            System.out.println("start...");
            joinPoint.proceed();
            System.out.println("end...");
        } catch (Throwable e) {
            System.out.println("Throwable...");
        }
    }

}

 public static void main(String[] args) {
        AspectJProxyFactory weaver = new AspectJProxyFactory();
        weaver.setProxyTargetClass(true);
        weaver.setTarget(new FooService());
        weaver.addAspect(LogAdvices.class);
        FooService fooService = weaver.getProxy();

        fooService.doSomething("tuhu");

    }

执行结果:

start...
tuhu
end...

AspectJProxyFactory 的使用与 ProxyFactory 没有多大差别,只不多多了 addAspect 方法,该方法可直接为 AspectJProxyFactory 提供 相应的 Aspect 定义。实际上,如果我们愿意,也可以把 AspectJProxyFactory 当做 ProxyFactory 来使用。

需要注意的是,AspectJProxyFactory 只是 Spring 为我们提供的一个编程是的 AOP 实现方式,实际上 Spring AOP 的自动式 AOP 实现还是通过 ProxyFactory 来实现的,下文会介绍

AspectJ 形式的 @Pointcut

AspectJ 形式的 pointcut 声明,依附在 @Aspect 所标注的 Aspect POJO 定义类之内。
ASpectJ 形式的 Pointcut 表达式是有一定限制的,我们只能使用 ASpectJ 的少数几种 Pointcut 标志符。

  • execution

Spring AOP 仅支持方法级别的 Pointcut,execution 是我们使用最多的标志符。它帮助我们匹配拥有指定方法签名的 Joinpoint。

方法 = 方法名 + 返回值 + 参数
public class Foo {

    public String doSomething(String string){
        return string;
    }
}

@Pointcut("execution( public String com.demo.Foo.doSomething(String))")
# 部分匹配模式可以省略:
@Pointcut("execution( String com.demo.Foo.doSomething(String))")
# 通配符 * 和 .. 
// 匹配 com.demo.UserService 类中只有一个参数的方法
@Pointcut("execution( * com.demo.UserService.*(*))")
// 匹配 com.demo.UserService 类中所有的方法
@Pointcut("execution( * com.demo.UserService.*(..))")

// 匹配 com.demo 包(不包括子包)下的所有方法
@Pointcut("execution( * com.demo.*.*(..))")
// 匹配 com.demo 包(及其子包)下的所有方法
@Pointcut("execution( * com.demo..*.*(..))")
* 匹配一个 Word , .. 匹配多个 Word
  • within

within 标志符只接受类型声明,它会匹配指定类型下的所有 Joinpoint ,由于 Spring AOP 支持方法级别的 Joinpoint,所以 within 会匹配所有方法。

// 匹配 com.demo.controller 包下(不包括子包)所有方法
@Pointcut("@within( com.demo.controller.* )")

// 匹配 com.demo.controller 包下(及其子包)所有方法
@Pointcut("@within( com.demo.controller..* )")

# within 还可指定某种类型的注解:

// 匹配 所有标注了 @RestController 注解的 类里的所有方法
@Pointcut("@within( RestController )")
  • target

如果目标对象拥有 @target 标志符锁指定的注解类型,那么目标对象所有的 方法都会被匹配

// 匹配 所有标注了 @RestController 注解的 类里的所有方法
@Pointcut("@target( RestController )")

在 Spring AOP 中, @within 和 @target 没有太大的区别,只不过 @within 属于静态匹配, 而 @target 属于运行时动态匹配

  • annotation

使用@annotation标志符的Pointcut表达式,将会尝试检查系统中所有对象的所有方法级别
Joinpoint。如果被检测的方法标注有 @annotation 标志符所指定的注解类型,那么当前方法所在的Joinpoint 将被 Pointcut 表达式所匹配。

// 匹配 所有标注了 @Transactional 注解的方法
@Pointcut("@annotation( Transactional )")

之后我们就可以为所有 标注了 @Transactional 注解的方法织入事物控制逻辑。

AspectJ 形式的 @Advice

@AspectJ形式的 Advice 定义,实际上就是使用 @Aspect 标注的 Aspect 定义类中的普通方法。只不过,这些方法需要针对不同的 Advice 类型使用对应的注解进行标注。

可以用于标注对应 Advice 定义方法的注解包括:

  • @Before。用于标注Before Advice定义所在的方法;
  • @AfterReturning。用于标注After Returning Advice定义所在的方法;
  • @AfterThrowing。用于标注After Throwing Advice定义所在的方法;
  • @After。用于标注After(finally)Advice定义所在的方法;
  • @Around。用于标注Around Advice定义所在的方法,也就是常说的拦截器类型的Advice;
@Aspect
public class LogAdvices {

    /**
     * 定义切点,可以在内部引用们也可以在外部引用
     */
    @Pointcut("execution( public String com.itguang.service.SchoolService.*(..))")
    public void pointcut() {
    }
    
    @Pointcut("@annotation(org.springframework.web.bind.annotation.RestController)")
    public void pointcut2() {
    }

    // 在内部引用切点

    @Before("pointcut()")
    public void before(JoinPoint joinPoint) {
        // 目标对象
        Object target = joinPoint.getTarget();
        // 代理对象
        Object aThis = joinPoint.getThis();
        // 目标方法参数
        Object[] args = joinPoint.getArgs();
        // 目标方法的签名信息
        Signature signature = joinPoint.getSignature();


        // doSomething
    }

    // AfterThrowing 绑定异常信息
    @AfterThrowing(value = "pointcut()",throwing = "e")
    public void afterThrowing(JoinPoint joinPoint,RuntimeException e) {
        // doSomething
    }


    // AfterReturning 绑定返回值
    @AfterReturning(value = "pointcut()",returning = "retValue")
    public void afterThrowing(JoinPoint joinPoint,String retValue) {
        // doSomething
    }

    // After(Finally) 绑定返回值: 更多时候我们使用 After(Finally)来处理网络连接的释放等。
    @After("pointcut()")
    public void after(JoinPoint joinPoint) {
        // doSomething
    }
    
    // Around ,注意第一个参数较之前几个有变化,必须是 ProceedingJoinPoint 类型,且需要 通过 调用其 proceed() 方法继续目标方法的执行
    @Around("pointcut()")
    public void around(ProceedingJoinPoint joinPoint) {
        try {
            joinPoint.proceed();
        } catch (Throwable e) {
            
        }
    }

}

Spring AOP 自动代理得以实现的原理

要使用自动代理机制,需要以 Spring IoC 容器为依托。更进一步说,需要使用 ApplicationContext 类型的IoC容器。

Spring AOP的自动代理的实现建立在IoC容器的BeanPostProcessor概念之上。还记得我们可以使用BeanPostProcessor 干预bean的实例化过程吗?通过 BeanPostProcessor,我们可以在遍历容器
中所有bean的基础上,对遍历到的bean进行一些操作。有了这个前提条件,要实现自动代理就很容易了。

其实我们不难想到,只要提供一个BeanPostProcessor,然后在这个BeanPostProcessor内部实
现这样的逻辑: 即当对象实例化的时候,为其生成代理对象并返回,而不是实例化后的目标对象本身,
从而达到代理对象自动生成的目的。
AnnotationAwareAspectJAutoProxyCreator提供对注解式(@AspectJ)AOP的支持。
详见:
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#wrapIfNecessary
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#createProxy

@AspectJ代表一种定义Aspect的风格,它让我们能够以POJO的形式定义Aspect,没有其他接口定
义限制。唯一需要的,就是使用相应的注解标注这些Aspect定义的POJO类。之后,Spring AOP会根据
标注的注解搜索这些Aspect定义类,然后将其织入系统。

Spring AOP作为一个轻量级的AOP框架,在简单与强大之间取得了很好的平衡。合理地
使用Spring AOP,将帮助我们更快更好地完成各种工作。

至此,Spring AOP 框架的相关介绍就算完成,具体细节需要大家亲自 Debug 过一遍才能更加深入体会到设计的精妙之处。

目录
相关文章
|
1月前
|
监控 Java API
掌握 Spring Boot AOP:使用教程
Spring Boot 中的面向切面编程(AOP)为软件开发提供了一种创新方法,允许开发者将横切关注点与业务逻辑相分离。这不仅提高了代码的复用性和可维护性,而且还降低了程序内部组件之间的耦合度。下面,我们深入探讨如何在 Spring Boot 应用程序中实践 AOP,以及它为项目带来的种种益处。
|
1月前
|
安全 Java Spring
Spring之Aop的底层原理
Spring之Aop的底层原理
|
1月前
|
Java 关系型数据库 MySQL
利用Spring AOP技术实现一个读写分离
利用Spring AOP技术实现一个读写分离
34 0
|
5天前
|
运维 Java 程序员
Spring5深入浅出篇:基于注解实现的AOP
# Spring5 AOP 深入理解:注解实现 本文介绍了基于注解的AOP编程步骤,包括原始对象、额外功能、切点和组装切面。步骤1-3旨在构建切面,与传统AOP相似。示例代码展示了如何使用`@Around`定义切面和执行逻辑。配置中,通过`@Aspect`和`@Around`注解定义切点,并在Spring配置中启用AOP自动代理。 进一步讨论了切点复用,避免重复代码以提高代码维护性。通过`@Pointcut`定义通用切点表达式,然后在多个通知中引用。此外,解释了AOP底层实现的两种动态代理方式:JDK动态代理和Cglib字节码增强,默认使用JDK,可通过配置切换到Cglib
|
4天前
|
XML Java 数据格式
Spring使用AOP 的其他方式
Spring使用AOP 的其他方式
14 2
|
4天前
|
XML Java 数据格式
Spring 项目如何使用AOP
Spring 项目如何使用AOP
18 2
|
10天前
|
Java 开发者 Spring
Spring AOP的切点是通过使用AspectJ的切点表达式语言来定义的。
【5月更文挑战第1天】Spring AOP的切点是通过使用AspectJ的切点表达式语言来定义的。
22 5
|
10天前
|
XML Java 数据格式
Spring AOP
【5月更文挑战第1天】Spring AOP
25 5
|
10天前
|
Java 编译器 开发者
Spring的AOP理解
Spring的AOP理解
|
11天前
|
XML Java 数据格式
如何在Spring AOP中定义和应用通知?
【4月更文挑战第30天】如何在Spring AOP中定义和应用通知?
16 0