Skip to content

Commit 0767f8d

Browse files
committed
Initial version with README
1 parent 85e2f2a commit 0767f8d

29 files changed

+1253
-1
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,6 @@ project/plugins/project/
1515
# Scala-IDE specific
1616
.scala_dependencies
1717
.worksheet
18+
19+
# IntelliJ IDEA specific
20+
.idea/

LICENSE

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Apache License
1+
Apache License
22
Version 2.0, January 2004
33
http://www.apache.org/licenses/
44

README.md

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Akka Spring Boot Integration Library
2+
3+
Easy-to-use Scala-friendly integration of Akka with Spring Boot.
4+
Get started quickly with minimal code by using a library that favors convention over configuration.
5+
6+
## Key Requirements
7+
* Akka configured via Spring configuration (property sources)
8+
* Supply a default actor system
9+
* Match actor system lifecycle to Spring context
10+
* Ease the creation of actor beans and actor references
11+
12+
## Getting Started
13+
14+
### build.sbt
15+
16+
````scala
17+
libraryDependencies ++= "com.github.scalaspring" %% "akka-spring-boot" % "0.1.0"
18+
````
19+
20+
### Create a Spring Configuration
21+
22+
Create a configuration class that extends the ActorSystemConfiguration trait and imports the AkkaAutoConfiguration
23+
24+
````scala
25+
@Configuration
26+
@ComponentScan
27+
@Import(Array(classOf[AkkaAutoConfiguration]))
28+
class Configuration extends ActorSystemConfiguration {
29+
30+
// Note: the EchoActor class is part of Akka test kit
31+
@Bean
32+
def echoActor = actorOf[EchoActor]
33+
34+
}
35+
````
36+
37+
### Testing Your Configuration
38+
39+
Create a ScalaTest-based test that uses the configuration
40+
41+
````
42+
@ContextConfiguration(
43+
loader = classOf[SpringApplicationContextLoader],
44+
classes = Array(classOf[AkkaAutoConfigurationSpec.Configuration])
45+
)
46+
class AkkaAutoConfigurationSpec extends FlatSpec with TestContextManagement with Matchers with AskSupport with ScalaFutures with StrictLogging {
47+
48+
implicit val timeout: Timeout = (1 seconds)
49+
50+
@Autowired val echoActor: ActorRef = null
51+
52+
"Echo actor" should "receive and echo message" in {
53+
val message = "test message"
54+
val future = echoActor ? message
55+
56+
whenReady(future) { result =>
57+
logger.info(s"""received result "$result"""")
58+
result should equal(message)
59+
}
60+
}
61+
62+
}
63+
````

build.sbt

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import sbt.Keys._
2+
3+
// Common dependency versions
4+
val akkaVersion = "2.3.9"
5+
val springVersion = "4.1.6.RELEASE"
6+
val springBootVersion = "1.2.3.RELEASE"
7+
8+
lazy val root = (project in file(".")).
9+
settings(net.virtualvoid.sbt.graph.Plugin.graphSettings: _*).
10+
settings(
11+
organization := "com.github.scalaspring",
12+
name := "akka-spring-boot-scala",
13+
description := "Integrates Akka and Spring Boot using standard Spring annotations and convention over configuration",
14+
scalaVersion := "2.11.6",
15+
crossScalaVersions := Seq("2.10.5"),
16+
javacOptions := Seq("-source", "1.7", "-target", "1.7"),
17+
resolvers += "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots",
18+
libraryDependencies ++= Seq(
19+
"com.typesafe.scala-logging" %% "scala-logging" % "3.+",
20+
"com.typesafe.akka" %% "akka-actor" % akkaVersion,
21+
"org.springframework" % "spring-context" % springVersion,
22+
"org.springframework.boot" % "spring-boot-starter" % springBootVersion
23+
// The following dependencies support configuration validation
24+
//"javax.validation" % "validation-api" % "1.1.0.Final",
25+
//"javax.el" % "javax.el-api" % "3.0.1-b04",
26+
//"org.hibernate" % "hibernate-validator" % "5.1.3.Final",
27+
),
28+
// Runtime dependencies
29+
libraryDependencies ++= Seq(
30+
"ch.qos.logback" % "logback-classic" % "1.1.2"
31+
).map { _ % "runtime" },
32+
// Test dependencies
33+
libraryDependencies ++= Seq(
34+
"org.scalatest" %% "scalatest" % "2.2.4",
35+
"com.github.lancearlaus" %% "scalatest-spring" % "0.1.0",
36+
"org.springframework" % "spring-test" % springVersion,
37+
"com.typesafe.akka" %% "akka-testkit" % akkaVersion
38+
).map { _ % "test" },
39+
// Publishing settings
40+
publishMavenStyle := true,
41+
publishArtifact in Test := false,
42+
pomIncludeRepository := { _ => false },
43+
publishTo := {
44+
val nexus = "https://oss.sonatype.org/"
45+
if (isSnapshot.value)
46+
Some("snapshots" at nexus + "content/repositories/snapshots")
47+
else
48+
Some("releases" at nexus + "service/local/staging/deploy/maven2")
49+
},
50+
pomExtra :=
51+
<url>http://github.com/scalaspring/akka-spring-boot-scala</url>
52+
<licenses>
53+
<license>
54+
<name>Apache License, Version 2.0</name>
55+
<url>http://www.apache.org/licenses/LICENSE-2.0.html</url>
56+
<distribution>repo</distribution>
57+
</license>
58+
</licenses>
59+
<scm>
60+
<url>git@github.com:scalaspring/akka-spring-boot-scala.git</url>
61+
<connection>scm:git:git@github.com:scalaspring/akka-spring-boot-scala.git</connection>
62+
</scm>
63+
<developers>
64+
<developer>
65+
<id>lancearlaus</id>
66+
<name>Lance Arlaus</name>
67+
<url>http://lancearlaus.github.com</url>
68+
</developer>
69+
</developers>
70+
)

project/plugins.sbt

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.7.5")
3+
4+
addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0")
5+
6+
addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "0.2.2")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package com.github.lancearlaus.akka.spring;
2+
3+
import org.springframework.beans.factory.annotation.Autowire;
4+
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
5+
import org.springframework.beans.factory.support.AbstractBeanDefinition;
6+
import org.springframework.context.annotation.Bean;
7+
import org.springframework.context.annotation.Scope;
8+
9+
import java.lang.annotation.ElementType;
10+
import java.lang.annotation.Retention;
11+
import java.lang.annotation.RetentionPolicy;
12+
import java.lang.annotation.Target;
13+
14+
/**
15+
* Annotate actor beans (not actor references!) within a Spring configuration.
16+
*
17+
* This annotation SHOULD only be required for Actor-based classes used from third-party
18+
* libraries, etc. Please use the {@code ActorComponent} annotation to annotate your
19+
* classes instead if you're creating your own actors for use with Spring.
20+
*
21+
* @see com.github.lancearlaus.akka.spring.ActorComponent
22+
*/
23+
@Bean
24+
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
25+
@Target({ElementType.METHOD})
26+
@Retention(RetentionPolicy.RUNTIME)
27+
public @interface ActorBean {
28+
/**
29+
* The name of this bean, or if plural, aliases for this bean. If left unspecified
30+
* the name of the bean is the name of the annotated method. If specified, the method
31+
* name is ignored.
32+
*/
33+
String[] name() default {};
34+
35+
/**
36+
* Are dependencies to be injected via convention-based autowiring by name or type?
37+
*/
38+
Autowire autowire() default Autowire.NO;
39+
40+
/**
41+
* The optional name of a method to call on the bean instance during initialization.
42+
* Not commonly used, given that the method may be called programmatically directly
43+
* within the body of a Bean-annotated method.
44+
* <p>The default value is {@code ""}, indicating no init method to be called.
45+
*/
46+
String initMethod() default "";
47+
48+
/**
49+
* The optional name of a method to call on the bean instance upon closing the
50+
* application context, for example a {@code close()} method on a JDBC
51+
* {@code DataSource} implementation, or a Hibernate {@code SessionFactory} object.
52+
* The method must have no arguments but may throw any exception.
53+
* <p>As a convenience to the user, the container will attempt to infer a destroy
54+
* method against an object returned from the {@code @Bean} method. For example, given
55+
* an {@code @Bean} method returning an Apache Commons DBCP {@code BasicDataSource},
56+
* the container will notice the {@code close()} method available on that object and
57+
* automatically register it as the {@code destroyMethod}. This 'destroy method
58+
* inference' is currently limited to detecting only public, no-arg methods named
59+
* 'close' or 'shutdown'. The method may be declared at any level of the inheritance
60+
* hierarchy and will be detected regardless of the return type of the {@code @Bean}
61+
* method (i.e., detection occurs reflectively against the bean instance itself at
62+
* creation time).
63+
* <p>To disable destroy method inference for a particular {@code @Bean}, specify an
64+
* empty string as the value, e.g. {@code @Bean(destroyMethod="")}. Note that the
65+
* {@link org.springframework.beans.factory.DisposableBean} and the
66+
* {@link java.io.Closeable}/{@link java.lang.AutoCloseable} interfaces will
67+
* nevertheless get detected and the corresponding destroy/close method invoked.
68+
* <p>Note: Only invoked on beans whose lifecycle is under the full control of the
69+
* factory, which is always the case for singletons but not guaranteed for any
70+
* other scope.
71+
* @see org.springframework.context.ConfigurableApplicationContext#close()
72+
*/
73+
String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;
74+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.github.lancearlaus.akka.spring;
2+
3+
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
4+
import org.springframework.context.annotation.Scope;
5+
import org.springframework.stereotype.Component;
6+
7+
import java.lang.annotation.ElementType;
8+
import java.lang.annotation.Retention;
9+
import java.lang.annotation.RetentionPolicy;
10+
import java.lang.annotation.Target;
11+
12+
/**
13+
* Annotate actor-specific beans classes.
14+
*
15+
* This is a Spring meta-annotation, the purpose of which is to make code more readable and to avoid
16+
* the common mistake of instantiating actors as singletons (the default for Spring beans).
17+
*/
18+
@Component
19+
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
20+
@Target({ElementType.TYPE})
21+
@Retention(RetentionPolicy.RUNTIME)
22+
public @interface ActorComponent {
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package com.github.lancearlaus.akka.spring.config;
2+
3+
import com.typesafe.config.Config;
4+
import com.typesafe.config.ConfigFactory;
5+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
6+
import org.springframework.context.ConfigurableApplicationContext;
7+
import org.springframework.context.annotation.Bean;
8+
import org.springframework.context.annotation.Configuration;
9+
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
10+
import org.springframework.core.env.ConfigurableEnvironment;
11+
12+
import java.util.Map;
13+
14+
import static com.github.lancearlaus.akka.spring.config.AkkaConfigPropertySourceAdapter.convertIndexedProperties;
15+
import static com.github.lancearlaus.akka.spring.config.AkkaConfigPropertySourceAdapter.getPropertyMap;
16+
17+
/**
18+
* Links Spring environment (property sources) and Akka configuration (Config).
19+
* This allows Akka configuration to be specified via standard Spring-based property source(s) and also allows
20+
* Spring to access Akka-based defaults.
21+
*
22+
* This configuration provides the following:
23+
*
24+
* 1. An Akka configuration bean populated with Akka defaults, overridden by Spring defined properties. This allows
25+
* Akka configuration to be specified via any Spring supported property source (files, URLs, YAML, etc.) while
26+
* still leveraging Akka defaults when not specified.
27+
* 2. An optional property sources placeholder configurer. Created if not defined elsewhere, this bean processes @Value
28+
* annotations that reference properties.
29+
*
30+
* Note: This class is intentionally written in Java to enable use in Java-only projects and due to the fact that
31+
* a static factory method is required for the {@code PropertySourcesPlaceholderConfigurer} bean. Do not instantiate
32+
* this bean in a non-static method, especially in configurations that have autowired properties. You've been warned.
33+
*/
34+
@Configuration
35+
public class AkkaConfigAutoConfiguration {
36+
37+
/**
38+
* Creates an Akka configuration populated via Spring properties with fallback to Akka defined properties.
39+
*/
40+
@Bean
41+
public Config akkaConfig(ConfigurableApplicationContext applicationContext, ConfigurableEnvironment environment) {
42+
final Map<String, Object> converted = convertIndexedProperties(getPropertyMap(environment));
43+
final Config defaultConfig = ConfigFactory.defaultReference(applicationContext.getClassLoader());
44+
45+
return ConfigFactory.parseMap(converted).withFallback(defaultConfig);
46+
}
47+
48+
/**
49+
* Create placeholder configurer to support {@code @Value} annotations that reference environment properties.
50+
*
51+
* Only one placeholder configurer is required, hence the conditional
52+
*/
53+
@Bean @ConditionalOnMissingBean(PropertySourcesPlaceholderConfigurer.class)
54+
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
55+
return new PropertySourcesPlaceholderConfigurer();
56+
}
57+
58+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.github.lancearlaus.akka.spring.config;
2+
3+
import com.typesafe.config.Config;
4+
import com.typesafe.config.ConfigValue;
5+
import org.springframework.core.env.EnumerablePropertySource;
6+
7+
import java.util.ArrayList;
8+
import java.util.List;
9+
import java.util.Map;
10+
import java.util.Set;
11+
12+
/**
13+
* Adapts Akka configuration properties to Spring by exposing them as a standard Spring EnumerablePropertySource.
14+
*/
15+
public class AkkaConfigPropertySource extends EnumerablePropertySource<Config> {
16+
17+
public static final String PROPERTY_SOURCE_NAME = "akkaConfig";
18+
19+
public AkkaConfigPropertySource(Config config) {
20+
super(PROPERTY_SOURCE_NAME, config);
21+
}
22+
23+
@Override
24+
public String[] getPropertyNames() {
25+
final Set<Map.Entry<String, ConfigValue>> entries = source.entrySet();
26+
final List<String> names = new ArrayList<>(entries.size());
27+
28+
for (Map.Entry<String, ConfigValue> entry : entries) {
29+
names.add(entry.getKey());
30+
}
31+
32+
return names.toArray(new String[names.size()]);
33+
}
34+
35+
@Override
36+
public Object getProperty(String name) {
37+
try {
38+
return source.hasPath(name) ? source.getAnyRef(name) : null;
39+
} catch (Throwable t) {
40+
// Catch bad path exceptions and return null
41+
return null;
42+
}
43+
}
44+
45+
}

0 commit comments

Comments
 (0)