Can I know which controller was called in #ControllerAdvice? - java

now, I using the #ControllerAdvice in Spring 4.*.
using beforeBodyWrite method.
create custom annotation in controller class.
get the information of controller when #ControllerAdvice processing.
I want to know that request coming from what's controller class.
but, I don't know the solution.
any help.?
thanks

Although your question doesn't state clearly what is the purpose of what you are achieving and why do you need to create a custom annotation I'll post you some guidelines showing how to determine the source of your RuntimeException inside the ControllerAdvice
Given the following Rest controllers:
#RestController
public class CARestController {
#RequestMapping(value = "/test", method = RequestMethod.GET)
public String testException() {
throw new RuntimeException("This is the first exception");
}
}
#RestController
public class CAOtherRestController {
#RequestMapping(value = "/test-other", method = RequestMethod.GET)
public String testOtherException() {
throw new RuntimeException("This is the second exception");
}
}
Which both throw an exception, we can capture this exception by using the following ControllerAdvice and determine the origin of the exception using the stack trace.
#ControllerAdvice
public class CAControllerAdvice {
#ExceptionHandler(value = RuntimeException.class)
protected ResponseEntity<String> handleRestOfExceptions(RuntimeException ex) {
return ResponseEntity.badRequest().body(String.format("%s: %s", ex.getStackTrace()[0].getClassName(), ex.getMessage()));
}
}
This is how the output of the endpoints will look:
My recommendation is that instead of doing this, you declare your own set of exceptions and then capture them in your controller advice, with independence of where they were thrown:
public class MyExceptions extends RuntimeException {
}
public class WrongFieldException extends MyException {
}
public class NotFoundException extends MyException {
}
#ControllerAdvice
public class CAControllerAdvice {
#ExceptionHandler(value = NotFoundException .class)
public ResponseEntity<String> handleNotFound() {
/**...**/
}
#ExceptionHandler(value = WrongFieldException .class)
public ResponseEntity<String> handleWrongField() {
/**...**/
}
}

Related

Is it possible to intercept an exception thrown from a custom annotation validator in Spring Boot?

I'm experimenting with the custom validator annotation with spring boot in my rest application.
here is the sample code from the validator, other requisite classes are present:
public class CustomerValidator implements ConstraintValidator<CustomerValidation, Customer>
{
public boolean isValid(Customer value, ConstraintValidatorContext cxt) {
if(value.getItemList.size() == 0) {
throw new CustomerOrderException();
}
if(value.getBalance() < value.getCost()) {
throw new InsufficientBalanceException();
}
return true;
}
}
I made a class annotated with controller advice and exception handler but it doesn't seem to intercept the exception thrown from the validator.
Ok I got it, I extended my controller advice class with ResponseEntityExceptionHandler and did the usual ExceptionHandler annotation specifying my custom exceptions.
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value={CustomerOrderException.class})
protected ResponseEntity<Object> handleException(CustomerOrderException ex, WebRequest request) {
//return response entity
}
}

Best way to return ResponseEntity with data, empty and error in Spring Boot?

I am trying to implement a generic approach that can be used for each return types from Service to Controller in my Spring Boot app. After searching and reading several threads and articles, I see that I need an approach with #ControllerAdvice and #ExceptionHandler annotations.
For exception, I created the following class:
#ControllerAdvice
public class FileUploadException extends ResponseEntityExceptionHandler {
#SuppressWarnings("rawtypes")
#ExceptionHandler(MaxUploadSizeExceededException.class)
public ResponseEntity handleMaxSizeException(MaxUploadSizeExceededException e) {
return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED)
.body(new ResponseMessage("Too large!"));
}
}
However, I am not sure if it is ok and how I should treat Optional.empty() results in my Service and Controller. So, could you please suggest me a proper approach or give an example that is used for exception, result with data and result with Optional.empty() ?
Please note that I have the following articles, but they are too old and I am not sure if these approach are still good or there is a better approach with the new versions of Spring Boot, etc?
Guide to Spring Boot REST API Error Handling
Exception Handling in Spring MVC
Specific ExceptionHandler:
#RestController
#RequestMapping("/products")
public class ProductController {
#Autowierd private ProductService productService;
#GetMapping("/{code}")
public ResponseEntity<Product> findByCode(#PathVariable String code) {
return productService.findByCode(code).map(ResponseEntity::ok).orElseThrow(() -> NoContentException::new);
}
#ExceptionHandler(NoContentException.class)
public ResponseEntity handleNoContent(NoContentException e) {
return ResponseEntity.status(HttpStatus.NO_CONTENT).body(new ResponseMessage("No data"));
}
}
Or Common ExceptionHandler :
#RestController
#RequestMapping("/products")
public class ProductController {
#Autowierd private ProductService productService;
#GetMapping("/{code}")
public ResponseEntity<Product> findByCode(#PathVariable String code) {
return productService.findByCode(code).map(ResponseEntity::ok).orElseThrow(() -> NoContentException::new);
}
}
Plus :
#ControllerAdvice
public class CommonExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(MaxUploadSizeExceededException.class)
public ResponseEntity handleMaxSizeException(MaxUploadSizeExceededException e) {
return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).body(new ResponseMessage("Too large!"));
}
#ExceptionHandler(NoContentException.class)
public ResponseEntity handleNoContent(NoContentException e) {
return ResponseEntity.status(HttpStatus.NO_CONTENT).body(new ResponseMessage("No data"));
}
}

