【转载】Java 动态代理
Java 动态代理
本文为 Android 开源项目源码解析 公共技术点中的 动态代理 部分
项目地址:Jave Proxy,分析的版本:openjdk 1.6,Demo 地址:Proxy Demo
分析者:Caij,校对者:Trinea,校对状态:完成
1. 相关概念
1.1 代理
在某些情况下,我们不希望或是不能直接访问对象 A,而是通过访问一个中介对象 B,由 B 去访问 A 达成目的,这种方式我们就称为代理。
这里对象 A 所属类我们称为委托类,也称为被代理类,对象 B 所属类称为代理类。
代理优点有:
- 隐藏委托类的实现
- 解耦,不改变委托类代码情况下做一些额外处理,比如添加初始判断及其他公共操作
根据程序运行前代理类是否已经存在,可以将代理分为静态代理和动态代理。
1.2 静态代理
代理类在程序运行前已经存在的代理方式称为静态代理。
通过上面解释可以知道,由开发人员编写或是编译器生成代理类的方式都属于静态代理,如下是简单的静态代理实例:
class ClassA {
public void operateMethod1() {};
public void operateMethod2() {};
public void operateMethod3() {};
}
public class ClassB {
private ClassA a;
public ClassB(ClassA a) {
this.a = a;
}
public void operateMethod1() {
a.operateMethod1();
};
public void operateMethod2() {
a.operateMethod2();
};
// not export operateMethod3()
}
上面ClassA
是委托类,ClassB
是代理类,ClassB
中的函数都是直接调用ClassA
相应函数,并且隐藏了Class
的operateMethod3()
函数。
静态代理中代理类和委托类也常常继承同一父类或实现同一接口。
1.3 动态代理
代理类在程序运行前不存在、运行时由程序动态生成的代理方式称为动态代理。
Java 提供了动态代理的实现方式,可以在运行时刻动态生成代理类。这种代理方式的一大好处是可以方便对代理类的函数做统一或特殊处理,如记录所有函数执行时间、所有函数执行前添加验证判断、对某个特殊函数进行特殊操作,而不用像静态代理方式那样需要修改每个函数。
静态代理
比较简单,本文上面已简单介绍,下面重点介绍动态代理
。
2. 动态代理实例
实现动态代理包括三步:
(1). 新建委托类;
(2). 实现InvocationHandler
接口,这是负责连接代理类和委托类的中间类必须实现的接口;
(3). 通过Proxy
类新建代理类对象。
下面通过实例具体介绍,假如现在我们想统计某个类所有函数的执行时间,传统的方式是在类的每个函数前打点统计,动态代理方式如下:
2.1 新建委托类
public interface Operate {
public void operateMethod1();
public void operateMethod2();
public void operateMethod3();
}
public class OperateImpl implements Operate {
@Override
public void operateMethod1() {
System.out.println("Invoke operateMethod1");
sleep(110);
}
@Override
public void operateMethod2() {
System.out.println("Invoke operateMethod2");
sleep(120);
}
@Override
public void operateMethod3() {
System.out.println("Invoke operateMethod3");
sleep(130);
}
private static void sleep(long millSeconds) {
try {
Thread.sleep(millSeconds);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Operate
是一个接口,定了了一些函数,我们要统计这些函数的执行时间。OperateImpl
是委托类,实现Operate
接口。每个函数简单输出字符串,并等待一段时间。
动态代理要求委托类必须实现了某个接口,比如这里委托类OperateImpl
实现了Operate
,原因会后续在微博公布。
2.2. 实现 InvocationHandler 接口
public class TimingInvocationHandler implements InvocationHandler {
private Object target;
public TimingInvocationHandler() {}
public TimingInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = System.currentTimeMillis();
Object obj = method.invoke(target, args);
System.out.println(method.getName() + " cost time is:" + (System.currentTimeMillis() - start));
return obj;
}
}
target
属性表示委托类对象。
InvocationHandler
是负责连接代理类和委托类的中间类必须实现的接口。其中只有一个
public Object invoke(Object proxy, Method method, Object[] args)
函数需要去实现,参数:proxy
表示下面2.3 通过 Proxy.newProxyInstance() 生成的代理类对象
。method
表示代理对象被调用的函数。args
表示代理对象被调用的函数的参数。
调用代理对象的每个函数实际最终都是调用了InvocationHandler
的invoke
函数。这里我们在invoke
实现中添加了开始结束计时,其中还调用了委托类对象target
的相应函数,这样便完成了统计执行时间的需求。invoke
函数中我们也可以通过对method
做一些判断,从而对某些函数特殊处理。
2.3. 通过 Proxy 类静态函数生成代理对象
public class Main {
public static void main(String[] args) {
// create proxy instance
TimingInvocationHandler timingInvocationHandler = new TimingInvocationHandler(new OperateImpl());
Operate operate = (Operate)(Proxy.newProxyInstance(Operate.class.getClassLoader(), new Class[] {Operate.class},
timingInvocationHandler));
// call method of proxy instance
operate.operateMethod1();
System.out.println();
operate.operateMethod2();
System.out.println();
operate.operateMethod3();
}
}
这里我们先将委托类对象new OperateImpl()
作为TimingInvocationHandler
构造函数入参创建timingInvocationHandler
对象;
然后通过Proxy.newProxyInstance(…)
函数新建了一个代理对象,实际代理类就是在这时候动态生成的。我们调用该代理对象的函数就会调用到timingInvocationHandler
的invoke
函数(是不是有点类似静态代理),而invoke
函数实现中调用委托类对象new OperateImpl()
相应的 method(是不是有点类似静态代理)。
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
loader
表示类加载器interfaces
表示委托类的接口,生成代理类时需要实现这些接口h
是InvocationHandler
实现类对象,负责连接代理类和委托类的中间类
我们可以这样理解,如上的动态代理实现实际是双层的静态代理,开发者提供了委托类 B,程序动态生成了代理类 A。开发者还需要提供一个实现了InvocationHandler
的子类 C,子类 C 连接代理类 A 和委托类 B,它是代理类 A 的委托类,委托类 B 的代理类。用户直接调用代理类 A 的对象,A 将调用转发给委托类 C,委托类 C 再将调用转发给它的委托类 B。
3. 动态代理原理
实际上面最后一段已经说清了动态代理的真正原理。我们来仔细分析下
3.1 生成的动态代理类代码
下面是上面示例程序运行时自动生成的动态代理类代码,如何得到这些生成的代码请见ProxyUtils,查看 class 文件可使用 jd-gui
import com.codekk.java.test.dynamicproxy.Operate;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy
implements Operate
{
private static Method m4;
private static Method m1;
private static Method m5;
private static Method m0;
private static Method m3;
private static Method m2;
public $Proxy0(InvocationHandler paramInvocationHandler)
throws
{
super(paramInvocationHandler);
}
public final void operateMethod1()
throws
{
try
{
h.invoke(this, m4, null);
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final boolean equals(Object paramObject)
throws
{
try
{
return ((Boolean)h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final void operateMethod2()
throws
{
try
{
h.invoke(this, m5, null);
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final int hashCode()
throws
{
try
{
return ((Integer)h.invoke(this, m0, null)).intValue();
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final void operateMethod3()
throws
{
try
{
h.invoke(this, m3, null);
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final String toString()
throws
{
try
{
return (String)h.invoke(this, m2, null);
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
static
{
try
{
m4 = Class.forName("com.codekk.java.test.dynamicproxy.Operate").getMethod("operateMethod1", new Class[0]);
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m5 = Class.forName("com.codekk.java.test.dynamicproxy.Operate").getMethod("operateMethod2", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
m3 = Class.forName("com.codekk.java.test.dynamicproxy.Operate").getMethod("operateMethod3", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
return;
}
catch (NoSuchMethodException localNoSuchMethodException)
{
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException)
{
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
}
从中我们可以看出动态生成的代理类是以$Proxy
为类名前缀,继承自Proxy
,并且实现了Proxy.newProxyInstance(…)
第二个参数传入的所有接口的类。
如果代理类实现的接口中存在非 public 接口,则其包名为该接口的包名,否则为com.sun.proxy
。
其中的operateMethod1()
、operateMethod2()
、operateMethod3()
函数都是直接交给h
去处理,h
在父类Proxy
中定义为
protected InvocationHandler h;
即为Proxy.newProxyInstance(…)
第三个参数。
所以InvocationHandler
的子类 C 连接代理类 A 和委托类 B,它是代理类 A 的委托类,委托类 B 的代理类。
3.2. 生成动态代理类原理
以下针对 Java 1.6 源码进行分析,动态代理类是在调用Proxy.newProxyInstance(…)
函数时生成的。
(1). newProxyInstance(…)
函数代码如下:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
if (h == null) {
throw new NullPointerException();
}
/*
* Look up or generate the designated proxy class.
*/
Class cl = getProxyClass(loader, interfaces);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
Constructor cons = cl.getConstructor(constructorParams);
return (Object) cons.newInstance(new Object[] { h });
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString());
} catch (IllegalAccessException e) {
throw new InternalError(e.toString());
} catch (InstantiationException e) {
throw new InternalError(e.toString());
} catch (InvocationTargetException e) {
throw new InternalError(e.toString());
}
}
从中可以看出它先调用getProxyClass(loader, interfaces)
得到动态代理类,然后将InvocationHandler
作为代理类构造函数入参新建代理类对象。
(2). getProxyClass(…)
函数代码及解释如下(省略了原英文注释):
/**
* 得到代理类,不存在则动态生成
* @param loader 代理类所属 ClassLoader
* @param interfaces 代理类需要实现的接口
* @return
*/
public static Class<?> getProxyClass(ClassLoader loader,
Class<?>... interfaces)
throws IllegalArgumentException
{
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// 代理类类对象
Class proxyClass = null;
/* collect interface names to use as key for proxy class cache */
String[] interfaceNames = new String[interfaces.length];
Set interfaceSet = new HashSet(); // for detecting duplicates
/**
* 入参 interfaces 检验,包含三部分
* (1)是否在入参指定的 ClassLoader 内
* (2)是否是 Interface
* (3)interfaces 中是否有重复
*/
for (int i = 0; i < interfaces.length; i++) {
String interfaceName = interfaces[i].getName();
Class interfaceClass = null;
try {
interfaceClass = Class.forName(interfaceName, false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != interfaces[i]) {
throw new IllegalArgumentException(
interfaces[i] + " is not visible from class loader");
}
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
if (interfaceSet.contains(interfaceClass)) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
interfaceSet.add(interfaceClass);
interfaceNames[i] = interfaceName;
}
// 以接口名对应的 List 作为缓存的 key
Object key = Arrays.asList(interfaceNames);
/*
* loaderToCache 是个双层的 Map
* 第一层 key 为 ClassLoader,第二层 key 为 上面的 List,value 为代理类的弱引用
*/
Map cache;
synchronized (loaderToCache) {
cache = (Map) loaderToCache.get(loader);
if (cache == null) {
cache = new HashMap();
loaderToCache.put(loader, cache);
}
}
/*
* 以上面的接口名对应的 List 为 key 查找代理类,如果结果为:
* (1) 弱引用,表示代理类已经在缓存中
* (2) pendingGenerationMarker 对象,表示代理类正在生成中,等待生成完成通知。
* (3) null 表示不在缓存中且没有开始生成,添加标记到缓存中,继续生成代理类
*/
synchronized (cache) {
do {
Object value = cache.get(key);
if (value instanceof Reference) {
proxyClass = (Class) ((Reference) value).get();
}
if (proxyClass != null) {
// proxy class already generated: return it
return proxyClass;
} else if (value == pendingGenerationMarker) {
// proxy class being generated: wait for it
try {
cache.wait();
} catch (InterruptedException e) {
}
continue;
} else {
cache.put(key, pendingGenerationMarker);
break;
}
} while (true);
}
try {
String proxyPkg = null; // package to define proxy class in
/*
* 如果 interfaces 中存在非 public 的接口,则所有非 public 接口必须在同一包下面,后续生成的代理类也会在该包下面
*/
for (int i = 0; i < interfaces.length; i++) {
int flags = interfaces[i].getModifiers();
if (!Modifier.isPublic(flags)) {
String name = interfaces[i].getName();
int n = name.lastIndexOf('.');
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
if (proxyPkg == null) { // if no non-public proxy interfaces,
proxyPkg = ""; // use the unnamed package
}
{
// 得到代理类的类名,jdk 1.6 版本中缺少对这个生成类已经存在的处理。
long num;
synchronized (nextUniqueNumberLock) {
num = nextUniqueNumber++;
}
String proxyName = proxyPkg + proxyClassNamePrefix + num;
// 动态生成代理类的字节码
// 最终调用 sun.misc.ProxyGenerator.generateClassFile() 得到代理类相关信息写入 DataOutputStream 实现
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces);
try {
// native 层实现,虚拟机加载代理类并返回其类对象
proxyClass = defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
// add to set of all generated proxy classes, for isProxyClass
proxyClasses.put(proxyClass, null);
} finally {
// 代理类生成成功则保存到缓存,否则从缓存中删除,然后通知等待的调用
synchronized (cache) {
if (proxyClass != null) {
cache.put(key, new WeakReference(proxyClass));
} else {
cache.remove(key);
}
cache.notifyAll();
}
}
return proxyClass;
}
函数主要包括三部分:
- 入参 interfaces 检验,包含是否在入参指定的 ClassLoader 内、是否是 Interface、interfaces 中是否有重复
- 以接口名对应的 List 为 key 查找代理类,如果结果为:
- 弱引用,表示代理类已经在缓存中;
- pendingGenerationMarker 对象,表示代理类正在生成中,等待生成完成返回;
- null 表示不在缓存中且没有开始生成,添加标记到缓存中,继续生成代理类。
- 如果代理类不存在调用
ProxyGenerator.generateProxyClass(…)
生成代理类并存入缓存,通知在等待的缓存。
函数中几个注意的地方:
- 代理类的缓存 key 为接口名对应的 List,接口顺序不同表示不同的 key 即不同的代理类。
- 如果 interfaces 中存在非 public 的接口,则所有非 public 接口必须在同一包下面,后续生成的代理类也会在该包下面。
- 代理类如果在 ClassLoader 中已经存在的情况没有做处理。
- 可以开启 System Properties 的
sun.misc.ProxyGenerator.saveGeneratedFiles
开关,保存动态类到目的地址。
Java 1.7 的实现略有不同,通过getProxyClass0(…)
函数实现,实现中调用代理类的缓存,判断代理类在缓存中是否已经存在,存在直接返回,不存在则调用proxyClassCache
的valueFactory
属性进行动态生成,valueFactory
的apply
函数与上面的getProxyClass(…)
函数逻辑类似。
4. 使用场景
4.1 J2EE Web 开发中 Spring 的 AOP(面向切面编程) 特性
作用:目标函数之间解耦。
比如在 Dao 中,每次数据库操作都需要开启事务,而且在操作的时候需要关注权限。一般写法是在 Dao 的每个函数中添加相应逻辑,造成代码冗余,耦合度高。
使用动态代理前伪代码如下:
Dao {
insert() {
判断是否有保存的权限;
开启事务;
插入;
提交事务;
}
delete() {
判断是否有删除的权限;
开启事务;
删除;
提交事务;
}
}
使用动态代理的伪代码如下:
// 使用动态代理,组合每个切面的函数,而每个切面只需要关注自己的逻辑就行,达到减少代码,松耦合的效果
invoke(Object proxy, Method method, Object[] args)
throws Throwable {
判断是否有权限;
开启事务;
Object ob = method.invoke(dao, args);
提交事务;
return ob;
}
4.2 基于 REST 的 Android 端网络请求框架 Retrofit
作用:简化网络请求操作。
一般情况下每个网络请求我们都需要调用一次HttpURLConnection
或者HttpClient
进行请求,或者像 Volley 一样丢进等待队列中,Retrofit 极大程度简化了这些操作,示例代码如下:
public interface GitHubService {
@GET("/users/{user}/repos")
List<Repo> listRepos(@Path("user") String user);
}
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint("https://api.github.com")
.build();
GitHubService service = restAdapter.create(GitHubService.class);
以后我们只需要直接调用
List<Repo> repos = service.listRepos("octocat");
即可开始网络请求,Retrofit
的原理就是基于动态代理,它同时用到了 注解 的原理,本文不做深入介绍,具体请等待 Retrofit 源码解析 完成。
【转载】Java 动态代理的更多相关文章
- java动态代理实现与原理详细分析(【转载】By--- Gonjan )
[转载]By---Gonjan 关于Java中的动态代理,我们首先需要了解的是一种常用的设计模式--代理模式,而对于代理,根据创建代理类的时间点,又可以分为静态代理和动态代理. 一.代理模式 ...
- Java动态代理 深度详解
代理模式是设计模式中非常重要的一种类型,而设计模式又是编程中非常重要的知识点,特别是在业务系统的重构中,更是有举足轻重的地位.代理模式从类型上来说,可以分为静态代理和动态代理两种类型. 今天我将用非常 ...
- 理解java动态代理
java动态代理是java语言的一项高级特性.在平时的项目开发中,可能很难遇到动态代理的案例.但是动态代理在很多框架中起着不可替代的作用,例如Spring的AOP.今天我们就聊一聊java动态代理的实 ...
- JAVA动态代理 你真的完全了解Java动态代理吗?
网上讲JAVA动态代理,说的天花乱坠,发现一篇文章写的通俗易懂,特意转载过来 原文地址:https://www.jianshu.com/p/95970b089360 动态代理看起来好像是个什么高大上的 ...
- Java 动态代理机制详解
在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的 ...
- Java动态代理全面分析
代理模式 解说:给某一个对象提供一个代理,并由代理对象控制对原对象的引用: 代理模式需要以下几个角色: 1 主题:规定代理类和真实对象共同对外暴露的接口: 2 代理类:专门代理真实对象的类: 3 ...
- JAVA动态代理模式(从现实生活角度理解代码原理)
所谓动态代理,即通过代理类:Proxy的代理,接口和实现类之间可以不直接发生联系,而可以在运行期(Runtime)实现动态关联. java动态代理主要是使用java.lang.reflect包中的两个 ...
- Java 动态代理作用是什么?
Java 动态代理作用是什么? 1 条评论 分享 默认排序按时间排序 19 个回答 133赞同反对,不会显示你的姓名 Intopass 程序员,近期沉迷于动漫ING 133 人赞同 ① 首先你 ...
- java动态代理原理
我们经常会用到Java的动态代理技术, 虽然会使用, 但是自己对其中的原理却不是很了解.比如代理对象是如何产生的, InvocationHandler的invoke方法是如何调用的?今天就来深究下Ja ...
随机推荐
- PYTHON线程知识再研习C---线程互斥锁
结合例子,就很好理解了. 就是不要让共享变量被各个线程无序执行,导致结果不可预期 threading模块中定义了Lock类,可以方便的处理锁定: #创建锁mutex = threading.Lock( ...
- Python 坑爹之 代码缩进
建议:统一使用空格!!!!!!!!!不要Tab Python代码缩进 这两天python-cn邮件列表有一条thread发展的特别长,题目是<python的代码缩进真是坑爹>(地址), ...
- MySQL数学函数
官方文档:Numeric Functions and Operators Name Description ABS() Return the absolute value ACOS() Return ...
- netcat
一.概述 netcat是网络工具中的瑞士军刀,它能通过TCP和UDP在网络中读写数据.通过与其他工具结合和重定向,你可以在脚本中以多种方式使用它.使用netcat命令所能完成的事情令人惊讶.netca ...
- [VBA] excel获取单元格的超链接地址函数
Function geturl(c As Range) As String geturl = c.Hyperlinks().Address End Function 设置超链接的函数是HYPERLIN ...
- acdream:Andrew Stankevich Contest 3:Two Cylinders:数值积分
Two Cylinders Special JudgeTime Limit: 10000/5000MS (Java/Others)Memory Limit: 128000/64000KB (Java/ ...
- hdu5035:概率论推公式
题目大意: 你要去邮局发一个包裹,有n个窗口,每个都有人,每一个窗口完成一次服务的时间 ti 的分布符合几何分布:ki*e^(-ki*t) 每个窗口当前服务已经进行了ci时间 你会去第一个完成当前服务 ...
- VS2012/2013编辑器问题
1. Visual Studio 2013 'Could not evaluate Expression' Debugger Abnormality 解决办法:http://weblog.west-w ...
- 第20讲- Spinner与适配器模式
第20讲 Spinner与适配器模式 使用Spinner相当于从下拉列表中选择项目,Spinner是一个每次只能选择所有项的一个项的控件.它的项来自于与之相关联的适配器中.Spinner的重点问题就是 ...
- pager-taglib 使用说明2
传两个值进去:1.pm.totles总记录数 2.pagesize 每页显示页数 3.<pg:param name="parentId"/>传给后台的变量值对(查询条件 ...