Springのスコーププロキシとは何ですか?


21

私たちが知っているように春には、(機能を追加するためにプロキシを使用する@Transactionalと、@Scheduledたとえば)。2つのオプションがあります-JDK動的プロキシを使用する(クラスは空でないインターフェースを実装する必要があります)、またはCGLIBコードジェネレーターを使用して子クラスを生成します。proxyModeを使用すると、JDK動的プロキシとCGLIBのどちらかを選択できるといつも思っていました。

しかし、私の想定が間違っていることを示す例を作成することができました。

ケース1:

シングルトン:

@Service
public class MyBeanA {
    @Autowired
    private MyBeanB myBeanB;

    public void foo() {
        System.out.println(myBeanB.getCounter());
    }

    public MyBeanB getMyBeanB() {
        return myBeanB;
    }
}

プロトタイプ:

@Service
@Scope(value = "prototype")
public class MyBeanB {
    private static final AtomicLong COUNTER = new AtomicLong(0);

    private Long index;

    public MyBeanB() {
        index = COUNTER.getAndIncrement();
        System.out.println("constructor invocation:" + index);
    }

    @Transactional // just to force Spring to create a proxy
    public long getCounter() {
        return index;
    }
}

メイン:

MyBeanA beanA = context.getBean(MyBeanA.class);
beanA.foo();
beanA.foo();
MyBeanB myBeanB = beanA.getMyBeanB();
System.out.println("counter: " + myBeanB.getCounter() + ", class=" + myBeanB.getClass());

出力:

constructor invocation:0
0
0
counter: 0, class=class test.pack.MyBeanB$$EnhancerBySpringCGLIB$$2f3d648e

ここでは2つのことがわかります。

  1. MyBeanB一度だけインスタンス化されまし
  2. @Transactional機能を追加するためにMyBeanB、SpringはCGLIBを使用しました。

ケース2:

MyBeanB定義を訂正させてください:

