37

I'm playing with the idea of using Spring @Configurable and @Autowire to inject DAOs into domain objects so that they do not need direct knowledge of the persistence layer.

I'm trying to follow http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/aop.html#aop-atconfigurable, but my code seems to have no effect.

Basically, I have:

@Configurable
public class Artist {

    @Autowired
    private ArtistDAO artistDao;

    public void setArtistDao(ArtistDAO artistDao) {
        this.artistDao = artistDao;
    }

    public void save() {
        artistDao.save(this);
    }

}

And:

public interface ArtistDAO {

    public void save(Artist artist);

}

and

@Component
public class ArtistDAOImpl implements ArtistDAO {

    @Override
    public void save(Artist artist) {
        System.out.println("saving");
    }

}

In application-context.xml, I have:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springsource.org/dtd/spring-beans-2.0.dtd">
<beans>

    <bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator" />
    <bean class="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect" factory-method="aspectOf"/>

</beans>

Class path scanning and initialisation is performed by the spring module for Play! framework, although other autowired beans work, so I'm pretty sure this is not the root cause. I'm using Spring 3.0.5.

In other code (inside a method in bean that's injected into my controller using Spring, in fact), I'm doing this:

Artist artist = new Artist();
artist.save();

This gives me a NullPointerException trying to access the artistDao in Artist.save().

Any idea what I'm doing wrong?

Martin

Sean Patrick Floyd
  • 292,901
  • 67
  • 465
  • 588
optilude
  • 3,538
  • 3
  • 22
  • 25

10 Answers10

10

You need to enable load-time weaving (or other kinds of weaving) in order to use @Configurable. Make sure you enabled it correctly, as described in 7.8.4 Load-time weaving with AspectJ in the Spring Framework.

axtavt
  • 239,438
  • 41
  • 511
  • 482
  • 1
    This is wrong. You do not need LTW to support @Configurable. Spring Roo does what he wants and it does not use LTW. Most likely his problem is that he is not compiling the code with the AspectJ compiler and Spring Aspects jar. – Adam Gent Sep 06 '11 at 13:06
  • 4
    @AdamGent He did say LTW (or other kinds of weaving). . . You need either load-time weaving or compile time weaving for the Configurable annotation. – Jasper Blues Jan 18 '13 at 08:10
  • @JasperBlues you are right that LTW should work. I could never get it to work in my environment and thought that I read in *AspectJ in Action* that constructing advice was not possible with LTW. – Adam Gent Jan 18 '13 at 15:39
  • Is this good idea to use spring bean(Map) as dependency in POJO to get say `employeeName` on `employeeId`, since I'm fetching list of 25 pojos at a time, 25 objects created and every POJO has Map as dependency? Both `employeeName` and `employeeId` are fields of POJO, but `employeeName` is null and `employeeId` is populated **OR** I should set `employeeName` in service Iterating the list?. – Shantaram Tupe Apr 03 '18 at 13:19
7

I was having this problem with Tomcat 7 using LTW trying to autowire beans into my domain classes.

There was some updates to the doc for 3.2.x at http://static.springsource.org/spring/docs/3.2.x/spring-framework-reference/html/aop.html#aop-configurable-container that revealed that one can use @EnableSpringConfigured instead of the xml configuration .

So I have the following annotation on my Domain object:

@Configurable(preConstruction=true,dependencyCheck=true,autowire=Autowire.BY_TYPE)
@EnableSpringConfigured

@EnableSpringConfigured is a substitue for

<context:spring-configured />

and don't forget to add this to your context xml file:

<context:load-time-weaver weaver-class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver" aspectj-weaving="on"/>

Of course I needed to setup Tomcat for load time weaving first.

Also, I ran into a bug in 3.2.0 (null pointer) so I needed to upgrade to Spring 3.2.1 (https://jira.springsource.org/browse/SPR-10108)

All is well now!

Chris B
  • 71
  • 1
  • 3
4

First, enable Spring debug logging. I use Log4j to do it. I've created a logger like so (with Log4j xml configuration so I can use RollingFileAppender):

<log4j:configuration>
  <appender name="roll" class="org.apache.log4j.rolling.RollingFileAppender">
     blah blah configuration blah blah
  </appender>
  <logger name="org.springframework">
    <level value="debug" />
    <appender-ref ref="roll" />
  </logger>
</log4j:configuration>

This will allow you to see what Spring is doing and when.

Second, you have ArtistDAO autowired but I don't see where you have a bean named ArtistDAO. Your DAO component bean will be named "artistDaoImpl" by default. Try changing @Component to @Component("artistDao") and applying @Autowired to the setter instead:

private ArtistDAO artistDao;

@Autowired
public void setArtistDao(ArtistDAO artistDao) 
{
  this.artistDao = artistDao;
}
Anthony Pegram
  • 123,721
  • 27
  • 225
  • 246
Paul
  • 19,704
  • 14
  • 78
  • 96
4

You should just look how Spring Roo does it since it does exactly what you want to do.

There are lots of things that can cause the NPE your having but most of the time it has to do with not compiling properly with AspectJ compiler and not having the Spring Aspects jar in your AspectJ lib path (this is different than your classpath).

First just try to get it to work with Maven and the AspectJ compiler plugin. Thats why I recommend Spring Roo as it will generate a POM file with the correct setup.

I have found @Configurable does not really work with LTW (despite one of the answers saying so). You need compile time weaving for @Configurable to work because the advice is happening at object construction time (constructor advice cannot be done with Springs LTW).

Adam Gent
  • 47,843
  • 23
  • 153
  • 203
  • What problems did you have with load-time weaving? I've used it quite successfully. I use LTW for integration tests and compile-time weaving for deploy. . This way my test coverage reports come out nicely, but my deploy is robust. – Jasper Blues Jan 18 '13 at 08:12
  • Because `@Configurable` allows you to use normal Java constructors to instantiate the object. The advice is applied on the Objects constructor. **I thought you could only apply constructor advice with compile time weaving** but it appears you can also do it with LTW. So you are right. What you can't do (and this is why I was confused) is make AspectJ ITDs with LTW. – Adam Gent Jan 18 '13 at 15:35
  • 1
    Here is an example of how to configure AspectJ compile time weaving with spring-aspects.jar, this solved my problem: http://stackoverflow.com/a/9512319/999422 – citress Mar 26 '13 at 16:44
3

I had the same problem and never managed to get the code working with @Configurable and @Autowired. I finally decided to write an aspect myself which would handle the @Configurable and @Autowired annotations. Here is the code:

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;

import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

@SuppressWarnings( "rawtypes" )
@Aspect
public class AutoInjectDependecyAspect implements ApplicationContextAware {
    private static final Logger LOGGER = Logger.getLogger( AutoInjectDependecyAspect.class );

    private ApplicationContext  applicationContext = null;

    @Pointcut( "execution(  (@org.springframework.beans.factory.annotation.Configurable *).new())" )
    public void constructor() {
    }

    @Before( "constructor()" )
    public void injectAutoWiredFields( JoinPoint aPoint ) {
        Class theClass = aPoint.getTarget().getClass();
        try{
            Field[] theFields = theClass.getDeclaredFields();
            for ( Field thefield : theFields ) {
                for ( Annotation theAnnotation : thefield.getAnnotations() ) {
                    if ( theAnnotation instanceof Autowired ) {
                        // found a field annotated with 'AutoWired'
                        if ( !thefield.isAccessible() ) {
                            thefield.setAccessible( true );
                        }

                        Object theBean = applicationContext.getBean( thefield.getType() );
                        if ( theBean != null ) {
                            thefield.set( aPoint.getTarget(), theBean );
                        }
                    }
                }
            }
        } catch ( Exception e ) {
            LOGGER.error( "An error occured while trying to inject bean on mapper '" + aPoint.getTarget().getClass() + "'", e );
        }

    }

    @Override
    public void setApplicationContext( ApplicationContext aApplicationContext ) throws BeansException {
        applicationContext = aApplicationContext;
    }

}

Next in your spring context define the aspect so that the springcontext will be injected into the aspect

<bean class="[package_name].AutoInjectDependecyAspect" factory-method="aspectOf"/>
Guy Chauliac
  • 640
  • 7
  • 8
1

I had a similar issue that I resolved today. The important thing is that you need to enable load-time weaving and make sure the appropriate aspectj classes are loaded. In your pom.xml you need to add the aspectjweaver artifact:

...
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.6.12</version>
</dependency>
....

You can change the version if you need to. Then, I would go the xsd route in you application-context.xml instead of the DTD route:

<?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-3.0.xsd">

    <!--Scans the classpath for annotated components @Component, @Repository, @Service, and @Controller -->
    <context:component-scan base-package="your.base.package"/>
    <!--Activates @Required, @Autowired, @PostConstruct, @PreDestroy and @Resource -->
    <context:annotation-config/>
    <!--This switches on the load-time weaving for @Configurable annotated classes -->
    <context:load-time-weaver/>

</beans>
Jorge
  • 1,924
  • 2
  • 14
  • 11
1

Perhaps using the @Repository annotation for the DAO will do it.

duffymo
  • 305,152
  • 44
  • 369
  • 561
1

try : @Configurable(autowire=Autowire.BY_TYPE). Autowired defaults to off :<

MikePatel
  • 2,593
  • 2
  • 24
  • 38
0

Also, please verify that your version of AspectJ is current. I wasted a few hours trying to make this work, and the cause was an old version of Aspectjweaver.jar. I updated to 1.7.2 and everything worked like a charm.

Alex
  • 228
  • 1
  • 10
0

There is a bug with @Configurable and LTW. If you use your class as a parameter in any method the auto wiring stops working. https://jira.spring.io/plugins/servlet/mobile#issue/SPR-8502

CodeMonster
  • 11
  • 1
  • 2