Spring丨AOP

前言

面向切面编程(AOP)和面向对象编程(OOP)类似,也是一种编程思想。
AOP 的全称是“Aspect Oriented Programming”,即面向切面编程,它将业务逻辑的各个部分进行隔离,使开发人员在编写业务逻辑时可以专心于核心业务,从而提高了开发效率。
AOP 采取横向抽取机制,取代了传统纵向继承体系的重复性代码,其应用主要体现在事务处理、日志管理、权限控制、异常处理等方面。
它在不改变原有纵向的代码的情况下,横向增加新功能。怎么实现增加的?通过代理模式

名词解释

为了更好地理解 AOP,就需要对 AOP 的相关术语有一些了解,这些专业术语主要包含 Joinpoint、Pointcut、Advice、Target、Weaving、Proxy 和 Aspect,它们的含义如下表所示。

名称说明
Joinpoint(连接点)指那些被拦截到的点,在 Spring 中,可以被动态代理拦截目标类的方法。
Pointcut(切入点)指要对哪些 Joinpoint 进行拦截,即被拦截的连接点。
Advice(通知)指拦截到 Joinpoint 之后要做的事情,即对切入点增强的内容。
Target(目标)指代理的目标对象。
Weaving(植入)指把增强代码应用到目标上,生成代理对象的过程。
Proxy(代理)指生成的代理对象。
Aspect(切面)切入点和通知的结合。

通知类型

通知(Advice)其实就是对目标切入点进行增强的内容,Spring AOP 为通知(Advice)提供了 org.aopalliance.aop.Advice 接口。
Spring 通知按照在目标类方法的连接点位置,可以分为以下五种类型,如下所示。

名称说明
org.springframework.aop.MethodBeforeAdvice(前置通知)在方法之前自动执行的通知称为前置通知,可以应用于权限管理等功能。
org.springframework.aop.AfterReturningAdvice(后置通知)在方法之后自动执行的通知称为后置通知,可以应用于关闭流、上传文件、删除临时文件等功能。
org.aopalliance.intercept.MethodInterceptor(环绕通知)在方法前后自动执行的通知称为环绕通知,可以应用于日志、事务管理等功能。
org.springframework.aop.ThrowsAdvice(异常通知)在方法抛出异常时自动执行的通知称为异常通知,可以应用于处理异常记录日志等功能。
org.springframework.aop.IntroductionInterceptor(引介通知)在目标类中添加一些新的方法和属性,可以应用于修改旧版本程序(增强类)。

代理模式

代理就是生活中的中介角色,如:房租中介、朋友圈境外代购。代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。
值得注意的是,代理类和被代理类应该共同实现一个接口,或者是共同继承某个类。
代理模式的核心思想:代理人跟被代理人都能做同一件事,只不过被代理人把执行权限交给了代理人,外部只能通过代理人来联系被代理人,通过代理人来增强被代理人的一些功能。

静态代理

为什么叫做静态呢?因为它的类型是事先预定好的。
例如,顾客要租房子,房东要出租房子,但是房东没时间去做 “出租房子”这件事,那么中介代替房东去做 “出租房子”这件事。其中,中介就是代理人,房东就是被代理人。

演示

在 src 目录下新建一个名为 com.study.staticProxy 的包,其中新建一个房东类、中介类、顾客类、代理行为接口。
代理行为接口 RentOutHouse

package com.study.staticProxy;

/**
 * 出租房子行为 接口
 */
public interface RentOutHouse {
    public void rentOutHouse();
}

房东类 Host

package com.study.staticProxy;

/**
 * 模拟 房东角色
 */
public class Host implements RentOutHouse{

    // 房东出租房子
    @Override
    public void rentOutHouse(){
        System.out.println("房子出租");
    }
}

中介类 Proxy

package com.study.staticProxy;

/**
 * 模拟 中介角色
 */
public class Proxy implements RentOutHouse{
    private Host host;

