一、概述

  代理是一种设计模式,其目的是为其他对象提供一个代理以控制对某个对象的访问,代理类负责为委托类预处理消息,过滤消息并转发消息以及进行消息被委托类执行后的后续处理。为了保持行为的一致性,代理类和委托类通常会实现相同的接口。

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

  • 静态代理:由程序员创建代理类或特定工具自动生成源代码再对其编译,也就是说在程序运行前代理类的.class文件就已经存在。
  • 动态代理:在程序运行时运用反射机制动态创建生成。

  下面在将动态代理的实现机制之前先简单介绍一下静态代理。

二、静态代理

  上面说过,代理类和委托类一般都要实现相同的接口,下面先定义这个接口:

public interface Service
{
public void add();
}

  委托类作为接口的一种实现,定义如下:

public class ServiceImpl implements Service
{
public void add()
{
System.out.println("添加用户!"); }
}

  假如我们要对委托类加一些日志的操作,代理类可做如下定义:

public class ServiceProxy implements Service
{
private Service service;
public ServiceProxy(Service service)
{
super();
this.service = service;
}
public void add()
{
System.out.println("服务开始");
service.add();
System.out.println("服务结束");
}
}

  编写测试类:

public class TestMain
{
public static void main(String[] args)
{
Service serviceImpl=new ServiceImpl();
Service proxy=new ServiceProxy(serviceImpl);
proxy.add();
}
}

  运行测试程序,结果如下图:

  从上面的代码可以看到,静态代理类只能为特定的接口服务,如果要服务多类型的对象,就要为每一种对象进行代理。我们就会想是否可以通过一个代理类完成全部的代理功能,于是引入的动态代理的概念。

三、动态代理

  Java的动态代理主要涉及两个类,Proxy和InvocationHandler。

  Proxy:提供了一组静态方法来为一组接口动态地生成代理类及其对象。

// 方法 1: 该方法用于获取指定代理对象所关联的调用处理器
static InvocationHandler getInvocationHandler(Object proxy) // 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象
static Class getProxyClass(ClassLoader loader, Class[] interfaces) // 方法 3:该方法用于判断指定类对象是否是一个动态代理类
static boolean isProxyClass(Class cl) // 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
static Object newProxyInstance(ClassLoader loader, Class[] interfaces,InvocationHandler h)

  InvocationHandler:它是调用处理器接口,自定义了一个invok方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问

// 该方法负责集中处理动态代理类上的所有方法调用。第一个参数既是代理类实例,第二个参数是被调用的方法对象
// 第三个方法是调用参数。调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行
Object invoke(Object proxy, Method method, Object[] args)

  实现Java的动态代理,具体有以下四个步骤:

  1. 通过实现InvocationHandler接口创建自己的调用处理器
  2. 通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理类
  3. 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器类接口类型
  4. 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入

  下面根据上述的四个步骤来实现自己的动态代理的示例:

  接口和接口的实现类(即委托类)跟上面静态代理的代码一样,这里我们来实现InvocationHandler接口创建自己的调用处理器

public class ServiceHandle implements InvocationHandler
{
private Object s; public ServiceHandle(Object s)
{
this.s = s;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable
{
System.out.println("服务开始");
//invoke表示对带有指定参数的指定对象调用由此 Method 对象表示的底层方法
Object result=method.invoke(s, args);
System.out.println("服务结束");
return result;
}
}

  编写测试类:

public class TestMain
{
public static void main(String[] args)
{
Service service=new ServiceImpl();
InvocationHandler handler=new ServiceHandle(service);
Service s=(Service) Proxy.newProxyInstance(service.getClass().getClassLoader(), service.getClass().getInterfaces(), handler);
s.add();
}
}

