SSM之Sping系列(五)---- Spring中AOP的引入及相关概念

AOP的引入

在上一篇文章中传送门,我们做了一个账户的 CRUD 的小案例,在讲 AOP 之前,我们先来分析该案例,一步一步地了解为什么要有 AOP 以及什么是 AOP。

案例中存在的问题

  • 回顾上篇文章中的业务层代码:
package com.cz.service.impl;

import com.cz.dao.AccountDao;
import com.cz.domain.Account;
import com.cz.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;


/**
 * 账户的业务层实现类
 */
package com.cz.service.impl;

import com.cz.dao.AccountDao;
import com.cz.domain.Account;
import com.cz.service.AccountService;

import java.util.List;


/**
 * 账户的业务层实现类
 */
public class AccountServiceImpl implements AccountService {
    
    
    private AccountDao accountDao;

    public void setAccountDao(AccountDao accountDao) {
    
    
        this.accountDao = accountDao;
    }

    public List<Account> findAllAccount() {
    
    
        return accountDao.findAllAccount();
    }

    public Account findAccountById(Integer accountId) {
    
    
        return accountDao.findAccountById(accountId);
    }

    public void saveAccount(Account account) {
    
    
        return accountDao.saveAccount(account);
    }

    public void updateAccount(Account account) {
    
    
        return accountDao.updateAccount(account);
    }

    public void removeAccount(Integer accountId) {
    
    
        return accountDao.removeAccount(accountId);
    }
}

问题就是:
  事务被自动提交了。换言之,我们使用了 connection对象的 setAutoCommit(true)此方式控制事务,如果我们每次都执行一条 sql 语句,没有问题,但是如果业务方法一次要执行多条 sql语句,这种方式就无法实现功能了。请看下面的演示:

  • 修改业务层和持久层,添加一个转账方法

业务层:

/**
 * 账户业务层接口
 */
public interface AccountService {
    
    
    /**
     * 转账
     * @param sourceName  转出账户名称
     * @param targetName  转入账户名称
     * @param money  转账金额
     */
    void transfer(String sourceName,String targetName,Float money);
}


/**
 * 账户的业务层实现类
 */
public class AccountServiceImpl implements AccountService {
    
    
    private AccountDao accountDao;
    public void transfer(String sourceName, String targetName, Float money) {
    
    
        //1.根据名称查询转出账户
        Account source = accountDao.findAccountByName(sourceName);
        //2.根据名称查询转入账户
        Account target = accountDao.findAccountByName(targetName);
        //3.转出账户减钱
        source.setMoney(source.getMoney()-money);
        //4.转入账户加钱
        target.setMoney(target.getMoney()+money);
        //5.更新转出账户
        accountDao.updateAccount(source);
        int i=1/0; //模拟转账异常
        //6.更新转入账户
        accountDao.updateAccount(target);
    }
}

持久层:

/**
 * 账户的持久层接口
 */
public interface AccountDao {
    
    
    /**
     * 根据名称查询账户
     * @param accountName
     * @return 如果有唯一结果,就返回。如果没有结果就返回null;
     *          如果结果集超过一个就抛异常
     */
    Account findAccountByName(String accountName);
}


/**
 * 账户的持久层实现类
 */

public class AccountDaoImpl implements AccountDao {
    
    
    public Account findAccountByName(String accountName) {
    
    
        try{
    
    
            List<Account> accounts =  runner.query("select * from account where name = ?",new BeanListHandler<Account>(Account.class),accountName);
            if (accounts == null || accounts.size() == 0){
    
    
                return null;
            }
            if (accounts.size() > 1){
    
    
                throw new RuntimeException("结果集不为1,数据异常");
            }
            return accounts.get(0);
        }catch (Exception e){
    
    
            throw new RuntimeException(e);
        }
    }
}

实现类:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {
    
    

    @Autowired
    private AccountService accountService;

    @Test
    public void testTransfer(){
    
    
        accountService.transfer("张三","李四",200f);
    }
}

在这里插入图片描述
在这里插入图片描述

