• 第4章 面向切面编程的Spring



    1、在软件系统中,有很多功能是被动调用的(这些功能不是主要关注的),在很多地方都需要明确的调用。这种被分散到多处的功能称为“横切关注点”。将横切关注点和业务逻辑分析是AOP要解决的问题。AOP将横切关注点模块化为了一些特殊的类,这些类称为切面。(以声明的方法定义横切关注点要以何时何地调用)
    2、AOP术语:
    1:通知(advice)
    切面必须要完成的工作称之为通知。
    通知除了描述要完成的工作之外,还描述了何时执行这个工作,它应该应用在什么方法之前,什么方法之后(方法连接点)。
    5种类型的通知:
    1:前置通知:在目标方法调用之前调用通知功能
    2:后置通知:在目标方法完成之后调用通知,此时不会关心方法的输出是什么
    3:返回通知:在目录方法成功执行之后调用的通知
    4:异常通知:在目标方法抛出异常时调用的通知
    5:环绕通知:包含了被通知的方法,在被通知方法执行之前和执行之后执行的自定义行为。
    2:连接点(Join point)
    应用执行过程中能够插入切面的一个点,可以是一个方法调用时、抛出异常时、修改字段时(其实就是一个触发的时机,查电表的电表)。
    3:切点(pointcut)
    切点匹配一部分连接点。
    4:切面(Aspect)
    切面是切点和通知的结合(切面知道自己要对谁做什么事情),通知和切点共同定义了切面的全部内容(是什么,在何时何处完成其功能)
    5:引入(Introduction):
    引入允许我们向现有的类添加新方法和新属性。
    6:织入(Weaving):
    把切面应用到目标对象,并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。在目标对象的生命周期中的编译器、类加载期、运行期被织入:
    3、Spring AOP 使用代理类将目标对象包裹。当调用者调用目标对象时,会被代理类拦截。
    由于Spring AOP 是基于代理的,所以只支持方法连接点(不支持构造方法连接点)。
    4、Spring AOP仅支持AspectJ切点指示器的一个子集。
    arg() 限制连接点匹配参数为指定类型的执行方法
    @args() 限制连接点匹配参数由指定注解标注的执行方法
    execution() 用于匹配是连接点的执行方法
    this() 限制连接点匹配 AOP 代理的 bean 引用为指定类型的类
    target 限制连接点匹配目标对象为指定类型的类
    @target() 限制连接点匹配特定的执行对象,这些对象对应的类要具有指定类型的注解
    within() 限制连接点匹配指定的类型
    @within() 限制连接点匹配指定注解所标注的类型(当使用 Spring AOP 时,方法定义在由指定的注解所标注的类里)
    @annotation 限定匹配带有指定注解的连接点
    5、编写切点
    例如:
    //定义一个表示表演的接口
    package concert;
    public interface performance{
    public void perform();
    }
    //我们要编写一个perform()触发的通知。下面是一个切点表达式,这个切点表达式设置了perform调用触发通知的调用。
          execution(* concert.Performance.perform(..))
    
                说明:
                    execution:在方法执行时触发
    *:不关心返回值
    concert.Performance.perform:方法全路径
    (..)任意参数
    现在我们配置的切点只匹配concert包。可以使用within()指示器来限制匹配。
    execution(* concert.Performance.perform(..)) && within(concert.*)
    说明:
    && : 且(还有|| or !)
    within(concert.*) : concert包下的任意方法被调用。
    6、在切点中选择bean
    bean() 是Spring新引入的指示器,可以在切点表达式中使用bean的ID来标识bean。
    使用beanID或者名称作为参数来限制切点只匹配到特定的bean。
    例如:
          execution(* concert.Performance.perform()) and bean('BeanID')
            执行concert.Performance.perform()时触发,但是bean的ID是'BeanID'
    类似的:
    execution(* concert.Performance.perform()) and !bean('BeanID')
    7、使用注解创建切面:
    AspectJ提供了5个注解来表明应该什么时候调用:
    @After 通知方法会在目标方法返回或抛出异常后调用
    @AfterReturning 通知方法会在目标方法返回后调用
    @AfterThrowing 通知方法会在目标方法抛出异常后调用
    @Around 通知方法会将目标方法封装起来
    @Before 通知方法会在目标方法调用之前执行
    下面使用@Aspect注解定义了一个“观众”切面(我们认为:观众是表演的横向关注点)
             package concert;
             @Aspect    //定义了一个切面
             public class Audience{
                @Before("execution("** concert.Performance.perform(..)")")      //这个通知方法会在目标方法调用之前调用
                public void 手机静音(){//演出之前鼓掌}
                @AfterReturning("execution("** concert.Performance.perform(..)")")  //这个通知方法会在目标方法调用之后调用
                public void 鼓掌(){//表演之后鼓掌}
                @AfterThrowing("execution("** concert.Performance.perform(..)")")   //这个通知方法会在目标方法抛出异常时调用
                public void 退款(){//表演失败时要求退款}
             }//上面这段代码重复的传入了3次切点表达式。
        可以使用@Pointcut注解定义重用的节点。
             package concert;
             @Aspect    //定义了一个切面
             public class Audience{
                @Pointcut("execution("** concert.Performance.perform(..)")")    //定义命名的切点
                public void performance(){}     //这个方法的方法体不重要,这个方法只是@Pointcut的载体
                @Before("performance()")
                public void 手机静音(){}
                @AfterReturning("performance()")
                public void 鼓掌(){}
                @AfterThrowing("performance()")
                public void 退款(){}
             }
    8、启动AspectJ注解的自动代理
    (7中配置了切面,只有启用了注解的自动代理才可以用)
    1:在JavaConfig中配置:
            @Configuration
            @EnableAspectJAutoproxy     //启动AspectJ自动代理
            @ComponentScan
            public class Config{
                @Bean
                public Audience audience(){return new Audience();}      //声明Audience的Bean
            }
        2:使用xml配置:
           <beans xmlns:xxx/yyy/aop>    //引入Spring AOP命名空间
                <context:component-scan base-package = "concert">
                <aop:aspectJ-autoproxy />           //启用AspectJ自动代理
                <bean class = "concert.Audience">
           </beans>
        注意:无论使用哪种方法,@Aspect注解的bean都会被创建一个代理,包裹切点匹配到的所有的bean周围。(形成了代理包裹目标bean的模型)
    9、创建环绕通知
    在一个通知方法中同时编写前置和后置通知。
    例如:
            @Aspect
            public class Audience{
                @Pointcut("execution(** concert.Performance.perform(..))")  //定义命名的切点
                public void performance(){}
    
                @Around("performance()")        //环绕通知方法(表示watchPerformance方法将作为performance切点的环绕通知)
                public void watchPerformance(ProceedingJoinPoint jp){
                    try{
                        //调用前置通知
                        jp.proceed();       //掉用ProceedingJoinPoint的proceed()将控制权交给目标方法。(如果不写,将导致目标方法的调用阻塞。多次调用可以实现重试)
                        //调用后置通知
                    }catch(Throwable e){
                        //调用异常通知
                    }
                }
            }
    10、处理通知中的参数:
    之前说过光盘中的磁道。假设:playTrack()方法用于播放光盘的某一个磁道,如果想要记录磁道的播放次数。最简单的办法是在playTrack()中添加一个计数的标记。但是计数这个事情怎么看都应该又切面来完成。
    使用切面记录磁道播放次数的例子:
            @Aspect
            public class TrackCount{
                private Map<Integer , Integer>  trackCounts = new HashMap<Integer , Integer>();
                 //playTrack(int):接收int类型的参数  args(trackNumber):指定参数。这个参数最终会被传递到通知中去。
                @Pointcut("execution(* xxx.yyy.playTrack(int)) && args(trackNumber)")
                public void trackPlayed(int trackNumber){}
                @Before("trackPlayed(trackNumber)")
                public void countTrack(int trackNumber){
                    int currentCount = trackCounts.containsKey(trackNumber) ? trackCount.get(trackNumber) : 0;
                    trackCount.put(trackNumber , currentCount + 1);
                }
            }
        //接下来将光盘类和TrackCount类定义为Bean,并启用AspectJ自动代理:
            @Configuration
            @EnableAspectJAutoproxy             //启动AspectJ自动代理
            public class TrackCounterConfig{
                @Bean
                public 光盘类 方法名(){
                    光盘 盘 = new 光盘();
                    。。。
                    return 盘;
                }
                @Bean
                public TrackCounter trackCounter(){
                    return new TrackCounter();
                }
            }
    11、通过注解引入新功能
    ?????????????????????????????
    12、在xml中声明切面
    Spring的aop命名空间中,提供了多个元素在xml中声明切面:
    <aop:advisor> 定义 AOP 通知器
    <aop:after> 定义 AOP 后置通知(不管被通知的方法是否执行成功)
    <aop:after-returning> 定义 AOP 返回通知
    <aop:after-throwing> 定义 AOP 异常通知
    <aop:around> 定义 AOP 环绕通知
    <aop:aspect> 定义一个切面
    <aop:aspectj-autoproxy> 启用@AspectJ注解驱动的切面
    <aop:before> 定义一个 AOP 前置通知
    <aop:config> 顶层的 AOP 配置元素。大多数的<aop:*>元素必须包含在<aop:config>元素内
    <aop:declare-parents> 以透明的方式为被通知的对象引入额外的接口
    <aop:pointcut> 定义一个切点
    例如:
            <aop:config>
                <aop:aspect ref = "pojoBean">     //这个POJOBean中定义了切面的功能(就是前置通知方法,后置通知方法等)
                    <aop:before pointcut = "execution(** xxx.yyy.perform(..))" method = "method1" />        //method1.2.3都定义在了上面引入的pojoBean
                    <aop:after-returning  "pointcut = "execution(** xxx.yyy.perform(..))" method = "method2" />
                    <aop:after-throwing   "pointcut = "execution(** xxx.yyy.perform(..))" method = "method3" />
                </aop:aspect>
            </aop:config>
            //上面的切点重复编写了。
    抽取切点声明:
           <aop:config>
               <aop:aspect ref = "pojoBean">     //这个POJOBean中定义了切面的功能(就是前置通知方法,后置通知方法等)
                   <aop:pointcut id = "performance" expression = "execution(** xxx.yyy.perform(..))" />
                   <aop:before pointcut-ref = "performance" method = "method1" />        //method1.2.3都定义在了上面引入的pojoBean
                   <aop:after-returning  pointcut-ref = "performance" method = "method2" />
                   <aop:after-throwing   pointcut-ref = "performance" method = "method3" />
               </aop:aspect>
           </aop:config>
    13、使用xml定义环绕通知
    去掉注解的AOP环绕通知方法
            public class Audience{
                public void watchPerformance(ProceedingJoinPoint jp){
                    try{
                        前置通知();
                        jp.proceed();
                        后置通知();
                    }catch(Throwable e){
                        异常通知();
                    }
                }
            }
        在xml中使用<aop:around>元素声明环绕通知
            <aop:config>
                <aop:aspect ref = "audience">   //链接环绕方法所在的Bean
                <aop:pointcut id = "performance" expression = "execution(** xxx.yyy.perform(..))">  //定义切点
                <aop:around pointcut-ref = "performance" method = "watchPerformance">   //给切点关联环绕通知方法。
            </aop:config>
    14、使用xml为通知传递参数:
    上面的笔记中有提到。记录光盘的磁道赌取次数的案例,下面是使用xml配置的相同案例:
    将切面注解去掉后的代码:
                public class TrackCount{
                    private Map<Integer , Integer>  trackCounts = new HashMap<Integer , Integer>();
                    public void countTrack(int trackNumber){
                        int currentCount = trackCounts.containsKey(trackNumber) ? trackCount.get(trackNumber) : 0;
                        trackCount.put(trackNumber , currentCount + 1);
                    }
                }
            xml配置文件;
            <beans>
                <bean id = "trackCount" class = "xxx.yyy.TrackCount">
                <bean id = "cd" class = "xxx.yyy.光盘">
                    <property name = "" value = "">
                    <property name = "磁道">
                        <list>
                            <value>"..."</value>
                        </list>
                    </property>
                </bean>
                <aop:config>
                    <aop:aspect ref = "trackCount">     //将TrackCount声明为切面
                        <aop:pointcut id = "trackPlayed" expression = "execution(* xxx.yyy.playTrack(int)) and args(trackNumber)" />
                        <aop:before pointcut-ref = "trackPlayed" method = "countTrack" />
                    </aop:aspect>
                </aop:config>
            </beans>
    15、通过xml引入新的功能
    ????????????
    16、注入AspectJ切面
    Spring的AOP解决方法相对于AspectJ非常粗糙的。可以使用Spring的依赖注入将AspectJ切面注入到Spring中使用。
    ???????????

  • 相关阅读:
    从内积的观点来看线性方程组
    《线性规划》(卢开澄,卢华明) 例2.1
    斐波那契数列
    共几只桃子
    计算 $s=1+(1+2)+(1+2+3)+cdots+(1+2+3+cdots+n)$
    【★】路由环路大总结!
    Apache与Tomcat有什么关系和区别
    Apache与Tomcat有什么关系和区别
    逻辑卷、物理卷、卷组 的关系
    逻辑卷、物理卷、卷组 的关系
  • 原文地址:https://www.cnblogs.com/Xmingzi/p/8874831.html
Copyright © 2020-2023  润新知