您的位置:首页 > 编程语言 > Java开发

JAVA设计模式之代理模式

2017-11-28 18:51 134 查看

1. 什么是代理模式?

《Head First设计模式》定义:为另一个对象提供一个“替身”或占位符以访问这个对象。

代理模式在结构上类似于装饰者模式,但是两者目的不同。代理模式是提供一个对象用来访问另一个对象,装饰者模式是创建一些对象来装饰一个对象。代理模式控制访问,装饰者模扩展功能。

代理模式除了控制访问以外,另一个重要应用就是:在不改变目标方法代码的基础上,增加额外的功能,为目标对象扩展功能。

代理模式分为静态代理动态代理。静态代理需要程序员在编码期间手动编写代理类,而且代理类和目标对象必须实现同一个接口。动态代理则可以在运行期间动态的生成代理类,程序员不用手动编写代理类。

动态代理有多种实现方式,目标对象拥有共同的接口时可以使用JDK实现动态代理。当目标对象只是单独的一个类时可以使用Cglib、Javassist 或者 ASM 库实现动态代理。JDK实现动态代理比较简单,他是内置在JDK中的不需要单独导jar包。Cglib、Javassist 或者 ASM 库方式功能强大,需要单独导jar包。ASM 是低级的字节码生成工具,使用 ASM 已经近乎于在使用 Java bytecode 编程,对开发人员要求最高,使用很繁琐,维护成本高。Cglib集成在spring中,因此推荐使用Cglib。

2. 角色

 


图片来源于《Head First设计模式》
公共接口(Subject):代理类和目标对象类都实现了此接口(动态代理的Cglib实现方式没有此角色)。
目标对象(RealSubject):目标对象类,其中包含真正的业务代码。
代理类(Proxy):代理类持有Subject的引用,可以通过自身调用目标对象的方法。动态代理中代理类不用实现Subject类(Cglib方式中都没有Subject角色),而且动态代理的代理对象是动态生成的,类文件中只有一个创建代理类的工厂方法。

注意:静态代理和动态代理中的角色有稍微的差别,注意区分。

3. 静态代理

3.1 要点

(1)静态代理在使用时需要定义一个公共的接口或者父类。目标对象和代理对象都必须实现这个接口或继承这个父类,代理对象通过调用相同的方法来调用目标对象方法。

(2)静态代理都需要再编码期间手动编写代理类。

3.2 优点

(1)控制目标对象的访问

(2)可以在不用修改目标对象的基础上扩展目标对象的功能

3.3 缺点

(1)一个目标对象可能存在很多代理对象,类文件数量增加。

(2)由于实现了同一个接口或父类,一旦接口修改方法,必须要同时维护目标类和代理类。如何解决这些缺点,可以使用动态代理。

3.4 示例代码

(1)公共接口

/**
* 公共接口,提供统一的方法
*/
public interface Subject {
public void getTime();
}

(2)目标对象类

/**
* 实际的目标类
*/
public class RealSubject implements Subject {

@Override
public void getTime() {
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("系统当前时间:"+sdf.format(new Date()));
}
}

(3)代理类


public class Proxy implements Subject {

private Subject subject;
public Proxy(Subject subject) {
this.subject=subject;
}
@Override
public void getTime() {
if(subject!=null){
System.out.println("代理调用服务前");
subject.getTime();
System.out.println("代理执行服务后");
}
}
}

(4)测试


public class Client {

public static void main(String[] args) {
Subject subject=new RealSubject();
subject.getTime();

System.out.println("--------");

Subject subjectProxy=new Proxy(subject);
subjectProxy.getTime();
}
}

(5)测试结果

系统当前时间:2017-11-28 17:51:35
--------
代理调用服务前
系统当前时间:2017-11-28 17:51:35
代理执行服务后


 

4.动态代理

4.1 JDK动态代理

4.1.1 要点

 (1)JDK方式实现动态代理,目标对象也必须要有一个共同的接口或父类,代理对象可以不实现这个接口。但是代理对象需要指定这个共同的接口(这主要体现在生成代理对象的时候需要传入接口对象)。

(2)代理对象是利用JDK内置api在内存中动态生成的。

(3)此代理方式也可以称为:JDK代理接口代理。主要通过java.lang.reflect包中的Proxy的

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) throws IllegalArgumentException
方法创建代理对象。

 参数含义:

ClassLoader loader,:指定当前目标对象使用类加载器,获取加载器的方法是固定的

Class<?>[] interfaces,:目标对象实现的接口的类型,使用泛型方式确认类型

InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入

 

InvocationHandler方法中需要自己实现一个回调函数,其中可以进行自己的功能扩展。

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


4.1.2 优点

(1)相对于静态代理,可以减少类的数量,而且代理类是动态生成的,目标对象有变化,可以不用维护代理类。

 

4.1.3 缺点

(1)代理类不需要实现接口,但是目标对象必须实现接口,否则不能使用JDK代理。

 

4.1.4 示例代码

(1)公共接口

/**
* 公共接口,提供统一的方法
*/
public interface Subject {
public String getLowCase(String Str);
}

(2)目标对象

/**
* 实际的目标类
*/
public class RealSubject implements Subject {

@Override
public String getLowCase(String Str) {
String str2=null;
if(Str!=null && !"".equals(Str)){
str2=Str.toLowerCase();
System.out.println("当前字符串:"+Str+">>>转换后:"+str2);
}
return str2;
}
}

(3)代理工厂类

