02 Spring的AOP的基础概念与简单使用 1 支持事务控制的转账案例 2 使用代理实现转账控制 3 Spring中的AOP 参考资料

02 Spring的AOP的基础概念与简单使用
1 支持事务控制的转账案例
2 使用代理实现转账控制
3 Spring中的AOP
参考资料

1-1 实现

数据访问层
package com.springlearn.dao.impl;
import com.mchange.v1.db.sql.ConnectionUtils;
import com.springlearn.dao.IAccountDao;
import com.springlearn.domain.Account;
import com.springlearn.util.ConnectionsUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import java.sql.SQLException;
import java.util.List;
public class AccountDao implements IAccountDao {
    private QueryRunner queryRunner;
    ConnectionsUtils connectionsUtils;
    // 通过set方法让Spring注入连接工具类实例对象,这里没有将实例对象作为方法的参数传入!!!!!
    public void setConnectionsUtils(ConnectionsUtils connectionsUtils) {
        this.connectionsUtils = connectionsUtils;
    }

    public void setQueryRunner(QueryRunner queryRunner) {
        this.queryRunner = queryRunner;
    }

    // connectionsUtils.getThreadConnection():每个操作都要绑定数据库连接对象
    public List<Account> findAllAccount() {
        try {
            return queryRunner.query(connectionsUtils.getThreadConnection(),"select * from account",new BeanListHandler<Account>(Account.class));
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    public Account findAccountById(Integer accountId) {
        try {
            return queryRunner.query(connectionsUtils.getThreadConnection(),"select * from account where id = ?",new BeanHandler<Account>(Account.class),accountId);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    public void saveAccount(Account account) {
        try {
            queryRunner.update(connectionsUtils.getThreadConnection(),"insert into account(name,money)values(?,?)",
                    account.getName(),
                    account.getMoney());
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public void updateAccount(Account account) {
        try {
            queryRunner.update(connectionsUtils.getThreadConnection(),"update account set name=?,money=? where id = ?",
                    account.getName(),
                    account.getMoney(),
                    account.getId());
        } catch (SQLException e) {
            e.printStackTrace();
        }

    }

    public void deleteAccout(Integer accountId) {
        try {
            queryRunner.update(connectionsUtils.getThreadConnection(),"delete from account where id = ?",accountId);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Override
    public Account findAccountByName(String accountName) {
        try {
            List<Account> accounts = queryRunner.query(connectionsUtils.getThreadConnection(),
                    "select * from account where name = ?",new BeanListHandler<>(Account.class),accountName);
            if(accounts == null || accounts.size() == 0)
                return null;
            if(accounts.size() > 1){
                throw new RuntimeException("结果集不唯一,数据有问题");
            }
            return accounts.get(0);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }
}
数据库连接工具类
  • 数据库连接实例对象: 通过ThreadLocal实现线程的隔离
package com.springlearn.util;

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

/*获取数据库连接实例对象放入ThreadLocal中与线程进行绑定*/
public class ConnectionsUtils {
    private ThreadLocal<Connection> t1 = new ThreadLocal<Connection>();  // Threadlocal对象绑定数据连接对象与线程
    private DataSource dataSource;                                       // 数据库连接参数
    public void setDataSource(DataSource dataSource){
        this.dataSource = dataSource;
    }
    public Connection getThreadConnection(){
        Connection conn = t1.get();
        try{
            if(conn == null){
                conn = dataSource.getConnection();
                t1.set(conn);

            }
            return conn;
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    public void removeConnection(){    // 使用完毕移除key,避免内存泄漏
        t1.remove();
    }
}
数据库事务管理工具类
package com.springlearn.util;
import java.sql.Connection;
import java.sql.SQLException;

/*
   数据库连接的事务管理工具类:
   1)开启事务
   2)提交事务
   3)回滚事务
   4)释放连接

 */
public class TransactionManage {
    private ConnectionsUtils connectionsUtils;
    public void setConnectionUtils(ConnectionsUtils connectionUtils) {
        this.connectionsUtils = connectionUtils;
    }
    // 开启事务,首先关系事务的自动提交
    public void beginTransaction(){
        try {
            connectionsUtils.getThreadConnection().setAutoCommit(false);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    // 提交事务
    public void commit(){
        try {
            connectionsUtils.getThreadConnection().commit();
        } catch (SQLException e) {
            e.printStackTrace();
        }

    }

    // 回滚事务
    public void rollback(){
        try {
            connectionsUtils.getThreadConnection().rollback();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    // 释放连接
    public void release(){
        try {
            /*数据库的底层的连接采用连接池,这里close是将线程资源还给线程池*/
            connectionsUtils.getThreadConnection().close();
            connectionsUtils.removeConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

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:context="http://www.springframework.org/schema/context"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
<!--    告知spring在创建容器时要扫描的包,配置所需要的标签不是在bean中,而是在名称为context的空间中-->
	<context:component-scan base-package="com.springlearn"></context:component-scan>
	<bean >
		<property name="accountDao" ref="accountDao"></property>
		<property name="transactionManage" ref="transactionManage"></property>
	</bean>

	<bean >
		<!--采用set的方式注入QueryRunner对象-->
		<property name="queryRunner" ref="runner"></property>
		<!--采用set的方式注入connectionUtils对象-->
		<property name="connectionsUtils" ref="connectionUtils"></property>
	</bean>



	<!--	数据库的操作对象工作在多线程环境下,为了避免相互干扰,这里采用多例对象,每个新的都是一个新的对象-->
	<bean ></bean>
	<bean >
        <!--连接工具内部使用set方法注入数据源-->
		<property name="dataSource" ref="datasource"></property>
	</bean>

	<!-- 数据源参数的注入-->
	<bean >
		<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
		<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/springlearn"></property>
		<property name="user" value="root"></property>
		<property name="password" value="123456"></property>
	</bean>
    <!--配置事务管理器-->
	<bean >
		<property name="connectionUtils" ref="connectionUtils"></property>
	</bean>
</beans>

1-2 Service层数据库事务控制及存在问题

package com.springlearn.service.impl;

import com.mchange.v1.db.sql.ConnectionUtils;
import com.springlearn.dao.IAccountDao;
import com.springlearn.domain.Account;
import com.springlearn.service.IAccountService;
import com.springlearn.util.TransactionManage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
public class AccountService implements IAccountService {
    IAccountDao accountDao;
    /*这里类提供了事务控制
    * 1)通过ThreadLocal保证线程安全性(配合线程池使用,注意及时remove)
    * 2)实现了事务的开启,提交以及回滚操作
    * */
    TransactionManage transactionManage;


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

    public void setTransactionManage(TransactionManage transactionManage) {
        this.transactionManage = transactionManage;
    }


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


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

    }
    public void saveAccount(Account account) {
        try{
            /*1)开启事务 2)执行操作 3)提交事务  4)返回结果*/
            transactionManage.beginTransaction();
            accountDao.saveAccount(account);
            transactionManage.commit();
        }catch (Exception e){
            /* 发生异常, 5)回滚操作*/
            transactionManage.rollback();
        }finally {
            /* 6)释放连接*/
            transactionManage.release();
        }
    }

    public void updateAccount(Account account) {
        try{
            /*1)开启事务 2)执行操作 3)提交事务  4)返回结果*/
            transactionManage.beginTransaction();
            accountDao.updateAccount(account);
            transactionManage.commit();
        }catch (Exception e){
            /* 发生异常, 5)回滚操作*/
            transactionManage.rollback();
        }finally {
            /* 6)释放连接*/
            transactionManage.release();
        }
    }

    public void deleteAccout(Integer accountId) {
        try{
            /*1)开启事务 2)执行操作 3)提交事务  4)返回结果*/
            transactionManage.beginTransaction();
            accountDao.deleteAccout(accountId);
            transactionManage.commit();
        }catch (Exception e){
            /* 发生异常, 5)回滚操作*/
            transactionManage.rollback();
        }finally {
            /* 6)释放连接*/
            transactionManage.release();
        }
    }

    @Override
    public void transfer(String sourceName, String targetName, float money) {
        try{
            /*1)开启事务 2)执行操作 3)提交事务  4)返回结果*/
            transactionManage.beginTransaction();
            /*基本步骤:1)查询两个账户 2)计算两个账户更新后的金额 3)更新两个账户记录*/
            Account source = accountDao.findAccountByName(sourceName);
            Account target = accountDao.findAccountByName(targetName);
            source.setMoney(source.getMoney()-money);
            target.setMoney(target.getMoney()+money);
            accountDao.updateAccount(source);
            accountDao.updateAccount(target);
            transactionManage.commit();
        }catch (Exception e){
            /* 发生异常, 5)回滚操作*/
            e.printStackTrace();
            transactionManage.rollback();
        }finally {
            /* 6)释放连接*/
            transactionManage.release();
        }
    }
}
实现存在的问题
问题1: service中的方法调用依赖事务管理类的方法调用,事务管理类的方法名称如果改变,那么service中的对应方法名称也需要改变。
问题2: service中的为了实现事务控制,有非常多的冗余代码。

回顾动态代理的优点

1)目标类增多,代理类不会变的多
2)目标类接的接口被修改,也不会影响代理类
  • 问题1可以通过动态代理的优点2)解决
  • 问题2则是代理模式的优点,将事务的控制类作为被代理类,避免冗余的代码。

动态代理的特点

2 使用代理实现转账控制

2-1 代理模式回顾

动态代理的特点:代理类的字节码文件随用随创建,随用随加载,不修改源码的基础上对方法进行加载

分类

1)基于接口的动态代理
2)基于子类的动态代理

2-1-1 基于接口的动态代理

涉及类:JDK官方提供的Proxy类

创建代理对象:使用Proxy类的newProxyInstance方法

要求:被代理类至少必须实现一个接口

newProxyInstance参数

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
ClassLoader:用于加载代理对象的字节码,参数设置时采用被代理对象使用相同的类加载器(固定写法)
Class[]: 字节码数组,让代理对象与被代理对象有相同的方法(固定写法)
InvocationHandler:用于提供增强的代码,有程序实现这个类提供代理对象方法的增强,调用者可以根据自己的需求指定增强的方式
Proxy.newProxyInstance(producer.getClass().getClassLoader(), producer.getClass().getInterfaces(),实现类)

InvocationHandler实现类的方法

  • invoke方法:执行被代理对象的任何接口方法都会经过该方法
 new InvocationHandler() {
     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable return null;}
 }
invoke方法的参数:
1)proxy:代理对象的引用
2)method:当前执行的方法
3)Object[]:当前执行方法所需的参数
方法的返回值:与被代理对象的方法具有相同的返回值

基于接口的动态代理实例

被代理类的接口

package proxy;
public interface IProducer {
    public void saleProduct(float money);
    public void afterService(float money);
}

被代理实现类

package proxy;
import java.lang.reflect.Proxy;
public class Producer implements IProducer{
    public void saleProduct(float money){
        System.out.println("销售产品,获取:"+money);
    }
    public void afterService(float money){
        System.out.println("提供售后服务,获取:"+money);
    }
}

基于接口的动态代理

package 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() {
                    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())){
                            returnValue = method.invoke(producer,money*0.8f);   // 这里修改了被代理方法类实际执行时的参数
                        }
                        /*返回被代理对象方法的返回值*/
                        return returnValue;
                    }
                });
        proxyProducer.saleProduct(12000f);
    }
}

总结:通过Proxy.newProxyInstance实现了代理类,并对被代理类的saleProduct方法进行了增强,在代理类的方法中改变了参数,将 销售获取的利润*0.8

执行结果如下

销售产品,获取:9600.0

2-1-2 基于子类的动态代理

涉及的类:第三方cglib库提供Enhancer

创建代理对象:使用Enhancer类的create方法

创建代理对象要求:被代理类必须能够被继承即不是最终类

Enhancer.create方法参数

class:被代理对象的字节码加载
Callback:用于提供增强的代码,需要实现MethodInterceptor(),该类实现了callback接口并提供intercept方法,类似于基于接口的动态代理的实现类的invoke方法。
        Enhancer.create(producer.getClass(), new MethodInterceptor() {
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                return null;
            }
        });

intercept方法的参数

intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
1)obj:代理对象的引用
2)method:当前执行的方法
3)Object[]:当前执行方法所需的参数
4)proxy:当前执行方法的代理对象
返回值类型:被代理类的方法的返回值一样

