2011-01-23 61 views
9

我正在使用Spring 3開發一個半大型應用程序,並且在一次拋出數百個用戶時遇到了性能問題。我使用Spring的AOP代理使用了幾個請求範圍的bean,我可以看到每次調用其中一個bean的任何方法時,調用CGLIB攔截器,然後調用AbstractBeanFactory.getBean(),它調用add()一個現有的Spring bean的同步集合。由於add()是同步的,因此當數以千計的調用都等待添加到同一個列表時,它會有效地鎖定服務器。使用大量的AOP請求範圍的bean時的性能問題

有沒有辦法解決這個使用請求範圍的bean?我在Spring文檔中讀到,如果bean實現了任何接口(http://static.springsource.org/spring/docs/2.0.0/reference/aop.html#d0e9015)但我的請求作用域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. 
    } 
} 

我的想法是下列之一:

  • 我可以做一個Spring bean請求範圍,而不進行代理bean上做出每一個方法調用?沒有將每種方法都標記爲「最終」?

或...

  • 我可以覆蓋Spring的bean工廠實現Bean的緩存,如果一個bean被調用AbstractBeanFactory.getBean緩存之前,將檢查()?如果是這樣,我在哪裏配置Spring來使用我的自定義bean工廠?
+0

1個電話確定,但2個電話會殺死您的應用程序? –

+0

如果它在第一次調用bean時調用它,那就沒問題。如果每次調用代理類,我都會調用bean上的任何方法,這會殺死我的應用程序。 – Cameron

回答

4

事實證明,Spring實際上會在請求屬性中緩存請求範圍的bean。如果你很好奇,看看AbstractRequestAttributesScope,其中RequestScope擴展:

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; 
} 

因此,儘管AbstractBeanFactory.getBean()不被調用上,因爲AOP代理的每個bean的方法調用,它只能導致彈簧加如果在請求屬性中尚未找到bean,則返回該同步集。

在我的請求中避免每個方法調用的代理scoped bean仍然會降低複雜性,但是使用這種緩存,性能影響將會很小。我認爲,如果我想要一大堆請求範圍的bean,並且仍然一次提供大量請求,那麼我將不得不忍受緩慢的性能。

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" />來覆蓋它,但在這種情況下它不起作用。

+0

如果我在具有多個接口的多個位置出現此問題,我需要爲每個接口創建一個新代理?是否有可能改寫AbstractBeanFactory.getBean()並在那裏實現bean緩存?如果是這樣,我不知道在Spring配置的哪個位置指定我的自定義bean工廠。 – Cameron

+0

查看我的回答;結果請求範圍的bean被請求屬性中的RequestScope緩存。誰知道! – Cameron

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(); 
    } 
} 

這將確保春節被要求爲bean每個請求只有一次,消除任何開銷由於範圍代理,並縮短堆痕跡。它不那麼靈活(因爲你不能隨意共享對請求作用域bean的引用,並讓作用域代理使用正確的bean),但你可能不需要靈活性。