27

测试控制器.java

@RestController
public class TestController {

    @Autowired
    private TestClass testClass;

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

测试类.java

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

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

    public void doSomething() {

    }

}

如您所见,我试图确定TestClass访问“xxx/test”时是否注入了新的。"new test class constructed."只打印了一次(我第一次触发“xxx/test”),而我期望它打印一样。那意味着@Autowired对象只能是@Singleton吗?那怎么@Scope工作呢?

编辑:

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

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

(第一次)

  • 构建了新的测试类。
  • 无效的

(第二)

  • 无效的

(第三)

  • 无效的

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

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

(第一的)

  • 新建的。
  • 新建的。
  • 无效的

(第二)

  • 新建的。
  • 新建的。
  • 无效的

(第三)

  • 新建的。
  • 新建的。
  • 无效的

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

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

4

5 回答 5

29

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

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将不是真正的TestClassbean,而是 bean 的代理TestClass,这个代理将理解prototype范围,并在每次需要时返回新实例。

于 2016-03-29T09:09:41.523 回答
9

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

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

于 2016-03-29T08:07:07.257 回答
4

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

这就是为什么只TestClass为唯一的 TestController实例实例化一个实例就足够了。

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

于 2016-03-29T08:00:58.850 回答
2

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

当您施加原型 bean 范围时,Spring 将为每个 DI 点实例化一个新 bean。换句话说,如果您通过 xml 或 java-config 将 bean 配置两次或 n 次,则该 bean 将具有原型范围 bean 的新实例。

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

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

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

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

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

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

鲍泽屏幕:

在此处输入图像描述

和我的日志屏幕:

在此处输入图像描述

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

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

于 2016-03-29T10:10:28.713 回答
2

你不能自动装配原型bean(好吧,你可以,但bean总是一样的)......自动装配ApplicationContext并手动获取所需原型bean的实例(例如在构造函数中):

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

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

于 2016-03-29T08:08:18.190 回答