回调机制

基于子类的动态代理实例

package cglib;

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();    // 匿名内部类引用外部需要final修饰
        Producer cglibProducer = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                Object returnValue = null;
                // 1.获取方法执行的参数
                Float money = (Float)args[0];
                // 2.判断当前方法的具体时哪一个
                if("saleProduct".equals(method.getName())){
                    returnValue = method.invoke(producer,money*0.8f);
                }
                /*返回被代理对象方法的返回值*/
                return returnValue;
            }
        })

        cglibProducer.saleProduct(12000f);
    }
}

注意点:匿名内部类引用外部变量,外部变量必须采用final修饰

java为什么匿名内部类的参数引用时final

2-2 采用动态代理改造转账案例

产生代理类的工厂类

基本思想:创建一个工厂类用于产生service的代理类

package com.springlearn.factory;

import com.springlearn.domain.Account;
import com.springlearn.service.IAccountService;
import com.springlearn.util.TransactionManage;

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

/*该工厂方法提供service的代理类*/
public class BeanFactory {
    private IAccountService accountService;
    private TransactionManage transactionManage;

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

    public final void setTransactionManage(TransactionManage transactionManage) {
        this.transactionManage = transactionManage;
    }
    /*工厂类提供方法用于采用动态代理生成service的代理类*/
    public IAccountService getAccountService(){
        IAccountService proxyService = (IAccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object rtvalue = null;
                try{

                    /*1)开启事务 2)执行操作 3)提交事务  4)返回结果*/
                    transactionManage.beginTransaction();
                    rtvalue = method.invoke(accountService,args);
                    transactionManage.commit();
                    return rtvalue;
                }catch (Exception e){
                    /* 发生异常, 5)回滚操作*/
                    transactionManage.rollback();
                }finally {
                    /* 6)释放连接*/
                    transactionManage.release();
                }
                return null;
            }
        });
        return proxyService;
    }
}
对应的转账service类(被代理类)
package com.springlearn.service.impl;

