在说动态代理时,我们先说说静态代理(java设计模式:代理模式),这就是静态代理。
静态代理:我们对某一个对象进行访问,突然需要判断访问者是否有权限或者访问次数统计、限制、添加日志、缓存等,那么我们可以直接修改代码,但是这样做不太合适,因为 1 这些特殊操作并不是对象的核心功能,2 如果后期取消权限验证,再改代码就比较恶心了。因此我们可以采用代理模式,生成代理对象,在代理对象中进行权限校验。
静态代理模式代码如下:
package dynamic.proxy;
/**简单的代理模式**/
public class SimpleProxyCompsite {
public static void main(String[] args) {
Pepole pep = new PepoleProxy(new User());
pep.printName();
}
}
//需要进行权限校验方法抽象的 集合
interface Pepole{
public void printName();
}
//被代理对象
class User implements Pepole{
//需要 校验的方法
public void printName(){
System.out.println("this is xxx");
}
//不需要校验的方法
public String getName(){
return "1231231";
}
}
//代理对象,需要实现方法集接口
class PepoleProxy implements Pepole{
//代理对象内部持有被代理对象
private Pepole pep;
public PepoleProxy(Pepole pep){
this.pep = pep;
}
//需要特殊处理的方法
public void printName(){
System.out.println("权限校验");
pep.printName();
}
}
通过接口把代理对象的相关方法进行抽取,然后在代理类中对方法进行预处理,处理完再调用被代理对象的相关方法。
通过上述静态代理,发现如果需要对某个对象的某些方法进行代理,就需要写相关的代理对象,抽取代理方法的集合。这样有几个缺陷:1 每一个被代理对象,都需要写一个代理对象;2 每一个需要特殊处理的方法,代理对象也必须有对应的。
动态代理对象
为了解决静态代理的各种缺陷,能否考虑使用一种动态的代理,解决一下问题:
1 每个被代理类对应一个代理类。
2 每个需要特殊处理的方法对应代理类的方法。
上述就是动态代理需要实现。
动态代理的范例
package dynamic.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DynamicProxy {
public static void main(String[] args) {
// 实例化InvocationHandler
PepoleInvocationHander handler = new PepoleInvocationHander(new User());
// 根据目标对象生成代理对象
Pepole p = (Pepole) handler.getProxy();
// 调用代理对象的方法
p.printName();
}
}
//代理类 实现 InvocationHandler,类似于PepolePoxy实现Pepole接口
class PepoleInvocationHander implements InvocationHandler{
//持有被代理对象
private Object target;
public PepoleInvocationHander(Object target){
this.target = target;
}
//被代理类需要特殊处理的方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().startsWith("print")){
System.out.println("执行前处理");
}
//注意invoke方法中,传递的对象是代理对象的实例,即target,而不是proxy
method.invoke(target, args);
if(method.getName().startsWith("print")){
System.out.println("执行后处理");
}
return null;
}
public Object getProxy(){
//生成代理对象,即User对象的代理对象。
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
}
java的动态代理,就是根据被代理类由java生成一个代理类class,省去了我们写代理类,该代理类自动提供方法接口中对应的方法,省去了我们自己写方法的步骤,而方法内部就是调用InvocationHandler的invoke方法。实现动态代理只需要创建一个自有的MyInvocationHandler实现InvocationHandler接口即可,实现方法invoke即可。
Proxy源码查看
**
* loader:类加载器
* interfaces:目标对象实现的接口
* h:InvocationHandler的实现类
*/
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对象,这是Proxy中最重要的方法
Class cl = getProxyClass(loader, interfaces);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
// 调用代理对象的构造方法(也就是$Proxy0(InvocationHandler h))
Constructor cons = cl.getConstructor(constructorParams);
// 生成代理类的实例并把MyInvocationHandler的实例传给它的构造方法
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()方法中
public static Class<?> getProxyClass(ClassLoader loader,
Class<?>... interfaces)
throws IllegalArgumentException
{
// 如果目标类实现的接口数大于65535个则抛出异常
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// 声明代理对象所代表的Class对象(有点拗口),即代理对象的Class对象。
Class proxyClass = null;
String[] interfaceNames = new String[interfaces.length];
Set interfaceSet = new HashSet(); // for detecting duplicates
// 遍历目标类所实现的接口
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");
}
// 中间省略了一些无关紧要的代码 .......
// 把目标类实现的接口代表的Class对象放到Set中
interfaceSet.add(interfaceClass);
interfaceNames[i] = interfaceName;
}
// 把目标类实现的接口名称作为缓存(Map)中的key
Object key = Arrays.asList(interfaceNames);
Map cache;
synchronized (loaderToCache) {
// 从缓存中获取cache
cache = (Map) loaderToCache.get(loader);
if (cache == null) {
// 如果获取不到,则新建地个HashMap实例
cache = new HashMap();
// 把HashMap实例和当前加载器放到缓存中
loaderToCache.put(loader, cache);
}
}
synchronized (cache) {
do {
// 根据接口的名称从缓存中获取对象,即代理对象实现的所有接口作为key,从缓存中获取代理对象的Class对象,如果接口的顺序发生变换,那么代理对象的Class对象就不是同一个。
Object value = cache.get(key);
if (value instanceof Reference) {
proxyClass = (Class) ((Reference) value).get();
}
if (proxyClass != null) {
// 如果代理对象的Class实例已经存在,则直接返回
return proxyClass;
} else if (value == pendingGenerationMarker) {
try {
cache.wait();
} catch (InterruptedException e) {
}
continue;
} else {
cache.put(key, pendingGenerationMarker);
break;
}
} while (true);
}
try {
// 中间省略了一些代码 .......
// 缓存没有命中Class对象,需要生成Class对象,这里就是动态生成代理对象的最关键的地方
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces);
try {
// 根据代理类的字节码生成代理类的实例
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);
}
// 中间省略了一些代码 .......
return proxyClass;
}
进去ProxyGenerator类的静态方法generateProxyClass,这里是真正生成代理类class字节码的地方。
public static byte[] generateProxyClass(final String name,
Class[] interfaces)
{
ProxyGenerator gen = new ProxyGenerator(name, interfaces);
// 这里动态生成代理类的字节码,由于比较复杂就不进去看了
final byte[] classFile = gen.generateClassFile();
// 如果saveGeneratedFiles的值为true,则会把所生成的代理类的字节码保存到硬盘上,因此需要查看代理类的class文件,就可以直接设置saveGeneratedFiles属性为true(System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", true);),那么系统就会把代理对象的class文件写到硬盘中。
if (saveGeneratedFiles) {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
public Void run() {
try {
FileOutputStream file =
new FileOutputStream(dotToSlash(name) + ".class");
file.write(classFile);
file.close();
return null;
} catch (IOException e) {
throw new InternalError(
"I/O exception saving generated file: " + e);
}
}
});
}
// 返回代理类的字节码
return classFile;
}
调用InvocationHandler的invoke方法
invoke方法的调用是在代理类的生成的方法中,看一看代理类。
如何生成代理类的class文件
1 在上述main方法中添加:System.setProperty(“sun.misc.ProxyGenerator.saveGeneratedFiles”, “true”);//告诉系统把代理类的class文件写到磁盘上。
运行main方法后,会提示:java.io.FileNotFoundException: dynamic\proxy$Proxy0.class (系统找不到指定的路径,无法找到代理类所在的dynamic\proxy路径,这其实是被代理对象的报名变成了路径,需要在项目的根目录下新建上述目录。例如我的项目在workspace\CompsiteTest中,那么在目录下建立相关的目录即可。
生成的class文件如下
package dynamic.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
//动态代理生成的class文件,类名直接是$Proxy,后面的数字是自增的,同时继承自Proxy类,实现了代理类的所有接口,这就是jdk动态代理的一个缺陷,被代理类不能继承其他类,因为它必须继承Proxy类。同时被代理类必须实现一个接口
public final class $Proxy0 extends Proxy
implements Pepole
{
private static Method m1;
private static Method m0;
private static Method m3;
private static Method m2;
//构造函数: 看到这儿InvocationHandler就会发现,定义的MyInvocationHandler作为这个的参数
public $Proxy0(InvocationHandler paramInvocationHandler)
throws
{
//调用Poxy的构造方法,把自定义的InvocationHandler设置为父类的handler属性的值。
super(paramInvocationHandler);
}
public final boolean equals(Object paramObject)
throws
{
try
{
return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final int hashCode()
throws
{
try
{
return ((Integer)this.h.invoke(this, m0, null)).intValue();
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
//需要特殊处理的方法 printName
public final void printName()
throws
{
try
{
//这儿就是调用 invoke方法,通过InvocationHandler对象h,调用invoke,之所以传入Method属性,是因为通过动态代理生成的对象,并不持有被代理对象的实例,因此只能通过Method进行调用,同时传入Method属性,也可以让MyInvocationHandler对方法进行特殊处理,例如:spring中,只对save开头的方法开启事务。
this.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)this.h.invoke(this, m2, null);
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
//静态代码块,初始化
static
{
try
{
//从m1就能看出代理类的equals方法是调用的Object对象的equals方法。同样hashCode,soString同样都是调用Object的方法。
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
//通过Class.forName获取Class对象,反射获取printName方法。
m3 = Class.forName("dynamic.proxy.Pepole").getMethod("printName", 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());
}
}
}
jdk动态代理总结
1 被代理对象不能有继承父类,因为如果被代理对象有父类,那么生成的代理对象也需要有父类,但是代理对象必须有父类Proxy,因此被代理对象不能有父类。
2 被代理对象必须实现至少一个接口。
3 被代理对象生成的class文件与被代理对象是在同一个位置(包名相同),当然如果被代理对象的接口有私有的,那么生成的代理对象就会在私有接口同位置。
4 被代理对象的命名是固定格式:$ProxyNum,Num是自增的,例如:$Proxy0,$Proxy1
参考文献:
1 https://www.zhihu.com/question/20794107 主要是讨论为什么要变静态代理为动态代理。
2 http://rejoy.iteye.com/blog/1627405 动态代理的实现源码,本文内容也是参考该文章,虽然文章内容差不多,但是也有一些自己的体验,同时打字的过程也是加深记忆的过程。
3 http://www.cnblogs.com/ctgulong/p/5011614.html 比较好的有jdk动态代理和cglib代理,同时提出几种本地生成代理类class文件
4 http://www.cnblogs.com/hujunzheng/p/5134478.html?utm_source=tuicool&utm_medium=referral 介绍了3中动态代理的方式:jdk动态代理,动态字节码生成(cglib)、javassist生成动态代理
5 http://blog.csdn.net/sadfishsc/article/details/9999169 JAVAssis学习的推荐文章。