Spring-AOP

Spring-AOP

第一节 初识AOP

1.1 AOP概述

AOP:全称是Aspect Oriented Programming。即:面向切面编程。

它是把我们业务逻辑中的各个部分进行隔离,使每个部分独立开来,在需要用到某个部分的时候,运用预编译和运 行期动态代理技术把增强的代码加入到我们的业务逻辑中,组成完整的功能。

它是一种编程思想,一种设计理念,是OOP的一种延续。运用AOP编程思想,可以提高代码的可重用性,使编码 更加简洁,更易于维护。

OOP:面向对象编程 。核心思想:封装,继承,多态. OOP 达到了软件工程的三个主要目标:重用性、灵活性和扩展性

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

1.2AOP的使用场景

1.2.1案例问题回顾

在Spring的注解loC课程的案例中,我们通过账户操作的综合案例把loC课程内容串联起来了,但是当我们加入了 转账功能后,代码变得不再简洁。我们的故事就从这里开始。

代码节选如下:(只保留了业务层代码,主要问题就在业务层实现类中)

    /**
     * 账户的业务层接口
     */
public interface AccountService {

    /**
     * 转账
     */
    void transfer(String resource,String target,double money);

    /**
     * 保存
     */
    void save(Account account);

    /**
     * 根据id删除
     */
    void delete(Integer id);

    /**
     * 更新账户
     */
    void update(Account account);

    /**
     * 根据id查询
     */
    Account findById(Integer id);

    /**
     * 根据名称查询账户
     */
    Account findByName(String name);

    /**
     * 查询所有
     */
    List<Account> findAll();
}

账户业务层接口实现类

@Service
public class AccountServiceImpl implements AccountService {

    // 依赖注入  通过依赖注入可以实现service调用dao的增删改查方法 通过方法进行增删改查的调用
    // 调用dao的时候要有接口所对应的SQL文件
    // 依赖注入
    @Autowired
    private AccountDao accountDao;
    @Autowired
    private TransactionManager transactionManager;

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

    // 转帐
    @Override
    public void transfer(String resource, String target, double money) {
        try {
            // 开启事务
            transactionManager.begin();
            /*Connection conn = dataSource.getConnection();
            System.out.println("conn = " + conn);
            conn.setAutoCommit(false);*/

            // 转账业务
            // 1.根据name 获取 账户对象
            Account resourceAccount = accountDao.findByName(resource);
            // 当前代码执行所在的线程对象
            System.out.println("当前线程:"+Thread.currentThread().getName());
            Account targetAccount = accountDao.findByName(target);
            // 2.更新账户对象中 金额
            resourceAccount.setMoney(resourceAccount.getMoney() - money);
            targetAccount.setMoney(targetAccount.getMoney() + money);
            // 3.更新数据库中  账户对象的金额
            accountDao.update(resourceAccount);
            // 当前代码执行所在的线程对象
            System.out.println("当前线程:"+Thread.currentThread().getName());
            // int i = 1/0;
            accountDao.update(targetAccount);

            // 提交事务
            transactionManager.commit();
        } catch (Exception e) {
            e.printStackTrace();
            // 回滚事务
            transactionManager.rollback();
        }finally {
            // 释放连接
            transactionManager.close();
        }


    }

    @Override
    public void save(Account account) {
        try {
            // 开启事务
            transactionManager.begin();
            accountDao.save(account);
            // 提交事务
            transactionManager.commit();
        }catch (Exception e){
            e.printStackTrace();
            // 回滚事务
            transactionManager.rollback();
        }finally {
            // 释放链接
        }
    }

    @Override
    public void delete(Integer id) {
        try {
            // 开启事务
            transactionManager.begin();

            accountDao.delete(id);
            // 提交事务
            transactionManager.commit();
        }catch (Exception e){
            e.printStackTrace();
            // 回滚事务
            transactionManager.rollback();
        }finally {
            // 释放链接
        }

    }

    @Override
    public void update(Account account) {
        try {
            // 开启事务
            transactionManager.begin();
            accountDao.update(account);
            // 提交事务
            transactionManager.commit();
        }catch (Exception e){
            e.printStackTrace();
            // 回滚事务
            transactionManager.rollback();
        }finally {
            // 释放链接
        }
    }

    // 下边三个try/catch  省略了
    @Override
    public Account findById(Integer id) {
        return accountDao.findById(id);
    }

    @Override
    public Account findByName(String name) {
        return accountDao.findByName(name);
    }

    @Override
    public List<Account> findAll() {
        return accountDao.findAll();
    }
}

image-20200830123633086

红框代码不一样所以无法抽取父类公共方法,面向对象(竖向)继承的思想解决不了这个问题。所以要用AOP思想(横向)抽取,把重复代码抽取到工具类中。

1.3解决思路分析

1.3.1明确目标

我们要明确解决什么问题?