import com.mchange.v1.db.sql.ConnectionUtils;
import com.springlearn.dao.IAccountDao;
import com.springlearn.domain.Account;
import com.springlearn.service.IAccountService;
import com.springlearn.util.TransactionManage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
public class AccountService implements IAccountService {
    IAccountDao accountDao;
    /*这里类提供了事务控制
    * 1)通过ThreadLocal保证线程安全性(配合线程池使用,注意及时remove)
    * 2)实现了事务的开启,提交以及回滚操作
    * */
    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }


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


    public Account findAccountById(Integer accountId) {
        Account account = accountDao.findAccountById(accountId);
        return account;
    }
    public void saveAccount(Account account) {
        accountDao.saveAccount(account);
    }

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

    }

    public void deleteAccout(Integer accountId) {
        accountDao.deleteAccout(accountId);

    }

    @Override
    public void transfer(String sourceName, String targetName, float money) {
        /*基本步骤:1)查询两个账户 2)计算两个账户更新后的金额 3)更新两个账户记录*/
        Account source = accountDao.findAccountByName(sourceName);
        Account target = accountDao.findAccountByName(targetName);
        source.setMoney(source.getMoney()-money);
        target.setMoney(target.getMoney()+money);
        accountDao.updateAccount(source);
        accountDao.updateAccount(target);
    }
}

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:context="http://www.springframework.org/schema/context"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
<!--    告知spring在创建容器时要扫描的包,配置所需要的标签不是在bean中,而是在名称为context的空间中-->
	<context:component-scan base-package="com.springlearn"></context:component-scan>
	<bean >
		<property name="accountDao" ref="accountDao"></property>

	</bean>

	<bean >
		<!--采用set的方式注入QueryRunner对象-->
		<property name="queryRunner" ref="runner"></property>
		<!--采用set的方式注入connectionUtils对象-->
		<property name="connectionsUtils" ref="connectionUtils"></property>
	</bean>

	<!--将工厂类放入到容器中-->
    <bean >
		<property name="transactionManage" ref="transactionManage"></property>
		<property name="accountService" ref="accountService"></property>
	</bean>

	<!--通过工厂方法创建accoutService的代理类-->
	<bean ></bean>

	<!--	数据库的操作对象工作在多线程环境下,为了避免相互干扰,这里采用多例对象,每个新的都是一个新的对象-->
	<bean ></bean>
	<bean >
        <!--连接工具内部使用set方法注入数据源-->
		<property name="dataSource" ref="datasource"></property>
	</bean>

	<!-- 数据源参数的注入-->
	<bean >
		<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
		<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/springlearn"></property>
		<property name="user" value="root"></property>
		<property name="password" value="123456"></property>
	</bean>
    <!--配置事务管理器-->
	<bean >
		<property name="connectionUtils" ref="connectionUtils"></property>
	</bean>
