I have this spring service:
#Service
#Transactional
public class ConsorcioServiceImpl implements ConsorcioService {
...
#Autowired
private ConsorcioRepository consorcioRepository;
#Override
public void saveBank(Consorcio consorcio) throws BusinessException {
try {
consorcioRepository.save(consorcio);
}
catch(DataIntegrityViolationException divex) {
if(divex.getMessage().contains("uq_codigo")) {
throw new DuplicatedCodeException(divex);
}
else {
throw new BusinessException(dives);
}
}
catch (Exception e) {
throw new BusinessException(e);
}
}
}
That service uses this Spring Data repository:
#Repository
public interface ConsorcioRepository extendsCrudRepository<Consorcio, Integer> {
}
I'm calling the service from a spring controller:
#Controller
#RequestMapping(value = "/bank")
public class BancaController {
#Autowired
private ConsorcioService consorcioService;
#RequestMapping(value="create", method=RequestMethod.POST)
public ModelAndView crearBanca(#Valid BancaViewModel bancaViewModel, BindingResult bindingResult,
RedirectAttributes redirectAttributes) {
ModelAndView modelAndView;
MessageViewModel result;
try {
consorcioService.saveBank(bancaViewModel.buildBanca());
result = new MessageViewModel(MessageType.SUCESS);
redirectAttributes.addFlashAttribute("messageViewModel", result);
modelAndView = new ModelAndView("redirect:/banca/crear");
return modelAndView;
} catch (Exception e) {
result = new MessageViewModel(MessageType.ERROR);
modelAndView = new ModelAndView("crear-bancas");
modelAndView.addObject("messageViewModel", result);
return modelAndView;
}
}
But the exception I get in the controller is: org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly
instead of the DuplicatedCodeException I throw in the service. I need to identify the type of exception so I can give a custom friendly user message.
also your DuplicatedCodeException , BusinessException should be runtime exception , or add for method saveBank :
#Transactinal(rolbackFor={BusinessException.class,DuplicatedCodeException.,class })
in other case spring will not rollback transaction.
from Spring documentation:
While the EJB default behavior is for the EJB container to
automatically roll back the transaction on a system exception (usually
a runtime exception), EJB CMT does not roll back the transaction
automatically on an application exception (that is, a checked
exception other than java.rmi.RemoteException). While the Spring
default behavior for declarative transaction management follows EJB
convention (roll back is automatic only on unchecked exceptions), it
is often useful to customize this.
Just add catch (TransactionSystemException tse) before catch (Exception e) branch and then extract your exception with getOriginalException().
try {
consorcioService.saveBank(bancaViewModel.buildBanca());
result = new MessageViewModel(MessageType.SUCESS);
redirectAttributes.addFlashAttribute("messageViewModel", result);
modelAndView = new ModelAndView("redirect:/banca/crear");
return modelAndView;
} catch (TransactionSystemException tse) {
final Throwable ex = tse.getOriginalException();
if (ex instanceof DuplicatedCodeException) {
// DuplicatedCodeException
}
} catch (Exception e) {
result = new MessageViewModel(MessageType.ERROR);
modelAndView = new ModelAndView("crear-bancas");
modelAndView.addObject("messageViewModel", result);
return modelAndView;
}
That happens because your exception is wrapped in RollbackException as Throwable cause. In it's turn RollbackException is also a cause of TransactionSystemException.
You can build global exception handler to catch and customize all exceptions as you wish:
#ControllerAdvice
class GlobalControllerExceptionHandler {
#ResponseStatus(HttpStatus.CONFLICT)
#ExceptionHandler(TransactionSystemException.class)
public ModelAndView handleDupilatedCode(HttpServletRequest req, TransactionSystemException ex) {
// Build you exception body here
Throwable e = ex.getOriginalException();
if(e instanceof DuplicatedCodeException)
// throw
// Or build custom exception as you want
ModelAndView mav = new ModelAndView();
mav.addObject("exception", e);
mav.addObject("url", req.getRequestURL());
mav.setViewName("error");
return mav;
}
}
#ControllerAdvice is available since Spring 3.2
Also you can use #ExceptionHandler at the #Controller level, or create this handling in abstract-controller if you have one as a super class of all your controllers.
public class FooController {
//...
#ExceptionHandler({ CustomException1.class, CustomException2.class })
public void handleException() {
//
}
}
There are some other approaches, for full reference please follow the:
Spring IO: Exception handling in Spring MVC
Baeldung: Exception handling with Spring
Related
I have a ControllerExceptionHandler with ControllerAdvise and when in application is thrown generalException or customException then i except to catch it in this ControllerExceptionHandler . But it doesn't happen. It looks very simple, read many sites , but it's not triggering.
Don't know where is the problem.
It's ControllerExceptionHandler.class
#ControllerAdvice
public class ControllerExceptionHandler extends ResponseEntityExceptionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(ControllerExceptionHandler.class);
#ExceptionHandler(Exception.class)
public final ResponseEntity<?> handleGeneralErrors(Exception e, UserPrincipal userPrincipal) {
if (e instanceof ClientAbortException) {
LOGGER.error("REST client abort: {}", e.getMessage());
} else {
LOGGER.error("REST controller error.", e);
}
//somelogic
return new ResponseEntity<>(responseObject, HttpStatus.INTERNAL_SERVER_ERROR);
}
#ExceptionHandler(MyCustomException.class)
public final ResponseEntity<?> handleCustomErrors(MyCustomException e, UserPrincipal userPrincipal) {
LOGGER.error("MyCustomException error.", e);
return new ResponseEntity<>(
responseObject,
HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Controller.class
#RestController
#RequestMapping(path = "/rest/test", produces = MediaType.APPLICATION_JSON_VALUE)
public class Controller {
private static final Logger LOGGER = LoggerFactory.getLogger(Controller .class);
#Autowired
private SomeLogicClass someLogicClass;
#GetMapping("/check")
#ResponseBody
public ResponseEntity<List<City>> list(UserPrincipal userPrincipal) throws Exception {
//Some logic
return ResponseEntity.ok(someLogicClass.handleRequest());
}
SomeLogicClass.class
#Service
public class SomeLogicClass{
public void handleRequest() throws Exception {
//some logic
} catch (Exception ex) {
throw new MyCustomException();
}
}
}
So, when the request is reaching SomeLogicClass, then some Exception(for example NPE) is thrown and then i want to throw in catch myCustomException. I expect that it will go to ControllerAdvise, but nothing and i see error 500.
Something is missing here or what? Controller and ControllerAdvise are located in some package.
I also tried to add the package
#ControllerAdvice("my.package.controller")
Found what caused that it skipped ControllerAdvise. The problem was UserPrinciple object, that is actually my custom class. Any custom class that i put additionally in input argument was a problem. I don't know the reason for this , but implemented without UserPrinciple.class.
I'm working on a web app using Spring MVC and AngularJS, I'm creating a Rest API that returns ResponseEntities that contains JSON strings.
I want to be able when an Exception happens to return a string that contains the error cause to my view and then show this error with a modal in AngularJS, I created a Class with the #ControllerAdvice annotation and in this class I defined a method with my custom exception like this
#ControllerAdvice
public class GlobalExceptionHandlerController {
#ExceptionHandler(PersonalException.class)
public String handleCustomExceptionRazon(PersonalException ex) {
String errorMessage = "custom error";
return errorMessage;
}
}
I have the following interface
public interface ClientDAO {
public void insertCLiente(Client client) throws PersonalException
}
And in my implementation
#Override
public void insertCLiente(Client client) throws PersonalException{
//method implementation
if (searchCLiente(client.name())) {
throw new PersonalException("client aleady exists");
} else {
//method implementation
}
}
My searchClient Method
public boolean searchClient(String name) {
try {
//method implementation
} catch (DataAccessException dataAccessException) {
System.out.println("");
dataAccessException.printStackTrace();
} catch (Exception e) {
System.out.println("");
e.printStackTrace();
}
//method implementation
}
My Client Controller
#Autowired
ClientDAO clientDAO;
#RequestMapping(value = "/client/", method = RequestMethod.POST)
public ResponseEntity<Void> createClient(#RequestBody final String DTOClientData, UriComponentsBuilder ucBuilder) {
//here I parse the JSON data and create my Client object
//here I dont know how can I return the error message
clientDAO.insertClient(client);
}
My custom Exception
public class PersonalException extends Exception {
public PersonalException (String msg) {
super(msg);
}
}
I don't know un my clientController method createClient how can I return an execption of the type PersonalException that I created
//here I dont know how can I return the error message
Just throw the exception from Controller.
#RequestMapping(value = "/client/", method = RequestMethod.POST)
public ResponseEntity<Void> createClient(#RequestBody final String DTOClientData, UriComponentsBuilder ucBuilder) throws PersonalException {
You can return error message in GlobalExceptionHandlerController like this...
/**
* REST exception handlers defined at a global level for the application
**/
#ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = { PersonalException.class })
protected ResponseEntity<RestResponse> handleUnknownException(PersonalException ex, WebRequest request) {
LOGGER.error(ex.getMessage(), ex);
return new ResponseEntity<RestResponse>(new RestResponse(Boolean.FALSE, ImmutableList.of("Exception message"), null), HttpStatus.INTERNAL_SERVER_ERROR);
}
Now, you might have noticed that we are not handling the Exception even in the Controller. Instead, we are Throwing it in the declaration hoping that somewhere we have handled this exceptional case gracefully showing the User a nice looking Toaster message.
The question may remains – Where the hell i am handling the Exception? It is handling by the #ExceptionHandler in GlobalExceptionHandlerController .
I am working in a spring mvc based project and have developed a ExceptionResolver by extending DefaultHandlerExceptionResolver to redirect to error page depending on the exception type. It is working for exception raised at facade, service, DAO layer.
But it doesn't work for any exceptions raised in Servlet filter. What changes should be made for that?
Below is my handlerExceptionResolver
public ModelAndView doResolveException(final HttpServletRequest request, final HttpServletResponse response, final Object obj,
final Exception exception){
ModelAndView modelAndView = super.doResolveException(request, response, obj, exception);
modelAndView = Objects.nonNull(modelAndView) ? modelAndView : new ModelAndView();
final String url = Config.getParameter(EXCEPTION_HANDLER_URL);
modelAndView.setViewName(url);
final FlashMap outputFlashMap = RequestContextUtils.getOutputFlashMap(request);
outputFlashMap.put(ERROR_DETAILS, exception);
if (exception instanceof BusinessExecutionException)
{
return handleBusinessExecutionExceptionMethod((BusinessExecutionException) exception, outputFlashMap, modelAndView);
}
else if (exception instanceof IntegrationExecutionException)
{
return handleIntegrationExecutionExceptionMethod((IntegrationExecutionException) exception, outputFlashMap,
modelAndView);
}
else if (exception instanceof DataAccessObjectExecutionException)
{
return handleDAOExecutionExceptionMethod((DataAccessObjectExecutionException) exception, outputFlashMap, modelAndView);
}
return handleMiscException(exception, outputFlashMap, modelAndView);
}
Use Spring exception handler:
https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
It is convenient - you can "catch" exception regarding type and HTTP status.
I have a question about Spring Security.
My idea is that in the case when the SM_USER header is wrong I don't want to send an uncatched exception (as the method loadUserByUsername of my class CustomUserDetailsService does).
public class CustomUserDetailsService implements UserDetailsService {...}
I want to catch it and thus redirect to the default page (with mapping my/default/page) and write a message text there like: try again please.
I already have an ExceptionResolver but it works only on controller level and not earlier.
#ControllerAdvice
public class ExceptionResolver {
#ExceptionHandler(PreAuthenticatedCredentialsNotFoundException.class)
public ResponseEntity<?> handleBindException(PreAuthenticatedCredentialsNotFoundException e) {
log.error(e.getMessage(), e);
...
return response;
}
#ExceptionHandler(UsernameNotFoundException.class)
public ResponseEntity<?> handleBindException(UsernameNotFoundException e) {
log.error(e.getMessage(), e);
...
return response;
}
}
As far as I understand I need to implement a new exception resolver for such cases, but when I try to built it in the application context, my whole programm crashes down.
#RequestMapping("/resource")
public class GlobalExceptionResolver extends AbstractHandlerExceptionResolver{
#Override
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse responce, Object handler, Exception exception) {
try {
responce.sendRedirect("/my/defualt/page");
} catch (IOException e) {
log.error(e);
}
ModelAndView mav = new ModelAndView();
if(exception instanceof PreAuthenticatedCredentialsNotFoundException){
mav.addObject("errorMessage","This user does not exist");
}
else if(exception instanceof UsernameNotFoundException){
mav.addObject("errorMessage","This user is too old");
}
return mav;
}
}
So, please, could you explain me how can I realize my plan in this case if spring security allows this in general?
Thank you in advance.
If you are using Spring xml you can call one bean with #PostConstruct when failed like this
<sec:form-login authentication-failure-handler-ref="afterLoginFail"
Example AfterLoginFail
public class AfterLoginFail extends SimpleUrlAuthenticationFailureHandler {
#PostConstruct
public void init() {
setDefaultFailureUrl("/login?status=failure");
}
}
Or if you use javaconfig use formLogin().failureUrl(authenticationFailureUrl) or .failureHandler()
I have a Spring MVC controller throw two kind of exception:
#RequestMapping(value = "forwardRefundApply",method = RequestMethod.GET)
public ModelAndView forwardRefundApply1(String ticketNbr)throws Exception {
if(true)
throw new Exception();
else
throw new ApplicationException("test");
}
then I write a AOP class to hanlder the Exception then return Model like this:
#Pointcut("execution(public * ..*(..))")
public void getRefundPointCut() {
}
#AfterThrowing(pointcut="getRefundPointCut()", throwing="e")
public ModelAndView throwException(Exception e){
ModelAndView mav = null;
if(e instanceof ApplicationException)
{
e.printStackTrace();
mav = new ModelAndView(CommonConstants.ERRORPAGE);
mav.addObject("errorMsg", "application error");
return mav;
}
else{
e.printStackTrace();
mav = new ModelAndView(CommonConstants.ERRORPAGE);
mav.addObject("errorMsg", "system error");
return mav;
}
}
the aop is work . but the the result is error. system error:
org.springframework.web.util.NestedServletException: Handler processing failed; nested exception is java.lang.NoSuchMethodError
is Aspect class cannot return ModelAndView to Controller?
Why use AOP in this case at all? Spring comes with everything you need:
HandlerExceptionResolver is a global entry point into exception handling.
#ExceptionHandler lets you define handlers in controllers.
#ControllerAdvice lets you define #ExceptionHandlers on a global level.
The NoSuchMethodError is unrelated to your question and is caused by something you haven't shown us.
As for the question
is Aspect class cannot return ModelAndView to Controller?
I couldn't find any reference to it anywhere in Spring's AOP documentation, but you can see it in the implementation.
When you declare a #AfterThrowing advice, Spring uses a AspectJAfterThrowingAdvice to handle it. Its invoke(..) method is implemented as
#Override
public Object invoke(MethodInvocation mi) throws Throwable {
try {
return mi.proceed();
}
catch (Throwable t) {
if (shouldInvokeOnThrowing(t)) {
invokeAdviceMethod(getJoinPointMatch(), null, t);
}
throw t;
}
}
where mi.proceed() invokes your advised method and invokeAdviceMethod(..) invokes your #AfterThrowing advice method. Notice that it does nothing with the return value. As such, you can return a ModelAndView object from a #AfterThrowing advice method, but it won't serve any purpose, it'll simply be discarded.
A possible alternative is to declare a #Around advice. Within it, you wrap the proceeding call and catch the possible exceptions, handling them appropriately
#Around(value = "getRefundPointCut()")
public ModelAndView throwException(ProceedingJoinPoint joinPoint) throws Throwable {
ModelAndView mav = null;
try {
return (ModelAndView) joinPoint.proceed(); // might want to make sure that it is a ModelAndView
} catch(ApplicationException e) {
e.printStackTrace();
mav = new ModelAndView("home");
mav.addObject("errorMsg", "application error");
return mav;
} catch (Exception e) {
e.printStackTrace();
mav = new ModelAndView("home");
mav.addObject("errorMsg", "system error");
return mav;
}
}
Here you return the value of the advised method if it returns correctly. Or your catch any thrown Exception and again handle it appropriately by returning a different ModelAndView.
I haven't tested it myself but Spring AOP works with proxy objects and not the actual objects. So e is actually an instance of proxy and not of ApplicationException. So the following condition never executes to true.
if(e instanceof ApplicationException)
Easiest way of handling this would be to mark your aop setting in Spring configuration file with proxy-target-class="true".
<aop:aspectj-autoproxy proxy-target-class="true"/>
HTH