使用的Fastjson的版本是1.2.7和1.2.47兩個版本。
1. 問題
使用Fastjson解析包含內(nèi)部類的對象時,發(fā)生異常,代碼大致如下:
public class HelloController {
public User test1(HttpServletRequest request) {
UserInfo userInfo = new UserInfo();
userInfo.setAge(12);
userInfo.setName("test");
String json = JSON.toJSONString(userInfo);
UserInfo userInfo1 = JSON.parseObject(json, UserInfo.class);
System.out.println(userInfo1);
return new User();
}
class UserInfo {
private String name;
private Integer age;
// 省略get,set方法
}
}
出現(xiàn)問題的方法是 parseObject 方法:
public static <T> T parseObject(String text, Class<T> clazz)
出現(xiàn)的異常:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is com.alibaba.fastjson.JSONException: can't create non-static inner class instance.
2. 原因
??由于我們創(chuàng)建的 UserInfo 屬于非靜態(tài)內(nèi)部類,而非靜態(tài)內(nèi)部類的實例化是依賴于外部類的實例的,必須先實例化外部類,然后再通過外部類來實例化,也就是說我們無法直接實例化內(nèi)部類,而Fastjson則也是直接實例化對象,由于實例化不成功,所以會拋出異常。而針對靜態(tài)內(nèi)部類來說,實例化靜態(tài)內(nèi)部類是不依賴于外部類的實例,直接實例化靜態(tài)內(nèi)部類即可,所以解決方式也就很顯而易見了。
// 非靜態(tài)內(nèi)部類無法直接實例化,拋出異常
HelloController.UserInfo userInfo2 = new HelloController.UserInfo();
// 需要先實例化外部類,再通過外部類的實例來實例化
HelloController helloController = new HelloController();
HelloController.UserInfo userInfo = helloController.new UserInfo();
我們可以來看部分源碼,根據(jù)異常鏈,我們可以定位到 JavaBeanDeserializer 的createInstance方法:
Constructor<?> constructor = beanInfo.defaultConstructor;
if (beanInfo.defaultConstructorParameterSize == 0) {
if (constructor != null) {
object = constructor.newInstance();
} else {
object = beanInfo.factoryMethod.invoke(null);
}
} else {
ParseContext context = parser.getContext();
if (context == null || context.object == null) {
throw new JSONException("can't create non-static inner class instance.");
}
...
}
這里,首先會先判斷JavaBeanInfo對象的defaultConstructorParameterSize屬性是否是0,如果等于0,會執(zhí)行初始化操作,而該屬性的值的設置在:
defaultConstructorParameterSize = defaultConstructor.getParameterTypes().length;
所以defaultConstructorParameterSize 屬性就表示構(gòu)造方法參數(shù)的個數(shù),而這個值是通過Constructor對象的getParameterTypes來獲取的;而在靜態(tài)內(nèi)部類中,構(gòu)造方法的參數(shù)個數(shù)是0,非靜態(tài)內(nèi)部類中,構(gòu)造方法的參數(shù)個數(shù)是1;因為非靜態(tài)內(nèi)部類會通過構(gòu)造方法來設置外部類的引用,而靜態(tài)內(nèi)部類則不會。這點可以通過javap來命令來查看字節(jié)碼,針對內(nèi)部類的部分字節(jié)碼如下:
public controller.HelloController();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public entity.User test1(javax.servlet.http.HttpServletRequest);
Code:
0: new #2 // class controller/HelloController$UserInfo
3: dup
4: aload_0
5: invokespecial #3 // Method controller/HelloController$UserInfo."<init>":(Lcontroller/HelloController;)V
可以看到最后的invokespecial指令,將外部類的引用作為構(gòu)造方法的參數(shù)。
3. 解決方式
??由于非靜態(tài)內(nèi)部類不能直接實例化,所以我們直接將內(nèi)部類修改為靜態(tài)內(nèi)部類即可,當然也可以把內(nèi)部類拆成一個外部類也可以。