第一次写博客只是想总结一下最近两天学的Java设计模式中的代理模式

来源:互联网 发布:企业网络布线图 编辑:程序博客网 时间:2024/04/28 13:20

一、Java中的设计模式

1、设计模式是什么?

      经查阅相关资料:设计模式是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。

2、使用设计模式的目的?

      为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。

二、Java设计模式之代理模式

1、代理模式是什么?

    a、现实生活中什么是代理?

             代理有很多,我们听得最多的就是校园代理(比如文具店),营业厅代理(比如联通营业厅)

       b、Java中的代理模式是什么?

             为其他对象提供一种代理以控制对这个对象的访问,在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用,以达到保护目标对象的作用。

2、代理模式的分类

     a、静态代理:由程序员创建生成代理类的源码,再编译代理类,所谓静态也就是在程序运行前就已经存在代理类的字节码文件。

       b、动态代理:在实现阶段不用关心代理类,而是在运行阶段才指定哪一个代理对象。

3、官方给出的代理模式中的三种角色

      a、抽象角色:通过接口或者抽象类定义真实角色的业务方法

      b、真实角色(目标角色/被代理角色/委托类):实现抽象角色,实现具体的业务逻辑,供代理角色调用

      c、代理角色:是真实角色的代理,通过调用真实角色的业务方法,并附加自己的操作。

4、代理模式的好处

   a、职责清晰:真实角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理角色完成一件 事务,附带的结果就是编程简洁清晰。

     b、代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了中介的作用和保护了目标对象的作用。

     c、高扩展性:具体主题角色是随时都会发生变化的,只要它实现了接口,甭管它如何变化,都逃不脱如来佛的手掌(接口),那我们的代理类完全就可以在不做任何修改的情况下使用。

三、代理模式中的静态代理

      为每一个被代理的类创建一个代理类,通过访问代理对象间接地访问真实角色

      首先让我们来看一个例子(由于静态代理比较简单,所以代码只是简单模拟一下):

      1、现在我们有一个接口IA(抽象角色:接口/抽象类),该接口有有一个fun()方法;还有一个实现了IA接口的类B(真实角色/目标角色/被代理角色/委托类),并且重写了fun()方法;有一个客户端Client;如果说在客户端调用类B中的fun()方法,你会怎么做?你可能会想在客户端直接实例化一个B类的对象,调用B类中的fun()方法,是的这样做没错。但是如果说突然有一天,类B的fun()方法需要升级,你会怎么办?你可能会想直接打开类B中的fun()方法,对它里面进行修改。这么做是不友好的,因为很有可能别人也在引用类B中的这个fun()方法,你把它修改了,别人在引用他的时候怎么办?这个时候静态模式就可以解决这个问题了。我们的做法是再写一个实现了接口IA的类BProxy(代理角色),在类BProxy里面我们也重写了fun()方法,在这个fun()方法里面我们实例化一个B对象,调用B类里面的fun()方法,这个时候你就可以在BProxy这个类里面的fun()方法中对类B中的fun()升级了。这个场景的类图如下所示:


      这个场景的代码下所示:




      以上的模式就是静态代理,但是对于静态代理来说它是有缺点的。为什么?你想象一下,假如说我们还有一个类D,类D里面也有一些方法,这些个方法也需要被代理,这时候可能你还需要为这个类D在创建一个属于它的代理类。因此静态代理是不太灵活的,所以JDK为我们提供了一种动态代理模式,可以在程序运行期间动态代理某个对象,不需要在修改底层代码。


四、代理模式中的动态代理

       1、动态代理在底层依赖于Java中的反射,首先让我们来看一下我认为在实现动态代理模式中使用反射中的比较重要一个方法(invoke方法)。假如说现在有两个类(类A和类B),在类A中有一个fun()方法,在类B中利用反射怎么执行类A中的fun()方法呢?我们可以这么做。


        输出结果:


     从输出结果我们就可以看到,Method对象的invoke方法相当于执行了通过getDeclaredMethod()获得的方法,并且invoke()方法的返回值就是获得的那个方法的返回值。这其中涉及到的两个重要的方法如下:

     a、public Method getDeclaredMethod(String name, Class<?>... parameterTypes)方法:它反映了此class对象所表示的类或接口中的指定义声明方法,参数name表示要获得的方法的名称,参数 parameterTypes表示class对象的一个数组,它按声明顺序标识该方法的形参类型。如果在某个类中声明了带有相同参数类型的多个方法,并且其中有一个方法的返回类型比其他方法的返回类型都特殊,则返回该方法;否则将从中任选一个方法。

     b、public Object invoke(Object obj, Object... args)方法:它表示执行对应类里面的对应方法。参数obj表示从中调用底层方法的对象,简单点来说就是调用谁的方法用谁的对象,这个参数也可以为null,但前提是对应的方法必须是静态的。参数args表示对应方法的参数,如果对应方法不需要参数,则args可以直接为null,如果对应方法的参数是一个数组类型,则args也必须是包含在new Object[]{}中,否则会报IllegalArgumentException异常。例如:public String[]  Show(String [] s)---m.incoke(null,new Object[]{s})。如果方法正常完成,则将该方法返回的值返回给调用者;如果该值为基本类型,则首先适当地将其包装在对象中。但是,如果该值的类型为一组基本类型,则数组元素不会被包装在对象中;也就是说,将返回基本类型的数组。如果底层方法返回类型为 void,则该调用返回 null。接下来,我们来看一下动态代理模式是怎么实现的。

2、动态代理

