Spring AOP

1. AOP 的概念

AOP(Aspect Oriented Programming),面向切面编程。意思为通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术

2. AOP 的作用

它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强

3. AOP 的优点

  • 减少重复代码
  • 提高开发效率
  • 维护方便

4. AOP 的应用

4.1 提出问题

下面在业务层中增加一个转账操作:

public void transfer(String sourceName, String targetName, double money) {
    //  1.查询两个账户信息
    Account source = accountDao.findAccountByName(sourceName);
    Account target = accountDao.findAccountByName(targetName);

    // 2. 转出账户减钱,转入帐户加钱
    source.setMoney(source.getMoney() - money);
    target.setMoney(target.getMoney() + money);

    // 3. 更新操作
    accountDao.updateAccount(source);
    // 4. 模拟转账异常
    int i = 3 / 0;
    accountDao.updateAccount(target);
}

  1. 出现问题

    由于执行过程出现异常,导致转出账户钱减少了,而转入账户钱并没有增多。

  2. 分析原因

    每次执行持久层方法时,都会获取一个新的 Connection,每个 Connection 都有自己的独立事务,而导致无法实现事务控制,这显然不符合事务的一致性

4.2 解决问题

  1. 解决思路

    我们应该让执行持久层方法时,获取同一个 Connection

  2. 解决方法

    使用 ThreadLocal 对象把 Connection 和当前线程绑定,从而使一个线程中只有一个控制事务的对象

  3. 实现步骤

    1. 创建一个连接的工具类,它用于从数据源获取一个连接,并实现和线程绑定

      /**
       * 连接的工具类,它用于从数据源获取一个连接,并实现和线程绑定
       */
      public class ConnectionUtils {
          private ThreadLocal<Connection> connectionThreadLocal = new ThreadLocal<Connection>();
      
          private DataSource dataSource;
      
          public void setDataSource(DataSource dataSource) {
              this.dataSource = dataSource;
          }
      
          /**
           * 获取当前线程上的连接
           *
           * @return
           */
          public Connection getThreadConnection() {
              try {
                  // 1.从 ThreadLocal 上获取 connection
                  Connection connection = connectionThreadLocal.get();
                  // 2.判断当前线程上是否有连接
                  if (connection == null) {
                      // 3.如果没有从数据源中取出一个连接,并存入 ThreadLocal
                      Connection conn = dataSource.getConnection();
                      connectionThreadLocal.set(conn);
                      return connectionThreadLocal.get();
                  }else {
                      // 4.返回当前线程上的连接
                      return connection;
                  }
              }catch (Exception e){
                  throw new RuntimeException(e);
              }
          }
      
          /**
           * 把连接和线程解绑
           */
          public void releaseConnection() {
              connectionThreadLocal.remove();
          }
      
      }
      
      
    2. 创建事务管理相关的工具类,它包含了事务的开启,提交,回滚,和释放连接

      /**
       * 事务管理相关的工具类,它包含了事务的开启,提交,回滚,和释放连接
       */
      public class TransactionManager {
          private ConnectionUtils connectionUtils;
      
          public void setConnectionUtils(ConnectionUtils connectionUtils) {
              this.connectionUtils = connectionUtils;
          }
      
          /**
           * 开启事务
           */
          public void begin(){
              try {
                  connectionUtils.getThreadConnection().setAutoCommit(false);
              } catch (SQLException e) {
                  e.printStackTrace();
              }
          }
      
          /**
           * 提交事务
           */
          public void commit(){
              try {
                  connectionUtils.getThreadConnection().commit();
              } catch (SQLException e) {
                  e.printStackTrace();
              }
          }
      
          /**
           * 回滚事务
           */
          public void rollback(){
              try {
                  connectionUtils.getThreadConnection().rollback();
              } catch (SQLException e) {
                  e.printStackTrace();
              }
          }
      
          /**
           * 释放连接
           */
          public void release(){
              try {
                  // 将连接还回连接池中
                  connectionUtils.getThreadConnection().close();
                  // 将连接和线程解绑
                  connectionUtils.releaseConnection();
              } catch (SQLException e) {
                  e.printStackTrace();
              }
          }
      
      
      }
      
    3. 不再给 queryRunner 对象提供连接池对象,而是提供 connectionUtils.getThreadConnection() 连接

      /**
       * 账户的持久层实现类
       */
      public class AccountDaoImpl implements AccountDao {
          private QueryRunner queryRunner;
          private ConnectionUtils connectionUtils;
      
          public void setQueryRunner(QueryRunner queryRunner) {
              this.queryRunner = queryRunner;
          }
      
          public void setConnectionUtils(ConnectionUtils connectionUtils) {
              this.connectionUtils = connectionUtils;
          }
      
          public List<Account> findAllAccount() {
              try {
                  String sql = "select * from account";
                  List<Account> accounts = queryRunner.query(connectionUtils.getThreadConnection(), sql, new BeanListHandler<Account>(Account.class));
                  return accounts;
              } catch (SQLException e) {
                  e.printStackTrace();
                  throw new RuntimeException(e);
              }
          }
      
          public Account findAccountById(Integer id) {
              try {
                  String sql = "select * from account where id = ?";
                  Account account = queryRunner.query(connectionUtils.getThreadConnection(), sql, new BeanHandler<Account>(Account.class), id);
                  return account;
              } catch (SQLException e) {
                  e.printStackTrace();
                  throw new RuntimeException(e);
              }
          }
      
          public Account findAccountByName(String accountName) {
              try {
                  String sql = "select * from account where name = ?";
                  Account account = queryRunner.query(connectionUtils.getThreadConnection(), sql, new BeanHandler<Account>(Account.class), accountName);
                  return account;
              } catch (SQLException e) {
                  e.printStackTrace();
                  throw new RuntimeException(e);
              }
          }
      
          public void saveAccount(Account account) {
              try {
                  String sql = "insert into account values(null,?,?)";
                  queryRunner.update(connectionUtils.getThreadConnection(), sql, account.getName(), account.getMoney());
              } catch (SQLException e) {
                  e.printStackTrace();
                  throw new RuntimeException(e);
              }
          }
      
          public void updateAccount(Account account) {
              try {
                  String sql = "update account set name = ? , money = ? where id = ?";
                  queryRunner.update(connectionUtils.getThreadConnection(), sql, account.getName(), account.getMoney(), account.getId());
              } catch (SQLException e) {
                  e.printStackTrace();
                  throw new RuntimeException(e);
              }
          }
      
          public void deleteAccount(Integer id) {
              try {
                  String sql = "delete from account where id = ?";
                  queryRunner.update(connectionUtils.getThreadConnection(), sql, id);
              } catch (SQLException e) {
                  e.printStackTrace();
                  throw new RuntimeException(e);
              }
          }
      }
      
      
    4. 改造转账操作

      public void transfer(String sourceName, String targetName, double money) {
          try {
              // 1. 开启事务
              transactionManager.begin();
              // 2. 执行操作
              // 2.1 查询两个账户信息
              Account source = accountDao.findAccountByName(sourceName);
              Account target = accountDao.findAccountByName(targetName);
              // 2.2 转出账户减钱,转入帐户加钱
              source.setMoney(source.getMoney() - money);
              target.setMoney(target.getMoney() + money);
              // 2.3 更新操作
              accountDao.updateAccount(source);
              // 2.4 模拟转账异常
              int i = 3 / 0;
              accountDao.updateAccount(target);
              // 3.提交事务
              transactionManager.commit();
          } catch (Exception e) {
              // 4.回滚事务
              transactionManager.rollback();
              e.printStackTrace();
              throw new RuntimeException(e);
          } finally {
              // 5.释放资源
              transactionManager.release();
          }
      }
      