我们要解决的是大量的重复代码导致开发效率下降,同时对后期维护造成不便的问题。换句话说就是对业务层瘦身(去除重复代码),但是必须保证业务层代码还有事务的支持((不能因为去重,而导致没有事务了)。

1.3.2技术选择

业务层中方法的代码需要解耦,实现把事务和具体业务剥离,在执行业务层方法时,通过对方法进行增强,从而实 现业务层方法的事务支持。其核心思想就是对业务层方法增强。

既然是对业务层方法增强,同时又不希望针对每个方法独立编写事务,那么就有两种技术可供选用了。 第一个是采用装饰者模式的设计理念,采用创建业务层对象的包装类,从而实现对方法增强,加入事务的支持(装 饰者模式也称为静态代理)。

简单举个例子 对原有方法进行增强

InputStream read()  一个一个字节读取
FileReader  一个个字符读取
BufferedReader readLine()  一次读取一行

new BufferedReader(new FileReader("a.txt"))

第二个是采用代理模式的设计理念,运用动态代理技术,通过运行期创建代理对象,对业务层方法增强,加入事务 的支持。

1)静态代理—装饰者模式

image-20200830125533945

  1. 动态代理 - 代理模式

image-20200830125941671

通过上面两张图的回顾,我们得知,静态代理需要为每个业务层都要创建一个包装类,那么此时其实并没有解决手 们现在的问题,因为一旦到了项目中,我们的业务层实现类一多起来,对每个实现类都创建包装类的话,同样会产 生大量的重复代码。但是装饰者模式的优势是,它可以针对不同租户进行不同的包装(增强),也就是说支持可足 制化,让我们的每个包装类都具有独立的特点。最明显的例子就是早期我们学的IO流对。只不过,这个优势在目 前我们的问题中反倒成了劣势。

由此,我们得出结论,我们需要选用动态代理技术解决案例中的问题。

1.4动态代理技术

动态代理作用:重复代码的抽取

作用:

在不修改源码的基础上,对已有方法增强。

动态代理特征

特征:

字节码(.class)是随用随创建,随用随加载。

动态代理分类

动态代理根据创建代理对象的类型,分为两类。

第一类:基于接口的动态代理,即创建的代理对象和被代理对象实现了相同的接口

第二类:基于子类的动态代理,即创建的代理对象是被代理对象的子类

1.4.1基于接口的动态代理

提供者

JDK官方ProxyI

使用要求

被代理类最少实现一个接口

创建代理对象的类

java.lang.reflect.Proxy

创建代理对象的方法

public static object newProxyInstance(classLoader loader,
class<?>[]interfaces,
InvocationHandler h)

方法参数的含义

参数/说明 是什么 做什么 些什么
ClassLoaderloader 类加载器 用于加载代理对象的字节码 和被代理对象使用相同的类加载器
Class<?>[] interfaces 字节码数组 用于让代理对象和被代理对象具有相同的行为(方法) 要根据被代理对象区别对待。如果被代理对象是一个普通类,那么写的就是它实现的接口数组,通常就是用被代理类字节码对象调用getInterfaces();方法。如果被代理对象本身就是一个接口,那么就直接创建一个字节码数组,把接口的字节码传入。通常就是new class[]{被代理接口的字节码}。Sq1Session.getMapper([UserDao.class] )
InvocationHandlerh 一个接口 用于给被代理对象方法提供增强代码的 编写InvocationHanlder的实现类,重写invoke方法。在方法中提供要增强的代码。

InvocationHandler的invoke方法

/**
* @param proxy创建的代理对象的引用
* @param method 当前执行的被代理对象的方法
* @param args当前被代理对象方法执行所需的参数
* @Return object 当前方法执行的返回值void 被看成Void类型
*/
public object invoke(object proxy, Method method, object[] args)
	throws Throwable;

1.4.2基于子类的动态代理

提供者

​ 第三方开源项目CGLIB (Code Generation Library)(代码生产库)

使用要求

​ 被代理类不能是最终类

创建代理对象的类

net. sf. cglib. proxy. Enhancer
<!-- https : //mvnrepository. com/ artifact/cglib/cglib -->

<dependency>
	<groupId>cg1ib</ groupId>
	<artifactId>cglib</artifactId>
	<version>3.2.5</version>
</ dependency>

创建代理对象的方法

public static object create(Class type, Callback callback)

方法参数的含义

参数/说明 是什么 做什么 写什么
Classtype 字节码 用于创建被代理的子类代理对象,同时用于得到加载代理对象的类加载器 被代理对象的字节码
Callbackcallback 一个接口 用于给被代理对象方法提供增强代码的。打开源码发现,此接口中没有任何方法。因为callback/接口是用于定义规范的标记接口,在实际开发中,我们一般使用它的子接口WethodInterceptor。 编写MethodInterceptor的实现类,重写intercept方法,在方法中提供要增强的代码。

MethodInterceptor的intercept方法