How to replace ErrorController deprecated function on Spring Boot?

Have a custom error controller on Spring boot:
package com.example.controllers;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.boot.web.servlet.error.ErrorController;
import javax.servlet.http.HttpServletRequest;
#Controller
public class CustomErrorController implements ErrorController
{
#RequestMapping("/error")
public String handleError(HttpServletRequest request)
{
...
}
#Override
public String getErrorPath()
{
return "/error";
}
}
But, when compile says: getErrorPath() in ErrorController has been deprecated. Ok, i found information: use server.error.path property. Ok, add this in application.properties and delete the function, but now says: CustomErrorController is not abstract and does not override abstract method getErrorPath() in ErrorController, ¿need a deprecated function?.
How to made the custom error controller?, the ErrorController requires getErrorPath but it is deprecated, what is the correct alternative?.
Starting version 2.3.x, Spring boot has deprecated this method. Just return null as it is anyway going to be ignored. Do not use #Override annotation if you want to prevent future compilation error when the method is totally removed. You can also suppress the deprecation warning if you want, however, the warning (also the #Override annotation) is helpful to remind you to cleanup/fix your code when the method is removed.
#Controller
#RequestMapping("/error")
#SuppressWarnings("deprecation")
public class CustomErrorController implements ErrorController {
public String error() {
// handle error
// ..
}
public String getErrorPath() {
return null;
}
}
#Controller
public class CustomErrorController implements ErrorController {
#RequestMapping("/error")
public ModelAndView handleError(HttpServletResponse response) {
int status = response.getStatus();
if ( status == HttpStatus.NOT_FOUND.value()) {
System.out.println("Error with code " + status + " Happened!");
return new ModelAndView("error-404");
} else if (status == HttpStatus.INTERNAL_SERVER_ERROR.value()) {
System.out.println("Error with code " + status + " Happened!");
return new ModelAndView("error-500");
}
System.out.println(status);
return new ModelAndView("error");
}
}
there is an #ControllerAdvice annotation
#ControllerAdvice
public class MyErrorController {
#ExceptionHandler(RuntimeException.class)
public String|ResponseEntity|AnyOtherType handler(final RuntimeException e) {
.. do handle ..
}
#ExceptionHandler({ Exception1.class, Exception2.class })
public String multipleHandler(final Exception e) {
}
}
To handle errors, There is no need to define a controller class
implementing an error controller.
To handle errors in your entire application instead of writing
#Controller
public class CustomErrorController implements ErrorController{
#RequestMapping("/error")
public String handleError(HttpServletRequest request)
{
...
}
}
use the below class
#ControllerAdvice
public class myExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(Exception.class)
public final ResponseEntity<YourResponseClass> handleAllExceptions(Exception ex, WebRequest request) {
YourResponseClassexceptionResponse = new YourResponseClass(new Date(), ex.getMessage());// Its an example you can define a class with your own structure
return new ResponseEntity<>(exceptionResponse, HttpStatus.INTERNAL_SERVER_ERROR);
}
#ExceptionHandler(CustomException.class)
public final ResponseEntity<YourResponseClass> handleAllExceptions(Exception ex, WebRequest request) {
YourResponseClass exceptionResponse = new YourResponseClass(new Date(), ex.getMessage()); // For reference
return new ResponseEntity<YourResponseClass>(exceptionResponse, HttpStatus.INTERNAL_SERVER_ERROR);
}
#ExceptionHandler(BadCredentialsException.class)
public final ResponseEntity<YourResponseClass> handleBadCredentialsException(BadCredentialsException ex, WebRequest request){
YourResponseClass exceptionResponse = new YourResponseClass(new Date(), ex.getMessage());// For refernece
return new ResponseEntity<>(exceptionResponse, HttpStatus.UNAUTHORIZED);
}
}
The class above annoted with #ControllerAdvice acts as custom exception handler and it handles all the expecptions thrown by ur application. In above code sample only three exceptions are showed for understanding. It can handle many exceptions
In your application if there's any exception thrown it will come to this class and send the response back. You can have a customized message and structure as per ur needs.
#Controller
public class AppErrorController implements ErrorController {
#RequestMapping("/error")
public String handleError(HttpServletRequest request) {
Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
if(status != null) {
int statusCode = Integer.valueOf(status.toString());
if (statusCode == HttpStatus.FORBIDDEN.value()) {
return "errorpages/error-403";
} else if (statusCode == HttpStatus.NOT_FOUND.value()) {
return "errorpages/error-404";
} else if (statusCode == HttpStatus.INTERNAL_SERVER_ERROR.value()) {
return "errorpages/error-500";
}
}
return "errorpages/error";
}
}

