java Prototype Bean 没有按预期自动装配

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/36278595/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 01:12:04  来源:igfitidea点击:

Prototype Bean doesn't get autowired as expected

javaspringspring-mvcspring-web

提问by Kim

TestController.java

测试控制器.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

测试类.java

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

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

    public void doSomething() {

    }

}

As you can see, I'm trying to figure out whether a new TestClasshas been injected when visit "xxx/test". "new test class constructed."got printed only once(first time I triggered "xxx/test") while I was expecting it printed equally. Is that mean @Autowiredobject can only be @Singleton? How does @Scopework then?

正如你所看到的,我试图弄清楚TestClass在访问“xxx/test”时是否注入了一个新的。"new test class constructed."只打印了一次(我第一次触发“xxx/test”),而我期待它同样打印。这意味着@Autowired对象只能是@Singleton吗?那怎么操作@Scope呢?

EDIT:

编辑:

TestController.java

测试控制器.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());
    }
}

I tried @Valerio Vaudisolution, registered as Scope(scopeName = "request"). Here is the three time result when I visit "xxx/test"

我尝试了@Valerio Vaudi解决方案,注册为Scope(scopeName = "request"). 这是我访问“xxx/test”时的三次结果

(first time)

(第一次)

  • new test class constructed.
  • null
  • 构建了新的测试类。
  • 空值

(second)

(第二)

  • null
  • 空值

(third)

(第三)

  • null
  • 空值

I don't understand why the result is null since it doens't reconstruct a new one each time I use it.

我不明白为什么结果为空,因为我每次使用它时都不会重建一个新的结果。

Then I tried @Nikolay Rusevsolution @Scope("prototype"):

然后我尝试了@Nikolay Rusev解决方案@Scope("prototype")

(first)

(第一的)

  • new one constructed.
  • new one constructed.
  • null
  • 新建造的。
  • 新建造的。
  • 空值

(second)

(第二)

  • new one constructed.
  • new one constructed.
  • null
  • 新建造的。
  • 新建造的。
  • 空值

(third)

(第三)

  • new one constructed.
  • new one constructed.
  • null
  • 新建造的。
  • 新建造的。
  • 空值

This is rather easy to understand since each time I use it(TestClass), Spring auto-regenerate a new instance of it. But the first scene I still cannot understand since it seems to retain only one new instance for each request.

这很容易理解,因为每次我使用它(TestClass)时,Spring 都会自动重新生成它的一个新实例。但是第一个场景我仍然无法理解,因为它似乎只为每个请求保留一个新实例。

The real purpose is:In each request lifecycle, a new testClassis required(if needed), and only one is required. At this moment it seems only ApplicationContextsolution is feasible(which I already knew), but I just want to know if this could be done automatically by using @Component+ @Scope+ @Autowired.

真正的目的是:在每个请求生命周期中,testClass都需要一个new (如果需要),并且只需要一个。在这一刻,似乎只有ApplicationContext可行的解决方案(我已经知道了),但我只是想知道,如果这可以通过使用自动完成@Component+ @Scope+ @Autowired

回答by Nikolay Rusev

all the answers above are correct. Controller by default is singletonand the injected testClassis instantiated once, because default scoped proxy mode is DEFAULTfrom spring doc.

以上所有答案都是正确的。默认情况下控制器是singleton并且注入的testClass被实例化一次,因为默认范围代理模式DEFAULT来自spring doc

public abstract ScopedProxyMode proxyMode Specifies whether a component should be configured as a scoped proxy and if so, whether the proxy should be interface-based or subclass-based. Defaults to 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.

Analogous to support in Spring XML.

See Also: ScopedProxyMode Default: org.springframework.context.annotation.ScopedProxyMode.DEFAULT

public abstract ScopedProxyMode proxyMode 指定是否应将组件配置为范围代理,如果是,则代理应基于接口还是基于子类。默认为 ScopedProxyMode.DEFAULT,这通常表示不应创建作用域代理,除非在组件扫描指令级别配置了不同的默认值。

类似于 Spring XML 中的支持。

另请参见:ScopedProxyMode 默认值:org.springframework.context.annotation.ScopedProxyMode.DEFAULT

