Making Spring Web Services With Scala
Together, Spring and Scala can create simple, scalable, secure, robust web services that should be at home in the JVM ecosystem.
Join the DZone community and get the full member experience.
Join For FreeSpring is perhaps the most popular web development framework for the Java platform. Scala is a statically typed, functional programming language that runs on the JVM. Scala is highly interoperable with the Java language, so any Java lib can be used alongside Scala. Scala can be used along with Spring's ecosystem to build highly scalable, robust web applications.
The following shows how to create a Spring web application in Scala, Maven, Spring Boot
The code for this is available on GitHub.
Create a Maven Spring Boot Application
Create a Maven project and add the following content to your Maven POM file. The easy way to bootstrap a Spring Boot Maven project is by using Spring Initializer.
<?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>org.springScala</groupId>
<artifactId>spring-scala</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spring-scala</name>
<description>Developing Scala based Spring applications</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.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>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>2.10.4</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>3.1.3</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Our Scala plugin used here will compile Scala code found under the src/main/scala and src/test/scala folders to Java class files (bytecode), during the compile and testCompile phases. We're also adding the Scala lib as a dependency.
Create a Scala Class Application.scala Inside src/main/scala
This is the Spring boot entrypoint class. At runtime, this class boots the Spring application and starts the spring context. The Object keyword is used to create a singleton instance of the class in Scala.
@SpringBootApplication
class Application
object Application extends App {
SpringApplication.run(classOf[Application]);
}
Make It RESTful
@RestController and @RequestMapping are used to create RESTful web services in Spring. A Controller class sample in Scala can be seen below:
@RestController
@RequestMapping(path = Array("/api"))
class UserController(@Autowired val userService: UserService, @Autowired val dataSource: DataSource) {
@GetMapping(path = Array("/users"))
def getAllUsers(): Iterable[Users] = {
userService.listUsers
}
@GetMapping(path = Array("/users/{id}"))
def getUser(@PathVariable id: Long): Users = {
userService.getUser(id)
}
@PostMapping(path = Array("/users"))
def createUser(@RequestBody users: Users): ResponseEntity[Long] = {
val id = userService.createUser(users)
new ResponseEntity(id, new HttpHeaders, HttpStatus.CREATED)
}
}
Making a Service Bean
Here's a sample Service bean in Scala. The service bean is where the web service logic goes into:
@Service
class UserService(@Autowired private val userRepository: UserRepository) {
def listUsers(): Iterable[Users] = {
userRepository.findAll
}
def getUser(id: Long): Users = {
userRepository.findOne(id)
}
def createUser(users: Users): Long = {
userRepository.save(users)
users.id
}
}
Adding Spring Data JPA
The Spring Data JPA Repository class looks like below:
@Repository
trait UserRepository extends CrudRepository[Users, Long] {
def findUserByUsername(username: String): Users
}
Adding the Entity Class
Now let's tackle the JPA Entity class. This can also be a case class, as popularly used in scala. But for simplicity, I am keeping it as just a scale class.
@Entity
class Users extends Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@BeanProperty
var id: Long = _
@BeanProperty
@Column(name = "username")
var username: String = _
@BeanProperty
@Column(name = "password")
var password: String = _
@BeanProperty
@Column(name = "enabled")
var enabled: Boolean = _
}
Adding the Data Source
An embedded H2 Database is used here for demonstration purposes. Spring Boot will create the H2 data source if H2 JARs are available on the classpath. The configuration required for the H2 database is in application.properties inside src/main/resources.
spring.datasource.url=jdbc:h2:~/test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=sa
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
The H2 web console, a client for the H2 embedded database, can also be created using the following configuration. The default username/password would be sa/sa. This would create an H2 console at http://localhost:8080/h2-console:
@Configuration
class H2Config {
@Bean def h2servletRegistration(): ServletRegistrationBean = {
val registrationBean = new ServletRegistrationBean(new WebServlet)
registrationBean.addUrlMappings("/console/*")
return registrationBean
}
}
Spring Boot also runs SQL with the name import.sql inside the resources folder during the startup.
Optional Swagger Configuration
Swagger is a REST web services documentation tool. Swagger also has a nice UI to test your services on the fly. Create a Swagger Configuration class. This would create a swagger UI at http://localhost:8080/swagger-ui.html:
@Configuration
@EnableSwagger2
class SwaggerConfig {
@Bean
def api(): Docket = {
new Docket(DocumentationType.SWAGGER_2).select.apis(RequestHandlerSelectors.any).paths(PathSelectors.any).build
}
}
Swagger exposes the various the REST APIs in the controller. This can be used to test the services.
Securing REST APIs With Spring Security
Create a Scala class called WebSecurityConfig:
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
class WebSecurityConfig(@Autowired val dataSource: DataSource) extends WebSecurityConfigurerAdapter {
override def configure(http: HttpSecurity) = {
http.authorizeRequests.antMatchers("/console", "/console/**", "/console/", "/swagger-ui.html", "/**/*.css", "/**/*.js", "/**/*.png", "/configuration/**", "/swagger-resources", "/v2/**").permitAll
http.authorizeRequests.anyRequest.authenticated
http.csrf.disable
http.headers.frameOptions.disable
http.httpBasic
}
@Bean override def userDetailsService: UserDetailsService = {
val manager = new JdbcDaoImpl
manager.setDataSource(dataSource)
manager
}
}
This Spring security configuration enables basic HTTP authentication. HTTP authentication is required to access the APIs after this change. The users inserted into the database using import.sql root/root can be used to authenticate.
@EnableGlobalMethodSecurity(prePostEnabled = true) enables Spring Security's method-level security. A Service bean can be added with method-level security:
@Service
class UserService(@Autowired private val userRepository: UserRepository) {
@PreAuthorize("hasRole('admin')")
def listUsers(): Iterable[Users] = {
userRepository.findAll
}
@PreAuthorize("hasRole('user')")
@PostAuthorize("returnObject.username==principal.username || hasRole('admin')")
def getUser(id: Long): Users = {
userRepository.findOne(id)
}
@PreAuthorize("hasRole('admin')")
def createUser(users: Users): Long = {
userRepository.save(users)
users.id
}
}
@PreAuthorize and @PostAuthorize can be used to add method-level validations to the application. This basically only allows listUsers() and createUser() operations if the logged-in user has an admin role. Meanwhile, this basically only allows getUser() operations if the logged-in user has a role of 'user'. These roles come from the AUTHORITIES table for different users. The following lines in import.sql will create these roles:
CREATE TABLE authorities (id bigint auto_increment not null, username varchar_ignorecase(50) not null, authority varchar_ignorecase(50) not null, constraint fk_authorities_users foreign key(username) references users(username));
INSERT INTO users (id, username, password,enabled) VALUES (1, 'root', 'root', true), (2, 'user', 'user', true);
INSERT INTO authorities (id, username, authority) VALUES (1, 'root', 'ROLE_user'), (2, 'root', 'ROLE_admin'), (3, 'user', 'ROLE_user');
Testing Web Service Endpoints in Spring Test and Scala
Here's our Spring Integration test class using Spring Test. @SpringBootTest creates a web application context and injects a RestTemplate for testing APIs.
@RunWith(classOf[SpringRunner])
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class UserTests {
@Autowired
var template: TestRestTemplate = _
@Test def testPostCreateUser() = {
val headers = new HttpHeaders
headers.add("Authorization", "Basic " + new String(Base64.encodeBase64(("root" + ":" + "root").getBytes)))
headers.setContentType(MediaType.APPLICATION_JSON)
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON))
val user = new Users
user.setId(101)
user.setUsername("Test")
user.setPassword("Test")
user.setEnabled(true)
val entity = new HttpEntity(user, headers)
val result = template.postForObject("/api/users", entity, classOf[String])
println(result)
}
}
And there you have it! Scala is the most popular functional programming language on the JVM, and Spring is the most popular framework for Java app development. Scala, with Spring, can be used to easily build scalable web services that run on the JVM.
Opinions expressed by DZone contributors are their own.
Comments