springAOP学习1

来源:互联网 发布:mac 开ssh 端口 编辑:程序博客网 时间:2024/06/05 19:57

AOP面向切面编程

   在使用面向对象编程OOP时,当需要为多个不具有继承关系的对象引入同一个公共行为时,如日志、安全检测等,我们只有在每个对象里引用公共行为,这样程序中就产生了大量的重复代码,程序就不便维护,所以就有了一个对面向对象编程的不成,面向方面编程AOP(ASPECT ORIENTEDPROGRAMMING),所以AOP是关注与横向的,不同于纵向的OOP。(有点合纵连横的意思啊,只不过这里是互补啊,而不是战国的互相克制啊。)摘自郝佳编著的《spring源码深度解析》

   Spring2.0开始对于AOP进行了大量的改进,以弥补之前版本的不足,因此这里也是按照从2.0及其以上的版本学习研究的。Spring2.0采用@AspectJ注解对POJO进行标注,从而定义一个包含切点信息和增强横切逻辑的切面。

  

   这里的研究主要根据林信良著的《spring2.0技术手册》和spring相关源码和spring的源码深度解析这三部分研究而得。

 

1.  从代理机制到AOP

 package onlyfun.caterpillar;

 

import java.util.logging.Level;

import java.util.logging.Logger;

 

public classHelloSperker {

   private Loggerlogger = Logger.getLogger(this.getClass().getName());

  

   public void hello(String name){

      //方法执行开始时留下记录

      logger.log(Level.INFO,"hello method starting ...");;

     

      System.out.println("name: "+name);

     

      //方法结束时留下记录

      logger.log(Level.INFO,"hello method ending ...");

     

   }

   public static void main(String []args){

      HelloSperkerhelloSperk= newHelloSperker();

      helloSperk.hello("adu");

   }

}

如果分析这段代码,当执行hello时在方法开始和结束都进行相应的记录,最简单的方法就是如上面使用日志动作,然而对于HelloSperker来说,日志的这几个动作并不属于它的业务逻辑,因此是给HelloSperker增加了额外的工作。

如果有大量的类似的程序,里面都需要进行与业务无关的日志记录、权限检查、事务管理等,而且如果要维护这些代码,如果仍然是这样写法,维护必要要耗费大量时间,负担不可谓不重啊。而且这样的代码,如果哪一天不需要相关的日志等服务了,那么修改也会让我们很痛苦。如何解决这样令我们痛苦的事情那?

一个好的方法就是使用代理proxy。代理可分为静态代理和动态代理。

静态代理:在静态代理中,代理对象与被代理对象必须实现同一个接口,在代理对象中可以实现日志等相关服务,并在需要的时候再调用被代理的对象,被代理对象当中就可以仅保留与业务相关的职责。

例子:

#Interface:IHello

package onlyfun.caterpillar;

 

public interfaceIHello {

public voidhell(String name);

}

#Class:HelloSperker1 :implements IHello

package onlyfun.caterpillar;

 

public classHelloSperker1 implements IHello {

 

   @Override

   public void hell(String name) {

      // TODO Auto-generated method stub

      System.out.println("name:"+name);

   }

 

}

#class:HelloProxy:implements IHello

package onlyfun.caterpillar;

 

import java.util.logging.Level;

import java.util.logging.Logger;

 

public classHelloProxy implementsIHello {

   private Loggerlogger = Logger.getLogger(this.getClass().getName());

   private IHellohelloObject;

   public HelloProxy(IHellohelloObject){

      this.helloObject =helloObject;

   }

   @Override

   public void hell(String name) {

      // TODO Auto-generated method stub

      log("hello method starting ....");

      helloObject.hell(name);

      log("hello method ending ...");

   }

   public void log(String msg){

      logger.log(Level.INFO,msg);

   }

}

#StaticProxyTest

package onlyfun.caterpillar;

 

public classStaticProxyTest {

 

   public static void main(String[] args) {

      // TODO Auto-generated method stub

      IHellohelloObject  = new HelloSperker1();

      HelloProxyproxy= newHelloProxy(helloObject);

      proxy.hell("along");

   }

 

}

这里确实是将业务和与业务无关的内容分开了,比较之前确实是有较大改善,但是如果这样每个业务类都需要对应一个这样的proxy,那也是工作量巨大,有没有更好的方式那。

自己的说明:这里代理类可以不实现IHello接口,只不过在该代理类内部写一个一样的方法,即可,只不过不是实现,貌似也能实现效果。有点像设计模式里的门面模式(装饰模式)吧。

