The Spring framework is a popular framework to develop Java web applications. This tutorial gives a introduction into the usage of this framework.
1. The Spring Ecosystem
Spring is a very comprehensive framework for multiple use cases. See Spring modules overview.
The basis of all Spring libraries is the Spring Framework.
The Spring Framework is basically a dependency injection container.
Spring allows to use annotations to configure itself.
The following annotations from the org.springframework.stereotype
package are frequently used:
-
@Service indicates that the classes provides a service and contains no encapsulated state
-
Component indicates that this class can be created by the Spring framework
2. Download and installation of the Spring Tool Suite
The Spring Tool Suite (STS) can be downloaded from the Spring tools site
After the download unpack it and STS is ready to be started. The latest STS release contains a runtime Java, so no additional installation is required.
Issue with the tooling can be reported here: https://github.com/spring-projects/sts4 Nightly builds are available here: https://dist.springsource.com/snapshot/STS4/nightly-distributions.html
3. Exercise - Creating a Spring application
In this exercise a minimal Spring application is created.
3.1. Create a Spring Project
Create a new project, via the
menu entry.Use com.vogella.spring.first.di
as the name.
Use the following input for the wizard.
Press Next and Finish in order to create a simple project.
The project looks like the following:
3.2. Create a data model
Create the a new package called com.vogella.spring.first.di.model
Create the following interface.
package com.vogella.spring.first.di.model;
import java.util.Date;
public interface ITodo {
long getId();
String getSummary();
void setSummary(String summary);
boolean isDone();
void setDone(boolean isDone);
Date getDueDate();
void setDueDate(Date dueDate);
ITodo copy();
}
Also add the implementation of this interface.
package com.vogella.spring.first.di.model;
import java.util.Date;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Todo implements ITodo {
public final long id;
private String summary;
private Boolean done;
private Date dueDate;
public Todo() {
this(-1);
}
public Todo(long i) {
this(i, "");
}
public Todo(long i, String summary) {
this.id = i;
this.summary = summary;
}
@Override
public long getId() {
return id;
}
@Override
public String getSummary() {
return summary;
}
@Override
public void setSummary(String summary) {
this.summary = summary;
}
@Override
public boolean isDone() {
return done;
}
@Override
public void setDone(boolean isDone) {
this.done = isDone;
}
@Override
public Date getDueDate() {
return dueDate;
}
@Override
public void setDueDate(Date dueDate) {
this.dueDate = dueDate;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (id ^ (id >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Todo other = (Todo) obj;
if (id != other.id)
return false;
return true;
}
@Override
public String toString() {
return "Todo [id=" + id + ", summary=" + summary + "]";
}
@Override
public Todo copy() {
Todo todo = new Todo(id, summary);
todo.setDone(this.isDone());
todo.setDueDate(this.getDueDate());
return todo;
}
}
3.3. Configure the package Spring scans for components
You need to configure Spring where to search for annotations which are relevant for Spring.
In the above example of the classes is annotated with the @Component
annotation.
Therefore you need to configure the package to be scanned by Spring for the annotation configuration information.
There are several ways of doing this.
One easy way is to use a Java configuration by using the @Configuration
and @ComponentScan
annotation.
Create a new com.vogella.spring.first.di.config package in the src/main/java source folder.
Create a Config.java
class inside the new package.
package com.vogella.spring.first.di.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = { "com.vogella.spring.first.di.model" })
public class Config {
}
This Config
class configures Spring to scan the whole defined package.
3.4. Test dependency injection and your configuration via the application class
Currently your application class starts a Spring Boot application.
We change this to make our configuration known to and to create an instance of ITodo
based on the @Component
annotation in the Todo
class.
Therefore, change the Application.java
class to the following:
package com.vogella.spring.first.di;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.vogella.spring.first.di.config.Config;
import com.vogella.spring.first.di.model.ITodo;
public class Application {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
ITodo todo = context.getBean(ITodo.class);
System.out.println(todo);
context.close();
}
}
3.5. Validate
Run the Application
class.
Check that an implementation of the ITodo
interface is found and provided by the AnnotationConfigApplicationContext
.
Besides the console output of the Spring framework this should be printed to the console:
Todo [id=-1, summary=]
4. Exercise - Spring DI Bean Config
Configure to use com.vogella.spring.first
for this exercise.
In the previous the Todo object was created by Spring using the @Component
annotation.
But since the Spring DI Container does not contain any objects, which are expected as constructor parameter in the Todo
implementation the no arg constructor is always used.
4.1. Using a custom constructor for Components/Beans
To create a specific Todo
instance, use a method annotated with the @Bean
annotation in the Config
class.
package com.vogella.spring.first.config;
import java.util.Date;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import com.vogella.spring.first.ITodo;
import com.vogella.spring.first.Todo;
@Configuration
@ComponentScan(basePackages = { "com.vogella.spring.first" })
public class Config {
@Bean
public ITodo getTodo() {
ITodo todo = new Todo(0, "Spring DI");
todo.setDone(false);
todo.setDueDate(new Date());
return todo;
}
}
4.2. Validate and fix the NoUniqueBeanDefinitionException
Now run the Application
class again.
You should see an error.
Exception in thread "main" org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [com.vogella.ITodo] is defined: expected single matching bean but found 2: todo,getTodo
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:366)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:332)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1066)
at com.vogella.Application.main(Application.java:12)
Due to the @Component
annotation on the Todo
and the new @Bean
method two Components/Beans are configured.
Spring cannot decide, which one to choose.
This ambiguity has to be handled.
Therefore, either the @Component
annotation on the Todo
has to be removed again or the @Primary
can be added.
package com.vogella.spring.first.di.config;
import java.util.Date;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import com.vogella.spring.first.di.ITodo;
import com.vogella.spring.first.di.Todo;
@Configuration
@ComponentScan(basePackages = { "com.vogella.spring.first.di" })
public class Config {
@Bean
@Primary
public ITodo getTodo() {
ITodo todo = new Todo(0, "Spring DI");
todo.setDone(false);
todo.setDueDate(new Date());
return todo;
}
}
4.3. Validate 2
Run the Application
class and see that an implementation of the ITodo
interface is found and provided by the AnnotationConfigApplicationContext
.
Besides the console output of the Spring framework this should be printed to the console:
Todo [id=0, summary=Spring DI]
5. Exercise - Spring DI Autowire Beans
5.1. Target
Let the beans get their dependencies themselves. Until now only the no arg constructor and an explicitly created Todo
where used.
But Beans themselves can also get their dependencies from Spring’s DI Container.
5.2. Using the @Autowired Annotation
The Todo
's constructor can also be annotated with the @Autowired
annotation.
By doing this and readding the @Component
annotation to the Todo
Spring will try to use the constructor with parameters, which are available in the Spring DI container.
package com.vogella.spring.first.di;
import java.util.Date;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Todo implements ITodo {
public final long id;
private String summary;
private Boolean done;
private Date dueDate;
public Todo() {
this(-1);
}
public Todo(long i) {
this(i, "");
}
@Autowired
public Todo(long i, String summary) {
this.id = i;
this.summary = summary;
}
@Override
public long getId() {
return id;
}
@Override
public String getSummary() {
return summary;
}
@Override
public void setSummary(String summary) {
this.summary = summary;
}
@Override
public boolean isDone() {
return done;
}
@Autowired
@Override
public void setDone(boolean isDone) {
this.done = isDone;
}
@Override
public Date getDueDate() {
return dueDate;
}
@Autowired
@Override
public void setDueDate(Date dueDate) {
this.dueDate = dueDate;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (id ^ (id >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Todo other = (Todo) obj;
if (id != other.id)
return false;
return true;
}
@Override
public String toString() {
return "Todo [id=" + id + ", summary=" + summary + "]";
}
@Override
public Todo copy() {
Todo todo = new Todo(id, summary);
todo.setDone(isDone());
todo.setDueDate(getDueDate());
return todo;
}
}
Now all constructor parameters have to be available so that @Autowired
can work properly.
Optionally also the setter methods, e.g., dueDate and done, can be provided.
package com.vogella.spring.first.di.config;
import java.util.Date;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = { "com.vogella.spring.first.di" })
public class Config {
@Bean
public Long getId() {
return Long.valueOf(0);
}
@Bean
public String getSummary() {
return "Spring DI";
}
@Bean
public Boolean isDone() {
return Boolean.FALSE;
}
@Bean
public Date getDueDate() {
return new Date();
}
}
5.3. Validate
With this approach the Todo
instance will be created by Spring again, but rather with the @Autowired
constructor than the no arg constructor.
The @Autowired
annotation is also used for some of the setter methods.
Setter injection should always be preferred, since classes that use field injection usually cannot be tested properly. |
6. Exercise - Spring DI Qualifier
6.1. Target
In the Exercise - Spring DI Bean Config
an ambiguous Bean declaration has been resolved by using @Primary
.
But @Primary
is very limited and can only be used once for a Bean.
What if one would like to decide, which Bean of the same type should be used under certain conditions?
6.2. Adding a description to the ITodo
The ITodo
interface now should also provide a description for additional information about the todo.
package com.vogella.spring.first.di;
import java.util.Date;
public interface ITodo {
long getId();
String getSummary();
void setSummary(String summary);
void setDescription(String description);
String getDescription();
boolean isDone();
void setDone(boolean isDone);
Date getDueDate();
void setDueDate(Date dueDate);
ITodo copy();
}
The Todo
implementation must implement the description getter and setter.
The description should also be autowired and therefore the setter is annotated with @Autowired
.
In order to see the description in the sample application it is added to the overridden toString()
method.
package com.vogella.spring.first.di;
import java.util.Date;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Todo implements ITodo {
public final long id;
private String summary;
private String description;
private Boolean done;
private Date dueDate;
public Todo() {
this(-1);
}
public Todo(long i) {
this(i, "");
}
@Autowired
public Todo(long i, String summary) {
this.id = i;
this.summary = summary;
}
@Override
public long getId() {
return id;
}
@Override
public String getSummary() {
return summary;
}
@Override
public void setSummary(String summary) {
this.summary = summary;
}
@Override
public String getDescription() {
return description;
}
@Autowired
@Override
public void setDescription(String description) {
this.description = description;
}
@Override
public boolean isDone() {
return done;
}
@Autowired
@Override
public void setDone(boolean isDone) {
this.done = isDone;
}
@Override
public Date getDueDate() {
return dueDate;
}
@Autowired
@Override
public void setDueDate(Date dueDate) {
this.dueDate = dueDate;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (id ^ (id >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Todo other = (Todo) obj;
if (id != other.id)
return false;
return true;
}
@Override
public String toString() {
return "Todo [id=" + id + ", summary=" + summary + ", description=" + description + "]";
}
@Override
public Todo copy() {
Todo todo = new Todo(id, summary);
todo.setDone(isDone());
todo.setDueDate(getDueDate());
todo.setDescription(getDescription());
return todo;
}
}
6.3. Validate 1
Run the application and see its output.
Todo [id=0, summary=Spring DI, description=Spring DI]
So the String bean has been used for both summary and description, since both methods expect a String.
6.4. Adding an additional Bean for the description of a Todo
To get a real description an additional Bean has to be added to the Config.java class.
package com.vogella.spring.first.di.config;
import java.util.Date;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = { "com.vogella.spring.first.di" })
public class Config {
@Bean
public Long getId() {
return Long.valueOf(0);
}
@Bean
public String getSummary() {
return "Spring DI";
}
@Bean
public String getDescription() {
return "Dependency in Spring is a powerful feauture for inversion of control.";
}
@Bean
public Boolean isDone() {
return Boolean.FALSE;
}
@Bean
public Date getDueDate() {
return new Date();
}
}
When running the application this again would result in an NoUniqueBeanDefinitionException
.
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [java.lang.String] is defined: expected single matching bean but found 2: getDescription,getSummary
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1126)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1014)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:813)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:741)
... 14 more
6.5. Using the @Qualifier Annotation
The qualifier annotation can be used to qualify a Bean.
So the summary Bean can be qualified with the word "summary" and the description can be qualified with the word "description".
package com.vogella.spring.first.di.config;
import java.util.Date;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = { "com.vogella.spring.first.di" })
public class Config {
@Bean
public Long getId() {
return Long.valueOf(0);
}
@Bean
@Qualifier("summary")
public String getSummary() {
return "Spring DI";
}
@Bean
@Qualifier("description")
public String getDescription() {
return "Dependency injection in Spring is a powerful feauture for inversion of control.";
}
@Bean
public Boolean isDone() {
return Boolean.FALSE;
}
@Bean
public Date getDueDate() {
return new Date();
}
}
To address a certain qualified Bean the @Qualifier
annotation can either be used on class, parameter, method or field level.
package com.vogella.spring.first.di;
import java.util.Date;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component
public class Todo implements ITodo {
public final long id;
private String summary;
private String description;
private Boolean done;
private Date dueDate;
public Todo() {
this(-1);
}
public Todo(long i) {
this(i, "");
}
@Autowired
public Todo(long i, @Qualifier("summary") String summary) {
this.id = i;
this.summary = summary;
}
@Override
public long getId() {
return id;
}
@Override
public String getSummary() {
return summary;
}
@Override
public void setSummary(String summary) {
this.summary = summary;
}
@Override
public String getDescription() {
return description;
}
@Autowired
@Qualifier("description")
@Override
public void setDescription(String description) {
this.description = description;
}
@Override
public boolean isDone() {
return done;
}
@Autowired
@Override
public void setDone(boolean isDone) {
this.done = isDone;
}
@Override
public Date getDueDate() {
return dueDate;
}
@Autowired
@Override
public void setDueDate(Date dueDate) {
this.dueDate = dueDate;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (id ^ (id >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Todo other = (Todo) obj;
if (id != other.id)
return false;
return true;
}
@Override
public String toString() {
return "Todo [id=" + id + ", summary=" + summary + ", description=" + description + "]";
}
@Override
public Todo copy() {
Todo todo = new Todo(id, summary);
todo.setDone(isDone());
todo.setDueDate(getDueDate());
todo.setDescription(getDescription());
return todo;
}
}
In this case the summary parameter in the constructor is annotated with @Qualifier("summary")
and the setDescription
method is annotated with @Qualifier("description")
.
6.6. Validate 2
Running the application should result in the following output.
Todo [id=0, summary=Spring DI, description=Dependency injection in Spring is a powerful feauture for inversion of control.]
7. Exercise - Spring Data JPA
This exercise shows how to use Spring Data JPA with an H2 database.
7.1. Create a Spring Data JPA Project
Create a spring starter project, via the
menu entry.Use the following input for the wizard:
Now press Next and choose Lombok, JPA and H2.
Then press Finish in order to create a Spring Data JPA project.
7.2. Creating a JPA Entity
This time the Todo
should be stored in a H2 database, therefore the Todo
must make use of JPA annotations, like @Entity
, @Id
and @GeneratedValue
.
In order to save time and avoid writing boilerplate code the @Data`annotation is used to generate getters and setters for the `Todo
entity.
package com.vogella.spring.jpa;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import lombok.Data;
@Entity
@Data
public class Todo {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String summary;
private String description;
private Boolean done;
private Date dueDate;
public Todo() {
}
public Todo(String summary) {
this.summary = summary;
}
public void setId(long id) {
// The id should not be set by clients, there this method is not
// generated by lombok
}
public Todo copy() {
Todo todo = new Todo(summary);
todo.setDone(getDone());
todo.setDueDate(getDueDate());
todo.setDescription(getDescription());
return todo;
}
}
7.3. Creating a DAO / Repository
When using Spring Data JPA it is not necessary to write all the boilerplate code, which usually has to be written to provide common CRUD functionality in a DAO.
For common CRUD functionality the CrudRepository
interface has to be extended and everything else is provided automatically then.
package com.vogella.spring.jpa.repository;
import org.springframework.data.repository.CrudRepository;
import com.vogella.spring.jpa.Todo;
public interface TodoRepository extends CrudRepository<Todo, Long> {
}
The CrudRepository
interface contains several commonly used DAO methods, which will be available automatically for interacting with persisted Todo
objects.
7.4. Validate
To work with the TodoRepository
a CommandLineRunner
can be used.
package com.vogella.spring.jpa;
import java.util.Date;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import com.vogella.spring.jpa.repository.TodoRepository;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public CommandLineRunner jpaSample(TodoRepository todoRepo) {
return (args) -> {
// save 2 todos in the H2 database
todoRepo.save(new Todo("Test"));
Todo todo = new Todo("Detailed test");
todo.setDueDate(new Date());
todo.setDescription("Detailed description");
todoRepo.save(todo);
// query for all todos in the H2 database and print them
todoRepo.findAll().forEach(System.out::println);
};
}
}
Running the application should result in the following output.
Todo [id=1, summary=Test, description=null]
Todo [id=2, summary=Detailed test, description=Detailed description]
7.5. Using the Spring Data JPA Query DSL
For repository interfaces it is possible to create query like method names, which are parsed by Spring.
7.5.1. Exercises Spring Data JPA Query DSL
Please add methods for the following queries:
-
Query Todos by summary
-
Query just one optional Todo by summary and due date
-
Query a stream of Todos by summary or description
-
Count the amout of Todos in the database table
-
Count the amout of Todos with a certain summary
-
Write a custom query for all todos in the year 2016
-
Get available Todos by summary asynchronously
Please do not look at the solutions beforehand and try it on your own.
7.5.2. Solutions for Spring Data JPA Query DSL exercises
Solution: Query Todos by summary
public interface TodoRepository extends JpaRepository<Todo, Long> {
List<Todo> getBySummary(String summary);
}
Solution: Query just one optional Todo by summary and due date
public interface TodoRepository extends JpaRepository<Todo, Long> {
Optional<Todo> findBySummaryAndDueDate(String summary, Date date);
}
Solution: Query a stream of Todos by summary or description
public interface TodoRepository extends JpaRepository<Todo, Long> {
Stream<Todo> readBySummaryOrDescription(String summary, String description);
}
Solution: Count the amout of Todos in the database table
The org.springframework.data.repository.CrudRepository<T, ID>
already offers a count()
method, which can be used for this use case.
Solution: Count the amout of Todos with a certain summary
public interface TodoRepository extends JpaRepository<Todo, Long> {
long countBySummary(String summary);
}
Solution: Write a custom query for all todos in the year 2016
public interface TodoRepository extends JpaRepository<Todo, Long> {
@Query("Select t from Todo t where t.dueDate BETWEEN '2016-01-01' AND '2016-12-31'")
List<Todo> getTodosOf2016();
}
Solution: Get available Todos by summary asynchronously
public interface TodoRepository extends JpaRepository<Todo, Long> {
@Async
CompletableFuture<List<Todo>> findAsyncJava8BySummary(String summary);
@Async
Future<List<Todo>> findAsyncBeforeJava8BySummary(String summary);
}
8. Exercise - Spring Data Rest
8.1. Target
This exercise should show how to use Spring Data Rest with an H2 database.
8.2. Create a Spring Data Rest Project
Create a spring starter project, via the
menu entry.Use the following input for the wizard:
Now press Next and choose Lombok, JPA, H2, Thymeleaf, Rest Repositories and HATEOAS.
Then press Finish in order to create a Spring Data Rest project.
8.3. Reusing Todo and TodoRepository
The Todo
and TodoRepository
can be reused from the previous exercise.
The TodoRepository
needs to have the @RepositoryRestResource
annotation.
package com.vogella.spring.data.rest;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
@RepositoryRestResource
public interface TodoRepository extends JpaRepository<Todo, Long> {
}
The @RepositoryRestResource
annotation will create restful endpoints for
http://localhost:8080/todoes
8.4. Validate
Add some todos to the repo and query them via rest endpoint.
Again a CommandLineRunner
can be used for this.
But this time the data won’t be received from the TodoRepository
directly, but by using the Spring RestTemplate
.
package com.vogella.spring.data.rest;
import java.util.Date;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public CommandLineRunner jpaSample(TodoRepository todoRepo) {
return (args) -> {
// save 2 todos in the H2 database
todoRepo.save(new Todo("Test"));
Todo todo = new Todo("Detailed test");
Date date = new Date();
todo.setDueDate(date);
todo.setDescription("Detailed description");
todoRepo.save(todo);
RestTemplate restTemplate = new RestTemplate();
Todo firstTodo = restTemplate.getForObject("http://localhost:8080/todoes/1", Todo.class);
Todo secondTodo = restTemplate.getForObject("http://localhost:8080/todoes/2", Todo.class);
System.out.println(firstTodo);
System.out.println(secondTodo);
};
}
}
Running the application should result in the following output.
Todo [id=1, summary=Test, description=null]
Todo [id=2, summary=Detailed test, description=Detailed description]
8.5. Configure the rest endpoint path
Spring is able to guess a name for the rest endpoint, as it has been done with the /todoes
endpoint.
But in some situations a different endpoint is desired, e.g., todos or tasks rather then todoes.
Therefore the @RepositoryRestResource
annotation can have additional parameters.
package com.vogella.spring.data.rest;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
@RepositoryRestResource(collectionResourceRel="tasks", path="tasks")
public interface TodoRepository extends JpaRepository<Todo, Long> {
}
And then also the Application
class has to be slightly modified, so that the URLs are
http://localhost:8080/tasks/1 and http://localhost:8080/tasks/2
now.
package com.vogella.spring.data.rest;
import java.util.Date;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public CommandLineRunner jpaSample(TodoRepository todoRepo) {
return (args) -> {
// save 2 todos in the H2 database
todoRepo.save(new Todo("Test"));
Todo todo = new Todo("Detailed test");
Date date = new Date();
todo.setDueDate(date);
todo.setDescription("Detailed description");
todoRepo.save(todo);
RestTemplate restTemplate = new RestTemplate();
Todo firstTodo = restTemplate.getForObject("http://localhost:8080/tasks/1", Todo.class);
Todo secondTodo = restTemplate.getForObject("http://localhost:8080/tasks/2", Todo.class);
System.out.println(firstTodo);
System.out.println(secondTodo);
};
}
}
8.6. Adding new Todos
The provided rest API can also handle post or put requests.
The following snippet can be added at the end of the CommandLineRunner
from the previous section.
// Create a new Todo, which should be added
Todo newTodo = new Todo("New Todo");
newTodo.setDescription("Todo added by rest API");
newTodo.setDone(true);
ResponseEntity<Todo> postForEntity = restTemplate.postForEntity("http://localhost:8080/tasks/", newTodo, Todo.class);
System.out.println(postForEntity);
The output of this snippet should look like this:
<201 Created,Todo [id=0, summary=New Todo, description=Todo added by rest API],{Server=[Apache-Coyote/1.1], Location=[http://localhost:8080/tasks/3], Content-Type=[application/json;charset=UTF-8], Transfer-Encoding=[chunked], Date=[Tue, 31 May 2016 16:48:47 GMT]}>
The Location in the output tells where to find the newly added curl http://localhost:8080/tasks/3 |
9. Exercise - Creating an UI Frontend
9.1. Target
In this exercise a UI frontend is created. Later on different JavaScript frameworks like Rest and React will be used.
9.2. Change the root URI for the rest API
Since the root URI, e.g.,
http://localhost:8080/
of a web application should show an UI to the user rather than json or xml, the root URI for the rest API should be changed.
This can be done by using the spring.data.rest.base-path
property in the application.properties.
spring.data.rest.base-path=/api
The application.properties file is usually located in src/main/resources/application.properties.
Now the URLs, which were used in the CommandLineRunner from previous exercises have to be adjusted.
|
9.3. Creating an index.html single page app
For the UI an index.html file should be created, which contains the single page app powered by React.
For now the index.html file should look like this:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Vogella Spring Training
<meta charset="UTF-8"/>
</head>
<body>
<div id="content">Hello Spring!</div>
</body>
</html>
The index.html file has to be located in src/main/resources/templates.
9.4. Creating a @Controller for the UI
To let the root URI point to this index.html file, the mapping to the index has to be defined.
package com.vogella.spring.data.rest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class RootUriController {
@RequestMapping(value = "/")
public String index() {
return "index";
}
}
9.5. Validate
Start the Spring Application, open up the
http://localhost:8080/
in a browser and see whether Hello Spring! is shown.
10. Exercise - Install and use NPM
10.1. Target
NPM is a build tool to resolve JavaScript dependencies, which should be used in a web project.
10.2. Install NPM
NPM is shipped together with NodeJS, which can be downloaded here: https://nodejs.org/en/download/
On this the proper operating system can be chosen.
To check the installation type npm -v
in the command line.
10.3. Generating a package.json file
A package.json file is used to store the meta data of a JavaScript project. It is pretty similar to a pom.xml or build.gradle file.
Having NPM properly installed it helps creating such a package.json file.
Just go to the /com.vogella.spring.data.rest/src/main/resources/static folder from previous exercises in the command line, type npm init
and give proper input, for example what is shown below.
10.4. Adding dependencies to the project
Dependencies can be added by using the npm install {your-desired-js-package}
command, e.g., npm install react --save
.
--save adds the dependency to the dependency closure in the package.json file.
|
--save-dev adds the dependency to the devDependencies closure in the package.json file, which is used for dependencies during development.
|
The following packages should be added:
-
react (use --save)
-
react-dom (use --save)
-
rest (use --save)
-
babel-core (use --save-dev)
-
babel-loader (use --save-dev)
-
babel-preset-es2015 (use --save-dev)
-
babel-preset-react (use --save-dev)
-
webpack (use --save-dev)
After adding all these packages the contents of the package.json file should look similar to this:
{
"name": "npm-sample",
"version": "1.0.0",
"description": "Creating a sample package.json file",
"main": "app.js",
"keywords": [
"spring",
"npm",
"react",
"rest"
],
"author": "Simon Scholz",
"license": " EPL-1.0",
"dependencies": {
"react": "^15.1.0", (1)
"react-dom": "^15.1.0", (2)
"rest": "^1.3.2" (3)
},
"devDependencies": {
"babel-core": "^6.9.1", (4)
"babel-loader": "^6.2.4", (5)
"babel-preset-es2015": "^6.9.0", (6)
"babel-preset-react": "^6.5.0", (7)
"webpack": "^1.13.1" (8)
}
}
The node_modules folder, which is created besides the package.json file should not be checked into version control. So it’s likely added to a .gitignore file. |
11. Exercise - Configure Webpack and use Babel
11.1. Target
Webpack is a tool to assemble several different JavaScript packages into one build artifact, e.g., build/bundle.js file. Babel can be used to transpile ES6 to ES5 or even react’s jsx to JavaScript, so one can write new code, but still run it in older browsers.
11.2. Create a simple JavaScript file
Inside the /src/main/resources/static directory a simple app.js JavaScript file should be provided.
document.write("Hello first JavaScript sample!");
This app.js script can be built by Webpack.
11.3. Configure Webpack
Webpack can be configured by a webpack.config.js, which basically also just exposes a configuration closure as module.
module.exports = {
entry : './app.js',
cache : true,
debug : true,
output : {
path : __dirname,
filename : './build/bundle.js'
},
module: {
loaders: [
{
test: /\.js$/,
loader: 'babel',
exclude: /node_modules/,
query: {
cacheDirectory: true,
presets: ['es2015', 'react']
}
}
]
}
};
The previously defined app.js script is know being transpiled by Babel if necessary and then packaged into a bundle.js file as output.
11.4. Using Gradle to run Webpack
Create a build.gradle file inside the /src/main/resources/static directory of the project.
buildscript {
repositories {
jcenter()
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath "com.moowork.gradle:gradle-node-plugin:0.12"
}
}
apply plugin: 'com.moowork.node'
task webpack(type: NodeTask, dependsOn: 'npmInstall') {
script = project.file('./node_modules/webpack/bin/webpack.js')
}
Afterwards go into the static directory in the command line and run the gradle webpack
task.
This will build the a bundle.js script file, which has been specified as output in the webpack.config.js configuration.
11.5. Using the built bundle.js script
In order to make use of the built bundle.js file a <script>
tag has to be added.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Vogella Spring Training
<meta charset="UTF-8"/>
</head>
<body>
<div id="content"></div>
<script src="build/bundle.js"></script>
</body>
</html>
11.6. Validate
Start the Spring application, go to
http://localhost:8080
and see wheter the input equals Hello first JavaScript sample!
The big benefit here is that the bundle.js script can be referenced by the HTML page and under the hood the bundle.js script can always be regenerated by using Webpack. |
12. Exercise - Using rest.js
12.1. Target
In this exercise rest.js, which is a minimalistic rest client for JavaScript, should be used to query the rest endpoints.
12.2. Configure the rest.js
In rest.js several different interceptors and converter can be configured.
For this example the hal+json mime type is used. https://github.com/vogellacompany/codeexamples-javaweb To configure a rest.js instance a restclient.js script is created inside the static directory.
'use strict';
var rest = require('rest');
var defaultRequest = require('rest/interceptor/defaultRequest');
var mime = require('rest/interceptor/mime');
var errorCode = require('rest/interceptor/errorCode');
var baseRegistry = require('rest/mime/registry');
var registry = baseRegistry.child();
registry.register('application/hal+json', require('rest/mime/type/application/hal'));
module.exports = rest
.wrap(mime, { registry: registry })
.wrap(errorCode)
.wrap(defaultRequest, { headers: { 'Accept': 'application/hal+json' }});
This restclient.js script can then be obtained and used in the app.js like this:
const client = require('./restclient'); (1)
client({path: '/api/tasks/1'}).then(response => { (2)
console.log('response: ', response);
document.write("response: " + response + " summary: " + response.summary);
}, error => { (3)
document.write("error: " + error);
});
1 | Require the previously specified rest client |
2 | Query a certain path and handle the returning response |
3 | Handle the error case |
13. Exercise - Creating a UI frontend with react
13.1. Target
In this exercise a UI frontend with react is created.
13.2. Using rest.js react to show a list of Todos
In order to make use of react these packages are required:
-
const React = require('react');
-
const ReactDOM = require('react-dom');
With the React.Component
class web components can be defined, which are rendered.
'use strict';
const React = require('react');
const ReactDOM = require('react-dom');
const client = require('./restclient');
class App extends React.Component {
constructor(props) {
super(props);
this.state = {tasks: []};
}
componentDidMount() {
client({method: 'GET', path: '/api/tasks'}).done(response => {
this.setState({tasks: response.entity._embedded.tasks});
});
}
render() {
return (
<TaskList tasks={this.state.tasks}/>
)
}
}
class TaskList extends React.Component{
render() {
var tasks = this.props.tasks.map(task =>
<Task key={task._links.self.href} task={task}/>
);
return (
<table>
<tr>
<th>Summary</th>
<th>Description</th>
</tr>
{tasks}
</table>
)
}
}
class Task extends React.Component{
render() {
return (
<tr>
<td>{this.props.task.summary}</td>
<td>{this.props.task.description}</td>
</tr>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('content')
)
When the app.js script has been changed, Webpack needs to be run again so that a new bundle.js file is generated.
14. Spring resources
14.1. vogella Java example code
If you need more assistance we offer Online Training and Onsite training as well as consulting