• spring之AOP(转)


    Spring之AOP篇:

    AOP框架是Spring的一个重要组成部分.但是Spring IOC 并不依赖于AOP,这就意味着你有权力选择是否使用AOP,AOP作为Spring IOC容器的一个补充,使它成为一个强大的中间件解决方案。

    一、AOP(Aspect-Oriented Programming)

        AOP的目标:横切关注点  这种问题用面向对象很难解决

      AOP是用来解决什么问题的呢?
         横越多个模块的业务关注点,比如记录日志,事务管理
     1、AOP的解决方案——拦截
       1)由谁来拦截?--代理类Proxy 和本类实现同一个接口
              就像是这样:action发出命令--代理类(处理一些操作)--本类
       2)拦截的过程:
           I.将Proxy(如UserServiceProxy)交给调用者(如UserAction),调用者调用需要的方法
           II.Proxy先拦截该方法,执行拦截逻辑
           III.然后执行调用者真正的实现类(如UserServiceImp)
       3)核心概念:
         a、代理对象Proxy:是核心,负责拦截,由工具自动创建
         b、拦截器(Interceptor):实现拦截逻辑的对象
         c、目标对象(Target):是Proxy所代理的对象,是已有的类
      以上三个类实现了一个“金三角”
         Proxy--拦截-->Interceptor--实现拦截逻辑--处理目标对象-->Target

     2、代理如何实现?

         
    有两种方式:

           
    1)JDK动态代理(使用这个比较多)

               
    由JDK自带的动态代码生成技术,可以对实现了接口的类进行处理

           
    2)CGLib

        
    对没有实现任何接口的类进行处理

     这两种方式的共同点:(都是在后台自动生成的)

      在程序运行期间,动态的生成代码并进行动态编译和加载

         

       
    问题:Spring的AOP是如何实现代理的?是自己实现的吗?

           
    注:Spring借用了JDK动态代理和CGLib来实现代理对象。

        
    Spring进行如下判断来实现代理:

      i、如果目标类实现了接口,Spring则调用JDK动态代理;

      ii、反之,调用CGLib

         

         
    Proxy是核心:

         
    创建Proxy的方法:ProxyFactoryBean

     <bean id="arithProxy"

            
    class="org.springframework.aop.framework.ProxyFactoryBean">

          
    <property name="target"
    ref="arith"/>

          
    <property
    name="interceptorNames">

          
    <list>

           
    <!--<value>logBefore</value>

              
    <value>logAfter</value>

              
    <value>logThrows</value>
    -->

          
    <value>logAround</value>

         
    </list>

        
    </property>

          JDK动态代理的实现方式:(工厂模式)
      UserDao userDao = new UserDaoImp();
      UserDao proxy = (UserDao)
      Proxy.newProxyInstance(userDao.getClass().getClassLoader(),

        userDao.getClass().getInterfaces(),

        new
    LogHandler(userDao));

    二、Spring中的AOP方式(在Spring中将拦截称为通知)
       拦截逻辑放在Advice之中,Advice按时间将拦截器分为四类:
          1)Before Advice:方法执行之前进行拦截
        public class LogBeforeAdvice implements MethodBeforeAdvice{

     //Method method拦截的方法
     //Object[] args 方法中的参数
     //target 目标对象
     @Override
     public void before
     (Method method, Object[] args, Object target)
       throws Throwable {
      MyLogger logger = new MyLogger();
      System.out.println("Methods:"+method.getName()+

        
    begins, args:"+Arrays.toString(args));

     }

    }

        2)After
    Advice:方法执行之后进行拦截

      public class LogAfterAdvice implements
    AfterReturningAdvice{

     @Override

     public void afterReturning(Object resultValue,
    Method method, Object[] args,

       Object
    target) throws Throwable {

      MyLogger logger = new
    MyLogger();

      System.out.println("Methods:"+method.getName()+

        
    ends, result:"+resultValue);

     }

    }

       3)AfterThrowing
    Advice:方法异常结束之后进行拦截

      public class LogThrowingAdvice implements
    ThrowsAdvice {

     

     //ThrowsAdvice 没有方法,但方法名必须是afterThrowing

     public void
    afterThrowing(IllegalArgumentException e)

     throws Throwable{

      MyLogger logger = new
    MyLogger();

      logger.log("IllegalArgumentException!");

     }

    }

       4)Around Advice:环绕通知

      public class LogAroundAdvice implements
    MethodInterceptor {

     //MethodInvocation相当于Method的包装
     @Override
     public Object invoke(MethodInvocation methodInvocation)
     throws Throwable {
      MyLogger logger = new MyLogger();
      try {
       //methodInvocation.getMethod()获得方法

       //methodInvocation.getArguments()获得方法的参数

       logger.log("before:"+

         methodInvocation.getMethod().getName()+

         ",
    args:"+methodInvocation.getArguments());

       

       //在环绕通知里面,必须执行目标方法!!!!!!!!!!!!!!

       Object result
    = methodInvocation.proceed();

       logger.log("ends:"+

         methodInvocation.getMethod().getName());

       return
    result;

      } catch (Exception e) {

       logger.log("Exception:"+e.getMessage());

       throw
    e;

      }

     }

      注意:虽然环绕通知包含了另外三种,但还是要依据业务逻辑来选择,这样有利于代码的编程量
           并且环绕通知最好不要和另外三种混用,并且并许执行目标方法proceed().

     也可以大致分成两组——
     i、BeforeAdvice, AfterReturning, Throwing
     ii、AroundAdvice:需要在内部负责调用目标类的方法。
          注意:AroundAdvice应单独使用


    <!-- Target Object -->
    <bean id="arith" class="myspring.calculator.ArithmeticCalculatorImp"></bean>

    <!-- Advice -->
    <bean id="logBefore" class="myspring.aop.LogBeforeAdvice"/>
    <bean id="logAfter" class="myspring.aop.LogAfterAdvice"/>
    <bean id="logThrows" class="myspring.aop.LogThrowingAdvice"/>
    <bean id="logAround" class="myspring.aop.LogAroundAdvice"/>

    <!-- Proxy -->
    <bean id="arithProxy"
    class="org.springframework.aop.framework.ProxyFactoryBean">

    <property name="target"
    ref="arith"/>

    <property
    name="interceptorNames">

      <list>

       
    <!--
    <value>logBefore</value>

       
    <value>logAfter</value>

       
    <value>logThrows</value>
    -->

       
    <value>logAround</value>

      </list>

    </property>

    </bean>

    在test类中这样用:ApplicationContext ac =
       new ClassPathXmlApplicationContext("aop-base.xml");
      IArithmeticCalculator proxy =
       (IArithmeticCalculator)ac.getBean("arithProxy");

         

     面试中可能会问到:
              proxyTargetClass属性
      将其加入到代理bean中去,则Spring将调用CGLib来实现创建Proxy对象。

                      
    (无论Target有无实现接口)

         
    形如

        
    <bean id=""
    class="....ProxyFactoryBean">

        .....

          
    <property name="proxyTargetClass" value="true"
    />

        
    </bean>

     三、Spring AOP基本方式  AspectJ
    Spring2.5之后用的

    Spring对AspectJ的支持
          应用于有多个target,不想一个一个的配置时,用AspectJ     

    1)POJO类(Aspect类),使用@Aspect 

      基本信息:Advice和Pointcut  

    2)定义Bean:在Bean定义文件中定义这个Aspect类 

    3)启用Spring的自动代理

      <aop:aspectj-autoproxy
    />  

       1、JoinPoint(连接点):被拦截的方法
                    程序执行过程中的点,就好像方法中的一个调用,
      或者一个特别的被抛出的异常
                   在Spring AOP中,一个连接点通常是方法调用.
       Spring并不明显地使用"连接点"作为术语,

      连接点信息可以通过MathodInvocation的参数穿过拦截器访问,

      相当于实现org.springframework.aop.Pointcut接口.

     2、JoinPoint对象

      获取连接点信息(获取目标方法以及目标对象的信息) 

     1)目标方法:

      i、getSignature():方法签名

        joinPoint.getSignature().getName()

      

      ii、getArgs():方法的实参(方法的实际参数)

                     
    joinPoint.getArgs()

      打印输出时:Arrays.toString(joinPoint.getArgs())

     2)目标对象:  getTarget()

        目标对象实际类型:getTarget().getClass()

     3、PointCut(切入点):一组连接点。
                    作用:定义了一组被拦截的方法 
      PointCut(切入点):当一个通知被激活的时候,

      会指定一些连接点.一个AOP框架必须允许开发者指定切入点。

      一个AOP框架必须允许开发者指定切入点:例如使用正则表达式.

      只有引入了切入点,才能很好的进行拦截

      PointCut的意义:可以一次性定义多个类的
        多个方法作为拦截目标(即JoinPoint)

      PointCut代表了Target
    一组Target组成了PointCut

      一组Target可以理解为一个类的多个方法,或者多个类的多个方法

      代理、拦截器、目标

      PointCut
       在AspectJ中,切入点是用PointCut Expression来
       定义的。(切入点表达式可以理解为过滤条件)

       

       PointCut表达式

        格式 
    execution(表达式)

        表达式 
    一个表达式就是一个匹配规则

         *
    myspring.calculator.IArithmeticCalculator.add(..)

      理解为public void
    myspring.calculator.IArithmeticCalculator.add(int a,int b)

      
    取代public void
    通配一切字符   

       Spring自动代理(即Spring自动生成Proxy对象)

        用<aop:aspectj-autoproxy
    />来启用

     Pointcut表达式语法
     1)方法级别  —— execution
      execution(* *.*(..)) :所有类的方法
      execution(public * somepackage.SomeClass.someMethod(..))
      总结:execution是对方法签名进行匹配
     2)对象级别(类级别)—— within
     within(somepackage.*):somepackage包下所有的类
     within(com.dang.service.*Service)
     即 com.dang.service包下所有以Service结尾的类
     within(somepackage..*):somepackage以及所有子包中的类
     within(somepackage.SomeInterface+):SomeInterface接口的所有实现类

     总结:within是对类名(带包名)进行匹配。 拦截这个类的所有方法 


    4、Aspect类的组成  一般都用注解 重点
      1)Advice(AspectJ中的通知类型)
       i、Before

      
    //@Before("LogPointcuts.logAdd()") 方法1

               
    @Before("execution(* .(..))") 方法2

            
    public void logBefore(JoinPoint joinPoint){

        
    logger.log("{before} the method: "

        
      +joinPoint.getSignature().getName()+

        
      
    args:"+Arrays.toString(joinPoint.getArgs())); 

     }

                
    注:方法1和方法2都是可以的,方法1的话需要在定义一个方法.

      在同一个类或不同的类都是可以的 直接调用这个方法

                     
    @Pointcut("execution(* .(..))")

             
    public void logOperation(){}

       ii、AfterReturning

      @AfterReturning(pointcut="execution(*
    .(..))",

       returning="result")

        public void
    afterReturning(JoinPoint joinPoint,Object result){

       logger.log("{AfterReturning}
    the method: "

         
      +joinPoint.getSignature().getName()

         
      +"
    result:"+result); 

     }

     
      注意:方法有两个参数,在注解中的returning的值必须和Object的值完全一样

       iii、AfterThrowing

      @AfterThrowing(pointcut="execution(*
    .(..))",

       throwing="e")

        public void
    afterThrowing(JoinPoint joinPoint,IllegalArgumentException
    e){

      logger.log("{AfterThrowing} the
    method: "

        
      +joinPoint.getSignature().getName()

        
      +" e:"+e.getMessage());

     }

       iv、After:相当于finally,它在AfterReturning

         和AfterThrowing之后执行。

         它的作用——一般为释放资源(如关闭Connection)

      @After("execution(*
    .(..))")

     public void after(JoinPoint joinPoint){

      logger.log("{After}
    method:"+joinPoint.getSignature().getName());

     }

      注意:After是肯定是要在最后执行的,
       v、Around

      @Around("execution(*
    .(..))")

     public Object logAround(ProceedingJoinPoint
    joinPoint) throws Throwable{

      logger.log("{Arount}Before
    method:"

        +joinPoint.getSignature().getName());

      try{

                          
    //一定不要忘记调用目标方法

       Object result
    = joinPoint.proceed();

       

       logger.log("{Arount}After
    method:"

         +joinPoint.getSignature().getName()

         +"
    result:"+result);

            
    //返回目标方法的返回值

       return
    result;

      }

      catch(IllegalArgumentException
    e){

       logger.log("{Around}Exception:Illegal
    Argument"

         +Arrays.toString(joinPoint.getArgs()));

       throw
    e;

      }

      注意:Around最好不要和其他四种同时使用

      把处理日志的代码放到一起就是切面,模块化(Aspect)

      5.Introduction(引入)
       1)Introduction的定义
      给正在运行的程序中的对象添加方法。
       其实是一种动态技术

      
    2)问题:如何给ArithmeticCalculatorImp类加入

       求最大值和最小值的方法

      
    假设:最大值和最小值的方法已经存在,且分别属于不同的类。

     extends MaxCalculatorImp, MinCalculatorImp
    不行,java不允许多继承

       3)要点:

     a、实现一个POJO类

      定义@DeclareParents来"继承"的父类

            
    @DeclareParents(

      value="myspring.calculator.ArithmeticCalculatorImp",

      defaultImpl=MaxCalculatorImp.class

     )

     private MaxCalculator maxCalculator;

     

     @DeclareParents(

       value="myspring.calculator.ArithmeticCalculatorImp",

       defaultImpl=MinCalculatorImp.class

      )

      private MinCalculator
    minCalculator;   

     b、定义这个Bean:在Bean定义文件中声明一下。

            
    <bean
    class="myspring.calculator.CalculatorIntroduction"></bean>

       
    在test类中测试时:ApplicationContext ac =

      new
    ClassPathXmlApplicationContext("aop.xml");

      

      IArithmeticCalculator cal =
    (IArithmeticCalculator)

      ac.getBean("arith");

      cal.add(2, 3);

      cal.div(4, 3);

      

      MaxCalculator max =
    (MaxCalculator)cal;

      max.max(3, 6);

      MinCalculator min =
    (MinCalculator)cal;

      min.min(3, 6);

     

    spring之AOP
  • 相关阅读:
    【PL/SQL练习】函数
    【PL/SQL练习】命名块: 存储过程、函数、触发器、包
    【PL/SQL练习】自定义异常
    织梦首页/列表页文章按权重排序
    织梦dede:channel取子栏目时重复显示同级栏目的解决方法
    织梦最新版后台一键更新网站、更新文档HTML卡死的解决方法
    虚拟主机二级域名(m.dedehtml.com)绑定到织梦子目录(m)做手机站
    织梦自定义表单数据校验不对的解决方法
    织梦自定义表单用js代替联动地区解决联动地区选择
    织梦自定义图片字段调用图片地址
  • 原文地址:https://www.cnblogs.com/jpfss/p/8126345.html
Copyright © 2020-2023  润新知