How cover a ControllerAdvice dedicated to handle exception

I am having problems testing my HandleException, even though I have searched I only find solutions for a standard controller that integrates error management but I have it separately.
The problem is that when it comes to doing the unit tests and its coverage, I don't know how to cover it properly.
#Order(Ordered.HIGHEST_PRECEDENCE)
#ControllerAdvice
public class RestExceptionHandler {
#ExceptionHandler(AmazonClientException.class)
protected ResponseEntity<Object> handleAmazonClientException(AmazonClientException ex) {
return buildResponseEntity(new ApiError(HttpStatus.INTERNAL_SERVER_ERROR, ex));
}
#ExceptionHandler(AmazonS3Exception.class)
protected ResponseEntity<Object> handleAmazonS3Exception(AmazonS3Exception ex) {
return buildResponseEntity(new ApiError(HttpStatus.NOT_FOUND, ex));
}
private ResponseEntity<Object> buildResponseEntity(ApiError apiError) {
return new ResponseEntity<>(apiError, apiError.getStatus());
}
}
RestExceptionHandler.class
ApiError.class
#Data
public class ApiError {
private HttpStatus status;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss")
private LocalDateTime timestamp;
private String message;
private String debugMessage;
private ApiError() {
setTimestamp(LocalDateTime.now());
}
ApiError(HttpStatus status) {
this();
setStatus(status);
}
ApiError(HttpStatus status, Throwable ex) {
this();
setStatus(status);
setMessage("Unexpected error");
setDebugMessage(ex.getLocalizedMessage());
}
ApiError(HttpStatus status, String message, Throwable ex) {
this();
setStatus(status);
setMessage(message);
setDebugMessage(ex.getLocalizedMessage());
}
}
How can see, i only use my RestExceptionHandler for that, not call another Business class.
Any advice appreciated.
To unit test #ControllerAdvice annotated classes, you can use something called MockMvc which Spring provides.
It is a very elegant way of unit testing your controllers and subsequent controller advices.
So, let's say this is my controller
#RestController
#RequestMapping("api")
public class RobotController {
private RobotService robotService;
#GetMapping
public Collection<Integer> get() {
throw new DemoException();
}
}
And here's my controller advice; DemoException is just an empty class that extends RuntimeException.
#RestControllerAdvice
public class RestExceptionHandler {
#ExceptionHandler(DemoException.class)
public ResponseEntity<Object> handleException(DemoException dex) {
return ResponseEntity
.status(400)
.body("Bad request");
}
}
So, this means, sending a GET request to BASEURL/api will give me Bad request text response with 400 status code.
Okay, since we are done with the setup, let's move to the actual unit test. This is a very simple unit test, but it does what you need.
public class AdviceTest {
private final MockMvc mockMvc = MockMvcBuilders
.standaloneSetup(new RobotController())
.setControllerAdvice(new RestExceptionHandler())
.build();
#Test
void testGetFails() {
mockMvc.perform(
MockMvcRequestBuilders.get("/api")
).andExpect(
status().isBadRequest()
);
}
}
So, let me explain the initialization of the MockMvc object. There are two versions.
Standalone setup in which you just provide manually created controller objects.
Web application context in which you just provide an autowired application context.
You can also autowire MockMvc object.
Out of those 2, I really like the former, because it does not include the whole Spring context, hence it is faster.
There are a lot of methods available from checking the json content to paths. You can read more here:
Spring Guide
Baeldung

How to handle exceptions using Spring Webclient's synchronous call?

I've created a service class to make an API call:
ServiceA {
public B sendReceive(Request req) {
return Optional.of(req).map(this::transform).map(apiService::call);
}
private ApiRequest transform(Request req) {
...
}
}
ApiService {
public B call(ApiRequest req) {
return webClient.post().bodyValue(req).bodyToMono().block();
}
#ControllerAdvice
ExceptionManager {
#ExceptionHandler(Exception.class)
public ResponseEntity handle(Exception ex) {
...
}
}
I tried to produce an exception in the transform method to test the ControllerAdvice class, but it does not work. The exception goes directly into InvocableHandlerMethod class instead of ExceptionManager class, which is inside the reactive package.
Since I'm not using any Mono/Flux, the typical .onError will not work for my case.
How do I make the ControllerAdvice working? Or how should I handle exception in the blocking call?

Categories

Resources