*@param obj创建的代理对象的引用
*@param method当前执行的被代理对象的方法
*@param args当前被代理对象方法执行所需的参数
*@param proxy当前执行被代理对象方法的代理对象。它也可以用于执行被代理对象方法,效率比 method.inovke略高。
*/
public object intercept(Dbject obj,java.lang.reflect.Method method,Object[] args,
MethodProxy proxy)throws Throwable;

1.5案例问题的解决

基于JDK的动态代理(proxy)

/**
 * 基于JDK的动态代理(proxy)
 * 作用:用于创建AccountService 动态代理对象的工厂类
 */
@Component
public class ProxyAccountServiceFactory {

    // 指定被代理对象(AccountServiceImpl对象)
    @Autowired
    private AccountService accountService;
    @Autowired
    private TransactionManager transactionManager;


    // 用于创建AccountService  动态代理对象
    @Bean("proxyAccountService")
    public AccountService createAccountServiceProxy(){
        // 创建AccountService 的动态代理对象
        /**
         * 参数一:被代理对象的类加载器
         * 参数二:被代理对象 所实现的所有接口
         * 参数三:指定原有方法  如何进行增强
         */
        AccountService proxyAccountService = (AccountService) Proxy.newProxyInstance(
                accountService.getClass().getClassLoader(),
                accountService.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     * invoke  方法,就是对原有功能方法的增强
                     * @param proxy  就是创建出来的动态代理对象的引用
                     * @param method    被代理对象 所要执行的方法
                     * @param args 被代理对象  所要执行的方法  所接收的实际参数
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("被代理对象="+ accountService);
                        System.out.println("被增强的方法="+method.getName());
                        System.out.println("被增强的方法  所接收的实际参数值= "+ Arrays.toString(args));
                        Object rtValue = null;

                        try {
                            // 开启事务(增强)
                            transactionManager.begin();
                            // 调用 被代理对象 的原有方法
                            rtValue = method.invoke(accountService,args);
                            // 提交事务(增强)
                            transactionManager.commit();
                        }  catch (Exception e) {
                            e.printStackTrace();
                            // 回滚事务(增强)
                            transactionManager.rollback();
                        } finally {
                            // 释放连接(增强)
                            transactionManager.close();
                        }

                        return proxy;
                    }
                }
        );

        return proxyAccountService;
    }

基于CgLib的动态代理(第三方jar包)

/**
 * 基于CgLib的动态代理(第三方jar包)
 * 作用:用于创建AccountService 动态代理对象的工厂类
 */
@Component
public class CglibAccountServiceFactory {

    // 指定被代理对象(AccountServiceImpl对象)
    @Autowired
    @Qualifier("accountServiceImpl")
    private AccountService accountService;
    @Autowired
    private TransactionManager transactionManager;


    // 用于创建AccountService  动态代理对象
    @Bean("proxyAccountService")
    public AccountService createAccountServiceProxy(){
        // 创建AccountService 的动态代理对象
        AccountService cgLibAccountService = (AccountService) Enhancer.create(
                accountService.getClass(),
                new MethodInterceptor() {
                    /**
                     * intercept  方法, 就是对原有功能方法的增强
                     * @param proxy 就是创建出来的动态代理对象的引用
                     * @param method  被代理对象 所要执行的方法
                     * @param objects  被代理对象  所要执行的方法  所接收的实际参数
                     * @param methodProxy 所要执行的方法的代理对象 method.invoke()
                     */
                    @Override
                    public Object intercept(Object proxy, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                        System.out.println("被代理对象="+ accountService);
                        System.out.println("被增强的方法="+method.getName());
                        System.out.println("被增强的方法  所接收的实际参数值= "+ Arrays.toString(objects));
                        Object rtValue = null;

                        try {
                            // 开启事务(增强)
                            transactionManager.begin();
                            // 调用 被代理对象 的原有方法
                            rtValue = method.invoke(accountService,objects);
                            // 提交事务(增强)
                            transactionManager.commit();
                        }  catch (Exception e) {
                            e.printStackTrace();
                            // 回滚事务(增强)
                            transactionManager.rollback();
                        } finally {
                            // 释放连接(增强)
                            transactionManager.close();
                        }
                        return proxy;
                    }

                });


        return cgLibAccountService;
    }

案例小结

我们在本示例中,只是生成了一个AccountService的实现,有些同学会觉得getProxyAccountService()这个方法 的返回值限定死了,只能生成AccountService类型的代理对象,其实不然,因为我们已经学过了Spring的loC,只 要把创建的代理对象传入进来就行了,只是我们目前案例只有一个Service的实现,因此把它写成固定的了。

第二节 AOP的相关概念

2.1 AOP的基础知识

AOP优势

运用AOP编程思想,具有以下优势:

1.提高代码的独立性

⒉.减少重复代码,提高开发效率

3.易于维护

AOP实现原理分析

通过《1.1AOP概述》小节中的概念介绍,我们得知AOP编程是通过预编译和运行期动态代理技术实现的对重复代 码进行统一的管理和调用。

AOP应用场景说明

在实际开发中,有很多地方都可以看到AOP编程思想的身影。我们第一次接触到它的地方,就是学习Filter过滤器 的时候,过滤器就是AOP思想的具体应用场景之一。

当然,在我们实际开发中,有很多具体的需求,都可以借助AOP编程思想来实现。

例如:记录访问日志,统计方法执行效率,判断用户的访问权限等等,这些场景都可以使用AOP编程思想。

2.2 Spring中的AOP

2.2.1 AOP相关术语

1)业务主线

