spring(三)之动态代理的案例到AOP

目录

  1. 完善我们的account案例
  2. 分析案例中的问题
  3. 回顾动态代理
  4. 动态代理的另一种实现方式
  5. 解决案例中的问题
  6. AOP中的概念
  7. spring中AOP的相关术语
  8. spring中基于XML和注解的AOP配置

1 完善account案例

IAccountService.java业务层接口上添加转账方法

public interface IAccountService {
   .....................

    /**
     * 转账
     * @param sourceName        转出账户名称
     * @param targetName        转入账户名称
     * @param money             转账金额
     */
    void transfer(String sourceName, String targetName, Float money);

 
}

在IAccountDao.java持久层接口上添加查询账户的方法

/**
 * 账户的持久层接口
 */
public interface IAccountDao {

  ........................

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

在持久层实现类上实现添加根据名称查找账户的方法

/**
 * 账户的持久层实现类
 */
public class AccountDaoImpl implements IAccountDao {

  .............................................


    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("结果集不唯一,数据有问题");
            }
            return accounts.get(0);
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

在业务层实现类上实现转账方法

/**
 * 账户的业务层实现类
 *
 * 事务控制应该都是在业务层
 */
public class AccountServiceImpl implements IAccountService{

    ...................................


    public void transfer(String sourceName, String targetName, Float money) {
        System.out.println("transfer....");
            //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);

            //2.6更新转入账户
            accountDao.updateAccount(target);
    }
}

测试

 @Test
    public  void testTransfer(){
        as.transfer("aaa","bbb",100f);
    }

2 存在问题:

若在业务层实现类上添加异常,转账方法上添加异常

  //2.5更新转出账户
    accountDao.updateAccount(source);

      int i=1/0;

    //2.6更新转入账户
    accountDao.updateAccount(target);

则会违背一致性,账户转出钱,但未转入别的账户

分析事务的问题并编写ConnectionUtils

目前QueryRunner的配置
每次操作数据库都会创建一个新的QueryRunner,并会从数据源中拿出一个连接
QueryRunner图
在这里插入图片描述

事务控制
在这里插入图片描述
每一次操作数据库,都会获得一个新的QueryRunner,操作一个新的连接
所以报错之后,后面的数据库连接的事务没法提交,但前面的连接的事务已经提交
改进目标:使这些数据库操作属于同一个连接

事务控制应该都在业务层

新建utils.ConnectionUtils类

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

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

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

    //不能自己new创建,必须等着spring来注入,提供一个set方法
    private DataSource dataSource;

    //实现spring的注入
    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    /**
     * 获取当前线程上的连接
     * @return
     */
    public Connection getThreadConnection() {
        try{
            //1.先从ThreadLocal上获取
            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

包含了开启事务,提交事务,回滚事务和释放连接
操作之前必须有线程上的connection,从ConnetionUtils对象中获取

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

    private ConnectionUtils connectionUtils;

    //提供一个set方法,等spring注入
    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();
        }
    }
}

编写业务层和持久层的代码控制,并配置spring的IOC

业务层

改造业务层的实现类
需要用到TransactionManager类的方法
添加一个成员变量

private TransactionManager txManager;

不能自己new,等着spring的依赖注入添加一个set方法

public void setTxManager(TransactionManager txManager) {
        this.txManager = txManager;
    }

在每个方法上加上事务控制

try{
    //1开启事务
     txManager.beginTransaction();
    //2执行操作
	不同操作不同
    //3提交事务
     	txManager.commit();
   //4返回结果
	不同操作不同
}catch(Exception e){
   //5 回滚操作,抛出异常
            txManager.rollback();
           throw new RuntimeException(e);
}finally{
  //6 释放连接
         txManager.release();
}
/**
 * 账户的业务层实现类
 *
 * 事务控制应该都是在业务层
 */
public class AccountServiceImpl_OLD implements IAccountService{

    private IAccountDao accountDao;
    private TransactionManager txManager;

    public void setTxManager(TransactionManager txManager) {
        this.txManager = txManager;
    }

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

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

    }

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

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

    }

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

    }

    @Override
    public void deleteAccount(Integer acccountId) {
        try {
            //1.开启事务
            txManager.beginTransaction();
            //2.执行操作
            accountDao.deleteAccount(acccountId);
            //3.提交事务
            txManager.commit();
        }catch (Exception e){
            //4.回滚操作
            txManager.rollback();
        }finally {
            //5.释放连接
            txManager.release();
        }

    }

    @Override
    public void transfer(String sourceName, String targetName, Float money) {
        try {
            //1.开启事务
            txManager.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);

          

            //2.6更新转入账户
            accountDao.updateAccount(target);
            //3.提交事务
            txManager.commit();

        }catch (Exception e){
            //4.回滚操作
            txManager.rollback();
            e.printStackTrace();
        }finally {
            //5.释放连接
            txManager.release();
        }


    }
}

