动态代理的实现分析

来源:互联网 发布:淘宝秒杀验证码看不懂 编辑:程序博客网 时间:2024/06/06 00:36

下边是我对动态代理底层实现的一些认识了解, 如有不对,敬请赐教:

 

1.聚合和组合的区别

2.静态代理的两种方法:
 * 继承:会产生很多类。比如某个目标对象的方法需要写日志,则创建个日志代理类;现在该目标对象的方法需要增加权限,则创建
  权限代理类;现在该目标对象的方法需要既写日志又权限,则还需要新建个权限日志代理类
 * 聚合:针对继承的第三种情况:目标对象的方法需要既写日志又权限时,实用聚合时不需要再创建新的代理类,只需要把写日志的
  代理类作为写权限的代理类的传入参数即可。这是因为使用聚合实现的代理满足两个条件:1.实现于接口类,因此写日志的
  代理类本身就是接口类型,可以作为写权限代理类的参数。2.以接口类作为属性(而继承是使用方法重写的方式,实现代理
  类),就这点来说聚合和组合类似
   因此:聚合比继承好

3.聚合静态代理:
 实现和目标相同的接口

4.代理和装饰者模式相近,只不过装饰者模式有一部分实现类是没包含接口作为其属性的。而代理类都是以接口作为属性的,在调用接口
属性的方法同时,在加一些独立的功能

5.当你发现在很多方法内都写相同的代码时,你需要抽离封装,否则你需要修改时,要修改很多地方


代理:
 在目标对象基础上添加独立的功能,提供外部使用者的是代理类,这样外部使用者既能是用目标对象的功能,又能实现代理添加的独
 立的功能,而不用通过修改目标对象的代码
静态代理:
 让静态代理类实现目标对象的接口类,同时以目标对象实现的接口类型作为属性,然后在静态代理类中添加独立的功能,同时
 也实现目标对象的方法.
 针对每一类接口对象至少要实现一个代理类, 如果有很多接口对象, 则需要写大量的代理类(为什么这里写的是接口对象而不是
 目标对象呢,因为同一个接口的实现类可以共用同一份静态代理类,只需在spring的配置文件中配置就依赖对象即可)
 不仅造成代理类膨胀, 也会造成事务处理功能的代码存放于大量类中, 给修改维护带来很大工作量.

针对这些缺点, 用动态代理来解决: 使用JDK提供的Proxy类的方法就可以在JVM内存中产生一份二进制字节码,完全限定类名为:$Proxy0,
$Proxy0类扮演的角色就相当于一个静态代理类,只不过$Proxy0只是调用Proxy类:
newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)方法时,才在JVM内存中动态生成并且返回。
根本上$Proxy0就是一个代理类,不过需要把他指向接口类的引用对象

 

下边分析JDK使用Proxy类实现动态代理的原理:
newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h), 动态地创建目标对象的代理类
loader: 加载$Proxy0源代码,在JVM内存中产生的二进制内容的代理类时,所使用的加载器,一般设置为目标对象所使用的ClassLoader
interface: 目标对象实现的接口类,该参数的作用时,作为$Proxy0类的implements 接口
h: 代理实例, 该类含有Ojbect obj属性, 创建该类时, 目标对象作为其构造函数参数, 从而在通过重写InvocationHandler接口的方法:
   invoke(Object proxy, Method method, Object[] args),添加代理的独立功能,并且调用目标的方法
   注意:Object proxy的完全限定名为:$Proxy0


下边是我针对用户管理,使用动态代理的一个简单范例:
************************************************************************
package com.liucw.proxy;

//用户数据库操作接口类
public interface UserDAO(){
 public int addUser(String userName, String password);
}


************************************************************************
package com.liucw.proxy;

//MySql数据库的用户操作实现类
public class UserDAO4MySql implements UserDAO {

 public int addUser(String userName, String password) {
  System.out.println("1.添加记录到用户管理表TB_SM_USER");
  System.out.println("2.事务处理");
  return 1;
 }

}

