2

I'm using Spring and am trying to autowire (using annotations) a DAO into a Service, which is then wired into a controller. Having

@Autowired
Movie movieDao;

on its own doesn't work, as I think the new method gets called, so that DAO isn't managed by Spring. The following does work, but it will look messy if I have to copy and paste that context configuration into each method

    @Autowired
    MovieDao movieDao;

    @Override
    public List<Movie> findAll() {
        GenericXmlApplicationContext context = new GenericXmlApplicationContext();
        context.load("classpath:app-context.xml");
        context.refresh();
        MovieDao movieDao = (MovieDao) context.getBean("movieDao", MovieDao.class);
        return movieDao.findAll();
    }

where this code is in my Service class. Is there a more elegant way to ensure that my DAO is initialised properly, rather than copying and pasting the first 4 lines of that method into each Service method?

[edit] The class that contains the code above is a class called MovieServiceImpl, and it essentially corresponds to the DataServicesImpl class in the architecture described on this page. (I'll add a summary/description of that architecture and what I'm trying to do soon). This is the code: http://pastebin.com/EiTC3bkj

false_azure
  • 1,333
  • 4
  • 25
  • 46
  • 1
    Can you give us more context about this class that contains that `movieDao` field? Generally speaking, I recommend constructor injection over field injection, largely for this reason. – chrylis -cautiouslyoptimistic- Sep 15 '14 at 00:16
  • Thanks, I've added a bit in the post and will try and update it soon. Autowiring a constructor sounds like it might solve the problem - I'll give it a go, thanks again. – false_azure Sep 15 '14 at 00:27
  • 1
    I think the main issue here is you are creating a new applicationContext every time this method is called. Spring cant inject a dependency when used like this. Is this a webapp? If so you should set the context using the ContextLoaderListener like [this](http://syntx.io/difference-between-loading-context-via-dispatcherservlet-and-contextloaderlistener/) . The reasons for the contextLoaderListener are described [here](http://stackoverflow.com/questions/11815339/role-purpose-of-contextloaderlistener-in-spring) – stringy05 Sep 15 '14 at 02:11
  • Yes, it's a web app. Thanks for the advice and link. – false_azure Sep 15 '14 at 02:16
  • why you load the spring context for each findAll() invocation? @Autowired injects already this field – Xstian Sep 15 '14 at 07:30

1 Answers1

1

I think that the main problem is that you want to instantiate your service directly (with new) and not with Spring:

MovieService movieService = new MovieServiceImpl();

When you do this, your MovieServiceImpl instance is constructed but not initialised (the field @Autowired MovieDao is null).

If you want to instantiate properly your object with field injection, you need to use Spring. As explained in the documentation or in this example, you can automatically detect all your annotated beans and initialize them in your context with the component scanning.

Example

In your case, using annotiations on (@Component, @Service, etc) and in (@Autowired, @Inject, etc) your beans, your project could look like this:

Spring configuration app-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- Use component scanning to auto-discover your beans (by annotation) and initialize them -->
    <context:component-scan base-package="com.se325.a01" />

    <!-- No need to declare manually your beans, because beans are auto-discovered thanks to <context:component-scan/> -->

</beans>

Entry point of your application App.java

package com.se325.a01;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.se325.a01.model.Movie;
import com.se325.a01.service.MovieService;

public class App {
    public static void main(String[] args) {
        // Let's create the Spring context based on your app-context.xml
        ApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"app-context.xml"});
        // Now your context is ready. All beans are initialised.
        // You can retrieve and use your MovieService
        MovieService movieService = context.getBean("movieService");

        Movie matrixMovie = new Movie("Matrix");
        movieService.create(matrixMovie);
    }
}

In fact, when you are using Spring, it is really important to understand how the context is initialized. In the example above, it can be sum up as:

  1. Your entry point App#main is called.
  2. The configuration app-context.xml is loaded by ClassPathXmlApplicationContext.
  3. The package com.se325.a01 is scanned thanks to the line <context:component-scan base-package="com.se325.a01" />. All annotated beans (@Component, @Service, etc) are contructed but not yet initialised.
  4. When all the beans are constructed, Spring initialises them by injecting dependencies. In the example, the @Autowired annotations which mark the dependencies are also discovered thanks to the line <context:component-scan ... \>.
  5. The context is ready with all beans :)

Notes

All this answer explains how you can use component scanning and annotations to use Spring in a main entry point. However, if you are developing a server application, the entry point is the WEB-INF/web.xml.

As @chrylis said, field injection is error prone. Prefer using constructor-based injection.

Kuhess
  • 2,571
  • 1
  • 18
  • 17