持久层

Dao实现类中执行方法时,给QueryRunner注入了connection之后,就会从连接取一个
问题
在这里插入图片描述

我不希望QueryRunner中从连接里去取,不再注入connection

<!--配置QueryRunner-->
 <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean>

但不提供connction对象的时候,Dao实现类里的操作将没有Connection

在Dao实现类里加一个新的成员变量

private ConnectionUtils connectionUtils;

并且提供set方法,让spring可以注入

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

在Dao实现类操作中选择第一个参数是带有连接对象的方法

eg:
runner.query(connectionUtils.getThreadConnection(),"select * from account",new BeanListHandler<Account>(Account.class));
/**
 * 账户的持久层实现类
 */
public class AccountDaoImpl implements IAccountDao {

    private QueryRunner runner;
    private ConnectionUtils connectionUtils;

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

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


    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 void saveAccount(Account account) {
        try{
            runner.update(connectionUtils.getThreadConnection(),"insert into account(name,money)values(?,?)",account.getName(),account.getMoney());
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    public void updateAccount(Account account) {
        try{
            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 void deleteAccount(Integer accountId) {
        try{
            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("结果集不唯一,数据有问题");
            }
            return accounts.get(0);
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

最后把新建的依赖注入好

首先配置Connection的工具类,注入数据源

<!-- 配置数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--连接数据库的必备信息-->
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/springtest"></property>
        <property name="user" value="root"></property>
        <property name="password" value="xgh961120"></property>
    </bean>

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

再配置Dao实现类,注入Connection的工具类

   <!--配置QueryRunner-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean>

<!--配置Dao对象-->
    <bean id="accountDao" class="com.tju.dao.impl.AccountDaoImpl">
        <!-- 注入QueryRunner -->
        <property name="runner" ref="runner"></property>
        <!-- 注入ConnectionUtils -->
        <property name="connectionUtils" ref="connectionUtils"></property>
    </bean>

最后配置业务层实现类业务层还需要事务管理器TransactionManager
1配置事务管理器,并注入成员变量ConnectionUtils

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

2 给service实现类注入事务管理器

 <!-- 配置Service -->
    <bean id="accountService" class="com.tju.service.impl.AccountServiceImpl">
        <!-- 注入dao -->
        <property name="accountDao" ref="accountDao"></property>
      <!-- 注入事务管理器 -->
        <property name="txManager" ref="txManage"></property>
    </bean>

测试转账并分析案例中的问题

 @Test
    public  void testTransfer(){
        as.transfer("aaa","bbb",100f);
    }

测试成功,满足事务的一致性

对service实现类进行进一步改造

原来的service实现类对TransactionManager类有方法依赖,TransactionManager里的方法变化,service实现类的调用也必须改变
在删去冗余后

/**
 * 账户的业务层实现类
 *
 * 事务控制应该都是在业务层
 */
public class AccountServiceImpl implements IAccountService{

    private IAccountDao accountDao;

    public void setAccountDao(IAccountDao 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) {
        accountDao.saveAccount(account);
    }


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


    public void deleteAccount(Integer acccountId) {
        accountDao.deleteAccount(acccountId);
    }


    public void transfer(String sourceName, String targetName, Float money) {
        System.out.println("transfer....");
            //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 代理分析

动态代理解决了什么问题?

举一个例子
原来我们买电脑,直接去生产厂家买电脑,生产厂家提供销售和售后

现在我们买电脑,去经销商那里买电脑,经销商就是生产厂家的一个代理
经销商对原来生产厂家的销售和售后进行改造,原来生产厂家卖4000,经销商卖7000

代理在这里插入图片描述

IProducer.java接口

/**
 * 对生产厂家要求的接口(代理商的要求,要求生产厂家必须提供销售和售后)
 */
public interface IProducer {


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

    /**
     * 售后
     * @param money
     */
    void afterSerivce(float money);

}

Producer实现类

/**
 * 一个生产者(生产厂家)
 */
public class Producer implements IProducer {

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

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

    }

}

Client.java(消费者)

//原来的模式
/**
 * 模拟一个消费者
 */
public class Client {
    public static void main(String[] args) {
        //原来的模式直接去生产厂商去买
        //客户就看中这个生产厂商的东西
        Producer producer = new Producer();
        //客户交钱拿货
        producer.saleProduct(10000f);
        

        
    }
}

结果:
销售产品,工厂并拿到钱10000.0

动态代理:

  • 特点:字节码随用随创建,随用随加载
  • 作用:不修改源码的基础上对方法增强(一会儿对Producer.java中的代码增强)
  • 分类:
    基于接口的动态代理
    基于子类的动态代理

基于接口的动态代理

  • 设计的类:Proxy
  • 提供者:JDK官方
  • 如何创建代理对象
    • 使用Proxy类中的newProxyInstance方法
  • 创建代理对象的要求:
    • 被代理类最少实现一个接口,如果没有则不能使用
newProxyInstance方法
  • newProxyInstance方法的参数
    • ClassLoader:类加载器
      • 它是用于加载代理对象字节码的,写的是被代理对象类的加载器(和被代理对象使用相同的类加载器)
      • 固定写法
        代理谁,就写谁的.getClass().getClassLoader()
    • Class[]:字节码数组
      • 它是用于让代理对象和被代理对象有相同的方法(只要两个都实现同一个接口,两个对象都会有接口的方法)
      • 固定写法
        代理谁,就写谁的.getclass().getInterfaces()
    • InvocationHandler:用于提供增强的代码
      • 它是让我们写如何代理,我们一般是写一个该接口的实现类,通常情况下都是匿名内部类(但不是必须的)
      • 此接口的实现类都是谁用谁写

实现InvocationHandler接口需要重写一个方法

public Object invoke(Object proxy, Method method, Object[] args)

  • 作用:执行被代理对象的任何接口方法都会经过该方法(被代理对象producer中的IProducer接口的方法,都会经过该方法)
  • 该方法就会有拦截的功能
  • 参数介绍:
    @param proxy 指的是代理对象的引用
    @param method 当前执行的方法
    @param args 当前执行方法所需的参数
    @return 和被代理对象方法有相同的返回值

再重写此方法时通常会用到反射中的invoke方法

  • invoke(Object obj, Object… args)
  • Object obj 指的是谁的方法,被代理对象(的方法):producer
  • Object… args 方法的参数
  • 匿名内部类访问外部成员变量的时候,用final修饰
  • 返回值是Object类型
  • eg: method.invoke(producer,args);

模拟动态模式的消费者

/**
 * 模拟一个消费者
 */
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() {
                   
                    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())){
                            //经销商进行代理,拿走2000
	         
  	    /**
                         * invoke(Object obj, Object... args)
                         * Object obj 指的是谁的方法,被代理对象(的方法):producer
                         * 匿名内部类访问外部成员变量的时候,用final修饰
                         *Object... args
                         * 方法的参数
                         * 返回值是Object类型
                         * method.invoke(producer,args);
                         */


                            returnValue = method.invoke(producer,money*0.8f);
                        }


                        return returnValue;
                      

                    }
                });
        proxyProducer.saleProduct(10000f);
      
    }
}

结果
销售产品,工厂并拿到钱8000.0
经过经销商的二次加工 ,也就是代理对象被代理对象的方法进行了改造

问题:

限制当我们的被代理对象不实现任何接口的时候会发生异常
Producer implements IProducer才会成功
想要代理一个普通的Java类

一个生产者(生产厂家)没有实现接口

public class Producer {

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

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

    }

}

