├── .gitignore ├── README.md ├── pom.xml └── src └── main ├── java └── de │ └── kimrudolph │ └── akkaflow │ ├── AkkaApplication.java │ ├── actors │ ├── Supervisor.java │ └── TaskActor.java │ ├── beans │ └── Task.java │ ├── configuration │ └── ApplicationConfiguration.java │ ├── extension │ ├── SpringActorProducer.java │ └── SpringExtension.java │ ├── mailbox │ └── PriorityMailbox.java │ └── services │ └── TaskDAO.java └── resources ├── application.conf └── logback.xml /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | *.class 4 | target/ 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Example taken and modified from the tutorial: 2 | https://github.com/typesafehub/activator-akka-java-spring 3 | 4 | Code for http://kimrudolph.de/blog/spring-boot-meets-akka 5 | 6 | Application can be started with: 7 | 8 | $ mvn clean package 9 | $ java -jar target/akkaflow-1.0-SNAPSHOT.jar 10 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | de.kimrudolph 7 | akkaflow 8 | 1.0-SNAPSHOT 9 | 10 | 11 | UTF-8 12 | 1.2.1.RELEASE 13 | 4.1.4.RELEASE 14 | 2.3.9 15 | 16 | 17 | 18 | 19 | 20 | org.springframework.boot 21 | spring-boot-autoconfigure 22 | ${spring-boot-version} 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter 28 | ${spring-boot-version} 29 | 30 | 31 | 32 | org.springframework 33 | spring-core 34 | ${spring-framework-version} 35 | 36 | 37 | 38 | org.springframework 39 | spring-context 40 | ${spring-framework-version} 41 | 42 | 43 | 44 | org.springframework 45 | spring-jdbc 46 | ${spring-framework-version} 47 | 48 | 49 | 50 | com.typesafe.akka 51 | akka-actor_2.11 52 | ${akka-version} 53 | 54 | 55 | 56 | com.typesafe.akka 57 | akka-slf4j_2.11 58 | ${akka-version} 59 | 60 | 61 | 62 | c3p0 63 | c3p0 64 | 0.9.1.2 65 | 66 | 67 | 68 | com.h2database 69 | h2 70 | 1.4.177 71 | 72 | 73 | 74 | ch.qos.logback 75 | logback-classic 76 | 1.1.2 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | org.apache.maven.plugins 86 | maven-compiler-plugin 87 | 3.0 88 | 89 | 90 | 91 | 92 | 93 | 94 | org.springframework.boot 95 | spring-boot-maven-plugin 96 | ${spring-boot-version} 97 | 98 | de.kimrudolph.akkaflow.AkkaApplication 99 | 100 | 101 | 102 | 103 | repackage 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /src/main/java/de/kimrudolph/akkaflow/AkkaApplication.java: -------------------------------------------------------------------------------- 1 | package de.kimrudolph.akkaflow; 2 | 3 | 4 | import akka.actor.ActorRef; 5 | import akka.actor.ActorSystem; 6 | import akka.actor.PoisonPill; 7 | import akka.event.Logging; 8 | import akka.event.LoggingAdapter; 9 | import de.kimrudolph.akkaflow.beans.Task; 10 | import de.kimrudolph.akkaflow.extension.SpringExtension; 11 | import org.springframework.boot.SpringApplication; 12 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 13 | import org.springframework.context.ApplicationContext; 14 | import org.springframework.context.annotation.ComponentScan; 15 | import org.springframework.context.annotation.Configuration; 16 | import org.springframework.jdbc.core.JdbcTemplate; 17 | 18 | import java.util.Random; 19 | 20 | /** 21 | * Tool to trigger messages passed to actors. 22 | */ 23 | @Configuration 24 | @EnableAutoConfiguration 25 | @ComponentScan("de.kimrudolph.akkaflow.configuration") 26 | public class AkkaApplication { 27 | 28 | public static void main(String[] args) throws Exception { 29 | 30 | ApplicationContext context = 31 | SpringApplication.run(AkkaApplication.class, args); 32 | 33 | ActorSystem system = context.getBean(ActorSystem.class); 34 | 35 | final LoggingAdapter log = Logging.getLogger(system, "Application"); 36 | 37 | log.info("Starting up"); 38 | 39 | SpringExtension ext = context.getBean(SpringExtension.class); 40 | 41 | // Use the Spring Extension to create props for a named actor bean 42 | ActorRef supervisor = system.actorOf( 43 | ext.props("supervisor").withMailbox("akka.priority-mailbox")); 44 | 45 | for (int i = 1; i < 1000; i++) { 46 | Task task = new Task("payload " + i, new Random().nextInt(99)); 47 | supervisor.tell(task, null); 48 | } 49 | 50 | // Poison pill will be queued with a priority of 100 as the last 51 | // message 52 | supervisor.tell(PoisonPill.getInstance(), null); 53 | 54 | while (!supervisor.isTerminated()) { 55 | Thread.sleep(100); 56 | } 57 | 58 | log.info("Created {} tasks", context.getBean(JdbcTemplate.class) 59 | .queryForObject("SELECT COUNT(*) FROM tasks", Integer.class)); 60 | 61 | log.info("Shutting down"); 62 | 63 | system.shutdown(); 64 | system.awaitTermination(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/de/kimrudolph/akkaflow/actors/Supervisor.java: -------------------------------------------------------------------------------- 1 | package de.kimrudolph.akkaflow.actors; 2 | 3 | import akka.actor.ActorRef; 4 | import akka.actor.Terminated; 5 | import akka.actor.UntypedActor; 6 | import akka.event.Logging; 7 | import akka.event.LoggingAdapter; 8 | import akka.routing.ActorRefRoutee; 9 | import akka.routing.Routee; 10 | import akka.routing.Router; 11 | import akka.routing.SmallestMailboxRoutingLogic; 12 | import de.kimrudolph.akkaflow.beans.Task; 13 | import de.kimrudolph.akkaflow.extension.SpringExtension; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.context.annotation.Scope; 16 | import org.springframework.stereotype.Component; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | 21 | /** 22 | * A sample supervisor which should handle exceptions and general feedback 23 | * for the actual {@link de.kimrudolph.akkaflow.actors.TaskActor} 24 | *

