Difference between revisions of "SpringBoot"
Line 294: | Line 294: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
+ | |||
+ | ==Error Handling for REST with Spring & SpringBoot== | ||
+ | ===Overview=== | ||
+ | '''Before Spring 3.2''', the two main approaches to handling exceptions in a Spring MVC application were: '''HandlerExceptionResolver''' or the '''@ExceptionHandler''' annotation. Both of these have some clear downsides. | ||
+ | |||
+ | After '''3.2''' we now have the new '''@ControllerAdvice''' annotation to address the limitations of the previous two solutions. | ||
+ | |||
+ | |||
+ | All of these do have one thing in common – they deal with the '''[[separation of concerns]]''' very well. The app can throw exception normally to indicate a failure of some kind – exceptions which will then be handled separately. | ||
+ | |||
+ | ===Solution 1 – The Controller level @ExceptionHandler=== | ||
+ | ====The New @ControllerAdvice (Spring 3.2 And Above)==== | ||
+ | Spring 3.2 brings support for a global @ExceptionHandler with the new @ControllerAdvice annotation. This enables a mechanism that breaks away from the older MVC model and makes use of ResponseEntity along with the type safety and flexibility of @ExceptionHandler: | ||
+ | |||
+ | <syntaxhighlight lang="java"> | ||
+ | @ControllerAdvice | ||
+ | public class RestResponseEntityExceptionHandler | ||
+ | extends ResponseEntityExceptionHandler { | ||
+ | |||
+ | @ExceptionHandler(value | ||
+ | = { IllegalArgumentException.class, IllegalStateException.class }) | ||
+ | protected ResponseEntity<Object> handleConflict( | ||
+ | RuntimeException ex, WebRequest request) { | ||
+ | String bodyOfResponse = "This should be application specific"; | ||
+ | return handleExceptionInternal(ex, bodyOfResponse, | ||
+ | new HttpHeaders(), HttpStatus.CONFLICT, request); | ||
+ | } | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | The new annotation allows the multiple scattered @ExceptionHandler from before to be consolidated into a single, global error handling component. | ||
+ | |||
+ | The actual mechanism is extremely simple but also very flexible: | ||
+ | *'''it allows full control over the body of the response as well as the status code''' | ||
+ | *'''it allows mapping of several exceptions to the same method, to be handled together''' | ||
+ | *'''it makes good use of the newer RESTful ResposeEntity response''' | ||
+ | |||
+ | One thing to keep in mind here is to match the exceptions declared with @ExceptionHandler with the exception used as the argument of the method. If these don’t match, the compiler will not complain – no reason it should, and Spring will not complain either. | ||
+ | |||
+ | However, when the exception is actually thrown at runtime, the exception resolving mechanism will fail with: | ||
+ | |||
+ | <syntaxhighlight lang="java"> | ||
+ | java.lang.IllegalStateException: No suitable resolver for argument [0] [type=...] | ||
+ | HandlerMethod details: ... | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | |||
+ | |||
Revision as of 05:57, 29 June 2018
Contents
SpringBoot - Maven - Pom.xml
<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>com.oasissoftech.springboot</groupId>
<artifactId>SpringBoot-Tutorial-1</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>SpringBoot-Tutorial-1</name>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.2.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<properties>
<java.version>1.8</java.version>
</properties>
</project>
to get latest Spring Boot Starter Parent pom.xml configurations visit : https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-parent
to get latest Spring Boot Web Starter pom.xml configurations visit : https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web
SpringBoot - Application Boot
Spring Boot application startup class :
package com.oasissofttech.springboot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringBootTutorial1App {
public static void main(String[] args) {
SpringApplication.run(SpringBootTutorial1App.class,args);
}
}
SpringBoot - Controller Class - GET, POST, PUT, DELETE
package com.oasissofttech.springboot.brand;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class BrandController {
@Autowired
private BrandService brandService;
@RequestMapping("/brands")
public List<Brand> getAllBrands() {
return brandService.getAllBrands();
}
@RequestMapping("/brand/{brandId}")
public Brand getBrand(@PathVariable("brandId") int id) {
return brandService.getBrand(id);
}
@RequestMapping(method=RequestMethod.POST,value="/brands")
public void addBrand(@RequestBody Brand brand) {
brandService.addBrand(brand);
}
@RequestMapping(method=RequestMethod.PUT,value="/brands/{id}")
public void addBrand(@RequestBody Brand brand,@PathVariable int id) {
brandService.updateBrand(brand,id);
}
@RequestMapping(method=RequestMethod.DELETE,value="/brands/{id}")
public void deleteBrand(@PathVariable int id) {
brandService.deleteBrand(id);
}
}
SpringBoot - Service Class
package com.oasissofttech.springboot.brand;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.stereotype.Service;
@Service
public class BrandService {
private List<Brand> brands = new ArrayList<>(Arrays.asList(
new Brand(1,"Aston Martin","(1913–present)"),
new Brand(2,"Bentley","(1919–present)"),
new Brand(3,"Jaguar Land Rover","(2013-present)"),
new Brand(4,"Lotus","(1952–present)"),
new Brand(5,"Rolls Royce","(1904–present)")
));
public List<Brand> getAllBrands(){
return brands;
}
public Brand getBrand(int id) {
return brands.stream().filter(b->b.getId()==id).findFirst().get();
}
public void addBrand(Brand brand) {
brands.add(brand);
}
public void updateBrand(Brand brand, int id) {
int c = brands.size();
for(int i=0;i<c;i++) {
if(brands.get(i).getId()==id) {
brands.set(i, brand);
return;
}
}
}
public void deleteBrand(int id) {
brands.removeIf(t->t.getId()==id);
}
}
Others
package com.oasissofttech.springboot.brand;
public class Brand {
private int id;
private String brandName;
private String description;
public Brand() {
}
public Brand(int id, String brandName, String description) {
super();
this.id = id;
this.brandName = brandName;
this.description = description;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getBrandName() {
return brandName;
}
public void setBrandName(String brandName) {
this.brandName = brandName;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
Common application properties
application.properties file
#===========================================
#=== application.properties file content ===
server.port=3000 #server HTTP port.
#===================end=====================
springboot properties file manual : https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
JPA - Java Persistance API
ORM - Object Relational Mapping
Spring Data JPA
Making CRUD Operation with Repository
BrandRepository interface
package com.oasissofttech.springboot.brand;
import org.springframework.data.repository.CrudRepository;
public interface BrandRepository extends CrudRepository<Brand, Integer> {
}
BrandService Class
package com.oasissofttech.springboot.brand;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class BrandService {
@Autowired
private BrandRepository brandRepository;
public List<Brand> getAllBrands(){
List<Brand> brands = new ArrayList<>();
brandRepository.findAll()
.forEach(brands::add);
return brands;
}
public Optional<Brand> getBrand(int id) {
return brandRepository.findById(id);
}
public void addBrand(Brand brand) {
brandRepository.save(brand);
}
public void updateBrand(Brand brand, int id) {
brandRepository.save(brand);
}
public void deleteBrand(int id) {
brandRepository.deleteById(id);
}
}
Error Handling for REST with Spring & SpringBoot
Overview
Before Spring 3.2, the two main approaches to handling exceptions in a Spring MVC application were: HandlerExceptionResolver or the @ExceptionHandler annotation. Both of these have some clear downsides.
After 3.2 we now have the new @ControllerAdvice annotation to address the limitations of the previous two solutions.
All of these do have one thing in common – they deal with the separation of concerns very well. The app can throw exception normally to indicate a failure of some kind – exceptions which will then be handled separately.
Solution 1 – The Controller level @ExceptionHandler
The New @ControllerAdvice (Spring 3.2 And Above)
Spring 3.2 brings support for a global @ExceptionHandler with the new @ControllerAdvice annotation. This enables a mechanism that breaks away from the older MVC model and makes use of ResponseEntity along with the type safety and flexibility of @ExceptionHandler:
@ControllerAdvice
public class RestResponseEntityExceptionHandler
extends ResponseEntityExceptionHandler {
@ExceptionHandler(value
= { IllegalArgumentException.class, IllegalStateException.class })
protected ResponseEntity<Object> handleConflict(
RuntimeException ex, WebRequest request) {
String bodyOfResponse = "This should be application specific";
return handleExceptionInternal(ex, bodyOfResponse,
new HttpHeaders(), HttpStatus.CONFLICT, request);
}
}
The new annotation allows the multiple scattered @ExceptionHandler from before to be consolidated into a single, global error handling component.
The actual mechanism is extremely simple but also very flexible:
- it allows full control over the body of the response as well as the status code
- it allows mapping of several exceptions to the same method, to be handled together
- it makes good use of the newer RESTful ResposeEntity response
One thing to keep in mind here is to match the exceptions declared with @ExceptionHandler with the exception used as the argument of the method. If these don’t match, the compiler will not complain – no reason it should, and Spring will not complain either.
However, when the exception is actually thrown at runtime, the exception resolving mechanism will fail with:
java.lang.IllegalStateException: No suitable resolver for argument [0] [type=...]
HandlerMethod details: ...