    public Proxy(){

    }

    public Proxy(Host host) {
        this.host = host;
    }

    public void setHost(Host host) {
        this.host = host;
    }

    @Override
    public void rentOutHouse(){
        chooseHouse();
        // 出租房东的房子
        host.rentOutHouse();
        getBrokerageFee();
    }

    // 物色房子
    private void chooseHouse(){
        System.out.println("中介带顾客去挑选出租屋");
    }

    // 事成之后收中介费
    private void getBrokerageFee(){
        System.out.println("中介收取中介费");
    }
}

顾客类 Client

package com.study.staticProxy;

/**
 * 模拟 租房子的客户角色
 */
public class Client {

    public static void main(String[] args) {
        Host host = new Host();
        Proxy proxy = new Proxy(host);
        proxy.rentOutHouse();
    }
}

优点:可以做到在不修改目标对象的功能前提下,对目标功能扩展;可以让真实业务操作变得更加纯粹,不用去关心其他的扩展业务,拓展业务由代理类去完成,一旦拓展业务变动了,不需要修改真实业务类。例如:增删查改是真实业务类,输出日志(执行时间、执行方法)是拓展业务,那么输出日志交给代理类负责。
缺点:因为代理对象需要与目标对象实现一样的接口,所以会有太多代理类。同时,一旦接口增加方法,目标对象与代理对象都要维护。

动态代理

动态代理的角色(抽象角色、真实角色、代理角色)和静态代理的是一样的,但是动态代理的代理类是动态生成的。
代理角色:动态生成,不是由开发者自己编写,而是开发者自定义一个模板,然后交给程序去生成代理对象;
动态代理的实现机制有两种:一种是基于接口的动态代理,还有一种就是基于 CGLlB 实现动态代理。

  • 基于接口:JDK 动态代理;
  • 基于类的动态代理:也就是 cglib 动态代理(需要导入额外的 cglib 相关依赖);
  • Java 字节码实现动态代理:Javassist。

首先需要熟悉两个 Java 反射类:

  1. Proxy:代理类;它提供用于创建动态代理类和实例的静态方法 newProxyInstance。
  2. InvocationHandler:代理类实例的调用处理程序实现的接口,其中有一个 invoke() 方法。

演示

在 src 目录下新建一个名为 com.study.dynamicProxy 的包,其中的 代理行为接口 RentOutHouse房东类 Host 与静态代理中的一样,需要改变的是中介类 Proxy