</beans>

总结:从上面的xml文件中可以看到容器中有两个IAccountService的实现类,分别时代理类与被代理类

测试方法

import com.springlearn.domain.Account;
import com.springlearn.service.IAccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountTest {
    // @Qualifier指定代理类
    @Autowired
    @Qualifier("proxyAccountService")
    IAccountService as;
    @Test
    public void testFindAll(){
        List<Account> accounts = as.findAllAccount();
        for(Account account:accounts){
            System.out.println(account);
        }
    }

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

注意:使用@Autowired自动匹配容器的接口实现类时,会匹配到代理类与被代理类,需要使用@Qualifier指定代理id。

总结:通过动态代理创建的service代理类解决了下面的两个问题

问题1: service中的方法调用依赖事务管理类的方法调用,事务管理类的方法名称如果改变,那么service中的对应方法名称也需要改变。
问题2: service中的为了实现事务控制,有非常多的冗余代码。
  • 问题1中的耦合被转移到代理类的方法增强中,实现了Service方法的解耦
存在的问题(AOP引入的动机)?
虽然动态代理解决了问题,但可以看到需要为代理类进行复杂的xml配置。因此spring中为了简化配置,提供了AOP的支持

3 Spring中的AOP

3-1 AOP的基础概念

定义:AOP(Aspect oriented Programming)即面向切面编程,把代码中重复的代码给抽出来,在需要执行的时候使用动态代理的技术,在不修改源码的基础上对方法进行增强

  • 预编译的方式与运行器件动态代理不修改原有方法的代码增强该代码的功能

作用:在程序运行期间,不修改源码的方式对方法进行增强

优势:减少重复的代码,提高开发效率,维护方便(只需关注方法增强部分)

AOP术语

Joinpoint (连接点):类里面可以被增强的方法,这些方法称为连接点;

Pointcut (切入点):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义;

  • 切入点是连接点的子集,一些方法能够被增强,但实际没有增强就不是切入点

Advice (通知/增强):所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能);

 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object rtvalue = null;
                try{
                    transactionManage.beginTransaction();             // 前置通知:切入点之前做的事情
                    rtvalue = method.invoke(accountService,args);     // 切入点的方法调用,也就是环绕通知
                    transactionManage.commit();                       // 后置通知:切入点之后做的事情
                    return rtvalue;
                }catch (Exception e){
                    /* 发生异常, 5)回滚操作*/
                    transactionManage.rollback();                    // 异常通知:发生异常做的事情
                }finally {
                    /* 6)释放连接*/
                    transactionManage.release();                     // 最终通知
                }
                return null;
            }
        });

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

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

