设计模式之代理模式

一、代理模式

1.代理模式简介:

代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。使用代理模式创建代理对象,让代理对象控制目标对象的访问(目标对象可以是远程的对象、创建开销大的对象或需要安全控制的对象),并且可以在不改变目标对象的情况下添加一些额外的功能。

所谓代理,就是一个人或者一个机构代表另一个人或者另一个机构采取行动。在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之前起到中介的作用。
代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。

2.代理模式涉及到的角色:

(1)抽象角色:声明真实对象和代理对象的共同接口;可以是抽象类,也可以是接口,是一个最普通的业务类型定义,无特殊要求。

(2)代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。

  也叫委托类、代理类。它把所有抽象角色定义的方法给真实角色实现,并且在真实角色处理完毕前后做预处理和善后工作。(最简单的比如打印日志)(自己并未实现业务逻辑接口,而是调用真实角色来实现。)

(3)真实角色:代理角色所代表的真实对象,是我们最终要引用的对象。也叫被委托角色、被代理角色。是业务逻辑的具体执行者。(真正实现了业务逻辑接口。)

示意图:

代理模式是常用的java设计模式,如上图所示,代理类和实现类实现了同一个接口,而代理类中则具有现有对象,通过代理类去操作现有对象,那么我们就可以在代理类中增加很多操作,包括前置增强、后置增强、环绕增强、异常增强等,这也就能解决我们实际当中遇到的一些问题,例如延迟对象的创建、控制对象的访问等。

代理模式的特征是代理类与委托类有同样的接口。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。
代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。

3.分类

按照代理的创建时期,代理类可以分为两种。
静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
动态代理:在程序运行时,运用反射机制动态创建而成。

二、静态代理

     静态代理是由程序员创建或特定工具自动生成源代码,再对其编译。最大的特点就是在程序运行时代理类的.class文件就已经存在了,但是这有一个很大的缺陷即每一个代理类只能为一个接口服务。静态代理的实现如下:

首先定义一个接口(抽象角色),里面有一个print()方法:

package com.proxyPattern.staticProxy;
/**
* 抽象角色
* @author lxx
*
*/
public interface StaticHelloWorld {
//打印方法
void print();
}

 真实角色:

package com.proxyPattern.staticProxy;
/**
* 真实角色
* @author lxx
*
*/
public class StaticHelloWorldImpl implements StaticHelloWorld{

public void print(){
System.out.println("Hello World!");
}
}

代理角色:注意,这里的重点是代理对象和实际对象实现的是同一个接口,因为希望在任何时候让代理对象替代实际对象

package com.proxyPattern.staticProxy;

/**
* 代理角色
* @author lxx
*
*/
public class StaticProxy implements StaticHelloWorld{
//代理类中含有对真实对象的引用
private StaticHelloWorld staticHelloWorld;

public StaticProxy(StaticHelloWorld staticHelloWorldImpl){
this.staticHelloWorld=staticHelloWorldImpl;
}

public void print(){
System.out.println("Before Hello World!");
staticHelloWorld.print();
System.out.println("After Hello World!");
}
}

测试类:写一个类去调用代理对象,在代理对象的构造函数中传入一个实际对象即可

package com.proxyPattern.staticProxy;

public class StaticTestMain {
    public static void main(String[] args){
        StaticHelloWorld shw=new StaticHelloWorldImpl();
        StaticProxy sp=new StaticProxy(shw);
        sp.print();
    }
}

运行结果:

Before Hello World!
Hello World!
After Hello World!

静态代理的缺点
静态代理的特点是静态代理的代理类是程序员创建的,在程序运行之前静态代理的.class文件已经存在了。
从静态代理来看,看到静态代理模式确实可以有一个代理对象来控制实际对象的引用,并通过代理对象来使用实际对象。这种模式在代理量较小的时候还可以,但是代理量一大起来,就存在着三个比较大的缺点:
(1)、如果想换一种代理内容,比如我在"Hello World"前后不想输入"Before XXX"和"After XXX"了,想输出运行前后系统当前时间,就必须新写一个代理对象。这样很容易造成代理对象的膨胀。
(2)、每一个代理类只能为一个接口服务,并且代理内容无法复用,也就是说"Before XXX"和"After XXX"只可以给某一个类使用,另一个类如果也想使用这个代理内容,必须自己也写一个,同样,造成的后果就是代理类的无限膨胀
(3)、接口里面如果新增了一个方法,实际对象实现了这个方法,代理对象也必须新增内容,去给这个新增方法增加代理内容(假如需要的话)

静态代理类优缺点
优点:业务类只需要关注业务逻辑本身,保证了业务类的重用性。这是代理的共有优点。
缺点:
1)代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
2)如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。

