关闭

java动态代理浅析

标签: 代理模式动态代理cglib
577人阅读 评论(0) 收藏 举报
分类:

1、代理

1.1代理模式 

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

代理模式:

    为了保持行为的一致性,代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。


生活中的例子:过年加班比较忙,没空去买火车票,这时可以打个电话到附近的票务中心,叫他们帮你买张回家的火车票,当然这会附加额外的劳务费。但要清楚票务中心自己并不卖票,只有火车站才真正卖票,票务中心卖给你的票其实是通过火车站实现的。
上面这个例子,你就是“客户(client)”,票务中心就是“代理角色(ProxySubject)”,火车站是“真实角色(RealSubject)”,卖票称为“抽象角色(Subject)”!


按照代理的创建时期,代理类可以分为两种。 

静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。 

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

动态代理:可以提供对另一个对象的访问,同时隐藏实际对象的具体事实。代理一般会实现它所表示的实际对象的接口。代理可以访问实际对象,但是延迟实现实际对象的部分功能,实际对象实现系统的实际功能,代理对象对客户隐藏了实际对象。客户不知道它是与代理打交道还是与实际对象打交道。


1.2代理模式的作用  (例子参考来源:为什么要使用代理模式)

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

生活中的例子:假设你有一套房子要卖,一种方法是你直接去网上发布出售信息,然后直接带要买房子的人来看房子、过户等一直到房子卖出去,但是可能你很忙,你没有时间去处理这些事情,所以你可以去找中介,让中介帮你处理这些琐碎事情,中介实际上就是你的代理。本来是你要做的事情,现在中介帮助你一一处理,对于买方来说跟你直接交易跟同中介直接交易没有任何差异,买方甚至可能觉察不到你的存在,这实际上就是代理的一个最大好处。


为什么你不直接买房子而需要中介(为什么需要代理)?

原因一:你可能在外地上班,买房子的人没法找到你直接交易。对应到我们程序设计的时候就是:客户端无法直接操作实际对象

那么为什么无法直接操作?

一种情况是你需要调用的对象在另外一台机器上,你需要跨越网络才能访问,如果让你直接coding去调用,你需要处理网络连接、处理打包、解包等等非常复杂的步骤,所以为了简化客户端的处理,我们使用代理模式,在客户端建立一个远程对象的代理,客户端就象调用本地对象一样调用该代理,再由代理去跟实际对象联系,对于客户端来说可能根本没有感觉到调用的东西在网络另外一端,这实际上就是Web Service的工作原理。

另一种情况虽然你所要调用的对象就在本地,但是由于调用非常耗时,你怕影响你正常的操作,所以特意找个代理来处理这种耗时情况,一个最容易理解的就是Word里面装了很大一张图片,在word被打开的时候我们肯定要加载里面的内容一起打开,但是如果等加载完这个大图片再打开Word用户等得可能早已经跳脚了,所以我们可以为这个图片设置一个代理,让代理慢慢打开这个图片而不影响Word本来的打开的功能。申明一下我只是猜可能Word是这么做的,具体到底怎么做的,俺也不知道。

原因二:你不知道怎么办过户手续,或者说除了你现在会干的事情外,还需要做其他的事情才能达成目的。对应到我们程序设计的时候就是:除了当前类能够提供的功能外,我们还需要补充一些其他功能。

最容易想到的情况就是权限过滤,我有一个类做某项业务,但是由于安全原因只有某些用户才可以调用这个类,此时我们就可以做一个该类的代理类,要求所有请求必须通过该代理类,由该代理类做权限判断,如果安全则调用实际类的业务开始处理。可能有人说为什么我要多加个代理类?我只需要在原来类的方法里面加上权限过滤不就完了吗?在程序设计中有一个类的单一性原则问题,这个原则很简单,就是每个类的功能尽可能单一。为什么要单一,因为只有功能单一这个类被改动的可能性才会最小,就拿刚才的例子来说,如果你将权限判断放在当前类里面,当前这个类就既要负责自己本身业务逻辑、又要负责权限判断,那么就有两个导致该类变化的原因,现在如果权限规则一旦变化,这个类就必需得改,显然这不是一个好的设计。


    代理类一般是做些除原始类核心功能以外的其他功能,比如权限、事务等等都要专门的代理来实现。当我们的代码每个类代表一个主要功能,而不是将所有功能混在一个类中,那么代码无疑清晰有条理的,易于维护,比如我要修改权限,就不必打开原始类代码,直接修改权限代理类就可以了。就很少发生修改一个BUG,带来一个新BUG的烦恼问题。


2、静态代理的实现

抽象角色:通过接口或抽象类声明真实角色实现的业务方法。

package ProxyTest;

public interface Subject {

	// 查看账户方法
	public void queryCount();

	// 修改账户方法
	public void updateCount();

}
真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。

package ProxyTest;

/**
 * 委托类(包含业务逻辑)
 */
