Search code examples
javaspringspring-mvcaopspring-aop

Field in Aspect injected after its first use, causing NullPointerException at startup


ABSTRACT:

I have some initialisation operations executed in @PostConstruct of @Service ServiceInitialiserFacsimile. Those operations include a call to a method after whose execution an Aspect (DoAttionalStuffAspect) is applied.

The Aspect is instantied through aspectOf, so it is handled by the Spring Container, but unfortunately its dependencies are injected AFTER the execution of ServiceInitialiserFacsimile @PostConstruct, resulting in a NullPointerException.

How can I tell the Spring Container to inject first the fields in the Aspect and then instantiate the ServiceInitialiserFacsimile ?

I tried with an Autowired constructor for the aspect, but I think in the end AspectJ requires the no-arg constructor, so it was no help

CODE

This is a Sample I created in order to reproduce the issue I have in a much more complicated app. Here is the project if you want to check it out. https://github.com/alessiop86/spring3-mvc-maven-xml-hello-world

Code below: This is the initialisation class:

@Component
public class ServiceInitialiserFacsimile {

    private final SampleService sampleService;

    @Autowired
    public ServiceInitialiserFacsimile(SampleService ss) {
        this.sampleService = ss;
    }

    @PostConstruct
    public void initialiseAllTheServices() {
        this.sampleService.init();
    }

}

This is the service with some custom logic that requires to be initialised by the ServiceInitialiserFacsimile @PostConstruct:

@Service
public class SampleService {

    public void init() {
        System.out.println("do some stuff");
        try {
            execute();
        }
        catch(Exception e) {
            System.err.println("I do not want to block to whole framework initialisation");
        }
    }

    @DoAdditionalStuff
    public void execute() {
        System.out.println("Phase 1");
    }
}

This is the annotation I use in the aspect definition

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DoAdditionalStuff {
}

This is the aspect

@Aspect
public class AdditionalStuffAspect {

    private AdditionalStuffService service;

    public AdditionalStuffService getService() {
        return service;
    }

    public void setService(AdditionalStuffService service) {
        this.service = service;
    }

    @Pointcut(value="execution(public * *(..))")
    private void anyPublicMethod() { }

    @AfterReturning("anyPublicMethod() && @annotation(doAdditionalStuff)")
    public void afterReturning(JoinPoint jointPoint, DoAdditionalStuff doAdditionalStuff) {
        System.out.println(jointPoint);
        service.doStuff();
    }
}

This is the service that is created, but not yet instantiated when the aspect is run:

@Service
public class AdditionalStuffService {

    public void doStuff() {
        System.out.println("Phase2: additional stuff");
    }
}

Spring context xml configuration file:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans     
        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
        http://www.springframework.org/schema/mvc 
        http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-3.2.xsd">

    <context:component-scan base-package="initialisation.mess"/>

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

    <mvc:resources mapping="/resources/**" location="/resources/"/>

    <mvc:annotation-driven/>

    <bean class="initialisation.mess.aspects.AdditionalStuffAspect" factory-method="aspectOf">
        <property name="service" ref="additionalStuffService" />
    </bean>
</beans>

Solution

  • I was able to enforce a dependency on the ServiceInitialiserFacsimile from the Aspect by setting an id in the xml:

      <bean id="myAspect" class="initialisation.mess.aspects.AdditionalStuffAspect" factory-method="aspectOf">
            <property name="service" ref="additionalStuffService" />
        </bean>
    

    and then specifying the dependency of ServiceInitialiserFacsimile from the spring managed AdditionalStuffAspect with a @DependsOn annotation:

    @Component
    @DependsOn("myAspect")
    public class ServiceInitialiserFacsimile {
    
        private final SampleService sampleService;
    
        @Autowired
        public ServiceInitialiserFacsimile(SampleService ss) {
            this.sampleService = ss;
        }
    
        @PostConstruct
        public void initialiseAllTheServices() {
            this.sampleService.init();
        }
    
    }