Dependency Injection to AspectJ aspects in Spring Boot

Dependency Injection to AspectJ aspects in Spring Boot

Previously we have discussed configuring AspectJ with Spring Boot. A topic that we left uncovered was dependency injection to AspectJ aspects in Spring Boot because that’s not as straightforward as adding an annotation to the class. It requires a little bit more work. In this article, we go through how to do dependency Injection to AspectJ aspects in Spring Boot.

An extension to the previous example

Recapping from the previous article example, source code on GitHub, there’s an interceptor called LoggingInterceptor. The related class is responsible to log something when the getUsersInternal private method of the UserController is invoked.

In that example, there’s no need to inject any Spring-managed beans into the aspect.

But let’s assume now that we would like a logging service that handles all logging in the application.

package com.madadipouya.sample.aspectj.service;
public interface LoggingService {
    void log(String message);
}

We can implement the above interface as below,

package com.madadipouya.sample.aspectj.service.impl;

import com.madadipouya.sample.aspectj.service.LoggingService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

/**
 * A dummy implementation of logging service,
 * just to inject it in {@link com.madadipouya.sample.aspectj.interceptor.LoggingInterceptor}
 * that's managed by AspectJ
 */
@Service
public class DefaultLoggingService implements LoggingService {

    private static final Logger logger = LoggerFactory.getLogger("sample-spring-aspectj");

    @Override
    public void log(String message) {
        logger.info(message);
    }
}

The problem with the above code is that we cannot inject LoggingService to LoggingInterceptor. That is because the classes are not managed by Spring anymore. Even if we try to annotate the interceptor with @Component (as suggested in many Stack Overflow threads as correct answers), it does not work still.

That is due to the fact that Spring sees AspectJ aspects as external classes like ObjectMapper or Gson classes. That means to add them to the Spring life cycle, we have to use the @Bean annotation instead. But that’s not all of it. We cannot create an instance of the AspectJ class using the new keyword. We have to rely on the Aspects factory class from AspectJ instead.

Implementation

To make the dependency injection workable, we have to create a bean from LoggingInterceptor using AspectJ Aspects.aspectOf,

package com.madadipouya.sample.aspectj.interceptor.config;

import com.madadipouya.sample.aspectj.interceptor.LoggingInterceptor;
import org.aspectj.lang.Aspects;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class LoggingInterceptorConfig {

    @Bean
    public LoggingInterceptor getAutowireCapableLoggingInterceptor() {
        return Aspects.aspectOf(LoggingInterceptor.class);
    }
}

The above code instructs AspectJ to create a singleton instance from the LogginInterceptor. The @Bean annotation adds the interceptor to the Spring life cycle.

Now we can inject dependencies to the AspectJ aspect like this,

package com.madadipouya.sample.aspectj.interceptor;

import com.madadipouya.sample.aspectj.service.LoggingService;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;

import java.time.ZoneOffset;
import java.time.ZonedDateTime;

@Aspect
public class LoggingInterceptor {

    @Autowired
    private LoggingService loggingService;

    @Before(value = "execution(* com.madadipouya.sample.aspectj.controller.UserController.getUsersInternal(..))")
    public void addCommandDetailsToMessage() throws Throwable {
        loggingService.log(String.format("User controller getUsers method called at %s", ZonedDateTime.now((ZoneOffset.UTC))));
    }
}

Note: Unfortunately, constructor injection is supported in AspectJ aspects. The only options are field injection and setter injection. That’s not the best practice and could make the testing more cumbersome potentially.

Conclusion

In this article, we covered how to do dependency Injection to AspectJ aspects in Spring BootDependency Injection to AspectJ aspects in Spring Boot. Since Spring sees AspectJ classes as externally sourced artifacts, we have to register them manually using the Aspects.aspectOf that creates an aspect of a given class. You can find the complete working example on GitHub at the link below,

https://github.com/kasramp/sample-spring-aspectj

If you are interested to compare the changes, check the commit 5c48872.

Inline/featured images credits