BeanUtils setProperty 抛出 IllegalArgumentException
BeanUtils setProperty throws an IllegalArgumentException
环境详细信息
我有三个项目:
- 项目 A:一个 Java REST 服务应用程序
- 项目 B:一个 Java 客户端应用程序
- 项目 C:POJO 的简单列表
前两个项目依赖第三个项目。
数据流
项目 B 向 项目 A 发出 HTTP 请求,该请求使用模型对象进行回复项目 C 将其转换为 JSON.
项目 B 使用 JSONObject and tries to obtain the original POJO object using BeanUtils.
解码 JSON 响应
代码示例
ExamplePOJO class(项目 C 的一部分):
public class ExamplePOJO {
private String id;
private AnotherPOJO anotherPOJO;
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
public void setAnotherPOJO(AnotherPOJO anotherPOJO ) {
this.anotherPOJO = anotherPOJO ;
}
public AnotherPOJO getAnotherPOJO() {
return anotherPOJO ;
}
}
项目 A 示例端点:
@Path("/sample")
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response getExampleResponse() {
try {
ServiceResponse<ExamplePOJO> response = new ServiceResponse<ExamplePOJO>();
ExamplePOJO eo = new ExamplePOJO();
eo.setId("1");
AnotherPOJO ap = new AnotherPOJO();
eo.setAnotherPojo(ap);
response.setResponse(eo);
return ((ResponseBuilder) Response.ok().entity(response)).type("application/json").build();
} catch(Exception E) {
//error
}
}
项目 A 响应对象容器:
public class ServiceResponse<T> {
private T response;
public T getResponse() {
return this.response;
}
public void setResponse(T response) {
this.response = response;
}
}
有趣的部分,项目 B:
public void callService() {
//....HTTP request...
//json object
JSONObject json = new JSONObject(jsonResponse);
//ServiceResponseEquivalent is the same as the ServiceResponse object of *Project A*
decodeResponse(json, ServiceResponseEquivalent.class, ExamplePOJO.class);
}
//this is a recursive function
public <T, V> T void decodeResponse(JSONObject json, Class<?> responseModel, Class<V> responseObjectModel) {
//this is the same as the ServiceResponse object of *Project A*
Object reflectedInstance = responseModel.newInstance();
//here I got the field "response" of ServiceResponse
Field[] fields = reflectedInstance.getClass().getDeclaredFields();
for(Field field: fields) {
//I got the json object based on the field name
Object objectFromResponse = json.get(field.getName());
if(objectFromResponse instanceof JSONObject) {
//this is the "response" property of *ServiceResponse* which I know is an instance of *ExamplePOJO* class (because who calls this function pass *Class<V> responseObjectModel*
if(if(field.getName().equals("response")) {
//recursive call
objectFromResponse = decodeResponse(json.getJSONObject(field.getName()), responseObjectModel, responseObjectModel);
}
//here I found another object inside the "response" object but I don't know which class is it. In this case it's an instance of *AnotherPOJO*
else {
//I try to get the class from the name of the property: in order to work, the property must be named as its class
String className = "com.example.packace." + field.getName().toUpperCase().charAt(0) + field.getName().substring(1, field.getName().length());
//className = com.example.package.AnotherPOJO
//recursive call
objectFromResponse = decodeResponse(json.getJSONObject(field.getName()), Class.forName(className), responseObjectModel);
//I try to set the object inside the response one
//HERE IT FAILS
BeanUtils.setProperty(reflectedInstance, field.getName(), objectFromResponse);
}
//here we found another Object but we don't know
} else {
//I add the element
BeanUtils.setProperty(reflectedInstance, field.getName(), objectFromResponse);
}
}
}
JSON 例子
客户收到这个JSON:
{
"response": { //response is an instance of ExamplePOJO
"id":"1",
"anotherPOJO":{
[...]
}
},
[ ...other fields...]
}
问题
当 decodeResponse 尝试解码递归调用中的 AnotherPOJO 对象时抛出此异常:
java.lang.IllegalArgumentException: Cannot invoke com.example.package.ExamplePOJO.setAnotherPOJO on bean class 'class com.example.package.ExamplePOJO' - argument type mismatch - had objects of type "com.example.package.AnotherPOJO" but expected signature "com.example.package.AnotherPOJO"
从异常中可以明显看出,这些对象是同一 class 的实例。
有什么想法吗?
哪种解码未知对象的方法更好class?这个:
String className = "com.example.packace." + field.getName().toUpperCase().charAt(0) + field.getName().substring(1, field.getName().length());
//className = com.example.package.AnotherPOJO
//recursive call
objectFromResponse = decodeResponse(json.getJSONObject(field.getName()), Class.forName(className), responseObjectModel);
有一个明显的问题,即字段必须命名为它的 class。
这似乎是 class 加载问题。
查看 Class.forName(String)
的来源,它使用来自调用者的 class 加载程序。这可能与加载目标 responseModel
的 class 加载程序不同,因此,请尝试以下操作:
//recursive call
objectFromResponse = decodeResponse(
json.getJSONObject(field.getName()),
Class.forName(className, true, responseModel.getClassLoader()),
responseObjectModel);
这应该确保模型子层次结构 classes 由相同的 class 加载器加载。
您在 field
变量中有关于 class 的信息。调用field.getType()
获取当前field/property的class ...
//here I found another object inside the "response" object. Use information from field to get class.
else {
objectFromResponse = decodeResponse(json.getJSONObject(field.getName()), field.getType(), responseObjectModel);
注意: 我建议 Jackson 将 JSON 转换为 java 个对象。
环境详细信息
我有三个项目:
- 项目 A:一个 Java REST 服务应用程序
- 项目 B:一个 Java 客户端应用程序
- 项目 C:POJO 的简单列表
前两个项目依赖第三个项目。
数据流
项目 B 向 项目 A 发出 HTTP 请求,该请求使用模型对象进行回复项目 C 将其转换为 JSON.
项目 B 使用 JSONObject and tries to obtain the original POJO object using BeanUtils.
解码 JSON 响应代码示例
ExamplePOJO class(项目 C 的一部分):
public class ExamplePOJO {
private String id;
private AnotherPOJO anotherPOJO;
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
public void setAnotherPOJO(AnotherPOJO anotherPOJO ) {
this.anotherPOJO = anotherPOJO ;
}
public AnotherPOJO getAnotherPOJO() {
return anotherPOJO ;
}
}
项目 A 示例端点:
@Path("/sample")
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response getExampleResponse() {
try {
ServiceResponse<ExamplePOJO> response = new ServiceResponse<ExamplePOJO>();
ExamplePOJO eo = new ExamplePOJO();
eo.setId("1");
AnotherPOJO ap = new AnotherPOJO();
eo.setAnotherPojo(ap);
response.setResponse(eo);
return ((ResponseBuilder) Response.ok().entity(response)).type("application/json").build();
} catch(Exception E) {
//error
}
}
项目 A 响应对象容器:
public class ServiceResponse<T> {
private T response;
public T getResponse() {
return this.response;
}
public void setResponse(T response) {
this.response = response;
}
}
有趣的部分,项目 B:
public void callService() {
//....HTTP request...
//json object
JSONObject json = new JSONObject(jsonResponse);
//ServiceResponseEquivalent is the same as the ServiceResponse object of *Project A*
decodeResponse(json, ServiceResponseEquivalent.class, ExamplePOJO.class);
}
//this is a recursive function
public <T, V> T void decodeResponse(JSONObject json, Class<?> responseModel, Class<V> responseObjectModel) {
//this is the same as the ServiceResponse object of *Project A*
Object reflectedInstance = responseModel.newInstance();
//here I got the field "response" of ServiceResponse
Field[] fields = reflectedInstance.getClass().getDeclaredFields();
for(Field field: fields) {
//I got the json object based on the field name
Object objectFromResponse = json.get(field.getName());
if(objectFromResponse instanceof JSONObject) {
//this is the "response" property of *ServiceResponse* which I know is an instance of *ExamplePOJO* class (because who calls this function pass *Class<V> responseObjectModel*
if(if(field.getName().equals("response")) {
//recursive call
objectFromResponse = decodeResponse(json.getJSONObject(field.getName()), responseObjectModel, responseObjectModel);
}
//here I found another object inside the "response" object but I don't know which class is it. In this case it's an instance of *AnotherPOJO*
else {
//I try to get the class from the name of the property: in order to work, the property must be named as its class
String className = "com.example.packace." + field.getName().toUpperCase().charAt(0) + field.getName().substring(1, field.getName().length());
//className = com.example.package.AnotherPOJO
//recursive call
objectFromResponse = decodeResponse(json.getJSONObject(field.getName()), Class.forName(className), responseObjectModel);
//I try to set the object inside the response one
//HERE IT FAILS
BeanUtils.setProperty(reflectedInstance, field.getName(), objectFromResponse);
}
//here we found another Object but we don't know
} else {
//I add the element
BeanUtils.setProperty(reflectedInstance, field.getName(), objectFromResponse);
}
}
}
JSON 例子
客户收到这个JSON:
{
"response": { //response is an instance of ExamplePOJO
"id":"1",
"anotherPOJO":{
[...]
}
},
[ ...other fields...]
}
问题
当 decodeResponse 尝试解码递归调用中的 AnotherPOJO 对象时抛出此异常:
java.lang.IllegalArgumentException: Cannot invoke com.example.package.ExamplePOJO.setAnotherPOJO on bean class 'class com.example.package.ExamplePOJO' - argument type mismatch - had objects of type "com.example.package.AnotherPOJO" but expected signature "com.example.package.AnotherPOJO"
从异常中可以明显看出,这些对象是同一 class 的实例。
有什么想法吗?
哪种解码未知对象的方法更好class?这个:
String className = "com.example.packace." + field.getName().toUpperCase().charAt(0) + field.getName().substring(1, field.getName().length());
//className = com.example.package.AnotherPOJO
//recursive call
objectFromResponse = decodeResponse(json.getJSONObject(field.getName()), Class.forName(className), responseObjectModel);
有一个明显的问题,即字段必须命名为它的 class。
这似乎是 class 加载问题。
查看 Class.forName(String)
的来源,它使用来自调用者的 class 加载程序。这可能与加载目标 responseModel
的 class 加载程序不同,因此,请尝试以下操作:
//recursive call
objectFromResponse = decodeResponse(
json.getJSONObject(field.getName()),
Class.forName(className, true, responseModel.getClassLoader()),
responseObjectModel);
这应该确保模型子层次结构 classes 由相同的 class 加载器加载。
您在 field
变量中有关于 class 的信息。调用field.getType()
获取当前field/property的class ...
//here I found another object inside the "response" object. Use information from field to get class.
else {
objectFromResponse = decodeResponse(json.getJSONObject(field.getName()), field.getType(), responseObjectModel);
注意: 我建议 Jackson 将 JSON 转换为 java 个对象。