Search code examples
spring-bootdependency-injectioncdijava-ee-8

What is SpringBoot alternative to JavaEE CDI?


I know that JakartaEE (formely JavaEE) has support for CDI since JavaEE 6.

To my knowledge SpringBoot does not have CDI but only DI.

Does SpringBoot support CDI (Context and Dependency Injection) or offer some alternative to it?


Solution

  • The whole Spring Framework in itself is actually an alternative. Difference here is that it's not as cleanly seperated in Spring - instead its part of the intricacies of the Spring Framework.

    The Spring Framework has an alternative for nearly every feature in CDI. For the sake of argument and completeness, we can do a comparison and discuss differences and similarities;

    Managed objects

    In CDI you would define an injectable or managed class in the following way:

    @[ManagedBean / Named]
    @[ApplicationScoped / Dependant / RequestScoped / Singleton / ViewScoped]
    public class MyClass {
        @PostConstruct private void init() { ... }
        @PreDestroy private void destroy() { ... }
    }
    

    Springs equivalent to the same definition looks like this:

    @[Bean / Controller / Component / Repository / Service]
    public class MyClass {
        @PostConstruct private void init() { ... }
        @PreDestroy private void destroy() { ... }
    }
    

    Whenever a class is defined as a @Component or a @Bean in Spring, it can also take a scope:

    @[Bean / Component]
    @Scope([
        SCOPE_APPLICATION / SCOPE_PROTOTYPE / SCOPE_REQUEST / SCOPE_SESSION / 
        SCOPE_SINGLETON / "websocket"
    ])
    public class MyClass { ... }
    

    Just like with CDI, Spring is also extendable and can be extended with additional scopes by developers if they need special behavior.

    With CDI we inject and let the framework inject managed objects by annotating a member with @Inject. The equivalent annotation for injecting managed objects in Spring is @Autowired.

    Signals

    When defining a signal in CDI, we would inject a member like:

    @Inject
    private Event<Payload> event;
    

    We would then send a signal to all listeners by invoking event.fire(new Payload()) or event.fireAsync(new Payload()). An event listener (or observer, as they are called in CDI) would then be defined by adding a method like this one to a managed object:

    public void onPayload(@Observes Payload event) { ... }
    

    In a similar manner, signals in a Spring application are defined like this:

    @Autowired
    private ApplicationEventPublisher event;
    

    In Spring, events are then fired by invoking event.publishEvent(new Payload()). Event listeners in Spring are defined slightly diffrently:

    @Component
    public class Observer {
        @EventListener public void onPayload(Payload payload) { ... }
    }
    

    Producers

    In CDI, we can create a method that "produces" a specific object by defining a producer. These essentially act like factories. These objects can later be created by injecting them with @Inject. By default, producers are @Dependant, but they can also define a scope.

    @RequestScoped
    public class LoggerFactory {
        @Produces
        public Logger createLogger() {
            return new Logger();
        }
    }
    

    To do the very same thing in Spring, we would do something like this:

    @Configuration
    public class LoggerFactory {
        @Bean @Scope(SCOPE_REQUEST)
        public Logger createLogger() {
            return new Logger();
        }
    }
    

    Of course, rather than using @Inject, you would use @Autowired in Spring to inject instances of these objects.

    Interceptors

    Interceptors allow us to "modify" or "decorate" methods with custom behavior. In CDI, we typically first define an annotation. This annotation is then later used to decorate methods with our custom code:

    @Target( { METHOD, TYPE } ) @Retention( RUNTIME )
    public @interface MyInterceptor { }
    

    We then define a behavior for that interceptor:

    @Interceptor @MyInterceptor
    public class MyInterceptorHandler {
        @AroundInvoke public Object interception(InvocationContext ctx) throws Exception {
            /* You can do something additional here ... */
            Object result = ctx.proceed();
            /* ... and/or here ... */
    
            return result;
        }
    }
    

    So here we can choose exactly where we want to insert extra code and when to proceed with the invocation of the decorated method. To decorate a method for interception you could do something like this:

    @ApplicationScoped
    public class MyService {
        @MyInterceptor public String operation(String str) {
            return str;
        }
    }
    

    In Spring its similar and different at the same time. Spring itself does not have anything like this specifically, but has instead added support for AspectJ into the framework. So to create a similar interceptor in Spring, you would do something like this:

    @Aspect
    public class MyInterceptorHandler {
        @Around("execution("*com.mycompany.project.MyService.*(..))")
        public Object interception(ProceedingJoinPoint jp) throws Throwable {
            /* You can do something additional here ... */
            Object result = jp.proceed();
            /* ... and/or here ... */
    
            return result;
        }
    }
    

    At this point we don't need to do anything more, because interception will be applied to all the methods of MyService, as specified by the @Around annotation.

    Conclusion

    So this post was a bit drawn out. But it does show some of the similarities and differences between CDI and Spring. Using this little how-to, you can, for the most common tasks, easily migrate to CDI if you know Spring and vice versa.