Java动态代理的实现机制
一、概述
代理是一种设计模式,其目的是为其他对象提供一个代理以控制对某个对象的访问,代理类负责为委托类预处理消息,过滤消息并转发消息以及进行消息被委托类执行后的后续处理。为了保持行为的一致性,代理类和委托类通常会实现相同的接口。
按照代理的创建时期,代理类可分为两种:
- 静态代理:由程序员创建代理类或特定工具自动生成源代码再对其编译,也就是说在程序运行前代理类的.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的动态代理,具体有以下四个步骤:
- 通过实现InvocationHandler接口创建自己的调用处理器
- 通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理类
- 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器类接口类型
- 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入
下面根据上述的四个步骤来实现自己的动态代理的示例:
接口和接口的实现类(即委托类)跟上面静态代理的代码一样,这里我们来实现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动态代理的实现机制的更多相关文章
- Java 动态代理与反射机制
java动态代理必须的两个类与两个接口: 首先需要有一个接口(委托者需要实现该接口的方法)示例如下: <pre name="code" class="html&qu ...
- Java动态代理和反射机制
反射机制 Java语言提供的一种基础功能,通过反射,我们可以操作这个类或对象,比如获取这个类中的方法.属性和构造方法等. 动态代理:分为JDK动态代理.cglib动态代理(spring中的动态代理). ...
- Java 动态代理机制分析及扩展
Java 动态代理机制分析及扩展,第 1 部分 王 忠平, 软件工程师, IBM 何 平, 软件工程师, IBM 简介: 本文通过分析 Java 动态代理的机制和特点,解读动态代理类的源代码,并且模拟 ...
- [转]Java 动态代理机制分析及扩展
引言 Java 动态代理机制的出现,使得 Java 开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象,便能动态地获得代理类.代理类会负责将所有的方法调用分派到委托对象上反射执行,在分派执 ...
- Java 动态代理机制分析及扩展--转
http://www.ibm.com/developerworks/cn/java/j-lo-proxy1/#icomments http://www.ibm.com/developerworks/c ...
- Java 动态代理机制分析及扩展,第 1 部分
Java 动态代理机制分析及扩展,第 1 部分 http://www.ibm.com/developerworks/cn/java/j-lo-proxy1/ 本文通过分析 Java 动态代理的机制和特 ...
- 详解java动态代理机制以及使用场景
详解java动态代理机制以及使用场景 https://blog.csdn.net/u011784767/article/details/78281384 深入理解java动态代理的实现机制 https ...
- 理解java动态代理
java动态代理是java语言的一项高级特性.在平时的项目开发中,可能很难遇到动态代理的案例.但是动态代理在很多框架中起着不可替代的作用,例如Spring的AOP.今天我们就聊一聊java动态代理的实 ...
- JAVA动态代理的全面深层理解
Java 动态代理机制的出现,使得 Java 开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象,便能动态地获得代理类.代理类会负责将所有的方法调用分派到委托对象上反射执行,在分派执行的过 ...
随机推荐
- ABAP--如何创建自定义打印条码
ABAP--如何创建自定义打印条码 BARCODE in Smartforms: How to create customize BARCODE for Smartforms. 1 Introduct ...
- C语言中的控制语句: 判断、环循等;
列举这一部分的目的就是注意它的写法,尤其是 分号 : 的位置: 1. if-else if else 语句: if (表达式1) 语句1: else if (表达式2) 语句2: else 语句3: ...
- InnoDB和MyISAM的区别与选择
MyISAM 性能(适合小项目,读快速)MyISAM 是MySQL中默认的存储引擎,比如适合新闻系统,读为主.InnoDB 事务或外键支持(适合大项目,高并发读写)活跃用户20多万时候,也能很轻松应付 ...
- htop和ncdu
今天突然发现服务器不能够mkdir了,说没空间,用df -h命令显示空间是很大的,网管过来用ncdu命令一下显示某些文件夹中有好多文件,说是因为linux中小文件太多引发,即实际空间还有很多,但是小文 ...
- Easyradius对接WayOs维盟小区版XQ教程
维盟极众多人的意见,然后又推出来一个新版本,唉~~~以前也是只有PC,有人给大量建议后,就再出个ISP,直接价格翻倍,再后来大家要桥接的功能又推出一个S9,这日子啥时候是个头啊. 小区版最主要的亮点就 ...
- Linux中的软链接与硬链接
软链接相当于windows的快捷方式,当源文件不存在时,软链接失效. 链接是指向文件名,当指向的文件名字删除的时候,就找不到源文件了.硬链接是指向文件本身,删除一个文件名字,还是可以找到源文件的.ls ...
- 【转】MongoDB学习笔记(查询)
原文地址 MongoDB学习笔记(查询) 基本查询: 构造查询数据. > db.test.findOne() { "_id" : ObjectId("4fd58ec ...
- 配置IDEA Scala环境
http://snglw.blog.51cto.com/5832405/1634595
- Linux 目录下属性查看操作
1. 查看当前目录所有文件和文件夹的大小 方法一: $du -sh * 或 $du -h -d 0 * '-d 0' 代表查询目录的深度为0 ,也就是当前目录,'-d 3' 表示文件目录深度为3,可以 ...
- Windows 2008驱动安装失败的原因及解决方法
希望这些内容能够帮助各位朋友顺利地在Windows Server 2008系统环境下安装使用好各种设备的驱动程序! 寻找安装失败原因 一般来说,当我们将目标设备的驱动安装光盘正确放置到Windows ...