a、动态代理是什么?

动态代理就是在实现阶段不用关心代理谁,而是在运行阶段才指定代理哪一个对象。相对来说,自己写代理类的

方式就是静态代理。在Java的动态代理机制中,有两个重要的类或者接口,一个InvocationHandler(接口),另一个是

Proxy(类)。但是有些类并没有实现接口,这就要使用cglib动态代理了。

b、模拟动态代理的场景:

现在有一个账户(IAccount)的接口(抽象角色:接口/抽象类),该接口定义了三个方法:分别是查看余额,取钱

,存款操作。


还有一个实现了该接口的AccountImpl类,并且重写了接口中的三个方法。

         还有一个客户端,分别调用AccountImpl类中的三个方法。

         如果说突然有一天,AccountImpl类中的三个方法都需要加入日志信息,你会怎么做?打开AccountImpl类,分别修改它里面的三个方法吗?这样做的工作量就会变得很大,所以应该摒弃这种做法。

        这个时候我们就会用到动态代理模式,我们来看一下此时这个场景类图。




在类图中增加了一个InvocationHandler(接口)和一个实现了该接口的DynamicAccount类,作用就是产生一动态代理对

象(实际上是类$Proxy的对象)。其中InvocationHandler(接口)是JDK提供的动态代理接口,对被代理类的方法进行

代理。我们来看一下JDK提供的代理的程序:


运行结果:


从运行结果我们可以看出,程序实现了为我们方法加入日志信息,而不是打开AccountImpl类对每一个方法进行修改,我们并没有写代理类,这些过程都是通过DynamicAccount类为我们生成的动态代理$Proxy类来实现的,接下来我们来分析一下具体怎么实现:

在运行阶段,程序会帮我们生成一个动态代理类(即就是$Proxy类,这个类也实现了IAccount接口,并且继承了DynamicAccount类,这个$Proxy类我们是看不见的)。动态代理是根据被代理的接口生成所有的方法,也就是说给定一个接口,动态代理回宣称“我已经实现该接口中所有方法了”,那动态代理怎么实现被代理接口中的方法呢?

在类DynamicAccount中的invoke()方法是接口InvocationHandle定义必须实现的,它完成对真实方法的调用,也就是说invoke()方法的作用是通过自动生成的$proxy对象执行目标角色对象的目标方法。invoke()方法的参数:参数Object proxy表示我们所代理的那个真实对象,参数Method method表示我们所要调用真实对象的某个方法的Method对象,参数Object[] args表示调用真实对象某个方法时接收的参数。

在DynameicAccount类中还有一个public Object getProxy(Object obj)方法,参数obj表示目标角色对象,这个方法中有一句代码:

Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);这句代码的意思是产生目标对象的动态代理对象,参数ClassLoader loader表示由哪个class对象来对生成的代理对象进行加载;Class<?> [] interfaces:一个interfaces数组,表示我将要给我代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口,这样这个代理对象就能调用这个接口中的方法了;InvocationHandler h 参数    表示一个InvocationHandler对象,代表的是当我这个动态代理对象在调用方法的时候,会关联到哪一个Invocation

Handler对象上。

在客户端中,我们先生成了实例化了目标角色AccountImpl,然后通过调用DynamicAccount类中的getProxy()方法

获得一个目标角色的代理对象,getProxy()方法参数传入我们希望得到的目标角色的实例化对象,该方法返回目标

角色的代理对象给引用ac,因为产生的代理类也实现了IAccount接口。最后在使用代理对象调用真实角色的方法的时

候,实际上是通过生成的代理类($Proxy)的对象调用了DynamicAccount中的invoke()方法,因为代理类

$Proxy)也继承了DynamicAccount。以上就是实现动态代理的整个过程,现在我们来总结一下。

c:动态代理的总结:

1:其实在DynamicAccount的invoke()方法中,我们看到了有一句调用method对象的

invoke()的代码,在接口InvocationHandler中会给我们生成反射中获取方法对象的代码(getDeclaredMethod),

以至于我们在这儿才能直接调用Method的invoke()方法。:

2:生成代理类的时候,根据传的Invocationhandler参数会在代理类的实现接口的方法里面实现InvocationHandler的invoke()方法,

也就是调用InvocationHandler实现类的invoke方法,然后在InvocationHandler实现类的invoke方法里加入业务逻辑,
而且中间在再通过invoke方法调用被代理类的方法,将来在执行目标方法时,在代理类中有接口中的方法,在代理类的实现接口的方法中调用DynamicAccount的invoke方法

3:在系统运行期间,通过Proxy的静态newProxyIntstance()方法获取目标对象的动态代理对象

$Proxy0(类名) implements IAccount,将来调用目标接口方法时,其实是通过$Proxy0调用了invoke方法

$Proxy0继承了Proxy

方法newProxyInstance()的作用:
1:根据参数loader和interfaces调用方法 getProxyClass(loader, interfaces)创建代理类$Proxy0.
$Proxy0类 实现了interfaces的接口,并继承了Proxy类. 返回代理类的Class类。
2:实例化$Proxy0并在构造方法中把DynamicAccount传过去,接着$Proxy0调用父类Proxy的构造器,为h赋值,如下: 
class Proxy{  
    InvocationHandler h=null;  
    protected Proxy(InvocationHandler h) {  
        this.h = h;  
    }  
    ...  
}  *
当执行ac.look()方法时,就调用了$Proxy0类中的look()方法,
进而调用父类Proxy中的h的invoke()方法.即InvocationHandler.invoke()。



原创粉丝点击