Spring 梳理-AOP

  1. 界面应用场景
    1. 日志、声明式事务、安全、缓存
  2. AOP功能演化图
    1.   
    2. 图片引用地址:https://www.cnblogs.com/best/p/5679656.html
  3. AOP设计模式-代理模式
    1. 静态代理:手动编写代理类,手动编写代码拦截每个方法。工作量极大。
    2. JDK代理:利用反射,实现InvocationHandler接口。 业务类需要实现“业务接口”,利用反射技术代理该“业务接口”
    3. 动态代理:利用cglib。cglib是一种动态代码生成器,可以在运行期动态生成代理类
  4. AOP术语
    1. 关注点:重复代码就叫做关注点;
    2. 切面: 关注点形成的类,就叫切面(类)!是通知和节点的集合,这个集合决定了切面:是什么,在何时和何处完成其功能。
    3. 通知(advice):切面类函数。切面要完成的工作(切面类中的方法)。通知定义了切面是什么以及何时使用。Spring的通知有5种类型:before、after、after-returning、after-throwing和around这五种类型。
    4. 连接点(joinpoint):程序事件。连接点表示在何种操作发生时应用切面。比如方法调用时、修改字段时和抛出异常时等。我们的应用可能有数以千计的时机,这些时机被称为连接点。连接点是应用执行过程中能够插入切面的一个点。
    5. 切点(拦截的作用)(pointcut): 执行目标对象方法,动态植入切面代码。可以通过切入点表达式,指定拦截哪些类的哪些方法; 给指定的类在运行的时候植入切面类代码。切点有助于缩小切面所通知的连接点的范围。如果说通知定义了切面“是什么”、“何时”,那么切点就定义了“何处”。
      1. 可能应用有很多事件(连接点)可以用来对外提供“触角”,但通过某种方式选定的连接点与通知发生作用,这个连接点成为切点。
  5. Spring AOP构建在动态代理基础之上,因此,Spring对AOP的支持局限于方法拦截
  6. Spring 项目与AspectJ项目 之间有大量的合作;Spring 使用 AspectJ项目的注解语法,实现Spring自己基于代理的AOP。也就是Spring与AspectJ在注解的语法上是相同的,但底层实现是不同的,Spring底层只支持方法拦截,无法在bean创建时应用通知将;,AspectJ却支持构造器、属性、方法拦截。
    1. Spring AOP的切面类的编写使用纯java语法;
    2. AspectJ AOP的切面类编写使用java语言扩展,自成一套新的语法。
    3. Spring在运行时才创建AOP代理对象。
  7. 联合使用@Component@Aspect,将切面类以一个普通的全局单实例bean的形式注入到容器中,;可以在其他业务类型的bean中使用@Autowired注解 将切面类bean注入到普通业务bean中,获取其运行时状态值。
  8. Spring使用AsjpectJ定义切点表达式
    1. 明确具体方法  execution( * cn.jt.ClassA.run(..))
    2. 限定切点所属的包  execution(* cn.jt.ClassA.run(..)) within(cn.*)
    3. 限定切点所在bean  execution(* cn.jt.ClassA.run(..)) and bean(‘beanID’)
  9. 使用AOP,为已封装好的Java类,添加新的方法,实现类似Ruby的动态类的概念。但实际上,Spring将一个bean分拆到多个类中:原有类方法在一个类中,新添加的方法在一个类中。
  10. XML方式实现AOP编程 :在没有源码,或者不想把AspectJ注解污染到代码之中的情况下使用
    1. 步骤
      1.          1) 引入jar文件  【aop 相关jar, 4个】

                 2) 引入aop名称空间

                 3)aop 配置

                           * 配置切面类 (重复执行代码形成的类)

                           * aop配置

                                    拦截哪些方法 / 拦截到方法后应用通知代

    2. 示例
      1.   
        <?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:p="http://www.springframework.org/schema/p"
            xmlns:context="http://www.springframework.org/schema/context"
            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/context
                http://www.springframework.org/schema/context/spring-context.xsd
                http://www.springframework.org/schema/aop
                http://www.springframework.org/schema/aop/spring-aop.xsd">
            
            <!-- dao 实例   在这里配置后就不用在类中使用注解 -->
            <bean id="aDao" class="xx"></bean>
            <bean id="bDao" class="yy"></bean>
            
            <!-- 切面类 -->
            <bean id="aop" class="aopClass1"></bean>
            
            <!-- Aop配置 -->
            <aop:config>
                <!-- 定义一个切入点表达式: 拦截哪些方法 -->
                <aop:pointcut expression="execution(* aopClass1.*.*(..))" id="pt"/>
                <!-- 切面 -->
                <aop:aspect ref="aop">
                    <!-- 环绕通知 -->
                    <aop:around method="around" pointcut-ref="pt"/>
                    <!-- 前置通知: 在目标方法调用前执行 -->
                    <aop:before method="begin" pointcut-ref="pt"/>
                    <!-- 后置通知: -->
                    <aop:after method="after" pointcut-ref="pt"/>
                    <!-- 返回后通知 -->
                    <aop:after-returning method="afterReturning" pointcut-ref="pt"/>
                    <!-- 异常通知 -->
                    <aop:after-throwing method="afterThrowing" pointcut-ref="pt"/>
                    
                </aop:aspect>
            </aop:config>
        </beans>

         

  11. 注解方式实现AOP编程
    1.  先引入aop相关jar文件                                                

               spring-aop-3.2.5.RELEASE.jar  

          aopalliance.jar                      

            aspectjweaver.jar                    

          aspectjrt.jar

    2. bean.xml中引入aop名称空间       

    3. 开启aop注解                   

    4. 使用注解
      1.   

        @Aspect                                                              指定一个类为切面类             

        @Pointcut("execution(* cn.itcast.e_aop_anno.*.*(..))")  指定切入点表达式

        @Before("pointCut_()")                                    前置通知: 目标方法之前执行

        @After("pointCut_()")                                         后置通知:目标方法之后执行(始终执行)

        @AfterReturning("pointCut_()")                         返回后通知: 执行方法结束前执行(异常不执行)

        @AfterThrowing("pointCut_()")                            异常通知:  出现异常时候执行

        @Around("pointCut_()")                                      环绕通知: 环绕目标方法执行

    5. 关于@Pointcut注解
      1. 是一个节点表达式,通过在一个空函数run()上使用@Pointcut注解,我们实际上扩展了切点表达式语言,这样就可以在任何切点表达式中使用run(),否则,需要在每个切点表达式使用那个更长的表达式。例如:
      2.     @AspectJ
            public class Audience{
                @Pointcut("execution(** cn.jt.run(..))")
                public void run(){}
                
                @Before("run()")
                public void beforeRun(ProceedingJoinPoint jp){
                    System.out.println("berfor");
                    jp.proceed();
                    System.out.println("after");
                }
                
                @Around("run()")
                public void watchRun(ProceedingJoinPoint jp){
                    System.out.println("berfor");
                    jp.proceed();
                    System.out.println("after");
                }
        
            }


        相当于:

      3.     @AspectJ
            public class Audience{
                @Before("execution(** cn.jt.run(..))")
                public void beforeRun(){
                    System.out.println("berfor");
                }
                
                @Around("execution(** cn.jt.run(..))")
                public void watchRun(ProceedingJoinPoint jp){
                    System.out.println("berfor");
                    jp.proceed();
                    System.out.println("after");
                }
        
            }

         

    6. 关于环绕通知:
      1. 环绕通知需要额外的ProceedingJoinPoint类型参数
      2. 如果不调用jp.proceed()方法,那么通知会阻塞对被通知方法的调用;或者调用多次,类似与“失败后重试”这样的业务逻辑。
    7. 将业务类方法中的参数,传递到通知(切面类方法)中。(使用 args 表达式)
      1. @Component
        @Aspect
        public class Audience {
            
            private Map<Integer, Integer> trackCounts = new HashMap<>();
            
            @Pointcut("execution(** concert.JayPerform.playTrack(int)) && args(trackNum)")  //<1>
            public void track(int trackNum) {}  //<2>
            
            @AfterReturning("track(trackNum)")  //<3>
            public void countTrack(int trackNum)    //<4>
            {
                
                int currentCount = getPlayCount(trackNum);
                trackCounts.put(trackNum, currentCount+1);
                System.out.println("------- 这首歌播放了"+(currentCount+1)+"");
            }
            
            public int getPlayCount(int trackNumber)
            {
                return trackCounts.containsKey(trackNumber)?trackCounts.get(trackNumber):0;
            }
        }

         

      2. 代码中 <1>、<2>、<3>、<4> 处,int型参数的名称都是trackNum,这样保证了从命名切点到通知方法的参数转移。并且,这里的参数trackNum与concert.JayPerform.playTrack(int trackNum) 的参数命名相同。
        经过实验,发现这4处的参数名称与concert.JayPerform.playTrack(int trackNum)中的参数名称不必相同,只要<1>与<2>处参数名称相同、<3>与<4>处参数名称相同即可
    8. 示例
      1. bean.xml
        1.   
          <?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:p="http://www.springframework.org/schema/p"
              xmlns:context="http://www.springframework.org/schema/context"
              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/context
                  http://www.springframework.org/schema/context/spring-context.xsd
                  http://www.springframework.org/schema/aop
                http://www.springframework.org/schema/aop/spring-aop.xsd">
              
              <!-- 使用注解时要开启注解扫描 要扫描的包 -->
              <context:component-scan base-package="cn.jt"></context:component-scan>
              
              <!-- 开启aop注解方式 -->
              <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
          </beans>

           

      2. Java代码
        @Component  //加入IOC容器
        @Aspect  // 指定当前类为切面类
        public class Aop {
        
            // 指定切入点表达式: 拦截哪些方法; 即为哪些类生成代理对象
          //解释@Pointcut("execution(* cn.jt.*.*(..))")
          //@Pointcut("execution(*    切入点表达式固定写法, cn.itcast.e_aop_anno表示包.类名(可以用*表示包下所有的类).方法名(可以用*表示类下所有的方法)(..)表示参数可以用..
            @Pointcut("execution(* cn.jt.*.*(..))")
            public void pointCut_(){
            }
            
          //@Before("execution(* cn.jt.*.*(..))")每个方法需要写相同的引用,所以将相同的部分抽取到一个空的方法中pointCut_(),
            // 前置通知 : 在执行目标方法之前执行
            @Before("pointCut_()")
            public void begin(){
                System.out.println("开始事务/异常");
            }
            
            // 后置/最终通知:在执行目标方法之后执行  【无论是否出现异常最终都会执行】
            @After("pointCut_()")
            public void after(){
                System.out.println("提交事务/关闭");
            }
            
            // 返回后通知: 在调用目标方法结束后执行 【出现异常不执行】
            @AfterReturning("pointCut_()")
            public void afterReturning() {
                System.out.println("afterReturning()");
            }
            
            // 异常通知: 当目标方法执行异常时候执行此关注点代码
            @AfterThrowing("pointCut_()")
            public void afterThrowing(){
                System.out.println("afterThrowing()");
            }
            
            // 环绕通知:环绕目标方式执行
            @Around("pointCut_()")
            public void around(ProceedingJoinPoint pjp) throws Throwable{
                System.out.println("环绕前....");
                pjp.proceed();  // 执行目标方法
                System.out.println("环绕后....");
            }
            
        }

        测试类

        public class App {
            
            ApplicationContext ac = 
                new ClassPathXmlApplicationContext("cn/jt/bean.xml");
        
            // 目标对象有实现接口,spring会自动选择“JDK代理”
            @Test
            public void testApp() {
                IUserDao userDao = (IUserDao) ac.getBean("userDao");
                System.out.println(userDao.getClass());
                userDao.save();
            }
            
            // 目标对象没有实现接口, spring会用“cglib代理”
            @Test
            public void testCglib() {
                OrderDao orderDao = (OrderDao) ac.getBean("orderDao");
                System.out.println(orderDao.getClass());
                orderDao.save();
            }

         

          1.     

转载于:https://www.cnblogs.com/jiangtao1218/p/9695015.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值