package com.study.dynamicProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyInvocationHandler implements InvocationHandler {
    // 代理的目标:真实角色
    private RentOutHouse rent;

    public void setRent(RentOutHouse rent) {
        this.rent = rent;
    }

    // 生成的代理类
    public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                rent.getClass().getInterfaces(), this);
    }

    /**
     * @param proxy 代理类
     * @param method 代理类的调用处理程序的方法对象
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        chooseHouse();
        Object result = method.invoke(rent, args);
        getBrokerageFee();
        return result;
    }

    // 物色房子
    private void chooseHouse(){
        System.out.println("中介带顾客去挑选出租屋");
    }

    // 事成之后收中介费
    private void getBrokerageFee(){
        System.out.println("中介收取中介费");
    }
}

其中,被代理的目标不一定局限于“房东”,还可以是其他类型的,那么修改如下:

package com.study.dynamicProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyInvocationHandler implements InvocationHandler {
    // 扩大了可代理的类型范围
    private Object target;

    public void setTarget(Object target) {
        this.target = target;
    }

    // 生成的代理类
    public Object getProxy() {
         /**
         * ClassLoader loader 定义代理类的类加载器
         * @NotNull Class<?>[] interfaces 代理类要实现的接口列表
         * @NotNull reflect.InvocationHandler h 指派方法调用的调用处理程序
         */
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                target.getClass().getInterfaces(), this);
    }

    /**
     * @param proxy 代理类
     * @param method 代理类的调用处理程序的方法对象
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        chooseHouse();
        Object result = method.invoke(target, args);
        getBrokerageFee();
        return result;
    }

    // 物色房子
    private void chooseHouse(){
        System.out.println("中介带顾客去挑选出租屋");
    }

    // 事成之后收中介费
    private void getBrokerageFee(){
        System.out.println("中介收取中介费");
    }
}

动态代理的好处:

  • 静态代理的优点全部具备;
  • 一个动态代理类代理的是一个接口,一般就是对应其某一个类型的业务,而不是单个业务;
  • 一个动态代理类可以代理多个类,只要是实现了同一个接口即可。

JDK 动态代理

JDK 动态代理是通过 JDK 中的 java.lang.reflect.Proxy 类实现的。

  • 创建接口 CustomerDao
    在 com.study.dynamicProxy 目录下创建一个名为 jdkProxy 的包,在该包下创建一个 CustomerDao 接口,如下所示。

    package com.study.dynamicProxy.jdkProxy;
    
    /**
     * 演示 JDK 动态代理接口
     */
    public interface CustomerDao {
      public void add();
    
      public void update();
    
      public void delete();
    
      public void find();
    }
  • 创建实现类 CustomerDaoImpl
    在同一个包内,创建 CustomerDao 接口的实现类 CustomerDaoImpl,并实现该接口中的所有方法,如下所示。

    package com.study.dynamicProxy.jdkProxy;
    
    /**
     * CustomerDao接口的实现类
     */
    public class CustomerDaoImpl implements CustomerDao{
      @Override
      public void add() {
          System.out.println("添加客户...");
      }
    
      @Override
      public void update() {
          System.out.println("修改客户...");
      }
    
      @Override
      public void delete() {
          System.out.println("删除客户...");
      }
    
      @Override
      public void find() {
          System.out.println("查询客户...");
      }
    }
  • 创建切面类 MyAspect
    在该包下创建一个切面类 MyAspect,如下所示。

    package com.study.dynamicProxy.jdkProxy;
    
    /**
     * 切面类
     */
    public class MyAspect {
      public void myBefore() {
          System.out.println("方法执行之前");
      }
    
      public void myAfter() {
          System.out.println("方法执行之后");
      }
    }

    上述代码中,在切面中定义了两个增强的方法,分别为 myBefore() 方法和 myAfter() 方法,用于对目标类(CustomerDaoImpl)进行增强。

  • 创建代理类 MyBeanFactory
    在同一个包下,创建一个名为 MyBeanFactory 的类,在该类中使用 java.lang.reflect.Proxy 实现 JDK 动态代理,如下所示。

    package com.study.dynamicProxy.jdkProxy;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    public class MyBeanFactory {
      public static CustomerDao getBean() {
          // 准备目标类
          final CustomerDao customerDao = new CustomerDaoImpl();
          // 创建切面类实例
          final MyAspect myAspect = new MyAspect();
          // 使用代理类,进行增强
          return (CustomerDao) Proxy.newProxyInstance(MyBeanFactory.class.getClassLoader(),
              new Class[]{CustomerDao.class}, new InvocationHandler() {
              @Override
              public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                  // 前增强
                  myAspect.myBefore();
                  Object obj = method.invoke(customerDao, args);
                  // 后增强
                  myAspect.myAfter();
                  return obj;
              }
          });
      }
    }

    上述代码中,定义了一个静态的 getBean() 方法,这里模拟 Spring 框架的 IoC 思想,通过调用 getBean() 方法创建实例,第 10 行代码创建了 customerDao 实例。
    第 12 行代码创建的切面类实例用于调用切面类中相应的方法;第 14~23 行就是使用代理类对创建的实例 customerDao 中的方法进行增强的代码,其中 Proxy 的 newProxyInstance() 方法的第一个参数是当前类的类加载器,第二参数是所创建实例的实现类的接口,第三个参数就是需要增强的方法。
    在目标类方法执行的前后,分别执行切面类中的 myBefore() 方法和 myAfter() 方法。

  • 创建测试类 JDKProxyTest
    在同一个包下创建一个名为 JDKProxyTest 的测试类,如下所示。

    package com.study.dynamicProxy.jdkProxy;
    
    import org.junit.Test;
    
    public class JDKProxyTest {
      @Test
      public void test() {
          // 从工厂获得指定的内容(相当于spring获得,但此内容时代理对象)
          CustomerDao customerDao = MyBeanFactory.getBean();
          // 执行方法
          customerDao.add();
          customerDao.update();
          customerDao.delete();
          customerDao.find();
      }
    }
  • 运行查看结果
    使用 JUnit 测试运行 test() 方法,运行成功后,控制台的输出结果如下所示。

    方法执行之前
    添加客户...
    方法执行之后
    方法执行之前
    修改客户...
    方法执行之后
    方法执行之前
    删除客户...
    方法执行之后
    方法执行之前
    查询客户...
    方法执行之后

    从输出结果中可以看出,在调用目标类的方法前后,成功调用了增强的代码,由此说明,JDK 动态代理已经实现。