Target(目标对象):代理的目标对象(要增强的类);

Weaving(织入):是把增强应用到目标的过程(比如给数据库连接service引入事务控制就是织入);

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

3-2 使用xml方式配置AOP方法

需求:通过AOP对service类的方法进行增强,让其在方法执行前打印日志信息

配置方法

基本步骤

1) 把通知bean交给spring进行管理
2) 使用aop:config标签表明开始开始AOP配置
3) 使用aop:aspect标签表明配置切面
       id属性:给切面提供一个唯一标识
       ref属性:指定通知类bean的i
4)在aop:aspect标签内部使用对应的标签来配置通知的类型(前置,后置,异常,最终,环绕通知)
       method属性:用于指定Logger类(通知bean)中哪个方法是前置通知
       pointcut属性:指定切入点表达式,spring根据表达式确定对哪些方法进行增强

切入点表达式的知识点(用于确定哪些方法进行增强)

execution(表达式)   表达式即  访问修饰符 返回值 类路径.方法名称 参数列表

实例: execution(public void com.springlearn.service.impl.AccountService.saveAccount())

1)包名可以使用通配符,表示任意包,有几级包就需要写几个*
实例:*.*.*.*.AccountService.saveAccount()
2)包名可以使用..表示当前包以及子包
3)类名以及方法名也可以使用*进行通配
4)参数列表:
    1)可以直接写数据类型:基本类型写名称,引用类型写包名.类名的方式
    2)*可以匹配任意类型参数
    3)..表示参数可有可无