下面将动态代理:

   在jdk1.3之后加入了可协助开发动态代理功能的API,使用动态代理,因此可以使用一个handler处理服务于各个对象,但是这个处理者的类handler必须实现java.lang.reflect.InvocationHandler接口。

具体的实现是:

package onlyfun.caterpillar;

 

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

import java.lang.reflect.Proxy;

import java.util.logging.Level;

import java.util.logging.Logger;

 

public classLogHandler implementsInvocationHandler {

   private Loggerlogger = Logger.getLogger(this.getClass().getName());

   //声明一个代理对象

   private Objectdelegate;

   //绑定该代理对象,返回一个代理,期间需要指定所要代理的接口

   public Object bind(Objectdelegate){

      this.delegate  =delegate;

      return Proxy.newProxyInstance(delegate.getClass().getClassLoader(),

                              delegate.getClass().getInterfaces(),

                              this);

   }

//重写invoke方法(使用代理时每次操作都会执行的方法,实际上执行的是

//method.invoke,在它前后加上日志动作即可)

   @Override

   public Object invoke(Objectproxy, Method method, Object[]args) throws Throwable {

      // TODO Auto-generated method stub

      Objectresult  =null;

      try{

         log("method starts: ....["+method+"]...");

         result  =method.invoke(delegate,args);

         log("method end:...["+method+"]...");

      }catch(Exceptione){

         log(e.toString());

        

      }

      return result;

   }

   public void log(String msg){

      logger.log(Level.INFO,msg);

   }

}

#动态代理测试

package onlyfun.caterpillar;

 

public classProxyDemo {

 

   public static void main(String[] args) {

      // TODO Auto-generated method stub

//初始化动态代理handler对象

      LogHandlerlogHandler= newLogHandler();

//绑定需要被代理的对象实例,并返回一个代理对象,

      IHello  helloSperk = (IHello)logHandler.bind(new HelloSperker1());

//执行输出

      helloSperk.hell("dudu");

   }

 

}

 

这里可以看出LogHandler不在执行与特定的接口,这里根据指定的接口代替了原来静态代理中的实现接口的方式,从而达到通用效果。

这个从单独没有使用代理,到使用静态代理,再到使用动态代理,这样逐层精进,逐层将业务独立,逐层达到设计上的复用、可维护等特性,正是体现了设计的方向。

 

如果从AOP角度理解的话,HelloSperker1本身的功能是显示招呼文字,却必须要加入日志动作,功能繁杂,用AOP术语,日志代码横切cross-cutting入了HelloSperker1程序的执行流程中,日志这样的动作在AOP中称为横切点关注cross-cuttingconcern。

使用代理对象将日志等与无关业务逻辑的动作提取出来,设计成一个服务对象,HelloProxy,LogHandler,这样的对象称为切面(Aspect)。

 

AOP中的Aspect所指的像日志等这类的动作或服务,将这些动作(cross-cutting)设计为通用、不介入特定业务对象的一个职责清晰的Aspect对象,这就是所谓的Aspect orientedprogramming (AOP).

 

2.  AOP术语

Aop aspect oriented programming 面向切面编程。

 

Cross-cutting concern 横切关注:就如日志等功能被横切如HelloSperker中,类似的是一些系统的服务,在一些应用程序中常被安插到各个对象的处理流程中,这些动作在AOP术语中称为cross-cutting concern。

 

Aspect将散落的各个业务逻辑中的cross-cuttingconcerns收集起来,设计成各个独立可重用的对象,这些对象称为Aspect。例如前面的例子中将日志动作设计为一个LogHandler类,LogHandler类在AOP的术语就是一个Aspect的一个具体的实例。在AOP中着重于aspect的辨认,使之从业务流程中独立出来。在需要该服务的时候,织入weave至应用程序之上;不需要服务的时候,也可以从应用程序中脱离出来,且应用程序中的可重用组件不用做任何修改。

另一方面,对于应用程序中可重用的组件,按照AOP的设计方式,不需要知道提供的服务的对象是否存在,具体说就是与服务相关API不会出现在可重用的应用程序组件中,因而可以提高这些组件的重用性,不会产生耦合。

 

 

Advice

Aspect当中对cross-cuttingconcerns的具体实现称之为Advice.以日志的动作而言,advice中包括日志程序是如何实现的,像动态代理中的LogHander的invoke方法,就是一个advice具体例子。Advice中包括了cross-cutting concerns的行为或所需要提供的服务。

 

Jointpoint