CGLlB 动态代理

虽然 JDK 动态代理使用起来非常简单,但是它也有一定的局限性,这是因为 JDK 动态代理必须要实现一个或多个接口,如果不希望实现接口,则可以使用 CGLIB 代理。
CGLIB(Code Generation Library)是一个高性能开源的代码生成包,它被许多 AOP 框架所使用,其底层是通过使用一个小而快的字节码处理框架 ASM(Java 字节码操控框架)转换字节码并生成新的类。
因此 CGLIB 要依赖于 ASM 的包,但是 Spring 的核心包已经集成了 CGLIB 所需要的包,所以在开发中不需要另外导入 ASM 的 JAR 包了。

  • 创建目标类 GoodsDao
    在 com.study.dynamicProxy 目录下创建一个名为 cglibProxy 的包,在包中新建一个目标类 GoodsDao,在类中定义增、删、改、查方法,并在每个方法编写输出语句,如下所示。

    package com.study.dynamicProxy.cglibProxy;
    
    /**
     * 演示 CGLlB动态代理
     * 目标类
     */
    public class GoodsDao {
      public void add() {
          System.out.println("添加商品...");
      }
    
      public void update() {
          System.out.println("修改商品...");
      }
    
      public void delete() {
          System.out.println("删除商品...");
      }
    
      public void find() {
          System.out.println("修改商品...");
      }
    }
  • 创建代理类 MyBeanFactory
    在同一个包下创建类 MyBeanFactory,如下所示。

    package com.study.dynamicProxy.cglibProxy;
    
    import com.study.dynamicProxy.jdkProxy.MyAspect;
    import org.springframework.cglib.proxy.Enhancer;
    import org.springframework.cglib.proxy.MethodInterceptor;
    import org.springframework.cglib.proxy.MethodProxy;
    import java.lang.reflect.Method;
    
    /**
     * 代理类
     */
    public class MyBeanFactory {
      public static GoodsDao getBean() {
          // 准备目标类
          final GoodsDao goodsDao = new GoodsDao();
          // 创建切面类实例
          final MyAspect myAspect = new MyAspect();
          // 生成代理类,CGLIB在运行时,生成指定对象的子类,增强
          Enhancer enhancer = new Enhancer();
          // 确定需要增强的类
          enhancer.setSuperclass(goodsDao.getClass());
          // 添加回调函数
          enhancer.setCallback(new MethodInterceptor() {
              // intercept 相当于 jdk invoke,前三个参数与 jdk invoke—致
              @Override
              public Object intercept(Object proxy, Method method, Object[] args, 
                                      MethodProxy methodProxy) throws Throwable {
                  // 前增强
                  myAspect.myBefore();
                  // 目标方法执行
                  Object obj = method.invoke(goodsDao, args);
                  // 后增强
                  myAspect.myAfter();
                  return obj;
              }
          });
          // 创建代理类
          GoodsDao goodsDaoProxy = (GoodsDao) enhancer.create();
          return goodsDaoProxy;
      }
    }

    上述代码中,应用了 CGLIB 的核心类 Enhancer。在第 21 行代码调用了 Enhancer 类的 setSuperclass() 方法,确定目标对象。
    第 23 行代码调用 setCallback() 方法添加回调函数;第 26 行代码的 intercept() 方法相当于 JDK 动态代理方式中的 invoke() 方法,该方法会在目标方法执行的前后,对切面类中的方法进行增强;第 38~39 行代码调用 Enhancer 类的 create() 方法创建代理类,最后将代理类返回。

  • 创建测试类
    在同一个包下创建测试类 CGLIBProxyTest,如下所示。

    package com.study.dynamicProxy.cglibProxy;
    
    import org.junit.Test;
    
    public class CGLIBProxyTest {
      @Test
      public void test() {
          // 从工厂获得指定的内容(相当于spring获得,但此内容时代理对象)
          GoodsDao goodsDao = MyBeanFactory.getBean();
          // 执行方法
          goodsDao.add();
          goodsDao.update();
          goodsDao.delete();
          goodsDao.find();
      }
    }

    上述代码中,调用 getBean() 方法时,依然获取的是 goodsDao 的代理对象,然后调用该对象的方法。使用 JUnit 测试运行 test() 方法,运行成功后,控制台的输出结果如下所示。

    方法执行之前
    添加商品...
    方法执行之后
    方法执行之前
    修改商品...
    方法执行之后
    方法执行之前
    删除商品...
    方法执行之后
    方法执行之前
    修改商品...
    方法执行之后

