2011-01-23 14 views
9

Sto lavorando a un'applicazione semi-large utilizzando Spring 3 e sto riscontrando problemi di prestazioni quando lancio centinaia di utenti contemporaneamente. Sto usando diversi bean con scope richiesta usando il proxy AOP di Spring e posso vedere che ogni volta che chiamo un metodo su uno di questi bean, viene richiamato l'intercettore CGLIB, che quindi chiama AbstractBeanFactory.getBean(), che chiama add() su un set sincronizzato di fagioli Spring esistenti. Poiché questo add() è sincronizzato, blocca efficacemente il server quando ci sono migliaia di chiamate a tutto questo in attesa di essere aggiunte alla stessa lista.Problemi di prestazioni durante l'utilizzo di un sacco di bean con scope richiesta AOP

C'è un modo per aggirare questo usando i bean con scope richiesta? Ho letto nella documentazione di Spring che CGLIB non è usato se il bean implementa qualsiasi interfaccia (http://static.springsource.org/spring/docs/2.0.0/reference/aop.html#d0e9015) ma i miei scope con scope di richiesta tutti implementano uno (lo stesso di fatto) e sta ancora accadendo. E ho sicuramente bisogno che i bean vengano sottoposti a scope di scope perché alcuni dei loro campi sono calcolati in una parte dell'app per una richiesta particolare e poi uso Spel per ottenere il loro valore in una parte diversa dell'app durante la stessa richiesta. Penso che se ho fatto il prototipo dei fagioli con scope, avrei un oggetto fresco quando ho usato SpEL per ottenerli la seconda volta.

Ecco un esempio di codice che illustra il mio problema. Vedi le ultime due righe per i commenti che descrivono esattamente dove sto avendo problemi.

<!-- 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. 
    } 
} 

Le mie idee sono una delle seguenti opzioni:

  • Posso fare una richiesta primavera Bean con scope senza il proxy ogni chiamata di metodo fatta sulla fava? E senza contrassegnare ogni metodo come "finale"?

o ...

  • Posso ignorare fabbrica di fagioli di primavera per implementare una cache Bean che controllerà se un chicco viene memorizzato nella cache prima di chiamare AbstractBeanFactory.getBean()? E se sì, dove posso configurare Spring per utilizzare la mia fabbrica di bean personalizzata?
+0

1 chiamata è OK, ma 2 chiamate x uccide la tua app? –

+0

Se lo chiamasse la prima volta che si faceva riferimento al bean, sarebbe okay. Se chiama la classe proxy ogni volta che chiamo un metodo sul bean, questo uccide la mia app. – Cameron

risposta

4

Come risulta, Spring memorizza effettivamente nella cache i bean con scope della richiesta, negli attributi della richiesta. Se siete curiosi, date un'occhiata al AbstractRequestAttributesScope, che si estende 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; 
} 

Così, mentre AbstractBeanFactory.getBean() venga invitato ogni metodo di fagiolo chiamata a causa della delega AOP, causa solo per aggiungere Primavera a quel set sincronizzato se il bean non era già stato trovato negli attributi della richiesta.

Evitare il proxy di ogni chiamata di metodo sulla mia richiesta i bean con ambito ridurrebbe comunque la complessità ma con questa memorizzazione nella cache, l'impatto sulle prestazioni sarebbe minimo. Penso che la prestazione lenta sia qualcosa con cui dovrò convivere se desidero una tonnellata di fagioli con scope richiesta e continuo a servire un sacco di richieste alla volta.

2

Interessante domanda.

Si scopre che il proxy con scope di Spring non memorizza nella cache gli oggetti risolti, in modo che ogni accesso al proxy dell'ambito causi la chiamata di getBean().

Per risolvere il problema, è possibile creare la cache ambito del proxy di un uomo povero, qualcosa di simile (non testato, fagioli obiettivo dovrebbe essere richiesta a livello di ambito, ma senza <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(); 
    } 

    ... 
} 

Per quanto riguarda i meccanismi dei proxy: a differenza altri proxy AOP, i proxy con scope sono CGLIB per impostazione predefinita, è possibile sovrascriverli impostando <aop:scoped-proxy proxy-target-class = "false" />, ma in questo caso non sarebbe di aiuto.

+0

Se ho questo problema in più di un punto con più di un'interfaccia, dovrei creare un nuovo proxy per ogni interfaccia? Sarebbe possibile invece sovrascrivere AbstractBeanFactory.getBean() e implementare una cache di bean lì? Se è così, non riesco a capire dove nella configurazione di Spring specifichi la mia fabbrica di fagioli personalizzata. – Cameron

+0

Vedi la mia risposta; risulta che i bean scope scope sono memorizzati nella cache da RequestScope negli attributi della richiesta. Chi lo sapeva! – Cameron

0

Una possibilità è quella di sostituire iniettare un proxy ambito con un lookup-method:

public abstract class ExecutingClass { 
    protected abstract SomeInterface makeMyBean(); 

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

che garantirà molla viene richiesto per il bean sola volta per ogni richiesta, eliminare qualsiasi sovraccarico dovuto al proxy ambito, e accorciare pila tracce. È meno flessibile (in quanto non è possibile condividere arbitrariamente i riferimenti al bean con scope della richiesta e il proxy dell'ambito utilizza il bean corretto), ma potrebbe non essere necessaria la flessibilità.

Problemi correlati