zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    批量修改文件的编码格式至UTF-8
    springboot搭建
    Redit集群搭建-Sentinel模式搭建
    Java并发编程:深入剖析ThreadLocal
    Hibernate常见问题 No row with the given identifier exists问题的解决办法及解决
    vector删除元素与清除内存空洞
    BZOJ 1003 [ZJOI2006]物流运输trans SPFA+DP
    Mybatis+Oracle批处理
    【日常学习】【线性DP】codevs1044 拦截导弹题解
    hdu5353 Average
  • 原文地址:https://www.cnblogs.com/anke-z/p/13592163.html
Copyright © 2011-2022 走看看