在讲解AOP术语之前,我们先来看一下下面这两张图,它们就是第一章节案例中的需求,只不过把它完善成了使用 三层架构实现账户的操作:

image-20200830211803811

​ 上图描述的就是未采用AOP思想设计的程序,当我们红色框中圈定的方法时,会带来大量的重复劳动。程序中充斥 着大量的重复代码,使我们程序的独立性很差。而下图中是采用了AOP思想设计的程序,它把红框部分的代码抽取 出来的同时,运用动态代理技术,在运行期对需要使用的业务逻辑方法进行增强。

image-20200830212038886

2)AOP术语

名词 解释
Joinpoint(连接点) 它指的是那些可以用于把增强代码加入到业务主线中的点,那么由上图中红色字体我们可以看出,这些点指的就是方法。在方法执行的前后通过动态代理技术加入增强的代码。在Spring框架AOP思想的技术实现中,也只支持方法类型的连接点。
Pointcut(切入点) 它指的是那些已经把增强代码加入到业务主线进来之后的连接点。由上图中,我们看出表现层transfer方法就只是连接点,因为判断访问权限的功能并没有对其增强。
Aspect(切面) 它指定是增强的代码所关注的方面,把这些相关的增强代码定义到一个类中,这个类就是切面类。例如,事务切面,它里面定义的方法就是和事务相关的,像开启事务,提交事务,回滚事务等等,不会定义其他与事务无关的方法。我们前面的案例中TrasnactionManager就是一个切面。
Advice(通知/增强) 它指的是切面类中用于提供增强功能的方法。并且不同的方法增强的时机是不一样的。比如,开启事务肯定要在业务方法执行之前执行;提交事务要在业务方法正常执行之后执行,而回滚事务要在业务方法执行产生异常之后执行等等。那么这些就是通知的类型。其分类有:前置通知后置通知异常通知最终通知环绕通知。
Target(目标对象) 它指的是代理的目标对象。即被代理对象。
Proxy(代理) 它指的是一个类被AOP织入增强后,产生的代理类。即代理对象。
Weaving(织入) 它指的是把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织 入, 而Aspect采用编译期织入和类装载期织入。
lntroduction(引 介) 它指的是在不修改类代码的前提下,运行期为目标对象动态地添加一些方法或字段。

2.2.2 Spring中AOP的代理选择

默认情况下,Spring会根据被代理对象是酋实现接口来选择使用DK还是CGLIB。当被代理对象没有实现任何接口 时,Spring会选择CGLIB。当被代理对象实现了接口,Spring会选择DK官方的代理技术,不过我们可以通过配置 的方式,让Spring强制使用CGLIB。关于配置的方式,我们在接下来的课程中给大家讲解。

2.2.3 Spring中AOP的配置方式

在Spring的AOP配置中,也和loC配置一样,支持3类配置方式。

第一类:使用XML配置

第二类:使用XML+注解组合配置

第三类:使用纯注解配置

2.2.4学习spring中的AOP要明确的事

1)开发阶段(我们做的)

编写核心业务代码((开发主线)∶大部分程序员来做,要求熟悉业务需求。

把公用代码抽取出来,制作成通知:AOP编程人员来做。

在配置文件中,声明切入点与通知间的关系,即切面。︰AOP编程人员来做。

2)运行阶段(Spring框架完成的)

Spring框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理 对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。

第三节 Spring中应用AOP

3.1基于XML配置的入门案例

3.1.1案例需求介绍

在业务层方法执行之前,输出记录日志的语句。

3.1.2导入坐标

<dependencies>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-context</artifactId>
		<version>5.1.9.RELEASE</version>
    </dependency>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-aop</artifactId>
		<version>5.1.9.RELEASE</version>
	</dependency>
	<dependency>
		<groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
		<version>1.9.4</version>
    </dependency>
</dependencies>

3.1.3 编写基础代码

// 账户业务层接口
public interface AccountService {
    /*
    * 模拟保存账户的方法
    * */
    void save();
}

// 账户接口的实现类
public class AccountServiceImpl implements AccountService {
    @Override
    public void save() {
        // 打印一句话,日志(前置增强)
        System.out.println("保存账户");
    }
}
/**
 * 日志切面类,提供了AccountServiceImpl 类 增强方法
 * */

