web-dev-qa-db-ja.com

プロトタイプBeanが期待どおりに自動配線されない

TestController.Java

_@RestController
public class TestController {

    @Autowired
    private TestClass testClass;

    @RequestMapping(value = "/test", method = RequestMethod.GET)
    public void testThread(HttpServletResponse response) throws Exception {
        testClass.doSomething();
    }
}
_

TestClass.Java

_@Component
@Scope("prototype")
public class TestClass {

    public TestClass() {
        System.out.println("new test class constructed.");
    }

    public void doSomething() {

    }

}
_

ご覧のとおり、「xxx/test」にアクセスしたときに新しいTestClassが挿入されたかどうかを把握しようとしています。 _"new test class constructed."_は1回だけ印刷されました(初めて "xxx/test"をトリガーしたとき)が、同じように印刷されることを期待していました。 _@Autowired_オブジェクトは_@Singleton_にしかなれないという意味ですか? _@Scope_はどのように機能しますか?

編集:

TestController.Java

_@RestController
public class TestController {

    @Autowired
    private TestClass testClass;

    @RequestMapping(value = "/test", method = RequestMethod.GET)
    public void testThread(HttpServletResponse response) throws Exception {
        testClass.setProperty("hello");
        System.out.println(testClass.getProperty());
    }
}
_

Scope(scopeName = "request")として登録された_@Valerio Vaudi_ソリューションを試しました。 「xxx/test」にアクセスしたときの3回の結果を次に示します

(初めて)

  • 構築された新しいテストクラス。
  • ヌル

(秒)

  • ヌル

(三番)

  • ヌル

使用するたびに新しい結果が再構築されないため、結果がnullになる理由がわかりません。

次に、_@Nikolay Rusev_ solution @Scope("prototype")を試しました:

(最初)

  • 新しいものが構築されました。
  • 新しいものが構築されました。
  • ヌル

(秒)

  • 新しいものが構築されました。
  • 新しいものが構築されました。
  • ヌル

(三番)

  • 新しいものが構築されました。
  • 新しいものが構築されました。
  • ヌル

これは、使用するたびに(TestClass)、Springが新しいインスタンスを自動再生成するため、かなり理解しやすいです。しかし、リクエストごとに新しいインスタンスを1つしか保持していないように見えるため、最初のシーンはまだ理解できません。

実際の目的は:各リクエストのライフサイクルで、新しいtestClassが必要であり(必要な場合)、1つだけが必要です。現時点ではApplicationContext解しか実現できないようですが(これは既に知っています)、_@Component_ + _@Scope_ + _@Autowired_。

24
Kim

上記の答えはすべて正しいです。デフォルトのスコーププロキシモードはsingleton from spring doc であるため、コントローラーはデフォルトでtestClassであり、挿入されたDEFAULTは1回インスタンス化されます。

public abstract ScopedProxyMode proxyModeコンポーネントをスコーププロキシとして構成する必要があるかどうかを指定し、構成する場合は、プロキシをインターフェイスベースにするかサブクラスベースにするかを指定します。デフォルトはScopedProxyMode.DEFAULTです。これは、通常、コンポーネントスキャン命令レベルで別のデフォルトが設定されていない限り、スコーププロキシを作成しないことを示します。

Spring XMLのサポートに類似しています。

関連項目:ScopedProxyModeデフォルト:org.springframework.context.annotation.ScopedProxyMode.DEFAULT

必要なたびに新しいインスタンスを挿入する場合は、TestClassを次のように変更する必要があります。

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

    public TestClass() {
        System.out.println("new test class constructed.");
    }

    public void doSomething() {

    }

}

この追加の構成では、挿入されたtestClassは実際にはTestClass Beanではなく、TestClass Beanのプロキシになり、このプロキシはprototypeスコープを理解して戻ります。毎回新しいインスタンスが必要です。

23
Nikolay Rusev

