一.前言
通过前几篇博客的学习和总结,让我们对反射、泛型和注解有了一定的认,今天我们将继续学习反射与JDK动态代理。
二.代理模式
(1).静态代理
在程序设计中,很多业务组件都需要实现相同的功能,比如事务管理,安全管理,日志管理等。这些相同的功能我们可以单独的抽取成一个类,那我们如何去组合业务类和功能类呢。第一种我们使用对象组合的模式,可以在每个业务类中保存功能类对象,在需要的时候调用功能类的方法,但这样会增强业务类和功能类的耦合性,也违反了设计类的单一职责原则。第二种我们可以引入一个业务类的代理类,这个代理类的方法和业务类中的方法完全相同,在代理类中保存被代理类的对象,在相应的方法中调用被代理类的方法,并且可以在方法调用前后做相应的事务,日志等操作。
在代理模式中为了使代理类具有被代理类的所有功能接口,代理类和被代理类要实现相同的接口,并且代理类中保存有被代理类的实例。
//定义接口
public interface IPay {
void getId();
double deposit(double money);
void getMoney(double money);
}
这里是被代理类,也就是业务类
public class CardPay implements IPay{
@Override
public double deposit(double money) {
// TODO Auto-generated method stub
System.out.println("存款"+money);
return 6930;
}
@Override
public void getMoney(double money) {
// TODO Auto-generated method stub
System.out.println("支出"+money);
}
@Override
public void getId() {
// TODO Auto-generated method stub
System.out.println("获取Id");
}
}
这里是代理 类
public class CardPayProxy implements IPay {
private IPay mCardPay;
public CardPayProxy(IPay iPay) {
// TODO Auto-generated constructor stub
this.mCardPay=iPay;
}
@Override
public double deposit(double money) {
// TODO Auto-generated method stub
System.out.println("用户验证");
mCardPay.deposit(money);
System.out.println("存款更新");
return 6930;
}
@Override
public void getMoney(double money) {
// TODO Auto-generated method stub
System.out.println("用户验证");
mCardPay.getMoney(money);
System.out.println("存款更新");
}
}
这就是静态代理,通过对象组合和功能复制实现功能的加强。静态代理的弊端是需要为每一个业务类创建对应的代理类,并且当接口中增加方法时,不但业务类中需要更改,代理类中也需要更改。
动态代理可以在运行时根据被代理类和业务接口动态的生成字节码并且可以创建业务类的代理类,我们来看一下如何实现JDK动态代理。
(2).JDK动态代理
public class PayInvocationHandler implements InvocationHandler{
private Object mTarget;
public PayInvocationHandler(Object target) {
// TODO Auto-generated constructor stub
this.mTarget=target;
}
//调用代理类的所有方法都会调用到这个方法
//method 是被代理对象中方法的Method实例
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
System.out.println("代理类的名称"+proxy.getClass().getCanonicalName());
System.out.println("用户验证");
//添加两个通用操作
System.out.println("方法名称:"+method);
System.out.println("存款更新");
System.out.println(Arrays.toString(args));
System.out.println("---------------------------------------------------");
Class<CardPay> cardPay=CardPay.class;
Method method2=cardPay.getDeclaredMethod("deposit", double.class);
System.out.println("被代理对象"+method2);
System.out.println("Method是否属于被代理对象"+(method2==method));
System.out.println("---------------------------------------------------");
Class<IPay> class1=IPay.class;
Method method3=class1.getDeclaredMethod("deposit", double.class);
System.out.println("被代理对象的接口"+method3);
System.out.println("Method是否属于被代理对象的接口"+(method3==method));
System.out.println("-----------------------------------------------");
Class class2=proxy.getClass();
Method method4=class2.getDeclaredMethod("deposit", double.class);
System.out.println("代理对象"+method4);
System.out.println("Method对象是否属于代理对象"+(method4==method));
return null;
}
}
PayInvocationHandler payInvocationHandler=new PayInvocationHandler(cardPay);
// //目标对象的类加载器 目标对象实现的接口
IPay iPay= (IPay) Proxy.newProxyInstance(cardPay.getClass().getClassLoader(), cardPay.getClass().getInterfaces(), payInvocationHandler);
iPay.deposit(245.9);
上面的这个类实现了InvacationHandler接口,这个接口中只有一个方法, Object invoke(Object proxy, Method method, Object[] args),这是一个回调接口,当代理类的任何方法被调用时,都会调用到这个方法。在测试代码中我们只需要调用Proxy类的静态方法newProxyInstance方法,然后返回代理对象。
存款1000.0
支出9000.0
代理类的名称com.sunjinxi.Impl.$Proxy0
用户验证
方法名称:public abstract double com.sunjinxi.Impl.IPay.deposit(double)
存款更新
[245.9]
---------------------------------------------------
被代理对象public double com.sunjinxi.Impl.CardPay.deposit(double)
Method是否属于被代理对象false
---------------------------------------------------
被代理对象的接口public abstract double com.sunjinxi.Impl.IPay.deposit(double)
Method是否属于被代理对象的接口false
-----------------------------------------------
分析打印结果:
(1). Object invoke(Object proxy, Method method, Object[] args),该方法中的参数args便是我们调用deposit(245.9)方法时传入的参数对象。
(2).proxy正是我们生成的代理对象。
(3).通过打印结果发现Method对象不是代理类字节码中的对象,也不是被代理类字节码中的对象,虽然从名称上这个对象和我们接口字节码中的Method方法名称相同,但是他们内存地址不同,也不是一个对象。Method对象可能是从接口字节码中的Methhod对象中克隆过来的对象。
(3).JDK动态代理源码查看
我们将通过查看Proxy的源码更深入的了解动态代理类的实现过程,并做出推测。
首先是入口方法
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
//如果InvocationHandler对象为空,则抛出异常
if (h == null) {
throw new NullPointerException();
}
//生成代理类之前,做安全检查
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, interfaces);
}
//获取代理类的字节码
Class<?> cl = getProxyClass0(loader, interfaces);
//从字节码中获取指定构造器,该构造器的参数为InvacationHandler对象,说明代理类中有InvacationHandler的实例
try {
final Constructor<?> cons = cl.getConstructor(constructorParams);
//调用构造器的newInstance方法初始化对象,参数是我们传入的InvacationHandler对象
final InvocationHandler ih = h;
if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl){
return AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
return newInstance(cons, ih);
}
});
} else {
return newInstance(cons, ih);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString());
}
}
在这段代码中,首先判断我们传入的InvocationHandler对象是否为null,为null则抛出异常,然后调用getProxyClass0(loader, interfaces)方法获取代理类的字节码对象,然后用该获取这个字节码对象的构造器,然后初始化该对象。
接下来看getProxyClass0方法
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
//接口数量大于65535则抛出非法参数异常
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
Class<?> proxyClass = null;
//这个字符串数组存储的是所有接口的名称
String[] interfaceNames = new String[interfaces.length];
//用来取出重复
Set<Class<?>> interfaceSet = new HashSet<>();
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<String> key = Arrays.asList(interfaceNames);
//从缓存中查找,key为ClassLoader对象,也就是一个被代理类对应的是一个缓存对象,缓存对象为一个Map集合key为接口名称集合,value为代理对象
Map<List<String>, Object> cache;
synchronized (loaderToCache) {
cache = loaderToCache.get(loader);
if (cache == null) {
cache = new HashMap<>();
loaderToCache.put(loader, cache);
}
}
//大意是从缓存中查找代理对象,有的话直接返回,如果为
// pendingGenerationMarker,他代表有代理对象正在被生成,这样的话
//当前线程开始等待,知道被唤醒,如果缓存中即没有,代表当前接口的代理对象//也没有在被生成则cache.put();方法,用来为其他线程标记正在生成当前接口的代理对象,这样做应该是为了保证同一组接口生成的代理对象是唯一的
synchronized (cache) {
do {
Object value = cache.get(key);
if (value instanceof Reference) {
proxyClass = (Class<?>) ((Reference) value).get();
}
if (proxyClass != null) {
// 代理已生成,返回
return proxyClass;
} else if (value == pendingGenerationMarker) {
// 代理正在生成,确保唯一,等待
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
//这段代码是为了保证接口中如果有两个非public接口,则他们必须在同一包下,但是如果代理类实现的接口是非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");
}
}
}
//来到这里说明,我们实现的全部是public接口,则包名为
//com.sun.proxy
if (proxyPkg == null) {
// if no non-public proxy interfaces, use com.sun.proxy package
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
{
/*
* Choose a name for the proxy class to generate.
*/
long num;
synchronized (nextUniqueNumberLock) {
num = nextUniqueNumber++;
}
//公共接口:类名为com.sun.proxy$Proxy+num
//num为创建的代理的数量,这里是以类加载器为标志的
String proxyName = proxyPkg + proxyClassNamePrefix + num;
//生成字节码
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces);
try {
//是一个natice方法,解析二进制,生成字节码对象
proxyClass = defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
//添加到map中
proxyClasses.put(proxyClass, null);
} finally {
//最终要放入缓存
synchronized (cache) {
if (proxyClass != null) {
cache.put(key, new WeakReference<Class<?>>(proxyClass));
} else {
cache.remove(key);
}
//生成代理的工作完成,唤醒其他可能在等待的线程
cache.notifyAll();
}
}
return proxyClass;
}
到这里生成代理的工作就完成了,重点地方我们也做了相应的注释,现在我们来梳理一下这个过程。
1.检查接口数量
2.检查类加载器是否与接口对应,字节码对象代表的类是否是接口类型,是否有重复的接口。
3.从Map<ClassLoader, Map<List<String>, Object>> loaderToCache中根据传来的类加载器取出缓存,如果没有则创建空缓存。
4.从缓存对象中根据key即接口集合取缓存,如果不为空则直接返回。如果为pendingGenerationMarker则表明有另一条线程正在生成同样的接口对应的代理类,则当前线程等待,否则把pendingGenerationMarker对象添加到缓存,表明自己正在创建代理,
从而保证了同一组接口,同一个类加载器对应的代理类对象唯一。
5.确定代理类的名称,没有非public接口则为com.sun.proxy$Proxy+num,有的话则把前缀替换为该接口所在包名。
6.调用generateProxyClass方法生成二进制字节码文件
7.调用defineClass0方法解析二进制生成代理Class对象,该方法是一个native方法,说明这个工作是在底层通过C/C++来实现的。
8.放入缓存。
到这里代理对象就已经被生成了,我们还要对第五点进行验证。
CardPay cardPay=new CardPay();
//取款存款密码验证
cardPay.deposit(1000);
cardPay.getMoney(9000);
PayInvocationHandler payInvocationHandler=new PayInvocationHandler(cardPay);
// //目标对象的类加载器 目标对象实现的接口
IPay iPay= (IPay) Proxy.newProxyInstance(cardPay.getClass().getClassLoader(), cardPay.getClass().getInterfaces(), payInvocationHandler);
System.out.println("接口中有非public"+iPay.getClass());
Ipay5 ipay5=(Ipay5) Proxy.newProxyInstance(Ipay5.class.getClassLoader(), new Class[]{Ipay5.class}, payInvocationHandler);
System.out.println("接口中全是public"+ipay5.getClass());
打印结果
存款1000.0
支出9000.0
接口中有非publicclass com.sunjinxi.Impl.$Proxy0
接口中全是publicclass com.sun.proxy.$Proxy1
通过测试发现,结果确实印证了源码中的实现,当所有接口中全是public接口时使用的包名为com.sun.proxy,否则为接口所在的包名。并且当我们使用不同的类加载器去创建代理对象时,代理名称的数量会加1,说明缓存的标志位类加载器对象和所有接口的名称。
(3).JD生成的动态代理的伪代码的推断
接下来我们会根据上面的源码分析来写一写通过动态代理类生成的代理类的伪代码。
public class DynamicProxy implements DynamicInterface{
private InvocationHandler mInvocationHandler;
public DynamicProxy(InvocationHandler invocationHandler) {
// TODO Auto-generated constructor stub
this.mInvocationHandler=invocationHandler;
}
@Override
public String getName(int id) {
// TODO Auto-generated method stub
Method method=getMethod("getName", new Class[]{int.class});
Object[] param=new Object[]{id};
try {
this.mInvocationHandler.invoke(this, method, param);
} catch (Throwable e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
private Method getMethod(String name,Class[] params){
Class clazz= this.getClass();
Class[] interfaces=clazz.getInterfaces();
for (int i = 0; i < interfaces.length; i++) {
Class interfaceClazz=interfaces[i];
Method method=null;
try {
method=interfaceClazz.getMethod(name, params);
return method;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return null;
}
}
由上述代码可以看到,当我们调用代理类的方法时,这个方法便会从接口中获取该方法对应的Method对象,然后将传入的参数封装为Object【】数组对象,然后调用this.mInvocationHandler.invoke(this, method, param);这个方法这样我们在mInvocationHandler中就可以进行操作了。
三.总结
通过今天的学习,使我们对动态代理以及Jdk动态代理的实现都有了相对深刻的认识。JDK的动态代理的实现完全依赖于接口以及被代理类的类加载器对象,不需要被代理对象,而在其内部只有一个InvocationHandler对象,代理对象的方法被调用时,便会获取对应的接口字节码中的Method对象,然后将传入的参数封装成Object数组,与代理对象传入InvocationHandler中,这样我们就可以通过反射来操作被代理对象,而不是直接去调用被代理对象中的方法, 在下一篇文章中我们将使用动态代理实现Xutils中的注入功能。