第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中使用。
???????????

posted on 2018-04-18 14:53  笑明子  阅读(103)  评论(0编辑  收藏  举报

导航