26

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.

Kim
  • 3,985
  • 4
  • 29
  • 53

5 Answers5

26

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.

Nikolay Rusev
  • 3,354
  • 1
  • 17
  • 27
7

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").

Alex Salauyou
  • 13,188
  • 4
  • 39
  • 67
4

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

Cootri
  • 3,486
  • 16
  • 27
2

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.

Matteo Baldi
  • 4,684
  • 9
  • 33
  • 44
2

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:

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

Valerio Vaudi
  • 3,649
  • 2
  • 21
  • 22
  • I've got a String property in TestClass, and there's getter and setter for that. I set property as some value in handler method, then I get that property, it returns `null`, why is that? I can see it constructs a new `TestClass` for exactly once for each request though. – Kim Mar 29 '16 at 12:09
  • Sorry but I don't understand the use case. Resuming you have a configuration like I suggest but now you have also a String property that you set in your handle method, but when you tray to retrive the data you get null. Consider that if you set a property in a method in your rest api method but then you get the value that you set in an other method is clear that you retrive null because the request is different and Spring inject a new bean that will have the property previously setted null – Valerio Vaudi Mar 29 '16 at 13:01
  • If you want expand the life of you injected bean you should consider to use the session scope. but if you build a rest api the api should be stateless and not statefull. But if your use case impose a conversation beetwen the interaction of rest api you should consider @Scope(scopeName = "session",proxyMode = ScopedProxyMode.TARGET_CLASS) – Valerio Vaudi Mar 29 '16 at 13:02
  • I hoe to understand well the your problem – Valerio Vaudi Mar 29 '16 at 13:03
  • I readed the your update and it is impessible. As soon as I update the my answare with the complete code of my poc in orther to show you the my complete basic configuration – Valerio Vaudi Mar 30 '16 at 16:43
  • The example in no way proves that the bean creation method was called. The autowired instance could very well have been created by Spring by simply calling the the default constructor on that class. – mlfan May 15 '17 at 05:28