  运行测试程序,结果同静态代理。我们可以看到上述代码并没有我们之前说的步骤2和3,这是因为Proxy的静态方法newProxyInstance已经为我们封装了这两个步骤。具体的内部实现如下:

// 通过 Proxy 为包括 Interface 接口在内的一组接口动态创建代理类的类对象
Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... }); // 通过反射从生成的类对象获得构造函数对象
Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class }); // 通过构造函数对象创建动态代理类实例
Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });

  newProxyInstance函数的内部实现为:

     public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException
  {
//检查h不为空,否则抛异常
Objects.requireNonNull(h);
//获得与制定类装载器和一组接口相关的代理类类型对象
final Class<?>[] intfs = interfaces.clone(); //检查接口类对象是否对类装载器可见并且与类装载器所能识别的接口类对象是完全相同的
final SecurityManager sm = System.getSecurityManager();
if (sm != null)
{
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
//获得与制定类装载器和一组接口相关的代理类类型对象
Class<?> cl = getProxyClass0(loader, intfs);
try
{
if (sm != null)
{
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
// 通过反射获取构造函数对象并生成代理类实例
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers()))
{
AccessController.doPrivileged(new PrivilegedAction<Void>()
{
public Void run()
{
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
}
catch (IllegalAccessException|InstantiationException e)
{
throw new InternalError(e.toString(), e);
}
catch (InvocationTargetException e)
{
Throwable t = e.getCause();
if (t instanceof RuntimeException)
{
throw (RuntimeException) t;
}
else
{
throw new InternalError(t.toString(), t);
}
}
catch (NoSuchMethodException e)
{
throw new InternalError(e.toString(), e);
}
 }

四、模拟实现Proxy类

  根据上面的原理介绍,我们可以自己模拟实现Proxy类:

public class Proxy
{
public static Object newProxyInstance(Class inface,InvocationHandle h) throws Exception
{
String rt="\r\n";
String methodStr="";
Method[] methods=inface.getMethods();
for(Method m:methods)
{
methodStr+="@Override"+rt+
"public void "+m.getName()+"()"+rt+"{" + rt +
" try {"+rt+
" Method md="+inface.getName()+".class.getMethod(\""+m.getName()+"\");"+rt+
"h.invoke(this,md);"+rt+
" } catch(Exception e){e.printStackTrace();}"+rt+ "}";
}
String src="package test;"+rt+
"import java.lang.reflect.Method;"+rt+
"public class ServiceImpl2 implements "+inface.getName()+ rt+
"{"+rt+
"public ServiceImpl2(InvocationHandle h)"+rt+
"{"+rt+
"this.h = h;"+rt+
"}"+rt+
" test.InvocationHandle h;"+rt+
methodStr+
"}";
String fileName="d:/src/test/ServiceImpl2.java";
//compile
compile(src, fileName);
//load into memory and create instance
Object m = loadMemory(h); return m;
}
private static void compile(String src, String fileName) throws IOException
{
File f=new File(fileName);
FileWriter fileWriter=new FileWriter(f);
fileWriter.write(src);
fileWriter.flush();
fileWriter.close();
//获取此平台提供的Java编译器
JavaCompiler compiler=ToolProvider.getSystemJavaCompiler();
//获取一个标准文件管理器实现的新实例
StandardJavaFileManager fileManager=compiler.getStandardFileManager(null,null, null);
//获取表示给定文件的文件对象
Iterable units=fileManager.getJavaFileObjects(fileName);
//使用给定组件和参数创建编译任务的 future
CompilationTask t=compiler.getTask(null, fileManager, null, null, null, units);
//执行此编译任务
t.call();
fileManager.close();
}
private static Object loadMemory(InvocationHandle h)
throws MalformedURLException, ClassNotFoundException,
NoSuchMethodException, InstantiationException,
IllegalAccessException, InvocationTargetException
{
URL[] urls=new URL[] {new URL("file:/"+"d:/src/")};
//从路径d:/src/加载类和资源
URLClassLoader ul=new URLClassLoader(urls);
Class c=ul.loadClass("test.ServiceImpl2");
//返回Class对象所表示的类的指定公共构造方法。
Constructor ctr=c.getConstructor(InvocationHandle.class);
//使用此 Constructor对象ctr表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例
Object m = ctr.newInstance(h);
return m;
}
}

五、总结

  1、所谓的动态代理就是这样一种class,它是在运行时生成的class,在生成它时你必须提供一组interface给它,然后改class就宣称它实现了这些interface,但是其实它不会替你作实质性的工作,而是根据你在生成实例时提供的参数handler(即InvocationHandler接口的实现类),由这个Handler来接管实际的工作。

  2、Proxy的设计使得它只能支持interface的代理,Java的继承机制注定了动态代理类无法实现对class的动态代理,因为多继承在Java中本质上就行不通。

  

Java动态代理的实现机制的更多相关文章

  1. Java 动态代理与反射机制

    java动态代理必须的两个类与两个接口: 首先需要有一个接口(委托者需要实现该接口的方法)示例如下: <pre name="code" class="html&qu ...

  2. Java动态代理和反射机制

    反射机制 Java语言提供的一种基础功能,通过反射,我们可以操作这个类或对象,比如获取这个类中的方法.属性和构造方法等. 动态代理:分为JDK动态代理.cglib动态代理(spring中的动态代理). ...

  3. Java 动态代理机制分析及扩展

    Java 动态代理机制分析及扩展,第 1 部分 王 忠平, 软件工程师, IBM 何 平, 软件工程师, IBM 简介: 本文通过分析 Java 动态代理的机制和特点,解读动态代理类的源代码,并且模拟 ...

  4. [转]Java 动态代理机制分析及扩展

    引言 Java 动态代理机制的出现,使得 Java 开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象,便能动态地获得代理类.代理类会负责将所有的方法调用分派到委托对象上反射执行,在分派执 ...

  5. Java 动态代理机制分析及扩展--转

    http://www.ibm.com/developerworks/cn/java/j-lo-proxy1/#icomments http://www.ibm.com/developerworks/c ...

  6. Java 动态代理机制分析及扩展,第 1 部分

    Java 动态代理机制分析及扩展,第 1 部分 http://www.ibm.com/developerworks/cn/java/j-lo-proxy1/ 本文通过分析 Java 动态代理的机制和特 ...

  7. 详解java动态代理机制以及使用场景

    详解java动态代理机制以及使用场景 https://blog.csdn.net/u011784767/article/details/78281384 深入理解java动态代理的实现机制 https ...

  8. 理解java动态代理

    java动态代理是java语言的一项高级特性.在平时的项目开发中,可能很难遇到动态代理的案例.但是动态代理在很多框架中起着不可替代的作用,例如Spring的AOP.今天我们就聊一聊java动态代理的实 ...

  9. JAVA动态代理的全面深层理解

    Java 动态代理机制的出现,使得 Java 开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象,便能动态地获得代理类.代理类会负责将所有的方法调用分派到委托对象上反射执行,在分派执行的过 ...

随机推荐

  1. JS对checkbox全选和取消全选

    需求:checkbox控制列表数据全选与取消全选择. 效果图: 1.html <body > <input type="button" name="in ...

  2. 2016年第七届蓝桥杯C/C++B组省赛题目解析

    题目1:煤球数目 有一堆煤球,堆成三角棱锥形.具体:第一层放1个,第二层3个(排列成三角形),第三层6个(排列成三角形),第四层10个(排列成三角形),....如果一共有100层,共有多少个煤球?请填 ...

  3. 理解Java动态代理(1)—找我还钱?我出钱要你的命

    代理模式是最常用的一个设计模式之一,理解起来也是很简单,一张图足以说明了,LZ就不废话了. 至于代理模式能干嘛也不是LZ今天想说的,今天主要想简单介绍下JAVA里面的动态代理.“动”当然是相对“静”来 ...

  4. Spring JDBC RowMapper接口示例

    JdbcTemplate类使用org.springframework.jdbc.core.RowMapper <T>接口在每行的基础上映射ResultSet的行.该接口的实现执行将每行映射 ...

  5. Spring Boot 快速搭建的三种方式

    方式一:http://start.spring.io/ 打开浏览器,在地址栏中输入http://start.spring.io/ 如下图:  点击generate project 然后就会有一个zip ...

  6. Linux环境安装MySQL数据库(RPM格式的软件包)

    1.  下载mysql安装包 下载地址1:  http://www.mysql.com/   (mysql官网) 下载地址2:  http://mirrors.sohu.com/mysql/   (其 ...

  7. k8s sidecar, Ambassador, Adapter containers

    When you start thinking in terms of Pods, there are naturally some general patterns of modular appli ...

  8. 【苏勇老师Linux 入门笔记】网络基础

    IP 地址 IP 编制时一个双层编制方案,一个 IP 地址标示一个主机 (或一个网卡接口). 一个 IP 地址分为两个部分:网络部分(所属区域)和主机部分(标示区域中的哪个主机).IPv4 共32位, ...

  9. 如何找回Ucenter创始人密码,账号无需修改

    UCenter 创始人的密码非常重要,忘记或丢失后,就不能进入 UCenter 进行用户和数据的管理,也会对站点造成安全隐患.由于 UCenter 的密码是采用两次 md5 加一个随机数的形式加密的, ...

  10. 如何换网页IP代理

    如何换网页IP代理 | 浏览:21 | 更新:2014-08-31 13:46 1 2 3 4 5 6 7 分步阅读 如何设置网页IP代理的步骤:现在就看下图如何换网页IP和清理浏览器cookie,[ ...