java - beans - Prototype Bean no se activa automáticamente como se espera
spring bean session (5)
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() {
}
}
Como puede ver, estoy tratando de averiguar si se ha inyectado un nuevo TestClass
cuando visita "xxx / test". "new test class constructed."
se imprimió solo una vez (la primera vez que activé "xxx / test") mientras esperaba que se imprimiera por igual. ¿ @Autowired
significa que el objeto @Autowired
solo puede ser @Singleton
? ¿Cómo funciona @Scope
entonces?
EDITAR:
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());
}
}
@Valerio Vaudi
solución @Valerio Vaudi
, registrada como Scope(scopeName = "request")
. Aquí está el resultado de tres veces cuando visito "xxx / test"
(primera vez)
- Nueva clase de prueba construida.
- nulo
(segundo)
- nulo
(tercero)
- nulo
No entiendo por qué el resultado es nulo ya que no se reconstruye uno nuevo cada vez que lo uso.
Luego probé la solución @Nikolay Rusev
@Scope("prototype")
:
(primero)
- Una nueva construida.
- Una nueva construida.
- nulo
(segundo)
- Una nueva construida.
- Una nueva construida.
- nulo
(tercero)
- Una nueva construida.
- Una nueva construida.
- nulo
Esto es bastante fácil de entender, ya que cada vez que lo uso (TestClass), Spring genera automáticamente una nueva instancia. Pero la primera escena que aún no puedo entender, ya que parece conservar solo una nueva instancia para cada solicitud.
El propósito real es: En cada ciclo de vida de la solicitud, se requiere una nueva testClass
(si es necesaria), y solo se requiere una. En este momento parece que solo la solución ApplicationContext
es factible (lo que ya sabía), pero solo quiero saber si esto se podría hacer automáticamente usando @Component
+ @Scope
+ @Autowired
.
Como se mencionó, el controlador es por defecto singleton, es por eso que la creación de instancias e inyección de TestClass
se realiza solo una vez en su creación.
La solución puede ser inyectar el contexto de la aplicación y obtener el bean manualmente:
@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();
}
}
Ahora, cuando se solicita un bean TestClass
, Spring, sabiendo que es @Prototype
, creará una nueva instancia y la devolverá.
Otra solución es hacer el controlador @Scope("prototype")
.
El punto clave es que el bean restController es un singleton y Spring creará solo una instancia de ese bean durante la creación del bean.
Cuando se impone un alcance de bean prototipo, Spring creará un nuevo bean para cada punto DI. En otras palabras, si configura un bean dos o n veces a través de xml o java-config, este bean tendrá una nueva instancia de su bean de prototipo.
En su caso, usa el estilo de anotación que en realidad es la forma predeterminada para que la capa web comience la primavera 3.x.
Una posibilidad de inyectar un bean fresco puede lograrse con un ámbito de bean en la sesión, pero en mi opinión, si su caso de uso es un descanso WS que considero sin estado, el uso de la sesión en mi opinión es una mala elección.
Una solución de su caso puede ser usar el alcance de la solicitud.
Actualización escribo también solo un ejemplo simple
@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());
}
}
el mi 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>
la pantalla bowser:
y la pantalla de mi registro:
No sé por qué no funciona para usted, probablemente se le olvidó alguna configuración.
Espero que esta solución más detallada pueda ayudarlo a comprender cómo resolver su problema.
Los controladores Spring son singletons por defecto (lo cual está bien debido a su naturaleza sin estado), así como a los otros Spring Beans.
Por eso es suficiente crear una instancia de TestClass
para la única instancia de TestController
.
Es fácil crear una instancia de TestClass
una vez más, simplemente inyectarlo en otro controlador o obtener del contexto programáticamente
No puede autowire bean de prototipo (bueno, puede pero el bean será siempre el mismo) ... autowire ApplicationContext y obtenga una instancia del bean de prototipo necesario manualmente (por ejemplo, en el constructor):
TestClass test = (TestClass) context.getBean("nameOfTestClassBeanInConfiguration");
De esta manera, está seguro de obtener una nueva instancia de TestClass.
Todas las respuestas anteriores son correctas. El controlador por defecto es singleton
y el testClass
inyectado se testClass
una instancia una vez, porque el modo de proxy con ámbito predeterminado es DEFAULT
from spring doc .
public abstract ScopedProxyMode proxyMode Especifica si un componente debe configurarse como un proxy con ámbito y, de ser así, si el proxy debe estar basado en la interfaz o en la subclase. El valor predeterminado es ScopedProxyMode.DEFAULT, que generalmente indica que no se debe crear un proxy de ámbito a menos que se haya configurado un valor predeterminado diferente en el nivel de instrucción de exploración de componentes.
Análogo a soportar en Spring XML.
Ver también: ScopedProxyMode Predeterminado: org.springframework.context.annotation.ScopedProxyMode.DEFAULT
Si desea que se inyecte una nueva instancia cada vez que lo necesite, debe cambiar su TestClass
para:
@Component
@Scope(value="prototype", proxyMode=ScopedProxyMode.TARGET_CLASS)
public class TestClass {
public TestClass() {
System.out.println("new test class constructed.");
}
public void doSomething() {
}
}
con esta configuración adicional, el testClass
inyectado no será realmente un bean TestClass
sino un proxy para el bean TestClass
y este proxy entenderá el alcance del prototype
y devolverá una nueva instancia cada vez que sea necesario.