zoukankan      html  css  js  c++  java
  • Spring:面向切片编程

      在之前我们记录Spring的随笔当中,都是记录的Spring如何对对象进行注入,如何对对象的属性值进行注入,即我们讲解的很大部分都是Spring的其中一个核心概念——依赖注入(或者说是控制翻转,IOC)的方面,那么通过前几天所学的《常用设计模式:代理模式》的学习之后,我们今天来学习一下与代理模式密切相关,或者说是代理模式的一个强大应用的Spring的另一个核心概念——面向切片编程,即AOP(Aspect OrientedProgramming)

      首先我们都知道java是一门经典的面向对象的编程语言(OOP,Object OrientedProgramming)面向对象的编程语言有三大特性,封装,继承,和多态,其中的继承允许我们定义从上到下的关系,即子类可以继承父类的一些功能函数,也可以改写父类的一些功能函数,但是要为分散的对象(即不是同一父类的对象,这里的父类不包括Object)引入一个公共的行为的时候,OOP就显得很乏力,它需要在每一个对象里头都添加这个公用的行为,这样代码的就显得很重复,很冗余,最典型的就是添加日志功能,这个日志功能和对象的核心功能毫无关系,但是要为分散的对象添加日志功能,就必须向打开每一个分散的封装好的对象,然后添加日志功能代码,我们将这种散步在各处与对象核心功能无关的代码,称之为横切代码。为了解决这个问题,AOP就应运而生。

      AOP是将多个类的公共行为封装到一个可重用的模块(如我们上一段所说的日志功能)并将其命名为“Aspect”,即是说将那些和类的核心业务无关的,却为业务模块所公共调用的逻辑处理封装起来,便可以减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP弥补了OOP在横向关系上操作的不足。而实现AOP的技术的主要两种方式,其中一种就是我们《常用设计模式:代理模式》所提到的动态代理技术;还有一种方式就是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。

      而Spring当中的AOP的实现原理就是我们之前所提到的动态代理技术(如有对原理不是很理解的,可以翻看《常用设计模式:代理模式》)。那么接下来我们要看看如何去使用Spring当中的面向切片编程去实现一些功能,首先我们先来看看在Spring的面向切片编程当中的一些常涉及的概念:

    1.连接点(Joinpoint): 程序执行过程中明确的点,如方法的调用或特定的异常被抛出。

    2.切入点(Pointcut): 指定一个通知将被引发的一系列连接点的集合。AOP框架必须允许开发者指定切入点:例如,使用正则表达式。 Spring定义了Pointcut接口,用来组合MethodMatcher和ClassFilter,可以通过名字很清楚的理解, MethodMatcher是用来检查目标类的方法是否可以被应用此通知,而ClassFilter是用来检查Pointcut是否应该应用到目标类上

    3.目标对象(Target Object): 包含连接点的对象。也被称作被通知或被代理对象。POJO

    4.AOP代理(AOP Proxy): AOP框架创建的对象,包含通知。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。

    5.切面(Advisor):包含切入点和增强类的对象,为拦截器提供切入点和对应的增强类。

    接下来我们将分几种在Spring当中使用AOP的不同形式来讲解如何使用AOP。

    1.基于代理的AOP

    还是以我们动态代理的买鞋子和买手机的例子为例,我们要做一个业务接口是买鞋子还有一个业务接口是买手机,而需要编写相应的类去实现买鞋子和买手机的业务接口,并且通过Spring当中的配置文件去做配置。首先先上我们的业务接口和业务接口实现类的代码:

    package wellhold.bjtu.AOP;
    
    public interface BuyPhoneITF {
    
        public void BuyPhone();
        
    }
    package wellhold.bjtu.AOP;
    
    public interface BuyShoesITF {
    
        public void BuyShoes();
        
    }
    package wellhold.bjtu.AOP;
    
    public class PhoneSeller implements BuyPhoneITF {
    
        @Override
        public void BuyPhone() 
        {
            System.out.println("这里是手机店,您要的手机有货");
        }
    
    }
    package wellhold.bjtu.AOP;
    
    public class ShoesSeller implements BuyShoesITF {
    
        @Override
        public void BuyShoes() {
    
            System.out.println("您要的鞋子已售出");
            
        }
    
    }

    之后我们在写一个统一的售前处理的方法和售后的处理方法的代码模块:

    package wellhold.bjtu.AOP;
    
    import java.lang.reflect.Method;
    
    import org.springframework.aop.AfterReturningAdvice;
    import org.springframework.aop.MethodBeforeAdvice;
    
    public class BuyHelper implements AfterReturningAdvice,MethodBeforeAdvice{
    
        @Override
        public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {
            System.out.println("需要提前询问是否有货");
            
        }
    
        @Override
        public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
    
            System.out.println("买到之后需要需要开发票");
            
        }
    
    
        
    }

    这个BuyHelper类实现了两个Spring框架当中AOP部分的两个接口,一个AfterReturningAdvice,一个MethodBeforeAdvice,这两个接口需要重写两个方法,这两个方法分别对应的是统一的售前处理方法和售后处理方法,可以把这个BuyHelper当做是所有与Buy有关的类的方法的增强类,可以增强Buy有关的方法,提供统一的售前处理和售后处理。之后我们就需要配置我们的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"  
        xmlns:context="http://www.springframework.org/schema/context"  
        xsi:schemaLocation="
           http://www.springframework.org/schema/beans  
           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
           http://www.springframework.org/schema/aop  
           http://www.springframework.org/schema/aop/spring-aop-3.0.xsd  
           http://www.springframework.org/schema/context  
           http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    
        <bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor" />   
        
        <context:annotation-config />
        
        <!--对应bean注册入Spring  -->
        <bean id="buyHelper" class="wellhold.bjtu.AOP.BuyHelper"/>
        <bean id="shoesSeller" class="wellhold.bjtu.AOP.ShoesSeller"/>
        <bean id="phoneSeller" class="wellhold.bjtu.AOP.PhoneSeller"/>
        
        
        <!-- 定义切入点,匹配所有的Buyshoes方法 -->
        <bean id ="buyShoesPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">  
               <property name="pattern" value=".*BuyShoes"></property>  
        </bean>  
        <!-- 定义切入点,匹配所有的Buyshoes方法 -->
        <bean id ="buyPhonePointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">  
               <property name="pattern" value=".*BuyPhone"></property>  
        </bean>  
        
        
        <!-- 定义一个基于Advisor的buyPhone切面    增强类+切点结合 -->  
        <bean id="buyPhoneHelperAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">  
             <property name="advice" ref="buyHelper"/>  
             <property name="pointcut" ref="buyPhonePointcut"/>
        </bean>
        
        <!-- 定义一个基于Advisor的buyPhone切面    增强类+切点结合 -->  
        <bean id="buyShoesHelperAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">  
             <property name="advice" ref="buyHelper"/>  
             <property name="pointcut" ref="buyShoesPointcut"/>
        </bean>
        
        
        <!-- 定义代理对象 -->  
        <bean id="buyPhoneProxy" class="org.springframework.aop.framework.ProxyFactoryBean">  
                <property name="target" ref="phoneSeller"/>
                <property name="interceptorNames" value="buyPhoneHelperAdvisor"/>  
        </bean> 
            
        <!-- 定义代理对象 -->  
        <bean id="buyShoesProxy" class="org.springframework.aop.framework.ProxyFactoryBean">  
                <property name="target" ref="shoesSeller"/>
                <property name="interceptorNames" value="buyShoesHelperAdvisor"/>  
        </bean>     
    
    </beans>

    这里主要解释一下什么是切入点,所谓的切入点,是为了给Spring提供何时切入信息的,比如说,我们的代码在运行到.BuyPhone方法的时候(这里使用正则表达式去统一匹配),如果匹配上是一个定义了的切入点,那么Spring会去寻找他的对应的增强类,说白了就去找对应这个切入点的预前处理方法和事后处理方法。一个切入点与他对应的增强类可以被看做是一个切面,这里是基于Advisor的切面,定义了两个切面,之后定义了两个代理对象去为这两个业务接口进行代理。之后我们运行以下我们的测试代码:

    package wellhold.bjtu.AOP;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class maintest {
    
        public static void main(String[] args) {
    
            ApplicationContext cfg=new ClassPathXmlApplicationContext("beans.xml");
            BuyPhoneITF ps =cfg.getBean("buyPhoneProxy",BuyPhoneITF.class);
            ps.BuyPhone();
            
            BuyShoesITF ss =cfg.getBean("buyShoesProxy",BuyShoesITF.class);
            ss.BuyShoes();
            
            
        }
    
    }

    简单的总结一下这种方式实现的AOP,这种方式有点类似于静态代理模式,每一个目标类需要对应一个代理,但是与静态代理模式不同,它抽象出了一个统一的预前处理和事后处理模块,不需要在代理类当中重写方法的时候重复写预前处理和事后处理,不过这种方式在配置文件上需要配置的东西有点繁多,需要将目标类和增强类都注册到容器中,还需要定义相应的切入点,再结合切入点和增强类定义切面,最后还需要定义代理,过程很繁琐,现在已经不常用。

    2.POJO类的切面

    从第一种方式可以看到,应对每一个不同的目标类还需要定义不同的代理去进行方法增强,那么在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"  
        xmlns:context="http://www.springframework.org/schema/context"  
        xsi:schemaLocation="
           http://www.springframework.org/schema/beans  
           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
           http://www.springframework.org/schema/aop  
           http://www.springframework.org/schema/aop/spring-aop-3.0.xsd  
           http://www.springframework.org/schema/context  
           http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    
        <bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor" />   
        
        <context:annotation-config />
        
        <!--对应bean注册入Spring  -->
        <bean id="buyHelper" class="wellhold.bjtu.AOP.BuyHelper"/>
        <bean id="shoesSeller" class="wellhold.bjtu.AOP.ShoesSeller"/>
        <bean id="phoneSeller" class="wellhold.bjtu.AOP.PhoneSeller"/>
        
        
        <!-- 定义切入点,匹配所有的Buyshoes方法 -->
        <bean id ="buyShoesPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">  
               <property name="pattern" value=".*BuyShoes"></property>  
        </bean>  
        <!-- 定义切入点,匹配所有的Buyshoes方法 -->
        <bean id ="buyPhonePointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">  
               <property name="pattern" value=".*BuyPhone"></property>  
        </bean>  
        
        
        <!-- 定义一个基于Advisor的buyPhone切面    增强类+切点结合 -->  
        <bean id="buyPhoneHelperAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">  
             <property name="advice" ref="buyHelper"/>  
             <property name="pointcut" ref="buyPhonePointcut"/>
        </bean>
        
        <!-- 定义一个基于Advisor的buyPhone切面    增强类+切点结合 -->  
        <bean id="buyShoesHelperAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">  
             <property name="advice" ref="buyHelper"/>  
             <property name="pointcut" ref="buyShoesPointcut"/>
        </bean>
        
        <!-- 自动扫描配置文件中的Advisor,并且去匹配其对应的实现切入点拦截方法接口的目标类 -->
         <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>  
    </beans>

    与1章节当中配置文件不同的地方就是之前的手动配置代理变为了让Spring自动去帮我们配置代理,利用DefaultAdvisorAutoProxyCreator去扫描Spring配置文件当中的Advisor,并且为每一个Advisor匹配对应实现了切点拦截方法的业务接口的实现类,举个例子,buyShoesHelperAdvisor的切点的拦截方法是BuyPhone(),那么Spring会自动匹配究竟有那个类去实现了声明了BuyPhone()方法的接口,就找到了phoneSeller(如果还有其他的类实现了这个方法的业务接口,也会一起被代理),并且自动为其配置代理。

    接下来我们来看下测试代码和效果:

    package wellhold.bjtu.AOP;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class maintest {
    
        public static void main(String[] args) {
    
            ApplicationContext cfg=new ClassPathXmlApplicationContext("beans.xml");
            BuyPhoneITF ps =cfg.getBean("phoneSeller",BuyPhoneITF.class);
            ps.BuyPhone();
            
            BuyShoesITF ss =cfg.getBean("shoesSeller",BuyShoesITF.class);
            ss.BuyShoes();
            
            
        }
    
    }

     

    这里你一定会有一个和我当时刚看的时候的疑问,为什么是从ioc容器中获取的是目标类的实例呢?而且最关键是为什么还能实现在切入点去调用增强类的方法呢?为此我也进行了一番调研,发现原来在使用DefaultAdvisorAutoProxyCreator的时候,这时候我们去调用getBean(“phoneSeller”),它其实给我们返回的是自动生成的AOP代理,而不是真实的phoneSeller这个目标类的实例,具体Spring怎么实现的,我就没有在深究,有知道的朋友可以和我相互交流一下。

      用这种方式去进行AOP的实现,可以说比第一种方式来的好方便很多,但是也有可能造成一定的不便。如我们需要在代码中调用到原始的目标类,那这时候我们就没办法再从Spring容器当中去获取了,应为通过getBean方法去获取的话,返回的是AOP代理。至于如何返回原始目标类,目前自己也没有想到什么方法。

    3.基于注解的AOP实现

      看到这里,也许你还是会觉得这个配置文件还是需要写好长一串,还需要定义什么切入点,还有什么切面什么的,还是相对比较麻烦的,那么不要着急,接下来我们要介绍的方法,可以让你摆脱这种困扰,那就是基于注解的方式去实现AOP。

      基于注解的形式,我们的业务接口和实现对象是不需要变化的,但是我们的增强类的编写就大为不同了,代码如下:

    package wellhold.bjtu.AOP;
    
    import org.aspectj.lang.annotation.AfterReturning;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.stereotype.Component;
    
    @Aspect
    @Component("buyHelperByAnnotation")
    public class BuyHelperByAnnotation {
    
        
    //    @Pointcut("execution(* *.BuyShoes(..))")
        @Pointcut("execution(* wellhold.bjtu.AOP.*.*(..))")
        public void BuyPhonepoint(){}
        
    
        
        @Before("BuyPhonepoint()")
        public void before()
        {
            System.out.println("需要提前询问是否有货");
        }
        
        @AfterReturning("BuyPhonepoint()")
        public void after()
        {
            System.out.println("买到之后需要需要开发票");
        }
        
    }

    在这个增强类当中,我们通过:

    1. @Aspect注解告诉了Spring这个类是用来提供切面的预前处理方法、事后处理方法、以及切点的。

    2. 在@Pointcut()注解修饰的方法,则是声明了一个切点,用正则表达式来表示,在代码中注释的那一行的含义是切点是所有名为.BuyShoes()方法,而没有被注释的那一行则是将在包wellhold.bjtu.AOP包下的所有类的所有方法都作为切点,即都拦截下来添加预前处理和时候处理。我们就以拦截所有类的所有方法这个注释为例。

    3. 而@Before()注释则是用来修饰预前处理方法,且要为注释提供切点方法,即之前用@Pointcut()注解修饰的方法名。

    4. @AfterReturnning的作用是用于修饰事后处理方法,且要为注释提供切点方法,即之前用@Pointcut()注解修饰的方法名。

    了解了基于注解的增强类的编写之后,我们可以看看我们的配置文件,这时候的配置文件就显得十分轻量级了。

    <?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-3.0.xsd  
           http://www.springframework.org/schema/aop  
           http://www.springframework.org/schema/aop/spring-aop-3.0.xsd  
           http://www.springframework.org/schema/context  
           http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    
        <bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor" />   
        
        <context:annotation-config />
        
        <!--扫描包 -->  
        <context:component-scan base-package="wellhold.bjtu.AOP" annotation-config="true"/>   
        <!-- ASPECTJ注解 -->  
        <aop:aspectj-autoproxy  proxy-target-class="true" />    
           
        <!-- 目标类 -->  
     
        <bean id="phoneSeller" class="wellhold.bjtu.AOP.PhoneSeller"/>
        <bean id="shoesSeller" class="wellhold.bjtu.AOP.ShoesSeller"/>
         
    
    
    </beans>

    只需要打开扫描注解的功能,以及提供目标类即可,其实熟悉Spring的朋友,都可以为PhoneSeller和ShoesSeller两个目标类使用@Component注解的分支去为其注入Spring容器,所以配置文件就显得十分轻量级,所以说基于注解的使用方式是很优雅的一种方式。当然初学者为了更好的理解其中的一些概念,还是需要像笔者这样一步一步学习来的印象较为深刻。

    接下来我们看一下我们的测试代码和运行效果:

    package wellhold.bjtu.AOP;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class maintest {
    
        public static void main(String[] args) {
    
            ApplicationContext cfg=new ClassPathXmlApplicationContext("beans2.xml");
            BuyPhoneITF ps =cfg.getBean("phoneSeller",BuyPhoneITF.class);
            ps.BuyPhone();
            System.out.println();
            
            System.out.println();
            BuyShoesITF ss =cfg.getBean("shoesSeller",BuyShoesITF.class);
            ss.BuyShoes();
            
    
            
            
        }
    
    }

    可以看到和之前一样,我们通过获取PhoneSeller和ShoesSeller的实例,Spring会给我们自动返回他们的代理对象,然后通过他们的代理对象去调用相应方法的接口,实现目标类的方法的增强,效果与其他方式一致。

    4. 半注解的形式

      我们从3章节里头可以看出,完全基于注解的形式可以很优雅的完成切片编程,但是有的时候,如果我们全是用注解的形式去处理切边编程,那么有的时候需要修改切边对应的增强类或者增强方法的时候,就只能重新打开源码进行修改,这样有时候会很不方便,所以我们有时候会选择半注解的形式去做切片编程,即到时候需要修改的时候,我们可以通过修改配置文件的方式,然后再重启代码就可以实现预前处理方法和事后处理方法的修改。

      在这里目标类和业务方法接口的代码就不重复上了,直接上我们的增强类方法:

    package wellhold.bjtu.AOP;
    
    import org.springframework.stereotype.Service;
    
    @Service("buyHelper02")
    public class BuyHelper02 {
        
        public void before()
        {
            System.out.println("需要提前询问是否有货--------------------------");
        }
        
        public void after()
        {
            System.out.println("买到之后询问是否需要发票--------------------");
        }
    
    }

    可以看到,在这个部分的增强类方法当中的事情处理和事后处理方法都没有任何注解去修饰,唯一的注解是将这个增强类注入到Spring的Ioc容器当中。

    接下来我们可以看下我们的配置文件:

    <?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-3.0.xsd  
           http://www.springframework.org/schema/aop  
           http://www.springframework.org/schema/aop/spring-aop-3.0.xsd  
           http://www.springframework.org/schema/context  
           http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    
        <bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor" />   
        
        <context:annotation-config />
        
        <!--扫描包 -->  
        <context:component-scan base-package="wellhold.bjtu.AOP" annotation-config="true"/>   
        
        <!-- ASPECTJ注解 -->  
        <!--
        <aop:aspectj-autoproxy  proxy-target-class="true" />    
        -->
        
        
        <!-- 目标类 -->  
        <bean id="phoneSeller" class="wellhold.bjtu.AOP.PhoneSeller"/>
        <bean id="shoesSeller" class="wellhold.bjtu.AOP.ShoesSeller"/>
        
        <aop:config>  
           <aop:aspect ref="buyHelper02">  
                <aop:before method="before" pointcut="execution(* wellhold.bjtu.AOP.*.*(..))"/>  
                <aop:after method="after" pointcut="execution(* wellhold.bjtu.AOP.*.*(..))"/>  
           </aop:aspect>  
        </aop:config>  
    
    
    </beans>

    在配置文件当中,我们用标签<aop:comfig>去对其进行配置,配置方法见配置文件即可,一目了然。通过配置文件的形式,我们可以在配置文件中直接修改他的切入点,以及对应的方法名,还有引用的增强类,而不需要打开源码进行修改,在某些应用场景当中还是很方便的。

    到此,四种实现SpringAOP的方式就讲解完毕了,其中第一种和第二种方法,几乎是没有再使用了的,但是为了更好的理解AOP的一些概念,比如切点,切面,建议,拦截器,目标类,增强类,我们还是手动实现了一遍方式一和方式二。就本人来说,还是比较喜欢使用方式三的去进行编写,不过再某些WEB开发当中,因为要把代码放到服务器当中打包成war包,所以要修改代码会比较困难,这时候方式四的优势就体现出来了,可以通过修改配置文件的形式去完成方法的重指向,切点的重指向,还是比较便利的。

    5. 补充内容

      我们之前的内容,都是以预前处理方法和事后处理方法进行举例子的,但在AOP当中,除了这两种方法之外,还有其他的方法,接下来我们来一一介绍,首先我们先来补充一个概念,那就是连接点的概念,连接点(JointPoint)是可以为通知方法被提供调用的业务接口方法的签名和传入的参数的,简单的说,在运行过程中,Spring会自动向JoinPoint对象自动注入被调用的业务接口方法的方法名和传入的参数,常常与通知方法联用,接下来我们一一介绍这些通知方法:

    1. 前置通知:其对应的注解为@Before ,也就是我们前边经常提到的通俗说法的预前处理方法,执行目标方法前拦截到的方法,常常可以与一个JointPoint联用,用来获取被调用的业务方法名称和参数,可以在这里做一个分支处理,比如调用方法一的时候,需要什么预前处理,调用方法二的时候需要什么预前处理。

    2. 后置通知:其对应的注解为@After,无论方法是否抛出异常,都必须运行这个注解修饰的方法,常常可以与一个JointPoint联用,用来获取被调用的业务方法名称和参数,可以在这里做一个分支处理,比如调用方法一的时候,需要什么处理,调用方法二的时候需要什么处理。

    3. 返回通知:其对应的注解为@AfterReturning,该通知是在目标方法执行成功后执行的通知,不仅仅使用JointPoint获取连接点信息,还可以在注解里通过result关键词获得目标方法执行的结果。

    4. 异常通知:其对应的注解为@AfterThrowing,该通知是在执行目标方法的过程中,出现异常,就会执行,其和返回通知不可能同时执行,使用方法和返回通知相似,不过不同的是通过throwing关键词获取跑出的异常。

    5.环绕通知:其对应的注解为@Around,环绕通知执行在前置通知之前,环绕通知需要携带ProceedingJoinPoint 这个类型的参数,环绕通知类似于动态代理的全过程ProceedingJoinPoint类型的参数可以决定是否执行目标函数环绕通知必须有返回值。其实就是包含了所有通知的全过程。

    接下来我们上一个代码,大家就可以理解我们的各个通知的作用和所处的位置了

    package wellhold.bjtu.AOP;
    
    import org.springframework.stereotype.Component;
    
    @Component("adviceType")
    public class adviceType implements AdviceTypeITF{
    
        @Override
        public String Method1(String name) {
            System.out.println("这是方法1");
            return "方法1执行成功";
        }
    
        @Override
        public void Method2(String name, String id) {
            System.out.println("这是方法2");
        }
    
    }
    package wellhold.bjtu.AOP;
    
    public interface AdviceTypeITF {
    
        public String Method1(String name);
        public void Method2(String name,String id);
        
    }
    package wellhold.bjtu.AOP;
    
    import org.aopalliance.intercept.Joinpoint;
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.AfterReturning;
    import org.aspectj.lang.annotation.AfterThrowing;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.stereotype.Component;
    
    @Aspect
    @Component("buyHelperByAnnotation")
    public class BuyHelperByAnnotation {
    
        
    //    @Pointcut("execution(* *.BuyShoes(..))")
        @Pointcut("execution(* wellhold.bjtu.AOP.*.*(..))")
        public void BuyPhonepoint(){}
        
    
        
        @Before("BuyPhonepoint()")
        public void before(JoinPoint joinpoint) 
        {
            System.out.print("这是前置通知 : ");
            for (int i = 0; i < joinpoint.getArgs().length; i++) {
                System.out.print(joinpoint.getArgs()[i]+" ");
            }
            System.out.println(joinpoint.getSignature().getName());
        }
        
        
        @After("BuyPhonepoint()")
        public void after(JoinPoint joinpoint)
        {
            System.out.print("这是最终通知 : ");
            for (int i = 0; i < joinpoint.getArgs().length; i++) {
                System.out.print(joinpoint.getArgs()[i]+" ");
            }
            System.out.println(joinpoint.getSignature().getName());
        }
        
        @AfterReturning(pointcut="BuyPhonepoint()",returning="result")
        public void afterReturning(JoinPoint jointpoint ,String result)
        {
            System.out.print("这是后置通知  : "+"result= "+result+" ");
            for (int i = 0; i < jointpoint.getArgs().length; i++) {
                System.out.print(jointpoint.getArgs()[i]+" ");
            }
            System.out.println(jointpoint.getSignature().getName());
        }
        
        @AfterThrowing(pointcut="BuyPhonepoint()",throwing="e")
        public void exception(Exception e)
        {
            System.out.println("这是后置异常通知 : "+e);
        }
        
        
        //环绕通知:需要携带ProceedingJoinPoint类型的参数  
        //环绕通知类似于动态代理的全过程:ProceedingJoinPoint类型的参数可以决定是否执行目标方法  
        //且环绕通知必须有返回值,返回值即目标方法的返回值。  
        @Around("BuyPhonepoint()")
        public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable
        {
            //可以在环绕通知之下进行权限判断
            System.out.print("这是环绕通知 : ");
            for (int i = 0; i < pjp.getArgs().length; i++) {
                System.out.print(pjp.getArgs()[i]+" ");
            }
            System.out.println(pjp.getSignature().getName());
            Object result=pjp.proceed();
            return result;
        } 
    }

    相信通过之前的讲解,这部分的代码就不需要在赘述了,之后我们来看看输出结果:

    至此,AOP部分的内容就讲解完毕了,水平有限,如有问题,可以联系或者站内留言交流。

  • 相关阅读:
    Intellij IDEA 打开文件tab数量限制的调整
    Mysql处理中文乱码的问题
    MIT算法导论笔记
    算法导论-排序(一)-插入排序、归并排序
    leetcode题解:Search for a Range (已排序数组范围查找)
    leetcode 题解:Merge Sorted Array(两个已排序数组归并)
    leetcode题解:Construct Binary Tree from Inorder and Postorder Traversal(根据中序和后序遍历构造二叉树)
    leetcode题解:Construct Binary Tree from Preorder and Inorder Traversal (根据前序和中序遍历构造二叉树)
    c++11 std::prev、std::next、std::advance与auto 使用
    (转)指针的引用(*&)与指针的指针(**)
  • 原文地址:https://www.cnblogs.com/WellHold/p/6655769.html
Copyright © 2011-2022 走看看