web-dev-qa-db-fra.com

Le prototype Bean n'est pas câblé automatiquement comme prévu

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

    }

}

Comme vous pouvez le voir, j'essaie de savoir si un nouveau TestClass a été injecté lors de la visite "xxx/test". "new test class constructed." A été imprimé une seule fois (la première fois que j'ai déclenché "xxx/test") alors que je m'attendais à ce qu'il soit imprimé également. Cela signifie-t-il que l'objet @Autowired Ne peut être que @Singleton? Comment fonctionne @Scope?

MODIFIER:

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

J'ai essayé la solution @Valerio Vaudi, Enregistrée en tant que Scope(scopeName = "request"). Voici le résultat trois fois lorsque je visite "xxx/test"

(première fois)

  • construction d'une nouvelle classe de tests.
  • nul

(seconde)

  • nul

(troisième)

  • nul

Je ne comprends pas pourquoi le résultat est nul car il n'en reconstruit pas un nouveau chaque fois que je l'utilise.

J'ai ensuite essayé la solution @Nikolay Rusev@Scope("prototype"):

(premier)

  • nouveau construit.
  • nouveau construit.
  • nul

(seconde)

  • nouveau construit.
  • nouveau construit.
  • nul

(troisième)

  • nouveau construit.
  • nouveau construit.
  • nul

C'est assez facile à comprendre car chaque fois que je l'utilise (TestClass), Spring en régénère automatiquement une nouvelle instance. Mais la première scène que je ne comprends toujours pas car elle semble ne conserver qu'une seule nouvelle instance pour chaque requête.

Le véritable objectif est: Dans chaque cycle de vie de la demande, un nouveau testClass est requis (si nécessaire), et un seul est requis. En ce moment, il semble que seule la solution ApplicationContext soit faisable (que je connaissais déjà), mais je veux juste savoir si cela pourrait être fait automatiquement en utilisant @Component + @Scope + @Autowired.

24
Kim

toutes les réponses ci-dessus sont correctes. Le contrôleur par défaut est singleton et le testClass injecté est instancié une fois, car le mode proxy de portée par défaut est DEFAULT de doc de printemps .

public abstract ScopedProxyMode proxyMode Spécifie si un composant doit être configuré en tant que proxy de portée et, dans l'affirmative, si le proxy doit être basé sur une interface ou sur une sous-classe. La valeur par défaut est ScopedProxyMode.DEFAULT, ce qui indique généralement qu'aucun proxy de portée ne doit être créé, sauf si une valeur par défaut différente a été configurée au niveau de l'instruction d'analyse des composants.

Analogue à la prise en charge dans Spring XML.

Voir aussi: ScopedProxyMode Par défaut: org.springframework.context.annotation.ScopedProxyMode.DEFAULT

si vous voulez qu'une nouvelle instance soit injectée à chaque fois que vous en avez besoin, vous devez changer votre TestClass en:

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

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

    public void doSomething() {

    }

}

avec cette configuration supplémentaire, le testClass injecté ne sera pas vraiment un bean TestClass mais un proxy vers le bean TestClass et ce proxy comprendra la portée prototype et retournera une nouvelle instance à chaque fois est nécessaire.

23
Nikolay Rusev

Comme mentionné, le contrôleur est par défaut singleton, c'est pourquoi l'instanciation et l'injection de TestClass n'est effectuée qu'une seule fois lors de sa création.

La solution peut être d'injecter le contexte de l'application et d'obtenir le bean manuellement:

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

Désormais, lorsqu'un bean TestClass est demandé, Spring sachant qu'il s'agit de @Prototype, Crée une nouvelle instance et la renvoie.

Une autre solution consiste à rendre le contrôleur @Scope("prototype").

5
Alex Salauyou

Les contrôleurs Spring sont des singletons par défaut (ce qui est OK en raison de leur nature sans état), ainsi que les autres beans Spring.

C'est pourquoi il suffit d'instancier une seule instance TestClass pour la seule instance TestController.

Il est facile d'instancier TestClass une fois de plus - il suffit de l'injecter dans un autre contrôleur ou de sortir du contexte par programmation

4
Cootri

Le point clé à entendre est que le bean restController est un singleton et Spring ne créera qu'une seule instance de ce bean lors de la création du bean.

Lorsque vous imposez une portée de bean prototype, Spring instanciera un nouveau bean pour chaque point DI. En d'autres termes, si vous configurez un bean deux ou n fois via xml ou Java-config, ce bean aura une nouvelle instance de votre bean à portée de prototype.

Dans votre cas, vous utilisez le style d'annotation qui est en fait la méthode par défaut pour la couche Web à partir du printemps 3.x.

Une possibilité d'injecter un bean frais peut être obtenue avec un bean limité à la session, mais à mon avis, si votre cas d'utilisation est un reste WS que je considère comme apatride, l'utilisation de la session à mon avis est un mauvais choix.

Une solution de votre cas peut être d'utiliser la portée de la demande.

Mise à jour J'écris aussi juste un exemple 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());
        }
    }

le mon 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>

l'écran bowser:

enter image description here

et l'écran mon journal:

enter image description here

Je ne sais pas pourquoi cela ne fonctionne pas pour vous, vous avez probablement oublié une configuration.

J'espère que cette solution plus détaillée peut vous aider à comprendre comment résoudre le problème

2
Valerio Vaudi

Vous ne pouvez pas câbler automatiquement le bean prototype (eh bien, vous pouvez, mais le bean sera toujours le même) ... câblez automatiquement ApplicationContext et obtenez manuellement une instance du bean prototype nécessaire (par exemple dans le constructeur):

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

De cette façon, vous êtes sûr d'obtenir une nouvelle instance de TestClass.

2
Matteo Baldi