2016-03-29 12 views
18

TestController.javaPrototype Bean nie zostanie autowired jak oczekiwano

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

    } 

} 

Jak widać, próbuję dowiedzieć się, czy nowy TestClass został wstrzyknięto podczas wizyty "xxx/test". "new test class constructed." wydrukowano tylko jeden raz (pierwszy raz uruchomiłem "xxx/test"), podczas gdy spodziewałem się, że wydrukowane zostanie ono jednakowo. Czy to oznacza, że ​​obiekt @Autowired może być tylko @Singleton? Jak działa wtedy @Scope?

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

Próbowałem @Valerio Vaudi rozwiązanie, zarejestrowaną jako Scope(scopeName = "request"). Oto wynik trzy razem, kiedy odwiedzam "XXX/test"

(po raz pierwszy)

  • nowa klasa Test skonstruowany.
  • zerowy

(sekundy)

  • zerowy

(trzeciej)

  • zerowy

Nie rozumiem, dlaczego wynik jest zerowy, ponieważ nie rekonstruuje on nowego za każdym razem, gdy go używam.

Potem próbuje @Nikolay Rusev roztwór @Scope("prototype"):

(pierwszy)

  • nowej budowy.
  • nowy zbudowany.
  • zerowy

(sekundy)

  • nowej budowy.
  • nowy zbudowany.
  • zerowy

(trzeciej)

  • nowej budowy.
  • nowy zbudowany.
  • zerowy

Jest to dość łatwe do zrozumienia, ponieważ za każdym razem go używać (TestClass), Wiosna automatycznej regeneracji nową instancję niego. Ale pierwsza scena wciąż nie mogę zrozumieć, ponieważ wydaje się zachować tylko jedną nową instancję dla każdego żądania.

Rzeczywistym celem jest: W każdym cyklu życia żądania, wymagany jest nowy testClass (wymagany) i wymagany jest tylko jeden. W tej chwili wydaje się, że tylko rozwiązanie jest możliwe (o czym już wiedziałem), ale chcę tylko wiedzieć, czy można to zrobić automatycznie, używając @Component + @Scope + @Autowired.

+0

nie mogę dostać to, gdzie przychodzi trzeci NULL? opublikuj swój pełny kod TestClass ... –

Odpowiedz

4

Sterowniki sprężyn są domyślnie skonfigurowane jako pojedyncze (co jest w porządku ze względu na ich bezpaństwowy charakter), a także z innych fasoli Spring.

Dlatego wystarczy utworzyć instancję tylko jedną instancję TestClass dla jedynej instancji TestController.

Jest łatwy do wystąpienia TestClass jeden więcej czasu - wystarczy wstrzyknąć go w innym sterownikiem lub uzyskać z kontekstu programowego

3

jak wspomniano, kontroler jest domyślnie Singleton, dlatego instancji i wstrzyknięcie TestClass odbywa się tylko raz na jego stworzenie.

rozwiązanie można ręcznie wprowadzić kontekst aplikacji i uzyskać Fasola:

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

Teraz, gdy TestClass fasola jest wymagane, Wiosna, wiedząc, że jest to @Prototype, stworzy nową instancję i odesłać go.

Kolejnym rozwiązaniem jest wykonanie sterownika @Scope("prototype").

2

Nie można autowire prototyp Bean (dobrze, można jednak fasola będzie zawsze taka sama) ... autowire się ApplicationContext i uzyskać instancję potrzebne prototypu fasoli ręcznie (na przykład w konstruktorze):

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

W ten sposób masz pewność, że otrzymasz nową instancję TestClass.

14

wszystkie powyższe odpowiedzi są poprawne. Domyślnie kontrolerem jest singleton, a wstrzyknięty testClass jest tworzony jeden raz, ponieważ domyślnym trybem proxy o ustalonej orientacji jest DEFAULT z spring doc.