如果要按照上述的方式(静态代理)使用代理模式,那么真实角色必须是实现已经存在的,并将其作为代理对象的内部属性。
但是实际使用时,一个真实角色必须对应一个代理角色,但如果大量使用会导致类的急剧膨胀;此外,如果事先并不知道真实角色,该如何使用代理呢?这个问题可以通过Java的动态代理类来解决。

三、动态代理

动态代理:在程序运行时,运用反射机制动态创建代理类(Proxy)而成。

动态代理类说明
所谓Dynamic Proxy是这样一种class:
它是在运行时生成的class,在生成它时你必须提供一组interface给它,然后该class就宣称它实现了这些interface。
你当然可以把该class的实例当作这些interface中的任何一个来用。
当然,这个Dynamic Proxy其实就是一个Proxy,它不会替你做实质性的工作,在生成它的实例时你必须提供一个handler,由它接管实际的工作
在使用动态代理类时,我们必须实现InvocationHandler接口。每一个动态代理类都会有一个与之关联的invocation handler。
真正的调用是在invocation handler的invoke()方法里完成的。

Java动态代理类位于java.lang.reflect包下,一般主要涉及到以下两个类:
1、Interface InvocationHandler:InvocationHandler 接口为方法调用接口,它声明了负责调用任意一个方法的invoke()方法。(InvocationHandler 是代理实例的调用处理程序实现的接口。invoke()在代理实例上处理方法调用并返回结果。)
该接口中仅定义了一个方法:

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

Object proxy:指动态代理类实例
Method method:要调用的方法
Object[] args:方法调用时所需要参数

2、Proxy(Proxy 提供用于创建动态代理类及其实例的静态方法.)
(1)getProxyClass()静态方法负责创建动态代理类,它的完整定义如下:

public static Class<?> getProxyClass(ClassLoader loader, Class<?>[] interfaces) throws IllegalArgumentException

参数loader 指定动态代理类的类加载器,参数interfaces 指定动态代理类需要实现的所有接口。

(2)newProxyInstance()静态方法负责创建动态代理类的实例,它的完整定义如下:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler) throws IllegalArgumentException

CLassLoader loader:类的加载器 (指定动态代理类的类加载器)
Class<?> interfaces:得到全部的接口 (指定动态代理类需要实现的所有接口)
InvocationHandler h:得到InvocationHandler接口的子类的实例 (指定与动态代理类关联的 InvocationHandler 对象)

假设有个Foo接口,具体定义不写了,以下两种方式都创建了实现Foo接口的动态代理类的实例:

方式一:

/**** 方式一 ****/
//创建InvocationHandler对象
InvocationHandler handler = new MyInvocationHandler(...);
//创建动态代理类
Class proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), new Class[] { Foo.class });
//创建动态代理类的实例
Foo foo = (Foo) proxyClass.getConstructor(new Class[] { InvocationHandler.class }).newInstance(new Object[] { handler });

方式二:

/**** 方式二 ****/
//创建InvocationHandler对象
InvocationHandler handler = new MyInvocationHandler(...);
//直接创建动态代理类的实例
Foo foo = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),new Class[] { Foo.class }, handler);

由Proxy类的静态方法创建的动态代理类的实例具有以下特点:
(1). 假定变量foo 是一个动态代理类的实例,并且这个动态代理类实现了Foo 接口,那么"foo instance of Foo"的值为true。把变量foo强制转换为Foo类型是合法的:(Foo) foo //合法
(2). 每个动态代理类实例都和一个InvocationHandler 实例关联。Proxy 类的getInvocationHandler(Object proxy)静态方法返回与参数proxy指定的代理类实例所关联的InvocationHandler 对象。
(3). 假定Foo接口有一个add()方法,那么当程序调用动态代理类实例foo的add()方法时,该方法会调用与它关联的InvocationHandler 对象的invoke()方法。

来看一个例子:

首先还是定义一个接口(抽象角色):

package com.proxyPattern.dynamicProxy;

public interface DynamicHelloWorld {
    String print();
}

 写一个类去实现它(真实角色):

package com.proxyPattern.dynamicProxy;

public class DynamicHelloWorldImpl implements DynamicHelloWorld{
    
    public String print(){
        System.out.println("Enter DynamicHelloWorldImpl.print()");
        return "DynamicHelloWorldImpl";
    }
}

之后定义一个动态代理类,可以像下面这样,让动态代理类在外面生成,只在构造函数中传入一个target:

package com.proxyPattern.dynamicProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * 该代理类的内部属性是Object类型,实际使用的时候通过该类的构造方法传递进来一个对象。
 * 该类实现了invoke()方法,该方法中的method.invoke()其实就是调用被代理对象的将要执行的方法,
 * 通过动态代理类,我们可以在执行真实对象的方法前后加入自己的一些额外方法
 *
 */