由于执行有异常,转账失败。但是因为每次执行持久层方法都是独立事务,导致无法实现事务控制 ( 不符合事务的一致性

解决办法

为了解决这个问题,我们可以让业务层来控制事务的提交和回滚

首先我们先对业务层的实现类的代码进行分析:
在这里插入图片描述

  • 创建工具类ConnectionUtils,该类用于获取和线程绑定的 Connection 对象,保证每个线程中只能有一个控制事务的 Connection对象
package com.cz.utils;

import javafx.scene.chart.PieChart;

import javax.sql.DataSource;
import java.sql.Connection;

/**
 * 连接的工具类,它用于从数据源中获取一个连接,并且实现和线程的绑定
 */
public class ConnectionUtils {
    
    

    private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
    private DataSource dataSource;

    public void setDataSource(DataSource dataSource) {
    
    
        this.dataSource = dataSource;
    }

    /**
     * 获取当前线程上的连接
     * @return
     */
    public Connection getThreadConnection(){
    
    
        try {
    
    
            //1.先从ThredLocal获取
            Connection conn = tl.get();
            //2.判断当前线程上是否有连接
            if (conn == null){
    
    
                //3.从数据源上获取一个连接,并且存入ThreadLocal中
                conn = dataSource.getConnection();
                // 与当前线程绑定
                tl.set(conn);
            }
            //4.返回当前线程上的连接
            return conn;
        }catch (Exception e){
    
    
            throw new RuntimeException(e);
        }
    }

    /**
     * 将连接和线程解绑
     */
    public void removeConnection() {
    
    
        tl.remove();
    }
}

  • 创建事务控制类TransactionManager
package com.cz.utils;

import java.sql.Connection;

/**
 * 和事务管理相关的工具类,它包含了开启事务,提交事务,回滚事务和释放连接
 */
public class TransactionManager {
    
    

    private ConnectionUtils connectionUtils;

    public void setConnectionUtils(ConnectionUtils connectionUtils) {
    
    
        this.connectionUtils = connectionUtils;
    }

    /**
     * 开启事务
     */
    public void beginTransaction(){
    
    
        try{
    
    
            connectionUtils.getThreadConnection().setAutoCommit(false);
        }catch (Exception e){
    
    
            e.printStackTrace();
        }
    }

    /**
     * 提交事务
     */
    public void commit(){
    
    
        try{
    
    
            connectionUtils.getThreadConnection().commit();
        }catch (Exception e){
    
    
            e.printStackTrace();
        }
    }

    /**
     * 回滚事务
     */
    public void rollback(){
    
    
        try{
    
    
            connectionUtils.getThreadConnection().rollback();
        }catch (Exception e){
    
    
            e.printStackTrace();
        }
    }

    /**
     * 释放连接
     */
    public void release(){
    
    
        try{
    
    
            //将连接还回连接池
            connectionUtils.getThreadConnection().close();
            //将连接与线程解绑,防止下次使用报错。因为连接被关闭后不能再调用
            connectionUtils.removeConnection();
        }catch (Exception e){
    
    
            e.printStackTrace();
        }
    }
}

要注意release()方法中,因为使用连接池,所以调用 Connection对象的 close()方法后,是将连接归还到连接池中,所以要把连接与绑定的线程解绑,不然下次从线程池中取出该线程,并获取绑定的连接进行使用时,由于连接被调用过 close()方法,会抛出异常 You can't operate on a closed Connection问题存在于 WEB 工程中

  • 修改业务层代码,为所有方法加上事务管理
package com.cz.service.impl;

import com.cz.dao.AccountDao;
import com.cz.domain.Account;
import com.cz.service.AccountService;
import com.cz.utils.TransactionManager;

import java.util.List;


/**
 * 账户的业务层实现类
 * 事务控制都在业务层
 */
public class AccountServiceImpl implements AccountService {
    
    
    private AccountDao accountDao;
    private TransactionManager tsManager;

    public void setTsManager(TransactionManager tsManager) {
    
    
        this.tsManager = tsManager;
    }

    public void setAccountDao(AccountDao accountDao) {
    
    
        this.accountDao = accountDao;
    }

    public List<Account> findAllAccount() {
    
    
        try{
    
    
            //1.开启事务
            tsManager.beginTransaction();
            //2.执行操作
            List<Account> accounts = accountDao.findAllAccount();
            //3.提交事务
            tsManager.commit();
            //4.返回结果
            return accounts;
        }catch (Exception e){
    
    
            //5.回滚操作
            tsManager.rollback();
            throw new RuntimeException(e);
        }finally {
    
    
            //6.释放连接
            tsManager.release();
        }
    }

    public Account findAccountById(Integer accountId) {
    
    
        try{
    
    
            //1.开启事务
            tsManager.beginTransaction();
            //2.执行操作
            Account account = accountDao.findAccountById(accountId);
            //3.提交事务
            tsManager.commit();
            //4.返回结果
            return account;
        }catch (Exception e){
    
    
            //5.回滚操作
            tsManager.rollback();
            throw new RuntimeException(e);
        }finally {
    
    
            //6.释放连接
            tsManager.release();
        }

    }

    public void saveAccount(Account account) {
    
    
        try{
    
    
            //1.开启事务
            tsManager.beginTransaction();
            //2.执行操作
            accountDao.saveAccount(account);
            //3.提交事务
            tsManager.commit();
        }catch (Exception e){
    
    
            //4.回滚操作
            tsManager.rollback();
            throw new RuntimeException(e);
        }finally {
    
    
            //5.释放连接
            tsManager.release();
        }
    }

    public void updateAccount(Account account) {
    
    
        try{
    
    
            //1.开启事务
            tsManager.beginTransaction();
            //2.执行操作
            accountDao.updateAccount(account);
            //3.提交事务
            tsManager.commit();
        }catch (Exception e){
    
    
            //4.回滚操作
            tsManager.rollback();
            throw new RuntimeException(e);
        }finally {
    
    
            //5.释放连接
            tsManager.release();
        }
    }

    public void removeAccount(Integer accountId) {
    
    
        try{
    
    
            //1.开启事务
            tsManager.beginTransaction();
            //2.执行操作
            accountDao.removeAccount(accountId);
            //3.提交事务
            tsManager.commit();
        }catch (Exception e){
    
    
            //4.回滚操作
            tsManager.rollback();
            throw new RuntimeException(e);
        }finally {
    
    
            //5.释放连接
            tsManager.release();
        }

    }

    public void transfer(String sourceName, String targetName, Float money) {
    
    
        try{
    
    
            //1.开启事务
            tsManager.beginTransaction();
            //2.执行操作

            //2.1根据名称查询转出账户
            Account source = accountDao.findAccountByName(sourceName);
            //2.2根据名称查询转入账户
            Account target = accountDao.findAccountByName(targetName);
            //2.3转出账户减钱
            source.setMoney(source.getMoney()-money);
            //2.4转入账户加钱
            target.setMoney(target.getMoney()+money);
            //2.5更新转出账户
            accountDao.updateAccount(source);
            int i=1/0; //模拟转账异常
            //2.6更新转入账户
            accountDao.updateAccount(target);

            //3.提交事务
            tsManager.commit();
        }catch (Exception e){
    
    
            //4.回滚操作
            tsManager.rollback();
        }finally {
    
    
            //5.释放连接
            tsManager.release();
        }
    }
}
  • 修改持久层代码,因为我们现在的 Connection 对象是由ConnectionUtils 工具类来提供的(与线程绑定),而不是让 QueryRunner 对象自动去连接池中取(没有与线程绑定),所以QueryRunner对象不再需要注入数据源了。
package com.cz.dao.impl;

import com.cz.dao.AccountDao;
import com.cz.domain.Account;
import com.cz.utils.ConnectionUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;

import java.util.List;


/**
 * 账户的持久层实现类
 */

public class AccountDaoImpl implements AccountDao {
    
    

    private QueryRunner runner;
    private ConnectionUtils connectionUtils;

    public void setConnectionUtils(ConnectionUtils connectionUtils) {
    
    
        this.connectionUtils = connectionUtils;
    }

    public void setRunner(QueryRunner runner) {
    
    
        this.runner = runner;
    }

    public List<Account> findAllAccount() {
    
    
        try{
    
    
            return runner.query(connectionUtils.getThreadConnection(),"select * from account",new BeanListHandler<Account>(Account.class));
        }catch (Exception e){
    
    
            throw new RuntimeException(e);
        }
    }

    public Account findAccountById(Integer accountId) {
    
    
        try{
    
    
            return runner.query(connectionUtils.getThreadConnection(),"select * from account where id = ?",new BeanHandler<Account>(Account.class),accountId);
        }catch (Exception e){
    
    
            throw new RuntimeException(e);
        }
    }

    public int saveAccount(Account account) {
    
    
        try{
    
    
            return runner.update(connectionUtils.getThreadConnection(),"insert into account(name,money)values(?,? )",account.getName(),account.getMoney());
        }catch (Exception e){
    
    
            throw new RuntimeException(e);
        }
    }

    public int updateAccount(Account account) {
    
    
        try{
    
    
            return runner.update(connectionUtils.getThreadConnection(),"update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
        }catch (Exception e){
    
    
            throw new RuntimeException(e);
        }
    }

    public int removeAccount(Integer accountId) {
    
    
        try{
    
    
            return runner.update(connectionUtils.getThreadConnection(),"delete from account  where id=?",accountId);
        }catch (Exception e){
    
    
            throw new RuntimeException(e);
        }
    }

    public Account findAccountByName(String accountName) {
    
    
        try{
    
    
            List<Account> accounts =  runner.query(connectionUtils.getThreadConnection(),"select * from account where name = ?",new BeanListHandler<Account>(Account.class),accountName);
            if (accounts == null || accounts.size() == 0){
    
    
                return null;
            }
            if (accounts.size() > 1){
    
    
                throw new RuntimeException("结果集不为1,数据异常");
            }
            return accounts.get(0);
        }catch (Exception e){
    
    
            throw new RuntimeException(e);
        }
    }
}
  • 编写配置文件,管理依赖关系
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       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">

    <!--配置service-->
    <bean id="accountService" class="com.cz.service.impl.AccountServiceImpl">
        <!--注入dao-->
        <property name="accountDao" ref="accountDao"></property>
        <!-- 注入事务管理器-->
        <property name="tsManager" ref="tsManager"/>
    </bean>
    <!-- 配置dao对象-->
    <bean id="accountDao" class="com.cz.dao.impl.AccountDaoImpl">
        <!--注入QueryRunner-->
        <property name="runner" ref="runner"></property>
        <!-- 注入ConnectionUtils-->
        <property name="connectionUtils" ref="connectionUtils"/>
    </bean>
    <!-- 配置QueryRunner-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean>

    <!-- 配置数据源-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--注入连接数据库的必备信息-->
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring?serverTimezone=UTC&amp;characterEncoding=utf-8"/>
        <property name="user" value="root"/>
        <property name="password" value="7107883"/>
    </bean>

    <!-- 配置 Connection 工具类 ConnectionUtils-->
    <bean id="connectionUtils" class="com.cz.utils.ConnectionUtils">
        <!-- 注入数据源-->
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 配置事务管理器 -->
    <bean id="tsManager" class="com.cz.utils.TransactionManager">
        <!-- 注入ConnectionUtils-->
        <property name="connectionUtils" ref="connectionUtils"/>
    </bean>
</beans>

新的问题

  通过对业务层加上事务控制,此时的转账方法已经可以正常运行,但是由于我们的业务层每个方法都加上了事务控制,所以显得业务层的方法特别臃肿业务核心代码可能就 accountDao.removeAccount(accountId); 这么一句,其余的都是和事务有关,这样也就造成了业务层方法的耦合度很高。

  如果此时我们的 TransactionManager 类中的方法名进行了更改,那么我们需要在每处调用的地方都进行修改,这样不利于我们后期的开发。

新问题的解决

那么如何解决呢?这里我们可以使用动态代理,把事务的控制交给代理对象,所以下面我们先来看看关于动态代理的基本使用。

动态代理介绍

之前在讲mybatis的入门案例的时候也给大家贴过两篇其他大佬写的动态代理,下面我们简单的了解一下,想深入的朋友可以去看看那两篇文章。

什么是动态代理

简单来说就是使用反射动态创建代理对象,使用代理对象来代替目标对象,因此达到增强目标对象的功能

动态代理的特点

字节码随用随创建,随用随加载。它与静态代理的区别也在于此,因为静态代理是字节码一上来就创建好,并完成加载。装饰者模式就是静态代理的一种体现。

动态代理的常用两种方式

基于接口的动态代理
基于子类的动态代理

基于接口的动态代理

提供者:JDK 官方的 Proxy 类。
要求:被代理类最少实现一个接口。

  • 在这里我们使用厂家的例子:以前厂家不仅要对产品进行售后处理,还得负责销售的环节,把产品卖给客户。但是随着时间的推移,出现了一个新的角色:代理商,这时候厂家只需要负责对产品的售后处理,销售的事情交给代理商即可。如果客户需要售后维修,那么只需要将产品交给代理商,由代理商送往厂家进行维修。如下图:
    在这里插入图片描述

  • 定义接口,表示厂家需要具备的功能(销售以及售后)

package com.cz.proxy;

/**
 * 生产厂家需要实现的接口
 */
public interface IProducer {
    
    

    /**
     * 销售
     * @param money
     */
     void saleProduct(float money);

    /**
     * 售后
     * @param money
     */
     void afterService(float money);
}
  • 定义厂家类
package com.cz.proxy;

/**
 * 一个生产者
 */
public class Producer implements IProducer{
    
    

    /**
     * 销售
     * @param money
     */
    public void saleProduct(float money){
    
    
        System.out.println("销售产品,并拿到钱:"  +money);
    }

    /**
     * 售后
     * @param money
     */
    public void afterService(float money){
    
    
        System.out.println("提供售后服务,并拿到钱:"+money);
    }
}

  • 定义测试类,进行动态代理
package com.cz.proxy;

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

/**
 * 消费者
 */
public class Client {
    
    

    public static void main(String[] args) {
    
    
        //厂家
        final Producer producer = new Producer();

        // 该对象相当于代理商
        IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
                producer.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;
                        //1.获取方法执行的参数
                        Float money = (Float)args[0];
                        //2.判断当前方法是不是销售
                        if ("saleProduct".equals(method.getName())){
    
    
                            System.out.println("消费者花了 " + money + "元购买商品...");
                            // 对参数进行增强,代理商需要抽取 2 成利润
                            returnValue = method.invoke(producer,money*0.8f);
                        }
                        return returnValue;
                    }
                });
        proxyProducer.saleProduct(10000f);
    }
}

  • 运行结果如图

