Spring nie wstrzykuje uslugi

0

Witam,
Mam bardzo dziwny problem z kontekstem Springa. Wstrzykiwanie nie działa. Servlet otrzymuje null. Korzystam z Apache Tomcat 8.

  1. Aplikacja skutecznie deployuje się (czyli czyta kontekst, inaczej Tomcat by krzyczal).
  2. Uruchomiłem autoskanowanie komponentów.
  3. Zarejestrowałem servlet w springu przez @Component.
  4. Wstrzyknąłem za pomocą @Inject. Próbowałem też przez @Autowired i nie udało się.
  5. Otrzymałem null. Co robię źle?
  6. Dokonałem walidacji XML w spring.xml. Przechodzi, raczej nie ma błędu składniowego.

Podejrzewam, że o czymś zapomniałem.

Moja konfiguracja Springa:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:security="http://www.springframework.org/schema/security"
       xmlns:jee="http://www.springframework.org/schema/jee"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:jpa="http://www.springframework.org/schema/data/jpa"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.1.xsd 
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd 
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd 
http://www.springframework.org/schema/data/p http://www.springframework.org/schema/p.xsd">
              
    <!-- scan for spring components -->
    <context:component-scan base-package="pl.hellospringioc" />
    <!-- end scan for spring components -->

</beans>

web.xml;

<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
    
    <!-- spring -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    
    <listener>
        <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
    </listener>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            /WEB-INF/spring.xml
        </param-value>
    </context-param>
    <!-- end spring -->
    
    <session-config>
        <session-timeout>
            30
        </session-timeout>
    </session-config>
    
</web-app>

Testowy servlet:

package pl.hellospringioc;

import java.io.IOException;
import java.io.PrintWriter;
import javax.inject.Inject;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import pl.hellospringioc.injectable.IBusinessService;


@WebServlet(name = "HelloWorldServlet", urlPatterns = {"/"})
@Component
public class HelloWorldServlet extends HttpServlet {

    @Inject
    private IBusinessService srv;
    
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");
        try (PrintWriter out = response.getWriter()) {
            /* TODO output your page here. You may use following sample code. */
            out.println("<!DOCTYPE html>");
            out.println("<html>");
            out.println("<head>");
            out.println("<title>Servlet HelloWorldServlet</title>");            
            out.println("</head>");
            out.println("<body>");
            out.println(srv.getHelloWorld());
            out.println("</body>");
            out.println("</html>");
        }
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
    }

    @Override
    public String getServletInfo() {
        return "Short description";
    }// </editor-fold>

}

Testowy service:

package pl.hellospringioc.injectable;

public interface IBusinessService {
    
    String getHelloWorld();
    
}
package pl.hellospringioc.injectable.impl;

import org.springframework.stereotype.Service;
import pl.hellospringioc.injectable.IBusinessService;

@Service
public class BusinessService implements IBusinessService {

    @Override
    public String getHelloWorld() {
        return "Hello from service";
    }

}

Mam podejrzenie, ze autoscan nie zadzialal. Nie rozumiem dlaczego.

0

Załącze jeszcze mój pom.xml. Pamietalem o zaliczeniu javax.inject.

<?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>pl.test</groupId>
    <artifactId>helloSpringIoC</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>helloSpringIoC</name>

    <properties>
        <endorsed.dir>${project.build.directory}/endorsed</endorsed.dir>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    
    <dependencies>
        <!-- spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>4.1.5.RELEASE</version>
        </dependency>
        
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-expression</artifactId>
            <version>4.1.5.RELEASE</version>
        </dependency>        
                
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>4.1.5.RELEASE</version>
        </dependency>
        
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>4.1.5.RELEASE</version>
        </dependency>
        
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.1.5.RELEASE</version>
        </dependency>
        
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>4.1.5.RELEASE</version>
        </dependency>
        
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>4.1.5.RELEASE</version>
        </dependency>             
        <!-- end spring -->
                
        <!-- jdbc -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>4.1.5.RELEASE</version>
        </dependency>
        <!-- end jdbc -->
                
        <!-- inject -->
        <dependency>
            <groupId>javax.inject</groupId>
            <artifactId>javax.inject</artifactId>
            <version>1</version>
        </dependency>
        <!-- end inject -->
        
        <!-- tomcat servlet api -->        
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-servlet-api</artifactId>
            <version>8.0.15</version>
            <scope>provided</scope>
        </dependency>
        <!-- end tomcat servlet api -->

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <compilerArguments>
                        <endorseddirs>${endorsed.dir}</endorseddirs>
                    </compilerArguments>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.3</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>2.6</version>
                <executions>
                    <execution>
                        <phase>validate</phase>
                        <goals>
                            <goal>copy</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${endorsed.dir}</outputDirectory>
                            <silent>true</silent>
                            <artifactItems>
                                <artifactItem>
                                    <groupId>javax</groupId>
                                    <artifactId>javaee-endorsed-api</artifactId>
                                    <version>7.0</version>
                                    <type>jar</type>
                                </artifactItem>
                            </artifactItems>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>