4.3 新的问题

通过对业务层改造,已经可以实现事务控制了。

但是由于我们添加了事务控制,也产生了一个新的问题:业务层方法变得臃肿了,里面充斥着很多重复代码。并且业务层方法和事务控制方法耦合了

4.4 新问题的解决

4.4.1 动态代理

  1. 动态代理的特点

    字节码随用随创建,随用随加载。

  2. 动态代理的作用

    在不修改源码的基础上,对方法进行增强

  3. 动态代理的分类

    • 基于接口的动态代理
      • 提供者:JDK 官方
      • 要求:被代理类最少实现一个接口
    • 基于子类的动态代理
      • 提供者:第三方的 CGLib
      • 要求:被代理类是不能用 final 修饰的类
  4. 基于接口的动态代理

    1. 创建代理对象

      使用 Proxy 类中的 newProxyInstance 方法

      Producer producerProxy = (Producer) Proxy.newProxyInstance(producer.getClass().getClassLoader(), producer.getClass().getInterfaces(), new InvocationHandler() {
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
              return null;
          }
      });
      
    2. newProxyInstance 方法的参数

      • ClassLoader:类加载器
        它是用于加载代理对象字节码的,和被代理对象使用相同的类加载器。
      • Class[] :字节码数组
        它是用于让代理对象和被代理对象有相同方法
      • InvocationHandler
        他是用于提供增强的代码。一般都是写一个该接口的实现类 InvocationHandler,通常情况下都是匿名内部类,但不是必须的。
    3. invoke 方法的作用

      执行被代理对象的任何接口方法都会经过该方法

    4. invoke 方法的参数

      • proxy:代理对象的引用
      • method:当前执行的方法
      • args:当前执行方法所需的参数
    5. invoke 方法的返回值

      和被代理对象方法有相同的返回值

    6. 基于接口的动态代理的完整实现

      // 创建真实对象
      final Producer producer = new ProducerImpl();
      
      // 创建代理对象
      Producer producerProxy = (Producer) Proxy.newProxyInstance(producer.getClass().getClassLoader(), producer.getClass().getInterfaces(), new InvocationHandler() {
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
              Object returnValue = null;
              if("saleProduct".equals(method.getName())){
                  double money = (Double) args[0];
                  returnValue = method.invoke(producer, money * 0.8);
              }
              return returnValue;
          }
      });
      
      // 通过代理对象执行方法
      producerProxy.saleProduct(1000);
      
  5. 基于子类的动态代理

    1. 导入 CGLib 的 jar 包

      <dependency>
          <groupId>cglib</groupId>
          <artifactId>cglib</artifactId>
          <version>2.1_3</version>
      </dependency>
      
    2. 创建代理对象

      使用 Enhancer 类中的 create 方法

      Enhancer.create(producer.getClass(), new MethodInterceptor() {
          public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
              return null;
          }
      });
      
    3. create 方法的参数

      • Class:字节码

        它是用于指定被代理对象的字节码

      • Callback

        他是用于提供增强的代码。一般都是写一个该接口的实现类 MethodInterceptor,通常情况下都是匿名内部类,但不是必须的。

    4. intercept 的作用

      执行被代理对象的任何接口方法都会经过该方法

    5. intercept 的参数

      • o:代理对象的引用
      • method:当前执行的方法
      • objects:当前执行方法所需的参数
      • methodProxy:当前执行方法的代理对象
    6. intercept 的返回值

      和被代理对象方法有相同的返回值

    7. 基于子类的动态代理的完整实现

      // 创建真实对象
      final ProducerImpl producer = new ProducerImpl();
      
      // 创建代理对象
      ProducerImpl producerProxy = (ProducerImpl) Enhancer.create(producer.getClass(), new MethodInterceptor() {
          public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
              Object returnValue = null;
              if("saleProduct".equals(method.getName())){
                  double money = (Double) objects[0];
                  returnValue = method.invoke(producer,money*0.8);
              }
              return returnValue;
          }
      });
      
      // 通过代理对象执行方法
      producerProxy.saleProduct(1000);
      