通知bean(Logger类)

package com.springlearn.logger;
/*用于打印日志,在切入点方法执行前执行,切入点方法就是业务方法(被代理类方法)*/
public class Logger {
    public void printLog(){
        System.out.println("在切入点方法之前执行,开始记录日志!");
    }
}

service类

package com.springlearn.service.impl;
import com.springlearn.service.IAccountService;
public class AccountService implements IAccountService {
    public void saveAccount() {
        System.out.println("执行了保存");

    }
    public int deleteAccount() {
        System.out.println("执行了删除");
        return 0;
    }
    public void updateAccount(int i) {
        System.out.println("执行了更新"+i);
    }
}

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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
    <bean ></bean>
    <bean />
    <aop:config>
        <!--配置切面-->
        <aop:aspect id= "logAdvice" ref="logger">
            <!--配置通知的类型,并建立通知方法与切入点方法的关联采用通配符匹配类中所有方法-->
            <aop:before method="printLog" pointcut="execution(* com.springlearn.service.impl.*.*(..))"></aop:before>
<!--            <aop:before method="printLog" pointcut="execution(public void com.springlearn.service.impl.AccountService.saveAccount())"></aop:before>-->
        </aop:aspect>
    </aop:config>

</beans>
  • 上面文件中将Logger类的方法调用作为前置通知放到了service各个方法调用前执行
  • 切入点表达式对service类下的所有方法进行匹配

测试方法

package com.springlearn.AOPtest;
import com.springlearn.service.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Aoptest {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        IAccountService as = (IAccountService)ac.getBean("accountService");
        as.saveAccount();
        as.deleteAccount();
        as.updateAccount(1);
    }
}

执行结果

在切入点方法之前执行,开始记录日志!
执行了保存
在切入点方法之前执行,开始记录日志!
执行了删除
在切入点方法之前执行,开始记录日志!
执行了更新1

总结:通过xml的配置,程序中如果调用相关方法,会自动创建代理类并执行对应的方法

3-3 AOP的通知类型

