Roo中ReflectionToStringBuilder引起的OutOfMemoryError

问题描述

项目里出现了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>();

    ...
}

留意到ProductBatch之间是一个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数量很大时,便会有内存不足的危险。

解决方案

ReflectionToStringBuilderOutOfMemoryError为关键字在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 

antibiotici-acquista.com

, 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

接下来运行项目,问题解决!

“Roo中ReflectionToStringBuilder引起的OutOfMemoryError”的一个回复

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据