public abstract ScopedProxyMode ProxyMode Określa, czy składnik powinien być skonfigurowany jako określania zakresów pełnomocnika, a jeśli tak, to czy pełnomocnik powinien być interfejs oparty na bazie lub podklasy. Domyślnie jest to ScopedProxyMode.DEFAULT, co zwykle oznacza, że ​​nie powinno się tworzyć pełnozakresowego proxy o ustalonej liczbie , chyba że skonfigurowano inne ustawienie domyślne na poziomie instrukcji skanowania składników.

Analogiczna do obsługi w Spring XML.

Zobacz także: ScopedProxyMode Wartość domyślna: org.springframework.context.annotation.ScopedProxyMode.DEFAULT

jeśli chcesz nową instancję być wstrzykiwany za każdym razem trzeba, należy zmienić TestClass do:

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

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

    public void doSomething() { 

    } 

} 

z tej dodatkowej konfiguracji wstrzyknięta testClass nie będzie naprawdę TestClass fasola ale proxy do fasoli TestClass i ten serwer proxy zrozumie zakres prototype i zwróci nową instancję za każdym razem, gdy będzie potrzebna.

+0

Najlepsze i łatwe rozwiązanie. – Arundev

2

Kluczowym punktem jest to, że bean restController jest singletonem, a Spring utworzy tylko jedno wystąpienie tego komponentu podczas tworzenia komponentu bean.

Po nałożeniu prototypowego zakresu komponentu bean, instancja będzie wstawiać nowy komponent bean dla każdego punktu DI. Innymi słowy, jeśli skonfigurujesz komponent bean dwa lub kilka razy za pomocą xml lub java-config, ten komponent bean będzie miał nową instancję komponentu o zakresie prototypowym.

W twoim przypadku używasz stylu adnotacji, który jest domyślnym sposobem dla warstwy internetowej rozpoczynającej się na wiosnę 3.x.

Jedną z możliwości wstrzyknięcia świeżej fasoli można osiągnąć przy pomocy zakresu ziaren w trakcie sesji, ale moim zdaniem, jeśli Twój przypadek użycia jest odpoczynkiem WS, który uważam za bezpaństwowy, użycie sesji w mojej opinii jest złym wyborem.

Rozwiązaniem Twojego przypadku może być zakres zastosowania żądania.

Aktualizacja piszę też po prostu prosty przykład

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

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> 

ekran Bowser:

enter image description here

i t on ekran dziennika:

enter image description here

Nie wiem, dlaczego to nie działa dla Ciebie, prawdopodobnie trzeba było zapomniał trochę konfigurację.

Mam nadzieję, że więcej detalied rozwiązanie może pomóc zrozumieć, w jaki sposób rozwiązać problem

+0

Mam właściwość String w TestClass, i istnieje getter i setter do tego. Ustawiam właściwość jako pewną wartość w procedurze obsługi, potem otrzymuję tę właściwość, zwraca 'null', dlaczego tak jest? Widzę, że konstruuje on nową 'TestClass' dla dokładnie jednego razu dla każdego żądania. – Kim

+0

Przepraszam, ale nie rozumiem przypadku użycia. Wznawiając masz konfigurację podobną do tej, którą proponuję, ale teraz masz również właściwość String, którą ustawiłeś w swojej metodzie obsługi, ale kiedy podasz dane do pobrania danych, otrzymasz wartość null. Weź pod uwagę, że jeśli ustawisz właściwość w metodzie w restuacyjnej metodzie api, ale otrzymasz wartość ustawioną w innej metodzie, jasne jest, że pobierasz wartość zerową, ponieważ żądanie jest inne i Spring wstrzykuje nowy komponent bean, który będzie miał właściwość poprzednio ustawiony null –

+0

Jeśli chcesz przedłużyć żywotność wstrzykniętego komponentu, powinieneś rozważyć użycie zakresu sesji.ale jeśli zbudujesz api odpoczynku, api powinno być bezpaństwowcem, a nie statefull. Ale jeśli twój przypadek użycia narzuca rozmowę z interakcji api odpoczynku, powinieneś rozważyć: @Scope (scopeName = "session", proxyMode = ScopedProxyMode.TARGET_CLASS) –

Powiązane problemy