Abstract : Dependency Injection (DI) is refine flavour of Inversion of Control (IoC) design pattern. One of the software frameworks out there which provides DI implementation is Spring. AKKA on the other hand is “a toolkit and runtime for building highly concurrent, distributed, and fault tolerant event-driven applications on the JVM“. In this article we are going to concentrate on the actor model implemented by AKKA and more specifically on dependency injection in the UntypedActor class, as well as, injecting AKKA actor in a Spring enabled service.
Goal : Create a software bridge between Spring and an AKKA actor to ensure basic dependency injection
Acknowledgement : My gratitude goes to the open source community
We are going to use the following components to reach our goal: AKKA version 2.0.2, Spring 3.1.1.RELEASE, FEST Reflect 1.4, Logback 1.0.6, CGLIB 2.2, and Maven to glue everything together. All programming will be done in Java and you can find all files on GitHub. Don’t hesitate to fork the project and improve it. Here is the Maven POM file:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.honeysoft.akka</groupId> <artifactId>akka-di</artifactId> <version>1.0</version> <properties> <akka.version>2.0.2</akka.version> <spring.version>3.1.1.RELEASE</spring.version> <fest-reflect.version>1.4</fest-reflect.version> <logback.version>1.0.6</logback.version> </properties> <dependencies> <!-- Akka dependencies --> <dependency> <groupId>com.typesafe.akka</groupId> <artifactId>akka-actor</artifactId> <version>${akka.version}</version> </dependency> <!-- Spring dependencies --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <!-- Tools --> <dependency> <groupId>org.easytesting</groupId> <artifactId>fest-reflect</artifactId> <version>${fest-reflect.version}</version> </dependency> <dependency> <groupId>cglib</groupId> <artifactId>cglib-nodep</artifactId> <version>2.2</version> </dependency> <!-- Logging --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>${logback.version}</version> </dependency> </dependencies> <repositories> <repository> <id>akka.repository</id> <name>Akka Maven Repository</name> <url>http://repo.akka.io/releases/</url> </repository> </repositories> </project>
Good. Once you have all what’s needed we can start by building our simple business service which we are going to inject in our actor, here it is:
package org.honeysoft.akka.service; public interface IBusinessService { void doBusiness(Object o); }
… and it’s implementation:
package org.honeysoft.akka.service.impl; import org.honeysoft.akka.service.IBusinessService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @Service public class BusinessService implements IBusinessService { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Override public void doBusiness(Object o) { logger.info("Doing business with {}", o); } }
As we can seen there is nothing fancy in our simple business service. All that it’s doing is logging an information about doing business with someone. Note, however, that it’s annotated with @Service this allows it to be discovered, created and injected by Spring.
Now, let’s see our actor:
package org.honeysoft.akka.actor; import akka.actor.UntypedActor; import org.honeysoft.akka.service.IBusinessService; import org.springframework.beans.factory.annotation.Autowired; public class BusinessActor extends UntypedActor { @Autowired private IBusinessService businessService; @Override public void onReceive(Object o) throws Exception { businessService.doBusiness(o); } }
Once more, nothing special, just injecting our business service using the @Autowired annotation. Now comes the interesting part. We are going to need a custom implementation of akka.actor.Props class:
package org.honeysoft.akka.di; import akka.actor.Props; import akka.actor.UntypedActorFactory; import org.springframework.context.ApplicationContext; public class DependencyInjectionProps extends Props { /** * No-args constructor that sets all the default values. */ public DependencyInjectionProps(ApplicationContext applicationContext, Class<?> actorClass) { super(new SpringUntypedActorFactory(actorClass, applicationContext)); } /** * Java API. */ public DependencyInjectionProps(ApplicationContext applicationContext, UntypedActorFactory factory) { super(new SpringUntypedActorFactory(factory, applicationContext)); } }
The interesting part here is our SpringUntypedActorFactory which is implemented as follows:
package org.honeysoft.akka.di; import akka.actor.UntypedActor; import akka.actor.UntypedActorFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import java.lang.reflect.Field; import static org.fest.reflect.util.Accessibles.setAccessible; import static org.fest.reflect.util.Accessibles.setAccessibleIgnoringExceptions; public class SpringUntypedActorFactory implements UntypedActorFactory { private final DependencyInjectionFactory dependencyInjectionFactory; private final ApplicationContext applicationContext; public SpringUntypedActorFactory(Class<?> actorClass, ApplicationContext applicationContext) { this.dependencyInjectionFactory = new DefaultUntypedActorFactory(actorClass); this.applicationContext = applicationContext; } public SpringUntypedActorFactory(UntypedActorFactory customFactory, ApplicationContext applicationContext) { this.dependencyInjectionFactory = new SpecificUntypedActorFactory(customFactory); this.applicationContext = applicationContext; } private interface DependencyInjectionFactory { UntypedActor createAndInject(); } private abstract class AbstractUntypedActorFactory implements DependencyInjectionFactory { @Override public final UntypedActor createAndInject() { try { UntypedActor untypedActor = create(); Class<?> aClass = getActorClass(); for (Field field : aClass.getDeclaredFields()) { if (field.getAnnotation(Autowired.class) != null) { boolean accessible = field.isAccessible(); try { setAccessible(field, true); field.set(untypedActor, applicationContext.getBean(field.getType())); } catch (IllegalAccessException e) { throw new IllegalStateException("Unable to create actor instance", e); } finally { setAccessibleIgnoringExceptions(field, accessible); } } } return untypedActor; } catch (InstantiationException e) { throw new IllegalStateException("Unable to create actor instance", e); } catch (IllegalAccessException e) { throw new IllegalStateException("Unable to create actor instance", e); } } protected abstract Class<?> getActorClass(); protected abstract UntypedActor create() throws InstantiationException, IllegalAccessException; } private final class SpecificUntypedActorFactory extends AbstractUntypedActorFactory { private final UntypedActorFactory specificFactory; private volatile Class<?> actorClass; private SpecificUntypedActorFactory(UntypedActorFactory specificFactory) { this.specificFactory = specificFactory; } @Override protected Class<?> getActorClass() { return actorClass; } @Override protected UntypedActor create() throws InstantiationException, IllegalAccessException { UntypedActor untypedActor = (UntypedActor) specificFactory.create(); actorClass = untypedActor.getClass(); return untypedActor; } } private final class DefaultUntypedActorFactory extends AbstractUntypedActorFactory { private final Class<?> actorClass; public DefaultUntypedActorFactory(Class<?> actorClass) { this.actorClass = actorClass; } @Override protected Class<?> getActorClass() { return actorClass; } @Override protected UntypedActor create() throws InstantiationException, IllegalAccessException { return (UntypedActor) actorClass.newInstance(); } } /** * This method must return a different instance upon every call. */ @Override public UntypedActor create() { return dependencyInjectionFactory.createAndInject(); } }
Inside our custom SpringUntypedActorFactory is a Strategy pattern which allows us to inject Spring context available beans in an actor fields annotated with @Autowired.
Well, that’s almost all. Now we need a bootstrap component and we are going to provide one using XML-less Spring configuration:
package org.honeysoft.akka; import akka.actor.ActorRef; import akka.actor.ActorSystem; import org.honeysoft.akka.actor.BusinessActor; import org.honeysoft.akka.di.DependencyInjectionProps; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; @Configuration @ComponentScan({"org.honeysoft.akka.service"}) public class Bootstrap { public static final String BUSINESS_ACTOR = "honeysoft-business-actor"; public static final String ACTOR_SYSTEM = "honeysoft-actor-actorSystem"; private ActorSystem actorSystem; @Autowired private ApplicationContext applicationContext; @Bean(name = ACTOR_SYSTEM, destroyMethod = "shutdown") public ActorSystem actorSystem() { actorSystem = ActorSystem.create(ACTOR_SYSTEM); return actorSystem; } @Bean(name = BUSINESS_ACTOR) @DependsOn({ACTOR_SYSTEM}) public ActorRef businessActor() { return actorSystem.actorOf(// new DependencyInjectionProps(applicationContext, BusinessActor.class), BUSINESS_ACTOR); } }
Oh, you can also inject our BusinessActor in another Spring enabled service (or component in general) like this:
import akka.actor.ActorRef; import org.honeysoft.akka.Bootstrap; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; @Service public class MyCoolService { @Autowired @Qualifier(Bootstrap.BUSINESS_ACTOR) private ActorRef businessActor; public void doSomething() { businessActor.tell("message"); // more logic goes here ... } }
That was all folks. Now it’s up to you to leave a comment or contribute on GitHub.
aswin nair
September 29, 2012 at 18:55
Nice post, but I think it can be simplified by doing the following in the Bootstrap and you do not need the special ActorFactory (the bulk of the code). But this does not solve the issue of having actors created under the right hierarchy, it always would be created under the ActorySystem root.
@Inject
ApplicationContext context;
@Bean(name = “actorSystem”, destroyMethod = “shutdown”)
public ActorSystem actorSystem() {
return ActorSystem.create(“defaultActorSystem”);
}
@Bean(name = “businessActor”)
@Scope(“prototype”)
public ActorRef accessPointSyncerRef() {
return actorSystem().actorOf(new Props(new UntypedActorFactory() {
@Override
public Actor create() {
BusinessActor actor = new BusinessActor();
context.getAutowireCapableBeanFactory().autowireBean(accessPointSyncer);
return accessPointSyncer;
}
}), “businessActor”);
}
Ivan
September 29, 2012 at 22:13
Hi there,
Thanks for sharing. I agree that a lot of things can be improved and simplified (otherwise we would be out of job ;-). If you feel like contributing you can find the code on GitHub https://github.com/ihr/akka-di/
Ivan
sabomichal
January 17, 2015 at 20:13
Or if you favor XML based Spring configuration, I would go with UntypedActor spring factories. You can find the example here – https://github.com/sabomichal/akka-java-springfactory.
stephen
January 30, 2013 at 15:25
Nice post… how will the spring config look like in xml
Ivan
February 20, 2013 at 11:24
Hi Stephen, I challenge you to try and come up with the xml yourself and maybe commit your work on GitHub.
Faye
February 20, 2013 at 06:49
I truly tend to go along with almost everything that has been authored
in “AKKA actor dependency injection using Spring
honeysoft”. Thank you for all of the actual advice.
Thank you-Sabrina
Ivan
February 20, 2013 at 11:23
I’m glad that you find the article useful, Sabrina. You can contact me by twitter if you have further questions or post another comment. Also feel free to contribute to the project on GitHub.