在这里插入图片描述

  • 使用 Proxy.newProxyInstance()创建代理对象,参数如下:

    • ClassLoader:类加载器,用于加载代理对象字节码,和被代理对象使用相同的类加载器。固定写法:被代理对象.getClass().getClassLoader()
    • Class[]:字节码数组,用于让代理对象和被代理对象具有相同的接口方法。固定写法:被代理对象.getClass().getInterfaces()
    • InvocationHandler: 处理器,用于提供增强的代码,一般都是编写一个该接口的匿名内部类。此接口的实现类都是谁用谁写。
  • invoke()方法用于拦截被代理对象的方法,执行被代理对象的任何方法都会被该方法拦截到,参数如下:

    • Object proxy :代理对象的引用
    • Method method:当前执行的方法
    • Object[] args :当前执行方法所需的参数
    • 返回值类型与被代理对象方法一致
  • 匿名内部类访问外部方法的成员变量时都要求外部成员变量添加final修饰符,final 修饰变量代表该变量只能被初始化一次,以后不能被修改。参考链接:
    匿名内部类如何访问外部类的成员变量
    匿名内部类访问方法成员变量需要加final的原因及证明

基于子类的动态代理

要求:被代理对象不能是最终类

  • 还是使用上面的例子
  • 基于子类的动态代理是使用第三方库 CGLIB 提供的 Enhancer类,要求被代理类不能是被final 修饰的类(最终类)
  • 导入第三方库依赖CGLIB或者手动导入jar 包 cglib-2.1.3.jarasm.jar
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.1_3</version>
</dependency>