4.4.2 使用动态代理实现事务控制

  1. 使用原来没有事务控制的业务层

    /**
     * 账户业务层实现类
     */
    public class AccountServiceImpl_OLD implements AccountService {
    
        private AccountDao accountDao;
    
        public void setAccountDao(AccountDao accountDao) {
            this.accountDao = accountDao;
        }
    
        public List<Account> findAllAccount() {
            return accountDao.findAllAccount();
        }
    
        public Account findAccountById(Integer id) {
            return accountDao.findAccountById(id);
        }
    
        public void saveAccount(Account account) {
            accountDao.saveAccount(account);
    
        }
    
        public void updateAccount(Account account) {
            accountDao.updateAccount(account);
        }
    
        public void deleteAccount(Integer id) {
            accountDao.deleteAccount(id);
    
        }
    
        public void transfer(String sourceName, String targetName, double money) {
            // 2.1 查询两个账户信息
            Account source = accountDao.findAccountByName(sourceName);
            Account target = accountDao.findAccountByName(targetName);
            // 2.2 转出账户减钱,转入帐户加钱
            source.setMoney(source.getMoney() - money);
            target.setMoney(target.getMoney() + money);
            // 2.3 更新操作
            accountDao.updateAccount(source);
            // 2.4 模拟转账异常
            int i = 3 / 0;
            accountDao.updateAccount(target);
        }
    
    }
    
  2. 创建 AccountService 的代理对象的工厂

    /**
     * 用于创建 Service 的代理对象的工厂
     */
    public class BeanFactory {
        // 真实对象
        private AccountService accountService;
    
        private TransactionManager transactionManager;
    
        public void setTransactionManager(TransactionManager transactionManager) {
            this.transactionManager = transactionManager;
        }
    
        public void setAccountService(AccountService accountService) {
            this.accountService = accountService;
        }
    
        /**
         * 获取代理对象
         * @return
         */
        public AccountService getAccountService(){
            AccountService accountServiceProxy = (AccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() {
                /**
                 * 添加事务的支持
                 * @param proxy
                 * @param method
                 * @param args
                 * @return
                 * @throws Throwable
                 */
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    Object returnValue = null;
                    try {
                        // 1. 开启事务
                        transactionManager.begin();
                        // 2. 执行操作
                        returnValue = method.invoke(accountService,args);
                        // 3.提交事务
                        transactionManager.commit();
                        return returnValue;
                    } catch (Exception e) {
                        // 4.回滚事务
                        transactionManager.rollback();
                        e.printStackTrace();
                        throw new RuntimeException(e);
                    } finally {
                        // 5.释放资源
                        transactionManager.release();
                    }
                }
            });
    
            return accountServiceProxy;
        }
    }
    
    
  3. 在 bean.xml 中配置对象

    <!--配置代理的 accountServiceProxy-->
    <bean id="accountServiceProxy" factory-bean="beanFactory" factory-method="getAccountService"></bean>
    
    <!--配置 BeanFactory 对象-->
    <bean id="beanFactory" class="com.zt.factory.BeanFactory">
        <property name="accountService" ref="accountService_OLD"></property>
        <property name="transactionManager" ref="transactionManager"></property>
    </bean>
    
    <!--配置 accountService_OLD 对象-->
    <bean id="accountService_OLD" class="com.zt.service.impl.AccountServiceImpl_OLD">
        <property name="accountDao" ref="accountDao"></property>
    </bean>
    
  4. 测试类

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations= {"classpath:bean.xml"})
    public class AccountService_OLDTest {
        @Autowired
        @Qualifier("accountServiceProxy")
        private AccountService accountService;
    
        @Test
        public void testTransfer(){
            accountService.transfer("aaa","bbb",500);
        }
    }
    
    