try{
    try{
        //@Before
        method.invoke(..);
    }finally{
        //@After
    }
    //@AfterReturning
}catch(){
    //@AfterThrowing
}

通知类型 通知的特点
前置通知
后置通知
异常通知
最终通知
环绕通知 spring中环绕通知让开发者通过代码的方式进行方法的增强,更加的灵活

环绕通知:sping中配置环绕通知之后,切入点方法没有执行,而通知方法执行了

  • 上述这个现象有别于需求,通常我们希望切入点方法与通知方法一起执行

策略:spring框架提供了接口,ProceedingJoinPoint,该接口有一个方法proceed,此方法就相当于调用切入点方法,该接口可以作为环绕通知方法参数,程序执行时,spring会自动提供。

通知的定义(增强方法的定义)

package com.springlearn.logger;

import org.aspectj.lang.ProceedingJoinPoint;

/*用于打印日志,在切入点方法执行前执行,切入点方法就是业务方法(被代理类方法)*/
public class Logger {
    /*前置通知*/
    public void beforePrintLog(){
        System.out.println("前置通知");
    }
    /*后置通知*/
    public void afterReturnPrintLog(){
        System.out.println("后置通知");
    }
    /*异常通知*/
    public void afterThrowingPrintLog(){
        System.out.println("异常通知");
    }
    /*最终通知*/
    public void afterPrintLog(){
        System.out.println("最终通知");
    }
    /*Spring环绕通知:Spring的环绕通知提供了一种通过代码的方式,手动控制增强方法何时执行的方式。
    ProceedingJoinPoint接口的Proceed()方法执行相当于执行被代理类方法
    * */
    public Object aroundPrintLog(ProceedingJoinPoint pjp){
        Object rtValue = null;
        try{
            System.out.println("前置通知");
            Object[] args = pjp.getArgs();
            rtValue = pjp.proceed();               // 执行被代理类的方法
            System.out.println("后置通知");
            return rtValue;
        }catch (Throwable t){
            System.out.println("异常通知");
            throw new RuntimeException(t);

        }finally {
            System.out.println("最终通知");
        }
    }
}

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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
    <bean ></bean>
    <bean />
    <aop:config>
        <!--
            注意下面这行配置必须写在切面前面,xml文件语法约定的
            pointcut-ref可以避免冗余的pointcut书写
        -->
        <aop:pointcut />
        <aop:aspect id= "logAdvice" ref="logger">
            <!--前置通知-->
<!--            <aop:before method="beforePrintLog" pointcut-ref="pt1"></aop:before>-->
            <!--最终通知-->
<!--            <aop:after method="afterPrintLog" pointcut="execution(* com.springlearn.service.impl.*.*(..))"></aop:after>-->
            <!--后置通知-->
<!--            <aop:after-returning method="afterReturnPrintLog" pointcut="execution(* com.springlearn.service.impl.*.*(..))"></aop:after-returning>-->
            <!--异常通知-->
<!--            <aop:after-throwing method="afterThrowingPrintLog" pointcut="execution(* com.springlearn.service.impl.*.*(..))"></aop:after-throwing>-->
             <!--环绕通知-->
            <aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
        </aop:aspect>
    </aop:config>
</beans>

注意:Spring中的环绕通知提供了让开发者在代码中手动控制增强方法何时执行的方法。

测试代码与执行结果

package com.springlearn;
import com.springlearn.service.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Aoptest {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        IAccountService as = (IAccountService)ac.getBean("accountService");
        as.saveAccount();
    }
}

执行结果:可以看到环绕通知中可以*的对被代理方法进行增强。

前置通知
执行了保存
后置通知
最终通知

Spring方法中多线程的AOP

3-4 基于注解的AOP配置

涉及的注解
注解的名称 作用
@Aspect 将类声明为切面(类中包含有方法增强的通知)
@Pointcut 指定切入点表达式(用于判断对那些方法进行增强)
@Before
@AfterReturning
@AfterThrowing
@After
@Around 5种通知类型

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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- 配置spring创建容器时要扫描的包-->
    <context:component-scan base-package="com.springlearn"></context:component-scan>
    <!--配置spring开启注解的AOP支持-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
  • 也可以使用@EnableAspectJAutoProxy注解取代上面的xml开启AOP支持

