I'm working on a semi-large application using Spring 3 and am running into performance problems when throwing hundreds of users at it at once. I'm using several request scoped beans using Spring's AOP proxy and I can see that every time I call any method on one of these beans, the CGLIB interceptor is invoked, which then calls AbstractBeanFactory.getBean(), which calls add() on a Synchronized Set of existing Spring beans. Since this add() is synchronized, it effectively locks up the server when there are thousands of calls to it all waiting to add to the same list.
Is there a way to get around this using request scoped beans? I read in the Spring documentation that CGLIB isn't used if the bean implements any interface (http://static.springsource.org/spring/docs/2.0.0/reference/aop.html#d0e9015) but my request scoped beans all implement one (the same one in fact) and it's still happening. And I definitely need the beans to be request scoped because some of their fields are calculated in one part of the app for a particular request and then I use SpEL to get their value in a different part of the app during the same request. I think if I made the beans prototype scoped, I'd have a fresh object when I used SpEL to get them the second time.
Here is a code sample that illustrates my problem. See the last two lines for comments describing where exactly I'm having issues.
<!-- 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.
}
}
My ideas are one of the following:
or...
As it turns out, Spring actually does cache the request scoped beans, in the request attributes. If you're curious, take a look at AbstractRequestAttributesScope, which RequestScope extends:
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;
}
So while AbstractBeanFactory.getBean() does get called on every bean method call because of the aop proxy, it only causes Spring to add to that synchronized set if the bean wasn't already found in the request attributes.
Avoiding the proxying of every method call on my request scoped beans would still reduce complexity but with this caching in place, the performance impact would be minimal. I think the slow performance is something I'm going to have to live with if I want a ton of request scoped beans and still serve a ton of requests at a time.
Interesting question.
It turns out that Spring's scoped proxy doesn't cache resolved objects, so that every access to the scoped proxy causes getBean()
to be called.
As a workaround, you can create a poor man's caching scoped proxy, something like this (untested, target bean should be request-scoped, but without <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();
}
...
}
Regarding the proxying mechanisms: unlike other AOP proxies, scoped proxies are CGLIB by default, you can override it by setting <aop:scoped-proxy proxy-target-class = "false" />
, but it wouldn't help in this case.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With