*************************************************************************
package com.liucw.proxy;

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

//实现InvocationHandler类,能处理Transaction的
public class TransactionInvocationHandler implements InvocationHandler{
 private Object object;
 
 public TransactionInvocationHandler(Object object){
  this.object = object;
 }

 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  System.out.println("invoke first param type: " + proxy.getClass().getName());
  System.out.println("----- Transaction start -----");
  Object ret = method.invoke(object, args);
  System.out.println("----- Transaction end -----");
  return ret;
 }
 
}


************************************************************************
package com.liucw.proxy;

import java.lang.reflect.Proxy;
import junit.framework.TestCase;

//使用JUnit单元测试动态代理运行过程
public class ProxyTest extends TestCase {
 public void testProxy1(){
  UserDAO userDAO = new UserDAO4MySql();
  TransactionInvocationHandler handler = new TransactionInvocationHandler(userDAO);
  UserDAO userDAOProxy = (UserDAO)Proxy.newProxyInstance(userDAO.getClass().getClassLoader(),
       userDAO.getClass().getInterfaces(), handler);
  userDAOProxy.addUser("test", "123456");
 }
}

执行过程是:
 返回接口类userDAOProxy.addUser("test", "123456") ( 相当于UserDAO userDAOProxy = new $Proxy0() )
 ---> $Proxy0.addUser("test", "123456")
 ---> $Proxy0.addUser方法内调用: InvocationHandler.invoke(this, method, args);  根据多态的原理,实际上调用的是:
      TransactionInvocationHandler的invoke(Object proxy, Method method, Object[] args)

*************************************************************************
运行结果:
invoke first param type: $Proxy0
----- Transaction start -----
1.添加记录到用户管理表TB_SM_USER
2.事务处理
----- Transaction end -----


其中在JVM内存中生成的$Proxy0二进制字节类的源代码为:
import java.lang.reflect.Method

public class $Proxy0 implements com.liucw.proxy.UserDAO{
 private InvocationHandler h;
 public $Proxy0(InvocationHandler h){
  this.h =h;
 }
 
 @override
 public int addUser(String userName, String password){
  try{
   Method md = com.liucw.proxy.UserDAO.class.getMethod("addUser");
   Object[] args = new Object[]{userName, password};
   h.invoke(this, md, args);
  }catch(Exception e){ 
   e.printStackTrace();
  } 
 }
}


------------------------------------------------------------------------
$Proxy0的生成过程:
1.Proxy的newProxyInstance(..)方法内有一份字符串:代理类的源代码
2.有两种方式处理这份代理类的源代码:
 * 使用jdk 6提供的Compiler API
 * 使用更底层的CGLIB/ASM  直接把字符串,编译,在JVM生成.class二进制码
 两者的区别:使用第一种方式会使用输出流在本地生成一份.java文件,然后然后使用Compiler把.java文件编译成.class字节码
      文件,然后使用指定的ClassLoader对.class字节码进行类加载,在JVM内存中生成一份Class实例,然后根据方法:
      newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)的Interfaces的Class
      类型和InvocationHandler,利用反射Constructor,创建返回一个代理类
 
     使用第二种方式:不会再本地生成代理类的.java文件

处理过程:
  JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
  StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
  Iterable units = fileMgr.getJavaFileObjects(fileName);
  CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
  t.call();
  fileMgr.close();
  
  //load into memory and create an instance
  URL[] urls = new URL[] {new URL("file:/" + "d:/src/")};
  URLClassLoader ul = new URLClassLoader(urls);
  Class c = ul.loadClass("com.bjsxt.proxy.$Proxy1");
  System.out.println(c);
  
  Constructor ctr = c.getConstructor(InvocationHandler.class);
  Object m = ctr.newInstance(h);

 

使用动态代理我觉得最重要的一点是: 调用Proxy的newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h), 在心中要有一份完全限定名为:$Proxy0的.java文件,$Proxy0类似我们写的一份静态代理类