解决:基于子类的动态代理

在pom.xml中导入依赖

<dependencies>
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>2.1_3</version>
    </dependency>
</dependencies>

基于子类的动态代理

  • 设计的类:Enhancer
  • 提供者:第三方cglib库
  • 如何创建代理对象
    • 使用Enhancer类中的create方法
  • 创建代理对象的要求:
    • 被代理类不能是最终类
    • create方法的参数
      • class:用于指定一个字节码
        • 它使用于指定被代理对象的字节码
        • 固定写法
          代理谁,就写谁的.getClass()
      • Callback:用于提供增强的代码
        • 它是让我们写如何代理,我们一般是写一个该接口的实现类,通常情况下都是匿名内部类(但不是必须的)
        • 此接口的实现类都是谁用谁写
        • 我们一般写的都是该接口的子接口的实现类:MethodInterceptor(子接口,方法拦截) public interface MethodInterceptor extends Callback
        • 实现子接口的话要重写一个方法
          public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable
          • 执行被代理对象的任何方法都会经过该方法
            前三个参数跟刚才基于接口动态代理的参数一样

          • @param proxy:指的是代理对象的引用

          • @param method:当前执行的方法

          • @param args:当前执行方法所需的参数

          • 以上三个参数和基于接口的动态代理中invoke方法的参数是一致的

          • @param methodProxy:当前执行方法的代理对象

          • @return Object 和被代理对象执行方法中的返回值是一样的