/**
* 创建目标代理对象工厂类 动态代理不需要代理类实现接口,但需要指定接口类型
*/
public class ProxyFactory {
// 指定并维护目标接口类型
private Object obj;

public ProxyFactory(Object obj) {
this.obj = obj;
}

// 生成一个代理对象
public Object getProxyInstance() {
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("方法调用前操作");
// 目标方法返回值
Object returnValue = null;
// 执行目标方法
returnValue = method.invoke(obj, args);
System.out.println("方法执行后操作");
return returnValue;
}
});
}
}

(4)测试

public class Client {

public static void main(String[] args) {
Subject sb1=new RealSubject();
//创建工厂对象
ProxyFactory proxy=new ProxyFactory(sb1);
//创建代理对象
Subject sb=(Subject) proxy.getProxyInstance();
System.out.println("方法返回值:"+sb.getLowCase("ABCdEfG"));
}
}


(5)测试结果

方法调用前操作
当前字符串:ABCdEfG>>>转换后:abcdefg
方法执行后操作
方法返回值:abcdefg


4.2 Cglib动态代理

4.2.1 要点

(1)当目标对象是单独的一个类,没有公共的接口的时候,就不能使用JDK代理实现动态代理,此时可以是使用Cglib方式实现动态代理,而且功能更强大。

(2)Cglib代理也叫子类代理。他是在内存中构建一个目标对象的子类来访问目标对象从而实现动态代理的。

(3)Cglib底层也是基于ASM来转换字节码并生成新的代理类。

(4)需要导入cglib.jar、asm.jar或者spring-core.jar

 

注意:

(1)代理的目标类不能是final,否则会报错。

(2)目标对象的方法如果是final或static类型,则不会执行目标对象额外的方法(比如代理类中扩展的方法)

 

4.2.2 优点

(1)相对于JDK代理,Cglib代理可以在目标对象是一个单独的类时使用

 
4.2.3 缺点

推介使用方式

 

4.2.4 示例代码

(1)目标方法

/**
* 目标方法,没有实现任何接口或父类
*/
public final class TargetClass {

public void getSystemDate(){
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
System.out.println("当前时间:"+sdf.format(new Date()));
}
}


(2)代理工厂类

/**
* 代理对象工厂类
* 采用cglib方式,不需要代理类实现某个接口,也不需要目标对象实现某个接口或继承某个父类
*/
public class ProxyFactory implements MethodInterceptor{
//维护目标对象
private Object target;

public ProxyFactory(Object target) {
this.target=target;
}

public Object getProxyInstance(){
//1.创建工具对象
Enhancer en=new Enhancer();
//2.设置父类
en.setSuperclass(target.getClass());
//3.设置回调函数,this表示此对象的回调函数intercept
en.setCallback(this);
//4.创建代理对象
return en.create();
}

/**
* 回调函数
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//目标方法返回值
Object returnValue=null;
System.out.println("方法调用前拦截");
//执行目标方法
returnValue=method.invoke(target, args);
System.out.println("方法执行后拦截");
return returnValue;
}
}


 

(3)测试



public class Client {

public static void main(String[] args) {
ProxyFactory proxy=new ProxyFactory(new TargetClass());
TargetClass targetClass=(TargetClass) proxy.getProxyInstance();
targetClass.getSystemDate();
}
}


(4)测试结果

方法调用前拦截
当前时间:2017-11-28 06:26:38
方法执行后拦截


5. 代理模式使用场景

5.1远程代理

也就是为一个对象在不同的地址空间提供局部代表,这样可以隐藏一个对象存在于不同地址空间的事实。比如说 WebService,当我们在应用程序的项目中加入一个Web 引用,引用一个WebService,此时会在项目中声称一个WebReference 的文件夹和一些文件,这个就是起代理作用的,这样可以让那个客户端程序调用代理解决远程访问的问题;

5.2虚拟代理

是根据需要创建开销很大的对象,通过它来存放实例化需要很长时间的真实对象。这样就可以达到性能的最优化,比如打开一个网页,这个网页里面包含了大量的文字和图片,但我们可以很快看到文字,但是图片却是一张一张地下载后才能看到,那些未打开的图片框,就是通过虚拟代里来替换了真实的图片,此时代理存储了真实图片的路径和尺寸;

5.3安全代理

用来控制真实对象访问时的权限。一般用于对象应该有不同的访问权限的时候;

5.4指针引用

是指当调用真实的对象时,代理处理另外一些事。比如计算真实对象的引用次数,这样当该对象没有引用时,可以自动释放它,或当第一次引用一个持久对象时,将它装入内存,或是在访问一个实际对象前,检查是否已经释放它,以确保其他对象不能改变它。这些都是通过代理在访问一个对象时附加一些内务处理;

5.5延迟加载

用代理模式实现延迟加载的一个经典应用就在 Hibernate 框架里面。当 Hibernate 加载实体bean 时,并不会一次性将数据库所有的数据都装载。默认情况下,它会采取延迟加载的机制,以提高系统的性能。Hibernate中的延迟加载主要分为属性的延迟加载和关联表的延时加载两类。实现原理是使用代理拦截原有的 getter 方法,在真正使用对象数据时才去数据库或者其他第三方组件加载实际的数据,从而提升系统性能。

 参考资料

https://www.ibm.com/developerworks/cn/java/j-lo-proxy-pattern/index.html

https://www.cnblogs.com/cenyu/p/6289209.html

 【四川乐山程序员联盟,欢迎大家加群相互交流学习5 7 1 8 1 4 7 4 3】
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: