4

I started to use Spring for Dependency Injection.

I added the jar spring-context-4.0.1

I added @Configuration to a class

@Configuration
public class SpringConfig {

    @Bean
    public CategoryDAO categoryDAO() {
        return new HibernateCategoryDAO();
    }
    
}

where

public interface CategoryDAO{
}

and

public class HibernateCategoryDAO implements CategoryDAO {
}

However, when I try to @Autowired it in a Component (more precisely a Repository)

@Repository
public class MyCategoryManager implements CategoryManager {

    @Autowired
    CategoryDAO categoryDAO;

}

categoryDAO is null. Furthermore, I see stepping through the code in debug mode that it never executes the Configuration file.

What am I missing?

I don't want to use XML in Spring but do I still need to add the application-context file?

EDIT:

Following @Gummyball answer, I added the configuration. It didn't help.

I initialize MyCategoryManager class using Reflections util:

Set<?> managers = Reflections.getSubTypesOf(CategoryManager.class);

Could it be the reason? how can I overcome it?

masterxilo
  • 2,503
  • 1
  • 30
  • 35
Dejell
  • 13,947
  • 40
  • 146
  • 229
  • Have you looked at the answer to this question? [http://stackoverflow.com/questions/8075790/how-to-register-spring-configuration-annotated-class-instead-of-applicationcont](http://stackoverflow.com/a/8076045/322197). – Gummyball Feb 28 '14 at 10:35
  • Yes - but I am not using an MVC application -it's a REST based on Jersey – Dejell Feb 28 '14 at 12:52
  • Could you add your web.xml as well? Have you defined a Spring listener in web.xml and does the context-param contextConfigLocation point to your Configuration class? – Gummyball Feb 28 '14 at 13:14
  • I didn't. Do I need to? – Dejell Feb 28 '14 at 13:43

2 Answers2

4

Yes, you need to bootstrap Spring by adding a listener class to your web.xml. By supplying your @Configuration class (.i.e your SpringConfig class) as a context parameter, your configuration class will be picked up by Spring.

<web-app>
    <!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext instead of the default XmlWebApplicationContext -->
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>
            org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        </param-value>
    </context-param>

    <!-- Configuration locations must consist of one or more comma- or space-delimited fully-qualified @Configuration classes. Fully-qualified packages may also be specified for component-scanning -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>your.own.package.SpringConfig</param-value>
    </context-param>

    <!-- Bootstrap the root application context as usual using ContextLoaderListener -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    // Your jersey config here
</web-app>

UPDATE

Based on the official Jersey Spring Example, I made this hello world example which uses Spring together with Jersey.

You'll need to have the following dependencies: spring framework, jersey-container-servlet and jersey-spring3. Or if using maven:

pom.xml

<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.stackoverflow.question.jersey.with.spring</groupId>
    <artifactId>SpringConfig</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>SpringConfig</name>

    <properties>
        <!-- Spring -->
        <spring-framework.version>4.0.1.RELEASE</spring-framework.version>
        <jersey.version>2.6</jersey.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring-framework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.containers</groupId>
            <artifactId>jersey-container-servlet</artifactId>
            <version>${jersey.version}</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.ext</groupId>
            <artifactId>jersey-spring3</artifactId>
            <version>${jersey.version}</version>
        </dependency>
    </dependencies>
</project>

In web.xml you'll need to configure Spring and Jersey

web.xml

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">

    <!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext instead of the default XmlWebApplicationContext -->
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>
            org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        </param-value>
    </context-param>

    <!-- Configuration locations must consist of one or more comma- or space-delimited fully-qualified @Configuration classes. -->
    <!-- Fully-qualified packages may also be specified for component-scanning -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>com.stackoverflow.question.jersey.with.spring.SpringConfig</param-value>
    </context-param>

    <!-- Bootstrap the root application context as usual using ContextLoaderListener -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- Jersey config -->
    <servlet>
        <servlet-name>SpringConfig</servlet-name>
        <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
        <init-param>
            <param-name>javax.ws.rs.Application</param-name>
            <param-value>com.stackoverflow.question.jersey.with.spring.MyApplication</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>SpringConfig</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>

The trick here is to tell Jersey which package(s) to scan. Alternatively, you could use register(.class) to register each component individually.

MyApplication.java

package com.stackoverflow.question.jersey.with.spring;

import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.spring.scope.RequestContextFilter;

/**
 * Spring HelloWorld Web Application configuration.
 * 
 * @author Jakub Podlesak (jakub.podlesak at oracle.com)
 */
public class MyApplication extends ResourceConfig
{

    /**
     * Register JAX-RS application components.
     */
    public MyApplication()
    {
        packages(true, "com.stackoverflow.question.jersey.with.spring");
    }
}

You can tell Spring to use component scanning by supplying a package to @ComponentScan. Or, if you want more control on how Spring beans are initialized, you could use separate @Bean annotations (like you did in your question) to specify each Spring bean independently.

SpringConfig.java

package com.stackoverflow.question.jersey.with.spring;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("com.stackoverflow.question.jersey.with.spring")
public class SpringConfig
{
}

Based on your comment about different CategoryManager implementations, I made a helper class that will return a different CategoryManager based on the enum type that you give it (which would be a user supplied value).

CategoryManagerFactory.java

package com.stackoverflow.question.jersey.with.spring;

import javax.inject.Inject;
import org.springframework.stereotype.Component;

@Component
public class CategoryManagerFactory
{
    @Inject
    private MyCategoryManager myCategoryManager;

    @Inject
    private OtherCategoryManager otherCategoryManager;

    public CategoryManager obtainCategoryManager(CategoryManagerTypes type)
    {
        switch (type) {
            case MY:
                return myCategoryManager;
            case OTHER:
                return otherCategoryManager;
            default:
                throw new IllegalArgumentException(String.format("Category %s not supported", type));
        }
    }

    public enum CategoryManagerTypes
    {
        MY, OTHER;
    }
}

MyCategoryManager.java

package com.stackoverflow.question.jersey.with.spring;

import javax.inject.Inject;
import org.springframework.stereotype.Component;

@Component
public class MyCategoryManager implements CategoryManager
{
    @Inject
    private CategoryDAO categoryDAO;

    @Override
    public String saySomething()
    {
        return "Using MyCategoryManager!";
    }
}

OtherCategoryManager.java

package com.stackoverflow.question.jersey.with.spring;

import javax.inject.Inject;
import org.springframework.stereotype.Component;

@Component
public class OtherCategoryManager implements CategoryManager
{
    @Inject
    private CategoryDAO categoryDAO;

    @Override
    public String saySomething()
    {
        return "Using OtherCategoryManager!";
    }
}

And to bring everything together, using Spring in a Jersey resource:

JerseyResource.java

package com.stackoverflow.question.jersey.with.spring;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import com.stackoverflow.question.jersey.with.spring.CategoryManagerFactory.CategoryManagerTypes;

@Path("jersey-hello")
public class JerseyResource
{
    @Inject
    private CategoryManagerFactory categoryManagerFactory;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String getHello(@Context HttpHeaders headers, @QueryParam("category") CategoryManagerTypes category)
    {
        CategoryManager categoryManager = categoryManagerFactory.obtainCategoryManager(category);

        return categoryManager.saySomething();
    }
}

By going to http://localhost:8080/SpringConfig/jersey-hello?category=MY you'd get 'Using MyCategoryManager!'

Gummyball
  • 220
  • 3
  • 10
  • Thanks. Now I see that it stops in SpringConfig, however my problem is that inside MyCategoryManager the categoryDAO is null. I annotated MyCategoryManager with @Repository but still categoryDAO is null. – Dejell Mar 01 '14 at 17:59
  • I saw the update to your question. If you want to use Spring's DI mechanism you'll need to also let Spring manage your beans. If you instantiate an annotated class yourself, Spring has no way of knowing about this and therefore cannot inject the @Autowired fields. Same goes for using reflection. Where do you want to use MyCategoryManager? If it's in one of your Jersey endpoints you need to inject it in that class using autowiring as well. – Gummyball Mar 02 '14 at 16:09
  • I ask the user to enter the category manager, and based on what he asks: e.g. My I would let My handle it. How could I do it via Spring? it's not something that I know in advance – Dejell Mar 02 '14 at 16:26
  • I tried to add an ApplicationContextProvider but not sure if it's the right approach. what do you think? – Dejell Mar 02 '14 at 18:13
  • I don't think you should use an ApplicationContextProvider if it's not absolutely needed. I've updated my answer with a hello-world-like example geared to your situation, hope it helps. – Gummyball Mar 03 '14 at 14:11
  • CategoryManagerFactory looks promising. I will try that! thanks! – Dejell Mar 03 '14 at 14:24
0

From your code it's not clear how Spring knows about MyCategoryManager. Try to add it to configuration

@Configuration
public class SpringConfig {

    @Bean
    public CategoryDAO categoryDAO() {
        return new HibernateCategoryDAO();
    }

    @Bean
    public MyCategoryManager myCategoryManager() {
        return new MyCategoryManager();
    }

    public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
    System.out.println(ctx.getBean(MyCategoryManager.class).categoryDAO);
}
Evgeniy Dorofeev
  • 133,369
  • 30
  • 199
  • 275