使用

AOP 是 Spring的一个重要组成部分,但是 IOC 容器并不依赖于 AOP,这意味着你有权利选择是否使用 AOP。
使用之前也需要导入命名空间和 schemaLocation 中 xsd 路径,XML 文件如下配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>

否则,运行程序会报错:

通配符的匹配很全面, 但无法找到元素 'aop:config' 的声明。

如果运行时报错:

Caused by: java.lang.ClassNotFoundException: org.aspectj.weaver.reflect.ReflectionWorld$ReflectionWorldException

那就是没有导入相对应的 Jar 包:需要在项目中加入依赖 aspectjweaver.jar 包。

实现 AOP

Spring 创建一个 AOP 代理的基本方法是使用 org.springframework.aop.framework.ProxyFactoryBean,这个类对应的切入点和通知提供了完整的控制能力,并可以生成指定的内容。
ProxyFactoryBean 类中的常用可配置属性如下所示。

属性名称描述
target代理的目标对象
proxyInterfaces代理要实现的接口,如果有多个接口,则可以使用以下格式赋值:


proxyTargetClass是否对类代理而不是接口,设置为 true 时,使用 CGLIB 代理
interceptorNames需要植入目标的 Advice
singleton返回的代理是否为单例,默认为 true(返回单实例)
optimize当设置为 true 时,强制使用 CGLIB

使用 Spring 的接口实现

在 src 目录下新建一个名为 com.study.aop 的包,在包中新建几个类。
UserServices 接口

package com.study.aop;

/**
 * 用户服务接口:增删查改
 */
public interface UserServices {
    public void add();
    public void update();
    public void delete();
    public void search();
}

实现类 UserServiceImpl

package com.study.aop;

public class UserServiceImpl implements UserServices{
    @Override
    public void add() {
        System.out.println("新增用户");
    }

    @Override
    public void update() {
        System.out.println("更新用户");
    }

    @Override
    public void delete() {
        System.out.println("注销用户");
    }

    @Override
    public void search() {
        System.out.println("搜索用户");
    }
}

前置通知类 Log

package com.study.aop;

import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;