5. Spring AOP

5.1 Spring AOP 说明

我们学习 Spring AOP,就是通过配置的方式,实现了上面动态代理的功能

注意:在 Spring 中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。

5.2 Spring AOP 相关术语

  • Joinpoint(连接点)
    所谓连接点是指那些被拦截到的点。在 Spring 中,这些点指的是方法,因为 Spring 只支持方法类型的连接点(如业务层接口中的方法)

  • Pointcut(切入点)
    所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义(也就是那些被增强的 Joinpoint)

    如在动态代理中,我们没有对 test 方法进行增强,test 方法就不是切入点

    if("test".equals(method.getName())){
        return method.invoke(accountService,args);
    }
    
  • Advice(通知/增强)
    通知是指拦截到 Joinpoint 之后所要做的事情就是通知。

    通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。

    在这里插入图片描述

  • Introduction(引介)
    引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或 Field。

  • Target(目标对象)
    被代理对象

  • Weaving(织入)
    是指把增强应用到目标对象来创建新的代理对象的过程
    spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入

  • Proxy(代理)
    一个类被 AOP 织入增强后,就产生一个结果代理类

  • Aspect(切面)
    是切入点和通知(引介)的结合

5.3 学习 Spring AOP 要明确的事

  1. 开发阶段(我们做的)
    1. 编写核心业务代码
    2. 把公用代码抽取出来,制作成通知。
    3. 在配置文件中,声明切入点与通知间的关系,即切面。
  2. 运行阶段(Spring 框架完成的)
    Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。

5.4 基于 XML 的 Spring AOP 配置

