Spring boot sample #45
Changes from all commits
0617aa0
3ed1c37
d4a0edb
271d955
1ffdf00
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
<?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>com.typesafe.akka.samples</groupId> | ||
<artifactId>akka-sample-spring-extension-java</artifactId> | ||
<version>0.0.1-SNAPSHOT</version> | ||
<packaging>jar</packaging> | ||
|
||
<name>akka-sample-spring-extension-java</name> | ||
<description>Demo project for Spring Boot</description> | ||
|
||
<parent> | ||
<groupId>org.springframework.boot</groupId> | ||
<artifactId>spring-boot-starter-parent</artifactId> | ||
<version>1.5.4.RELEASE</version> | ||
<relativePath /> <!-- lookup parent from repository --> | ||
</parent> | ||
|
||
<properties> | ||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> | ||
<java.version>1.8</java.version> | ||
<scala.version>2.11</scala.version> | ||
<akka.version>2.5.6</akka.version> | ||
<akka.http.version>10.0.10</akka.http.version> | ||
</properties> | ||
|
||
<dependencies> | ||
<!-- Akka Dependencies --> | ||
<dependency> | ||
<groupId>com.typesafe.akka</groupId> | ||
<artifactId>akka-actor_${scala.version}</artifactId> | ||
<version>${akka.version}</version> | ||
</dependency> | ||
|
||
<!-- Spring Dependencies --> | ||
<dependency> | ||
<groupId>org.springframework.boot</groupId> | ||
<artifactId>spring-boot-starter</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>javax.inject</groupId> | ||
<artifactId>javax.inject</artifactId> | ||
<version>1</version> | ||
</dependency> | ||
</dependencies> | ||
|
||
<build> | ||
<plugins> | ||
<plugin> | ||
<groupId>org.apache.maven.plugins</groupId> | ||
<artifactId>maven-compiler-plugin</artifactId> | ||
<configuration> | ||
<source>1.8</source> | ||
<target>1.8</target> | ||
</configuration> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
|
||
</project> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package org.example; | ||
|
||
import org.example.akka.di.SpringExtension; | ||
import org.example.akka.message.Message; | ||
import org.springframework.boot.SpringApplication; | ||
import org.springframework.boot.autoconfigure.SpringBootApplication; | ||
import org.springframework.context.ConfigurableApplicationContext; | ||
|
||
import akka.actor.ActorRef; | ||
import akka.actor.ActorSystem; | ||
|
||
@SpringBootApplication | ||
public class App { | ||
|
||
public static void main(String[] args) { | ||
ConfigurableApplicationContext context = SpringApplication.run(App.class, args); | ||
ActorSystem system = context.getBean("actorSystem", ActorSystem.class); | ||
|
||
// Using constructor with parameters. | ||
ActorRef myActor1 = system.actorOf(SpringExtension.SpringExtProvider.get(system).props("myActor", "Parametrized", 100), "MyActor1"); | ||
myActor1.tell(new Message(), ActorRef.noSender()); | ||
|
||
// Using constructor with no parameters | ||
ActorRef myActor2 = system.actorOf(SpringExtension.SpringExtProvider.get(system).props("myActor"), "MyActor2"); | ||
myActor2.tell(new Message(), ActorRef.noSender()); | ||
|
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package org.example.akka; | ||
|
||
import akka.actor.ActorSystem; | ||
import com.typesafe.config.Config; | ||
import com.typesafe.config.ConfigFactory; | ||
|
||
import org.example.akka.di.SpringExtension; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.context.ApplicationContext; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
|
||
@Configuration | ||
public class AkkaConfig { | ||
|
||
@Autowired | ||
private ApplicationContext applicationContext; | ||
|
||
@Bean(destroyMethod = "terminate") | ||
public ActorSystem actorSystem() { | ||
ActorSystem actorSystem = ActorSystem.create("akka-system", akkaConfiguration()); | ||
SpringExtension.SpringExtProvider.get(actorSystem).initialize(applicationContext); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This means that there is a potential short time when the SpringExtProvider is not yet initialised and trying to create an actor using it would fail with a NPE. This could be made completely safe byt passing the applicationContext to the extension as a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm, at this point I am a little lost. I'm very new to the way Akka works. I understand that the ActorSystem is initialized before the Extension, thus, the ActorSystem could try to create an Actor using the Extension just to find that the Extension´s ApplicationContext attribute is still null. If so, I'm lost here because the initialization of the ActorSystem would depend on the initialization of the Extension's ApplicationContext, and viceversa. Could I get a little extra help to further improve the PR? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
|
||
return actorSystem; | ||
} | ||
|
||
@Bean | ||
public Config akkaConfiguration() { | ||
return ConfigFactory.load(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package org.example.akka.actor; | ||
|
||
import javax.inject.Named; | ||
|
||
import org.example.akka.message.Message; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.context.annotation.Scope; | ||
|
||
import akka.actor.AbstractActor; | ||
|
||
@Named("myActor") | ||
@Scope("prototype") | ||
public class MyActor extends AbstractActor { | ||
|
||
@Autowired | ||
private MyActorService myActorService; | ||
|
||
private String attribute; | ||
private Integer attribute2; | ||
|
||
public MyActor(String attribute, Integer attribute2) { | ||
super(); | ||
this.attribute = attribute; | ||
this.attribute2 = attribute2; | ||
} | ||
|
||
public MyActor() { | ||
this.attribute = "Default value"; | ||
this.attribute2 = Integer.MAX_VALUE; | ||
} | ||
|
||
@Override | ||
public Receive createReceive() { | ||
return receiveBuilder().match(Message.class, msg -> { | ||
myActorService.printService(getSelf().path() + " " + attribute + " " + attribute2); | ||
}).build(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package org.example.akka.actor; | ||
|
||
import javax.annotation.PostConstruct; | ||
|
||
import org.springframework.stereotype.Service; | ||
|
||
@Service | ||
public class MyActorService { | ||
|
||
public MyActorService() { | ||
|
||
} | ||
|
||
@PostConstruct | ||
public void init() { | ||
System.out.println("Created service as bean"); | ||
} | ||
|
||
public void printService(String string) { | ||
System.out.println("Printed with bean: " + string); | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would a typical spring "Service" contain mutable state and be injected in multiple actors? In that case please add a comment here that the service must be thread safe. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not really a state because it's not an attribute. The service just "processes" the input string, in this case by printing it to console. A very simple example to show that the Autowired annotation works. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, I got that the sample is stateless, was more thinking of how it would be in a real(tm) spring app. People tend to copy paste these sample and build whole applications so some friendly comment-nudging in the right direction is often good. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh sure, I'll do that. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package org.example.akka.di; | ||
|
||
import akka.actor.Actor; | ||
import akka.actor.IndirectActorProducer; | ||
import org.springframework.context.ApplicationContext; | ||
|
||
public class SpringActorProducer implements IndirectActorProducer { | ||
private final ApplicationContext applicationContext; | ||
private final String actorBeanName; | ||
final private Object[] args; | ||
|
||
public SpringActorProducer(ApplicationContext applicationContext, String actorBeanName) { | ||
this.applicationContext = applicationContext; | ||
this.actorBeanName = actorBeanName; | ||
this.args = null; | ||
} | ||
|
||
public SpringActorProducer(ApplicationContext applicationContext, String actorBeanName, Object... args) { | ||
this.applicationContext = applicationContext; | ||
this.actorBeanName = actorBeanName; | ||
this.args = args; | ||
} | ||
|
||
@Override | ||
public Actor produce() { | ||
if (args == null) { | ||
return (Actor) applicationContext.getBean(actorBeanName); | ||
} else { | ||
return (Actor) applicationContext.getBean(actorBeanName, args); | ||
} | ||
} | ||
|
||
@SuppressWarnings("unchecked") | ||
@Override | ||
public Class<? extends Actor> actorClass() { | ||
return (Class<? extends Actor>) applicationContext.getType(actorBeanName); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package org.example.akka.di; | ||
|
||
import akka.actor.AbstractExtensionId; | ||
import akka.actor.ExtendedActorSystem; | ||
import akka.actor.Extension; | ||
import akka.actor.Props; | ||
import org.springframework.context.ApplicationContext; | ||
|
||
public class SpringExtension extends AbstractExtensionId<SpringExtension.SpringExt> { | ||
|
||
public static SpringExtension SpringExtProvider = new SpringExtension(); | ||
|
||
@Override | ||
public SpringExt createExtension(ExtendedActorSystem system) { | ||
return new SpringExt(); | ||
} | ||
|
||
public static class SpringExt implements Extension { | ||
private volatile ApplicationContext applicationContext; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the closest I found to the documentation. The only difference is that the Extension class is inside the AbstractExtensionId class. |
||
|
||
public void initialize(ApplicationContext applicationContext) { | ||
this.applicationContext = applicationContext; | ||
} | ||
|
||
public Props props(String actorBeanName) { | ||
return Props.create(SpringActorProducer.class, applicationContext, actorBeanName); | ||
} | ||
|
||
public Props props(String actorBeanName, Object... args) { | ||
return Props.create(SpringActorProducer.class, applicationContext, actorBeanName, args); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package org.example.akka.message; | ||
|
||
import java.io.Serializable; | ||
|
||
public class Message implements Serializable { | ||
|
||
private static final long serialVersionUID = 713662864902548777L; | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please don't format the code using tabs -- follow the same style as other examples do, which is 2 spaces