`
haofenglemon
  • 浏览: 240456 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

spring Aop中动态代理

    博客分类:
  • user
阅读更多
Spring 缺省使用J2SE 动态代理(dynamic proxies ) 来作为AOP 的代理。这样任何接口都可以被代理。Spring 也支持使用CGLIB 代理. 对于需要代理类而不是代理接口的时候CGLIB 代理是很有必要的。如果一个业务对象并没有实现一个接口,默认就会使用CGLIB 这是Spring Framework 开发手册中对AOP 的一个简要概括 在我看来 SF 是个烩面杂烩,它提供了许多框架的接口。当然它自己也有一套 MVC 框架。其中最为常见也最为重要也就是 IoC 和 AOP 。所谓的 LightWeight Container( 轻量级 ) 就是整个容器的侵入性极低或者没有侵入性让对象与对象之间的关系通过配置来体现避免了对象之间的直接调用 ( 当然这不是轻量级容器的完全定义 ), 轻量级带来的就是单例和工厂的有效减少。IoC 是一种思想,它的实现有依赖注入和依赖查找。开发中遇到比较多的就是依赖注入 Spring 所提供的方法有 (Setter 方法注入,构造器注入 以及接口注入 ) 三种方法的使用程度也如我所列的顺序一样,当然各人所好不同。 AOP 是面向切面编程,平时我们所面对的都是 OO 那是一个纵向的编程思想,而 AOP 的出现使得面向的切面 ( 即横向编程 ) 的理念得到了众多的认可。其实 AOP 的思想早期在 EJB( 个人对 EJB 了解不是很够,这里就不在细说 ) 中也得以体现,最为常用的就是声明式事务的使用。
ex:比如我声明这个类中所有以 save 开头的接口都用事务,所以当其被调用时开启事务,成功后提交事务,失败了就回滚事务。那 Spring 中所提供的 AOP 与 EJB 中的所采用的拦截机制有什么区别呢?对早期的 EJB(3.0 之前 ) 来说,你只有实现了 EJB 才有该功能而 Spring 则不同, Spring 对普通的 POJO 都可以实现 AOP 。这就是为什么 EJB2.x 以失败而告终,所以当 EJB3.0 卷土重来时它就加入对 Spring 的集成。这也是开源的一大优势呐。 AOP : Spring 中默认是通过 JDK 动态代理来是 AOP 。其实你要是对 JDK 动态代理理解烂熟于心我想我下面的内容你是不用看了。如果你还是不怎么熟悉,希望大家一起学习。JDK 动态代理分静态代理和动态代理,其中静态代理适用于代理比较少的情形它是一个实实在在的代理类所以当代理比较多的时候你得去编写许多代理类效率自然就下降了。而动态是在运行时才生产的,当你调用时才生成代理当然它的前提是继承接口(invocationHandler) 实现 invoke() 方法。下面我们看个动态代理的例子:
一个接口: UserManagery
接口的实现类: UserManageryImpl
代理类: SecurityHandler
以及一个简单的客户端: Client
public interface UserManager {  
public void  addUser();  
public void removeUser();}
public class UsreManagerImpl implements UserManager {  
   public void addUser() {  
        /* 
         * 比如说要在添加之前做一些安全性检查,当然最原始的做法时在调用方法之前写一些验证代码。你可以将验证专门抽取出来写成一个方法甚至一个类,然后进行调用。 For example 
         * 该类中抽取出一个scurity()的方法用于验证,不过你每次验证都需要如下的调用 
         * 如此来若需要的调用的方法多了,方法甚至类就不再单一了。甚至一眼看不出这到底是一个具体功能模块 
         * 还是验证模块。这样类就不再便于管理(方法太多)。于是就出现了代理,通过代理类来实现那些不是主要的功能 
         * 这样模块的功能就很清晰,同时你在不修改原先类的情况下给该类添加功能实现 
         */ 
        // security();  
        System.out.println("-----addUser-----");  
    }  
 
    public void removeUser() {  
        System.out.println("-----remove-----");  
    }     

只是此时提及一下代理的作用,比如说我在调用 ADD() 方法之前需要进行安全验证(这是个很常规的步骤)传统的编码方式就是将验证方法直接写在类中,当然这无可厚非但是当需要调用的方法不断增加时整个类的就会很模糊。有人说我将需要验证的方法单独抽象出来成一个类。但这样你也要在原来的类中不断的用实例化这个验证类,这也存在所需方法不断增多的情况。这样我们就考虑 又要调用验证又要不去破坏(修改)原来类的代码。所以代理就粉墨登场,通过一个代理类来实现这个功能。
* 创建一个专门的执行security方法的类。实现InvocationHandler接口  
import java.lang.reflect.InvocationHandler;  
import java.lang.reflect.Method;  
import java.lang.reflect.Proxy;  
public class SecurityHandler implements InvocationHandler {  
    //适合于所有的对象  
    private Object object;  
      
    //通过构造方法将参数传递  
    public Object newProxy(Object object){  
        this.object=object;  
        //生成动态代理 3个参数  
        return Proxy.newProxyInstance(object.getClass().getClassLoader(),   object.getClass().getInterfaces(),this);  
    }  
    public Object invoke(Object arg0, Method method, Object[] arg2)  
            throws Throwable {  
        //代理类在调用任何方法都先调用invoke()方法,这里的invoke()方法中先执行checkSecurity()方法  
        checkSecurity();  
        //这里可以查看invoke中调用方法的名称  
        System.out.println("method name="+method.getName());  
        //该参数是一个数组类型Object[] arg2  
        for(int i=0;i<arg2.length;i++){  
            System.out.println(arg2[i]);  
        }  
        Object result=null;  
        //下面才是真正调用的方法,将对象添加到invoke 方法中  
        try{  
        result=method.invoke(object, arg2);  
        //方法执行之后,也可以自定义方法  
        }catch(Exception e){  
            e.printStackTrace();  
        }  
        return result;  
    }  
    public void checkSecurity(){  
        System.out.println("-----checkSecurity------");  
    }  
 

下面我们来细细分析这个类,首先实现接口 invocationHandler ,还有方法 invoke(). 当代理类产生代理之后,在调用所有的方法之前都会先执行 invoke() 方法(想到 AOP 中的 BeforeAdvice 了吗?)。方法 checkSecurity() 就是我提到的验证方法,我们写在代理类中,而你在原先类中去看不到他的具体引用。其中 method.getName() 是获得所传入对象所调用的方法的方法名。 ( 假如你只需要对名称为 addUser 的方法进行单独验证,加个条件判断不就可以了麽,有点 Spring 的味道了吗? ) 再看, method.invoke() 这才是真正调用的方法,在到这一步之前我已经添加了许多验证方法了,而这在原先类中却什么都看不到一切我们都在代理类中实现的。这就避免了对原先类的修改了。当然这个方法调用之后,你还可以继续添加方法(想到了 AOP 的 afteradvice 通知了吗?)
当你所需要的方法不断增多是你不是可以写成 xml 文件么,通过 xml 文件来配置方法。 Spring 大致就是从这个思想演变过来的。
public class Client {  
  public static void main(String args[]){  
      SecurityHandler handler=new SecurityHandler();  
      UserManager userManager=(UserManager)handler.newProxy(new UsreManagerImpl());  
      userManager.addUser();  
  }  

通过代类理的实例来代理原先类( newProxy(new UserManagerImple()) ) . 然后你再去调用 addUser() 方法。所有的验证都添加进去了。 可能讲了半天有的朋友还只是说没有 AOP ,其实要讲 AOP 不一定要把 AOP 啃个遍,关键是要理解如何实现 AOP 如何区别于 OOP 的纵向编程。




面向切面编程基础

通常,系统由很多组件组成,每个组件负责一部分功能,然而,这些组件也经常带有一些除了核心功能之外的附带功能 。系统服务如日志、事务管理和安全经常融入到一些其他功能模块中。这些系统服务通常叫做交叉业务,这是因为它们总是分布在系统的很多组件中。通过将这些业务分布在多个组件中,给我们的代码引入了双重复杂性。

(1) 实现系统级业务的代码在多个组件中复制。这意味着如果你要改变这些业务逻辑,你就必须到各个模块去修改。就算把这些业务抽象成一个独立模块,其它模块只是调用它的一个方法,但是这个方法调用也还是分布在很多地方。

(2) 组件会因为那些与自己核心业务无关的代码变得杂乱。一个向地址录中添加条目的方法应该只关心如何添加地址,而不是关心它是不是安全或支持事务的。

此时,我们该怎么办呢?这正是AOP用得着的地方。AOP帮助我们将这些服务模块化,并把它们声明式地应用在需要它们的地方,使得这些组件更加专注于自身业务,完全不知道其它涉及到的系统服务。

这里的概念切面,就是我们要实现的交叉功能,是应用系统模块化的一个方面或领域。切面的最常见例子就是日志记录。日志记录在系统中到处需要用到,利用继承来重用日志模块是不合适的,这样,就可以创建一个日志记录切面,并且使用AOP在系统中应用。

通知Advice是切面的实际实现。连接点Joinpoint是应用程序执行过程中插入切面的地点,这个地点可以是方法调用,异常抛出,甚至可以是要修改的字段,切面代码在这些地方插入到你的应用流程中,添加新的行为。切入点Pointcut定义了Advice应该应用在那些连接点,通常通过指定类名和方法名,或者匹配类名和方法名式样的正则表达式来指定切入点。

AOP在Spring中的实现

基于AOP,业界存在各种各样的AOP实现,比如,JBoss AOP、Spring AOP、AspectJ、Aspect Werkz等。各自实现的功能也不一样。AOP实现的强弱在很大程度上取决于连接点模型。目前,Spring只支持方法级的连接点。这和一些其他AOP框架不一样,如AspectJ和JBoss,它们还提供了属性接入点,这样可以防止你创建特别细致的通知,如对更新对象属性值进行拦截。然而,由于Spring关注于提供一个实现J2EE服务的框架,所以方法拦截可以满足大部分要求,而且Spring的观点是属性拦截破坏了封装,让Advice触发在属性值改变而不是方法调用上无疑是破坏了这个概念。

Spring的AOP框架的关键点如下:

(1)Spring实现了AOP联盟接口。在Spring AOP中,存在如下几种通知(Advice)类型

Before通知:在目标方法被调用前调用,涉及接口org.springframework.aop.MethodBeforeAdvice;

After通知:在目标方法被调用后调用,涉及接口为org.springframework.aop.AfterReturningAdvice;

Throws通知:目标方法抛出异常时调用,涉及接口org.springframework.aop.MethodBeforeAdvice;

Around通知:拦截对目标对象方法调用,涉及接口为org.aopalliance.intercept.MethodInterceptor。

(2)用java编写Spring通知,并在Spring的配置文件中,定义在什么地方应用通知的切入点。

(3)Spring的运行时通知对象。代理Bean只有在第一次被应用系统需要的时候才被创建。如果你使用的是ApplicationContext,代理对象在BeanFactory载入所有Bean的时候被创建。Spring有两种代理创建方式。如果目标对象实现了一个或多个接口暴露的方法,Spring将使用JDK的java.lang.reflect.Proxy类创建代理。这个类让Spring动态产生一个新的类,它实现所需的接口,织入了通知,并且代理对目标对象的所有请求。如果目标对象没有实现任何接口,Spring使用CGLIB库生成目标对象的子类。在创建这个子类的时候,Spring将通知织入,并且将对目标对象的调用委托给这个子类。此时,需要将Spring发行包lib/cglib目录下的JAR文件发布到应用系统中。

Spring AOP的优势

借助于Spring AOP,Spring IoC能够很方便的使用到非常健壮、灵活的企业级服务,是因为Spring AOP能够提供如下几方面的优势:

(1)允许开发者使用声明式企业服务,比如事务服务、安全性服务;EJB开发者都知道,EJB组件能够使用J2EE容器提供的声明式服务,但是这些服务要借助于EJB容器,而Spring AOP却不需要EJB容器,借助于Spring的事务抽象框架就可以这些服务。

(2)开发者可以开发满足业务需求的自定义切面;

(3)开发Spring AOP Advice很方便。因为这些AOP Advice仅是POJO类,借助于Spring提供的ProxyFactoryBean,能够快速的搭建Spring AOP Advice。


====================================================================== AOP正在成为软件开发的下一个圣杯。使用AOP,你可以将处理aspect的代码注入主程序,通常主程序的主要目的并不在于处理这些aspect。AOP可以防止代码混乱。
为了理解AOP如何做到这点,考虑一下记日志的工作。日志本身不太可能是你开发的主程序的主要任务。如果能将“不可见的”、通用的日志代码注入主程序中,那该多好啊。AOP可以帮助你做到。
Spring framework是很有前途的AOP技术。作为一种非侵略性的,轻型的AOP framework,你无需使用预编译器或其他的元标签,便可以在Java程序中使用它。这意味着开发团队里只需一人要对付AOP framework,其他人还是象往常一样编程。
AOP是很多直觉难以理解的术语的根源。幸运的是,你只要理解三个概念,就可以编写AOP模块。这三个概念是:advice,pointcut和advisor。advice是你想向别的程序内部不同的地方注入的代码。pointcut定义了需要注入advice的位置,通常是某个特定的类的一个public方法。advisor是pointcut和advice的装配器,是将advice注入主程序中预定义位置的代码。

既然我们知道了需要使用advisor向主要代码中注入“不可见的”advice,让我们实现一个Spring AOP的例子。在这个例子中,我们将实现一个before advice,这意味着advice的代码在被调用的public方法开始前被执行。以下是这个before advice的实现代码:
代码:
package com.company.springaop.test;

import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;

public class TestBeforeAdvice implements MethodBeforeAdvice {

  public void before(Method m, Object[] args, Object target)
  throws Throwable {
    System.out.println("Hello world! (by "
        + this.getClass().getName()
        + ")");
  }
}
接口MethodBeforeAdvice只有一个方法before需要实现,它定义了advice的实现。before方法共用三个参数,它们提供了相当丰富的信息。参数Method m是advice开始后执行的方法。方法名称可以用作判断是否执行代码的条件。Object[] args是传给被调用的public方法的参数数组。当需要记日志时,参数args和被执行方法的名称,都是非常有用的信息。你也可以改变传给m的参数,但要小心使用这个功能;编写最初主程序的程序员并不知道主程序可能会和传入参数的发生冲突。Object target是执行方法m对象的引用。
在下面的BeanImpl类中,每个public方法调用前,都会执行advice:
代码:
package com.company.springaop.test;
public class BeanImpl implements Bean {
  public void theMethod() {
    System.out.println(this.getClass().getName()
        + "." + new Exception().getStackTrace()[0].getMethodName()
        + "()"
        + " says HELLO!");
  }
}
类BeanImpl实现了下面的接口Bean:
代码:
package com.company.springaop.test;
public interface Bean {
  public void theMethod();
}
虽然不是必须使用接口,但面向接口而不是面向实现编程是良好的编程实践,Spring也鼓励这样做。
pointcut和advice通过配置文件来实现,因此,接下来你只需编写主方法的Java代码: 代码:
package
com.company.springaop.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class Main {

  public static void main(String[] args) {
    //Read the configuration file
    ApplicationContext ctx
        = new FileSystemXmlApplicationContext("springconfig.xml");

    //Instantiate an object
    Bean x = (Bean) ctx.getBean("bean");

    //Execute the public method of the bean (the test)
    x.theMethod();
  }
}




我们从读入和处理配置文件开始,接下来马上要创建它。这个配置文件将作为粘合程序不同部分的“胶水”。读入和处理配置文件后,我们会得到一个创建工厂ctx。任何一个Spring管理的对象都必须通过这个工厂来创建。对象通过工厂创建后便可正常使用。

仅仅用配置文件便可把程序的每一部分组装起来。 代码:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC  "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>
  <!--CONFIG-->
  <bean id="bean" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="proxyInterfaces">
      <value>com.company.springaop.test.Bean</value>
    </property>
    <property name="target">
      <ref local="beanTarget"/>
    </property>
    <property name="interceptorNames">
      <list>
        <value>theAdvisor</value>
      </list>
    </property>
  </bean>

  <!--CLASS-->
  <bean id="beanTarget" class="com.company.springaop.test.BeanImpl"/>

  <!--ADVISOR-->
  <!--Note: An advisor assembles pointcut and advice-->
  <bean id="theAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
    <property name="advice">
      <ref local="theBeforeAdvice"/>
    </property>
    <property name="pattern">
      <value>com\.company\.springaop\.test\.Bean\.theMethod</value>
    </property>
  </bean>

  <!--ADVICE-->
  <bean id="theBeforeAdvice" class="com.company.springaop.test.TestBeforeAdvice"/>
</beans>




四个bean定义的次序并不重要。我们现在有了一个advice,一个包含了正则表达式pointcut的advisor,一个主程序类和一个配置好的接口,通过工厂ctx,这个接口返回自己本身实现的一个引用。

BeanImpl和TestBeforeAdvice都是直接配置。我们用一个唯一的ID创建一个bean元素,并指定了一个实现类。这就是全部的工作。

advisor通过Spring framework提供的一个RegexMethodPointcutAdvisor类来实现。我们用advisor的一个属性来指定它所需的advice-bean。第二个属性则用正则表达式定义了pointcut,确保良好的性能和易读性。

最后配置的是bean,它可以通过一个工厂来创建。bean的定义看起来比实际上要复杂。bean是ProxyFactoryBean的一个实现,它是Spring framework的一部分。这个bean的行为通过一下的三个属性来定义:


属性proxyInterface定义了接口类。
属性target指向本地配置的一个bean,这个bean返回一个接口的实现。
属性interceptorNames是唯一允许定义一个值列表的属性。这个列表包含所有需要在beanTarget上执行的advisor。注意,advisor列表的次序是非常重要的。


Spring工具

虽然你可以手工修改Ant构建脚本,但使用SpringUI(译注:SpringUI现在是Spring framework的一部分,并改名为spring-ide),使用Spring AOP变得很简单,只要点点鼠标即可。你可以把SpringUI安装成Eclipse的一个plug-in。然后,你只需在你的project上右击鼠标,并选择“add Spring Project Nature”。在project属性中,你可以在“Spring Project”下添加Spring配置文件。在编译前把下面的类库加入project:aopalliance.jar,commons-logging.jar,jakarta-oro-2.0.7.jar和spring.jar。运行程序时你会看到下面的信息:

... (logging information)
Hello world! (by com.company.springaop.test.TestBeforeAdvice)
com.company.springaop.test.BeanImpl.theMethod() says HELLO!


优点和缺点

Spring比起其他的framework更有优势,因为除了AOP以外,它提供了更多别的功能。作为一个轻型framework,它在J2EE不同的部分都可以发挥作用。因此,即使不想使用Spring AOP,你可能还是想使用Spring。另一个优点是,Spring并不要求开发团队所有的人员都会用它。学习Spring应该从Spring reference的第一页开始。读了本文后,你应该可以更好地理解Spring reference了。Spring唯一的缺点是缺乏更多的文档,但它的mailing list是个很好的补充,而且会不断地出现更多的文档。


















分享到:
评论
1 楼 laodongbao 2014-05-07  
终于找到了我能理解和接受的的spring aop和动态代理的结合的文章了,赶紧标注下。

相关推荐

Global site tag (gtag.js) - Google Analytics