5.4.1 基于 XML 的 Spring AOP 配置步骤

  1. 把通知 Bean 也交给 Spring 来管理

  2. 使用 aop:config 标签表明开始 AOP 的配置

  3. 使用 aop:aspect 标签表明配置切面

    • id 属性:是给切面提供一个唯一标识
    • ref 属性:是指定通知类 bean 的 Id
  4. 在 aop:aspect 标签的内部使用对应标签来配置通知的类型

    • aop:before:表示配置前置通知
      • method属性:用于指定类中哪个方法是前置通知
      • pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强
  5. 切入点表达式的写法

    1. 关键字

      execution(表达式)
      
    2. 标准表达式的写法

      访问修饰符  返回值  包名.类名.方法名(参数列表)
      

      例如:

      public void com.zt.service.impl.AccountServiceImpl.saveAccount()
      

      注意:访问修饰符可以省略,上面等价于

      void com.zt.service.impl.AccountServiceImpl.saveAccount()
      
    3. 返回值可以使用通配符,表示任意返回值

      * com.zt.service.impl.AccountServiceImpl.saveAccount()
      
    4. 包名可以使用通配符,表示任意包。有几级包,就需要写几个 *

      * *.*.*.*.AccountServiceImpl.saveAccount()
      
    5. 包名可以使用 … 表示当前包及其子包

      * *..AccountServiceImpl.saveAccount()
      
    6. 类名和方法名都可以使用 * 来实现通配

      * *..*.saveAccount()
      
      * *..*.*()
      
    7. 参数列表

      • 基本类型直接写名称,如:int

        * *..*.*(int)
        
      • 引用类型写包名.类名的方式,如:java.lang.String

        * *..*.*(java.lang.String)
        
      • 可以使用通配符表示任意类型,但是必须有参数

        * *..*.*(*)
        
      • 可以使用 … 表示有无参数均可,有参数可以是任意类型

        * *..*.*(..)
        
    8. 全通配表达式写法

      * *..*.*(..)
      

5.4.2 基于 XML 的 Spring AOP 配置实例

  1. 导入 jar 包

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>
    </dependencies>
    
  2. 准备必要代码

    1. 账户的业务层接口

      /**
       * 账户的业务层接口
       */
      public interface AccountService {
          /**
           * 模拟保存账户
           */
          void saveAccount();
      
          /**
           * 模拟更新账户
           * @param i
           */
          void updateAccount(int i);
      
          /**
           * 模拟删除账户
           * @return
           */
          int deleteAccount();
      }
      
    2. 账户的业务层实现类

      /**
       * 账户的业务层实现类
       */
      public class AccountServiceImpl implements AccountService {
          public void saveAccount() {
              System.out.println("执行了保存");
          }
      
          public void updateAccount(int i) {
              System.out.println("执行了更新"+i);
          }
      
          public int deleteAccount() {
              System.out.println("执行了删除");
              return 0;
          }
      }
      
    3. 记录日志的工具类,它里面提供了公共的代码

      /**
       * 记录日志的工具类,它里面提供了公共的代码
       */
      public class Logger {
      
          /**
           * 用于打印日志:计划让其在切入点方法执行之前执行(切入点方法就是业务层方法)
           */
          public  void printLog(){
              System.out.println("Logger 类中的 pringLog 方法开始记录日志了");
          }
      }
      
      
  3. 创建 Spring 的配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:aop="http://www.springframework.org/schema/aop"
           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">
        <!-- 配置 AccountService 对象-->
        <bean id="accountService" class="com.zt.service.impl.AccountServiceImpl"></bean>
    
        <!--配置 Logger 对象-->
        <bean id="logger" class="com.zt.utils.Logger"></bean>
    
        <!--配置 AOP-->
        <aop:config>
            <!--配置切面-->
            <aop:aspect id="loggerAdvice" ref="logger">
                <!-- 配置通知的类型,并且建立通知方法和切入点方法的关联-->
                <aop:before method="printLog" pointcut="execution(* com.zt.service.impl.*.*(..))"></aop:before>
            </aop:aspect>
        </aop:config>
    
    </beans>
    

5.4.3 四种常用通知类型

<!--配置 AOP-->
<aop:config>
    <!--配置切面-->
    <aop:aspect id="loggerAdvice" ref="logger">
        <!-- 配置前置通知:在切入点方法执行之前执行-->
        <aop:before method="beforePrintLog" pointcut="execution(* com.zt.service.impl.*.*(..))"></aop:before>
        <!-- 配置后置通知:在切入点方法正常执行之后执行-->
        <aop:after-returning method="afterReturningPrintLog" pointcut="execution(* com.zt.service.impl.*.*(..))"></aop:after-returning>
        <!-- 配置异常通知:在切入点方法执行产生异常之后执行-->
        <aop:after-throwing method="afterThrowingPrintLog" pointcut="execution(* com.zt.service.impl.*.*(..))"></aop:after-throwing>
        <!-- 配置最终通知:无论切入点方法是否正常执行,它都会在其后面执行-->
        <aop:after method="afterPrintLog" pointcut="execution(* com.zt.service.impl.*.*(..))"></aop:after>
    </aop:aspect>
</aop:config>

5.4.4 配置切入点表达式