25 | * A router is configured at startup time, managing a pool of task actors. 26 | */ 27 | @Component 28 | @Scope("prototype") 29 | public class Supervisor extends UntypedActor { 30 | 31 | private final LoggingAdapter log = Logging 32 | .getLogger(getContext().system(), "Supervisor"); 33 | 34 | @Autowired 35 | private SpringExtension springExtension; 36 | 37 | private Router router; 38 | 39 | @Override 40 | public void preStart() throws Exception { 41 | 42 | log.info("Starting up"); 43 | 44 | List routees = new ArrayList(); 45 | for (int i = 0; i < 100; i++) { 46 | ActorRef actor = getContext().actorOf(springExtension.props( 47 | "taskActor")); 48 | getContext().watch(actor); 49 | routees.add(new ActorRefRoutee(actor)); 50 | } 51 | router = new Router(new SmallestMailboxRoutingLogic(), routees); 52 | super.preStart(); 53 | } 54 | 55 | @Override 56 | public void onReceive(Object message) throws Exception { 57 | 58 | if (message instanceof Task) { 59 | router.route(message, getSender()); 60 | } else if (message instanceof Terminated) { 61 | // Readd task actors if one failed 62 | router = router.removeRoutee(((Terminated) message).actor()); 63 | ActorRef actor = getContext().actorOf(springExtension.props 64 | ("taskActor")); 65 | getContext().watch(actor); 66 | router = router.addRoutee(new ActorRefRoutee(actor)); 67 | } else { 68 | log.error("Unable to handle message {}", message); 69 | } 70 | } 71 | 72 | @Override 73 | public void postStop() throws Exception { 74 | log.info("Shutting down"); 75 | super.postStop(); 76 | } 77 | } -------------------------------------------------------------------------------- /src/main/java/de/kimrudolph/akkaflow/actors/TaskActor.java: -------------------------------------------------------------------------------- 1 | package de.kimrudolph.akkaflow.actors; 2 | 3 | import akka.actor.UntypedActor; 4 | import akka.event.Logging; 5 | import akka.event.LoggingAdapter; 6 | import de.kimrudolph.akkaflow.beans.Task; 7 | import de.kimrudolph.akkaflow.services.TaskDAO; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.context.annotation.Scope; 10 | import org.springframework.stereotype.Component; 11 | 12 | @Component 13 | @Scope("prototype") 14 | public class TaskActor extends UntypedActor { 15 | 16 | private final LoggingAdapter log = Logging 17 | .getLogger(getContext().system(), "TaskProcessor"); 18 | 19 | @Autowired 20 | private TaskDAO taskDAO; 21 | 22 | @Override 23 | public void onReceive(Object message) throws Exception { 24 | 25 | Long result = taskDAO.createTask((Task) message); 26 | log.debug("Created task {}", result); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/de/kimrudolph/akkaflow/beans/Task.java: -------------------------------------------------------------------------------- 1 | package de.kimrudolph.akkaflow.beans; 2 | 3 | /** 4 | * Payload container 5 | */ 6 | public class Task { 7 | 8 | private final String payload; 9 | 10 | private final Integer priority; 11 | 12 | public Task(final String payload, final Integer priority) { 13 | this.payload = payload; 14 | this.priority = priority; 15 | } 16 | 17 | public String getPayload() { 18 | return payload; 19 | } 20 | 21 | public Integer getPriority() { 22 | return priority; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/de/kimrudolph/akkaflow/configuration/ApplicationConfiguration.java: -------------------------------------------------------------------------------- 1 | package de.kimrudolph.akkaflow.configuration; 2 | 3 | import akka.actor.ActorSystem; 4 | import com.mchange.v2.c3p0.ComboPooledDataSource; 5 | import com.typesafe.config.Config; 6 | import com.typesafe.config.ConfigFactory; 7 | import de.kimrudolph.akkaflow.extension.SpringExtension; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.context.ApplicationContext; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.ComponentScan; 12 | import org.springframework.context.annotation.Configuration; 13 | import org.springframework.context.annotation.Lazy; 14 | import org.springframework.jdbc.core.JdbcTemplate; 15 | 16 | import java.util.Properties; 17 | 18 | @Configuration 19 | @Lazy 20 | @ComponentScan(basePackages = { "de.kimrudolph.akkaflow.services", 21 | "de.kimrudolph.akkaflow.actors", "de.kimrudolph.akkaflow.extension" }) 22 | public class ApplicationConfiguration { 23 | 24 | // The application context is needed to initialize the Akka Spring 25 | // Extension 26 | @Autowired 27 | private ApplicationContext applicationContext; 28 | 29 | @Autowired 30 | private SpringExtension springExtension; 31 | 32 | /** 33 | * Actor system singleton for this application. 34 | */ 35 | @Bean 36 | public ActorSystem actorSystem() { 37 | 38 | ActorSystem system = ActorSystem 39 | .create("AkkaTaskProcessing", akkaConfiguration()); 40 | 41 | // Initialize the application context in the Akka Spring Extension 42 | springExtension.initialize(applicationContext); 43 | return system; 44 | } 45 | 46 | /** 47 | * Read configuration from application.conf file 48 | */ 49 | @Bean 50 | public Config akkaConfiguration() { 51 | return ConfigFactory.load(); 52 | } 53 | 54 | /** 55 | * Simple H2 based in memory backend using a connection pool. 56 | * Creates th only table needed. 57 | */ 58 | @Bean 59 | public JdbcTemplate jdbcTemplate() throws Exception { 60 | 61 | // Disable c3p0 logging 62 | final Properties properties = new Properties(System.getProperties()); 63 | properties.put("com.mchange.v2.log.MLog", 64 | "com.mchange.v2.log.FallbackMLog"); 65 | properties.put("com.mchange.v2.log.FallbackMLog.DEFAULT_CUTOFF_LEVEL", 66 | "OFF"); 67 | System.setProperties(properties); 68 | 69 | final ComboPooledDataSource source = new ComboPooledDataSource(); 70 | source.setMaxPoolSize(100); 71 | source.setDriverClass("org.h2.Driver"); 72 | source.setJdbcUrl("jdbc:h2:mem:taskdb"); 73 | source.setUser("sa"); 74 | source.setPassword(""); 75 | 76 | JdbcTemplate template = new JdbcTemplate(source); 77 | template.update("CREATE TABLE tasks (id INT(11) AUTO_INCREMENT, " + 78 | "payload VARCHAR(255), updated DATETIME)"); 79 | return template; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/de/kimrudolph/akkaflow/extension/SpringActorProducer.java: -------------------------------------------------------------------------------- 1 | package de.kimrudolph.akkaflow.extension; 2 | 3 | import akka.actor.Actor; 4 | import akka.actor.IndirectActorProducer; 5 | import org.springframework.context.ApplicationContext; 6 | 7 | /** 8 | * An actor producer that lets Spring create the Actor instances. 9 | */ 10 | public class SpringActorProducer implements IndirectActorProducer { 11 | 12 | private final ApplicationContext applicationContext; 13 | private final String actorBeanName; 14 | 15 | public SpringActorProducer(ApplicationContext applicationContext, 16 | String actorBeanName) { 17 | this.applicationContext = applicationContext; 18 | this.actorBeanName = actorBeanName; 19 | } 20 | 21 | @Override 22 | public Actor produce() { 23 | return (Actor) applicationContext.getBean(actorBeanName); 24 | } 25 | 26 | @Override 27 | public Class actorClass() { 28 | return (Class) applicationContext.getType(actorBeanName); 29 | } 30 | } -------------------------------------------------------------------------------- /src/main/java/de/kimrudolph/akkaflow/extension/SpringExtension.java: -------------------------------------------------------------------------------- 1 | package de.kimrudolph.akkaflow.extension; 2 | 3 | import akka.actor.Extension; 4 | import akka.actor.Props; 5 | import org.springframework.context.ApplicationContext; 6 | import org.springframework.stereotype.Component; 7 | 8 | /** 9 | * Extension to tell Akka how to create beans via Spring. 10 | */ 11 | @Component 12 | public class SpringExtension implements Extension { 13 | 14 | private ApplicationContext applicationContext; 15 | 16 | /** 17 | * Used to initialize the Spring application context for the extension. 18 | */ 19 | public void initialize(ApplicationContext applicationContext) { 20 | this.applicationContext = applicationContext; 21 | } 22 | 23 | /** 24 | * Create a Props for the specified actorBeanName using the 25 | * SpringActorProducer class. 26 | */ 27 | public Props props(String actorBeanName) { 28 | return Props.create(SpringActorProducer.class, 29 | applicationContext, actorBeanName); 30 | } 31 | } -------------------------------------------------------------------------------- /src/main/java/de/kimrudolph/akkaflow/mailbox/PriorityMailbox.java: -------------------------------------------------------------------------------- 1 | package de.kimrudolph.akkaflow.mailbox; 2 | 3 | import akka.actor.ActorSystem; 4 | import akka.dispatch.PriorityGenerator; 5 | import akka.dispatch.UnboundedPriorityMailbox; 6 | import com.typesafe.config.Config; 7 | import de.kimrudolph.akkaflow.beans.Task; 8 | 9 | /** 10 | * Simple priority queue mapping the task priority to the mailbox priority. 11 | */ 12 | public class PriorityMailbox extends UnboundedPriorityMailbox { 13 | 14 | public PriorityMailbox(ActorSystem.Settings settings, Config config) { 15 | 16 | // Create a new PriorityGenerator, lower priority means more important 17 | super(new PriorityGenerator() { 18 | 19 | @Override 20 | public int gen(Object message) { 21 | if (message instanceof Task) { 22 | return ((Task) message).getPriority(); 23 | } else { 24 | // default 25 | return 100; 26 | } 27 | } 28 | }); 29 | 30 | } 31 | } -------------------------------------------------------------------------------- /src/main/java/de/kimrudolph/akkaflow/services/TaskDAO.java: -------------------------------------------------------------------------------- 1 | package de.kimrudolph.akkaflow.services; 2 | 3 | 4 | import de.kimrudolph.akkaflow.beans.Task; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.jdbc.core.JdbcTemplate; 7 | import org.springframework.jdbc.core.PreparedStatementCreator; 8 | import org.springframework.jdbc.support.GeneratedKeyHolder; 9 | import org.springframework.jdbc.support.KeyHolder; 10 | import org.springframework.stereotype.Repository; 11 | 12 | import java.sql.Connection; 13 | import java.sql.PreparedStatement; 14 | import java.sql.SQLException; 15 | import java.sql.Statement; 16 | 17 | /** 18 | * DAO for handling {@link de.kimrudolph.akkaflow.beans.Task} creation. 19 | */ 20 | @Repository 21 | public class TaskDAO { 22 | 23 | @Autowired 24 | private JdbcTemplate jdbcTemplate; 25 | 26 | public long createTask(final Task task) { 27 | 28 | KeyHolder holder = new GeneratedKeyHolder(); 29 | 30 | jdbcTemplate.update(new PreparedStatementCreator() { 31 | 32 | @Override 33 | public PreparedStatement createPreparedStatement( 34 | Connection connection) throws SQLException { 35 | PreparedStatement ps = connection 36 | .prepareStatement("INSERT INTO tasks (payload, updated" + 37 | ") VALUES(?, NOW())", 38 | Statement.RETURN_GENERATED_KEYS); 39 | ps.setString(1, task.getPayload()); 40 | return ps; 41 | } 42 | }, holder); 43 | 44 | return holder.getKey().longValue(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | 3 | # Loggers to register at boot time (akka.event.Logging$DefaultLogger logs 4 | # to STDOUT) 5 | loggers = ["akka.event.slf4j.Slf4jLogger"] 6 | 7 | # Log level used by the configured loggers (see "loggers") as soon 8 | # as they have been started; before that, see "stdout-loglevel" 9 | # Options: OFF, ERROR, WARNING, INFO, DEBUG 10 | loglevel = "INFO" 11 | 12 | # Log level for the very basic logger activated during ActorSystem startup. 13 | # This logger prints the log messages to stdout (System.out). 14 | # Options: OFF, ERROR, WARNING, INFO, DEBUG 15 | stdout-loglevel = "ERROR" 16 | 17 | 18 | priority-mailbox { 19 | mailbox-type = "de.kimrudolph.akkaflow.mailbox.PriorityMailbox" 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %date{ISO8601} %-5level %X{akkaSource} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | --------------------------------------------------------------------------------