/**
 * 前置通知
 */
public class Log implements MethodBeforeAdvice {

    /**
     * 在实现操作业务之前首先要先做一些日志信息
     * @param method 要执行的目标对象的方法
     * @param objects 参数,对象数组
     * @param o 目标对象
     * @throws Throwable
     */
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println(o.getClass().getName() + "的" + method.getName() +"方法被执行");
    }
}

测试类 Test

package com.study.aop;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * 测试类
 */
public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("aop.xml");
        UserServices service = (UserServices) context.getBean("userService");
        service.add();
    }
}

配置文件 aop.xml
基于 XML 的声明式是指通过 Spring 配置文件的方式定义切面、切入点及声明通知,而所有的切面和通知都必须定义在 <aop: config> 元素中。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="userService" class="com.study.aop.UserServiceImpl"/>
    <bean id="log" class="com.study.aop.Log"/>

    <!-- 配置 aop -->
    <aop:config>
        <!-- 表达式中 execution 是固定的,其中 log 只对 UserServiceImpl的 add()方法进行切入 -->
        <aop:pointcut id="pointcut" expression="execution(* com.study.aop.UserServiceImpl.add())"/>
        <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
    </aop:config>
</beans>

但是以上的配置,只是对 UserServiceImpl 类中的 add()方法进行切入,其他方法没有,所以可以改为 * 号。

<!-- <aop:pointcut id="pointcut" expression="execution(* com.study.aop.UserServiceImpl.add())"/> -->
<!-- * 表示 UserServiceImpl 的所有方法都适用,() 表示所有的无参方法 -->
<aop:pointcut id="pointcut" expression="execution(* com.study.aop.UserServiceImpl.*())"/>

这样一来,所有的无参数方法都适用,但只是无参方法适用,对于有参数的方法就不起作用了。
如果想对所有的方法都适用,那么在 () 中新增两个点号:

<!-- 如果想所有的方法都适用,在()中新增.. -->
<aop:pointcut id="pointcut" expression="execution(* com.study.aop.UserServiceImpl.*(..))"/>

后置通知类 AfterLog

package com.study.aop;

import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;

/**
 * 后置通知,并且接收返回值
 */
public class AfterLog implements AfterReturningAdvice {

    @Override
    public void afterReturning(Object returnValue, Method method, Object[] objects, Object o1) throws Throwable {
        System.out.println("执行了" + method.getName() + "方法,返回结果为" + returnValue);
    }
}

配置文件对应修改

<bean id="userService" class="com.study.aop.UserServiceImpl"/>
<!-- 前置通知 -->
<bean id="log" class="com.study.aop.Log"/>
<!-- 后置通知 -->
<bean id="afterLog" class="com.study.aop.AfterLog"/>

<!-- 配置 aop -->
<aop:config>
    <aop:pointcut id="pointcut" expression="execution(* com.study.aop.UserServiceImpl.*(..))"/>
    <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
    <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>

测试类 Test

package com.study.aop;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * 测试类
 */
public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("aop.xml");
        UserServices service = (UserServices) context.getBean("userService");
        // 动态代理代理的是接口,实现类不需要被代理
        UserServices service2 = context.getBean("userService", UserServices.class);
        service2.add();
        service2.delete();
        service2.search();
        service2.update();
    }
}

Execution 表达式

execution() 是最常用的切点函数,其语法格式为:方法注解 权限修饰符 返回值 全限定类名 方法名 (参数)。

  • 方法注解:可选操作,即一个方法上的注解,例如@Override;
  • 权限修饰符:public,protected,default(权限修饰符为可选操作,即可有可无);
  • 返回值:有多种,这里可以用 * 来表示这个表达式的返回值为任意返回值;
  • 全限定类名:其名称格式为,包.类名称;如果这个包下面还有子包,可以使用“…”来代替所有的子包;
  • 方法名:普通普通的方法名称,这边可以和全限定类名进行组合,所以总的名称格式可以为包.类名称.方法名(参数);
  • (参数):括号里可以使用“..”来表示任意的参数

