springservlet - Spring DI: la propiedad de Autowired es nula en un servicio REST
spring jersey (4)
¡Usted tenía razón! Parece que el problema es que Jersey ignora por completo a Spring y crea una instancia de su propio objeto. Para hacer que Jersey fuera consciente de las creaciones de objetos Spring (mediante inyección de dependencia) tuve que integrar Spring + Jersey.
Integrar:
Añadir dependencias maven
<dependency> <groupId>com.sun.jersey.contribs</groupId> <artifactId>jersey-spring</artifactId> <version>1.17.1</version> <exclusions> <exclusion> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> </exclusion> <exclusion> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> </exclusion> <exclusion> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> </exclusion> <exclusion> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> </exclusion> </exclusions> </dependency>
Use SpringServlet para jersey-servlet en
web.xml
<servlet> <servlet-name>jersey-servlet</servlet-name> <servlet-class> com.sun.jersey.spi.spring.container.servlet.SpringServlet </servlet-class> <load-on-startup>1</load-on-startup> </servlet>
Ahora el @Autowired funciona correctamente y el objeto ya no es nulo.
Estoy un poco confundido acerca de las exclusiones que tengo que usar en Maven cuando uso la dependencia de jersey-spring, pero ese es otro problema :)
¡Gracias!
Estoy empezando con Spring DI, pero estoy luchando con la inyección de dependencia y lo peor es que ni siquiera estoy seguro de por qué, ya que me parece bien. ¡Espero que ustedes me puedan ayudar!
El problema es que una propiedad anotada como @Autowired siempre es nula
Tengo algunos proyectos con estructura Maven:
- com.diegotutor.lessondeliver
- com.diegotutor.utility
Estoy ejecutando los ejemplos sobre Tomcat 7
Estoy usando las siguientes dependencias en mi pom.xml
:
- contexto de primavera 3.2.4
- web de primavera 3.2.4
- jersey-server 1.17.1
- jersey-core 1.17.1
- jersey-servlet 1.17.1
La idea simple es tener un servicio REST que, a través de la inyección de dependencia, pueda imprimir el valor de una propiedad ubicada en un archivo de configuración ubicado en: D:/configuracion.conf.
En com.diegotutor.utility tengo la siguiente interfaz:
package com.diegotutor.utility;
public interface ConfigService {
public String getProperty(final String propertyName);
}
Implementado por:
package com.diegotutor.utility.impl;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.Properties;
import com.diegotutor.utility.ConfigService;
public class PropertyFileConfigService implements ConfigService{
Properties prop;
public PropertyFileConfigService (final InputStream input) throws IOException {
if(input == null) {
throw new IllegalArgumentException("Input stream can''t be null");
}
prop = new Properties();
prop.load(input);
}
public PropertyFileConfigService (final String fileName) throws IOException {
final FileInputStream input = new FileInputStream(fileName);
prop = new Properties();
prop.load(input);
}
public PropertyFileConfigService(final Reader input) throws IOException {
prop = new Properties();
prop.load(input);
}
public String getProperty(final String propertyName) {
return prop.getProperty(propertyName);
}
}
Y en com.diegotutor.lessondeliver tengo el servicio RESTful en el que me gustaría usar una instancia inyectada de ConfigService:
package com.diegotutor.lessondeliver;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.diegotutor.utility.ConfigService;
@Path("/")
@Component
public class HelloWorld {
private static final Log log = LogFactory.getLog(HelloWorld.class);
@Autowired
private ConfigService configService;
@Path("/helloworld")
@GET
@Produces(MediaType.TEXT_PLAIN)
public String getHello() {
String host = configService.getProperty("host");
return "Hello World! HOST" + host;
// configService IS NULL!!
//SO IT THROWS A NULLPOINTER EXCEPTION WHEN INVOKING getProperty ON IT
}
}
Finalmente, en /com.diegotutor.lessondeliver/src/main/webapp/WEB-INF/service-beans.xml
tengo el siguiente archivo de contexto de aplicación XML, donde uso la implementación de ConfigService (PropertyFileConfigService)
inyectando en ella la ruta para la archivo de configuración para leer:
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="configService" class="com.diegotutor.utility.impl.PropertyFileConfigService">
<constructor-arg type="java.lang.String"
value="D:/configuracion.conf" />
</bean>
<context:component-scan base-package="com.diegotutor" />
</beans>
Obviamente, he especificado en el web.xml
de esta aplicación web com.diegotutor.lessondeliver
que deseo service-beans.xml
como ConfigLocation y un oyente ContextLoaderListener
, y el servicio RESTful se basa en ServletContainer
Si estoy especificando context: component-scan para buscar Componentes en com.diegotutor
como se sugiere here y estoy forzando la creación de objetos a través de Spring al no usar ninguna Declaración nueva como se sugiere here , ¿Por qué obtengo el servicio de configuración anotado como nulo? ¿Por qué Spring no puede inyectar una instancia de com.diegotutor.utility.impl.PropertyFileConfigService
?
¡Cualquier ayuda será muy apreciada!
Gracias
EDITADO:
Según lo solicitado, mi web.xml es el siguiente:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<display-name>com.diegotutor.lessondeliver</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/service-beans.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>jersey-servlet</servlet-name>
<servlet-class>
com.sun.jersey.spi.container.servlet.ServletContainer
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>jersey-servlet</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
</web-app>
Integración Spring con Jersey 2 ( org.glassfish.*
):
Maven
Algunas dependencias pueden ser innecesarias, verifíquelas y elimínelas después de que las cosas funcionen.
<properties>
<jersey.version>2.5</jersey.version>
</properties>
<!-- Jersey -->
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-server</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<!-- if your container implements Servlet API older than 3.0, use "jersey-container-servlet-core" -->
<artifactId>jersey-container-servlet</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-client</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
<artifactId>jersey-test-framework-provider-inmemory</artifactId>
<version>${jersey.version}</version>
</dependency>
<!-- Jersey + Spring -->
<dependency>
<groupId>org.glassfish.jersey.ext</groupId>
<artifactId>jersey-spring3</artifactId>
<version>${jersey.version}</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</exclusion>
</exclusions>
</dependency>
web.xml
<servlet>
<servlet-name>my-rest-service</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>my.package.with.rest.services</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>my-rest-service</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
applicationContext.xml
Durante la actualización de Spring tuve que moverlo de /main/webapp/WEB-INF/
a /main/resources/
( details ).
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:annotation-config />
<context:component-scan base-package="my.package.with.rest.services" />
</beans>
Ejemplo de servicio REST
public interface MyService
{
String work(String s);
}
...
@Service
public class MyServiceImpl implements MyService
{
@Override
public String work(String s)
{
return "Hello, " + s;
}
}
...
@Path("demo/")
@Component
public class DemoRestService
{
@Autowired
private MyService service;
@GET
@Path("test")
public Response test(@FormParam("param") String par)
{
try
{
String entity = service.work(par);
return Response.ok(entity).build();
}
catch (Exception e)
{
e.printStackTrace();
return Response.status(Status.INTERNAL_SERVER_ERROR).entity("Epic REST Failure").build();
}
}
}
Otra opción posible es invocar manualmente el cableado automático en su recurso de jersey:
@Context
private ServletContext servletContext;
@PostConstruct
public void init() {
SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(this, servletContext);
}
Hmm, obtienes un "cableado manual" ...
o simplemente puede extender la clase SpringBeanAutoWiringSupport. Así: public class DemoRestService extends SpringBeanAutoWiringSupport
. Al extender esta clase de soporte, las propiedades de su clase de servicio se pueden conectar automáticamente.