老样子,报错的看这两篇文章传送门1
传送门2

  • 厂家类无需实现接口
package com.cz.cglib;

/**
 * 一个生产者
 */
public class Producer{
    
    

    /**
     * 销售
     * @param money
     */
    public void saleProduct(float money){
    
    
        System.out.println("销售产品,厂家拿到钱:"  +money);
    }

    /**
     * 售后
     * @param money
     */
    public void afterService(float money){
    
    
        System.out.println("提供售后服务,并拿到钱:"+money);
    }
}
  • 编写测试类
package com.cz.cglib;

import com.cz.proxy.IProducer;
import com.cz.proxy.Producer;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

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

/**
 * 消费者
 */
public class Client {
    
    

    public static void main(String[] args) {
    
    
        //厂家
        final Producer producer = new Producer();

        Producer cglibProducer = (Producer) Enhancer.create(producer.getClass(),
                new MethodInterceptor() {
    
    
                    /**
                     *执行被代理对象的任何方法,都会经过该方法。在此方法内部就可以对被代理对象的任何方法进行增强。
                     * @param proxy
                     * @param method
                     * @param args
                     * 前三个和基于接口的动态代理是一样的。
                     * @param methodProxy 当前执行方法的代理对象。
                     * @return 当前执行方法的返回值
                     * @throws Throwable
                     */
                    @Override
                    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    
    
                        //提供增强代码
                        Object returnValue = null;
                        //1.获取方法执行的参数
                        Float money = (Float)args[0];
                        //2.判断当前方法是不是销售
                        if ("saleProduct".equals(method.getName())){
    
    
                            System.out.println("消费者花了 " + money + "元购买商品...");
                            // 对参数进行增强,代理商需要抽取 2 成利润
                            returnValue = method.invoke(producer,money*0.8f);
                        }
                        return returnValue;
                    }
                });
        cglibProducer.saleProduct(120000f);
    }
}
  • 使用Enhancer.create()创建代理对象,参数如下:
    • Class : 字节码,用于指定被代理对象的字节码。固定写法:被代理对象.getClass()
    • Callback : 回调接口,用于提供增强的代码,一般都是编写该接口的子接口实现类 MethodInterceptor
  • intercept() 方法用于拦截被代理对象的方法,执行被代理对象的任何方法都会被该方法拦截到,参数如下:
    • Object proxy :代理对象的引用
    • Method method :当前执行的方法
    • Object[] args:当前执行方法所需的参数
    • MethodProxy methodProxy:当前执行方法的代理对象
    • 返回值类型与被代理对象方法一致

