04_03_Spring||day03_Spring第三天||day03_Spring第三天(总结篇,原文很详细)

本文详细介绍了Spring框架中的AOP(面向切面编程)概念,包括动态代理机制、基于XML和注解的AOP配置、各种通知类型如前置、后置、异常、最终通知以及环绕通知的使用方法。
Spring第三天:AOP的相关概念
  • 细说一天的学习内容:
    1. 完善我们的account案例
    2. 分析案例中问题
    3. 回顾之前讲过的一个技术:动态代理
    4. 动态代理另一种实现方式
    5. 解决案例中的问题
    6. AOP的概念
    7. spring中的AOP相关术语
    8. spring中基于XML和注解的AOP配置
      • 前面都是介绍,这一步就是死的,需要掌握

1. 完善我们的account案例【了解】

  1. 涉及到金钱的问题
  2. 这里使用事务的,且每一次操作都算一次事务。但是还会出现中间错后,转账人掉钱,收账人每增钱的问题。
    在这里插入图片描述
  3. 事务控制都应该在业务层
  4. 创建一个连接的工具类,它用于从数据源中获取一个连接,并且实现和线程的绑定。
    public class ConnectionUtils {
    
        private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
    
        private DataSource dataSource;
    
        public void setDataSource(DataSource dataSource) {
            this.dataSource = dataSource;
        }
    
        /**
         * 获取当前线程上的连接
         * @return
         */
        public Connection getThreadConnection() {
            try{
                //1.先从ThreadLocal上获取
                Connection conn = tl.get();
                //2.判断当前线程上是否有连接
                if (conn == null) {
                    //3.从数据源中获取一个连接,并且存入ThreadLocal中
                    conn = dataSource.getConnection();
                    tl.set(conn);
                }
                //4.返回当前线程上的连接
                return conn;
            }catch (Exception e){
                throw new RuntimeException(e);
            }
        }
    
        /**
         * 把连接和线程解绑
         * 由5衍生出来的细节:当线程还回去后,已经没有该线程了,再次获取该线程来判断上面有没有连接池(有),但是该连接已经不能用了
         * 写好这个方法,到时候5里面调用就可以了
         */
        public void removeConnection(){
            tl.remove();
        }
    }
    
  5. 创建一个和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接。
    public class TransactionManager {
    
        private ConnectionUtils connectionUtils;
    
        public void setConnectionUtils(ConnectionUtils connectionUtils) {
            this.connectionUtils = connectionUtils;
        }
    
        /**
         * 开启事务
         */
        public  void beginTransaction(){
            try {
                connectionUtils.getThreadConnection().setAutoCommit(false);
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    
        /**
         * 提交事务
         */
        public  void commit(){
            try {
                connectionUtils.getThreadConnection().commit();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    
        /**
         * 回滚事务
         */
        public  void rollback(){
            try {
                connectionUtils.getThreadConnection().rollback();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    
    
        /**
         * 释放连接
         */
        public  void release(){
            try {
                connectionUtils.getThreadConnection().close();//还回连接池中
                connectionUtils.removeConnection();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
    
  6. 编写业务层和持久层事务控制代码并配置Spring的IOC
    1. 通过上面4,5步骤,我们就可以在业务层的实现类中加上
          private TransactionManager txManager;
          
          及set方法
      
    2. 就可以在后面的方法中写事务了处理了比如:
      在这里插入图片描述
  7. bean.xml中连接池删掉后,修改持久层
    在这里插入图片描述
  8. 最后将刚才写好的依赖都注入好
    在这里插入图片描述
    在这里插入图片描述
  • 总结:虽然成功了,但是代码很臃肿,相互也有依赖

2. 回顾之前讲过的一个技术:动态代理机制

  • 使用中间有代理的机制,
    • 可以实现对我们整个业务流程的隔层。
      在这里插入图片描述

3. 动态代理另一种实现方式

  • 注:匿名内部类访问外部成员变量时,要加final
  1. 模拟一个消费者(使用动态代理)
    public class Client {

        public static void main(String[] args) {
            final Producer producer = new Producer();
    
            /**
             * 动态代理:
             *  特点:字节码随用随创建,随用随加载
             *  作用:不修改源码的基础上对方法增强
             *  分类:
             *      基于接口的动态代理
             *      基于子类的动态代理
             *  基于接口的动态代理:
             *      涉及的类:Proxy
             *      提供者:JDK官方
             *  如何创建代理对象:
             *      使用Proxy类中的newProxyInstance方法
             *  创建代理对象的要求:
             *      被代理类最少实现一个接口,如果没有则不能使用
             *  newProxyInstance方法的参数:
             *      ClassLoader:类加载器
             *          它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。
             *      Class[]:字节码数组
             *          它是用于让代理对象和被代理对象有相同方法。固定写法。
             *      InvocationHandler:用于提供增强的代码
             *          它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
             *          此接口的实现类都是谁用谁写。
             */
           IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
                    producer.getClass().getInterfaces(),
                    new InvocationHandler() {
                        /**
                         * 作用:执行被代理对象的任何接口方法都会经过该方法
                         * 方法参数的含义
                         * @param proxy   代理对象的引用
                         * @param method  当前执行的方法
                         * @param args    当前执行方法所需的参数
                         * @return        和被代理对象方法有相同的返回值
                         * @throws Throwable
                         */
                        @Override
                        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(10000f);
        }
    }
  1. 对生产厂家要求的接口
    /**
     * 对生产厂家要求的接口
     */
    public interface IProducer {
    
        /**
         * 销售
         * @param money
         */
        public void saleProduct(float money);
    
        /**
         * 售后
         * @param money
         */
        public void afterService(float money);
    }
  1. 模拟生产者
    /**
     * 一个生产者
     */
    public class Producer implements IProducer{
    
        /**
         * 销售
         * @param money
         */
        public void saleProduct(float money){
            System.out.println("销售产品,并拿到钱:"+money);
        }
    
        /**
         * 售后
         * @param money
         */
        public void afterService(float money){
            System.out.println("提供售后服务,并拿到钱:"+money);
        }
    }
  1. 总结:
    • 实现了但是缺点是:
      • 如果不实现任何接口,会出现代理异常

4. 基于子类的动态代理

  1. 首先导入jar包
  2. 涉及的类:Enhancer
  3. 提供者:第三方cglib库
  4. 代码演示:
    package com.itheima.cglib;
    
    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    import java.lang.reflect.Method;
    
    /**
     * 模拟一个消费者
     */
    public class Client {
    
        public static void main(String[] args) {
            final Producer producer = new Producer();
    
            /**
             * 动态代理:
             *  特点:字节码随用随创建,随用随加载
             *  作用:不修改源码的基础上对方法增强
             *  分类:
             *      基于接口的动态代理
             *      基于子类的动态代理
             *  基于子类的动态代理:
             *      涉及的类:Enhancer
             *      提供者:第三方cglib库
             *  如何创建代理对象:
             *      使用Enhancer类中的create方法
             *  创建代理对象的要求:
             *      被代理类不能是最终类
             *  create方法的参数:
             *      Class:字节码
             *          它是用于指定被代理对象的字节码。
             *
             *      Callback:用于提供增强的代码
             *          它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
             *          此接口的实现类都是谁用谁写。
             *          我们一般写的都是该接口的子接口实现类:MethodInterceptor
             */
            Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() {
                /**
                 * 执行北地阿里对象的任何方法都会经过该方法
                 * @param proxy
                 * @param method
                 * @param args
                 *    以上三个参数和基于接口的动态代理中invoke方法的参数是一样的
                 * @param methodProxy :当前执行方法的代理对象
                 * @return
                 * @throws Throwable
                 */
                @Override
                public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                    //提供增强的代码
                    Object returnValue = null;
    
                    //1.获取方法执行的参数
                    Float money = (Float)args[0];
                    //2.判断当前方法是不是销售
                    if("saleProduct".equals(method.getName())) {
                        returnValue = method.invoke(producer, money*0.8f);
                    }
                    return returnValue;
                }
            });
            cglibProducer.saleProduct(12000f);
        }
    }
    
    

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

  1. Service中不在需要代理对象(实现分离)
    代码演示:
    //创建Service的代理对象工厂
    package com.itheima.factory;

    import com.itheima.service.IAccountService;
    import com.itheima.utils.TransactionManager;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    /**
     * 用于创建Service的代理对象的工厂
     */
    public class BeanFactory {
    
        private IAccountService accountService;
    
        private TransactionManager txManager;
    
        public void setTxManager(TransactionManager txManager) {
            this.txManager = txManager;
        }
    
    
        public final void setAccountService(IAccountService accountService) {
            this.accountService = accountService;
        }
    
        /**
         * 获取Service代理对象
         * @return
         */
        public IAccountService getAccountService() {
            return (IAccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
                    accountService.getClass().getInterfaces(),
                    new InvocationHandler() {
                        /**
                         * 添加事务的支持
                         *
                         * @param proxy
                         * @param method
                         * @param args
                         * @return
                         * @throws Throwable
                         */
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
                            if("test".equals(method.getName())){
                                return method.invoke(accountService,args);
                            }
    
                            Object rtValue = null;
                            try {
                                //1.开启事务
                                txManager.beginTransaction();
                                //2.执行操作
                                rtValue = method.invoke(accountService, args);
                                //3.提交事务
                                txManager.commit();
                                //4.返回结果
                                return rtValue;
                            } catch (Exception e) {
                                //5.回滚操作
                                txManager.rollback();
                                throw new RuntimeException(e);
                            } finally {
                                //6.释放连接
                                txManager.release();
                            }
                        }
                    });
    
        }
    }

    //配置bean.xml中的beanfactory和配置代理的Service
    <!--配置代理的service-->
    <bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"></bean>

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

    import com.itheima.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;
    
    /**
     * 使用Junit单元测试:测试我们的配置
     */
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = "classpath:bean.xml")
    public class AccountServiceTest {
    
        @Autowired
        @Qualifier("proxyAccountService")
        private  IAccountService as;
    
        @Test
        public  void testTransfer(){
            as.transfer("aaa","bbb",100f);
        }
    
    }

6. AOP的相关概念【了解】

  1. 作用:在程序运行期间,不修改源码对已有方法进行增强。
  2. 优势:
    • 减少重复代码
    • 提高开发效率
    • 维护方便

7. Spring中的AOP【重点掌握】

  1. 说明:学习Spring中的aop,是通过配置的方式,实现上面代码编写的功能
    • 配置方式:注解和XML
  2. AOP相关术语【现在作为了解】
    1. Joinpoint(连接点):
      • 所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的连接点。
    2. Pointcut(切入点):
      • 所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。
    3. Advice(通知/增强):
      • 所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。
      • 通知的类型: 前置通知,后置通知,异常通知,最终通知,环绕通知。
    4. Introduction(引介)【暂时用不到】:
      • 引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方
        法或 Field。
    5. Target(目标对象):
      • 代理的目标对象。也就是被代理对象
    6. Weaving(织入):
      • 是指把增强应用到目标对象来创建新的代理对象的过程。
      • spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。
    7. Proxy(代理):
      • 一个类被 AOP 织入增强后,就产生一个结果代理类。
    8. Aspect(切面):
      • 是切入点和通知(引介)的结合。
  3. 学习Spring中的AOP要明确的事
    在这里插入图片描述

8. Spring基于XML的AOP

  1. 配置步骤
  2. 实现代码:
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <!-- 配置Spring的Ioc,把service对象配置进来-->
        <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
    
        <!--spring中基于XML的AOP配置步骤#########
            1、把通知Bean也交给spring来管理
            2、使用aop:config标签表明开始AOP的配置
            3、使用aop:aspect标签表明配置切面
                    id属性:是给切面提供一个唯一标识
                    ref属性:是指定通知类bean的Id。
            4、在aop:aspect标签的内部使用对应标签来配置通知的类型
                   我们现在示例是让printLog方法在切入点方法执行之前之前:所以是前置通知
                   aop:before:表示配置前置通知
                        method属性:用于指定Logger类中哪个方法是前置通知
                        pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强
    
                切入点表达式的写法:
                    关键字:execution(表达式)
                    表达式:
                        访问修饰符  返回值  包名.包名.包名...类名.方法名(参数列表)
                    标准的表达式写法:
                        public void com.itheima.service.impl.AccountServiceImpl.saveAccount()
                    访问修饰符可以省略
                        void com.itheima.service.impl.AccountServiceImpl.saveAccount()
                    返回值可以使用通配符,表示任意返回值
                        * com.itheima.service.impl.AccountServiceImpl.saveAccount()
                    包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*.
                        * *.*.*.*.AccountServiceImpl.saveAccount())
                    包名可以使用..表示当前包及其子包
                        * *..AccountServiceImpl.saveAccount()
                    类名和方法名都可以使用*来实现通配
                        * *..*.*()
                    参数列表:
                        可以直接写数据类型:
                            基本类型直接写名称           int
                            引用类型写包名.类名的方式   java.lang.String
                        可以使用通配符表示任意类型,但是必须有参数
                        可以使用..表示有无参数均可,有参数可以是任意类型
                    全通配写法:****
                        * *..*.*(..)
    
                    实际开发中切入点表达式的通常写法:
                        切到业务层实现类下的所有方法
                            * com.itheima.service.impl.*.*(..)
        -->
    
        <!-- 配置Logger类 -->
        <bean id="logger" class="com.itheima.utils.Logger"></bean>
    
        <!--配置AOP-->
        <aop:config>
            <!--配置切面 -->
            <aop:aspect id="logAdvice" ref="logger">
                <!-- 配置通知的类型,并且建立通知方法和切入点方法的关联-->
                <aop:before method="printLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:before>
            </aop:aspect>
        </aop:config>
    
    </beans>
    
    • 实际开发中切入点表达式的通常写法
      • 切到业务层实现类下的所有方法
        • com.itheima.service.impl..(…)
  3. 总结图:
    在这里插入图片描述
  4. 小细节
  • 在建立工程时候导入的aspactj就是用来负责解析切入点表达式

9 四种常用的通知类型

  1. 前置,后置,异常,最终通知
  2. 代码演示:
    • 用于记录日志的工具类,它里面提供了公共的代码
      public class Logger {
      
          /**
           * 前置通知
           */
          public  void beforePrintLog(){
              System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。");
          }
      
          /**
           * 后置通知
           */
          public  void afterReturningPrintLog(){
              System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。");
          }
          /**
           * 异常通知
           */
          public  void afterThrowingPrintLog(){
              System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。");
          }
      
          /**
           * 最终通知
           */
          public  void afterPrintLog(){
              System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。");
          }
      }
      
    • 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
              http://www.springframework.org/schema/beans/spring-beans.xsd
              http://www.springframework.org/schema/aop
              http://www.springframework.org/schema/aop/spring-aop.xsd">
      
          <!-- 配置srping的Ioc,把service对象配置进来-->
          <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
      
      
          <!-- 配置Logger类 -->
          <bean id="logger" class="com.itheima.utils.Logger"></bean>
      
          <!--配置AOP-->
          <aop:config>
              <!-- 配置切入点表达式 id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容
                    此标签写在aop:aspect标签内部只能当前切面使用。
                    它还可以写在aop:aspect外面,此时就变成了所有切面可用
                -->
              <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
              <!--配置切面 -->
              <aop:aspect id="logAdvice" ref="logger">
                  <!-- 配置前置通知:在切入点方法执行之前执行
                  <aop:before method="beforePrintLog" pointcut-ref="pt1" ></aop:before>-->
      
                  <!-- 配置后置通知:在切入点方法正常执行之后值。它和异常通知永远只能执行一个
                  <aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>-->
      
                  <!-- 配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个
                  <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>-->
      
                  <!-- 配置最终通知:无论切入点方法是否正常执行它都会在其后面执行
                  <aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>-->
      
                  <!-- 配置环绕通知 详细的注释请看Logger类中-->
                  <aop:around method="aroundPringLog" pointcut-ref="pt1"></aop:around>
              </aop:aspect>
          </aop:config>
      
      </beans>
      
      1. 配置前置通知:在切入点方法执行之前执行
      2. 配置后置通知:在切入点方法正常执行之后值。它和异常通知永远只能执行一个
      3. 配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个
      4. 配置最终通知:无论切入点方法是否正常执行它都会在其后面执行
    • 测试AOP的配置
      public class AOPTest {
      
          public static void main(String[] args) {
              //1.获取容器
              ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
              //2.获取对象
              IAccountService as = (IAccountService)ac.getBean("accountService");
              //3.执行方法
              as.saveAccount();
          }
      }
      
  3. 正常情况下
  4. 出现异常的情况下
  5. 注:
    • 事务要么提交要么回滚,所以不管什么情况都只有4条。

10 通用化切入点表达式

  1. 通用化切入点思路
    1. 每个通知都要写一次切入点表达式,太冗余。
    2. 就写一个公用的,其他通知ref引入该id即可
    3. 在aop:aspect内部配置的话,只能当前切面使用
    4. 所以配置到外面(要放到aop:aspect上面
      • 原因:导入了约束,就要跟着约束走
            //约束说要求必须放到上面
            <beans xmlns="http://www.springframework.org/schema/beans"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xmlns:aop="http://www.springframework.org/schema/aop"
               xsi:schemaLocation="http://www.springframework.org/schema/beans
                http://www.springframework.org/schema/beans/spring-beans.xsd
                http://www.springframework.org/schema/aop
                http://www.springframework.org/schema/aop/spring-aop.xsd">
        
  2. 代码演示
        <!--配置AOP-->
        <aop:config>
            <!-- 配置切入点表达式 id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容
                  此标签写在aop:aspect标签内部只能当前切面使用。
                  它还可以写在aop:aspect外面,此时就变成了所有切面可用
              -->
            <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
            <!--配置切面 -->
            <aop:aspect id="logAdvice" ref="logger">
                <!-- 配置前置通知:在切入点方法执行之前执行
                <aop:before method="beforePrintLog" pointcut-ref="pt1" ></aop:before>-->
    
                <!-- 配置后置通知:在切入点方法正常执行之后值。它和异常通知永远只能执行一个
                <aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>-->
    
                <!-- 配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个
                <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>-->
    
                <!-- 配置最终通知:无论切入点方法是否正常执行它都会在其后面执行
                <aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>-->
    
            </aop:aspect>
        </aop:config>
    

11 Spring中的环绕通知

  1. Spring中的环绕通知的概念
    • 它是Spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
    • 前面我们是通过固定的配置
  2. 代码演示
    • Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法【重点掌握】
    • 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
                http://www.springframework.org/schema/beans/spring-beans.xsd
                http://www.springframework.org/schema/aop
                http://www.springframework.org/schema/aop/spring-aop.xsd">
        
            <!-- 配置srping的Ioc,把service对象配置进来-->
            <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
        
        
            <!-- 配置Logger类 -->
            <bean id="logger" class="com.itheima.utils.Logger"></bean>
        
            <!--配置AOP-->
            <aop:config>
                <!-- 配置切入点表达式 id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容
                      此标签写在aop:aspect标签内部只能当前切面使用。
                      它还可以写在aop:aspect外面,此时就变成了所有切面可用
                  -->
                <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
                <!--配置切面 -->
                <aop:aspect id="logAdvice" ref="logger">
                    <!-- 配置前置通知:在切入点方法执行之前执行
                    <aop:before method="beforePrintLog" pointcut-ref="pt1" ></aop:before>-->
        
                    <!-- 配置后置通知:在切入点方法正常执行之后值。它和异常通知永远只能执行一个
                    <aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>-->
        
                    <!-- 配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个
                    <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>-->
        
                    <!-- 配置最终通知:无论切入点方法是否正常执行它都会在其后面执行
                    <aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>-->
        
                    <!-- 配置环绕通知 详细的注释请看Logger类中-->
                    <aop:around method="aroundPringLog" pointcut-ref="pt1"></aop:around>
                </aop:aspect>
            </aop:config>
        
        </beans>
    
    • Logger.java
        package com.itheima.utils;
    
        import org.aspectj.lang.ProceedingJoinPoint;
        
        /**
         * 用于记录日志的工具类,它里面提供了公共的代码
         */
        public class Logger {
        
            /**
             * 前置通知
             */
            public  void beforePrintLog(){
                System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。");
            }
        
            /**
             * 后置通知
             */
            public  void afterReturningPrintLog(){
                System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。");
            }
            /**
             * 异常通知
             */
            public  void afterThrowingPrintLog(){
                System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。");
            }
        
            /**
             * 最终通知
             */
            public  void afterPrintLog(){
                System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。");
            }
        
            /**
             * 环绕通知
             * 问题:
             *      当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。
             * 分析:
             *      通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。
             * 解决:
             *      Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。
             *      该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。
             *
             * spring中的环绕通知:
             *      它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
             */
            public Object aroundPringLog(ProceedingJoinPoint pjp){
                Object rtValue = null;
                try{
                    Object[] args = pjp.getArgs();//得到方法执行所需的参数
        
                    System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置");
        
                    rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)
        
                    System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置");
        
                    return rtValue;
                }catch (Throwable t){
                    System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常");
                    throw new RuntimeException(t);
                }finally {
                    System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终");
                }
            }
        }
    
    

12 Spring基于注解的AOP配置

  1. 用注解方式,来使用四种常用的通知的时候顺序是不正常的,顺序是前置->业务->最终->后置。
    • 所以使用常用的通知时要十分慎重。
  2. 解决方法:使用环绕通知的方式来改变顺序,从而解决了问题。
  • 注:四种常用通知和环绕通知是互斥的。不能一起使用。
  1. 代码演示:
    • pom.xml
        <packaging>jar</packaging>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>5.0.2.RELEASE</version>
            </dependency>
    
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjweaver</artifactId>
                <version>1.8.7</version>
            </dependency>
        </dependencies>
    
    • bean.xml
        <?xml version="1.0" encoding="UTF-8"?>
        <beans xmlns="http://www.springframework.org/schema/beans"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xmlns:aop="http://www.springframework.org/schema/aop"
               xmlns:context="http://www.springframework.org/schema/context"
               xsi:schemaLocation="http://www.springframework.org/schema/beans
                http://www.springframework.org/schema/beans/spring-beans.xsd
                http://www.springframework.org/schema/aop
                http://www.springframework.org/schema/aop/spring-aop.xsd
                http://www.springframework.org/schema/context
                http://www.springframework.org/schema/context/spring-context.xsd">
        
            <!-- 配置spring创建容器时要扫描的包-->
            <context:component-scan base-package="com.itheima"></context:component-scan>
        
            <!-- 配置spring开启注解AOP的支持 -->
            <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
        </beans>
    
    • Logger.java
        package com.itheima.utils;
    
        import org.aspectj.lang.ProceedingJoinPoint;
        import org.aspectj.lang.annotation.*;
        import org.springframework.stereotype.Component;
        
        /**
         * 用于记录日志的工具类,它里面提供了公共的代码
         */
        @Component("logger")
        @Aspect//表示当前类是一个切面类
        public class Logger {
        
            @Pointcut("execution(* com.itheima.service.impl.*.*(..))")
            private void pt1(){}
        
            /**
             * 前置通知
             */
        //    @Before("pt1()")
            public  void beforePrintLog(){
                System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。");
            }
        
            /**
             * 后置通知
             */
        //    @AfterReturning("pt1()")
            public  void afterReturningPrintLog(){
                System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。");
            }
            /**
             * 异常通知
             */
        //    @AfterThrowing("pt1()")
            public  void afterThrowingPrintLog(){
                System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。");
            }
        
            /**
             * 最终通知
             */
        //    @After("pt1()")
            public  void afterPrintLog(){
                System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。");
            }
        
            /**
             * 环绕通知
             * 问题:
             *      当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。
             * 分析:
             *      通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。
             * 解决:
             *      Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。
             *      该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。
             *
             * spring中的环绕通知:
             *      它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
             */
            @Around("pt1()")
            public Object aroundPringLog(ProceedingJoinPoint pjp){
                Object rtValue = null;
                try{
                    Object[] args = pjp.getArgs();//得到方法执行所需的参数
        
                    System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置");
        
                    rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)
        
                    System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置");
        
                    return rtValue;
                }catch (Throwable t){
                    System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常");
                    throw new RuntimeException(t);
                }finally {
                    System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终");
                }
            }
        }
    
    
  • 当不想在使用XML配置方式的时候

13 详细的原文

详页连接

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

狂野小白兔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值