public class LogUtils {

    // 打印日志(增强方法)
    public void printLog(){
        System.out.println("增强方法printLog 执行了");
    }
}

3.1.4 配置Spring的IoC

3.1.5 配置Spring的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
       https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 配置service 对象 -->
    <bean id="accountService" class="com.zhuxu.service.impl.AccountServiceImpl"/>

    <!-- 配置LogUtil 切面类(增强功能)对象 -->
    <bean id="logUtils" class="com.zhuxu.utils.LogUtils"/>

    <!-- 配置AOP 实现了对service 中的方法 进行增强(LogUtil) -->
    <aop:config>
        <!-- 配置切面 -->
        <aop:aspect id="logUtil" ref="logUtils">
            <!-- 配置切入点表达式 目的是为了让我们service层中的方法和我们要增强的功能代码结合在一起 -->
            <aop:before method="printLog" pointcut="execution(public void com.zhuxu.service.impl.AccountServiceImpl.save())"></aop:before>
        </aop:aspect>
    </aop:config>
</beans>

3.1.6 编写测试类代码

public class SpringAOPTest {
    public static void main(String[] args) {
        // 1.创建Spring容器对象
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml");
        // 2.获取AccountService对象
        AccountService accountService = applicationContext.getBean(AccountService.class);
        // 3.调用方法
        accountService.save();
    }
}

3.2基于XML的AOP配置细节

3.2.1关于切入点表达式

在入门案例中,我们实现了对AccountServiceImpl的save方法进行增强,在其执行之前,输出了记录日志的语 句。这里面,我们接触了一个比较陌生的名称:切入点表达式,它是做什么的呢?我们往下看。

1)概念及作用

切入点表达式,也称之为Aspect]切入点表达式,指的是遵循特定语法结构的字符串,其作用是用于对符合语法格 式的连接点进行增强。,它是AspectJ表达式的一部分。

回2)关于AspectJ

Aspect是一个基于Java语言的AOP框架,Spring框架从2.0版本之后集成了AspectJ框架中切入点表达式的部分, 开始支持AspectJ切入点表达式。

3)表达式中的关键字

关键字 说明
execution 用于匹配方法执行的连接点

4)切入点表达式的使用示例

全限定方法名
		访问修饰符返回值包名.包名.包名.类名.方法名(参数列表)
全匹配方式:
		public void com. itheima. service . impl . AccountServiceImpl. saveAccount()
访问修饰符可以省略
		void com. itheima . service . impl. AccountServiceImpl . saveAccount( )
返回值可以使用*,表示任意返回值
		* com. itheima. service . impl . AccountServiceImpl . saveAccount()
包名可以使用" .. "表示当前包及其子包
		* com. itheima. service. . AccountServiceImpl. saveAccount( )
类名和方法名,都可以使用*,表示任意类,任意方法
		* com. itheima . service. imp1.*.*()
参数列表,可以使用具体类型
		基本类型直接写类型名称: int
		引用类型必须写全限定类名: java.1ang . String
参数列表可以使用*,表示任意参数类型,但是必须有参数
		com. itheima . service . impl . AccountServiceImpl. saveAccount(*)
参数列表可以使用...表示有无参数均可。有参数可以是任意类型
	* com. itheima. service . impl . AccountServiceImpl. saveAccount(..)
全通配方式:
		* *..*.*(..)
开发中常用的方法
 		* com. itheima.service..*.*(..)

3.2.2 入门案例的标签

1)aop:config标签

<!--
    作用:
    	用于表示开始aop的配置
    出现位置:
    	写在beans标签的内部
	属性:
		proxy-target-class:用于指定代理方式。默认值是false。当取值为true时, 采用cglib的代理方
		expose-proxy:用于指定是否暴露代理对象,通过AopContext可以进行访问代理对象。
 -->   
<aop : config proxy-target-class="false" expose- proxy= "false"></aop:config>

2)aop:aspect标签

<!--
    作用:
    	用于配置切面
    出现位置:
    	aop:config标签内部
	属性:
		id:用于指定切面的唯一标识。
		ref:用于指定引用bean的id。
		order:用于指定多个切面中,相同通知类型的执行顺序。取值是个整数,数值越小优先级越高
-->
< aop:aspect id="logAdvice" ref="logUtil1" order="1">< /aop: aspect>

3)aop:pointcut标签

<!--
    作用:
    	用于配置通用切入点表达式
    出现位置:
		aop : config标签内部,当出现在此处时,要求必须在所有aop: aspect标签之前。它可以供所有切面使用 
        aop : aspect标签内部,当出现在此处时,它没有顺序要求,但只能供当前切面使用。
-->
<aop : pointcut id="pointcut1"
expression="execution(public * com. itheima . service. impl . AccountServiceImpl.save())">
</ aop: pointcut>