Advice在应用程序执行时加入业务流程的点或时机称为jointpoint,就是advice在应用程序中执行的时机。Spring只支持方法的jointpoint,执行时机可能是每个方法被执行之前或之后,或是方法中某个异常发生的时候。

 

Pointcut

Pointcut定义了感兴趣的jointpoint,当调用的方法符合pointcut表达式时,将advice编织如应用程序上提供服务。在spring2.0中,可以定义文件或annotation中编写pointcut,说明哪些advice要应用到方法的前后。

 

Target

一个advice被应用的对象或目标对象。

 

Introduction

对于一个现存的类,introduction可以为其增加行为,且不修改该类的程序,具体来说可以为某个已经编写或编译完的类,在执行时期动态加入一些方法或行为,而不用修改或新增任何一段程序代码。?

 

Proxy在spring中AOP主要是通过动态代理实现的,可用于代理任何的接口。另一方面spring也可以使用CGLIB代理,用以代理类,像一些遗留类(Legacy classes).

 

weave

advice被应用至对象之上的过程称为织入weave,在AOP中织入的方式有几个时间点:编译时期、类加载时期、执行时期。

 

Spring AOP

不同的AOP框架会有其对AOP概念的不同实现方式,主要差别在于所提供的pointcut、aspect的丰富程度,以及他们如何被织入weave至应用程序(像pointcuts的定义方式)、代理的方式等。

 

Spring的advice是用java程序语言编写的,它不使用特定的AOP语言,在定义pointcuts时可以使用xml配置文件或annotation。

 

Spring2.0中提供了3中实现AOP的方式

(1)  实现spring api

(2)  基于xml的配置

(3)  使用@AspectJ的annotation。由于使用annotation,必须需要在JDK5.0版本以上才可以,好处是不用在XML文件中做过多的设置,只要在advice上标记何时的annotation即可完成设置。

Spring2.0的方式的advice是在执行时期导入至Targets的,可以让目标实现预先定义好的接口,spring在执行时期会使用java.lang.proxy来进行动态代理,如果不实现接口,spring则会使用CGLIB为targets产生一个子类作为代理proxy classes。

 

Spring只支持jointpoint,也就是advices将在方法执行的前后被使用,spring不支持Field成员的jointpoint。

 

2.2     advices(比如日志记录、权限过滤、事务管理)

advices实现了aspect的真正逻辑,具体来说在java中就是一个类或更细力度的设计成一个方法(由一个类来几种管理许多advices).由于织入至targets的时机不同,spring提供了几种不同的advices,像beforeAdvice、AfterAdvice,AroundAdvice,ThrowAdvice等。我们先从编写使用advice来了解Spring AOP.

(1)     BeforeAdvice

Beforeadvice会在目标对象的方法执行之前被调用,可以实现org.springframework.aop.MethodBeforeAdvice接口来实现

其定义:

 

public interfaceMethodBeforeAdvice extendsBeforeAdvice{

   void before(Methodmethod, Object[] args, Objecttarget) throws Throwable;

}  从其定义中我们可以看到MethodBeforeAdvice接口继承自BeforeAdvice接口,BeforeAdvice接口又继承自Advice接口,后两者都是空的接口,只是标志它是标签接口,methodBeforeAdvice接口中有before方法,暂时只是支持方法的beforeAdvice,以后可能会有对于field的支持。

它只是在方法执行前运行该服务,并不会影响原有方法的运行,除非是丢出了异常。

接下来我们再看之前的日志动作改由AOP操作时,如下:

#IHello

package onlyfun.caterpillar;

 

public interfaceIHello {

public voidhell(String name);

}

#HelloSperker1
packageonlyfun.caterpillar;

 

public classHelloSperker1 implements IHello {

 

   @Override

   public void hell(String name) {

      // TODO Auto-generated method stub

      System.out.println("name:"+name);

   }

 

}

#LogBeforeAdvice

package onlyfun.myaop;

import java.lang.reflect.Method;

import java.util.logging.Level;

import java.util.logging.Logger;

 

import org.springframework.aop.MethodBeforeAdvice;

public classLogBeforeAdvice implements MethodBeforeAdvice {

   private Loggerlogger  = Logger.getLogger(this.getClass().getName());

 

   public void log(String msg){

      logger.log(Level.INFO,msg);

   }

 

   @Override

   public void before(Method method, Object[]args, Object target)throws Throwable {

      // TODO Auto-generated method stub

      log("method starting ...."+method);

   }

}