0

Krótka odpowiedź:

 <context:annotation-config/>

Długa odpowiedź: ale co ty właściwie chcesz osiągnąć? Bo robisz coś bardzo bardzo dziwnego. Dlaczego próbujesz tu używać gołego servletu a nie Spring MVC?

0

@Shalom Ale on nie potrzebuje annotation config gdy ma już component scan

0

@Shalom:
Offtop:
Nie chcę używać Servletu, ani Spring MVC. Po prostu na servlecie debuguje niedziałające IoC (stąd tyle namespace): docelowo aplikacja ma byc restowa (JAX-RS), REST działa, DI nie, To moje pierwsze kroki ze Springiem, pracowalem z innym DI.

W skrócie, chcę beania JAX-R oznaczyć jako @Component, a następnie wstrzyknąc do niego @Service springa za pomocą @Inject.

Na servlecie latwiej to zdebugowac, bo latwiej wejsc debugerem.

End offtop

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:security="http://www.springframework.org/schema/security"
       xmlns:jee="http://www.springframework.org/schema/jee"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:jpa="http://www.springframework.org/schema/data/jpa"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.1.xsd 
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd 
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd 
http://www.springframework.org/schema/data/p http://www.springframework.org/schema/p.xsd">
              
    <!-- scan for spring components -->
    <context:annotation-config/>
    <context:component-scan base-package="pl.hellospringioc" />
    <!-- end scan for spring components -->

</beans>

Logi:

08-Mar-2015 19:14:08.019 INFO [http-nio-8084-exec-273] org.apache.jasper.servlet.TldScanner.scanJars At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.
08-Mar-2015 19:14:08.037 INFO [http-nio-8084-exec-273] org.springframework.web.context.ContextLoader.initWebApplicationContext Root WebApplicationContext: initialization started
08-Mar-2015 19:14:08.097 INFO [http-nio-8084-exec-273] org.springframework.web.context.support.XmlWebApplicationContext.prepareRefresh Refreshing Root WebApplicationContext: startup date [Sun Mar 08 19:14:08 CET 2015]; root of context hierarchy
08-Mar-2015 19:14:08.128 INFO [http-nio-8084-exec-273] org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions Loading XML bean definitions from ServletContext resource [/WEB-INF/spring.xml]
08-Mar-2015 19:14:08.251 INFO [http-nio-8084-exec-273] org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.<init> JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
08-Mar-2015 19:14:08.284 INFO [http-nio-8084-exec-273] org.springframework.web.context.ContextLoader.initWebApplicationContext Root WebApplicationContext: initialization completed in 247 ms

Jest to bardzo dziwne, ale szukam dalej.

0

Zadeployowalem na GlassFishu: problem wciaz wystepuje. Obnizylem wersje do 4.0.6: nie pomoglo (w pom.xml i schema do 4.0 w spring.xml).

Wciaz nie widze rozwiazania.

0

A nie możesz sprawdzić DI w teście ?

 
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"/spring.xml"})
public class BusinessServiceTest {
@Autowired
private IBusinessService bs;
@Test
public void bsShouldNotBeNull() {
assertNotNull(bs);
}
}

0

Ok a ja chyba rozumiem co ty robisz. W przeciwieństwie do ciebie. Adnotacja webservlet jest obsługiwana przez serwer aplikacyjny i w runtime utworzy ci ten servlet. Spring łyknie swoje adnotacje i stworzy SWOJE ZARZĄDZANE OBIEKTY. W efekcie będziesz miał DWA obiekty tego typu i tylko jeden z nich, ten stworzony przez springa dostanie wstrzykniętą zależność.
Ale ty tego obiektu nigdzie nie używasz. Używasz tego stworzonego przez serwer aplikacyjny.

0

@NoZi:
Test eksplodował.

import org.junit.Test;
import static org.junit.Assert.*;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import pl.hellospringioc.injectable.IBusinessService;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
    "classpath:**/WEB-INF/spring.xml"})
public class BusinessServiceTest {

    @Autowired
    private IBusinessService bs;

    @Test
    public void bsShouldNotBeNull() {
        assertNotNull(bs);
    }
}

Tests in error:
bsShouldNotBeNull(pl.hellospringioc.injectable.impl.HelloSpringTest): Error creating bean with name 'pl.hellospringioc.injectable.impl.HelloSpringTest': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private pl.hellospringioc.injectable.IBusinessService pl.hellospringioc.injectable.impl.HelloSpringTest.bs; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [pl.hellospringioc.injectable.IBusinessService] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@JAVAX.inject.Inject()}


Dziwne. Jest jedna implementacja. Ta implementacja jest oznaczona jako @Service (czyli springowy bean). Więc qualifier raczej nie jest potrzebny. To oczywiste jakiej implementacji uzyc.