if you want new instance to be injected every time you need, you should change your TestClassto :

如果您希望每次需要时都注入新实例,您应该将您的更改TestClass为:

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

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

    public void doSomething() {

    }

}

with this additional configuration the injected testClasswill not be really a TestClassbean but proxy to TestClassbean and this proxy will understand the prototypescope and will return new instance every time is needed.

有了这个额外的配置,注入的testClass将不是一个真正的TestClassbean,而是 bean 的代理TestClass,这个代理将理解prototype范围并在每次需要时返回新实例。

回答by Alex Salauyou

As mentioned, controller is by default singleton, that's why instantiation and injection of TestClassis performed only once on its creation.

如前所述,控制器默认是单例的,这就是为什么TestClass在其创建时只执行一次实例化和注入。

Solution can be to inject application context and get the bean manually:

解决方案可以是注入应用程序上下文并手动获取 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();
    }
}

Now, when a TestClassbean is requested, Spring knowing that it is @Prototype, will create a new instance and return it.

现在,当TestClass请求一个bean时,Spring 知道它是@Prototype,将创建一个新实例并返回它。

Another solution is to make the controller @Scope("prototype").

另一个解决方案是制作控制器@Scope("prototype")

回答by Cootri

Spring controllers are singletons by default (which is OK due to their stateless nature), as well as the other Spring beans.

Spring 控制器在默认情况下是单例的(由于它们的无状态性质,这是可以的),以及其他 Spring bean。

That's why it is enough to instantiate only one TestClassinstance for the only TestControllerinstance.

这就是为什么TestClass对于唯一TestController实例仅实例化一个实例 就足够了。

It is easy to instantiate TestClassone more time - just inject it in another controller or get from the context programmatically

再次实例化很容易TestClass- 只需将其注入另一个控制器或以编程方式从上下文中获取

回答by Matteo Baldi

You cannot autowire prototype bean (well, you can but the bean will be always the same)... autowire the ApplicationContext and get an instance of the needed prototype bean manually (for example in the constructor):

您不能自动装配原型 bean(好吧,您可以,但 bean 将始终相同)...自动装配 ApplicationContext 并手动获取所需原型 bean 的实例(例如在构造函数中):

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

In this way you're sure of getting a new instance of TestClass.

通过这种方式,您肯定会获得一个新的 TestClass 实例。

回答by Valerio Vaudi

The key point hear is that the restController bean is a singleton and Spring will create only one instance of that bean during the creation of bean.

听到的关键点是 restController bean 是一个单例,Spring 将在创建 bean 期间只创建该 bean 的一个实例。

When you impose a prototype bean scope Spring will instance a new bean for every DI point. In other words if you configure a bean a two or n-times via xml or java-config this bean will have a fresh instance of your prototype-scoped bean.

当您强加原型 bean 范围时,Spring 将为每个 DI 点实例一个新 bean。换句话说,如果你通过 xml 或 java-config 配置一个 bean 两次或 n 次,这个 bean 将有一个新的原型作用域 bean 实例。

In your case you use the annotation style that actually is the default way for web layer starting spring 3.x.

在您的情况下,您使用的注释样式实际上是 web 层从 spring 3.x 开始的默认方式。

One possibility to inject a fresh bean may be achieved with a bean scoped on the session, but in my opinion if your use case is a rest WS that I consider stateless, the session use in my opinion is a bad choice.

注入新 bean 的一种可能性可以通过会话范围内的 bean 来实现,但在我看来,如果您的用例是我认为无状态的休息 WS,那么在我看来,会话使用是一个糟糕的选择。

A solution of your case may be use request scope.

您的情况的解决方案可能是使用请求范围。

UpdateI write also just a simple example

更新我也写了一个简单的例子

     @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());
        }
    }

the my pom.xml

我的 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>

the bowser screen:

鲍泽屏幕:

enter image description here

在此处输入图片说明

and the my log screen:

和我的日志屏幕:

enter image description here

在此处输入图片说明

I don't know why it don't work for you probably you had forgot some configuration.

我不知道为什么它对你不起作用,可能你忘记了一些配置。

I hope that this more detalied solution can help you to understand how solve the your problem

我希望这个更详细的解决方案可以帮助您了解如何解决您的问题