Skip to content
This repository has been archived by the owner on Apr 18, 2024. It is now read-only.

Spring boot sample #45

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
63 changes: 63 additions & 0 deletions akka-sample-spring-extension-java/pom.xml
@@ -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>
@@ -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);
Copy link
Member

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

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());

}
}
@@ -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);
Copy link
Member

Choose a reason for hiding this comment

The 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 akka.actor.setup.Setup instead, and pick it up from the actor system in createExtension. That way the application context field of the extension could also be final instead of having to be volatile.

Copy link
Author

Choose a reason for hiding this comment

The 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?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Setup API allows you to define and pass objects into the actorsystem that is then available through ActorSystem.settings().setup(). Create your own Setup subclass which takes the application context to its constructor and keeps it as a final field. Something like this:

class SpringSetup extends Setup { ... }

// where you create the actor system
ActorSystem system = ActorSystem.create("system-name", 
        ActorSystemSetup.create(new SpringSetup(applicationContext)));
    
// in the extension
ApplicationContext ctx = system.settings().setup().get(SpringSetup.class)
  .orElseThrow(new RuntimeException("ActorSystem must be created with a SpringSetup"))
  .getApplicationContext

return actorSystem;
}

@Bean
public Config akkaConfiguration() {
return ConfigFactory.load();
}
}
@@ -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();
}
}
@@ -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);
}
}
Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Author

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh sure, I'll do that.

@@ -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);
}
}
@@ -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;
Copy link
Author

Choose a reason for hiding this comment

The 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);
}
}
}
@@ -0,0 +1,9 @@
package org.example.akka.message;

import java.io.Serializable;

public class Message implements Serializable {

private static final long serialVersionUID = 713662864902548777L;

}