动态代理的基本使用就讲到这了,接下来我们回归正题。如何解决问题:

  • 修改业务层代码,将其恢复原样,不再添加事务的控制代码
  • 创建代理工厂,用于获取 service 的代理对象
package com.cz.factory;

import com.cz.service.AccountService;
import com.cz.utils.TransactionManager;

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

/**
 * 用于创建Service的代理对象的工厂
 */
public class BeanFactory {
    
    

    private AccountService accountService;
    private TransactionManager tsManager;

    public void setTsManager(TransactionManager tsManager) {
    
    
        this.tsManager = tsManager;
    }

    public final void setAccountService(AccountService accountService) {
    
    
        this.accountService = accountService;
    }

    /**
     * 获取Service代理对象
     */
    public AccountService getAccountService() {
    
    
        return  (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 rtValue = null;
                        try {
    
    
                            //1.开启事务
                            tsManager.beginTransaction();
                            //2.执行操作
                            rtValue = method.invoke(accountService, args);
                            //3.提交事务
                            tsManager.commit();
                            //4.返回结果
                            return rtValue;
                        } catch (Exception e) {
    
    
                            //5.回滚操作
                            tsManager.rollback();
                            throw new RuntimeException(e);
                        } finally {
    
    
                            //6.释放连接
                            tsManager.release();
                        }
                    }
                });
    }
}

  • 修改配置文件
    <!-- 配置代理的service -->
    <bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"></bean>
    <!-- 配置 BeanFactory -->
    <bean id="beanFactory" class="com.cz.factory.BeanFactory">
        <!-- 注入 service -->
        <property name="accountService" ref="accountService"/>
        <!-- 注入事务管理器 -->
        <property name="tsManager" ref="tsManager"/>
    </bean>

    <!--配置service-->
    <bean id="accountService" class="com.cz.service.impl.AccountServiceImpl">
        <!--注入dao-->
        <property name="accountDao" ref="accountDao"></property>
    </bean>

