问题描述
项目里出现了java.lang.OutOfMemoryError: Java heap space
错误,根据trace定位到entity.toString()
方法上,再追溯到entity_Roo_ToString.aj
文件的toString()
方法上。为什么这个方法会引起内存错误呢?
线索
entity_Roo_ToString.aj
的代码如下所示:
privileged aspect Entity\_Roo\_ToString {
public String Entity.toString() {
return ReflectionToStringBuilder.toString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
}
可以看出,这个aj文件定义了该实体的toString()
方法。
那么,这个文件是如何生成的呢?有何作用?注意到其中的ReflectionToStringBuilder
类,它的作用是什么?
@RooToString
当我们在Roo Shell中用命令生成以个Entity时,Roo会为实体自动添加注解@RooToString
,这里对该注解有如下定义:
Provides a Object.toString() method if requested.
Whilst it is possible to apply this annotation to any class that you’d like a Object.toString() method produced for, it is generally triggered automatically via the use of most other annotations in the system. The created method is conservative and only includes public accessor methods within the produced string. Further, any accessor which returns a common JDK Collection type is restricted to displaying its size only.
可以看出,@RooToString
注解为实体类添加了toString()
方法,而且会自动遍历该实体的public accessor,组装成可读的字符串信息,返回给调用者。注意到:
any accessor which returns a common JDK Collection type is restricted to displaying its size only.
这一句的意思是,返回Collection
的accessor不会被逐个遍历输出,而是输出它的size。
ReflectionToStringBuilder
Roo通过@RooToString
注解为我们生成toString()
方法,具体的实现由ReflectionToStringBuilder
这个类来完成。这里是这样定义这个类的:
Assists in implementing Object.toString() methods using reflection.
通常可以这样使用:
public String toString() {
return ReflectionToStringBuilder.toString(this);
}
也可以这样用,方便我们debug:
System.out.println("An object: " + ReflectionToStringBuilder.toString(anObject));
由此可以推断,Java heap space
应该是出在这个ReflectionToStringBuilder
身上。具体地,entity文件如下所示:
@RooJavaBean
@RooToString()
@RooJpaActiveRecord(finders = { "findBatchesByIdentifierLike" })
@RooJson
public class Batch {
@NotNull
private String identifier;
private String description;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "batch", fetch = FetchType.LAZY)
private Set<Product> products = new HashSet<Product>();
...
}
留意到Product
与Batch
之间是一个ManyToOne的关系,难道是ReflectionToStringBuilder
遍历了products
属性导致内存不足?但上文中的说明里提到Collection
的accessor不会被逐个遍历输出,而是输出它的size。为什么会导致内存不足呢?尝试在控制台中打印出batch.toString()
,如下所示:
Batch[identifier=123,description=123123123,products=[Product[identifier=123_12,description=123_12...], Product[...],...]
可见,ReflectionToStringBuilder
居然遍历了products
属性,将其中的product全部输出。当batch
中的product
数量很大时,便会有内存不足的危险。
解决方案
以ReflectionToStringBuilder
和OutOfMemoryError
为关键字在google上搜索,找到了这里:
I assume that as the ReflectionToStringBuilder moved down the object graph, it eventually exceeded the memory. By adding an ‘excludedFields’ property to @RooToString and adding any object fields, the problem was stopped.
可知道@RooToString
注解有一个’excludedFields’的参数,可以指定哪些属性被忽略。这里是这样定义的:
excludeFields
public abstract String[] excludeFields
Returns:
an array of fields to exclude in the toString method
Default:
""
可知,excludeFields
可传入一个字符串数组。于是我们为@RooToString
添加如下参数:
@RooToString(excludeFields={"products"})
Roo自动update了aj文件,内容如下:
privileged aspect Batch_Roo_ToString {
public String Batch.toString() {
return new ReflectionToStringBuilder(this
, ToStringStyle.SHORT_PREFIX_STYLE).setExcludeFieldNames("products").toString();
}
}
其中setExcludeFieldNames("products")
一句设置了忽略的属性的名字。这里可以找到该方法的定义:
public ReflectionToStringBuilder setExcludeFieldNames(String... excludeFieldNamesParam)
Sets the field names to exclude.
Parameters:
excludeFieldNamesParam - The excludeFieldNames to excluding from toString or null.
Returns:
this
接下来运行项目,问题解决!