Guiding Principles of Rest API
For more information visit the link below:
https://restfulapi.net/
Serializing enums
package com.springdemo.springrestdochateos.enums;
public enum Gender {
MALE("male"),FEMALE("female");
private String displayName;
Gender(String displayName) {
this.displayName = displayName;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
}
@GetMapping("/genders")
public List<Gender> getGenderList(){
return Arrays.asList(Gender.values());
}
Enum as Json
package com.springdemo.springrestdochateos.enums;
import com.fasterxml.jackson.annotation.JsonFormat;
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum Gender {
MALE("male"),FEMALE("female");
private String displayName;
Gender(String displayName) {
this.displayName = displayName;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
}
Serializing an Enum
package com.springdemo.springrestdochateos.enums;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import java.io.IOException;
public class GenderSerializer extends StdSerializer<Gender> {
protected GenderSerializer(Class<Gender> t) {
super(t);
}
protected GenderSerializer() {
super(Gender.class);
}
@Override
public void serialize(Gender value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeStartObject();
gen.writeFieldName("name");
gen.writeObject(value.name());
gen.writeFieldName("displayName");
gen.writeObject(value.getDisplayName());
gen.writeEndObject();
}
}
package com.springdemo.springrestdochateos.enums;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
@JsonSerialize(using = GenderSerializer.class)
public enum Gender {
MALE("male"),FEMALE("female");
private String displayName;
Gender(String displayName) {
this.displayName = displayName;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
}
Exercise 1
Spring Data Rest
Spring Data Rest builds on top of Spring Data repositories and automatically export those as Rest Resource.
Repositories Detection Strategy
Spring Data REST uses Repositories Detection Strategy to determine if a repository will be exported as Rest resource or not.
Name | Description |
---|---|
Default | Expose all public repositories consider exported flag of Rest Resource |
All | Expose all repositories |
Annotations | Only repositories annotated with @RestResource |
Visibility | Only public repositories annotated are exposed |
Changing base URI
spring.data.rest.basePath=/api
Spring data Rest serves up the rest resource at the Root URI "/".
In order to change this default setting just set the following value in application.properties.
Name | Description |
---|---|
basepath | Root URI for Spring Data Rest |
defaultPageSize | Default number of items served in single page |
pageParamName | Name of query param for page |
limitParamName | Name of query param for items to show in page |
sortParamName | Name of query param for sorting |
returnBodyOnCreate | body should return created entity |
returnBodyOnUpdate | body should return updated entity |
Repository Resources Fundamentals
Spring Data REST uses HAL to render Responses. HAL defines links to be contained in a property of the returned document.
build.gradle
buildscript {
ext {
springBootVersion = '2.0.4.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
repositories {
mavenCentral()
}
dependencies {
compile('org.springframework.boot:spring-boot-starter-data-jpa')
compile('org.springframework.boot:spring-boot-starter-data-rest')
compile('org.springframework.boot:spring-boot-starter-hateoas')
compile('org.springframework.boot:spring-boot-starter-web')
runtime('mysql:mysql-connector-java')
testCompile('org.springframework.boot:spring-boot-starter-test')
testCompile('org.springframework.restdocs:spring-restdocs-mockmvc')
}
Employee Repository
package com.springdemo.springrestdochateos.repositories;
import com.springdemo.springrestdochateos.entities.Employee;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
@RepositoryRestResource
public interface EmployeeRepository extends CrudRepository<Employee,Long> {
}
package com.springdemo.springrestdochateos.entities;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Employee {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Integer age;
private Integer salary;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Integer getSalary() {
return salary;
}
public void setSalary(Integer salary) {
this.salary = salary;
}
@Override
public String toString() {
return "Employee{" +
"id=" + id +
", name='" + name + '\'' +
", age='" + age + '\'' +
", salary=" + salary +
'}';
}
}
Employee Entity
Bootstrap Data
package com.springdemo.springrestdochateos.events;
import com.springdemo.springrestdochateos.entities.Employee;
import com.springdemo.springrestdochateos.repositories.EmployeeRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import java.util.stream.IntStream;
@Component
public class Bootstrap {
@Autowired
EmployeeRepository employeeRepository;
@EventListener(ApplicationStartedEvent.class)
public void init(){
if(!employeeRepository.findAll().iterator().hasNext()) {
IntStream.range(1,51).forEach(e->{
Employee employee = new Employee();
employee.setAge(20+e);
employee.setName("name "+e);
employee.setSalary(20000+(e*1000));
employeeRepository.save(employee);
});
}
}
}
package com.springdemo.springrestdochateos;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@SpringBootApplication
@EnableJpaRepositories(basePackages = "com.springdemo.springrestdochateos.repositories")
@EntityScan(basePackages = "com.springdemo.springrestdochateos.entities")
public class SpringRestDocHateosApplication {
public static void main(String[] args) {
SpringApplication.run(SpringRestDocHateosApplication.class, args);
}
}
Config file for Repository Detection Strategy
package com.springdemo.springrestdochateos.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.core.mapping.RepositoryDetectionStrategy;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter;
@Configuration
public class SpringRestConfigurerAdapter extends RepositoryRestConfigurerAdapter {
@Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.setRepositoryDetectionStrategy(RepositoryDetectionStrategy
.RepositoryDetectionStrategies.ANNOTATED);
}
}
Request
http://localhost:8080/api
Reponse
{
"_links" : {
"employees" : {
"href" : "http://localhost:8080/api/employees"
},
"profile" : {
"href" : "http://localhost:8080/api/profile"
}
}
}
http://localhost:8080/api/employees/{id}
{"name":"Peter","age":27,"salary":26000}
{"name":"Peter","age":27,"salary":26000}
Note: Content-Type should be application/json or application/hal+json
Default Operations
Provide pagination and Sorting
package com.springdemo.springrestdochateos.repositories;
import com.springdemo.springrestdochateos.entities.Employee;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
@RepositoryRestResource
public interface EmployeeRepository extends PagingAndSortingRepository<Employee,Long> {
}
Extend your repository with PaginationAndSortingRepository
Now you can hit the url below to get the sorted and paginated data:
http://localhost:8080/api/employees?page=0&size=5&sort=age,desc
Expose Ids
package com.springdemo.springrestdochateos.config;
import com.springdemo.springrestdochateos.entities.Employee;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.core.mapping.RepositoryDetectionStrategy;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter;
@Configuration
public class SpringRestConfigurerAdapter extends RepositoryRestConfigurerAdapter {
@Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.setRepositoryDetectionStrategy(RepositoryDetectionStrategy
.RepositoryDetectionStrategies.ANNOTATED)
.exposeIdsFor(Employee.class);
}
}
Ignore attribute
In order to ignore the attribute on an entity just place @JsonIgnore annotation on the entity
Additional methods
package com.springdemo.springrestdochateos.repositories;
import com.springdemo.springrestdochateos.entities.Employee;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.data.rest.core.annotation.RestResource;
import java.util.List;
@RepositoryRestResource
public interface EmployeeRepository extends PagingAndSortingRepository<Employee,Long> {
List<Employee> findAllByAge(Integer age);
Employee findByName(String name);
}
Now make a GET request on following url:
http://localhost:8080/api/employees/search/
(Cont.)
Response
{
"_links" : {
"findByName" : {
"href" : "http://localhost:8080/api/employees/search/findByName{?name}",
"templated" : true
},
"findAllByAge" : {
"href" : "http://localhost:8080/api/employees/search/findAllByAge{?age}",
"templated" : true
},
"self" : {
"href" : "http://localhost:8080/api/employees/search/"
}
}
}
find employee by age
http://localhost:8080/api/employees/search/findByAge?age=23
Change the path of your resource
package com.springdemo.springrestdochateos.repositories;
import com.springdemo.springrestdochateos.entities.Employee;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
@RepositoryRestResource(path = "/persons")
public interface EmployeeRepository extends PagingAndSortingRepository<Employee,Long> {
Employee findByAge(@Param("age") Integer age);
Employee findByName(@Param("name") String name);
}
Now employee will be found on : http://localhost:8080/api/persons
Hide a search
package com.springdemo.springrestdochateos.repositories;
import com.springdemo.springrestdochateos.entities.Employee;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.data.rest.core.annotation.RestResource;
@RepositoryRestResource(path = "/persons")
public interface EmployeeRepository extends PagingAndSortingRepository<Employee,Long> {
@RestResource(exported = false)
Employee findByAge(@Param("age") Integer age);
Employee findByName(@Param("name") String name);
}
Hide RestResource
package com.springdemo.springrestdochateos.repositories;
import com.springdemo.springrestdochateos.entities.Employee;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.data.rest.core.annotation.RestResource;
@RepositoryRestResource(path = "/persons",exported = false)
public interface EmployeeRepository extends PagingAndSortingRepository<Employee,Long> {
Employee findByAge(@Param("age") Integer age);
Employee findByName(@Param("name") String name);
}
Exercise 2
HATEOAS
Lets take simple example of shopping items online to understand HATEOAS
Lets create a class Item
package com.springdemo.springrestdochateos.entities;
public class Item {
private Integer itemId;
private String name;
private Integer price;
public Item(Integer itemId, String name, Integer price) {
this.itemId = itemId;
this.name = name;
this.price = price;
}
public Integer getItemId() {
return itemId;
}
public void setItemId(Integer itemId) {
this.itemId = itemId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getPrice() {
return price;
}
public void setPrice(Integer price) {
this.price = price;
}
}
package com.springdemo.springrestdochateos.entities;
import java.util.ArrayList;
import java.util.List;
public class Cart {
private List<Item> list= new ArrayList<>();
public List<Item> getList() {
return list;
}
public void setList(List<Item> list) {
this.list = list;
}
}
Now Lets create a shopItemController
package com.springdemo.springrestdochateos.controllers;
import com.springdemo.springrestdochateos.entities.Cart;
import com.springdemo.springrestdochateos.entities.Item;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.mvc.ControllerLinkBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
import java.util.List;
@RestController
@RequestMapping("/item")
public class ShopItemsController {
Item item1 = new Item(1,"Item1",25);
Item item2 = new Item(2,"Item2",30);
List<Item> items=Arrays.asList(item1,item2);
Cart cart= new Cart();
@GetMapping("/{id}")
public Item getItem1(@PathVariable("id") Integer id){
Item selectedItem=items.stream().filter(item->item.getItemId()==id).findFirst().get();
return selectedItem;
}
@GetMapping("/addToCart/{id}")
public Cart addToCart(@PathVariable("id") Integer id){
Item selectedItem=items.stream().filter(item->item.getItemId()==id).findFirst().get();
cart.getList().add(selectedItem);
return cart;
}
@GetMapping("/removeFromCart/{id}")
public Cart removeCart(@PathVariable("id") Integer id){
Item selectedItem=items.stream().filter(item->item.getItemId()==id).findFirst().get();
cart.getList().remove(selectedItem);
return cart;
}
@GetMapping("/cart")
public Cart getCart(){
return cart;
}
}
In order to Provide the support of introducing links in the resource. We do it by extending ResourceSupport class
package com.springdemo.springrestdochateos.entities;
import org.springframework.hateoas.ResourceSupport;
public class Item extends ResourceSupport {
private Integer itemId;
private String name;
private Integer price;
public Item(Integer itemId, String name, Integer price) {
this.itemId = itemId;
this.name = name;
this.price = price;
}
public Integer getItemId() {
return itemId;
}
public void setItemId(Integer itemId) {
this.itemId = itemId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getPrice() {
return price;
}
public void setPrice(Integer price) {
this.price = price;
}
}
Returning Item response with HATEOAS Links
package com.springdemo.springrestdochateos.controllers;
import com.springdemo.springrestdochateos.entities.Cart;
import com.springdemo.springrestdochateos.entities.Item;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.mvc.ControllerLinkBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
import java.util.List;
@RestController
@RequestMapping("/item")
public class ShopItemsController {
Item item1 = new Item(1,"Item1",25);
Item item2 = new Item(2,"Item2",30);
List<Item> items=Arrays.asList(item1,item2);
Cart cart= new Cart();
@GetMapping("/{id}")
public Item getItem1(@PathVariable("id") Integer id){
Link selfLink= ControllerLinkBuilder
.linkTo(ShopItemsController.class)
.slash(id).withSelfRel();
Link addToCart=ControllerLinkBuilder
.linkTo(ShopItemsController.class)
.slash("addToCart").slash(id).withRel("addToCart");
Link removeFromCart=ControllerLinkBuilder
.linkTo(ShopItemsController.class)
.slash("removeFromCart").slash(id).withRel("removeFromCart");
Item selectedItem=items.stream().filter(item->item.getItemId()==id).findFirst().get();
selectedItem.getLinks().clear();
selectedItem.add(selfLink);
selectedItem.add(addToCart);
selectedItem.add(removeFromCart);
return selectedItem;
}
@GetMapping("/addToCart/{id}")
public Cart addToCart(@PathVariable("id") Integer id){
Item selectedItem=items.stream().filter(item->item.getItemId()==id).findFirst().get();
cart.getList().add(selectedItem);
return cart;
}
@GetMapping("/removeFromCart/{id}")
public Cart removeCart(@PathVariable("id") Integer id){
Item selectedItem=items.stream().filter(item->item.getItemId()==id).findFirst().get();
cart.getList().remove(selectedItem);
return cart;
}
@GetMapping("/cart")
public Cart getCart(){
return cart;
}
}
Exercise 3
Swagger
Swagger can be used to document our rest APIs
Following are the steps to configure swagger in our application:
compile('io.springfox:springfox-swagger2:2.2.2')
compile('io.springfox:springfox-swagger-ui:2.2.2')
@EnableSwagger2 and Register the Docket Bean
package com.springdemo.springrestdochateos;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.task.TaskExecutor;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.time.LocalDate;
import java.util.Arrays;
@SpringBootApplication
@EnableJpaRepositories(basePackages = "com.springdemo.springrestdochateos.repositories")
@EntityScan(basePackages = "com.springdemo.springrestdochateos.entities")
@EnableAsync
@EnableCaching
@EnableSwagger2
public class SpringRestDocHateosApplication {
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build().pathMapping("/")
.directModelSubstitute(LocalDate.class,String.class)
.genericModelSubstitutes(ResponseEntity.class)
.apiInfo(metaData());
}
private ApiInfo metaData() {
ApiInfo apiInfo = new ApiInfo(
"Spring Boot REST API",
"Spring Boot REST API for Online Store",
"1.0",
"Terms of service",
"Spring-Developer",
"Apache License Version 2.0",
"https://www.apache.org/licenses/LICENSE-2.0");
return apiInfo;
}
@Bean
public TaskExecutor myTaskExecutor(){
return new ThreadPoolTaskExecutor();
}
@Bean
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Arrays.asList(
new ConcurrentMapCache("CacheResource")
));
return cacheManager;
}
public static void main(String[] args) {
SpringApplication.run(SpringRestDocHateosApplication.class, args);
}
}
In order to display swagger UI make the following configuration (Configure this class only if your swagger html is not rendering )
package com.springdemo.springrestdochateos.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
@EnableWebMvc
public class CustomWebSecurityAdapter extends WebMvcConfigurerAdapter {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
Following are the URLs for swagger
For swagger doc
http://localhost:8080/v2/api-docs
For swagger-UI
http://localhost:8080/swagger-ui.html
You can use @ApiOperation on Resource to be more descriptive
@ApiOperation(value = "Get the item")
Exercise 4
Introduce swagger for you Restful API
Evolving API without breaking client
Understanding and anticipating change
A clear message to clients : Ignore any unexpected data in the interaction with the API
Why don't just version your API?
Types of changes
Adding new data to a resource
Changing data type in an Output resource
Change data type in an input resource
Change data structure in a resource
Breaking change
Remove data from a resource
Deprecation Policy and Sunsetting
Fundamentals of monitoring with boot
Dependency required for actuator
compile('org.springframework.boot:spring-boot-starter-actuator')
In order to activate end points for actuator set following property in application.properties
management.endpoints.web.exposure.include=*
/actuator will give you the details on the endpoints available
Some customisations in application.properties
management.endpoints.web.exposure.include=* (To enable all the endpoints by default) management.endpoint.shutdown.enabled=true (To enable single endpoint) info.app.name=My App info.build.version=1.0.2 (Specify app info)
management.endpoint.health.show-details=always
(Gives full details about disk space and DM try shutting down Mysql and running health check again)
Getting git details in info
Introduce following plugin in build.gradle
plugins { id "com.gorylenko.gradle-git-properties" version "1.5.1" }
In application.properties set following value
management.info.git.mode=full
Custom Heath check
package com.springdemo.springrestdochateos.config;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.stereotype.Component;
@Component
public class CustomHealthCheck extends AbstractHealthIndicator {
public boolean checkCachingLayerErrors(){
return true;
}
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
if(checkCachingLayerErrors()){
builder.down().withDetail("Caching layer message","It is down for some reason");
}
}
}
Hit actuator/health to see the output
Using Project properties for /info
group = 'com.spring-demo'
version = '0.0.1-SNAPSHOT'
description="This is a spring project"
processResources{
expand(project.properties)
}
Please make sure you have following property set in build.gradle
Do the following entries in application.properties
# INFO ENDPOINT CONFIGURATION
info.app.name=${project.name}
info.app.description=${project.description}
info.app.version=${project.version}
/metrics
In order to get the options available for metrics just hit the link below
http://localhost:8080/actuator/metrics
To get the details of a metrics hit the link below:
http://localhost:8080/actuator/metrics/{requiredMetricName}
ETags
If the condition doesn't match then it returns 304 Not Modified status
package com.springdemo.springrestdochateos;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.web.filter.ShallowEtagHeaderFilter;
import javax.servlet.Filter;
@SpringBootApplication
@EnableJpaRepositories(basePackages = "com.springdemo.springrestdochateos.repositories")
@EntityScan(basePackages = "com.springdemo.springrestdochateos.entities")
public class SpringRestDocHateosApplication {
@Bean
public Filter filter(){
ShallowEtagHeaderFilter filter=new ShallowEtagHeaderFilter();
return filter;
}
public static void main(String[] args) {
SpringApplication.run(SpringRestDocHateosApplication.class, args);
}
}
Configuring SwallowEtagHeaderFilter bean
Now lets make a get request to the employee resource
http://localhost:8080/person/1
Now in the response header we will get ETag value. Now again hit the previous resource but this time pass request header If-None-Match: "ETag value in double codes"
You will notice that HttpStatus 304 is returned with no response
Exercise 5
Long Running requests
@GetMapping("/longRunningThread")
public Callable<String> getResponse() throws Exception{
System.out.println("Request Recieved");
Callable<String> stringCallable= new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(2000L);
System.out.println("Execute Long running something");
return "Done";
}
};
System.out.println("Request Completed");
return stringCallable;
}
@Bean
public TaskExecutor myTaskExecutor(){
return new ThreadPoolTaskExecutor();
}
Deferred Execution
@GetMapping("/Deferred")
public DeferredResult<String> getDeferredResult(){
DeferredResult<String> deferredResult= new DeferredResult<>();
CompletableFuture.supplyAsync(()->{
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Deferred Result";
}).whenComplete((result,ex)->{ deferredResult.setResult(result);});
return deferredResult;
}
Async Operation
@GetMapping("/asyncOperation")
public String asyncOperation(){
System.out.println("recieved request");
dummy.longAsyncOperation();
return "Done" ;
}
package com.springdemo.springrestdochateos.services;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class Dummy {
@Async
public void longAsyncOperation() {
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("long asyn operation");
}
}
Enable async via @EnableAsyn annotation
Caching the API response
@EnableCaching
compile('org.springframework.boot:spring-boot-starter-cache')
Include following dependency in build.gradle
@Bean
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Arrays.asList(
new ConcurrentMapCache("CacheResource")
));
return cacheManager;
}
@Cacheable(value = "CacheResource")
@GetMapping("/cachedData")
public String cache() throws InterruptedException {
Thread.sleep(4000L);
return "Hello Cache";
}
@GetMapping("/evictCaching")
@CacheEvict(value = "CacheResource")
public String cacheEvict(){
return "Cache Cleared";
}