前述のように、コントローラーはデフォルトでシングルトンであるため、TestClassのインスタンス化と挿入は、作成時に1回だけ実行されます。

解決策は、アプリケーションコンテキストを注入し、Beanを手動で取得することです。

_@RestController
public class TestController {

    @Autowired
    ApplicationContext ctx;

    @RequestMapping(value = "/test", method = RequestMethod.GET)
    public void testThread(HttpServletResponse response) throws Exception {
        ((TestClass) ctx.getBean(TestClass.class)).doSomething();
    }
}
_

これで、TestClass Beanが要求されると、Springは_@Prototype_であることを認識し、新しいインスタンスを作成して返します。

別の解決策は、コントローラーを@Scope("prototype")にすることです。

5
Alex Salauyou

Springコントローラーは、デフォルトではシングルトンであり(ステートレスであるため、問題ありません)、他のSpring Beanも同様です。

そのため、TestClassインスタンスのみに対してTestControllerインスタンスを1つだけインスタンス化するだけで十分です。

もう一度TestClassをインスタンス化するのは簡単です-別のコントローラーに挿入するか、プログラムからコンテキストから取得するだけです

4
Cootri

重要な点は、restController Beanはシングルトンであり、SpringはBeanの作成中にそのBeanのインスタンスを1つだけ作成するということです。

プロトタイプBeanスコープを課すと、SpringはDIポイントごとに新しいBeanをインスタンス化します。つまり、xmlまたはJava-configを使用してBeanを2回またはn回構成すると、このBeanにはプロトタイプスコープBeanの新しいインスタンスが含まれます。

あなたの場合、Spring 3.xから始まるWebレイヤーの実際のデフォルトの方法である注釈スタイルを使用します。

新しいBeanを注入する可能性の1つは、セッションでスコープされたBeanで達成される可能性がありますが、私の意見では、ユースケースがステートレスと見なすWSである場合、私の意見ではセッションの使用は悪い選択です。

あなたのケースの解決策は、使用リクエストのスコープかもしれません。

Update私も簡単な例を書いています

     @SpringBootApplication
     public class DemoApplication {

        public static void main(String[] args) {
            SpringApplication.run(DemoApplication.class, args);
        }

        @Bean
        @Scope(scopeName = "request",proxyMode = ScopedProxyMode.TARGET_CLASS)
        public RequestBeanTest requestBeanTest(){
            return new RequestBeanTest();
        }

    }

    class RequestBeanTest {
        public RequestBeanTest(){
            Random random = new Random();
            System.out.println(random.nextGaussian());
            System.out.println("new object was created");
        }

        private String prop;

        public String execute(){

            return "hello!!!";
        }

        public String getProp() {
            return prop;
        }

        public void setProp(String prop) {
            this.prop = prop;
        }
    }


    @RestController
    class RestTemplateTest {

        @Autowired
        private RequestBeanTest requestBeanTest;

        @RequestMapping("/testUrl")
        public ResponseEntity responseEntity(){
            requestBeanTest.setProp("test prop");

            System.out.println(requestBeanTest.getProp());
            return ResponseEntity.ok(requestBeanTest.execute());
        }
    }

my pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.Apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.Apache.org/POM/4.0.0 http://maven.Apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>demo</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.3.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <Java.version>1.8</Java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

クッパ画面:

enter image description here

と私のログ画面:

enter image description here

なぜ設定が機能しなかったのかわかりませんが、おそらく設定を忘れていたのでしょう。

このより詳細なソリューションが、問題の解決方法を理解するのに役立つことを願っています

2
Valerio Vaudi

プロトタイプBeanを自動配線することはできません(ただし、Beanは常に同じです)... ApplicationContextを自動配線し、必要なプロトタイプBeanのインスタンスを手動で(たとえば、コンストラクターで)取得します。

    TestClass test = (TestClass) context.getBean("nameOfTestClassBeanInConfiguration");

この方法で、TestClassの新しいインスタンスを確実に取得できます。

2
Matteo Baldi