3.2.3配置SpringAOP使用Cglib代理模式

在前面我们已经说了,Spring在选择创建代理对象时,会根据被代理对象的实际情况来选择的。被代理对象实现了 接口,则采用基于接口的动态代理。当被代理对象没有实现任何接口的时候,Spring会自动切换到基于子类的动态 代理方式。|

但是我们都知道,无论被代理对象是否实现接口,只要不是final修饰的类都可以采用cglib提供的方式创建代理对 象。所以Spring也考虑到了这个情况,提供了配置的方式实现强制使用基于子类的动态代理〈(即cglib的方式)。 配置的方式有两种。

1)第一种:使用aop:config标签配置

<aop:config proxy-target-class="true">

2)第二种:使用aop:aspectj-autoproxy标签配置

<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>

3.3五种通知类型

3.3.1前置通知

配置方式: aop:before标签

<!--
    作用:
    	用于配置前置通知
    出现位置:
    	它只能出现在aop:aspect标签内部
	属性:
		method:用于指定前置通知的方法名称
		pointcut:用于指定切入点表达式
		pointcut-ref:用于指定切入点表达式的引用
-->
<aop : before method="printLog" pointcut-ref-" pointcut1"></aop: before>

执行时机

前置通知永远都会在切入点方法(业务核心方法)执行之前执行。

细节

前置通知可以获取切入点方法的参数,并对其进行增强。

3.3.2 后置通知

配置方式

<!--
	作用:
		用于配置后置通知
	出现位置:
		它只能出现在aop: aspect标签内部
	属性:
		method:用于指定后置通知的方法名称
		pointcut:用于指定切入点表达式
		pointcut-ref:用于指定切入点表达式的引用
		returning:用于指定返回值类型
-->
<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>

执行时机
通知就不再执行了,而是执行异常通知。
细节
后置通知既可以获取到切入点方法的参数,也可以获取切入点方法的返回值。

3.3.3异常通知

配置方式

<!--
	作用:
		用于配置异常通知。
	出现位置:
		它只能出现在aop:aspect标签内部
	属性:
		method:用于指定异常通知的方法名称
		pointcut:用于指定切入点表达式
		pointcut-ref:用于指定切入点表达式的引用
		arg-names:用于指定通知方法的参数名称,要求表达式中必须有描述args的语句
		throwing:用于指定异常通知中异常的变量名称
-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1" ></aop:after-throwing>

执行时机
异常通知的执行时机是在切入点方法(业务核心方法))执行产生异常之后,异常通知执行。如果切入点方法执行没有产生异常,则异常通知不会执行。
细节
异常通知不仅可以获取切入点方法执行的参数,也可以获取切入点方法执行产生的异常信息。

3.3.4 最终通知

配置方式

<!--
	作用:
		用于指定最终通知。
	出现位置:
		它只能出现在aop: aspect标签内部
	属性:
		method:用于指定最终通知的方法名称
		pointcut:用于指定切入点表达式
		pointcut-ref:用于指定切入点表达式的引用
		arg-names:用于指定通知方法的参数名称,要求表达式中必须有描述args的语句
-->
<aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>


执行时机
切入点方法执行是否产生异常,它都会在返回之前执行。
细节
最终通知执行时,可以获取到通知方法的参数。同时它可以做一些清理操作。
最终通知的执行时机是在切入点方法(业务核心方法)执行完成之后,切入点方法返回之前执行。换句话说,无论

3.3.5 环绕通知

配置方式

<!--
	作用:
		用于配置环绕通知。
	出现位置:
		它只能出现在aop:aspect标签的内部
	属性:
		method:用于指定环绕通知的方法名称
		pointcut:用于指定切入点表达式
		pointcut-ref:用于指定切入点表达式的引用
		arg-names:用于指定环绕通知方法的参数名称,要求表达式中必须有描述args的语句
-->
<aop: around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>

特别说明

环绕通知,它是有别于前面四种通知类型外的特殊通知。前面四种通知(前置,后置,异常和最终)它们都是指定何时增强的通知类型。而环绕通知,它是Spring框架为我们提供的一种可以通过编码的方式,控制增强代码何时执行的通知类型。它里面借助的ProceedingloinPoint接口及其实现类,实现手动触发切入点方法的调用。

Proceeding]oinPoint接口介绍

image-20200831120433185

3.3.6 五种通知类型的完整案例

第一步:导入坐标

<dependencies>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-context</artifactId>
		<version>5.1.9.RELEASE</version>
    </dependency>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-aop</artifactId>
		<version>5.1.9.RELEASE</version>
	</dependency>
	<dependency>
		<groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
		<version>1.9.4</version>
    </dependency>
</dependencies>

第二步:基础代码

账户的业务层接口

// 账户的业务层接口
public interface AccountService {
    /**
     * 模拟保存操作
     */
    void save();

