Prototype Bean doesn't get autowired as expected

為{幸葍}努か 提交于 2019-11-29 05:21:39

问题


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() {

    }

}

As you can see, I'm trying to figure out whether a new TestClass has 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 @Autowired object can only be @Singleton? How does @Scope work then?

EDIT:

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

I tried @Valerio Vaudi solution, registered as Scope(scopeName = "request"). Here is the three time result when I visit "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 Rusev solution @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.

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


回答1:


all the answers above are correct. Controller by default is singleton and the injected testClass is instantiated once, because default scoped proxy mode is DEFAULT from 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

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

@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 testClass will not be really a TestClass bean but proxy to TestClass bean and this proxy will understand the prototype scope and will return new instance every time is needed.




回答2:


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

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

@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 TestClass bean is requested, Spring knowing that it is @Prototype, will create a new instance and return it.

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




回答3:


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

That's why it is enough to instantiate only one TestClass instance for the only TestController instance.

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




回答4:


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):

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

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




回答5:


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.

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.

In your case you use the annotation style that actually is the default way for web layer starting 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.

A solution of your case may be use request scope.

Update I 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

<?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:

and the my log screen:

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



来源:https://stackoverflow.com/questions/36278595/prototype-bean-doesnt-get-autowired-as-expected

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!