动态代理、静态代理优缺点
优点:业务类只需要关注业务逻辑本身,保证了业务类的重用性。这是代理的共有优点。动态代理只有在用到被代理对象的时候才会对被代理类进行类加载。 而静态代理在编译器就已经开始占内存了。。
缺点:
1)代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
2)如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
弱引用
被弱引用关联的对象只能生存到下一次垃圾收集发生之前。 当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。 在JDK 1.2之后,提供了WeakReference类来实现弱引用。jdk动态代理使用WeakCache
类 对代理类对象进行缓存, 代理类的引用就是采用的虚引用key 继承自WeakReference
。
借助JDK实现一个动态代理
JDK动态代理步骤
步骤一、编写代理接口
package com.cbam.demo.dynamicProxy;
/**
* CopyRright (c)2014-2016 Haerbin Hearglobal Co.,Ltd
* Project: demo
* Comments:
* Author:cbam
* Create Date:2017/3/29
* Modified By:
* Modified Date:
* Modified Reason:
*/
public interface HelloService {
void sayHello(String name);
}
步骤二、编写接口实现类
package com.cbam.demo.dynamicProxy;
/**
* CopyRright (c)2014-2016 Haerbin Hearglobal Co.,Ltd
* Project: demo
* Comments:
* Author:cbam
* Create Date:2017/3/29
* Modified By:
* Modified Date:
* Modified Reason:
*/
public class HelloServiceImpl implements HelloService {
@Override
public void sayHello(String name) {
System.out.println("Hello " + name);
}
}
步骤二、编写接口实现类
package com.cbam.demo.dynamicProxy;
/**
* CopyRright (c)2014-2016 Haerbin Hearglobal Co.,Ltd
* Project: demo
* Comments:
* Author:cbam
* Create Date:2017/3/29
* Modified By:
* Modified Date:
* Modified Reason:
*/
public class HelloServiceImpl implements HelloService {
@Override
public void sayHello(String name) {
System.out.println("Hello " + name);
}
}
步骤三、编写InvocationHandler 实现类
package com.cbam.demo.dynamicProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* CopyRright (c)2014-2016 Haerbin Hearglobal Co.,Ltd
* Project: demo
* Comments:
* Author:cbam
* Create Date:2017/3/29
* Modified By:
* Modified Date:
* Modified Reason:
*/
public class ProxyInvocationHandler implements InvocationHandler {
//要代理的对象
private Object target;
public ProxyInvocationHandler(Object target) {
this.target = target;
}
public Object getProxy() {
return Proxy.newProxyInstance(Thread.currentThread()
.getContextClassLoader(), target.getClass().getInterfaces(),
this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("----- before -----");
Object rtn = method.invoke(target, args);
System.out.println("----- after -----");
return rtn;
}
}
步骤四、编写InvocationHandler 实现类
package com.cbam.demo.dynamicProxy;
import sun.misc.ProxyGenerator;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* CopyRright (c)2014-2016 Haerbin Hearglobal Co.,Ltd
* Project: demo
* Comments:
* Author:cbam
* Create Date:2017/3/29
* Modified By:
* Modified Date:
* Modified Reason:
*/
public class Main {
public static void main(String[] args) {
HelloService helloService = new HelloServiceImpl();
ProxyInvocationHandler proxyInovationHandler = new ProxyInvocationHandler(helloService);
HelloService proxy = (HelloService) proxyInovationHandler.getProxy();
proxy.sayHello("梁舒");
}
}
输出:
----- before -----
Hello 梁舒
----- after -----
前面代码示例展示了jdk动态代理怎么玩儿。 下面我们来分析一下, jdk底层到底搞了啥就生成了代理类:
首先我们从获取代理类的方法中的Proxy.newProxyInstance
public Object getProxy() {
return Proxy.newProxyInstance(Thread.currentThread()
.getContextClassLoader(), target.getClass().getInterfaces(),
this);
}
点进去, 可以在Proxy
中看到该静态方法的签名:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
该方法的注释:
Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation handler.返回一个特定接口的代理类的实例, 代理类对象可以分发method调用到专门的调用处理器。
待会儿就能理解这句话的含义了。根据javaDoc 得出:
第一个参数为一个类加载器, 就是指定一个类加载器来加载所生成的代理类的字节码而已。
第二个参数为代理类要去实现的接口
第三个参数就是我们所定义的invocation handler
我们毕竟刚才传递的是this
嘛
再看整个方法里面的核心逻辑(删除部分校验逻辑):
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
//接口克隆了一份
final Class<?>[] intfs = interfaces.clone();
/*
* Look up or generate the designated proxy class.
* 查询或着生成指定的代理类, 之所以查询是因为, 之前曾经生成
* 的代理类可能被缓存了。 在这里只是生成了代理类class对象
* 而已
*/
Class<?> cl = getProxyClass0(loader, intfs);
//在这里根据class对象拿到代理类构造方法
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;
}
});
}
//这里真正的创建了 一个代理对象的实例,
//我们留一个疑问, 为什么传过去一个包含h的数组???
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
。。。。。
}
我们接下来把getProxyClass0()
方法逻辑拉出来
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
return proxyClassCache.get(loader, interfaces);
}
可以看到Cache字眼了, 如果缓存中存在代理类, 直接从Cache取出, 否则生成并缓存。这里有意思的是如何进行的缓存? 如何判断的代理类是否是同一个?
缓存逻辑
继续看下proxyClassCache.get()
方法逻辑, 这个方法在WeakCache
类中实现。而proxyClassCache
只是Proxy
类的一个WeakCache
类组合对象, 在Proxy
类是这样定义的:
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
观察WeakCache
构造参数, 里面传入了两个工厂实例, 两个工厂均实现了BiFunction
函数接口, KeyFactory
用来生产 缓存 key
,ProxyClassFactory
用来生产字节码 。
看看WeakCache
类用什么来进行的缓存:
可以看到使用两层ConcurrentHashMap来存的:(key, sub-key) -> value}
其中key
和 value
是弱引用, sub-key
是强引用。其中的key
就是我们指定的类加载器。 sub-key
是通过subKeyFactory
工厂方法产生, value
是通过valueFactory
工厂产生, 在上图WeakCache
中都能找到。
怎么获取到key
?
Object cacheKey = CacheKey.valueOf(key, refQueue);
点开valueOf
, 可以看到CacheKey
是WeakCache
的一个实现了WeakReference
的静态内部类, 其中有个静态valueOf
方法来产生 我们上面所说的第一个参数key
, super(key, refQueue);
这句话意味着, 将类加载器作为了弱引用的key
。refQueue
表示弱引用队列(会被清理)。
private static final class CacheKey<K> extends WeakReference<K> {
private static final Object NULL_KEY = new Object();
static <K> Object valueOf(K key, ReferenceQueue<K> refQueue) {
return key == null
? NULL_KEY
: new CacheKey<>(key, refQueue);
}
private final int hash;
private CacheKey(K key, ReferenceQueue<K> refQueue) {
super(key, refQueue);
this.hash = System.identityHashCode(key); // compare by identity
}
怎么拿到的subKey
?
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
看下subKeyFactory.apply()
方法代码, 方法实现还是在Proxy
类的KeyFactory
静态内部工厂中。Key1
类并没有对实例的弱引用
private static final class KeyFactory
implements BiFunction<ClassLoader, Class<?>[], Object>
{
@Override
public Object apply(ClassLoader classLoader, Class<?>[] interfaces) {
switch (interfaces.length) {
case 1: return new Key1(interfaces[0]); // the most frequent
case 2: return new Key2(interfaces[0], interfaces[1]);
case 0: return key0;
default: return new KeyX(interfaces);
}
}
}
怎么拿到的Supplier
? 我们缓存Map
的第二层Map
中的也就是我们的代理类, 存的就是我们下面这个工厂实例, 实现了Supplier
, 其实Factroy
类 就是对我们缓存Map
第二层Map
的一个封装。 只有当我们拿到Supplier
调用里面的 get
方法时才会返回真正的代理类。注意当调用 get
方法首先从缓存的第二层Map
中取, 如果没有
那么调用
value = Objects.requireNonNull(valueFactory.apply(key, parameter));
其中valueFactory 是我们上面说过的java.lang.reflect.Proxy.ProxyClassFactory
这个工厂类来生产字节码的, 这个类的核心逻辑代码是:
/*
* Generate the specified proxy class.
*/
//生成字节码, 并返回在堆中实例完的对象
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
待会我们利用这段代码, 来生成一下我们的字节码文件。
下面就是包装代理类的工厂的代码:
private final class Factory implements Supplier<V> {
private final K key;
private final P parameter;
private final Object subKey;
// 工厂封装的缓存Map的第二个Map
private final ConcurrentMap<Object, Supplier<V>> valuesMap;
Factory(K key, P parameter, Object subKey,
ConcurrentMap<Object, Supplier<V>> valuesMap) {
this.key = key;
this.parameter = parameter;
this.subKey = subKey;
this.valuesMap = valuesMap;
}
@Override
public synchronized V get() { // serialize access
// re-check
Supplier<V> supplier = valuesMap.get(subKey);
if (supplier != this) {
return null;
}
V value = null;
try {
// 这里是缓存中没有找到代理类后所进行的逻辑,上面已经解释
value = Objects.requireNonNull(valueFactory.apply(key, parameter));
} finally {
if (value == null) { // remove us on failure
valuesMap.remove(subKey, this);
}
}
assert value != null;
CacheValue<V> cacheValue = new CacheValue<>(value);
if (valuesMap.replace(subKey, this, cacheValue)) {
// put also in reverseMap
reverseMap.put(cacheValue, Boolean.TRUE);
} else {
throw new AssertionError("Should not reach here");
}
// successfully replaced us with new CacheValue -> return the value
// wrapped by it
return value;
}
}
有了上面的准备, 下面看:proxyClassCache.get()
:
public V get(K key, P parameter) {
Objects.requireNonNull(parameter);
expungeStaleEntries();
// 由上面所说的生成缓存Map的第一个参数key
Object cacheKey = CacheKey.valueOf(key, refQueue);
//懒惰实例化缓存Map的第二个参数, 其中cacheKey作为第一个参数(加载器嘛)
ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);//挺好理解
if (valuesMap == null) {
ConcurrentMap<Object, Supplier<V>> oldValuesMap
= map.putIfAbsent(cacheKey,
valuesMap = new ConcurrentHashMap<>());
if (oldValuesMap != null) {
valuesMap = oldValuesMap;
}
}
//这里拿到subKey(缓存Map中那个subKey) , 来获取可能的 //存储在valuesMap中的Supplier
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
//supplier所指向的实例就是Factroy实例, 包装了第二层Map
Supplier<V> supplier = valuesMap.get(subKey);
Factory factory = null;
while (true) {
if (supplier != null) {
//有代理类缓存就直接返回
V value = supplier.get();
if (value != null) {
return value;
}
}
if (factory == null) {
factory = new Factory(key, parameter, subKey, valuesMap);
}
//这里的扔进去的supplier 实质上是一个实现了supplier的工厂实例
if (supplier == null) {
supplier = valuesMap.putIfAbsent(subKey, factory);
if (supplier == null) {
// successfully installed Factory
supplier = factory;
}
// else retry with winning supplier
} else {
if (valuesMap.replace(subKey, supplier, factory)) {
// successfully replaced
// cleared CacheEntry / unsuccessful Factory
// with our Factory
supplier = factory;
} else {
// retry with current supplier
supplier = valuesMap.get(subKey);
}
}
}
}
到了这里我们捋一捋调用过程:
首先Proxy.newProxyInstance()
-> Class<?> cl = getProxyClass0(loader, intfs);
-> proxyClassCache.get(loader, interfaces)
->
1、 Object cacheKey = CacheKey.valueOf(key, refQueue);
//拿到第一层key
2、Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
// 拿到第二层subKey
3、Supplier<V> supplier = valuesMap.get(subKey);
拿到代理类的包装类。
缓存总结: 缓存逻辑的key
和value
都是工厂方法产生, 能够学习到怎么利用弱引用来写一个缓存管理器。
回过头,再来看我们生成的代理类
利用上面那段生成字节码的代码, 我们也来生成一下, 看看这个代理类长什么样子:
package com.cbam.demo.dynamicProxy;
import sun.misc.ProxyGenerator;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* CopyRright (c)2014-2016 Haerbin Hearglobal Co.,Ltd
* Project: demo
* Comments:
* Author:cbam
* Create Date:2017/3/29
* Modified By:
* Modified Date:
* Modified Reason:
*/
public class Main {
public static void main(String[] args) {
HelloService helloService = new HelloServiceImpl();
ProxyInvocationHandler proxyInovationHandler = new ProxyInvocationHandler(helloService);
HelloService proxy = (HelloService) proxyInovationHandler.getProxy();
proxy.sayHello("梁舒");
String path = "D:/$Proxy0.class";
byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0",
HelloServiceImpl.class.getInterfaces());
FileOutputStream out = null;
try {
out = new FileOutputStream(path);
out.write(classFile);
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
下面给出了反编译$Proxy0.class
之后的代码。代理类继承了Proxy
实现了HelloService
我们的代理接口
我们看看$Proxy0
构造函数? 是不是有感觉? 还记得当初留得疑问么
return cons.newInstance(new Object[]{h});
其中的m1
m2
…..都是我们要代理的那些方法, 可以说我们用代理类调用的方法, 其实都是委托给我们自定义的InvocationHandler
来调用其invoke
方法实现的!而我们每次动态获取代理类其实很可能从缓存中取出来的!
import com.cbam.demo.dynamicProxy.HelloService;
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 HelloService
{
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy0(InvocationHandler paramInvocationHandler)
throws
{
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 void sayHello(String paramString)
throws
{
try
{
this.h.invoke(this, m3, new Object[] { paramString });
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);
}
}
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);
}
}
static
{
try
{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m3 = Class.forName("com.cbam.demo.dynamicProxy.HelloService").getMethod("sayHello", new Class[] { Class.forName("java.lang.String") });
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
}
catch (NoSuchMethodException localNoSuchMethodException)
{
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException)
{
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
}
好了, 上面的疑问,都解释差不多了。。。