public class Client {
    public static void main(String[] args) {

        final Producer producer = new Producer();


        /**
         * 基于子类的动态代理
         *      设计的类:Enhancer
         *      提供者:第三方cglib库
         *  如何创建代理对象
         *      使用Enhancer类中的create方法
         *  创建代理对象的要求:
         *      被代理类不能是最终类
         *   create方法的参数
         *      class:用于指定一个字节码
         *          它使用于指定被代理对象的字节码
         *          固定写法
         *          代理谁,就写谁的.getClass()
         *      Callback:用于提供增强的代码
         *          它是让我们写如何代理,我们一般是写一个该接口的实现类,通常情况下都是匿名内部类(但不是必须的)
         *          此接口的实现类都是谁用谁写
         *          我们一般写的都是该接口的子接口的实现类:MethodInterceptor(子接口,方法拦截)
         *          public interface MethodInterceptor extends Callback
         */
        Producer cglibproducer = (Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() {
            /**
             * 执行被代理对象的任何方法都会经过改方法
             * 前三个参数跟刚才基于接口动态代理的参数一样
             * @param proxy:指的是代理对象的引用
             * @param method:当前执行的方法
             * @param args:当前执行方法所需的参数
             *以上三个参数和基于接口的动态代理中invoke方法的参数是一致的
             * @param methodProxy:当前执行方法的代理对象
             * @return Object 和被代理对象执行方法中的返回值是一样的
             * @throws Throwable
             */
            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())) {
                    //经销商进行代理,拿走2000
                    /**
                     * invoke(Object obj, Object... args)
                     * Object obj 指的是谁的方法,被代理对象(的方法):producer
                     * 匿名内部类访问外部成员变量的时候,用final修饰
                     *Object... args
                     * 方法的参数
                     * 返回值是Object类型
                     * method.invoke(producer,args);
                     */
                    returnValue = method.invoke(producer, money * 0.8f);
                }


                return returnValue;

            }
        });

        cglibproducer.saleProduct(10000f);

    }
}

结果
销售产品,工厂并拿到钱8000.0
使用cglib代理了没有接口实现的类

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

我们希望用我们改造的简洁service 并实现事务控制

public class AccountServiceImpl implements IAccountService{

    private IAccountDao accountDao;

    public void setAccountDao(IAccountDao 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) {
        accountDao.saveAccount(account);
    }


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


    public void deleteAccount(Integer acccountId) {
        accountDao.deleteAccount(acccountId);
    }


    public void transfer(String sourceName, String targetName, Float money) {
        System.out.println("transfer....");
            //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);
    }
}

新建一个类叫做BeanFactory

用于创建Service代理对象的工厂
原来事务的代码改成代理对象重写匿名内部对象new InvocationHandler()中
public Object invoke(Object proxy, Method method, Object[] args)的方法

原来事务的代码

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

改造成利用反射的方法

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      Object rtValue =null;

 try {
            //1.开启事务
            txManager.beginTransaction();
            //2.执行操作
             //不能直接写对象调用Account account = accountDao.findAccountById(accountId);
           //参数 被代理对象,被代理对象的方法
            rtValue = method.invoke(accountService, args);
            //3.提交事务
            txManager.commit();
            //4.返回结果
            return account;
        }catch (Exception e){
            //5.回滚操作
            txManager.rollback();
            throw new RuntimeException(e);
        }finally {
            //6.释放连接
            txManager.release();
        }
public class BeanFactory {

   //被代理对象	
    private IAccountService accountService;
    //重新service方法中用到此对象
    private TransactionManager txManager;

   //通过spring注入的方式,获取成员变量的值
    public void setTxManager(TransactionManager txManager) {
        this.txManager = txManager;
    }

	
    //需要通过spring注入的方式,获取成员变量的值
    public final void setAccountService(IAccountService accountService) {
        this.accountService = accountService;
    }

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

    }
}

事务控制和业务层的方法实现了分离

在beans.xml
先配置beanfactory

 <!--配置beanfactory-->
    <bean id="beanFactory" class="com.tju.factory.BeanFactory">
        <!-- 注入service -->
        <property name="accountService" ref="accountService"></property>
        <!-- 注入事务管理器 -->
        <property name="txManager" ref="txManager"></property>
    </bean>