```java
package pl.hellospringioc.injectable.impl;

import org.springframework.stereotype.Service;
import pl.hellospringioc.injectable.IBusinessService;

@Service
public class BusinessService implements IBusinessService {

    @Override
    public String getHelloWorld() {
        return "Hello from service";
    }

}
package pl.hellospringioc.injectable;

public interface IBusinessService {
    
    String getHelloWorld();
    
}
0

@Shalom:
Uwzglednilem Twoje sugestie i czegos sie nauczylem.

@Component("myServlet")
public class MyServlet implements HttpRequestHandler {

    @Inject
    private IBusinessService srv;

    @Override
    public void handleRequest(HttpServletRequest hsr, HttpServletResponse hsr1) throws ServletException, IOException {
        hsr1.getWriter().println(srv.getHelloWorld());
    }

}

Taki servlet rozumie Springa. Widocznie mam bardzo podobny problem z JAX-RS: mam dwa komponenty.
a) jeden stworzony przez springowe fabryki
b) drugi przez kontener Spring (@Component)

Panowie, czy uwazacie, ze powinienem dac spokoj z JAX-RS przy Springu i przejsc na Spring MVC?

Wciaz test mi nie dziala, wiec bede wdzieczny za sugestie jesli zrobilem cos zle.

0

Moim zdaniem nie bardzo jest sens bawić sie w spinanie JAX-RS ze Spring DI skoro Spring MVC daje takie same możliwości i nie wymaga cudów na kiju do integracji.

0

Jeśli chodzi o test, to problem prawdopodobnie leży w ścieżce to xml'a. Jako próbę możesz spróbować takiej samej konfiguracji w Javie, @Configuration + @ComponentScan a w teście w @ContextConfiguration dajesz NazwaKlasy.class oznaczoną @Configuration. Zobacz czy wtedy Ci test przejdzie.

Jeśli chodzi o SpringMVC vs JAX-RS, to co kto lubi. Ja generalnie zawsze używam SpringMVC.

0

@NoZi; dziękuje za pomoc, ale na razie odpuszczam ten test.

Co do JAX-RS i JAX-WS ze Springiem znalazłem, że Apache CXF wspiera integracje ze Springiem bez cudów na kiju. Mam zamiar jeszcze spróbować w ten sposób.

0

Mam jeszcze jedno rozwiązanie. W przypadku endpointa np. JAX-WS czy JAX-RS wcale nie trzeba używać CXF, aby dobrze działało to ze Springiem.

Znalazłem następującą klasę:
http://docs.spring.io/spring-framework/docs/2.5.6/api/org/springframework/web/context/support/SpringBeanAutowiringSupport.html

Rozszerzenie endpointa np. JAX-RS pozwala użyć @Autowired i wstrzykiwac service springowy. Nie potrzebuej wiec Spring MVC do REST.

Panowie, jakie jest Wasze zdanie?

0

Ale po co kombinujesz? Co Ci się nie podoba w Spring MVC?

0

Spring MVC jest na pewno ok, jednak nie udalo mi sie go uruchomic w ciagu 1h. Jednak to kawal frameworka jest i trzeba sie go nauczyc.

JAX-RS bez problemu, po prostu kontekst Springa nie dzialal. Osobiscie preferuje JEE nad Springiem, ale powoli rozszerzam horyzonty poznajac nowe rozwiazania. Fajnie byloby popracowac nad jednym projektem z kims kto zna Spring MVC, ale srednio chce mi sie uczyc nowej technologii.

Z tego co przeczytalem powszechna praktyka jest wystawienie endpointow SOAP lub REST poprzez dodanie mozliwosci stosowania @Autowired w komponentach niespringowych. I podobno nie ma problemu. Jedyne co mnie wkurza, ze nie moge uzyc @Inject (JSR-330).

Spring MVC uzylbym gdybym chcial np. wystawic aplikacja portalowa extJS z jednoczesna obsluga wersji REST i html/js. Przy moim use-case raczej nie widze wartosci dodanej w sporze Spring MVC vs JAX-RS. Obydwa frameworki nadaja sie.

Pytanie czy jest to kombinowanie? Moim subiektywnym zdaniem skonfigurowanie JAX-RS i rozszerzenie klasy przez extends jest o niebo prostsze niz konfiguracja Spring MVC jak ktos sie nie zna na Springu.

0

Konfiguracja Springa jest dość prosta, i dziwne, że nie udało Ci się tego zrobić w 1h. Dodatkowo pisanie testów integracyjnych jest mega proste w Springu.

Skorzystaj z template'a @Shalom jak nie umiesz sobie poradzić z konfiguracją. Po ściągnięciu repo powinno śmigać
https://github.com/Pharisaeus/SpringScaffoldApplication

0

Wiesz co juz sobie poradzilem po prostu umiarkowanie widze wartosc dodana (a konfiguracja jest dosc nudna): obydwa frameworki raczej robia to samo. Nastepny projekt bedzie na SpringMVC dla urozmaicenia.

A jaki framework do SOAP uzywacie? W Spring w Akcji polecaja JAX-WS z rozszerzeniem klasy, o ktorej wspomnialem. Zastosowanie JAX-RS wydaje mi sie rownie poprawne.

1 użytkowników online, w tym zalogowanych: 0, gości: 1