设计模式--代理模式(proxy)
一 、什么是代理
- 什么是代理模式?
日常生活中我们经常会碰到代理模式,例如我们找房产中介帮我们介绍房子,找婚姻中介帮我们介绍对象,找保洁帮我们打理房间,找律师帮我们进行诉讼等。我们在无形中运用到了代理模式,却不知道它的存在。- 为什么要使用代理?
运用代理可以使我们的生活更加便利,有了代理,我们不需要自己去找房子,不需要自己去找对象,不需要自己去打理房间,不需要自己去诉讼。当然,你也可以选择一切都自己来干,但是存在前提条件,一是你是否都具备这样的资源和能力来做这些事情,二是你是否愿意花费这么多精力和时间来做这些事情。总之,代理模式使我们各专其事,我们可以将时间浪费在美好的事情上,而不用天天被一些琐事所羁绊。- 代理模式有哪些实现?
Java中的代理有静态代理和动态代理,下面我会分别用一个简单的例子来介绍一下静态代理和动态代理代码实现。
二、静态代理
步骤
1、创建目标对象方法接口
public interface UserDao {
void save();
}
2、实现接口
public class UserDaoImpl implements UserDao{
@Override
public void save() {
System.out.println("保存用户信息");
}
}
3、创建代理对象
public class TransactionHandler implements UserDao {
//传入被代理的目标对象
private UserDaoImpl user;
//代理类初始化的时候生成代理对象
public TransactionHandler(UserDaoImpl user) {
this.user = user;
}
@Override
public void save() {
System.out.println("开启事务");
user.save();
System.out.println("关闭事务");
}
}
4、测试
public class Main {
public static void main(String[] args) {
//创建目标对象
UserDaoImpl userDaoImpl = new UserDaoImpl();
//创建代理对象
UserDao transactionHandler = new TransactionHandler(userDaoImpl);
transactionHandler.save();
}
}
可以看到静态代理通过传入目标对象进行代理,这样代理对象和目标对象高度耦合。(耦合是我们最讨厌的。。。),所以我们不怎么使用静态代理,而是使用动态代理。
三、动态代理
动态代理主要有两种。JDK动态代理(对象必须实现接口,底层使用反射技术生成class文件)和CGLIB动态代理(在类上进行代理,底层使用asm技术)。
3.1 JDK动态代理
接下来通过一个例子。然后看看JDK动态代理它是怎么实现的。
3.1.1 例子说明
还是上面那个例子的改造方式。
1、2还是用上面的例子
3、代理对象
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.nio.file.ReadOnlyFileSystemException;
public class TransactionHandlerDynamic implements InvocationHandler {
//传入的是一个Object对象,可以为任意对象进行事务控制
private Object target;
public TransactionHandlerDynamic(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//调用方法前的处理
System.out.println("开启事务控制");
//调用方法
Object result = method.invoke(target, args);
//调用方法后的处理
System.out.println("关闭事务控制");
return result;
}
}
4、测试
import java.lang.reflect.Proxy;
import org.junit.Test;
public class DynamicTest {
@Test
public void test(){
//新建目标对象
UserDao userDaoImpl = new UserDaoImpl();
//新建事务控制
TransactionHandlerDynamic handle = new TransactionHandlerDynamic(userDaoImpl);
//生成代理类并使用接口对其引用
UserDao userDao = (UserDao) Proxy.newProxyInstance(userDaoImpl.getClass().getClassLoader(), userDaoImpl.getClass().getInterfaces(), handle);
userDao.save();
}
}
输出结果一样。但我们可以通过代理对象和测试方法进行比较可以发现。动态代理可以传入一个Object对象,表示可以对任意的对象进行代理。而不依赖于被代理的对象。而要代理哪个对象则转化为实现过程传入。这样当我们要再代理一个类的时候只需要生成新的代理类即可。
3.1.2 内部执行过程
从上面的例子中我们可以看到,在实现的时候是使用这样一行代码
UserDao userDao = (UserDao) Proxy.newProxyInstance(userDaoImpl.getClass().getClassLoader(), userDaoImpl.getClass().getInterfaces(), handle);
这里使用的是Proxy调用newProxyInstance方法来生成一个代理类。这个方法有三个参数:userDaoImpl.getClass().getClassLoader()
目标对象类加载器、userDaoImpl.getClass().getInterfaces()
目标对象实现接口、 handle
InvocationHandler实例。
那么内部是怎么样生成这个代理类呢,让我们从这个方法出发,看个究竟。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) throws IllegalArgumentException {
//验证传入的InvocationHandler不能为空
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);
}
//获取参数类型是InvocationHandler.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;
}
});
}
//传入InvocationHandler实例去构造一个代理类的实例
//所有代理类都继承自Proxy, 因此这里会调用Proxy的构造器将InvocationHandler引用传入
return cons.newInstance(new Object[]{h});
} catch (Exception e) {
//为了节省篇幅, 笔者统一用Exception捕获了所有异常
throw new InternalError(e.toString(), e);
}
}
这个方法上面有一段说明
Returns an instance of a proxy class for the specified interfaces
* that dispatches method invocations to the specified invocation
* handler.
大概就是“返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。”,那么这个方法是怎么样生成一个代理类呢。(这里不细究态具有源码,想了解细节可以查看我的参考博客,这里只介绍大概的一个流程)
1)验证传入的InvocationHandler不能为空,就是你必须执行这个事务控制。
2)然后进行一些安全权限的效验
3)然后到缓冲中获取是否存在这个代理类,如果存在则直接返回,不存在才创建 getProxyClass0(loader, intfs),我们可以进入到这个方法中去看一下它是怎么实现的
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// If the proxy class defined by the given loader implementing
// the given interfaces exists, this will simply return the cached copy;
// otherwise, it will create the proxy class via the ProxyClassFactory
return proxyClassCache.get(loader, interfaces);//重点是这个,从缓存中获取
}
我们从getProxyClass0(loader, intfs)
这个方法中可以看到关键的步骤是return proxyClassCache.get(loader, interfaces);
,那么我们再进入这个对象
/**
* a cache of proxy classes
*/
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
可以看到这个对象是由new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
是WeakCache创建的。那么这个到底是什么东东呢。下面我们来看看这个缓存机制大概原理
< 3.1 >WeakCache缓存机制
final class WeakCache<K, P, V> {
interface BiFunction<T, U, R> {
//Reference引用队列
private final ReferenceQueue<K> refQueue = new ReferenceQueue<>();
//缓存的底层实现, key为一级缓存, value为二级缓存。 为了支持null, map的key类型设置为Object
private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>>
map = new ConcurrentHashMap<>();
//reverseMap记录了所有代理类生成器是否可用, 这是为了实现缓存的过期机制
private final ConcurrentMap<Supplier<V>, Boolean> reverseMap = new ConcurrentHashMap<>();
//生成二级缓存key的工厂, 这里传入的是KeyFactory
private final BiFunction<K, P, ?> subKeyFactory;
//生成二级缓存value的工厂, 这里传入的是ProxyClassFactory
private final BiFunction<K, P, V> valueFactory;
//构造器, 传入生成二级缓存key的工厂和生成二级缓存value的工厂
public WeakCache(BiFunction<K, P, ?> subKeyFactory, BiFunction<K, P, V> valueFactory) {
this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
this.valueFactory = Objects.requireNonNull(valueFactory);
}
......
......
}
我们大概可以了解到这个WeakMap底层是用一个ConcurrentHashMap来存储。key为第一级缓存,而Value又是一个ConcurrentHashMap来作为二级缓存
接下来我们来看一下我们获取缓存的实现方式:get()方法
public V get(K key, P parameter) {
//这里要求实现的接口不能为空
Objects.requireNonNull(parameter);
//清除过期的缓存
expungeStaleEntries();
//将ClassLoader包装成CacheKey, 作为一级缓存的key
Object cacheKey = CacheKey.valueOf(key, refQueue);
//获取得到二级缓存
ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
//如果根据ClassLoader没有获取到对应的值
if (valuesMap == null) {
//以CAS方式放入, 如果不存在则放入,否则返回原先的值
ConcurrentMap<Object, Supplier<V>> oldValuesMap = map.putIfAbsent(cacheKey,
valuesMap = new ConcurrentHashMap<>());
//如果oldValuesMap有值, 说明放入失败
if (oldValuesMap != null) {
valuesMap = oldValuesMap;
}
}
//根据代理类实现的接口数组来生成二级缓存key, 分为key0, key1, key2, keyx
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
//这里通过subKey获取到二级缓存的值
Supplier<V> supplier = valuesMap.get(subKey);
Factory factory = null;
//这个循环提供了轮询机制, 如果条件为假就继续重试直到条件为真为止
while (true) {
//如果通过subKey取出来的值不为空
if (supplier != null) {
//在这里supplier可能是一个Factory也可能会是一个CacheValue
//在这里不作判断, 而是在Supplier实现类的get方法里面进行验证
V value = supplier.get();
if (value != null) {
return value;
}
}
if (factory == null) {
//新建一个Factory实例作为subKey对应的值
factory = new Factory(key, parameter, subKey, valuesMap);
}
if (supplier == null) {
//到这里表明subKey没有对应的值, 就将factory作为subKey的值放入
supplier = valuesMap.putIfAbsent(subKey, factory);
if (supplier == null) {
//到这里表明成功将factory放入缓存
supplier = factory;
}
//否则, 可能期间有其他线程修改了值, 那么就不再继续给subKey赋值, 而是取出来直接用
} else {
//期间可能其他线程修改了值, 那么就将原先的值替换
if (valuesMap.replace(subKey, supplier, factory)) {
//成功将factory替换成新的值
supplier = factory;
} else {
//替换失败, 继续使用原先的值
supplier = valuesMap.get(subKey);
}
}
}
}
可以看到大概的思路就是将ClassLoader包装成CacheKey, 作为一级缓存的key。然后查找二级缓存。如果缓存中不存在,则采用CAS方式将其替换。如果存在旧值,则放入失败。二级缓存的值是一个Factory实例,最终代理类的值是通过Factory这个工厂来获得的。
4)我们再看看Factory这个内部工厂类,可以看到它的get方法是使用synchronized关键字进行了同步。进行get方法后首先会去验证subKey对应的suppiler是否是工厂本身,如果不是就返回null,而WeakCache的get方法会继续进行重试。如果确实是工厂本身,那么就会委托ProxyClassFactory生成代理类,ProxyClassFactory是在构造WeakCache的时候传入的。所以这里解释了为什么最后会调用到Proxy的ProxyClassFactory这个内部工厂来生成代理类。生成代理类后使用弱引用进行包装并放入reverseMap中,最后会返回原装的代理类。
private final class Factory implements Supplier<V> {
//一级缓存key, 根据ClassLoader生成
private final K key;
//代理类实现的接口数组
private final P parameter;
//二级缓存key, 根据接口数组生成
private final Object subKey;
//二级缓存
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() {
//这里再一次去二级缓存里面获取Supplier, 用来验证是否是Factory本身
Supplier<V> supplier = valuesMap.get(subKey);
if (supplier != this) {
//在这里验证supplier是否是Factory实例本身, 如果不则返回null让调用者继续轮询重试
//期间supplier可能替换成了CacheValue, 或者由于生成代理类失败被从二级缓存中移除了
return null;
}
V value = null;
try {
//委托valueFactory去生成代理类, 这里会通过传入的ProxyClassFactory去生成代理类
value = Objects.requireNonNull(valueFactory.apply(key, parameter));
} finally {
//如果生成代理类失败, 就将这个二级缓存删除
if (value == null) {
valuesMap.remove(subKey, this);
}
}
//只有value的值不为空才能到达这里
assert value != null;
//使用弱引用包装生成的代理类
CacheValue<V> cacheValue = new CacheValue<>(value);
//将包装后的cacheValue放入二级缓存中, 这个操作必须成功, 否则就报错
if (valuesMap.replace(subKey, this, cacheValue)) {
//将cacheValue成功放入二级缓存后, 再对它进行标记
reverseMap.put(cacheValue, Boolean.TRUE);
} else {
throw new AssertionError("Should not reach here");
}
//最后返回没有被弱引用包装的代理类
return value;
}
}
5)通过前面几步的分析我们知道代理类是通过Proxy类的ProxyClassFactory工厂生成的,这个工厂是在生成ProxyClassCache时传入的:proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
,这个工厂类会去调用ProxyGenerator类的generateProxyClass()方法来生成代理类的字节码。ProxyGenerator这个类存放在sun.misc包下,我们可以通过OpenJDK源码来找到这个类,该类的generateProxyClass()静态方法的核心内容就是去调用generateClassFile()实例方法来生成Class文件。我们直接来看generateClassFile()这个方法内部做了些什么。
我们来看看是怎么样生成Class文件的
private byte[] generateClassFile() {
//第一步, 将所有的方法组装成ProxyMethod对象
//首先为代理类生成toString, hashCode, equals等代理方法
addProxyMethod(hashCodeMethod, Object.class);
addProxyMethod(equalsMethod, Object.class);
addProxyMethod(toStringMethod, Object.class);
//遍历每一个接口的每一个方法, 并且为其生成ProxyMethod对象
for (int i = 0; i < interfaces.length; i++) {
Method[] methods = interfaces[i].getMethods();
for (int j = 0; j < methods.length; j++) {
addProxyMethod(methods[j], interfaces[i]);
}
}
//对于具有相同签名的代理方法, 检验方法的返回值是否兼容
for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
checkReturnTypes(sigmethods);
}
//第二步, 组装要生成的class文件的所有的字段信息和方法信息
try {
//添加构造器方法
methods.add(generateConstructor());
//遍历缓存中的代理方法
for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
for (ProxyMethod pm : sigmethods) {
//添加代理类的静态字段, 例如:private static Method m1;
fields.add(new FieldInfo(pm.methodFieldName,
"Ljava/lang/reflect/Method;", ACC_PRIVATE | ACC_STATIC));
//添加代理类的代理方法
methods.add(pm.generateMethod());
}
}
//添加代理类的静态字段初始化方法
methods.add(generateStaticInitializer());
} catch (IOException e) {
throw new InternalError("unexpected I/O Exception");
}
//验证方法和字段集合不能大于65535
if (methods.size() > 65535) {
throw new IllegalArgumentException("method limit exceeded");
}
if (fields.size() > 65535) {
throw new IllegalArgumentException("field limit exceeded");
}
//第三步, 写入最终的class文件
//验证常量池中存在代理类的全限定名
cp.getClass(dotToSlash(className));
//验证常量池中存在代理类父类的全限定名, 父类名为:"java/lang/reflect/Proxy"
cp.getClass(superclassName);
//验证常量池存在代理类接口的全限定名
for (int i = 0; i < interfaces.length; i++) {
cp.getClass(dotToSlash(interfaces[i].getName()));
}
//接下来要开始写入文件了,设置常量池只读
cp.setReadOnly();
ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream dout = new DataOutputStream(bout);
try {
//1.写入魔数
dout.writeInt(0xCAFEBABE);
//2.写入次版本号
dout.writeShort(CLASSFILE_MINOR_VERSION);
//3.写入主版本号
dout.writeShort(CLASSFILE_MAJOR_VERSION);
//4.写入常量池
cp.write(dout);
//5.写入访问修饰符
dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER);
//6.写入类索引
dout.writeShort(cp.getClass(dotToSlash(className)));
//7.写入父类索引, 生成的代理类都继承自Proxy
dout.writeShort(cp.getClass(superclassName));
//8.写入接口计数值
dout.writeShort(interfaces.length);
//9.写入接口集合
for (int i = 0; i < interfaces.length; i++) {
dout.writeShort(cp.getClass(dotToSlash(interfaces[i].getName())));
}
//10.写入字段计数值
dout.writeShort(fields.size());
//11.写入字段集合
for (FieldInfo f : fields) {
f.write(dout);
}
//12.写入方法计数值
dout.writeShort(methods.size());
//13.写入方法集合
for (MethodInfo m : methods) {
m.write(dout);
}
//14.写入属性计数值, 代理类class文件没有属性所以为0
dout.writeShort(0);
} catch (IOException e) {
throw new InternalError("unexpected I/O Exception");
}
//转换成二进制数组输出
return bout.toByteArray();
}
主要的步骤大概可以总结三步:
第一步:收集所有要生成的代理方法,将其包装成ProxyMethod对象并注册到Map集合中。
第二步:收集所有要为Class文件生成的字段信息和方法信息。
第三步:完成了上面的工作后,开始组装Class文件。
我们知道一个类的核心部分就是它的字段和方法。我们重点聚焦第二步,看看它为代理类生成了哪些字段和方法。在第二步中,按顺序做了下面四件事。
1.为代理类生成一个带参构造器,传入InvocationHandler实例的引用并调用父类的带参构造器。
2.遍历代理方法Map集合,为每个代理方法生成对应的Method类型静态域,并将其添加到fields集合中。
3.遍历代理方法Map集合,为每个代理方法生成对应的MethodInfo对象,并将其添加到methods集合中。
4.为代理类生成静态初始化方法,该静态初始化方法主要是将每个代理方法的引用赋值给对应的静态字段。
通过以上分析,我们可以大致知道JDK动态代理最终会为我们生成如下结构的代理类:
public class Proxy0 extends Proxy implements UserDao {
//第一步, 生成构造器
protected Proxy0(InvocationHandler h) {
super(h);
}
//第二步, 生成静态域
private static Method m1; //hashCode方法
private static Method m2; //equals方法
private static Method m3; //toString方法
private static Method m4; //...
//第三步, 生成代理方法
@Override
public int hashCode() {
try {
return (int) h.invoke(this, m1, null);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
@Override
public boolean equals(Object obj) {
try {
Object[] args = new Object[] {obj};
return (boolean) h.invoke(this, m2, args);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
@Override
public String toString() {
try {
return (String) h.invoke(this, m3, null);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
@Override
public void save(User user) {
try {
//构造参数数组, 如果有多个参数往后面添加就行了
Object[] args = new Object[] {user};
h.invoke(this, m4, args);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
//第四步, 生成静态初始化方法
static {
try {
Class c1 = Class.forName(Object.class.getName());
Class c2 = Class.forName(UserDao.class.getName());
m1 = c1.getMethod("hashCode", null);
m2 = c1.getMethod("equals", new Class[]{Object.class});
m3 = c1.getMethod("toString", null);
m4 = c2.getMethod("save", new Class[]{User.class});
//...
} catch (Exception e) {
e.printStackTrace();
}
}
}
至此,我们可以看到代理类的整体生成过程就是上面这样的一个步骤:
1、检查二级缓存中有没有代理类,有就返回
2、没有,则使用ProxyClassFactory工厂生成
3、这个工厂会调用ProxyGenerator来生成class文件
4、根据需要收集信息,按class文件组装。
5、生成二进制文件返回
3.2 CGLIB代理
从上一节中我们看到JDK代理是对实现了接口的类进行代理的,也就是说JDK代理只能代理实现接口的类,当一个类没有实现接口的时候就无法代理,那么这时候就可以使用CGLIB代理了,CGLIB代理的原理是对指定目标类生成一个子类,并覆盖其中方法进行增强,该方法采用的是继承的方式进行的,所有不能对fInal修饰的类进行代理。而CGLIB底层使用的是ASM字节码生成框架
ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。
本来想看一下源码,结果发现没有源码asm-3.2.rar,只有.class文件。。。。。
参考链接:
1、https://www.cnblogs.com/liuyun1995/p/8157098.html JDK动态代理的底层实现之Proxy源码分析
2、https://www.cnblogs.com/liuyun1995/p/8144676.html WeakCache缓存的实现机制
3、https://www.cnblogs.com/liuyun1995/p/8144706.html ProxyGenerator生成代理类的字节码文件解析
4、https://blog.csdn.net/zhushuai1221/article/details/52169218 什么是ASM