使用注解配置切面

package com.springlearn.logger;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/*用于打印日志,提供了用于增强的方法*/
@Component(value = "logger")
// 将该类声明为切面,是切入点和通知(引介)的结合
@Aspect
public class Logger {

    @Pointcut("execution(* com.springlearn.service.impl.*.*(..))")
    private void pt1(){};

    /*前置通知*/
    //  @Before(value="pt1()")
    public void beforePrintLog(){
        System.out.println("前置通知");
    }
    /*后置通知*/
    // @AfterReturning(value="pt1()")
    public void afterReturnPrintLog(){
        System.out.println("后置通知");
    }
    /*异常通知*/
    // @AfterThrowing(value="pt1()")
    public void afterThrowingPrintLog(){
        System.out.println("异常通知");
    }
    /*最终通知*/
    // @After(value="pt1()")
    public void afterPrintLog(){
        System.out.println("最终通知");
    }
    /*Spring环绕通知:Spring的环绕通知提供了一种通过代码的方式,手动控制增强方法何时执行的方式。
    ProceedingJoinPoint接口的Proceed()方法执行相当于执行被代理类方法
    * */
    @Around(value="pt1()")
    public Object aroundPrintLog(ProceedingJoinPoint pjp){
        Object rtValue = null;
        try{
            System.out.println("前置通知");
            Object[] args = pjp.getArgs();
            rtValue = pjp.proceed();               // 执行被代理类的方法
            System.out.println("后置通知");
            return rtValue;
        }catch (Throwable t){
            System.out.println("异常通知");
            throw new RuntimeException(t);

        }finally {
            System.out.println("最终通知");
        }
    }
}

测试代码

package com.springlearn;
import com.springlearn.service.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Aoptest {
    public static void main(String[] args) {

        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        IAccountService as = (IAccountService)ac.getBean("accountService");
        as.saveAccount();
    }
}

四种通知执行结果

前置通知
执行了保存
最终通知
后置通知

环绕通知执行结果

前置通知
执行了保存
后置通知
最终通知

注意点:spring提供的四个注解的通知,最终通知与后置通知的顺序会颠倒对通知的顺序有着非常严格的要求,最好使用环绕通知的方式,手动的控制通知的执行顺序

3-5 转账案例采用AOP注解的方式进行事务控制

package com.springlearn.factory;
import com.springlearn.util.TransactionManage;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;

/*该工厂方法提供service的代理类*/
@Component(value = "beanFactory")
@Aspect
@EnableAspectJAutoProxy
public class BeanFactory {
    @Autowired
    private TransactionManage transactionManage;
    // 切入表达式指定增强的方法类路径
    @Pointcut("execution(* com.springlearn.service.impl.*.*(..))")
    private void pt1(){};
    // 这里通过环绕通知的方式来设置事务管理类的方法与Service类中的方法执行顺序
    @Around(value="pt1()")
    public Object aroundPrintLog(ProceedingJoinPoint pjp){
        Object rtValue;
        try{
            System.out.println("开始事务");
            transactionManage.beginTransaction();
            Object[] args = pjp.getArgs();
            rtValue = pjp.proceed(args);               // 执行被代理类的方法
            transactionManage.commit();
            System.out.println("提交事务");
            return rtValue;
        }catch (Throwable t){
            /* 发生异常, 5)回滚操作*/
            transactionManage.rollback();
            throw new RuntimeException(t);
        }finally {
            /* 6)释放连接*/
            transactionManage.release();
            System.out.println("释放连接");
        }
    }
}

总结:使用AOP提供的注解将实现的事务管理器方法用于增强Service层的数据访问操作

参考资料

01 Spring基础

02 Spring整理比较好的笔记