public class RealSubject implements Subject {

	@Override
	public void queryCount() {
		System.out.println("查看账户方法...");
	}

	@Override
	public void updateCount() {
		System.out.println("修改账户方法...");
	}

}
代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
package ProxyTest;

/**
 * 这是一个代理类(增强RealSubject实现类)
 */
public class ProxySubject implements Subject {

	private Subject realSubject;

	public ProxySubject() {

	}

	public ProxySubject(Subject realSubject) {
		this.realSubject = realSubject;
	}

	public void queryCount() {
		System.out.println("事务处理之前");
		// 调用委托类的方法;
		realSubject.queryCount();
		System.out.println("事务处理之后");
	}

	@Override
	public void updateCount() {
		System.out.println("事务处理之前");
		// 调用委托类的方法;
		realSubject.updateCount();
		System.out.println("事务处理之后");
	}

}

测试类:

package ProxyTest;

/** 
 *测试C</span><span style="font-family:KaiTi_GB2312;"><span style="font-size: 14px;">lient</span></span><span style="font-size:14px;">类 
 */  
public class Client {
	
	public static void main(String[] args) {
		Subject subject = new RealSubject();
		ProxySubject proxy = new ProxySubject(subject);
		proxy.queryCount();
		proxy.updateCount();
	}

}

运行结果:

事务处理之前
查看账户方法...
事务处理之后


事务处理之前
修改账户方法...
事务处理之后


观察代码可以发现每一个代理类只能为一个接口服务,这样一来程序开发中必然会产生过多的代理,而且,所有的代理操作除了调用的方法不一样之外,其他的操作都一样,则此时肯定是重复代码。解决这一问题最好的做法是可以通过一个代理类完成全部的代理功能,那么此时就必须使用动态代理完成。


3、动态代理的实现

3.1 动态代理主要包含以下角色:
  动态代理类(以下简称为代理类)是一个实现在创建类时在运行时指定的接口列表的类,该类具有下面描述的行为。

 代理接口 是代理类实现的一个接口。
  代理实例 是代理类的一个实例。

目前Java开发包中包含了对动态代理的支持,但是其实现只支持对接口的的实现。 其实现主要通过java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。 Proxy类主要用来获取动态代理对象,InvocationHandler接口用来约束调用者实现。


3.2 使用Java动态代理机制的好处:
1、减少编程的工作量:假如需要实现多种代理处理逻辑,只要写多个代理处理器就可以了,无需每种方式都写一个代理类。
2、系统扩展性和维护性增强,程序修改起来也方便多了(一般只要改代理处理器类就行了)。
 


3.3 使用Java动态代理机制的限制:
    目前根据GOF的代理模式,代理类和委托类需要都实现同一个接口。也就是说只有实现了某个接口的类可以使用Java动态代理机制。动态代理在运行期通过接口动态生成代理类。,这为其带来了一定的灵活性,但这个灵活性却带来了两个问题,

1.代理类必须实现一个接口,如果没实现接口会抛出一个异常但是,事实上使用中并不是遇到的所有类都会给你实现一个接口。因此,对于没有实现接口的类,目前无法使用该机制。有人说这不是废话吗,本来Proxy模式定义的就是委托类要实现接口的啊!但是没有实现接口的类,该如何实现动态代理呢?当然不是没有办法,那就是CGLib.

2.性能影响,因为动态代理使用反射的机制实现的,首先反射肯定比直接调用要慢,其次使用反射大量生成类文件可能引起Full GC造成性能影响,因为字节码文件加载后会存放在JVM运行时区的方法区(或者叫持久代)中,当方法区满的时候,会引起Full GC,所以当你大量使用动态代理时,可以将持久代设置大一些,减少Full GC次数。


3.4 动态代理的实现

JDK动态代理中包含一个类和一个接口: 


InvocationHandler接口: 

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

参数说明: 
Object proxy:指被代理的对象。 
Method method:指代的是我们所要调用真实对象的某个方法的Method对象 
Object[] args:方法调用时所需要的参数 


可以将InvocationHandler接口的子类想象成一个代理的最终操作类,替换掉ProxySubject。 


Proxy(代理) 类: 
Proxy类是专门完成代理的操作类,可以通过此类为一个或多个接口动态地生成实现类,此类提供了如下的操作方法: 
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, 
InvocationHandler h) throws IllegalArgumentException 
参数说明: 
ClassLoader loader:类加载器 
Class<?>[] interfaces:得到全部的接口 
InvocationHandler h:得到InvocationHandler接口的子类实例 


反射调用方法,可以通过Method类的invoke方法实现动态方法的调用
public Object invoke(Object obj, Object... args)
第一个参数代表对象
第二个参数代表执行方法上的参数