@Service
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyBeanB {

この場合の出力は次のとおりです。

constructor invocation:0
0
constructor invocation:1
1
constructor invocation:2
counter: 2, class=class test.pack.MyBeanB$$EnhancerBySpringCGLIB$$b06d71f2

ここでは2つのことがわかります。

  1. MyBeanB3回インスタンス化されました。
  2. @Transactional機能を追加するためにMyBeanB、SpringはCGLIBを使用しました。

何が起こっているのか説明してもらえますか?プロキシモードは実際にはどのように機能しますか?

PS

ドキュメントを読みました:

/**
 * Specifies whether a component should be configured as a scoped proxy
 * and if so, whether the proxy should be interface-based or subclass-based.
 * <p>Defaults to {@link ScopedProxyMode#DEFAULT}, which typically indicates
 * that no scoped proxy should be created unless a different default
 * has been configured at the component-scan instruction level.
 * <p>Analogous to {@code <aop:scoped-proxy/>} support in Spring XML.
 * @see ScopedProxyMode
 */

しかし、それは私には分かりません。

更新

ケース3:

私はもう1つのケースを調査しましたMyBeanB

public interface MyBeanBInterface {
    long getCounter();
}



@Service
public class MyBeanA {
    @Autowired
    private MyBeanBInterface myBeanB;


@Service
@Scope(value = "prototype", proxyMode = ScopedProxyMode.INTERFACES)
public class MyBeanB implements MyBeanBInterface {

この場合の出力は次のとおりです。

constructor invocation:0
0
constructor invocation:1
1
constructor invocation:2
counter: 2, class=class com.sun.proxy.$Proxy92

ここでは2つのことがわかります。

  1. MyBeanB3回インスタンス化されました。
  2. @Transactional機能を追加するためにMyBeanB、SpringはJDK動的プロキシを使用しました。

トランザクション構成を教えてください。
Sotirios Delimanolis

@SotiriosDelimanolis特別な構成はありません
gstackoverflow

スコープ付きBeanや、SpringやJEEに含まれる他の種類のエンタープライズフレームワークマジックについては知りません。@SotiriosDelimanolisがそのことについて素晴らしい答えを書いたので、JDKとCGLIBプロキシについてのみコメントしたいと思います。1と2の場合、MyBeanBクラスはインターフェイスを拡張しないため、コンソールログにCGLIBプロキシインスタンスが表示されることは当然です。ケース3では、インターフェースを導入して実装し、その結果、JDKプロキシを取得します。導入テキストでこれを説明することもできます。
kriegaex

したがって、インターフェース以外のタイプの場合、本当に選択の余地はありません。JDKプロキシはインターフェースタイプに対してのみ機能するため、それらはCGLIBプロキシでなければなりません。ただし、Spring AOPを使用している場合は、インターフェイスタイプに対してもCGLIBプロキシを適用できます。これは、<aop:config proxy-target-class="true">またはを介して@EnableAspectJAutoProxy(proxyTargetClass = true)それぞれ設定されます。
kriegaex

@kriegaex Aspectjはプロキシ生成にCGlibを​​使用すると言いますか?
gstackoverflow

回答:


10

@Transactional動作用に生成されたプロキシは、スコープ付きプロキシとは異なる目的を果たします。

@Transactionalプロキシは、セッション管理動作を追加するために、特定のBeanをラップ1です。すべてのメソッド呼び出しは、実際のBeanに委任する前後にトランザクション管理を実行します。

あなたがそれを説明すると、それは次のようになります

main -> getCounter -> (cglib-proxy -> MyBeanB)

私たちの目的のために、基本的にその動作を無視できます(削除する@Transactionalと、cglibプロキシがないことを除いて、同じ動作が表示されます)。

@Scopeプロキシは動作が異なります。ドキュメントは述べています:

[...]スコープオブジェクトと同じパブリックインターフェイスを公開するが、関連するスコープ(HTTPリクエストなど)から実際のターゲットオブジェクトを取得し、メソッドコールを実際のオブジェクトに委任できるプロキシオブジェクトを注入する必要がある

Springが実際に行っているのは、プロキシーを表すあるタイプのファクトリーのシングルトンBean定義を作成することです。ただし、対応するプロキシオブジェクトは、呼び出しごとに実際のBeanのコンテキストをクエリします。

あなたがそれを説明すると、それは次のようになります

main -> getCounter -> (cglib-scoped-proxy -> context/bean-factory -> new MyBeanB)

以来MyBeanB、プロトタイプの豆で、コンテキストは常に新しいインスタンスを返します。

この回答の目的のためにMyBeanB

MyBeanB beanB = context.getBean(MyBeanB.class);

これは本質的に、Springが@Autowired注入ターゲットを満たすために行うことです。


最初の例では、

@Service
@Scope(value = "prototype")
public class MyBeanB { 

(アノテーションを介して)プロトタイプBean定義を宣言します。要素を@Scope持っていproxyModeます

コンポーネントをスコープ付きプロキシとして構成する必要があるかどうかを指定します。構成する場合は、プロキシをインターフェイスベースにするかサブクラスベースにするかを指定します。

デフォルトはですScopedProxyMode.DEFAULT。これは通常、コンポーネントスキャンの命令レベルで別のデフォルトが構成されていない限り、スコープ付きプロキシを作成しないことを示します。

したがって、Springは結果のBeanのスコープ付きプロキシを作成していません。あなたはその豆を

MyBeanB beanB = context.getBean(MyBeanB.class);

これでMyBeanB、Springによって作成された新しいオブジェクトへの参照があります。これは他のJavaオブジェクトと同様に、メソッドの呼び出しは参照されるインスタンスに直接移動します。

getBean(MyBeanB.class)再度使用した場合、Bean定義はプロトタイプBean用であるため、Springは新しいインスタンスを返します。あなたはそれをしていないので、あなたのすべてのメソッド呼び出しは同じオブジェクトに行きます。


2番目の例では、

@Service
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyBeanB {

cglibを介して実装されるスコープ付きプロキシを宣言します。SpringからこのタイプのBeanをリクエストすると

MyBeanB beanB = context.getBean(MyBeanB.class);

Spring MyBeanBはスコープ付きプロキシであることをMyBeanB認識しているため、MyBeanBメソッド呼び出しごとにタイプの実際のBeanを取得する方法を内部的に知っているAPI (つまり、すべてのパブリックメソッドを実装)を満たすプロキシオブジェクトを返します。

実行してみてください

System.out.println("singleton?: " + (context.getBean(MyBeanB.class) == context.getBean(MyBeanB.class)));

これはtrue、Springが(プロトタイプBeanではなく)シングルトンプロキシオブジェクトを返しているという事実にヒントを戻します。

メソッド呼び出しでは、Springはプロキシー実装内getBeanで、プロキシー定義と実際のMyBeanBBean定義を区別する方法を知っている特別なバージョンを使用します。これにより新しいMyBeanBインスタンスが返され(プロトタイプであるため)、Springはリフレクション(クラシックMethod.invoke)を介してメソッドの呼び出しをそのインスタンスに委任します。


3番目の例は、基本的に2番目の例と同じです。


自然のラップtransactional_proxyラップscoped_proxy:だからseсond場合のために、私は2つのプロキシ持っMyBeanB_beanをscoped_proxy-
gstackoverflow

scoped_proxyにCGLIBプロキシを、transactiona_proxyにJDK_Dynamic_proxyを設定することは可能ですか?
gstackoverflow '10

1
@gstackoverflow実行するとcontext.getBean(MyBeanB.class)、実際にはプロキシを取得せず、実際のBeanを取得します。@Autowiredプロキシを取得します(実際MyBeanBには、インターフェイスタイプの代わりに注入すると失敗します)。Spring getBean(MyBeanB.class)でINTERFACESを使用できる理由がわかりません。
Sotirios Delimanolis

1
@gstackoverflow忘れてください@Transactional@Autowired MyBeanBInterfaceスコーププロキシと、春は、プロキシオブジェクトを挿入します。getBean(MyBeanB.class)ただし、その場合、Springはプロキシを返さず、ターゲットBeanを返します。
Sotirios Delimanolis

1
それの価値が、これがあることを指摘委任パターン春内の豆に関して実装
ステファン・
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.