9

私はSpring 3を使用して準大規模なアプリケーションに取り組んでおり、一度に何百人ものユーザーを投入するとパフォーマンスの問題が発生します. Spring の AOP プロキシを使用していくつかのリクエスト スコープ Bean を使用しています。これらの Bean のいずれかでメソッドを呼び出すたびに、CGLIB インターセプターが呼び出され、AbstractBeanFactory.getBean() が呼び出され、add() が呼び出されることがわかります。既存の Spring Bean の同期セット。この add() は同期されているため、同じリストへの追加を待機している何千もの呼び出しがあると、サーバーが効果的にロックされます。

リクエストスコープのBeanを使用してこれを回避する方法はありますか? Bean がインターフェイスを実装している場合は CGLIB が使用されないことを Spring のドキュメントで読みました (http://static.springsource.org/spring/docs/2.0.0/reference/aop.html#d0e9015) が、私のリクエストはスコープ Bean です。すべてが1つ(実際には同じもの)を実装しており、それはまだ起こっています。また、Bean のフィールドの一部は特定のリクエストに対してアプリの一部で計算され、SpEL を使用して同じリクエスト中にアプリの別の部分で値を取得するため、Bean をリクエスト スコープにする必要があります。Bean プロトタイプをスコープ化した場合、SpEL を使用して 2 回目に取得したときに新しいオブジェクトが得られると思います。

これは私の問題を示すコードサンプルです。私が問題を抱えている正確な場所を説明するコメントについては、最後の 2 行を参照してください。

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

私のアイデアは次のいずれかです。

  • Bean で行われたすべてのメソッド呼び出しをプロキシせずに、スコープ指定された Spring Bean リクエストを作成できますか? そして、すべてのメソッドを「最終」としてマークせずに?

また...

  • Spring の Bean ファクトリをオーバーライドして、AbstractBeanFactory.getBean() を呼び出す前に Bean がキャッシュされているかどうかを確認する Bean キャッシュを実装できますか? もしそうなら、カスタム Bean ファクトリーを使用するように Spring をどこで構成すればよいでしょうか?
4

3 に答える 3

4

結局のところ、Spring は実際にはリクエスト スコープ Bean をリクエスト属性にキャッシュします。興味があれば、RequestScope が拡張する AbstractRequestAttributesScope を見てください。

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

そのため、Aop プロキシのために、すべての Bean メソッド呼び出しで AbstractBeanFactory.getBean() が呼び出されますが、Bean が要求属性でまだ見つからない場合にのみ、Spring がその同期セットに追加されます。

リクエスト スコープの Bean ですべてのメソッド呼び出しのプロキシを回避しても複雑さは軽減されますが、このキャッシュが適切に配置されていれば、パフォーマンスへの影響は最小限に抑えられます。大量のリクエスト スコープ Bean が必要な場合でも、一度に大量のリクエストを処理する場合は、パフォーマンスの低下に対処する必要があると思います。

于 2011-01-26T23:27:35.983 に答える
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" />

于 2011-01-24T14:08:03.720 に答える
0

1 つのオプションは、スコープ プロキシの注入を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 はリクエストごとに 1 回だけ Bean を要求され、スコープ指定されたプロキシによるオーバーヘッドがなくなり、スタック トレースが短縮されます。柔軟性は劣りますが (要求スコープ Bean への参照を任意に共有できず、スコープ プロキシに適切な Bean を使用させることができないという点で)、柔軟性は必要ないかもしれません。

于 2011-01-26T19:57:31.080 に答える