public class DynamicProxy implements InvocationHandler{
    
    //对真实对象的引用
    private Object target;
    
    public DynamicProxy(Object target){
        this.target=target;
    }
    
    public Object invoke(Object proxy,Method method,Object[] args) 
        throws Throwable{
        //目标方法之前执行
        System.out.println("Before DynamicProxy");
        //通过反射机制来调用目标类方法
        method.invoke(target, args);
        //目标方法之后执行
        System.out.println("After DynamicProxy");
        return null;
    }
}

测试类:

package com.proxyPattern.dynamicProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class DynamicTestMain {
    
    public static void main(String[] args){
        DynamicHelloWorld dhwi=new DynamicHelloWorldImpl();
        InvocationHandler ih=new DynamicProxy(dhwi);
        //创建代理实例(使用Proxy类和自定义的调用处理逻辑(handler)来生成一个代理对象)
        DynamicHelloWorld dhw=(DynamicHelloWorld)Proxy.
            newProxyInstance(DynamicHelloWorld.class.getClassLoader(), new Class<?>[]{DynamicHelloWorld.class}, ih);
        //调用方法时,转移给handler接管,由其中的invoke()方法实际完成方法执行
        dhw.print();
    }
}

也可以像下面这样,把newInstance即生成一个动态代理类的过程放到InvocationHandler的实现类中:

package com.proxyPattern.dynamicProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class DynamicProxy1 implements InvocationHandler{
    
    private Object target;
    
    public Object newInstance(Object target){
        this.target = target;
        //创建代理类实例
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), 
                target.getClass().getInterfaces(), this);
    }
    
    public Object invoke(Object proxy,Method method,Object[] args) 
            throws Throwable{
         //目标方法之前执行
        System.out.println("Before DynamicProxy");
        //通过反射机制来调用目标类方法
        method.invoke(target, args);
        //目标方法之后执行
        System.out.println("After DynamicProxy");
        return null;
    }
}

测试类:

package com.proxyPattern.dynamicProxy;

public class DynamicTestMain1 {
    
    public static void main(String[] args){
        DynamicProxy1 dp = new DynamicProxy1();
        DynamicHelloWorld dhwi = new DynamicHelloWorldImpl();
        DynamicHelloWorld dhw = (DynamicHelloWorld)dp.newInstance(dhwi);
        dhw.print();
    }
    
}

不管哪种写法,运行结果都是一样的:
Before DynamicProxy
Enter DynamicHelloWorldImpl.print()
After DynamicProxy

首先创建委托类对象,将其以构造函数传入代理处理器,代理处理器DynamicProxy中会以Java反射方式调用该委托类对应的方法。然后使用Java反射机制中的Proxy.newProxyInstance方式创建一个代理类实例,创建该实例需要指定该实例的类加载器,需要实现的接口(即目标接口),以及处理代理实例接口调用的处理器。

最后,调用代理类目标接口方法时,会自动将其转发到代理处理器中的invoke方法内,invoke方法内部实现预处理,对委托类方法调用,事后处理等逻辑。

总结:
1.创建动态代理步骤:
(1).创建一个实现接口InvocationHandler的类,它必须实现invoke()方法。
(2).创建被代理的类以及接口。
(3).通过Proxy的静态方法newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)创建一个代理。
(4).通过代理调用方法。

2.动态代理,利用动态编译+反射技术,把对实际对象的方法调用转换成对传入的InvocationHandler接口实现类的invoke方法的调用,这是动态代理模式实现的关键点。

动态代理的优点
(1)、最直观的,类少了很多。
(2)、代理内容也就是InvocationHandler接口的实现类可以复用,可以给A接口用、也可以给B接口用,A接口用了InvocationHandler接口实现类A的代理,不想用了,可以方便地换成InvocationHandler接口实现B的代理。
(3)、与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
(4)、最重要的,用了动态代理,就可以在不修改原来代码的基础上,就在原来代码的基础上做操作,这就是AOP即面向切面编程。

动态代理的缺点
动态代理有一个最大的缺点,就是它只能针对接口生成代理,不能只针对某一个类生成代理,比方说我们在调用Proxy的newProxyInstance方法的时候,第二个参数传某个具体类的getClass(),那么会报错:
Exception in thread "main" java.lang.IllegalArgumentException: proxy.DynamicHelloWorldImpl is not an interface
这是因为java.lang.reflect.Proxy的newProxyInstance方法会判断传入的Class是不是一个接口。

而实际使用中,我们为某一个单独的类实现一个代理也很正常,这种情况下,我们就可以考虑使用CGLIB(一种字节码增强技术)来为某一个类实现代理了。

 
 
posted @ 2019-09-25 10:19  ZJfor  阅读(189)  评论(0编辑  收藏  举报