    /**
     * 模拟更新操作
     * @param i
     */
    void update(int i);

    /**
     * 模拟删除操作
     * @return
     */
    int delete();
}

账户业务层接口实现类

// 账户业务层接口实现类
public class AccountServiceImpl implements AccountService {
    @Override
    public void save() {
        System.out.println("保存账户.....");
        // 异常测试
    	int i = 1/0;
        
    }

    @Override
    public void update(int i) {
        System.out.println("更新账户....."+i);

    }

    @Override
    public int delete() {
        System.out.println("删除账户...0");
        return 999;
    }
}

日志的切面类(包含了 增强方法)

public class LogUtil {
    // 配置 前置增强:执行实现 在切入点方法执行前执行
    public void beforePrintLog(JoinPoint joinPoint) throws Throwable {
        // 获取切入点方法的参数
        Object[] args = joinPoint.getArgs();

        System.out.println("前置增强beforePrintLog="+ Arrays.toString(args));
    }
    // 配置 后置增强:执行时机 在切入点方法执行完毕后,执行;若切入点方法执行过程中产生了异常,后置增强不执行
    public void afterReturningPrintLog(JoinPoint joinPoint,Object rtValue) throws Throwable {
        System.out.println("后置增强afterReturningPrintLog="+ rtValue);

    }
    // 配置 异常增强:执行时机 在切入点方法执行过程中 抛出了异常
    public void afterThrowingPrintLog(JoinPoint joinPoint,Exception e) throws Throwable {
        System.out.println("异常增强:afterThrowingPrintLog="+e);
    }

    // 配置 最终增强:执行时机 在切入点方法执行完毕后,执行(无论是否有异常,最终增强都执行)
    public void afterPrintLog(JoinPoint joinPoint) throws Throwable {
        System.out.println("最终增强:afterPrintLog");

    }
    
    // 配置 环绕通知:包含了(前置增强,后置增强,异常增强,最终增强)
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        Object rtValue = null;

        try {
            // 配置 前置增强
            System.out.println("前置增强");

            // 获取方法执行时,所需的实际参数
            Object[] args = joinPoint.getArgs();
            // 原有的方法执行
            rtValue=joinPoint.proceed(args);

            // 配置 后置增强
            System.out.println("后置增强");
        } catch (Throwable throwable) {
            throwable.printStackTrace();

            // 配置 异常增强
            System.out.println("异常增强");
        } finally {

            // 配置 最终增强
            System.out.println("最终增强");
        }
        return rtValue;
    }
}

Spring配置文件

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

    <!-- 配置service -->
    <bean id="accountService" class="com.zhuxu.service.impl.AccountServiceImpl"/>
    <!-- 配置切面类(增强) -->
    <bean id="logUtil" class="com.zhuxu.utils.LogUtil"/>

    <!-- 配置AOP -->
    <aop:config>
        <!-- 配置 通用的 切入点表达式 -->
        <aop:pointcut id="pc" expression="execution(* com.zhuxu.service..*.*(..))"></aop:pointcut>
        <!-- 配置切面(织入) -->
        <aop:aspect id="logUtil" ref="logUtil">
            <!-- 配置 前置增强:执行时机  在切入点方法执行前执行 -->
            <aop:before method="beforePrintLog" pointcut-ref="pc"></aop:before>
            <!-- 配置 后置增强:执行时机 在切入点方法执行完毕后,执行;若切入点方法执行过程中产生了异常,后置增强不执行-->
            <aop:after-returning method="afterReturningPrintLog" pointcut-ref="pc" returning="rtValue"></aop:after-returning>
            <!-- 配置 异常增强:执行时机 在切入点方法执行过程中 抛出了异常,执行-->
            <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pc" throwing="e"></aop:after-throwing>

            <!-- 配置 最终增强:执行时机 在切入点方法执行完毕后,执行(无论是否有异常,最终增强都执行)-->
            <aop:after method="afterPrintLog" pointcut-ref="pc"></aop:after>
        </aop:aspect>
            
            <!-- 上边四种增强或环绕增强 二选一 -->
            
            <!-- 配置 环绕通知:包含了(前置增强,后置增强,异常增强,最终增强)-->
            <aop:around method="around" pointcut-ref="pc"></aop:around>
    </aop:config>
</beans>

测试类

package com.zhuxu.service;

import junit.framework.TestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * @author Lucky
 * @date 2020/8/31
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:ApplicationContext.xml"})
public class AccountServiceTest {

    // 自动注入
    @Autowired
    private AccountService accountService;

    @Test
    public void testSave() {
        accountService.save();
        
    }

    @Test
    public void testUpdate() {
        accountService.update(123);
    }

    @Test
    public void testDelete() {
        accountService.delete();
    }
}

第四节 基于注解的AOP配置

注解方式的AOP一定要采用环绕通知,否则发生报错后,最终增强在前,异常增强和后置增强在后,发生位置混乱