<!--配置 AOP-->
<aop:config>
    <!--配置切入点表达式-->
    <aop:pointcut id="pt1" expression="execution(* com.zt.service.impl.*.*(..))"></aop:pointcut>
    <!--配置切面-->
    <aop:aspect id="loggerAdvice" ref="logger">
        <!-- 配置前置通知:在切入点方法执行之前执行-->
        <aop:before method="beforePrintLog" pointcut-ref="pt1"></aop:before>
        <!-- 配置后置通知:在切入点方法正常执行之后执行-->
        <aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>
        <!-- 配置异常通知:在切入点方法执行产生异常之后执行-->
        <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>
        <!-- 配置最终通知:无论切入点方法是否正常执行,它都会在其后面执行-->
        <aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>
    </aop:aspect>
</aop:config>

注意:配置切入点表达式要写在配置切面的前面

5.4.5 环绕通知

  1. 环绕通知的作用

    它是 Spring 框架为我们提供的一种可以在代码中手动控制增强代码什么时候执行的方式。

  2. 环绕通知的配置

    <!--配置 AOP-->
    <aop:config>
        <!--配置切入点表达式-->
        <aop:pointcut id="pt1" expression="execution(* com.zt.service.impl.*.*(..))"></aop:pointcut>
        <!--配置切面-->
        <aop:aspect id="loggerAdvice" ref="logger">
            <!--配置环绕通知-->
            <aop:around method="AroundPrintLog" pointcut-ref="pt1"></aop:around>
        </aop:aspect>
    </aop:config>
    
    /**
     * 环绕通知
     * 问题:
     *      当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。
     * 分析:
     *      通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。
     * 解决:
     *      Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。
     *      该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。
     *
     */
    public Object aroundPrintLog(ProceedingJoinPoint proceedingJoinPoint){
        Object returnValue = null;
        try {
            // 得到方法执行所需的参数
            Object[] args = proceedingJoinPoint.getArgs();
            // 前置通知
            System.out.println("前置通知");
            // 调用切入点方法
            returnValue = proceedingJoinPoint.proceed(args);
            // 后置通知
            System.out.println("后置通知");
        }catch (Throwable t){
            // 异常通知
            System.out.println("异常通知");
            throw new RuntimeException(t);
        }finally {
            // 最终通知
            System.out.println("最终通知");
        }
        return returnValue;
    }
    

5.5 基于注解的 Spring AOP 配置

  1. 导入 jar 包

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>
    </dependencies>
    
  2. 准备必要代码

    拷贝前面的基于 XML 的 Spring AOP 配置的代码即可

  3. 创建 Spring 的配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xmlns:context="http://www.springframework.org/schema/context"
           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
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd">
    
        <!-- 配置spring创建容器时要扫描的包-->
        <context:component-scan base-package="com.zt"></context:component-scan>
    
        <!-- 配置spring开启注解AOP的支持 -->
        <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    
    </beans>
    
  4. 改造日志工具类

    /**
     * 记录日志的工具类,它里面提供了公共的代码
     */
    @Component("logger")
    // 表示当前类是一个通知类
    @Aspect
    public class Logger {
    
        @Pointcut("execution(* com.zt.service.impl.*.*(..))")
        private void pt1() {}
    
        /**
         * 前置通知
         */
        @Before("pt1()")
        public void beforePrintLog() {
            System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。");
        }
    
        /**
         * 后置通知
         */
        @AfterReturning("pt1()")
        public void afterReturningPrintLog() {
            System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。");
        }
    
        /**
         * 异常通知
         */
        @AfterThrowing("pt1()")
        public void afterThrowingPrintLog() {
            System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。");
        }
    
        /**
         * 最终通知
         */
        @After("pt1()")
        public void afterPrintLog() {
            System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。");
        }
    
        /**
         * 环绕通知
         */
        //@Around("pt1()")
        public Object aroundPrintLog(ProceedingJoinPoint proceedingJoinPoint) {
            Object returnValue = null;
            try {
                // 得到方法执行所需的参数
                Object[] args = proceedingJoinPoint.getArgs();
                // 前置通知
                System.out.println("前置通知");
                // 调用切入点方法
                returnValue = proceedingJoinPoint.proceed(args);
                // 后置通知
                System.out.println("后置通知");
            } catch (Throwable t) {
                // 异常通知
                System.out.println("异常通知");
                throw new RuntimeException(t);
            } finally {
                // 最终通知
                System.out.println("最终通知");
            }
            return returnValue;
        }
    }
    
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bm1998

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值