Pergunta

I am trying to run TestNG tests for DAO class in Spring. But DataSource reference is not autowired despite of the annotation. Here is part of the test:

@ContextConfiguration(locations={"classpath:WEB-INF/servlet-context.xml"})
public class ToDoDaoImplTest extends AbstractTestNGSpringContextTests {

    @Autowired
    // Construction of this object fails
    private ToDoItemDaoImpl toDoDao;
}

Here is my Spring configuration:

<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/view/"/>
    <property name="suffix" value=".jsp"/>
</bean>

<!-- DataSource to be injected -->
<bean id="dataSource"
      class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="org.h2.Driver" />
    <property name="url" value="jdbc:h2:mem:test" />
    <property name="username" value="sa" />
    <property name="password" value="" />
</bean>

<jdbc:initialize-database data-source="dataSource" ignore-failures="DROPS">
    <jdbc:script location="classpath:db.create.sql" />
</jdbc:initialize-database>

<context:component-scan base-package="org.myapp"/>

DAO class:

@Repository
public class ToDoItemDaoImpl implements ToDoItemDao {
    private DataSource dataSource;
    private SimpleJdbcInsert insert;

    public ToDoItemDaoImpl() {
        // dataSource is null here
        insert = new SimpleJdbcInsert(dataSource).withTableName("toDoItem").usingGeneratedKeyColumns("id");
    }

    @Autowired
    @Qualifier("dataSource")
    // This method is not called by Spring
    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

Here is stacktrace of the error:

Caused by: org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [org.myapp.dao.ToDoItemDaoImpl]: Constructor threw exception; nested exception is java.lang.IllegalArgumentException: Property 'dataSource' is required
    at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:163)
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:87)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1004)
    ... 50 more
Caused by: java.lang.IllegalArgumentException: Property 'dataSource' is required
    at org.springframework.jdbc.support.JdbcAccessor.afterPropertiesSet(JdbcAccessor.java:134)
    at org.springframework.jdbc.core.JdbcTemplate.<init>(JdbcTemplate.java:165)
    at org.springframework.jdbc.core.simple.AbstractJdbcInsert.<init>(AbstractJdbcInsert.java:97)
    at org.springframework.jdbc.core.simple.SimpleJdbcInsert.<init>(SimpleJdbcInsert.java:60)
    at org.myapp.dao.ToDoItemDaoImpl.<init>(ToDoItemDaoImpl.java:33)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
    at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:148)
    ... 52 more
null

Part of the log that shows that dataSource is created:

DEBUG - DefaultListableBeanFactory - Creating shared instance of singleton bean 'dataSource'
DEBUG - DefaultListableBeanFactory - Creating instance of bean 'dataSource'
DEBUG - DefaultListableBeanFactory - Eagerly caching bean 'dataSource' to allow for resolving potential circular references
INFO  - DriverManagerDataSource    - Loaded JDBC driver: org.h2.Driver
DEBUG - DefaultListableBeanFactory - Finished creating instance of bean 'dataSource'
DEBUG - DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.jdbc.datasource.init.DataSourceInitializer#0'
DEBUG - DefaultListableBeanFactory - Creating instance of bean 'org.springframework.jdbc.datasource.init.DataSourceInitializer#0'
DEBUG - DefaultListableBeanFactory - Eagerly caching bean 'org.springframework.jdbc.datasource.init.DataSourceInitializer#0' to allow for resolving potential circular references
DEBUG - DefaultListableBeanFactory - Returning cached instance of singleton bean 'dataSource'

This is confuses me. The dataSource object is created, but is not Autowired into a Spring-managed object. What am I doing wrong?

Foi útil?

Solução

The issue is here

public ToDoItemDaoImpl() {
    // dataSource is null here
    insert = new SimpleJdbcInsert(dataSource).withTableName("toDoItem").usingGeneratedKeyColumns("id");
}

Spring can only autowire fields after the object has been created. That happens after the constructor is finished.

Spring will use reflection, ie. something like Class.forName(yourClass).newInstance(), to create your bean and then again use reflection to set each property.

However, in your constructor, the field is still null as that is the default value for all uninitialized fields of reference types.

One solution is to leave the constructor empty and add a @PostConstruct annotated method

@PostConstruct
public void init() {
    insert = new SimpleJdbcInsert(dataSource).withTableName("toDoItem").usingGeneratedKeyColumns("id");
}

Another solution is to add that initialization inside the setDataSource() method.

Outras dicas

Besides what Sotirios suggested above, you can also use Spring's contructor-args dependency injection to initialize the dataSource. So you can modify the constructor to be:

public ToDoItemDaoImpl(DataSource dataSource) {
        this.dataSource = dataSource;
        insert = new SimpleJdbcInsert(dataSource).withTableName("toDoItem").usingGeneratedKeyColumns("id");
    }

Then you also need to add the following lines in the bean configuration file:

<bean id="toDoItemDaoImpl" class="mypackage.ToDoItemDaoImpl">
    <constructor-arg ref="dataSource"/>
</bean> 
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top