Проблемы с производительностью при использовании большого количества bean-объектов в области запросов AOP - PullRequest
9 голосов
/ 23 января 2011

Я работаю над полуразмерным приложением, использующим Spring 3, и сталкиваюсь с проблемами производительности, когда в него одновременно бросают сотни пользователей.Я использую несколько bean-объектов в области запроса, используя прокси Spring AOP, и я вижу, что каждый раз, когда я вызываю какой-либо метод для одного из этих bean-компонентов, вызывается перехватчик CGLIB, который затем вызывает AbstractBeanFactory.getBean (), который вызывает add () дляСинхронизированный набор существующих бинов Spring.Поскольку метод add () синхронизирован, он эффективно блокирует сервер, когда к нему обращаются тысячи вызовов, ожидающих добавления в тот же список.

Есть ли способ обойти это, используя bean-объекты области запроса?Я прочитал в документации Spring, что CGLIB не используется, если бин реализует какой-либо интерфейс (http://static.springsource.org/spring/docs/2.0.0/reference/aop.html#d0e9015), но все бины области моего запроса реализуют один (фактически один), и это все еще происходит. И мне определенно нужно, чтобы бины былиобласть действия запроса, потому что некоторые из их полей вычисляются в одной части приложения для конкретного запроса, а затем я использую 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 в im?дополнить кеш Бина, который будет проверять, кэшируется ли бин перед вызовом AbstractBeanFactory.getBean ()?И если да, то где я могу настроить Spring для использования моей фабрики пользовательских компонентов?

Ответы [ 3 ]

4 голосов
/ 27 января 2011

Как оказалось, 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 () вызывается при каждом вызове метода bean-компонента из-за aop-прокси, он заставляет Spring добавлять этот синхронизированный набор, только если bean-компонент не был найден в атрибутах запроса.

Исключение прокси при каждом вызове метода для моих bean-объектов в области объема запросов по-прежнему уменьшит сложность, но с этим кэшированием влияние на производительность будет минимальным. Я думаю, что медленная производительность - это то, с чем мне придется смириться, если я захочу тонну бобов в области запросов и по-прежнему обслуживать тонну запросов за раз.

2 голосов
/ 24 января 2011

Интересный вопрос.

Оказывается, что прокси-сервер области Spring не кэширует разрешенные объекты, поэтому каждый доступ к прокси-серверу вызывает вызов getBean().

В качестве обходного пути вы можете создатьпрокси-сервер области кэширования бедного человека, что-то вроде этого (непроверенный, целевой бин должен быть областью запроса, но без <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 голосов
/ 26 января 2011

Один из вариантов - заменить внедрение прокси-объекта с областью действия 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-компоненте), но вам может не потребоваться гибкость.

...