到这里的时候,改造已经完成,通过代理对象一样可以控制住事务,同时业务层不再需要编写一堆的重复事务控制代码。但是如果每次管理事务的时候,我们都要像这样自己创建代理对象,那不是也挺麻烦的吗?有没有更好的方法呢?这时候就需要 AOP 的出场了。

Spring 中的 AOP

什么是 AOP

  • AOP 即面向切面编程,英文全称为 Aspect Oriented Programming
    • 简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。
  • 作用
    • 在程序运行期间,不修改源码对已有方法进行增强
  • 优势
    • 减少重复代码
    • 提高开发效率
    • 维护方便

AOP 中的术语

Joinpoint (连接点)

  • 所谓连接点是指那些被拦截到的方法,在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的连接点
  • 比如说,我们的业务层中的方法都是连接点,因为我们对业务层中的所有方法都进行了拦截

Pointcut (切入点)

  • 所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义,简单来说就是被增强的方法

  • 比如说,我们在业务层新增了一个方法test() ,但是在代理工厂中,我们不对该方法进行增强,而是直接放行,那么此时的test()就不是切入点,仅仅是一个连接点,而其他的方法都被事务管理,也就是切入点
    在这里插入图片描述

Advice (通知/ 增强)

  • 所谓通知是指拦截到方法之后所要做的事情,简单来说就是对切入点进行的增强操作
  • 通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知
    在这里插入图片描述

Introduction (引介)

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

Target (目标对象)

  • 被代理的对象

Weaving (织入)

  • 是指把增强应用到目标对象来创建新的代理对象的过程

Proxy (代理)

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

Aspect (切面)

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

学习 spring 中的 AOP 要明确的事

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

这篇文章就到这了,有点长,能看完的都很了不起。下一篇文章将讲解Spring 中基于注解的 AOP 配置

猜你喜欢

转载自blog.csdn.net/weixin_43844418/article/details/113803462