2011-01-23 5 views
9

Estoy trabajando en una aplicación de tamaño medio con Spring 3 y tengo problemas de rendimiento al lanzar cientos de usuarios a la vez. Estoy usando varios beans de ámbito de solicitud utilizando el proxy AOP de Spring y puedo ver que cada vez que llamo a cualquier método en uno de estos beans, se invoca el interceptor CGLIB, que luego llama a AbstractBeanFactory.getBean(), que llama a add() en un conjunto sincronizado de beans de primavera existentes. Como este complemento() está sincronizado, efectivamente bloquea el servidor cuando hay miles de llamadas esperando a que se agreguen a la misma lista.Problemas de rendimiento al usar muchos beans de ámbito de solicitud de AOP

¿Hay alguna forma de evitar esto al usar beans de ámbito solicitado? Leí en la documentación de Spring que CGLIB no se usa si el bean implementa alguna interfaz (http://static.springsource.org/spring/docs/2.0.0/reference/aop.html#d0e9015) pero mi solicitud tuvo el alcance de los beans todos implementan uno (el mismo de hecho) y todavía está sucediendo. Y definitivamente necesito que los beans se soliciten con un alcance porque algunos de sus campos se calculan en una parte de la aplicación para una solicitud en particular y luego uso SpEL para obtener su valor en una parte diferente de la aplicación durante la misma solicitud. Creo que si creara el prototipo del frijol, tendría un objeto nuevo cuando use SpEL para obtenerlos por segunda vez.

Aquí hay un ejemplo de código que ilustra mi problema. Vea las últimas dos líneas para comentarios que describan exactamente dónde estoy teniendo problemas.

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

Mis ideas son uno de los siguientes:

  • ¿Puedo hacer una solicitud de beans Spring con ámbito sin proxy cada llamada al método realizado en el grano? ¿Y sin marcar cada método como "final"?

o ...

  • ¿Puedo anular fábrica de beans de primavera para implementar una memoria caché Bean que se compruebe si un grano se almacena en caché antes de llamar AbstractBeanFactory.getBean()? Y si es así, ¿dónde configuro Spring para usar mi fábrica de bean personalizada?
+0

1 llamada está bien, pero 2 x llamadas mata a su aplicación? –

+0

Si lo llamó la primera vez que se ha hecho referencia el frijol, que estaría bien. Si llama a la clase proxy cada vez que llamo a cualquier método en el bean, eso mata mi aplicación. – Cameron

Respuesta

4

Como resultado, la primavera hace realidad caché de la petición en ámbito de los granos, en los atributos de la petición. Si usted es curioso, echar un vistazo a AbstractRequestAttributesScope, que se extiende 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; 
} 

Así, mientras AbstractBeanFactory.getBean() se consiga llamar en cada llamada al método de frijol debido a que el proxy AOP, que sólo hace que la primavera para añadir a ese conjunto sincronizado si el bean no se encontró en los atributos de solicitud.

Evitar el proxy de cada llamada a un método en mi petición en ámbito de los granos se siguen reduciendo la complejidad, pero con este almacenamiento en caché en su lugar, el impacto de rendimiento serían mínimos. Creo que el bajo rendimiento es algo que voy a tener que vivir con si quiero una tonelada de petición en ámbito de los granos y todavía sirven un montón de peticiones a la vez.

2

Interesante pregunta.

Resulta que el proxy con ámbito de Spring no almacena en caché los objetos resueltos, por lo que cada acceso al proxy de ámbito hace que se llame a getBean().

Como solución alternativa, puede crear el almacenamiento en caché de ámbito de representación de un hombre pobre, algo como esto (, bean destino no probado debe ser pedido con ámbito, pero sin <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(); 
    } 

    ... 
} 

En cuanto a los mecanismos de proxies: a diferencia otros proxies AOP, los proxies del alcance son CGLIB por defecto, puede anularlo estableciendo <aop:scoped-proxy proxy-target-class = "false" />, pero no ayudaría en este caso.

+0

Si tengo este problema en más de un lugar con más de una interfaz, que había necesidad de crear un nuevo proxy para cada interfaz? ¿Sería posible en su lugar anular AbstractBeanFactory.getBean() e implementar un caché de bean allí? Si es así, no puedo averiguar dónde en la configuración de Spring para especificar mi fábrica de bean personalizada. – Cameron

+0

Ver mi respuesta; resulta que los beans con alcance de solicitud se almacenan en caché por RequestScope en los atributos de solicitud. ¡Quien sabe! – Cameron

0

Una opción es sustituir la inyección de un proxy de ámbito 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(); 
    } 
} 

que asegurará la primavera que se pide para el bean sólo una vez por pedido, eliminar cualquier sobrecarga debido al proxy de ámbito, y acortar pila rastros Es menos flexible (en el sentido de que no puede compartir arbitrariamente las referencias al bean con ámbito de la solicitud y hacer que el proxy del alcance use el bean correcto), pero es posible que no necesite la flexibilidad.

Cuestiones relacionadas