配置代理的service对象,有事务支持的

<!--配置代理的service-->
<!--对象创建实例工厂factory-bean,创建方法是factory-method-->
    <bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"></bean>

在这里插入图片描述
有两个service对象,一个原本的sevice实现类,一个用动态代理实现+事务控制的service对象,@Qualifier("proxyAccountService")区分

/**
 * 使用Junit单元测试:测试我们的配置
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {

    @Autowired
    @Qualifier("proxyAccountService")
    private  IAccountService as;

    @Test
    public  void testTransfer(){
        as.transfer("aaa","bbb",100f);
    }

}

虽然通过动态代理的方式
实现了service对象事务控制,和业务的分离
但是配置复杂
如何改进?

5 AOP 面向切面编程

spring中的AOP

spring选择AOP有一个准则
根据是否实现了接口,来决定是基于接口的动态代理,还是基于子类的动态代理

AOP相关概念

Joinpoint (连接点)

所谓连接点是只那些被拦截的点,在spring中,这些点指的是方法,因为spring只支持方法类型的连接点

类比刚才的案例
打开业务层接口,这些接口的方法,可以通过动态代理的方法增强代码加上事务的逻辑

public interface IAccountService {

    /**
     * 查询所有
     * @return
     */
    List<Account> findAllAccount();

    /**
     * 查询一个
     * @return
     */
    Account findAccountById(Integer accountId);

    /**
     * 保存
     * @param account
     */
    void saveAccount(Account account);

    /**
     * 更新
     * @param account
     */
    void updateAccount(Account account);

    /**
     * 删除
     * @param acccountId
     */
    void deleteAccount(Integer acccountId);

    /**
     * 转账
     * @param sourceName        转出账户名称
     * @param targetName        转入账户名称
     * @param money             转账金额
     */
    void transfer(String sourceName, String targetName, Float money);

}

Pointcut(切入点)

所谓切入点是指我们要对哪些Jointpoint进行拦截的定义,切入点是指被增强的方法,有的方法可以不被增强则称为连接点

比如在业务层接口添加

public interface IAccountService {

..........................

    void test();//它只是连接点,但不是切入点,因为没有被增强
}