Ps:类加载器 
在Proxy类中的newProxyInstance()方法中需要一个ClassLoader类的实例,ClassLoader实际上对应的是类加载器,在Java中主要有一下三种类加载器; 
Booststrap ClassLoader:此加载器采用C++编写,一般开发中是看不到的; 
Extendsion ClassLoader:用来进行扩展类的加载,一般对应的是jre\lib\ext目录中的类; 
AppClassLoader:(默认)加载classpath指定的类,是最常使用的是一种加载器。 


与静态代理类对照的是动态代理类,动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java 反射机制可以生成任意类型的动态代理类。java.lang.reflect 包中的Proxy类和InvocationHandler 接口提供了生成动态代理类的能力。 



抽象角色:通过接口或抽象类声明真实角色实现的业务方法。

package ProxyTest;

public interface Subject {

	// 查看账户方法
	public void queryCount();

	// 修改账户方法
	public void updateCount();

}

真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。

package ProxyTest;

/**
 * 委托类(包含业务逻辑)
 */
public class RealSubject implements Subject {

	@Override
	public void queryCount() {
		System.out.println("查看账户方法...");
	}

	@Override
	public void updateCount() {
		System.out.println("修改账户方法...");
	}

}
代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。

package ProxyTest;

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

/** 
 * JDK动态代理代理类 
 */ 
public class MyInvocationHandler implements InvocationHandler{

	// 目标对象   
    private Object target;  
      
    /** 
     * 构造方法 
     * @param target 目标对象  
     */  
    public MyInvocationHandler(Object target) {  
        super();  
        this.target = target;  
    }  
  
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		
		// 在目标对象的方法执行之前简单的打印一下  
        System.out.println("------------------before------------------");  
          
        // 执行目标对象的方法  
        Object result = method.invoke(target, args);  
          
        // 在目标对象的方法执行之后简单的打印一下  
        System.out.println("-------------------after------------------");  
          
        return result; 
	}
	
	public Object getProxy(){
		return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
	}

}
测试类:

package ProxyTest;

public class TestProxy {
	
	public static void main(String[] args) {
		
		Subject subject = new RealSubject();
		MyInvocationHandler invocationHandler = new MyInvocationHandler(subject);
		Subject proxy = (Subject) invocationHandler.getProxy();
		proxy.queryCount();
		proxy.updateCount();
	}

}
运行结果:
------------------before------------------
查看账户方法...
-------------------after------------------
------------------before------------------
修改账户方法...

-------------------after------------------


但是,JDK的动态代理依靠接口实现,如果有些类并没有实现接口,则不能使用JDK代理,这就要使用cglib动态代理了。

4、Cglib动态代理

JDK的动态代理机制只能代理实现了接口的类,只能代理接口中的方法,而不能实现接口的类就不能实现JDK的动态代理,

cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。 

JDK的自带的动态代理也是动态生成了字节码(当然当中还用了反射),只是他的生成字节码的方式必须和接口绑定

cglib,并不是正对接口来而是针对普通类来动态的生成字节码

/** 
 * 这个是没有实现接口的实现类 
 *  
 */  
public class BookFacadeImpl1 {  
    public void addBook() {  
        System.out.println("增加图书的普通方法...");  
    }  
}


import java.lang.reflect.Method;  
  
import net.sf.cglib.proxy.Enhancer;  
import net.sf.cglib.proxy.MethodInterceptor;  
import net.sf.cglib.proxy.MethodProxy;  
  
/** 
 * 使用cglib动态代理  
 *  
 */  
public class BookFacadeCglib implements MethodInterceptor {  
    private Object target;  
  
    /** 
     * 创建代理对象 
     *  
     * @param target 
     * @return 
     */  
    public Object getInstance(Object target) {  
        this.target = target;  
        Enhancer enhancer = new Enhancer();  
        enhancer.setSuperclass(this.target.getClass());  
        // 回调方法  
        enhancer.setCallback(this);  
        // 创建代理对象  
        return enhancer.create();  
    }  
  
    @Override  
    // 回调方法  
    public Object intercept(Object obj, Method method, Object[] args,  
            MethodProxy proxy) throws Throwable {  
        System.out.println("事物开始");  
        proxy.invokeSuper(obj, args);  
        System.out.println("事物结束");  
        return null;  
  
  
    }  
  
}  


import net.battier.dao.impl.BookFacadeImpl1;  
import net.battier.proxy.BookFacadeCglib;  
  
public class TestCglib {  
      
    public static void main(String[] args) {  
        BookFacadeCglib cglib=new BookFacadeCglib();  
        BookFacadeImpl1 bookCglib=(BookFacadeImpl1)cglib.getInstance(new BookFacadeImpl1());  
        bookCglib.addBook();  
    }  
}

cglib介绍参考:CGlib简单介绍



参考来源:

java动态代理学习笔记

java动态代理(JDK和cglib)





0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:70380次
    • 积分:1609
    • 等级:
    • 排名:千里之外
    • 原创:89篇
    • 转载:15篇
    • 译文:0篇
    • 评论:26条
    最新评论