比如事务控制,先开启事务,然后转账,转账后先提交事务,再释放连接,如果不用环绕注解,刚转完帐就关闭了,无法提交

4.1 入门案例

4.1.1 导入坐标

<dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.1.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.1.9.RELEASE</version>
        </dependency>
    </dependencies>

4.1.2 基础代码

账户业务层接口

public interface AccountService {
    /**
     * 模拟保存操作
     */
    void save();

    /**
     * 模拟更新操作
     * @param i
     */
    void update(int i);

    /**
     * 模拟删除操作
     * @return
     */
    int delete();
}

账户业务层接口实现类

@Service
public class AccountServiceImpl implements AccountService {
    @Override
    public void save() {
        System.out.println("保存账户.....");
        int i = 1/0;
    }

    @Override
    public void update(int i) {
        System.out.println("更新账户....."+i);

    }

    @Override
    public int delete() {
        System.out.println("删除账户...");
        return 999;
    }
}

4.1.3 日志的切面类

@Component
// 设置切面类
@Aspect
/**
 * 替换掉的spring.xml代码
 *     <!-- 配置切面类(增强) -->
 *     <bean id="logUtil" class="com.zhuxu.utils.LogUtil"/>
 * */
public class LogUtil {

    // 配置通用的切入点表达式
    @Pointcut("execution(* com.zhuxu.service..*.*(..))")
    public void pointcut(){}

    // 配置 前置增强:执行实现 在切入点方法执行前执行
    // @Before("pointcut()")
    public void beforePrintLog(JoinPoint joinPoint) throws Throwable {
        // 获取切入点方法的参数
        Object[] args = joinPoint.getArgs();

        System.out.println("前置增强beforePrintLog="+ Arrays.toString(args));
    }
    // 配置 后置增强:执行时机 在切入点方法执行完毕后,执行;若切入点方法执行过程中产生了异常,后置增强不执行
    // @AfterReturning("pointcut()")
    public void afterReturningPrintLog(JoinPoint joinPoint) throws Throwable {
        System.out.println("后置增强afterReturningPrintLog=");

    }
    // @AfterThrowing("pointcut()")
    // 配置 异常增强:执行时机 在切入点方法执行过程中 抛出了异常
    public void afterThrowingPrintLog(JoinPoint joinPoint) throws Throwable {
        System.out.println("异常增强:afterThrowingPrintLog=");
    }

    // 配置 最终增强:执行时机 在切入点方法执行完毕后,执行(无论是否有异常,最终增强都执行)
    // @After("pointcut()")
    public void afterPrintLog(JoinPoint joinPoint) throws Throwable {
        System.out.println("最终增强:afterPrintLog");

    }


    // 配置 环绕通知:包含了(前置增强,后置增强,异常增强,最终增强)
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        Object rtValue = null;

        try {
            // 配置 前置增强
            System.out.println("前置增强");

            // 获取方法执行时,所需的实际参数
            Object[] args = joinPoint.getArgs();
            // 原有的方法执行
            rtValue=joinPoint.proceed(args);

            // 配置 后置增强
            System.out.println("后置增强");
        } catch (Throwable throwable) {
            throwable.printStackTrace();

            // 配置 异常增强
            System.out.println("异常增强");
        } finally {

            // 配置 最终增强
            System.out.println("最终增强");
        }
        return rtValue;
    }
}

4.1.4 spring配置文件

<!-- 开启SpringIOC容器的注解扫描 -->
    <context:component-scan base-package="com.zhuxu"></context:component-scan>
    <!--代替的代码
     配置service
    <bean id="accountService" class="com.zhuxu.service.impl.AccountServiceImpl"/>
    -->

    <!-- 开启SpringAOP的注解扫描-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

4.1.5 测试类

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

    // 自动注入
    @Autowired
    private AccountService accountService;

    @Test
    public void testSave() {
        accountService.save();
    }

    @Test
    public void testUpdate() {
        accountService.update(123);
    }

    @Test
    public void testDelete() {
        accountService.delete();
    }
}

4.2注解驱动开发AOP的使用

4.2.1写在最前

<!--开启spring对注解aop的支持-->
<aop:aspectj-autoproxy/>

4.2.2创建配置类

@Configuration
@ComponentScan( "com.itheima")
public class SpringConfiguration {
}

4.2.3注解配置Spring对注解AOP的支持

在使用注解驱动开发aop时,我们要明确的就是,是注解替换掉配置文件中的下面这行配置:

4.3注解详解

4.3.1用于开启注解AOP支持的

1)@EnableAspectJAutoProxy

4.3.2用于配置切面的

1)@Aspect

4.3.3用于配置切入点表达式的

  1. @Pointcut

4.3.4用于配置通知的

1)@Before
2)@AfterReturning
3)@AfterThrowing
4)@After
5) @Around

原文地址:https://www.cnblogs.com/anke-z/p/13592163.html