在代理对象工厂增强代码加上判断方法是不是test,是的话才增强

 public IAccountService getAccountService() {
        return (IAccountService)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 {
	     //如果是接口方法test,则不增强,此时test只是连接点,不是切入点
                        if("test".equals(method.getName())){
                            return method.invoke(accountService,args);
                        }
		Object rtValue = null;
                        try {
                            //1.开启事务
                            txManager.beginTransaction();
.......
}}

Advice(通知/增强)

所谓通知是指拦截到Jointpoint之后所要做的通知
通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知‘

一般动态代理中的invoke方法就是对接口方法进行增强的

通知类型图
在这里插入图片描述

Target(目标对象)

代理的目标对象(也就是被代理的对象)

Weaving(织入)

是指把增强应用到目标对象来创建新的代理对象的过程
原有的accountService对象没法实现事务,通过动态代理的方式返回加入事务管理的动态代码
整个过程叫做织入

Proxy(代理)

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

Aspect(切面)

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

比如切入点是指IAcountService接口哪些方法被增强过
通知是提供公共代码的TransactionManager类,这些代码何时去执行呢?
建立切入点和通知方法的对应关系就是切面

Spring基于XML的AOP编写必要代码

导入依赖

 <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>

IAccountService接口的三类方法

在这里插入图片描述

IAccountService接口

public interface IAccountService {
    /**
     * 模拟保存账户
     */
    void saveAccount();

    /**
     * 模拟更新用户
     * @param i
     */
    void updateAccount(int i);

    /**
     * 删除账户
     * @return
     */
    int deleteAccount();
}

接口的实现类

public class AccountServiceImpl implements IAccountService {
    public void saveAccount() {
        System.out.println("执行了保存");
    }

    public void updateAccount(int i) {
        System.out.println("执行了更新");
    }

    public int deleteAccount() {
        System.out.println("执行了删除");
        return 0;
    }
}

公共代码部分

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

spring基于XML的AOP-配置步骤

创建bean.xml
导入aop约束

<?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">

有一个service实现类对象需要加上增强,利用springIOC加载进来

有一个日志工具类(公共代码部分)(其实就是通知),它里面的方法可以加上日志增强,利用springIOC加载进来

配置AOP
首先配置切面,切面就是引用通知(日志工具类)
通知中的方法(printLog)是准备在切入点之前先执行,指明切入点方法的具体位置(切入点表达式)

思路
在这里插入图片描述

spring中基于XML的AOP配置步骤
1.把通知类的Bean也交给spring来管理
2.使用aop:config标签表明开始AOP的配置
3.使用aop:aspect标签表面配置切面
id属性:是给切面提供一个唯一标识
ref属性:是指定通知类bean的id
4.在aop:aspect标签内部使用对应标签来配置通知的类型
我们现在的示例是让printLog方法在切入点方法执行之前:所以是前置通知
aop:before:表示配置前置通知
method属性:用于指定logger类中哪个方法是前置通知
pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层哪些方法增强

切入点表达式的写法:
关键字:execution(表达式)
表达式:
访问修饰符 返回值 包名.包名.包名…类名.方法名(参数列表)
标准的表达式写法
public void cn.tju.service.impl.AccountServiceImpl.saveAccount();

 <!--配置spring的IOC,把service对象配置进来-->
        <bean id="accountService" class="cn.tju.service.impl.AccountServiceImpl"></bean>

        <!--spring中基于XML的AOP配置步骤
        1.把通知类的Bean也交给spring来管理
        2.使用aop:config标签表明开始AOP的配置
        3.使用aop:aspect标签表面配置切面
            id属性:是给切面提供一个唯一标识
            ref属性:是指定通知类bean的id
         4.在aop:aspect标签内部使用对应标签来配置通知的类型
            我们现在的示例是让printLog方法在切入点方法执行之前:所以是前置通知
                aop:before:表示配置前置通知
                    method属性:用于指定logger类中哪个方法是前置通知
                    pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层哪些方法增强

                    切入点表达式的写法:
                        关键字:execution(表达式)
                        表达式:
                            访问修饰符 返回值 包名.包名.包名....类名.方法名(参数列表)
                     标准的表达式写法
                      public void cn.tju.service.impl.AccountServiceImpl.saveAccount();
                    -->


        <!--配置logger类-->
        <bean id="logger" class="cn.tju.utils.Logger"></bean>
        <!--配置AOP-->
        <aop:config>
            <!--配置切面-->
        <aop:aspect id="logAdvice" ref="logger">
            <!--配置通知的类型,并且建立通知方法和切入点方法的关联-->
            <aop:before method="printLog" pointcut="execution(public void cn.tju.service.impl.AccountServiceImpl.saveAccount())"></aop:before>
        </aop:aspect>
        </aop:config>

测试

/**
 * 测试AOP的测试
 */
public class AOPTest {
    public static void main(String[] args) {
        //1获取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //2获取对象
        IAccountService as = (IAccountService)ac.getBean("accountService");
        //3执行方法
        as.saveAccount();
    }
}

结果
Logger类中的printlog方法开始记录日志了。。。
执行了保存

难道要对每一个业务层的接口方法都配一遍切入点表达式?

切入点表达式的写法

切点全通配写法* *..*.*(..)

分析有标准的表达式如何到全通配写法

标准
public void cn.tju.service.impl.AccountServiceImpl.saveAccount()

访问修饰符可以省略
void cn.tju.service.impl.AccountServiceImpl.saveAccount();

返回值可以使用通配符表示任意返回值

* cn.tju.service.impl.AccountServiceImpl.saveAccount()
包名可以使用通配符表示任意包,但是有几级包,就需要写几个*.
*  *.*.*.*.AccountServiceImpl.saveAccount()

包名可以使用 .. 表示当前包及其子包,*..代表任意包下的AccountServiceImp类都会被增强

*  *..AccountServiceImpl.saveAccount()

类名和方法名都可以使用*来实现通配
先改类名

*  *..*.saveAccount()

再改方法名

*  *..*.*()

此时接口中没有参数的方法都会被增强,void updateAccount(int i);不会
void saveAccount();
void updateAccount(int i);
int deleteAccount();

参数列表:可以直接写数据类型:
基本数据类型直接写名称 int
引用类型写包名.类名的方式 java.lang.String
类型可以使用通配符*表示任意类型,但必须有参数
可以使用 .. 表示有无参数均可,有参数可以是任意类型
示例1

*  *..*.*(int)

只有参数是int的方法被增强
void updateAccount(int i);

示例2
参数任意类型都可以,但只针对有参数

*  *..*.*(*)

void updateAccount(int i);被增强

示例3

*  *..*.*(..)

参数有无,有任意类型均可以,
三个方法全部增强

实际开发中切入点表达式的通常写法
切到业务层实现类下的所有方法
标准:
void cn.tju.service.impl.AccountServiceImpl.saveAccount();
(任意返回值 cn.tju.service.impl这个包下的任意类,任意方法名称,方法参数任意可有可无)

* cn.tju.service.impl.*.*(..)

修改beans.xml一个位置

 <aop:before method="printLog" pointcut="execution(* *..*.*(..))"></aop:before>

测试结果

public class AOPTest {
    public static void main(String[] args) {
        //1获取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //2获取对象
        IAccountService as = (IAccountService)ac.getBean("accountService");
        //3执行方法
        as.saveAccount();
        as.updateAccount(0);
        as.deleteAccount();
    }
}

结果
Logger类中的printlog方法开始记录日志了。。。
执行了保存
Logger类中的printlog方法开始记录日志了。。。
执行了更新
Logger类中的printlog方法开始记录日志了。。。
执行了删除

四种常用的通知类型

修改通知类Logger
配置通知类中的四种通知方法

public class Logger {
    /**
     * 用于打入日志,计划让其在切入点方法方法执行之前执行(切入点方法就是业务层方法)
     * 前置通知
     */
    public void beforePrintLog(){
        System.out.println("Logger类中前置通知的printlog方法开始记录日志了。。。");
    }
    /**
     *
     * 后置通知
     */
    public void afterReturningPrintLog(){
        System.out.println("Logger类中后置通知的printlog方法开始记录日志了。。。");
    }

    /**
     * 异常通知
     */
    public void afterThrowingPrintLog(){
        System.out.println("Logger类中异常通知的printlog方法开始记录日志了。。。");
    }
    /**
     * 最终通知
     */
    public void afterPrintLog(){
        System.out.println("Logger类中最终通知的printlog方法开始记录日志了。。。");
    }
}

在beans.xml中配置四种通知

 <!--配置logger类-->
        <bean id="logger" class="cn.tju.utils.Logger"></bean>
        <!--配置AOP-->
        <aop:config>
            <!--配置切面-->
        <aop:aspect id="logAdvice" ref="logger">
            <!--配置通知的类型,并且建立通知方法和切入点方法的关联-->
            <!--配置前置通知,在切入点方法执行之前执行-->
            <aop:before method="beforePrintLog" pointcut="execution(* cn.tju.service.impl.*.*(..))"></aop:before>
            <!--配置后置通知,在切入点方法正式常执行之后执行,后置通知和异常通知永远只能执行一个-->
            <aop:after-returning method="afterReturningPrintLog" pointcut="execution(* cn.tju.service.impl.*.*(..))"></aop:after-returning>
            <!--配置异常通知,再切入点方法执行产生异常之后执行-->
            <aop:after-throwing method="afterThrowingPrintLog" pointcut="execution(* cn.tju.service.impl.*.*(..))"></aop:after-throwing>
            <!--配置最终通知,无论切入点是否正常执行它都会在其后面执行-->
            <aop:after method="afterPrintLog" pointcut="execution(* cn.tju.service.impl.*.*(..))"></aop:after>

        </aop:aspect>
        </aop:config>

注意:
后置通知和异常通知永远只能执行一个
在这里插入图片描述

测试

   //1获取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //2获取对象
        IAccountService as = (IAccountService)ac.getBean("accountService");
        //3执行方法
        as.saveAccount();

结果
Logger类中前置通知的printlog方法开始记录日志了。。。
执行了保存
Logger类中后置通知的printlog方法开始记录日志了。。。
Logger类中最终通知的printlog方法开始记录日志了。。。

通用化切入点表达式

切入点表达式设置太繁琐了
引入新的标签 <aop:pointcut id="pt1" expression="execution(* cn.tju.service.impl.*.*(..))"></aop:pointcut>
配置切入点表达式

  • id属性用于指定表达式的唯一标识
  • expression属性用于指定表达式内容
    此标签写在aop:aspect标签内部只能当前切面使用,它还可以写在aop:aspect外面,此时就变成了所有切面可用

当写在aop:aspect标签内部时

  <aop:aspect id="logAdvice" 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>

            <!--配置切入点表达式,id属性用于指定表达式的唯一标识,expression属性用于指定表达式内容
            此标签写在aop:aspect标签内部只能当前切面使用。
            它还可以写在aop:aspect外面,此时就变成了所有切面可用-->
            <aop:pointcut id="pt1" expression="execution(* cn.tju.service.impl.*.*(..))"></aop:pointcut>

        </aop:aspect>

当写在aop:aspect标签外部时:

 <!--配置AOP-->
        <aop:config>
            <!--配置切面-->
            <aop:pointcut id="pt1" expression="execution(* cn.tju.service.impl.*.*(..))"></aop:pointcut>
        <aop:aspect id="logAdvice" 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>

            <!--配置切入点表达式,id属性用于指定表达式的唯一标识,expression属性用于指定表达式内容
            此标签写在aop:aspect标签内部只能当前切面使用。
            它还可以写在aop:aspect外面,此时就变成了所有切面可用-->
        </aop:aspect>
        </aop:config>
spring中的环绕通知

在切面中配置环绕通知

 <!--配置logger类-->
        <bean id="logger" class="cn.tju.utils.Logger"></bean>
        <!--配置AOP-->
        <aop:config>
            <!--配置切面-->
            <aop:pointcut id="pt1" expression="execution(* cn.tju.service.impl.*.*(..))"></aop:pointcut>
        <aop:aspect id="logAdvice" ref="logger">
            <!--配置环绕通知-->
            <aop:around method="aroundPringLog" pointcut-ref="pt1"></aop:around>
        </aop:aspect>
        </aop:config>

在日志工具类中配置环绕通知方法

public class Logger {
  
    public void aroundPringLog(){
        System.out.println("Logger类中最终通知的aroundPringLog方法开始记录日志了。。。");
    }
}

测试

 //1获取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //2获取对象
        IAccountService as = (IAccountService)ac.getBean("accountService");
        //3执行方法
        as.saveAccount();

结果
Logger类中最终通知的aroundPringLog方法开始记录日志了。。。

当我们配置了环绕通知的时候,切入点方法没有执行,而通知方法执行了
分析:回顾动态代理中的环绕通知为整个方法是环绕通知,在环绕通知中有明确的切入点调用(绿色)

在这里插入图片描述

通过对比动态代理中的环绕通知代码,发现动态代理中的环绕通知有明确的切入点方法调用,而我们的代码中没有

解决:
  • spring框架为我们提供了一个接口:ProceedingJoinPoint,该接口有一个方法proceed(),此方法相当于明确调用切入点方法
  • 该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用
spring中的环绕通知

它是spring框架为我们提供的一种方式,一种可以在代码中手动控制增强方法何时执行的方式

public class Logger {
 
    public Object aroundPringLog(ProceedingJoinPoint pjp){
        Object rtValue = null;
        try {
            Object[] args =pjp.getArgs();//得到方法执行所需要的参数
            System.out.println("Logger类中通知的aroundPringLog方法前置开始记录日志了。。。"); //此时相当于前置方法
            rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)
            System.out.println("Logger类中通知的aroundPringLog方法后置开始记录日志了。。。"); //此时相当于后置方法
            return rtValue;
        } catch (Throwable throwable) {
            System.out.println("Logger类中通知的aroundPringLog方法异常开始记录日志了。。。"); //此时相当异常方法
            throw new RuntimeException(throwable);
        }finally {
            System.out.println("Logger类中通知的aroundPringLog方法最终开始记录日志了。。。"); //此时相当最终方法

        }
    }
}

结果等效于XML中配置了切面方法
Logger类中最终通知的aroundPringLog方法前置开始记录日志了。。。
执行了保存
Logger类中最终通知的aroundPringLog方法后置开始记录日志了。。。
Logger类中最终通知的aroundPringLog方法最终开始记录日志了。。。

spring基于注解AOP配置

首先更改bean.xml中的约束
配置spring创建容器需要扫描的包
service实现类对象不用配了直接加上注解

@Service("accountService")
public class AccountServiceImpl implements IAccountService{

Logger类也不用配了,直接加上注解,还得加上注解说明这是个切面类
前置通知就在方法上@Before(“pt1()”)
后置通知就在方法上@AfterReturning(“pt1()”)
异常通知就在方法上@AfterThrowing(“pt1()”)
最终通知就在方法上@After(“pt1()”)
环绕通知就在方法上@Around(“pt1()”)

@Component("logger")
@Aspect//表示当前类是一个切面类
public class Logger {

//定义切入点方法,生成切入点表达式
    @Pointcut("execution(* com.tju.service.impl.*.*(..))")
    private void pt1(){}

最后配置开启注解AOP的支持

bean.xml

<?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.tju"></context:component-scan>

    <!-- 配置spring开启注解AOP的支持 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值