使用大量AOP请求范围bean时的性能问题

我正在使用Spring 3处理一个半大型应用程序,并且在同时向其投入数百个用户时遇到了性能问题。 我使用Spring的AOP代理使用了几个请求范围的bean,我可以看到,每当我调用其中一个bean上的任何方法时,都会调用CGLIB拦截器,然后调用AbstractBeanFactory.getBean(),它调用add()on现有Spring bean的同步集。 由于这个add()是同步的,所以当有数千个调用它等待添加到同一个列表时,它会有效地锁定服务器。

有没有办法使用请求范围的bean来解决这个问题? 我在Spring文档中读到,如果bean实现了任何接口(http://static.springsource.org/spring/docs/2.0.0/reference/aop.html#d0e9015),那么CGLIB不会被使用,但是我的请求范围是bean所有实现一个(实际上是同一个),它仍然发生。 我肯定需要将bean作为请求作用域,因为它们的某些字段是在应用程序的一个部分中为特定请求计算的,然后我使用SpEL在同一请求期间在应用程序的不同部分获取它们的值。 我想如果我把bean原型作为范围,当我第二次使用SpEL来获取它时,我会有一个新鲜的对象。

这是一个代码示例,说明了我的问题。 请参阅最后两行,以获取描述我究竟遇到问题的评论。

         public Interface SomeInterface { public String getProperty1(); public void setProperty1(String property); public String getProperty2(); public void setProperty2(String property); } public class SomeClass implements SomeInterface { private String property1; private String property2; public String getProperty1() { return propery1; } public void setProperty1(String property) { property1=property;} public String getProperty2() { return propery2; } public void setProperty2(String property) { property2=property;} } public class ExecutingClass { private SomeInterface myBean; public void execute() { String property = myBean.getProperty1(); // CGLIB interceptor is invoked here, registering myBean as a bean String otherProperty = myBean.getProperty2(); // CGLIB interceptor is invoked here too! Seems like this is unnecessary. And it's killing my app. } } 

我的想法是以下之一:

  • 我可以在没有代理对bean进行的每个方法调用的情况下创建一个Spring Bean请求作用域吗? 并没有将每种方法都标记为“最终”?

要么…

  • 我可以覆盖Spring的bean工厂来实现一个Bean缓存,它会在调用AbstractBeanFactory.getBean()之前检查bean是否被缓存吗? 如果是这样,我在哪里配置Spring以使用我的自定义bean工厂?

事实certificate,Spring实际上会在请求属性中缓存请求范围的bean。 如果你很好奇,请看一下RequestRecope扩展的AbstractRequestAttributesScope:

 public Object get(String name, ObjectFactory objectFactory) { RequestAttributes attributes = RequestContextHolder.currentRequestAttributes(); Object scopedObject = attributes.getAttribute(name, getScope()); if (scopedObject == null) { scopedObject = objectFactory.getObject(); attributes.setAttribute(name, scopedObject, getScope()); } return scopedObject; } 

因此,由于aop代理,每次bean方法调用都会调用AbstractBeanFactory.getBean(),如果在请求属性中找不到bean,它只会导致Spring添加到该同步集。

避免对我的请求作用域上的每个方法调用的代理仍会降低复杂性,但是通过这种缓存,性能影响将是最小的。 我认为如果我想要大量的请求范围的bean并且仍然一次提供大量请求,那么我将不得不忍受这种缓慢的性能。

有趣的问题。

事实certificate,Spring的作用域代理不会缓存已解析的对象,因此每次访问作用域代理都会导致调用getBean()

作为一种解决方法,你可以创建一个穷人的缓存范围代理,类似这样(未经测试,目标bean应该是请求范围的,但没有 ):

 public class MyScopedProxy implements SomeInterface, BeanFactoryAware { private BeanFactory factory; private Scope scope; private String targetBeanName; private ThreadLocal cache = new ThreadLocal(); private SomeInterface resolve() { SomeInterface v = cache.get(); if (v == null) { v = (SomeInterface) factory.getBean(targetBeanName); cache.set(v); scope.registerDestructionCallback(targetBeanName, new Runnable() { public void run() { cache.remove(); } }); } return v; } public void setBeanFactory(BeanFactory factory) { this.factory = factory; this.scope = ((ConfigurableBeanFactory) factory).getRegisteredScope("request"); } public String getProperty() { return resolve().getProperty(); } ... } 

关于代理机制:与其他AOP代理不同,默认情况下,作用域代理是CGLIB,您可以通过设置来覆盖它,但在这种情况下它无济于事。

一种选择是使用lookup-method替换注入作用域代理:

 public abstract class ExecutingClass { protected abstract SomeInterface makeMyBean(); public void execute() { SomeInterface myBean = makeMyBean(); String property = myBean.getProperty1(); String otherProperty = myBean.getProperty2(); } } 

这将确保每个请求只向Spring请求一次bean,消除由于作用域代理导致的任何开销,并缩短堆栈跟踪。 它不太灵活(因为你不能随意共享对请求作用域bean的引用并让作用域代理使用正确的bean)但你可能不需要灵活性。