到这里可以看到HelloSperker1中只是相关业务,而LogBeforeAdvice中则只是和日志相关的内容,两者完全绝缘,没有交点。下面就是配置文件:

<?xmlversion="1.0"encoding="UTF-8"?>

<beansxmlns="http://www.springframework.org/schema/beans" 

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:aop="http://www.springframework.org/schema/aop" 

    xmlns:tx="http://www.springframework.org/schema/tx" 

    xmlns:context="http://www.springframework.org/schema/context"  

    xsi:schemaLocation=

        http://www.springframework.org/schema/context  

        http://www.springframework.org/schema/context/spring-context-3.0.xsd   

    http://www.springframework.org/schema/beans   

    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 

    http://www.springframework.org/schema/tx  

    http://www.springframework.org/schema/tx/spring-tx-3.0.xsd 

    http://www.springframework.org/schema/aop  

     http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> 

    

    <beanid="logBeforeAdvice"class="onlyfun.myaop.LogBeforeAdvice"/>

 

    <beanid= "helloSperker"class ="onlyfun.caterpillar.HelloSperker1"/>

   

    <beanid="helloProxy"class="org.springframework.aop.framework.ProxyFactoryBean">

       <propertyname="proxyInterfaces"value ="onlyfun.caterpillar.IHello"/>

       <propertyname="target"ref ="helloSperker"/>

       <propertyname="interceptorNames" >

                <list>

                   <value>logBeforeAdvice</value>

                </list>

       </property>

   </bean>

 </beans>

 

这里需要重点关注一下:

<beanid="helloProxy"class="org.springframework.aop.framework.ProxyFactoryBean">

       <propertyname="proxyInterfaces"value ="onlyfun.caterpillar.IHello"/>

       <propertyname="target"ref ="helloSperker"/>

       <propertyname="interceptorNames" >

                <list>

                   <value>logBeforeAdvice</value>

                </list>

       </property>

   </bean>

proxyFactoryBean是代理工厂的bean,用于生产相应的代理对象,(这个类会被beanfactory用来创建代理)。ProxyInterfaces指定代理的接口,

traget为被代理的对象目标,这里是helloSperker;

interceptroNames为设置advice实例,可能有多个advice实例,这里使用了list,before advice 会被织入至接口上所有定义的方法之前(如果不指定目标方法)

#test

package onlyfun.myaoptest;

 

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;

 

import onlyfun.caterpillar.HelloSperker1;

import onlyfun.caterpillar.IHello;

 

 

public classLogBeforeTest {

 

   public static void main(String[] args) {

      // TODO Auto-generated method stub

      ApplicationContextcontext =newClassPathXmlApplicationContext("spring-config.xml");

      IHellohelloSperk= (IHello)context.getBean("helloProxy");

      helloSperk.hell("adu aop test");

   }

 

}

这里在操作取回的对象代理时,必须转换做接口才可执行,不然的会报转换异常的错误。

(2)afteradvice

 Afteradvice会在方法执行之后运行,可以实现org.springframework.aop.AfterReturningAdvice接口来实现afteradvice逻辑

package onlyfun.myaop;

import java.lang.reflect.Method;

import java.util.logging.Level;

import java.util.logging.Logger;

import org.springframework.aop.AfterReturningAdvice;

 

public classLogAfterAdvice implements AfterReturningAdvice {

   private Loggerlogger = Logger.getLogger(this.getClass().getName());

   public void log(String msg){

      logger.log(Level.INFO,msg);

   }

   @Override

   public void afterReturning(Object returnValue, Methodmethod, Object[] args,Object target)throwsThrowable {

      // TODO Auto-generated method stub

      log("method ending ..."+method);

   }

}

 

配置文件中部分内容为:

 

 <beanid= "logBeforeAdvice"class ="onlyfun.myaop.LogBeforeAdvice"/>

 <beanid= "logAfterAdvice" class = "onlyfun.myaop.LogAfterAdvice"/>

  <beanid = "helloSperker"   class = "onlyfun.caterpillar.HelloSperker1"/>

   

    <beanid = "helloProxy"      class="org.springframework.aop.framework.ProxyFactoryBean">

       <propertyname= "proxyInterfaces"value ="onlyfun.caterpillar.IHello" />

       <propertyname= "target"ref ="helloSperker"/>

       <propertyname= "interceptorNames" >

                <list>

                   <value>logBeforeAdvice</value>

                   <value>logAfterAdvice</value>

                </list>

       </property>

   </bean>

   其他文件不动。

