Demonstrating the steps that are necessary to use Spring with Scala by translating Spring.io's guides from Java to Scala. A talk I have given at the Spring I/O 2016 in Barcelona
6. Spring I/O
2016
Why Scala?
• For fun
• Learning a new language makes me a better
developer
• Functional Programming!
• For profit
• Scala is (becoming) popular: LinkedIn, Guardian,
Twitter, many more ...
7. Spring I/O
2016
What I like about Scala
(as a Java dev)
• Code tends to be more concise
• Scala was built for FP from ground up
• In Scala I can mix OO + FP
• Java interop allows to move gradually towards
Scala
• Java 8 is catching up (e.g. Lambdas) but Scala still
has many other features (e.g. Pattern matching)
8. Spring I/O
2016
Why Scala & Spring
• Learning a new language is enough, I’d like to keep
my favourite frameworks & tools (SpringBoot,
Gradle)
• New languages come with new frameworks that are
unproven
• I want to gradually introduce Scala into my existing
Java&Spring project
10. Spring I/O
2016
Translated guides from Java to Scala
• Copy & Paste of Java files into Scala dir
• Transformed each .java into .scala
• Kept Gradle as build tool
• Source code available at GitHub
(github.com/BernhardWenzel/spring-scala-
examples)
15. Spring I/O
2016
Controller
@Controller
public class GreetingController {
@RequestMapping("/greeting")
public String greeting(
@RequestParam(value="name",required=false) String name,
Model model) {
model.addAttribute("name", name);
return "greeting";}}
@Controller
class GreetingController {
@RequestMapping(Array("/greeting"))
def greeting(
@RequestParam(value="name", required=false) name: String,
model: Model) : String = {
model.addAttribute("name", name)
"greeting"
}}
16. Spring I/O
2016
That’s it
$ gradle run
. . .
2016-05-06 16:05:52.644 INFO 1297 --- [ restartedMain] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat
2016-05-06 16:05:52.650 INFO 1297 --- [ restartedMain] scala.App$class : Started App.class in
$ curl http://localhost:8080/greeting?name=SpringIO
<!DOCTYPE HTML>
<html>
<head>
<title>Getting Started: Serving Web Content</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<p>Hello, SpringIO!</p>
</body>
</html>
17. Spring I/O
2016
Translation steps:
• Gradle: add scala plugin (optional application)
• Application class: split into companion object in
Scala and run with args:_*
• Controller: @RequestMapping annotation need to
convert String into Array(String)
19. Spring I/O
2016
Beans in Java
Value.java
@JsonIgnoreProperties(ignoreUnknown = true)
public class Value {
private Long id;
private String quote;
public Long getId() {return this.id;}
public String getQuote() {return this.quote;}
public void setId(Long id) {this.id = id;}
public void setQuote(String quote) {this.quote = quote;}
@Override
public String toString() {
return "Value{" +
"id=" + id +
", quote='" + quote + ''' +
'}';
}}
20. Spring I/O
2016
Beans in Scala
Value.scala
@JsonIgnoreProperties(ignoreUnknown = true)
class Value {
@BeanProperty
var id: Long = _
@BeanProperty
var quote: String = _
override def toString : String =
"Value{id=" + id +", quote=" + quote +"}"
}
21. Spring I/O
2016
Application class
public void run(String... args) throws Exception {
RestTemplate restTemplate = new RestTemplate();
Quote quote = restTemplate.getForObject(
"http://gturnquist-quoters.cfapps.io/api/random",
Quote.class);
log.info(quote.toString());
}
override def run(args: String*): Unit = {
val restTemplate = new RestTemplate()
val quote : Quote = restTemplate.getForObject(
"http://gturnquist-quoters.cfapps.io/api/random",
classOf[Quote])
log.info(quote.toString)
}
22. Spring I/O
2016
Asynchronous version using Lambdas
Application.java
AsyncRestTemplate asyncTemplate = new AsyncRestTemplate();
final ListenableFuture<ResponseEntity<Quote>> quoteFuture = asy
"http://gturnquist-quoters.cfapps.io/api/random",
Quote.class);
quoteFuture.addCallback(
success -> log.info("async: " + quote),
failure -> log.error("Async error", failure));
void addCallback(
SuccessCallback<? super T> successCallback,
FailureCallback failureCallback);
23. Spring I/O
2016
Java 8 Lambdas vs.
Scala function literals
(a:Int, b:Int) => a + b
Scala function literal
(Integer a, Integer b) -> a + b
Java 8 lambdas
26. Spring I/O
2016
We need ugly anonymous classes
Application.scala
quoteFuture.addCallback(new ListenableFutureCallback[ResponseEntit
override def onSuccess(entity : ResponseEntity[Quote]) : Unit =
log.info("async: " + entity.getBody.toString)
override def onFailure(t : Throwable) : Unit =
log.error("Async error", t)
})
27. Spring I/O
2016
Works in Scala
$ gradle run
. ____ _ __ _ _
/ / ___'_ __ _ _(_)_ __ __ _
( ( )___ | '_ | '_| | '_ / _` |
/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |___, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.3.3.RELEASE)
…
2016-05-02 11:44:49.052 INFO 3702 --- [ main] hello.Application : async: Quote{type='success'
over configuration, providing an experience on par with frameworks that excel
at early stage development, such as Ruby on Rails.}}
…
BUILD SUCCESSFUL
28. Spring I/O
2016
Translation steps
• Beans: use @BeanProperty annotation
• Java 8 Lambdas != Scala function literals, in Scala
have to use anonymous classes
30. Spring I/O
2016
Person resource in Scala
@Entity
class Person {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@BeanProperty
var id: Long = _
@BeanProperty
var firstName: String = _
@BeanProperty
var lastName: String = _
}
32. Spring I/O
2016
Oops…
PersonRepository.scala:8: type arguments [hello.Person,Long]
do not conform to trait PagingAndSortingRepository's type
parameter bounds [T,ID <: java.io.Serializable]
trait PersonRepository extends
PagingAndSortingRepository[Person, Long] {
scala.Long != java.lang.Long
36. Spring I/O
2016
Final example: JDBC
public void run(String... strings) throws Exception {
jdbcTemplate.execute("DROP TABLE customers IF EXISTS");
jdbcTemplate.execute("CREATE TABLE customers(" +
"id SERIAL, first_name VARCHAR(255), last_name VARCHAR(255)
// Split up the array of whole names into an array of
// first/last names
List<Object[]> splitUpNames = Arrays
.asList("John Woo", "Jeff Dean", "Josh Bloch",
"Josh Long“)
.stream()
.map(name -> name.split(" "))
.collect(Collectors.toList());
. . .
37. Spring I/O
2016
JDBC: Java Batch Update
// Use a Java 8 stream to print out each tuple of the list
splitUpNames.forEach(
name -> log.info(String.format(
Inserting customer record for %s %s",
name[0], name[1])));
// Uses JdbcTemplate's batchUpdate operation to
// bulk load data
jdbcTemplate.batchUpdate(
"INSERT INTO customers(first_name, last_name) VALUES (?,?)",
splitUpNames);
log.info("Querying for customer records where first_name = 'Josh':");
38. Spring I/O
2016
jdbcTemplate.query(
"SELECT id, first_name, last_name FROM customers " +
"WHERE first_name = ?",
new Object[] { "Josh" },
(rs, rowNum) ->
new Customer(
rs.getLong("id"),
rs.getString("first_name"),
rs.getString("last_name"))
).forEach(customer -> log.info(customer.toString()));
public interface RowMapper<T> {
T mapRow(ResultSet rs, int rowNum) throws SQLException;
}
public <T> List<T> query(String sql, Object[] args, RowMapper<T> rowMapper)
JDBC: Java Query
39. Spring I/O
2016
override def run(args: String*): Unit = {
jdbcTemplate.execute("DROP TABLE customers IF EXISTS")
jdbcTemplate.execute("CREATE TABLE customers("id SERIAL,
first_name VARCHAR(255), last_name VARCHAR(255))“)
JDBC Scala create tables
40. Spring I/O
2016
JDBC Scala names
collection
import scala.collection.mutable.ListBuffer
val splitUpNames = ListBuffer(
"John Woo", "Jeff Dean", "Josh Bloch", "Josh Long“)
.map(_.split(" "))
splitUpNames.foreach(name => log.info(
"Inserting customer record for %s %s".format(name(0), name(1))))
Scala: collections are immutable
java.util.List <-> scala.collection.mutable.Buffer
41. Spring I/O
2016
jdbcTemplate.batchUpdate(
"INSERT INTO customers(first_name, last_name) VALUES (?,?)“,
splitUpNames
.asInstanceOf[mutable.Buffer[Array[AnyRef]])
JDBC Scala batchUpdate
java.lang.Object <-> scala.AnyRef
List<Object[]> <-> mutable.Buffer[Array[AnyRef]]
public int[] batchUpdate(String sql, List<Object[]> batchArgs)
42. Spring I/O
2016
JDBC Scala batchUpdate
Still a Scala collection
jdbcTemplate.batchUpdate(
"INSERT INTO customers(first_name, last_name) VALUES (?,?)“,
splitUpNames
.asInstanceOf[mutable.Buffer[Array[AnyRef]])
java.lang.Object <-> scala.AnyRef
List<Object[]> <-> mutable.Buffer[Array[AnyRef]]
public int[] batchUpdate(String sql, List<Object[]> batchArgs)
43. Spring I/O
2016
Convert from Scala
JavaConverters.asJava <- Scala
import collection.JavaConverters._
jdbcTemplate.batchUpdate(
"INSERT INTO customers(first_name, last_name) VALUES (?,?)“,
splitUpNames
.asInstanceOf[mutable.Buffer[Array[AnyRef]].asJava)
44. Spring I/O
2016
jdbcTemplate.query(
"SELECT id, first_name, last_name FROM customers WHERE first_name = ?",
Array("Josh").asInstanceOf[Array[AnyRef]],
new RowMapper[Customer]{
override def mapRow(rs: ResultSet, rowNum: Int): Customer =
new Customer(
rs.getLong("id"),
rs.getString("first_name"),
rs.getString("last_name"))
})
JDBC Scala query
(again casting Object -> AnyRef)
(again implement anonymous class)
52. Spring I/O
2016
Simple translation steps
• add Scala plugin to Gradle
• Application class requires object
• add Array to @Annotations(Array(…))
• Fields of beans require @BeanProperty annotation
53. Spring I/O
2016
Slightly annoying translation steps
• Avoiding Scala base types for Sprint interfaces
• Casting to/from Java (e.g. Java Object to Scala
AnyRef)
• Requiring scala.JavaTransformations toJava and
toScala with collections
• Using Scala mutable collections when interacting
with Java
55. Spring I/O
2016
Annoying translation step
Will work in Scala 2.12
jdbcTemplate.query(
"SELECT id, first_name, last_name FROM customers WHERE first_name = ?",
Array("Josh").asInstanceOf[Array[AnyRef]],
(rs: ResultSet, rowNum: Int) =>
new Customer(
rs.getLong("id"),
rs.getString("first_name"),
rs.getString("last_name")))
• Can’t use Scala functions as Java 8 Lamdbas
56. Spring I/O
2016
Scala SDK is getting better dealing with Java
• Additional frameworks?
• e.g. defunct (Pivotal) Spring-Scala project
• Eases some issues, e.g. no @BeanProperty
necessary
• Better to be consistent across all Java
dependencies
58. Spring I/O
2016
Conclusion
• Drawbacks using Spring instead of pure Scala
framework:
• not always able to write most idiomatic Scala
code
• translation from/to Java can be annoying
sometimes
59. Spring I/O
2016
Conclusion
• Advantages:
• I can keep my Spring productivity & use a well
proven framework while focusing on Scala
features
• Scala SDK constantly improving Java interop so
current issues can be expected to become
obsolete
60. Spring I/O
2016
Thank you
• Source code available at Github:
github.com/BernhardWenzel/spring-scala-examples
• Slides and talk online:
bernhardwenzel.com/blog/2016/04/22/using-spring-
with-scala/
Contact:
• http://bernhardwenzel.com
• twitter: @bernhardwenzel