自定义类实现

AOP 它底层是通过动态代理实现。所以,可以自定义类实现 AOP 只需要编写自定义类,无需实现 Spring 自带的接口,然后通过配置文件进行切面的处理配置。
在同一个目录 com.study.aop 下创建一个自定义日志类 CustomLog:

package com.study.aop;

/**
 * 自定义类实现 AOP,不需要实现 Spring的API
 */
public class CustomLog {
    public void before() {
        System.out.println("【INFO---BEFORE】方法执行之前");
    }

    public void after() {
        System.out.println("【INFO---AFTER】方法执行之后");
    }
}

UserServices 接口和 UserServiceImpl 接口实现类都不需要改变,要改变的是 XML 配置文件:

<!-- 自定义通知 -->
<bean id="customLog" class="com.study.aop.CustomLog"/>
<aop:config>
  <!-- 这里要写 customLog的id -->
  <aop:aspect ref="customLog">
      <aop:pointcut id="pc" expression="execution(* com.study.aop.UserServiceImpl.*(..))"/>
      <!-- 配置前置通知:method 填写 ref中的 before方法,也就是 customLog的before -->
      <aop:before method="before" pointcut-ref="pc"/>
      <!-- 配置后置通知:method 填写 ref中的 after方法,也就是 customLog的after -->
      <aop:after method="after" pointcut-ref="pc"/>
    </aop:aspect>
</aop:config>

使用注解实现

在 Spring 中,尽管使用 XML 配置文件可以实现 AOP 开发,但是如果所有的相关的配置都集中在配置文件中,势必会导致 XML 配置文件过于臃肿,从而给维护和升级带来一定的困难。为此,AspectJ 框架为 AOP 开发提供了另一种开发方式——基于 Annotation 的声明式。AspectJ 允许使用注解定义切面、切入点和增强处理,而 Spring 框架则可以识别并根据这些注解生成 AOP 代理。

在同一个目录 com.study.aop 下创建一个自定义日志类 AnnotationLog,它使用了切面注解 @Aspect:

package com.study.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

/**
 * 添加注解@Aspect 表示这个类是一个切面类
 */
@Aspect
public class AnnotationLog {
    /**
     * @Before 添加切入点,原本写在 XML配置文件的,写在这里了
     */
    @Before("execution(* com.study.aop.UserServiceImpl.*(..))")
    public void before() {
        System.out.println("【注解-BEFORE】方法执行之前");
    }

    /**
     * @After 添加切入点,原本写在 XML配置文件的,写在这里了
     */
    @After("execution(public * com.study.aop.UserServiceImpl.*(..))")
    public void after() {
        System.out.println("【注解-AFTER】方法执行之后");
    }

    /**
     * 在环绕通知中,可以给定一个参数,代表我们要获取的切入的点
     *
     * @param pj
     * @throws Throwable
     */
    @Around("execution(public * com.study.aop.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint pj) throws Throwable {
        System.out.println("【INFO】使用环绕之前");
        // 签名就是方法签名
        System.out.println("【INFO】获得签名:" + pj.getSignature());
        // 执行proceed() 会得到一个返回结果
        Object result = pj.proceed();
        System.out.println("【INFO】获得执行对象:" + result);
        System.out.println("【INFO】使用环绕之后");
    }
}

对应的 XML 配置文件:

<!-- 注解通知类 -->
<bean id="AnLog" class="com.study.aop.AnnotationLog"/>
<!-- 3.使用注解实现 AOP -->
<aop:aspectj-autoproxy/>

参考

Java三种代理模式:静态代理、动态代理和cglib代理 - SegmentFault 思否
轻松理解AOP(面向切面编程) - 望远的个人页面 - OSCHINA - 中文开源技术交流社区
DataFlair
打赏
评论区
头像
文章目录