我正在使用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来获取它时,我会有一个新鲜的物体。
这是一个代码示例,说明了我的问题。请参阅最后两行,了解描述我遇到问题的地方。
<!-- Spring config -->
<bean name="someBean" class="some.custom.class.SomeClass" scope="request">
<property name="property1" value="value1"/>
<property name="property2" value="value2"/>
<aop:scoped-proxy/>
</bean>
<bean name="executingClass" class="some.other.custom.class.ExecutingClass" scope="singleton">
<property name="myBean" ref="someBean" />
</bean>
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.
}
}
我的想法是以下之一:
...或
答案 0 :(得分:4)
事实证明,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并且仍然一次提供大量请求,那么我将不得不忍受这种缓慢的性能。
答案 1 :(得分:2)
有趣的问题。
事实证明,Spring的作用域代理不会缓存已解析的对象,因此每次访问作用域代理都会导致调用getBean()
。
作为一种解决方法,您可以创建一个穷人的缓存范围代理,类似这样(未经测试,目标bean应该是请求范围的,但没有<aop:scoped-proxy />
):
public class MyScopedProxy implements SomeInterface, BeanFactoryAware {
private BeanFactory factory;
private Scope scope;
private String targetBeanName;
private ThreadLocal<SomeInterface> cache = new ThreadLocal<SomeInterface>();
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,您可以通过设置<aop:scoped-proxy proxy-target-class = "false" />
来覆盖它,但在这种情况下它无济于事。
答案 2 :(得分:0)
一种选择是使用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),但你可能不需要灵活性。