`

静态代理,动态代理,面向方面

阅读更多

代理的意思很好理解,它借鉴了我们日常所用的代理的意思:就是本来该自己亲自去做的某件事,由于某种原因不能直接做,而只能请人代替你做,这个被你请来做事的人就是代理。比如过春节要回家,由于你要上班,没时间去买票,就得票务中介代你购买,这就是一种代理模式。这个情景可以形象的描述如下:

class:火车站
{
    卖票:
    {……}
}

 

 火车站是卖票的地方,我们假设只能在火车站买到票。卖票的动作实质是火车站类完成的。

Class:票务中介
{
    卖票:
    {
        收中介费;
        火车站.卖票;
    }
}

 

 

  顾客找票务中介买票的时候,调用票务中介.卖票。票务中介其实做了两件事,一是去火车站买票,二是不能白帮你卖票,肯定要收中介费。


  以上我们简单模拟了代理模式的情景和为什么要使用代理模式,下面我们以一个例子来具体分析一下JAVA中的代理模式。

  假设有一个信息管理系统,用些用户有浏览信息的权限,有些用户有浏览、添加和修改信息的权限,还有些用户有除了上述的权限,还有删除信息的权限,那么我们最容易想到的做法如下:

public class ViewAction
{
    //由userId计算权限
    ……
    String permission = ……;
    if(permission.equals(Constants.VIEW))
    {
          System.out.println(“You could view the information……”);
          ……
    }
}

 

  其他的动作都和浏览信息的动作差不多。我们来看这样的类,很容易看出它的一些缺点来:第一、它把权限计算和动作执行都放在一个类里,两者的功能相互混在一起,容易造成思路的混乱,而且修改维护和测试都不好;一句话来说,它不满足单一职责原则[其实不满足单一职责功能也可以实现,但使用代理模式是更合理的设计]。第二是客户调用的时候依赖具体的类,造成扩展和运行期内的调用的困难,不满足依赖颠倒原则。[DI是OOD的一个原则;这里还是一个哪种设计更合理的问题。

 

既然有这么多的问题,我们有必要对该类进行重新设计。其实大家早已想到,这个类应该使用代理模式。是啊,和我们买火车票的动作一样,动作类不能直接执行那个动作,而是要先检查权限,然后才能执行;先检查权限,后执行的那各类其实就是一个代理类,修改后的代码如下:

public interface Action
{
    public void doAction();
}

 


  首先是设计一个接口,用来满足依赖颠倒原则。

Public class ViewAction implements Action {
    public void doAction() {
        //做View的动作
        System.out.println(“You could view the information……”);
        ……
    }
}

 


  这个类跟火车站一样,是动作的真实执行者。

Public class ProxyViewAction implements Action
{
    private Action action = new ViewAction();
    public void doAction()
    {
          //调用权限类的方法取得用户权限
          if(Permission.getPermission(userId).equals(Constants.VIEW))
          {
              action.doAction();
          }
    }
}

 

  这是代理类,很容易理解。在我们的ProxyViewAction类中,除了做了客户真正想要做的动作:doAction()以外,还进行了额外的动作检查用户的权限。而方法doAction()在类ViewAction中进行,这个类只做核心动作,对处理其他需求,满足了单一职责原则。


  客户端通过调用代理类来执行动作,而代理类一是将权限判断和动作的执行分离开来,满足了单一职责原则;二是实现了一个接口,从而满足了依赖颠倒原则。比第一个思路好了很多。
 
  我们再来看代理类ProxyViewAction,可以看到它不仅依赖于接口Action,而且依赖于具体的实现ViewAction。这样对我们的系统扩展很不利,比如我们有Add动作、Delete动作、Modify动作等等,我们需要对每一个动作都写一个代理类。显而易见,有两点不合理的地方:

  1.需要写太多的类;

  2.这些代理类都做同样的事情,先进行权限判断,然后再委派。

 

  所以我们需要对这些代理再进行一次抽象,让代理类只依赖接口Action,而不依赖于具体的实现实现这样的想法,我们需要将代理类中的具体实现提走让代理的使用者在运行期提供具体的实现类,即所谓的依赖注入,如下:

Public class ProxyAction implements Action
{
    private Action action;
    public ProxyAction(Action action) {
          this.action = action;
    }

    public void doAction(){
      //调用权限类的方法取得用户权限            
      if(Permission.getPermission(userId).equals(action.getClass().getName()))  {
          action.doAction();
      }
    }
}

 

    这样,我们就将所有实现了Action接口的实现使用一个代理类来代理它们。除了ViewAction类能用,以后扩展的AddAction、ModifyAction、DeleteAction类等等,都可以使用一个代理类:ProxyAction

  而我们的客户端类似如下:

Action action = ProxyAction(new ViewAction);
Action.doAction();


  通过对代理类的依赖注入,我们使得代理类初步有了一定扩展性。但是我们还要看到,这个代理类依赖于某一个确定的接口。这仍然不能满足我们的实际要求,如我们的系统的权限控制一般是整个系统级的,这样系统级的权限控制,我们很难在整个系统里抽象出一个统一的接口,可能会有多个接口,按照上面的代理模式,我们需要对每一个接口写一个代理类,同样,这些类的功能都是一样的。这显然不是一个好地解决办法。

  基于上面的原因,我们需要解决一个系统在没有统一的接口的情况下,对一些零散的对象的某一些动作使用代理模式的问题。JAVA API为我们引入了动态代理或动态委派的技术。动态代理的核心是InvocationHandler接口,要使用动态代理就必须实现该接口。这个接口的委派任务是在invoke(Object proxy, Method m, Object[] args)方法里面实现的:

//调用核心功能之前作一些动作

...
//调用核心功能
m.invoke(obj, args);
//调用核心功能以后做一些动作

...

  我们可以看到动态代理其实用的是反射机制来调用核心功能的:m.invoke(obj, args);正是反射机制的使用使得我们调用核心功能更加灵活,而不用依赖于某一个具体的接口,而是依赖于Object对象

  下面我们来具体看看动态代理或动态委派如何使用:

public class ProxyAction implements InvocationHandler {
private Object action;

public ProxyAction(Object action) {
    this.action = action;
}

public static Object getInstance(Object action){
    return Proxy.newProxyInstance(action.getClass().getClassLoader(),  action.getClass().getInterfaces(),  new ProxyAction(action));
}

public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {    
    Object result;

    try {
      //在委派之前作动作,如权限判断等
      System.out.println("before method " + m.getName());
      //进行委派
      result = m.invoke(action, args);
    } catch (InvocationTargetException e) {
      throw e.getTargetException();
    } catch (Exception e) {
      throw new RuntimeException("unexpected invocation exception: "+ e.getMessage());
    } finally {
      //在委派之后做动作
      System.out.println("after method " + m.getName());
    }

    return result;
 }
}


  这个代理类,首先是实现了InvocationHandler接口;然后在getInstance()方法里得到了代理类的实例;在invoke()方法里实现代理功能,也很简单。 下面我们来看客户端:

Action action = (Action)ProxyAction.getInstance(new ViewAction());
Action.doAction();

 

  我们可以看到代理类对接口的依赖也转移到了客户端上,这样,代理类不依赖于某个接口。对于同样的代理类ProxyAction,我们也可以有如下的客户端调用:

Engine engine = (Engine)ProxyAction.getInstance(new EngineImpl());
Engine.execute();

 
  只要engineImpl类实现了Engine接口,就可以像上面那样使用。


  现在我们可以看到,动态代理的确是拥有相当的灵活性。但我们同时也看到了,这个代理类写起来比较麻烦,而且也差不多每次都写这样千篇一律的东西,只有委派前的动作和委派后的动作在不同的代理里有着不同,其他的东西都需要照写。如果这样的代理类写多了,也会有一些冗余代理。需要我们进一步优化,这里我们使用模板方法模式来对这个代理类进行优化,如下:

public abstract class BaseProxy implements InvocationHandler {
private Object obj;
protected BaseProxy(Object obj){
    this.obj = obj;
}
public static Object getInstance(Object obj,InvocationHandler instance){
    return Proxy.newProxyInstance(obj.getClass().getClassLoader(),
      obj.getClass().getInterfaces(),instance);
}

public Object invoke(Object proxy, Method m, Object[] args)
          throws Throwable {
    Object result;
    try {
      System.out.println("before method " + m.getName());
      this.doBegin();
      result = m.invoke(obj, args);

    } catch (InvocationTargetException e) {
      throw e.getTargetException();

    } catch (Exception e) {
      throw new RuntimeException("unexpected invocation exception: "+ e.getMessage());

    } finally {
      System.out.println("after method " + m.getName());
      this.doAfter();
    }
    return result;
}
    public abstract void doBegin();
    public abstract void doAfter();
}

 

  这样,代理的实现类只需要关注实现委派前的动作和委派后的动作就行,如下:

public class ProxyImpl extends BaseProxy {
protected ProxyImpl(Object o) {
    super(o);
}
public static Object getInstance(Object foo) {
    return getInstance(foo,new ProxyImpl(foo));
}

//委派前的动作
public void doBegin() {
    System.out.println("begin doing....haha");
}

//委派后的动作
public void doAfter() {
    System.out.println("after doing.....yeah");
}

}


  从上面的代码,我们可以看出代理实现类的确是简单多了,只关注了委派前和委派后的动作,这是我们作为一个代理真正需要关心的。


  至此,代理模式和动态代理已经告一段落。我们将动态代理引申一点说开去,来作为这篇文章的蛇足。


  这个话题就是面向方面的编程,或者说AOP。我们看上面的ProxyImpl类,它的两个方法doBegin()和doAfter(),这是做核心动作之前和之后的两个截取段。正是这两个截取段,是Java AOP的基础

 

  在OOP里,doBegin(),核心动作,doAfter()这三个动作在多个类里始终在一起,但他们所要完成的逻辑却是不同的,如doBegin()可能做的是权限,在所有的类里它都做权限;而在每个类里核心动作却各不相同;doAfter()可能做的是日志,在所有的类里它都做日志。正是因为在所有的类里,doBegin()或doAfter()都做的是同样的逻辑,因此我们需要将它们提取出来,单独分析、设计和编码,这就是我们的AOP的思想。

 

分享到:
评论

相关推荐

    AOP从静态代理到动态代理(Emit实现)详解

    AOP实现的关键在于AOP框架自动创建的AOP代理,AOP代理主要分为静态代理和动态代理,静态代理的代表为AspectJ;而动态代理则以Spring AOP为代表。 何为切面? 一个和业务没有任何耦合相关的代码段,诸如:调用日志,...

    面向对象23种设计模式之代理模式

    1. **静态代理**:在编译时就确定代理类和真实对象的关系,代理类和真实对象是明确的类关系。静态代理的灵活性较差,因为每次新增或修改接口都需要修改对应的代理类。 2. **动态代理**:在运行时动态创建代理类,...

    java jdk 动态代理 演示demo

    在静态代理中,我们通过编写额外的代理类来实现这一目标,但在动态代理中,代理类是在运行时生成的,无需预先编写代码。 Java JDK提供了`java.lang.reflect.Proxy`类和`java.lang.reflect.InvocationHandler`接口,...

    java代理模式

    在Java中,代理模式有静态代理和动态代理两种实现方式。 首先,我们来详细了解一下静态代理。在静态代理中,我们需要创建一个代理类,这个代理类与目标类实现相同的接口,代理类在调用目标类方法的同时,可以添加...

    proxy-demo.zip

    2. **动态代理**:为了解决静态代理的灵活性问题,Java提供了动态代理机制,主要通过`java.lang.reflect.Proxy`类和`java.lang.reflect.InvocationHandler`接口实现。在运行时,我们可以动态地创建一个实现了指定...

    AOP技术及其在J2EE中动态代理实现

    AOP的实现机制主要有两类,一种是静态代理,另一种是动态代理。静态代理通常需要编译器的支持,在编译时期就生成代理类代码;动态代理则是在运行时,通过特定的AOP框架动态生成代理对象。 在J2EE(Java 2 Platform,...

    Android设计模式之代理模式(Proxy Pattern)

    在软件工程中,设计模式是一种解决常见问题的模板或最佳实践,它被广泛应用于各种编程语言,包括Android开发。代理模式是设计模式的...在实际项目中,可以根据具体需求选择静态代理或动态代理,以达到最佳的设计效果。

    Java动态代理(JDK和cglib)

    根据代理对象创建的时间不同,代理又可分为静态代理和动态代理两种类型。 #### 二、静态代理 静态代理是指在程序运行前,代理类的`.class`文件就已经存在的情况。具体来说,静态代理需要程序员事先手动编写代理类...

    Spring3注解

    AOP(Aspect Orient Programming),也就是面向方面编程,作为面向对象编程的一种补充,专门用于处理系统中分布于各个模块(不同方法)中的交叉关注点的问题,在 Java EE 应用中,常常通过 AOP 来处理一些具有横切...

    简单动态代理模式

    静态代理是我们在编译时期就知道代理类和真实对象之间的关系,而动态代理则是在运行时根据需求动态生成代理类。这种灵活性使得动态代理在处理事件监听、事务管理、性能监控等方面尤为有用。 动态代理的核心在于Java...

    面向方面编程_AOP_介绍.pdf

    ### 面向方面编程(AOP)介绍 #### 一、面向方面编程产生的背景 在软件开发领域,面向对象编程(OOP)是长期以来的主要编程范式之一。然而,在实际的软件开发过程中,经常会遇到一些跨越多个类或模块的关注点,如...

    浅谈java代理机制 .txt

    Java代理机制主要分为两大类:静态代理和动态代理。 ##### 1. 静态代理 静态代理是指由程序员创建或特定工具自动生成源代码,再通过编译器编译成.class文件的代理类。静态代理的核心优点是简单易懂,缺点在于代理...

    基于JAVA的动态代理实现的AOP的研究.pdf

    总之,面向方面编程是软件工程中的一种重要补充,通过动态代理在Java中实现AOP,可以有效地组织和管理横切关注点,提高代码的可读性和可维护性。随着技术的不断进步,AOP将会在更多的场景下得到应用,帮助开发者更...

    代理模式

    代理模式的实现通常有静态代理和动态代理两种方式: 1. 静态代理:在代码编写阶段就已经明确知道代理对象和真实对象的关系,代理类和真实类通常是兄弟关系,代理类直接引用真实类,实现相同接口或继承同一父类。 2. ...

    Java中的动态代理模式.doc

    - **权限控制**:在访问控制方面,动态代理可以用来添加权限验证逻辑。 通过上述分析可以看出,动态代理模式不仅可以简化代码的设计,提高系统的可扩展性和灵活性,还能有效地支持多种高级功能的实现。

    java语言反射与动态代理学习笔记

    动态代理通常用于AOP(面向切面编程)、事务管理、日志记录等场景。 **4.1 JDK动态代理**:基于`java.lang.reflect.Proxy`类和`java.lang.reflect.InvocationHandler`接口实现。`Proxy.newProxyInstance()`方法用于...

    webservice原理

    WebSocket 服务是一种基于 SOAP(简单对象访问协议)标准的网络通信技术,它允许不同系统间的应用程序通过互联网...无论是动态代理还是静态代理,它们都在提高代码复用、简化系统架构和增强功能方面发挥着重要作用。

    设计模式之代理模式

    这些资源可能涵盖了静态代理和动态代理的实现,以及在实际项目中的具体应用。通过学习这些源码和工具,开发者可以更好地理解和掌握代理模式的使用。 总之,代理模式是一种强大的设计模式,它提供了一种灵活的方式来...

Global site tag (gtag.js) - Google Analytics