(3)如果想在业务方法之前前后进行切入,可以联合使用MethodBeforeAdvice和AfterReturningAdvice,也可以使用org.aopalliance.intercept.MethodInterceptor接口,于方法执行前、后执行相关的服务。

package onlyfun.myaop;

 

import java.util.logging.Level;

import java.util.logging.Logger;

 

import org.aopalliance.intercept.MethodInterceptor;

import org.aopalliance.intercept.MethodInvocation;

 

public classLogInterceptor implements MethodInterceptor {

   private Loggerlogger  = Logger.getLogger(this.getClass().getName());

  

   @Override

   public Objectinvoke(MethodInvocationmethodInvocation) throwsThrowable {

      // TODO Auto-generated method stub

      Objectresult= null;

      logger.log(Level.INFO,"method starting..."+methodInvocation.getMethod());

      try{

         result = methodInvocation.proceed();

      }catch(Exceptione){

         logger.info(e.toString());

      }finally{

         logger.log(Level.INFO,"method ending ..."+methodInvocation.getMethod());

        

      }

     

      return result;

   }

 

}

实现的invoke方法,有返回值,

public Object invoke(MethodInvocationmethodInvocation) throwsThrowable {

methodInvocation的proceed()方法来执行目标对象。该方法会回传执行后的object执行结果,所以在invoke执行结束之前,我们可以修改该对象,这要视需要而定,当然一般不要修改。另外这里处理可能会出现异常最好加上finally,里面放置一些业务方法执行后要执行的服务。

Spring在真正执行某个方法前,会先插入interceptor,如果有多个,每个interceptor会执行自己的处理,然后执行method下一个Interceptor,如果没有下一个,就执行真正调用的方法,方法执行完毕,在一层层返回interceptor堆栈,最后离开堆栈返回应用程序本身流程。

 

(4)throw  advice

  Throw advice 在发生异常时可以通知某些服务对象做出处理。实现org.springframework.aop.ThrowsAdvice接口

#IHello

package onlyfun.caterpillar;

 

public interfaceIHello {

public voidhell(String name)throwsThrowable;

 

}

#HelloSperker1

package onlyfun.caterpillar;

 

import onlyfun.exception.MyException;

 

public classHelloSperker1 implements IHello {

 

   @Override

   public void hell(String name)throws Throwable {

      // TODO Auto-generated method stub

      System.out.println("name:"+name);

      throw new MyException("my exception is coming up...");

   }

 

}

#throwableAdvice

package onlyfun.myaop;

 

import java.lang.reflect.Method;

import java.util.logging.Level;

import java.util.logging.Logger;

 

import org.springframework.aop.ThrowsAdvice;

 

public classThrowAdviceDemo implements ThrowsAdvice {

   private Loggerlogger = Logger.getLogger(this.getClass().getName());

  

   public void afterThrowing(Throwablethrowable){

      logger.log(Level.INFO,"there is a exception :"+throwable.toString());

   }

   public void afterThrowing(Method method,Object[]args,Object target,Throwablethrowable)

   {

      logger.log(Level.INFO,"there is a exception :"+throwable.toString());

   }

}

#xml

 

   <beanid= "throwAdvice"    class = "onlyfun.myaop.ThrowAdviceDemo"/>

    <beanid = "helloSperker"   class = "onlyfun.caterpillar.HelloSperker1"/>

   

    <beanid = "helloProxy"      class="org.springframework.aop.framework.ProxyFactoryBean">

       <propertyname= "proxyInterfaces"value ="onlyfun.caterpillar.IHello" />

       <propertyname= "target"ref ="helloSperker"/>

       <propertyname= "interceptorNames" >

                <list>

                          <value>throwAdvice</value>

                </list>

       </property>

   </bean>

#test

public staticvoidmain(String[] args){

      // TODO Auto-generated method stub

      ApplicationContextcontext=newClassPathXmlApplicationContext("spring-config.xml");

      IHellohelloSperk= (IHello)context.getBean("helloProxy");

      try {

         helloSperk.hell("adu aop test");

      }catch(Throwablee) {

         // TODO Auto-generated catch block

         e.printStackTrace();

      }

   }

这里的afterThrowing方法,如果声明了不同的异常类型,则按照异常发生的类型不同,通知不同的方法处理。注意发生异常时,throwadvice的任务只是执行对象的方法,并不能在throwsadvice中将异常处理掉,在throwsadvice执行完后,异常仍被传播至应用程序中。

下图为spring3.2.4jar包外需要的其他jar

0 0
原创粉丝点击