├── .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 extends Actor> actorClass() {
28 | return (Class extends Actor>) 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 |
--------------------------------------------------------------------------------