使用内省的Grails JSON marhsaling会导致Classloader.loadClass()

时间:2016-01-13 04:47:07

标签: json grails marshalling

我正在使用Grails 2.2.4并且有一个控制器端点,它将域对象列表转换为JSON。在负载下(少至5个并发请求),编组性能非常差。进行线程转储时,线程被阻止:

java.lang.ClassLoader.loadClass(ClassLoader.java:291)

有一个marhsaler注册使用反射和内省来编组所有域对象。意识到反射和内省比直接方法调用慢,我仍然看到意外的行为,因为类加载器每次都调用,反过来阻塞发生。示例堆栈跟踪如下:

java.lang.Thread.State: BLOCKED (on object monitor)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:291)
    - waiting to lock <785e31830> (a org.grails.plugins.tomcat.ParentDelegatingClassLoader)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
    at java.beans.Introspector.instantiate(Introspector.java:1470)
    at java.beans.Introspector.findExplicitBeanInfo(Introspector.java:431)
    at java.beans.Introspector.<init>(Introspector.java:380)
    at java.beans.Introspector.getBeanInfo(Introspector.java:167)
    at java.beans.Introspector.getBeanInfo(Introspector.java:230)
    at java.beans.Introspector.<init>(Introspector.java:389)
    at java.beans.Introspector.getBeanInfo(Introspector.java:167)
    at java.beans.Introspector.getBeanInfo(Introspector.java:230)
    at java.beans.Introspector.<init>(Introspector.java:389)
    at java.beans.Introspector.getBeanInfo(Introspector.java:167)
    at java.beans.Introspector.getBeanInfo(Introspector.java:230)
    at java.beans.Introspector.<init>(Introspector.java:389)
    at java.beans.Introspector.getBeanInfo(Introspector.java:167)
    at org.springframework.beans.CachedIntrospectionResults.<init>(CachedIntrospectionResults.java:217)
    at org.springframework.beans.CachedIntrospectionResults.forClass(CachedIntrospectionResults.java:149)
    at org.springframework.beans.BeanWrapperImpl.getCachedIntrospectionResults(BeanWrapperImpl.java:324)
    at org.springframework.beans.BeanWrapperImpl.getPropertyValue(BeanWrapperImpl.java:727)
    at org.springframework.beans.BeanWrapperImpl.getPropertyValue(BeanWrapperImpl.java:721)
    at org.springframework.beans.PropertyAccessor$getPropertyValue.call(Unknown Source)
    at com.ngs.id.RestDomainClassMarshaller.extractValue(RestDomainClassMarshaller.groovy:203)
...
...

使用相同参数加载相同端点的简单基准测试会导致loadClass调用。

我的印象是,类至少会被类加载器缓存,并且不会在每个方法调用上加载以使属性被封送。

检索属性值的代码如下:

BeanWrapper beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(domainObject);
return beanWrapper.getPropertyValue(property.getName());

是否需要配置设置以确保仅加载一次类?或者可能采用不同的方式来获得每次都不会导致类加载的属性?或者也许是一种更高效的方法来实现这一目标?

为每个域类编写一个自定义编组程序可以避免反射和内省,但会有很多重复代码。

感谢任何输入。

1 个答案:

答案 0 :(得分:0)

所以经过多次挖掘后,这才是我发现的。

使用BeanUtils.getPropertyDescriptors和getValue将始终尝试使用类加载器查找描述bean的BeanInfo类。在这种情况下,我们不为grails域类提供BeanInfo类,因此这个调用是多余的。我找到了一些信息,你可以提供一个自定义的BeanInfoFactory来绕过它并排除你的包但我找不到如何用Grails配置它。

另外搜索springframework文档有一个配置选项,你可以传递Introspector.IGNORE_ALL_BEANINFO,告诉CachedIntorspectionResults永远不要查找bean类。但是,弹簧框架的3.1.4版本中没有这个版本,这是当前的grails 2.2.4。较新的版本似乎有这个选项。

因此,如果使用BeanUtils,则无法在类加载器上传递此初始查找。但是后续的加载器应该由CachedIntrospectionResults缓存。不幸的是,在我们的场景中不会发生在测试中看起来有一个错误,看看查找是否可以缓存。请在下面查看更多信息。

修复最终会回归到使用纯粹的反射。而不是使用:

beanWrapper.getPropertyValue(property.getName());

使用:

PropertyDescription pd = BeanUtils.getPropertyDescriptor(domainObject.getClass(), property.getName())
pd.readMethod.invoke(domainObject)

pd缓存的地方。

修复此问题后,探查器仍然显示CachedIntorspectionResults上没有缓存,因为开箱即用的grails marshaller。这是由于CachedIntrospectionResults中的缓存实现不当造成的。解决这个问题的方法是将正确的类加载器添加到CachedIntrospectionResults中的acceptedClassLoaders。

public class EnhanceCachedIntrospectionResultsAcceptedClassLoadersListener implements ServletContextListener {

    public void contextInitialized(ServletContextEvent event) {
        CachedIntrospectionResults.acceptClassLoader(Thread.currentThread().getContextClassLoader().getParent());
    }

    public void contextDestroyed(ServletContextEvent event) {
        CachedIntrospectionResults.clearClassLoader(Thread.currentThread().getContextClassLoader().getParent());
        Introspector.flushCaches();
    }
}

请注意,需要将父级添加到接受的类装入器列表而不是当前的类装入器。不确定这是否特定于grails,但这解决了这个问题。我不确定这个修复是否会产生副作用。

总之,在使用直接反射并修复CachedIntrospectionResults缓存后,我们从原始设置中的10个请求/秒变为120个请求/秒。

然而真正关注的是,如果我们使用每个域类的1-1 marshaller,我们看到的性能比通用编组器的性能提高了x2,我们测试对象是否属于类等实例。我们是使用通用编组器保存了大量代码,但还有很多工作要做,以获得与编写1-1 marshaller相当的性能。

希望这对遇到这个问题的其他人有用......