├── .gitignore
├── .springBeans
├── README.md
├── blog.md
├── build.gradle
├── manifest.yml
├── pom-war.xml
├── pom.xml
├── settings.gradle
├── src
└── main
│ ├── java
│ ├── demo
│ │ ├── config
│ │ │ ├── ExceptionConfiguration.java
│ │ │ ├── ResponseDataControllerAdvice.java
│ │ │ └── package-info.java
│ │ ├── example
│ │ │ ├── ExampleExceptionHandlerExceptionResolver.java
│ │ │ ├── ExampleSimpleMappingExceptionResolver.java
│ │ │ └── package-info.java
│ │ ├── exceptions
│ │ │ ├── CustomException.java
│ │ │ ├── DatabaseException.java
│ │ │ ├── InvalidCreditCardException.java
│ │ │ ├── OrderNotFoundException.java
│ │ │ ├── SupportInfoException.java
│ │ │ ├── UnhandledException.java
│ │ │ └── package-info.java
│ │ ├── filter
│ │ │ └── BrokenFilter.java
│ │ ├── main
│ │ │ ├── Main.java
│ │ │ ├── Profiles.java
│ │ │ └── package-info.java
│ │ ├── utils
│ │ │ └── BeanLogger.java
│ │ └── web
│ │ │ ├── ViewMappingController.java
│ │ │ └── package-info.java
│ ├── demo1
│ │ └── web
│ │ │ ├── ExceptionHandlingController.java
│ │ │ └── package-info.java
│ ├── demo2
│ │ └── web
│ │ │ ├── ControllerWithoutExceptionHandlers.java
│ │ │ ├── GlobalExceptionHandlingControllerAdvice.java
│ │ │ └── package-info.java
│ ├── demo3
│ │ ├── config
│ │ │ ├── DemoExceptionConfiguration.java
│ │ │ └── package-info.java
│ │ └── web
│ │ │ ├── ExceptionThrowingController.java
│ │ │ ├── SwitchController.java
│ │ │ ├── SwitchableSimpleMappingExceptionResolver.java
│ │ │ └── package-info.java
│ ├── demo5
│ │ └── web
│ │ │ ├── ReturnOrRedirectController.java
│ │ │ └── package-info.java
│ ├── mvc-configuration.xml
│ └── org
│ │ └── springframework
│ │ └── dao
│ │ ├── DataAccessException.java
│ │ └── DataIntegrityViolationException.java
│ └── resources
│ ├── application.properties
│ ├── mvc-configuration.xml
│ ├── public
│ ├── extlink.png
│ ├── logo-vmware-tanzu.png
│ ├── pws-header-logo_new.png
│ ├── spring-trans.png
│ └── styles.css
│ └── templates
│ ├── creditCardError.html
│ ├── databaseError.html
│ ├── databaseException.html
│ ├── defaultErrorPage.html
│ ├── demo5.html
│ ├── error.html
│ ├── footer.html
│ ├── global.html
│ ├── header.html
│ ├── index.html
│ ├── link.html
│ ├── local.html
│ ├── no-handler.html
│ ├── support.html
│ └── unannotated.html
└── support.png
/.gitignore:
--------------------------------------------------------------------------------
1 | *.class
2 | target/*
3 | build/
4 | .gradle/
5 | .project
6 | .classpath
7 | .settings/
8 | bin/
9 | Thumbs.db
10 | .DS_Store
11 |
12 | # Package Files #
13 | *.jar
14 | *.war
15 | *.ear
16 | /target
17 |
--------------------------------------------------------------------------------
/.springBeans:
--------------------------------------------------------------------------------
1 |
2 |
Application has encountered an error. Please contact support on ...
123 | 124 | 130 | ``` 131 | 132 | For the Thymeleaf equivalent see 133 | support.html 134 | in the demo application. The result looks like this. 135 | 136 |  137 | 138 | ## Global Exception Handling 139 | ### Using @ControllerAdvice Classes 140 | 141 | A controller advice allows you to use exactly the same exception handling techniques but apply them 142 | across the whole application, not just to an individual controller. You can think of them as an annotation 143 | driven interceptor. 144 | 145 | Any class annotated with `@ControllerAdvice` becomes a controller-advice and three types of method 146 | are supported: 147 | 148 | * Exception handling methods annotated with `@ExceptionHandler`. 149 | * Model enhancement methods (for adding additional data to the model) annotated with 150 | `@ModelAttribute`. Note that these attributes are _not_ available to the exception handling views. 151 | * Binder initialization methods (used for configuring form-handling) annotated with 152 | `@InitBinder`. 153 | 154 | We are only going to look at exception handling - see the online manual for more on 155 | `@ControllerAdvice` methods. 156 | 157 | Any of the exception handlers you saw above can be defined on a controller-advice class - but now they 158 | apply to exceptions thrown from any controller. Here is a simple example: 159 | 160 | ```java 161 | @ControllerAdvice 162 | class GlobalControllerExceptionHandler { 163 | @ResponseStatus(HttpStatus.CONFLICT) // 409 164 | @ExceptionHandler(DataIntegrityViolationException.class) 165 | public void handleConflict() { 166 | // Nothing to do 167 | } 168 | } 169 | ``` 170 | 171 | If you want to have a default handler for any exception, there is a slight wrinkle. You need to ensure 172 | annotated exceptions are handled by the framework. The code looks like this: 173 | 174 | ```java 175 | @ControllerAdvice 176 | class GlobalDefaultExceptionHandler { 177 | public static final String DEFAULT_ERROR_VIEW = "error"; 178 | 179 | @ExceptionHandler(value = Exception.class) 180 | public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception { 181 | // If the exception is annotated with @ResponseStatus rethrow it and let 182 | // the framework handle it - like the OrderNotFoundException example 183 | // at the start of this post. 184 | // AnnotationUtils is a Spring Framework utility class. 185 | if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) 186 | throw e; 187 | 188 | // Otherwise setup and send the user to a default error-view. 189 | ModelAndView mav = new ModelAndView(); 190 | mav.addObject("exception", e); 191 | mav.addObject("url", req.getRequestURL()); 192 | mav.setViewName(DEFAULT_ERROR_VIEW); 193 | return mav; 194 | } 195 | } 196 | ``` 197 | 198 | ## Going Deeper 199 | 200 | ### HandlerExceptionResolver 201 | 202 | Any Spring bean declared in the `DispatcherServlet`'s application context that implements 203 | `HandlerExceptionResolver` will be used to intercept and process any exception raised 204 | in the MVC system and not handled by a Controller. The interface looks like this: 205 | 206 | ```java 207 | public interface HandlerExceptionResolver { 208 | ModelAndView resolveException(HttpServletRequest request, 209 | HttpServletResponse response, Object handler, Exception ex); 210 | } 211 | ``` 212 | 213 | The `handler` refers to the controller that generated the exception (remember that 214 | `@Controller` instances are only one type of handler supported by Spring MVC. 215 | For example: `HttpInvokerExporter` and the WebFlow Executor are also types of handler). 216 | 217 | Behind the scenes, MVC creates three such resolvers by default. It is these resolvers that implement the 218 | behaviours discussed above: 219 | 220 | * `ExceptionHandlerExceptionResolver` matches uncaught exceptions against for 221 | suitable `@ExceptionHandler` methods on both the handler (controller) and on any controller-advices. 222 | * `ResponseStatusExceptionResolver` looks for uncaught exceptions 223 | annotated by `@ResponseStatus` (as described in Section 1) 224 | * `DefaultHandlerExceptionResolver` converts standard Spring exceptions and converts them 225 | to HTTP Status Codes (I have not mentioned this above as it is internal to Spring MVC). 226 | 227 | These are chained and processed in the order listed (internally Spring creates a dedicated bean - the 228 | HandlerExceptionResolverComposite to do this). 229 | 230 | Notice that the method signature of ```resolveException``` does not include the ```Model```. This is why 231 | ```@ExceptionHandler``` methods cannot be injected with the model. 232 | 233 | You can, if you wish, implement your own `HandlerExceptionResolver` to setup your own custom 234 | exception handling system. Handlers typically implement Spring's `Ordered` interface so you can define the 235 | order that the handlers run in. 236 | 237 | ### SimpleMappingExceptionResolver 238 | 239 | Spring has long provided a simple but convenient implementation of `HandlerExceptionResolver` 240 | that you may well find being used in your appication already - the `SimpleMappingExceptionResolver`. 241 | It provides options to: 242 | 243 | * Map exception class names to view names - just specify the classname, no package needed. 244 | * Specify a default (fallback) error page for any exception not handled anywhere else 245 | * Log a message (this is not enabled by default). 246 | * Set the name of the `exception` attribute to add to the Model so it can be used inside a View 247 | (such as a JSP). By default this attribute is named ```exception```. Set to ```null``` to disable. Remember 248 | that views returned from `@ExceptionHandler` methods _do not_ have access to the exception but views 249 | defined to `SimpleMappingExceptionResolver` _do_. 250 | 251 | Here is a typical configuration using XML: 252 | 253 | ```xml 254 |@ResponseStatus
to them.
389 | @ExceptionHandler
method on a
390 | @ControllerAdvice
class or use an instance of SimpleMappingExceptionResolver
.
391 | You may well have SimpleMappingExceptionResolver
configured for your application already,
392 | in which case it may be easier to add new exception classes to it than implement a @ControllerAdvice
.
393 | @ExceptionHandler
methods to your controller.
394 | @ExceptionHandler
397 | methods on the Controller
398 | are always selected before those on any @ControllerAdvice
instance. It is undefined
399 | what order controller-advices are processed.
400 |
19 | * If you prefer to do this in XML set the {@link Main#activeProfile} property
20 | * to XML_CONFIG_PROFILE
(see mvc-configuration.xml
).
21 | *
22 | * The use of the JAVA_CONFIG profile here is for demonstration only. A real 23 | * application wouldn't normally need to switch between XML or Java 24 | * Configuration. You would use one, or other, or both, all the time. 25 | * 26 | * @author Paul Chapman 27 | */ 28 | @Configuration 29 | @Profile(Profiles.JAVA_CONFIG_PROFILE) 30 | public class ExceptionConfiguration { 31 | 32 | protected Logger logger; 33 | 34 | public ExceptionConfiguration() { 35 | logger = LoggerFactory.getLogger(getClass()); 36 | logger.info("Creating ExceptionConfiguration"); 37 | } 38 | 39 | /** 40 | * Setup the classic SimpleMappingExceptionResolver. This provides useful 41 | * defaults for logging and handling exceptions. It has been part of Spring 42 | * MVC since Spring V2 and you will probably find most existing Spring MVC 43 | * applications are using it. 44 | *
45 | * Only invoked if the "global" profile is active. 46 | * 47 | * @return The new resolver 48 | */ 49 | @Bean(name = "simpleMappingExceptionResolver") 50 | public SimpleMappingExceptionResolver createSimpleMappingExceptionResolver() { 51 | logger.info("Creating SimpleMappingExceptionResolver"); 52 | SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver(); 53 | 54 | Properties mappings = new Properties(); 55 | mappings.setProperty("DatabaseException", "databaseException"); 56 | mappings.setProperty("InvalidCreditCardException", "creditCardError"); 57 | 58 | r.setExceptionMappings(mappings); // None by default 59 | r.setExceptionAttribute("ex"); // Default is "exception" 60 | r.setWarnLogCategory("demo1.ExceptionLogger"); // No default 61 | 62 | /* 63 | * Normally Spring MVC has no default error view and this class is the 64 | * only way to define one. A nice feature of Spring Boot is the ability 65 | * to provide a very basic default error view (otherwise the application 66 | * server typically returns a Java stack trace which is not acceptable 67 | * in production). See Blog for more details. 68 | * 69 | * To stick with the Spring Boot approach, DO NOT set this property of 70 | * SimpleMappingExceptionResolver. 71 | * 72 | * Here we are choosing to use SimpleMappingExceptionResolver since many 73 | * Spring applications have used the approach since Spring V1. Normally 74 | * we would specify the view as "error" to match Spring Boot, however so 75 | * you can see what is happening, we are using a different page. 76 | */ 77 | r.setDefaultErrorView("defaultErrorPage"); 78 | return r; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/demo/config/ResponseDataControllerAdvice.java: -------------------------------------------------------------------------------- 1 | package demo.config; 2 | 3 | import java.util.Date; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.web.bind.annotation.ControllerAdvice; 7 | import org.springframework.web.bind.annotation.ModelAttribute; 8 | import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver; 9 | 10 | import demo.main.Main; 11 | import demo3.web.SwitchableSimpleMappingExceptionResolver; 12 | 13 | /** 14 | * Adds useful data into to the model for every request. This is another use of 15 | * a Controller Advice (besides exception handling). 16 | * 17 | * @author Paul Chapman 18 | * 19 | */ 20 | @ControllerAdvice 21 | public class ResponseDataControllerAdvice { 22 | 23 | public static final String SOURCE_ON_GITHUB = // 24 | "https://github.com/paulc4/mvc-exceptions/blob/master/src/main"; 25 | 26 | public static final String BLOG_URL = // 27 | "https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc"; 28 | 29 | private SimpleMappingExceptionResolver resolver; 30 | 31 | /** 32 | * Need to see if the {@link SwitchableSimpleMappingExceptionResolver} is 33 | * being used and if so, is it enabled? 34 | * 35 | * @param resolver 36 | */ 37 | @Autowired(required = false) 38 | public void setSimpleMappingExceptionResolver( 39 | SimpleMappingExceptionResolver resolver) { 40 | this.resolver = resolver; 41 | } 42 | 43 | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 44 | /* . . . . . . . . . . . . . . MODEL ATTRIBUTES . . . . . . . . . . . . .. */ 45 | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 46 | 47 | /** 48 | * What profile are we currently using? 49 | *
50 | * Note that error views do not have automatically have access to the model, 51 | * so they do not have access to model-attributes either. 52 | * 53 | * @return Always includes "CONTROLLER". 54 | */ 55 | @ModelAttribute("profiles") 56 | public String getProfiles() { 57 | return Main.getProfiles(); 58 | } 59 | 60 | /** 61 | * Required for compatibility with Spring Boot. 62 | * 63 | * @return Date and time of current request. 64 | */ 65 | @ModelAttribute("timestamp") 66 | public String getTimestamp() { 67 | return new Date().toString(); 68 | } 69 | 70 | /** 71 | * Do we have a {@link SwitchableSimpleMappingExceptionResolver} and if so, 72 | * what state is it in? 73 | * 74 | * @return Date and time of current request. 75 | */ 76 | @ModelAttribute("switchState") 77 | public String getSwitchState() { 78 | // Check if the SwitchableSimpleMappingExceptionResolver is in use and 79 | // enabled, or not. 80 | if (resolver == null) 81 | return "off"; 82 | else if (resolver instanceof SwitchableSimpleMappingExceptionResolver) { 83 | return ((SwitchableSimpleMappingExceptionResolver) resolver) 84 | .isEnabled() ? "on" : "off"; 85 | } else 86 | return "on"; 87 | } 88 | 89 | /** 90 | * URL of this source on github. 91 | * 92 | * @return 93 | */ 94 | @ModelAttribute("gitHubSrc") 95 | public String getGitHubSrcURL() { 96 | return SOURCE_ON_GITHUB; 97 | } 98 | 99 | /** 100 | * Add Blog URL to model for use in any web-page. 101 | * 102 | * @return URL of the Spring IO Blog article this demo relates to. 103 | */ 104 | @ModelAttribute("blogUrl") 105 | public String getBlogUrl() { 106 | return BLOG_URL; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/demo/config/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Spring Configuration files 3 | * 4 | * @author Paul Chapman 5 | */ 6 | package demo.config; -------------------------------------------------------------------------------- /src/main/java/demo/example/ExampleExceptionHandlerExceptionResolver.java: -------------------------------------------------------------------------------- 1 | package demo.example; 2 | 3 | import java.util.Date; 4 | 5 | import javax.servlet.http.HttpServletRequest; 6 | import javax.servlet.http.HttpServletResponse; 7 | 8 | import org.springframework.web.method.HandlerMethod; 9 | import org.springframework.web.servlet.ModelAndView; 10 | import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver; 11 | 12 | import demo.config.ExceptionConfiguration; 13 | 14 | /** 15 | * Not used in this application, but an example of how to extend the 16 | * ExceptionHandlerExceptionResolver to provide extra information in the 17 | * model for the view. To use, add a @Bean method to 18 | * {@link ExceptionConfiguration} to return an instance. 19 | * 20 | * @author Paul Chapman 21 | */ 22 | public class ExampleExceptionHandlerExceptionResolver extends 23 | ExceptionHandlerExceptionResolver { 24 | 25 | /** 26 | * The default ExceptionHandlerExceptionResolver has order MAX_INT 27 | * (lowest priority - see {@link Ordered#LOWEST_PRECEDENCE). The constructor 28 | * gves this slightly higher precedence so it runs first. Also enable 29 | * logging to this classe's logger by default. 30 | */ 31 | public ExampleExceptionHandlerExceptionResolver() { 32 | // Turn logging on by default 33 | setWarnLogCategory(getClass().getName()); 34 | 35 | // Make sure this handler runs before the default 36 | // ExceptionHandlerExceptionResolver 37 | setOrder(LOWEST_PRECEDENCE - 1); 38 | } 39 | 40 | /** 41 | * Override the default to generate a log message with dynamic content. 42 | */ 43 | @Override 44 | public String buildLogMessage(Exception e, HttpServletRequest req) { 45 | return "MVC exception: " + e.getLocalizedMessage(); 46 | } 47 | 48 | /** 49 | * This method uses the newee API and gets passed the handler-method 50 | * (typically the method on the @Controller) that generated the 51 | * exception. 52 | */ 53 | @Override 54 | protected ModelAndView doResolveHandlerMethodException( 55 | HttpServletRequest request, HttpServletResponse response, 56 | HandlerMethod handlerMethod, Exception exception) { 57 | 58 | // Get the ModelAndView to use 59 | ModelAndView mav = super.doResolveHandlerMethodException(request, 60 | response, handlerMethod, exception); 61 | 62 | // Make more information available to the view 63 | mav.addObject("exception", exception); 64 | mav.addObject("url", request.getRequestURL()); 65 | mav.addObject("timestamp", new Date()); 66 | mav.addObject("status", 500); 67 | return mav; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/demo/example/ExampleSimpleMappingExceptionResolver.java: -------------------------------------------------------------------------------- 1 | package demo.example; 2 | 3 | import java.util.Date; 4 | 5 | import javax.servlet.http.HttpServletRequest; 6 | import javax.servlet.http.HttpServletResponse; 7 | 8 | import org.springframework.web.servlet.ModelAndView; 9 | import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver; 10 | 11 | import demo.config.ExceptionConfiguration; 12 | 13 | /** 14 | * Not used in this application, but an example of how to extend the 15 | * SimpleMappingExceptionResolver to provide extra information in the 16 | * log messages it generates and in the model for the view. To use, change 17 | * {@link ExceptionConfiguration#createSimpleMappingExceptionResolver()} to 18 | * return an instance of this class instead. 19 | * 20 | * @author Paul Chapman 21 | */ 22 | public class ExampleSimpleMappingExceptionResolver extends 23 | SimpleMappingExceptionResolver { 24 | 25 | /** 26 | * Also enable logging to this classe's logger by default. 27 | */ 28 | public ExampleSimpleMappingExceptionResolver() { 29 | // Turn logging on by default 30 | setWarnLogCategory(getClass().getName()); 31 | } 32 | 33 | /** 34 | * Override the default to generate a log message with dynamic content. 35 | */ 36 | @Override 37 | public String buildLogMessage(Exception e, HttpServletRequest req) { 38 | return "MVC exception: " + e.getLocalizedMessage(); 39 | } 40 | 41 | /** 42 | * This method uses the older API and gets passed the handler (typically the 43 | * @Controller) that generated the exception. 44 | */ 45 | @Override 46 | protected ModelAndView doResolveException(HttpServletRequest request, 47 | HttpServletResponse response, Object handler, Exception exception) { 48 | 49 | // Get the ModelAndView to use 50 | ModelAndView mav = super.doResolveException(request, response, handler, 51 | exception); 52 | 53 | // Make more information available to the view - note that 54 | // SimpleMappingExceptionResolver adds the exception already 55 | mav.addObject("url", request.getRequestURL()); 56 | mav.addObject("timestamp", new Date()); 57 | mav.addObject("status", 500); 58 | return mav; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/demo/example/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Example classes not used in the application that you might find them useful 3 | * in your own project. 4 | * 5 | * @author Paul Chapman 6 | */ 7 | package demo.example; -------------------------------------------------------------------------------- /src/main/java/demo/exceptions/CustomException.java: -------------------------------------------------------------------------------- 1 | package demo.exceptions; 2 | 3 | public class CustomException extends RuntimeException { 4 | 5 | /** 6 | * Unique ID for Serialized object 7 | */ 8 | private static final long serialVersionUID = 4657491283614755649L; 9 | 10 | public CustomException(String msg) { 11 | super(msg); 12 | } 13 | 14 | public CustomException(String msg, Throwable t) { 15 | super(msg, t); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/demo/exceptions/DatabaseException.java: -------------------------------------------------------------------------------- 1 | package demo.exceptions; 2 | 3 | public class DatabaseException extends RuntimeException { 4 | 5 | /** 6 | * Unique ID for Serialized object 7 | */ 8 | private static final long serialVersionUID = 4657491283614455649L; 9 | 10 | public DatabaseException(String msg) { 11 | super(msg); 12 | } 13 | 14 | public DatabaseException(String msg, Throwable t) { 15 | super(msg, t); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/demo/exceptions/InvalidCreditCardException.java: -------------------------------------------------------------------------------- 1 | package demo.exceptions; 2 | 3 | public class InvalidCreditCardException extends RuntimeException { 4 | 5 | /** 6 | * Unique ID for Serialized object 7 | */ 8 | private static final long serialVersionUID = 6648725043534411041L; 9 | 10 | public InvalidCreditCardException(String msg) { 11 | super(msg); 12 | } 13 | 14 | public InvalidCreditCardException(String msg, Throwable t) { 15 | super(msg, t); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/demo/exceptions/OrderNotFoundException.java: -------------------------------------------------------------------------------- 1 | package demo.exceptions; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | /** 7 | * An example of an application-specific exception. Defined here for convenience 8 | * as we don't have a real domain model or its associated business logic. 9 | */ 10 | @ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "No such order") 11 | public class OrderNotFoundException extends RuntimeException { 12 | 13 | /** 14 | * Unique ID for Serialized object 15 | */ 16 | private static final long serialVersionUID = -8790211652911971729L; 17 | 18 | public OrderNotFoundException(String orderId) { 19 | super(orderId + " not found"); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/demo/exceptions/SupportInfoException.java: -------------------------------------------------------------------------------- 1 | package demo.exceptions; 2 | 3 | public class SupportInfoException extends RuntimeException { 4 | 5 | /** 6 | * Unique ID for Serialized object 7 | */ 8 | private static final long serialVersionUID = 4657491283614755649L; 9 | 10 | public SupportInfoException(String msg) { 11 | super(msg); 12 | } 13 | 14 | public SupportInfoException(String msg, Throwable t) { 15 | super(msg, t); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/demo/exceptions/UnhandledException.java: -------------------------------------------------------------------------------- 1 | package demo.exceptions; 2 | 3 | public class UnhandledException extends RuntimeException { 4 | 5 | /** 6 | * Unique ID for Serialized object 7 | */ 8 | private static final long serialVersionUID = 4657422283614755649L; 9 | 10 | public UnhandledException(String msg) { 11 | super(msg); 12 | } 13 | 14 | public UnhandledException(String msg, Throwable t) { 15 | super(msg, t); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/demo/exceptions/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The exceptions used by the demo. 3 | * 4 | * @author Paul Chapman 5 | */ 6 | package demo.exceptions; 7 | -------------------------------------------------------------------------------- /src/main/java/demo/filter/BrokenFilter.java: -------------------------------------------------------------------------------- 1 | package demo.filter; 2 | 3 | import java.io.IOException; 4 | import java.util.logging.Logger; 5 | 6 | import javax.servlet.Filter; 7 | import javax.servlet.FilterChain; 8 | import javax.servlet.FilterConfig; 9 | import javax.servlet.ServletException; 10 | import javax.servlet.ServletRequest; 11 | import javax.servlet.ServletResponse; 12 | import javax.servlet.http.HttpServletRequest; 13 | 14 | import org.springframework.boot.web.servlet.FilterRegistrationBean; 15 | import org.springframework.stereotype.Component; 16 | 17 | /** 18 | * A demo filter that deliberately throws an exception if the URL "/broken" is 19 | * invoked. Due to Spring Boot auto-configuration, the exception will be 20 | * reported via the usual Spring Boot page (by default the Whitelabel error page 21 | * but in this application, our Thymeleaf error-page). 22 | *
23 | * As this is a Spring Boot application, the filter can be created as a Spring 24 | * Bean by the component scanner in the usual way and Spring Boot will register 25 | * it as a filter, mapped to "/*" by default. For more control consider creating 26 | * a {@link FilterRegistrationBean} to configure it. 27 | */ 28 | @Component 29 | public class BrokenFilter implements Filter { 30 | 31 | @SuppressWarnings("serial") 32 | protected static class FilterException extends RuntimeException { 33 | 34 | public FilterException(String message) { 35 | super(message); 36 | } 37 | } 38 | 39 | private Logger logger = Logger.getLogger(BrokenFilter.class.getName()); 40 | 41 | @Override 42 | public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 43 | throws IOException, ServletException { 44 | 45 | if (request instanceof HttpServletRequest) { 46 | if (((HttpServletRequest) request).getRequestURI().endsWith("broken")) { 47 | logger.severe("BROKEN FILTER FORCES AN EXCEPTION"); 48 | throw new FilterException("Failure in BrokenFilter"); 49 | } 50 | } 51 | 52 | chain.doFilter(request, response); 53 | } 54 | 55 | @Override 56 | public void init(FilterConfig arg0) throws ServletException { 57 | } 58 | 59 | @Override 60 | public void destroy() { 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/demo/main/Main.java: -------------------------------------------------------------------------------- 1 | package demo.main; 2 | 3 | import java.util.Properties; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.boot.autoconfigure.SpringBootApplication; 8 | import org.springframework.boot.builder.SpringApplicationBuilder; 9 | import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; 10 | import org.springframework.context.annotation.ImportResource; 11 | 12 | import demo.config.ExceptionConfiguration; 13 | 14 | /** 15 | * Main entry point for our application using Spring Boot. It can be run as an 16 | * executable or as a standard war file. 17 | *
18 | * Details of annotations used: 19 | *
@EnableAutoConfiguration
: makes Spring Boot setup its
21 | * defaults.
22 | * @ComponentScan
: Scan for @Component classes,
23 | * including @Configuration classes.
24 | * @ImportResource
: Import Spring XML configuration file(s).
25 | * SimpleMappingExceptionResolver
be created?
35 | * SimpleMappingExceptionResolver
is setup that can be enabled or
38 | * disabled programmatically (just for the purpose of this demo).
39 | * mvc-configuration.xml
.
41 | * SimpleMappingExceptionResolver
is created.
43 | * 45 | * Demo mode is the default - set to "java-config" or "xml-config" to match 46 | * however you intend to use Spring for a more realistic setup. 47 | * 48 | * @see Profiles 49 | */ 50 | public static final String activeProfile = Profiles.DEMO_CONFIG_PROFILE; 51 | 52 | // Local logger 53 | protected Logger logger; 54 | 55 | // Holds Spring Boot configuration properties 56 | protected Properties props = new Properties(); 57 | 58 | /** 59 | * Retrieve requested Profiles. Depends on the value of {@link #activeProfile}. 60 | * 61 | * @return Comma-separated list of profiles. 62 | */ 63 | public static String getProfiles() { 64 | return activeProfile; 65 | } 66 | 67 | /** 68 | * We are using the constructor to perform some useful initializations: 69 | *
88 | * This application can also run as a traditional war file because it extends
89 | * SpringBootServletInitializer
as well.
90 | *
91 | * @param args
92 | * @throws Exception
93 | */
94 | public static void main(String[] args) throws Exception {
95 | // Create an instance and invoke run(); Allows the contructor to perform
96 | // initialisation regardless of whether we are running as an application
97 | // or in a container.
98 | new Main().runAsJavaApplication(args);
99 | }
100 |
101 | /**
102 | * Run the application using Spring Boot. SpringApplication.run
103 | * tells Spring Boot to use this class as the initialiser for the whole
104 | * application (via the class annotations above). This method is only used when
105 | * running as a Java application.
106 | *
107 | * @param args
108 | * Any command line arguments.
109 | */
110 | protected void runAsJavaApplication(String[] args) {
111 | SpringApplicationBuilder application = new SpringApplicationBuilder();
112 | configure(application);
113 | application.run(args);
114 | logger.info("Go to this URL: http://localhost:8080/");
115 | }
116 |
117 | /**
118 | * Configure the application using the supplied builder when running as a WAR.
119 | * This method is invoked automatically when running in a container and
120 | * explicitly by {@link #runAsJavaApplication(String[])}.
121 | *
122 | * @param application
123 | * Spring Boot application builder.
124 | */
125 | @Override
126 | protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
127 | application.sources(Main.class);
128 |
129 | logger.info("Spring Boot configuration: profiles = " + activeProfile);
130 | application.profiles(activeProfile);
131 |
132 | // Set additional properties.
133 | logger.info("Spring Boot configuratoon: properties = " + props);
134 | application.properties(props);
135 |
136 | return application;
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/src/main/java/demo/main/Profiles.java:
--------------------------------------------------------------------------------
1 | package demo.main;
2 |
3 | import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
4 |
5 | import demo.config.ExceptionConfiguration;
6 | import demo1.web.ExceptionHandlingController;
7 | import demo2.web.ControllerWithoutExceptionHandlers;
8 | import demo2.web.GlobalExceptionHandlingControllerAdvice;
9 | import demo3.config.DemoExceptionConfiguration;
10 |
11 | /**
12 | * Spring Bean configuration profiles used in this application. Three profile
13 | * combinations are currently in use:
14 | *
CONTROLLER_PROFILE
- creates a controller that also does its
16 | * own exception handling - see {@link ExceptionHandlingController}.
17 | * GLOBAL_PROFILE
and JAVA_CONFIG_PROFILE
-
18 | * creates a controller with no exception handlers. Instead exceptions are
19 | * handled globally - see {@link ControllerWithoutExceptionHandlers} and
20 | * {@link GlobalExceptionHandlingControllerAdvice}. A
21 | * {@link SimpleMappingExceptionResolver} is also created using Java Config -
22 | * see {@link ExceptionConfiguration}.
23 | * GLOBAL_PROFILE
and XML_CONFIG_PROFILE
- as
24 | * previous but the {@link SimpleMappingExceptionResolver} is configured using
25 | * XML - see mvc-configuration.xml
26 | * SimpleMappingExceptionResolver
is just for demonstration
29 | * purposes. In a real application use one or the other, but not both. You are
30 | * very likely to have an existing XML definition for this bean and there is no
31 | * need to change it.
32 | *
33 | * @author Paul Chapman
34 | */
35 | public interface Profiles {
36 |
37 | /**
38 | * Java configuration profile - see {@link ExceptionConfiguration}. Value =
39 | * {@value}
40 | */
41 | public static final String JAVA_CONFIG_PROFILE = "java-config";
42 |
43 | /**
44 | * XML configuration property - see mvc-configuration.xml
. Value =
45 | * {@value}
46 | */
47 | public static final String XML_CONFIG_PROFILE = "xml-config";
48 |
49 | /**
50 | * DEMO mode configuration property - see {@link DemoExceptionConfiguration}.
51 | * Value = {@value}
52 | */
53 | public static final String DEMO_CONFIG_PROFILE = "demo-config";
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/java/demo/main/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Main entry point for Application plus available profiles.
3 | *
4 | * @author Paul Chapman
5 | */
6 | package demo.main;
--------------------------------------------------------------------------------
/src/main/java/demo/utils/BeanLogger.java:
--------------------------------------------------------------------------------
1 | package demo.utils;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.springframework.beans.BeansException;
6 | import org.springframework.beans.factory.config.BeanPostProcessor;
7 | import org.springframework.core.Ordered;
8 | import org.springframework.stereotype.Component;
9 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
10 | import org.springframework.web.servlet.handler.HandlerExceptionResolverComposite;
11 |
12 | /**
13 | * A simple implementation of a BeanPostProcessor that logs every bean
14 | * created by Spring. When working with Spring Boot, a lot of beans get created
15 | * "auto-magically". This logger allows you to see what beans have been setup.
16 | * 17 | * You should remember that most of the web-layer beans are created by Spring 18 | * MVC automatically, whether you are using Spring Boot or not. To see what 19 | * beans are created, examine the @Bean methods in 20 | * {@link WebMvcConfigurationSupport}. 21 | * 22 | * @author Paul Chapman 23 | */ 24 | @Component 25 | public class BeanLogger implements BeanPostProcessor { 26 | 27 | protected Logger logger; 28 | protected boolean enabled = false; 29 | 30 | public BeanLogger() { 31 | logger = LoggerFactory.getLogger(getClass()); 32 | } 33 | 34 | @Override 35 | public Object postProcessBeforeInitialization(Object bean, String beanName) 36 | throws BeansException { 37 | // Nothing to do. Must return the bean or we lose it! 38 | return bean; 39 | } 40 | 41 | @Override 42 | public Object postProcessAfterInitialization(Object bean, String beanName) 43 | throws BeansException { 44 | if (!enabled) 45 | return bean; // Must return the bean or we lose it! 46 | 47 | // Display the bean. If it is Ordered, print its order also. Several MVC 48 | // classes are chained by Order and it is often useful to see what order 49 | // they are configured to run. 50 | if (bean instanceof Ordered) { 51 | String order = (bean instanceof Ordered) ? String 52 | .valueOf(((Ordered) bean).getOrder()) : "unknown"; 53 | logger.info(bean.getClass().getName() + " - Order: " + order); 54 | } else { 55 | logger.info(bean.getClass().getName()); 56 | } 57 | 58 | // Since we are concerned with exception-resolvers, lets print extra 59 | // info about this one. It delegates in turn to all the default 60 | // resolvers - see "Going Deeper" in the Blog for more details. 61 | if (bean instanceof HandlerExceptionResolverComposite) { 62 | logger.info(" resolvers: " 63 | + ((HandlerExceptionResolverComposite) bean) 64 | .getExceptionResolvers()); 65 | } 66 | 67 | // Must return the bean or we lose it! 68 | return bean; 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/demo/web/ViewMappingController.java: -------------------------------------------------------------------------------- 1 | package demo.web; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | 7 | import demo.config.ResponseDataControllerAdvice; 8 | 9 | /** 10 | * Maps URLs directly to views. Unlike View Controllers, using an actual 11 | * Controller causes the {@link ResponseDataControllerAdvice} to be invoked and 12 | * setup model data needed by these views. 13 | * 14 | * @author Paul Chapman 15 | */ 16 | @Controller 17 | public class ViewMappingController { 18 | 19 | @GetMapping("/") 20 | public String home() { 21 | return "index"; 22 | } 23 | 24 | @GetMapping("/unannotated") 25 | public String unannotated() { 26 | return "unannotated"; 27 | } 28 | 29 | @GetMapping("/no-handler") 30 | public String noHandler() { 31 | return "no-handler"; 32 | } 33 | 34 | @GetMapping("/demo5") 35 | public String demo4() { 36 | return "demo5"; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/demo/web/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Web artifacts used by all three demos. 3 | * 4 | * @author Paul Chapman 5 | */ 6 | package demo.web; -------------------------------------------------------------------------------- /src/main/java/demo1/web/ExceptionHandlingController.java: -------------------------------------------------------------------------------- 1 | package demo1.web; 2 | 3 | import java.sql.SQLException; 4 | import java.util.Date; 5 | 6 | import javax.servlet.http.HttpServletRequest; 7 | 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.core.annotation.AnnotationUtils; 11 | import org.springframework.dao.DataAccessException; 12 | import org.springframework.dao.DataIntegrityViolationException; 13 | import org.springframework.http.HttpStatus; 14 | import org.springframework.stereotype.Controller; 15 | import org.springframework.web.bind.annotation.ExceptionHandler; 16 | import org.springframework.web.bind.annotation.GetMapping; 17 | import org.springframework.web.bind.annotation.RequestMapping; 18 | import org.springframework.web.bind.annotation.ResponseStatus; 19 | import org.springframework.web.servlet.ModelAndView; 20 | 21 | import demo.exceptions.DatabaseException; 22 | import demo.exceptions.InvalidCreditCardException; 23 | import demo.exceptions.OrderNotFoundException; 24 | import demo.exceptions.SupportInfoException; 25 | import demo.exceptions.UnhandledException; 26 | 27 | /** 28 | * A controller whose request-handler methods deliberately throw exceptions to 29 | * demonstrate the points discussed in the Blog. 30 | *
31 | * Contains its own @ExceptionHandler methods to handle (most of) the 32 | * exceptions it raises. 33 | * 34 | * @author Paul Chapman 35 | */ 36 | @Controller 37 | @RequestMapping("/local") 38 | public class ExceptionHandlingController { 39 | 40 | protected Logger logger; 41 | 42 | public ExceptionHandlingController() { 43 | logger = LoggerFactory.getLogger(getClass()); 44 | } 45 | 46 | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 47 | /* . . . . . . . . . . . . . . REQUEST HANDLERS . . . . . . . . . . . . .. */ 48 | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 49 | 50 | /** 51 | * Home page. 52 | * 53 | * @return The view name (an HTML page with Thymeleaf markup). 54 | */ 55 | @GetMapping("") 56 | String home1() { 57 | logger.info("Local home page 1"); 58 | return "local"; 59 | } 60 | 61 | /** 62 | * Home page. 63 | * 64 | * @return The view name (an HTML page with Thymeleaf markup). 65 | */ 66 | @GetMapping("/") 67 | String home2() { 68 | logger.info("Local home page 2"); 69 | return "local"; 70 | } 71 | 72 | /** 73 | * No handler is needed for this exception since it is annotated with 74 | * @ResponseStatus. 75 | * 76 | * @return Nothing - it always throws the exception. 77 | * @throws OrderNotFoundException 78 | * Always thrown. 79 | */ 80 | @GetMapping("/orderNotFound") 81 | String throwOrderNotFoundException() { 82 | logger.info("Throw OrderNotFoundException for unknown order 12345"); 83 | throw new OrderNotFoundException("12345"); 84 | } 85 | 86 | /** 87 | * Throws an unannotated DataIntegrityViolationException. Must be 88 | * caught by an exception handler. 89 | * 90 | * @return Nothing - it always throws the exception. 91 | * @throws DataIntegrityViolationException 92 | * Always thrown. 93 | */ 94 | @GetMapping("/dataIntegrityViolation") 95 | String throwDataIntegrityViolationException() throws SQLException { 96 | logger.info("Throw DataIntegrityViolationException"); 97 | throw new DataIntegrityViolationException("Duplicate id"); 98 | } 99 | 100 | /** 101 | * Simulates a database exception by always throwing SQLException. 102 | * Must be caught by an exception handler. 103 | * 104 | * @return Nothing - it always throws the exception. 105 | * @throws SQLException 106 | * Always thrown. 107 | */ 108 | @GetMapping("/databaseError1") 109 | String throwDatabaseException1() throws SQLException { 110 | logger.info("Throw SQLException"); 111 | throw new SQLException(); 112 | } 113 | 114 | /** 115 | * Simulates a database exception by always throwing 116 | * DataAccessException. Must be caught by an exception handler. 117 | * 118 | * @return Nothing - it always throws the exception. 119 | * @throws DataAccessException 120 | * Always thrown. 121 | */ 122 | @GetMapping("/databaseError2") 123 | String throwDatabaseException2() throws DataAccessException { 124 | logger.info("Throw DataAccessException"); 125 | throw new DataAccessException("Error accessing database"); 126 | } 127 | 128 | /** 129 | * Simulates an illegal credit-card exception by always throwing 130 | * InvalidCreditCardException. Handled by 131 | * SimpleMappingExceptionResolver. 132 | * 133 | * @return Nothing - it always throws the exception. 134 | * @throws InvalidCreditCardException 135 | * Always thrown. 136 | */ 137 | @GetMapping("/invalidCreditCard") 138 | String throwInvalidCreditCard() throws Exception { 139 | logger.info("Throw InvalidCreditCardException"); 140 | throw new InvalidCreditCardException("1234123412341234"); 141 | } 142 | 143 | /** 144 | * Simulates a database exception by always throwing 145 | * DatabaseException. Handled by 146 | * SimpleMappingExceptionResolver. 147 | * 148 | * @return Nothing - it always throws the exception. 149 | * @throws DatabaseException 150 | * Always thrown. 151 | */ 152 | @GetMapping("/databaseException") 153 | String throwDatabaseException() throws Exception { 154 | logger.info("Throw InvalidCreditCardException"); 155 | throw new DatabaseException("Database not found: info.db"); 156 | } 157 | 158 | /** 159 | * Always throws a SupportInfoException. Must be caught by an 160 | * exception handler. 161 | * 162 | * @return Nothing - it always throws the exception. 163 | * @throws SupportInfoException 164 | * Always thrown. 165 | */ 166 | @GetMapping("/supportInfoException") 167 | String throwCustomException() throws Exception { 168 | logger.info("Throw SupportInfoException"); 169 | throw new SupportInfoException("Custom exception occurred"); 170 | } 171 | 172 | /** 173 | * Simulates a database exception by always throwing 174 | * UnhandledException. Must be caught by an exception handler. 175 | * 176 | * @return Nothing - it always throws the exception. 177 | * @throws UnhandledException 178 | * Always thrown. 179 | */ 180 | @GetMapping("/unhandledException") 181 | String throwUnhandledException() throws Exception { 182 | logger.info("Throw UnhandledException"); 183 | throw new UnhandledException("Some exception occurred"); 184 | } 185 | 186 | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 187 | /* . . . . . . . . . . . . . EXCEPTION HANDLERS . . . . . . . . . . . . .. */ 188 | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 189 | 190 | /** 191 | * Convert a predefined exception to an HTTP Status code 192 | */ 193 | @ResponseStatus(value = HttpStatus.CONFLICT, reason = "Data integrity violation") 194 | // 409 195 | @ExceptionHandler(DataIntegrityViolationException.class) 196 | public void conflict() { 197 | logger.error("Request raised a DataIntegrityViolationException"); 198 | // Nothing to do 199 | } 200 | 201 | /** 202 | * Convert a predefined exception to an HTTP Status code and specify the 203 | * name of a specific view that will be used to display the error. 204 | * 205 | * @return Exception view. 206 | */ 207 | @ExceptionHandler({ SQLException.class, DataAccessException.class }) 208 | public String databaseError(Exception exception) { 209 | // Nothing to do. Return value 'databaseError' used as logical view name 210 | // of an error page, passed to view-resolver(s) in usual way. 211 | logger.error("Request raised " + exception.getClass().getSimpleName()); 212 | return "databaseError"; 213 | } 214 | 215 | /** 216 | * Demonstrates how to take total control - setup a model, add useful 217 | * information and return the "support" view name. This method explicitly 218 | * creates and returns 219 | * 220 | * @param req 221 | * Current HTTP request. 222 | * @param exception 223 | * The exception thrown - always {@link SupportInfoException}. 224 | * @return The model and view used by the DispatcherServlet to generate 225 | * output. 226 | * @throws Exception 227 | */ 228 | @ExceptionHandler(SupportInfoException.class) 229 | public ModelAndView handleError(HttpServletRequest req, Exception exception) 230 | throws Exception { 231 | 232 | // Rethrow annotated exceptions or they will be processed here instead. 233 | if (AnnotationUtils.findAnnotation(exception.getClass(), 234 | ResponseStatus.class) != null) 235 | throw exception; 236 | 237 | logger.error("Request: " + req.getRequestURI() + " raised " + exception); 238 | 239 | ModelAndView mav = new ModelAndView(); 240 | mav.addObject("exception", exception); 241 | mav.addObject("url", req.getRequestURL()); 242 | mav.addObject("timestamp", new Date().toString()); 243 | mav.addObject("status", 500); 244 | 245 | mav.setViewName("support"); 246 | return mav; 247 | } 248 | } -------------------------------------------------------------------------------- /src/main/java/demo1/web/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Artifacts used by Demo 1 - a Controller that handles its own exceptions. 3 | * 4 | * @author Paul Chapman 5 | */ 6 | package demo1.web; -------------------------------------------------------------------------------- /src/main/java/demo2/web/ControllerWithoutExceptionHandlers.java: -------------------------------------------------------------------------------- 1 | package demo2.web; 2 | 3 | import java.sql.SQLException; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.dao.DataAccessException; 8 | import org.springframework.dao.DataIntegrityViolationException; 9 | import org.springframework.stereotype.Controller; 10 | import org.springframework.web.bind.annotation.GetMapping; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | 13 | import demo.exceptions.OrderNotFoundException; 14 | import demo.exceptions.SupportInfoException; 15 | 16 | /** 17 | * A controller whose request-handler methods deliberately throw exceptions to 18 | * demonstrate the points discussed in the Blog. 19 | *
20 | * Expects a @ControllerAdvice to handle its exceptions - see 21 | * {@link GlobalExceptionHandlingControllerAdvice}. 22 | * 23 | * @author Paul Chapman 24 | */ 25 | @Controller 26 | @RequestMapping("/global") 27 | public class ControllerWithoutExceptionHandlers { 28 | 29 | protected Logger logger; 30 | 31 | public ControllerWithoutExceptionHandlers() { 32 | logger = LoggerFactory.getLogger(getClass()); 33 | } 34 | 35 | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 36 | /* . . . . . . . . . . . . . . REQUEST HANDLERS . . . . . . . . . . . . . . */ 37 | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 38 | 39 | /** 40 | * Controller to demonstrate exception handling. 41 | * 42 | * @return The view name (an HTML page with Thymeleaf markup). 43 | */ 44 | @GetMapping("") 45 | String home1() { 46 | logger.info("Global home page 1"); 47 | return "global"; 48 | } 49 | 50 | /** 51 | * Controller to demonstrate exception handling.. 52 | * 53 | * @return The view name (an HTML page with Thymeleaf markup). 54 | */ 55 | @GetMapping("/") 56 | String home2() { 57 | logger.info("Global home page 2"); 58 | return "global"; 59 | } 60 | 61 | /** 62 | * No handler is needed for this exception since it is annotated with 63 | * @ResponseStatus. 64 | * 65 | * @return Nothing - it always throws the exception. 66 | * @throws OrderNotFoundException 67 | * Always thrown. 68 | */ 69 | @GetMapping("/orderNotFound") 70 | String throwOrderNotFoundException() { 71 | logger.info("Throw OrderNotFoundException for unknown order 12345"); 72 | throw new OrderNotFoundException("12345"); 73 | } 74 | 75 | /** 76 | * Throws an unannotated DataIntegrityViolationException. Must be 77 | * caught by an exception handler. 78 | * 79 | * @return Nothing - it always throws the exception. 80 | * @throws DataIntegrityViolationException 81 | * Always thrown. 82 | */ 83 | @GetMapping("/dataIntegrityViolation") 84 | String throwDataIntegrityViolationException() throws SQLException { 85 | logger.info("Throw DataIntegrityViolationException"); 86 | throw new DataIntegrityViolationException("Duplicate id"); 87 | } 88 | 89 | /** 90 | * Simulates a database exception by always throwing SQLException. 91 | * Must be caught by an exception handler. 92 | * 93 | * @return Nothing - it always throws the exception. 94 | * @throws SQLException 95 | * Always thrown. 96 | */ 97 | @GetMapping("/databaseError1") 98 | String throwDatabaseException1() throws SQLException { 99 | logger.info("Throw SQLException"); 100 | throw new SQLException(); 101 | } 102 | 103 | /** 104 | * Simulates a database exception by always throwing 105 | * DataAccessException. Must be caught by an exception handler. 106 | * 107 | * @return Nothing - it always throws the exception. 108 | * @throws DataAccessException 109 | * Always thrown. 110 | */ 111 | @GetMapping("/databaseError2") 112 | String throwDatabaseException2() throws DataAccessException { 113 | logger.info("Throw DataAccessException"); 114 | throw new DataAccessException("Error accessing database"); 115 | } 116 | 117 | /** 118 | * Always throws a SupportInfoException. Must be caught by an 119 | * exception handler. 120 | * 121 | * @return Nothing - it always throws the exception. 122 | * @throws SupportInfoException 123 | * Always thrown. 124 | */ 125 | @GetMapping("/supportInfoException") 126 | String throwCustomException() throws Exception { 127 | logger.info("Throw SupportInfoException"); 128 | throw new SupportInfoException("Custom exception occurred"); 129 | } 130 | 131 | 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/demo2/web/GlobalExceptionHandlingControllerAdvice.java: -------------------------------------------------------------------------------- 1 | package demo2.web; 2 | 3 | import java.sql.SQLException; 4 | import java.util.Date; 5 | 6 | import javax.servlet.http.HttpServletRequest; 7 | 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.core.annotation.AnnotationUtils; 11 | import org.springframework.dao.DataAccessException; 12 | import org.springframework.dao.DataIntegrityViolationException; 13 | import org.springframework.http.HttpStatus; 14 | import org.springframework.web.bind.annotation.ControllerAdvice; 15 | import org.springframework.web.bind.annotation.ExceptionHandler; 16 | import org.springframework.web.bind.annotation.ResponseStatus; 17 | import org.springframework.web.servlet.ModelAndView; 18 | 19 | import demo.exceptions.SupportInfoException; 20 | import demo1.web.ExceptionHandlingController; 21 | 22 | /** 23 | * Performs the same exception handling as {@link ExceptionHandlingController} 24 | * but offers them globally. The exceptions below could be raised by any 25 | * controller and they would be handled here, if not handled in the controller 26 | * already. 27 | * 28 | * @author Paul Chapman 29 | */ 30 | @ControllerAdvice 31 | public class GlobalExceptionHandlingControllerAdvice { 32 | 33 | protected Logger logger; 34 | 35 | public GlobalExceptionHandlingControllerAdvice() { 36 | logger = LoggerFactory.getLogger(getClass()); 37 | } 38 | 39 | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 40 | /* . . . . . . . . . . . . . EXCEPTION HANDLERS . . . . . . . . . . . . . . */ 41 | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 42 | 43 | /** 44 | * Convert a predefined exception to an HTTP Status code 45 | */ 46 | @ResponseStatus(value = HttpStatus.CONFLICT, reason = "Data integrity violation") 47 | // 409 48 | @ExceptionHandler(DataIntegrityViolationException.class) 49 | public void conflict() { 50 | logger.error("Request raised a DataIntegrityViolationException"); 51 | // Nothing to do 52 | } 53 | 54 | /** 55 | * Convert a predefined exception to an HTTP Status code and specify the 56 | * name of a specific view that will be used to display the error. 57 | * 58 | * @return Exception view. 59 | */ 60 | @ExceptionHandler({ SQLException.class, DataAccessException.class }) 61 | public String databaseError(Exception exception) { 62 | // Nothing to do. Return value 'databaseError' used as logical view name 63 | // of an error page, passed to view-resolver(s) in usual way. 64 | logger.error("Request raised " + exception.getClass().getSimpleName()); 65 | return "databaseError"; 66 | } 67 | 68 | /** 69 | * Demonstrates how to take total control - setup a model, add useful 70 | * information and return the "support" view name. This method explicitly 71 | * creates and returns 72 | * 73 | * @param req 74 | * Current HTTP request. 75 | * @param exception 76 | * The exception thrown - always {@link SupportInfoException}. 77 | * @return The model and view used by the DispatcherServlet to generate 78 | * output. 79 | * @throws Exception 80 | */ 81 | @ExceptionHandler(SupportInfoException.class) 82 | public ModelAndView handleError(HttpServletRequest req, Exception exception) 83 | throws Exception { 84 | 85 | // Rethrow annotated exceptions or they will be processed here instead. 86 | if (AnnotationUtils.findAnnotation(exception.getClass(), 87 | ResponseStatus.class) != null) 88 | throw exception; 89 | 90 | logger.error("Request: " + req.getRequestURI() + " raised " + exception); 91 | 92 | ModelAndView mav = new ModelAndView(); 93 | mav.addObject("exception", exception); 94 | mav.addObject("url", req.getRequestURL()); 95 | mav.addObject("timestamp", new Date().toString()); 96 | mav.addObject("status", 500); 97 | 98 | mav.setViewName("support"); 99 | return mav; 100 | } 101 | } -------------------------------------------------------------------------------- /src/main/java/demo2/web/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Artifacts used by Demo 2 - a Controller that relies on a global Controller 3 | * Advice to handle its exceptions. 4 | * 5 | * @author Paul Chapman 6 | */ 7 | package demo2.web; -------------------------------------------------------------------------------- /src/main/java/demo3/config/DemoExceptionConfiguration.java: -------------------------------------------------------------------------------- 1 | package demo3.config; 2 | 3 | import java.util.Properties; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.context.annotation.Profile; 10 | import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver; 11 | 12 | import demo.main.Profiles; 13 | import demo3.web.SwitchController; 14 | import demo3.web.SwitchableSimpleMappingExceptionResolver; 15 | 16 | /** 17 | * Setup for exception handling using a {@link SimpleMappingExceptionResolver} 18 | * sub-class that can be enabled or disabled programmatically. 19 | *
20 | * The use of the "demo-config" profile here is for demonstration only. A real 21 | * application would either create a {@link SimpleMappingExceptionResolver} or 22 | * it wouldn't. 23 | * 24 | * @author Paul Chapman 25 | */ 26 | @Configuration 27 | @Profile(Profiles.DEMO_CONFIG_PROFILE) 28 | public class DemoExceptionConfiguration { 29 | 30 | protected Logger logger; 31 | 32 | public DemoExceptionConfiguration() { 33 | logger = LoggerFactory.getLogger(getClass()); 34 | logger.info("Creating DemoExceptionConfiguration"); 35 | } 36 | 37 | /** 38 | * Setup the switchable {@link SimpleMappingExceptionResolver} to provide 39 | * useful defaults for logging and handling exceptions. 40 | * 41 | * @return The new resolver 42 | */ 43 | @Bean(name = "simpleMappingExceptionResolver") 44 | public SwitchableSimpleMappingExceptionResolver createSwitchableSimpleMappingExceptionResolver() { 45 | logger.info("Creating SwitchableSimpleMappingExceptionResolver in disabled mode"); 46 | 47 | // Turn exception resolving off to start 48 | boolean initialState = false; 49 | 50 | SwitchableSimpleMappingExceptionResolver resolver = new SwitchableSimpleMappingExceptionResolver( 51 | initialState); 52 | 53 | Properties mappings = new Properties(); 54 | mappings.setProperty("DatabaseException", "databaseException"); 55 | mappings.setProperty("InvalidCreditCardException", "creditCardError"); 56 | 57 | resolver.setExceptionMappings(mappings); // None by default 58 | resolver.setExceptionAttribute("ex"); // Default is "exception" 59 | resolver.setWarnLogCategory("demo1.ExceptionLogger"); // No default 60 | 61 | // See comment in ExceptionConfiguration 62 | resolver.setDefaultErrorView("defaultErrorPage"); 63 | return resolver; 64 | } 65 | 66 | /** 67 | * Create the {@link SwitchController}. 68 | */ 69 | @Bean(name = "switchController") 70 | public SwitchController createSwitchController() { 71 | logger.info("Creating SwitchController"); 72 | return new SwitchController( 73 | createSwitchableSimpleMappingExceptionResolver()); 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/demo3/config/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Configuration for demonstrating the use of the 3 | * {@link org.springframework.web.servlet.handler.SimpleMappingExceptionResolver}. 4 | * 5 | * @author Paul Chapman 6 | */ 7 | package demo3.config; 8 | 9 | -------------------------------------------------------------------------------- /src/main/java/demo3/web/ExceptionThrowingController.java: -------------------------------------------------------------------------------- 1 | package demo3.web; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Controller; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver; 10 | 11 | import demo.exceptions.CustomException; 12 | import demo.exceptions.DatabaseException; 13 | import demo.exceptions.InvalidCreditCardException; 14 | import demo.exceptions.SupportInfoException; 15 | import demo.exceptions.UnhandledException; 16 | 17 | /** 18 | * A controller whose request-handler methods deliberately throw exceptions to 19 | * demonstrate the points discussed in the Blog. 20 | *
21 | * Expects a SimpleMappingExceptionResolver to handle its exceptions.
22 | *
23 | * @author Paul Chapman
24 | */
25 | @Controller
26 | @RequestMapping("/throw")
27 | public class ExceptionThrowingController {
28 |
29 | protected Logger logger;
30 | protected SimpleMappingExceptionResolver resolver;
31 |
32 | public ExceptionThrowingController() {
33 | logger = LoggerFactory.getLogger(getClass());
34 | }
35 |
36 | /**
37 | * Need to see if the {@link SwitchableSimpleMappingExceptionResolver} is
38 | * being used and if so, is it enabled?
39 | *
40 | * @param resolver
41 | */
42 | @Autowired(required = false)
43 | public void setSimpleMappingExceptionResolver(
44 | SimpleMappingExceptionResolver resolver) {
45 | this.resolver = resolver;
46 | }
47 |
48 | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
49 | /* . . . . . . . . . . . . . . REQUEST HANDLERS . . . . . . . . . . . . .. */
50 | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
51 |
52 | /**
53 | * Home page.
54 | *
55 | * @return The view name (an HTML page with Thymeleaf markup).
56 | */
57 | @GetMapping("/")
58 | String home() {
59 | logger.info("Throw home page");
60 |
61 | // Check if the SwitchableSimpleMappingExceptionResolver is in use and
62 | // enabled, or not.
63 | if (resolver == null)
64 | return "no-handler";
65 | else if (resolver instanceof SwitchableSimpleMappingExceptionResolver) {
66 | return ((SwitchableSimpleMappingExceptionResolver) resolver)
67 | .isEnabled() ? "unannotated" : "no-handler";
68 | } else
69 | return "unannotated";
70 | }
71 |
72 | /**
73 | * Simulates a database exception by always throwing
74 | * DatabaseException. Handled by
75 | * SimpleMappingExceptionResolver.
76 | *
77 | * @return Nothing - it always throws the exception.
78 | * @throws DatabaseException
79 | * Always thrown.
80 | */
81 | @GetMapping("/databaseException")
82 | String throwDatabaseException() throws Exception {
83 | logger.info("Throw DatabaseException");
84 | throw new DatabaseException("Database not found: info.db");
85 | }
86 |
87 | /**
88 | * Simulates an illegal credit-card exception by always throwing
89 | * InvalidCreditCardException. Handled by
90 | * SimpleMappingExceptionResolver.
91 | *
92 | * @return Nothing - it always throws the exception.
93 | * @throws InvalidCreditCardException
94 | * Always thrown.
95 | */
96 | @GetMapping("/invalidCreditCard")
97 | String throwInvalidCreditCard() throws Exception {
98 | logger.info("Throw InvalidCreditCardException");
99 | throw new InvalidCreditCardException("1234123412341234");
100 | }
101 |
102 | /**
103 | * Simulates a database exception by always throwing
104 | * CustomException. Must be caught by an exception handler.
105 | *
106 | * @return Nothing - it always throws the exception.
107 | * @throws CustomException
108 | * Always thrown.
109 | */
110 | @GetMapping("/supportInfoException")
111 | String throwCustomException() throws Exception {
112 | logger.info("Throw SupportInfoException");
113 | throw new SupportInfoException("Exception occurred");
114 | }
115 |
116 | /**
117 | * Simulates a database exception by always throwing
118 | * UnhandledException. Must be caught by an exception handler.
119 | *
120 | * @return Nothing - it always throws the exception.
121 | * @throws UnhandledException
122 | * Always thrown.
123 | */
124 | @GetMapping("/unhandledException")
125 | String throwUnhandledException() throws Exception {
126 | logger.info("Throw UnhandledException");
127 | throw new UnhandledException("Some exception occurred");
128 | }
129 |
130 | }
131 |
--------------------------------------------------------------------------------
/src/main/java/demo3/web/SwitchController.java:
--------------------------------------------------------------------------------
1 | package demo3.web;
2 |
3 | import org.slf4j.LoggerFactory;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.context.annotation.Profile;
6 | import org.springframework.stereotype.Controller;
7 | import org.springframework.ui.Model;
8 | import org.springframework.web.bind.annotation.GetMapping;
9 | import org.springframework.web.bind.annotation.ModelAttribute;
10 | import org.springframework.web.bind.annotation.PathVariable;
11 | import org.springframework.web.bind.annotation.RequestMapping;
12 |
13 | import demo.main.Main;
14 |
15 | /**
16 | * A simple controller for enabling and disabling the
17 | * {@link SwitchableSimpleMappingExceptionResolver}.
18 | */
19 | @Controller
20 | @Profile("no-auto-creation")
21 | public class SwitchController {
22 |
23 | protected SwitchableSimpleMappingExceptionResolver resolver;
24 |
25 | @Autowired
26 | public SwitchController(SwitchableSimpleMappingExceptionResolver resolver) {
27 | this.resolver = resolver;
28 | }
29 |
30 | /**
31 | * Must return the currently active profiles for the home page (index.html)
32 | * to use.
33 | *
34 | * @return Currently active profiles.
35 | */
36 | @ModelAttribute("profiles")
37 | public String getProfiles() {
38 | return Main.getProfiles();
39 | }
40 |
41 | /**
42 | * Enable the profile if action
is "on".
43 | *
44 | * @param action
45 | * What to do. Resolver is enabled if set to "ON" (case ignored).
46 | * @return Redirection back to the home page.
47 | */
48 | @GetMapping("/simpleMappingExceptionResolver/{action}")
49 | public String enable(@PathVariable("action") String action, Model model) {
50 | LoggerFactory.getLogger(getClass()).info(
51 | "SwitchableSimpleMappingExceptionResolver is " + action);
52 | // Anything other than ON, is treated as OFF
53 | boolean enabled = action.equalsIgnoreCase("on");
54 | resolver.setEnabled(enabled);
55 |
56 | // The ResponseDataControllerAdvice sets this for every request, but
57 | // before it is changed by this controller. So we need to set it
58 | // again - to reflect the new value.
59 | model.addAttribute("switchState", enabled ? "on" : "off");
60 |
61 | // Which view to return depends on resolver status
62 | // - Enabled? Show use of exception handing without annotations.
63 | // - Disabled? Show what happens with no excpetion handling at all.
64 | return enabled ? "unannotated" : "no-handler";
65 | }
66 | }
--------------------------------------------------------------------------------
/src/main/java/demo3/web/SwitchableSimpleMappingExceptionResolver.java:
--------------------------------------------------------------------------------
1 | package demo3.web;
2 |
3 | import javax.servlet.http.HttpServletRequest;
4 |
5 | import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;
6 | import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
7 |
8 | /**
9 | * A sub-class of {@link SimpleMappingExceptionResolver} that can be turned on
10 | * and off for demonstration purposes (you wouldn't do this in a real
11 | * application).
12 | */
13 | public class SwitchableSimpleMappingExceptionResolver extends
14 | SimpleMappingExceptionResolver {
15 |
16 | protected boolean enabled = false;
17 |
18 | public SwitchableSimpleMappingExceptionResolver(boolean enabled) {
19 | this.enabled = enabled;
20 | }
21 |
22 | /**
23 | * Enabled or not?
24 | *
25 | * @return Is enabled?
26 | */
27 | public boolean isEnabled() {
28 | return enabled;
29 | }
30 |
31 | /**
32 | * Allow this resolver to be turned on and off whilst the application is
33 | * running.
34 | *
35 | * @param enabled
36 | * Set to enabled?
37 | */
38 | public void setEnabled(boolean enabled) {
39 | this.enabled = enabled;
40 | }
41 |
42 | /**
43 | * Resolver only handles exceptions if enabled. Overrides method inherited
44 | * from {@link AbstractHandlerExceptionResolver}
45 | */
46 | @Override
47 | protected boolean shouldApplyTo(HttpServletRequest request, Object handler) {
48 | return enabled && super.shouldApplyTo(request, handler);
49 | }
50 |
51 | }
--------------------------------------------------------------------------------
/src/main/java/demo3/web/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Artifacts used by Demo 3 - a Controller that relies on a Simple Mapping
3 | * Exception Resolver to handle its exceptions.
4 | *
5 | * @author Paul Chapman
6 | */
7 | package demo3.web;
--------------------------------------------------------------------------------
/src/main/java/demo5/web/ReturnOrRedirectController.java:
--------------------------------------------------------------------------------
1 | package demo5.web;
2 |
3 | import javax.servlet.http.HttpServletRequest;
4 |
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 | import org.springframework.http.HttpStatus;
8 | import org.springframework.stereotype.Controller;
9 | import org.springframework.web.bind.annotation.ExceptionHandler;
10 | import org.springframework.web.bind.annotation.GetMapping;
11 | import org.springframework.web.bind.annotation.RequestMapping;
12 |
13 | import demo.exceptions.OrderNotFoundException;
14 |
15 | /**
16 | * Demonstrate the difference between returning the error view and redirecting
17 | * to the Spring Boot error page (which uses the same error view).
18 | *
19 | * @author Paul Chapman
20 | *
21 | */
22 | @Controller
23 | public class ReturnOrRedirectController {
24 |
25 | enum Action {
26 | RETURN_ACTION, FORWARD_ACTION
27 | }
28 |
29 | @SuppressWarnings("serial")
30 | protected static class DemoException extends RuntimeException {
31 | final public Action action;
32 |
33 | public DemoException(Action action) {
34 | this.action = action;
35 | }
36 | }
37 |
38 | protected Logger logger;
39 |
40 | public ReturnOrRedirectController() {
41 | logger = LoggerFactory.getLogger(getClass());
42 | }
43 |
44 | /**
45 | * Throws a {@link DemoException} with the action "return" - see
46 | * {@link
47 | *
48 | * @return Nothing - it always throws the exception.
49 | * @throws OrderNotFoundException
50 | * Always thrown.
51 | */
52 | @GetMapping("/demo5/return")
53 | String throwDemoException1() {
54 | logger.info("Throw DemoException - ask hander to return 'error' view");
55 | throw new DemoException(Action.RETURN_ACTION);
56 | }
57 |
58 | /**
59 | * Throws a {@link DemoException} with the action "return" - see
60 | * {@link
61 | *
62 | * @return Nothing - it always throws the exception.
63 | * @throws OrderNotFoundException
64 | * Always thrown.
65 | */
66 | @GetMapping("/demo5/forward")
67 | String throwDemoException2() {
68 | logger.info("Throw DemoException - ask hander to return 'error' view");
69 | throw new DemoException(Action.FORWARD_ACTION);
70 | }
71 |
72 | /**
73 | * Handle a {@link DemoException} by returning the "error" view by name or
74 | * redirecting to the "/error" URL. The first shows the error page with no
75 | * additional information (ExceptionResolvers by default don't provide any).
76 | * The second forces a redirect via Spring Boot's internal
77 | * BasicErrorController whose @GetMapping
method adds
78 | * exception and other details into the Model for the error view to use.
79 | *
80 | * @return Exception view.
81 | */
82 | @ExceptionHandler
83 | public String handleDemoException(DemoException exception,
84 | HttpServletRequest req) {
85 | logger.error("Handle DemoExeption - action is " + exception.action);
86 |
87 | // Because we are handling the error, the server thinks everything is
88 | // OK, so the status is 200. So let's set it to something else.
89 | req.setAttribute("javax.servlet.error.status_code",
90 | HttpStatus.BAD_REQUEST.value());
91 | return exception.action == Action.RETURN_ACTION ? "error"
92 | : "forward:/error";
93 | }
94 |
95 | }
96 |
--------------------------------------------------------------------------------
/src/main/java/demo5/web/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Controller that demos exception handling using Spring Boot's internal setup.
3 | *
4 | * @author Paul Chapman
5 | */
6 | package demo5.web;
--------------------------------------------------------------------------------
/src/main/java/mvc-configuration.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 | Your credit card has been rejected: 11 | error here 12 |
13 | Note: You would never generate a page that displays a Java 14 | exception to an end-user in a real application 15 |16 | 17 | 18 | 19 | 20 |
23 | You are seeing this error page due to the definition of the
24 | SimpleMappingExceptionResolver
.
25 |
An internal data storage error has occurred accessing:
10 |
11 | Request URL
12 |
26 | Note: You would never generate a page that displays a Java 27 | exception to an end-user in a real application 28 |29 |
33 | Cause unknown (no exception details available)
34 | 35 | 36 | 37 |Notice that there is very little information here (above the 42 | line). Firstly, end-users should not be shown internal exception 43 | details, but secondly, no exception details are available anyway.
44 |ExceptionHandlingController.handleError()
48 | or
49 | GlobalControllerExceptionHandler.handleError()
50 | which explicitly create a Model and put extra details into it (these
51 | are the methods used when a
52 | SupportInfoException
53 | is thrown).
54 |
57 | Alternatively you could extend
58 | ExceptionHandlerExceptionResolver
59 |
61 |
62 | and override
63 |
doResolveHandlerMethodException(HttpServletRequest req, HttpServletResponse resp, 65 | HandlerMethod handlerMethod, Exception exception)66 |
67 | to add exception and other attributes to the model. Also set the
68 | inherited
69 | order
70 | data-member to a value less than
71 | MAX_INT
72 | so it runs before the default
73 | ExceptionHandlerExceptionResolver
74 | instance (it is easier to create your own handler than try to modify
75 | the one created by Spring).
76 |
An internal data storage error has occurred accessing:
10 |
11 | Request URL
12 |
14 | Exception
15 |
16 | Note: You would never generate a page that displays a Java 17 | exception to an end-user in a real application 18 |19 | 20 | 21 | 22 | 23 |
26 | You are seeing this error page due to the definition of the
27 | SimpleMappingExceptionResolver
28 | .
29 |
24 | Page: Page URL 25 |
26 | 27 |28 | URL: Request 29 | URL 30 |
31 | 32 |33 | Occurred: Timestamp 34 |
35 | 36 |37 | Response Status: status-code error ... 39 |
40 | 41 | 43 |50 | Note: You would never generate a page that displays a Java 51 | exception to an end-user in a real application 52 |53 |
60 | You are seeing this error page due to the definition of the
61 | SimpleMappingExceptionResolver
.
62 |
Exception handling with Spring Boot.
12 | 13 |ReturnOrRedirectContoller
, BasicErrorController
.
18 |
25 | Spring Boot generates its error page by forwarding to it via an
26 | internal controller (see below for details). This demo shows this at
27 | work. The exceptions below are thrown and handled within the same
28 | Controller -
29 | demo5.web.ReturnOrRedirectController
30 |
33 | .
34 |
To fully understand this demo, review the output in the server 36 | (console) log as you click the links below.
37 | 38 |Note: After clicking on any of the links below, use your browser's back button to return to this page.
41 | 42 |@ExceptionHandler
44 | method return the logical view-name "error
" - this will
45 | display the error.html
template. Notice it contains no
46 | useful data because, by default, @ExceptionHandler
47 | methods don't provide any.
48 |
50 | Throw an exception and make the
51 | @ExceptionHandler
52 | forward the request to
53 | /error
54 | . This will also display the
55 | error.html
56 | template but notice it now contains useful data.
57 |
59 | This is because Spring Boot's internal
60 | BasicErrorController
61 | defines an
62 | @RequestMapping
63 | method mapped to
64 | /error
65 | which is able to add exception and other useful information to the
66 | Model. Look for the
67 | BasicErrorController.errorHtml()
68 |
70 |
71 | which in turn relies on
72 |
DefaultErrorAttributes.getErrorAttributes()
73 |
75 |
76 |
77 |
82 | Return to Home page. 83 |
84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /src/main/resources/templates/error.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 17 | 18 |25 | Page: Page URL 26 |
27 | 28 |29 | Occurred: Timestamp 30 |
31 | 32 |33 | Response Status: status-code error ... 35 |
36 | 37 | 39 |46 | Note: You would never generate a page that displays a Java 47 | exception to an end-user in a real application 48 |49 |
53 | Cause unknown (no exception details available)
54 | 55 | 56 | 57 |
61 | You are seeing this error page due to Spring Boot (which returns a
62 | view called "error" in response to uncaught exceptions. Since we are
63 | using Thymeleaf this corresponds to the template
64 | error.html
65 | )
66 |
Handling exceptions using a global Controller Advice.
11 | 12 |ControllerWithoutExceptionHandlers
,
17 | GlobalExceptionHandlingControllerAdvice
.
18 |
25 | The exceptions below are are not handled by the controller that threw
26 | them, but rely on a
27 | ControllerAdvice
28 |
30 |
31 | instead.
32 |
demo2.web.ControllerWithoutExceptionHandlers
35 |
37 | demo2.web.GlobalExceptionHandlingControllerAdvice
40 |
42 | To fully understand this demo, review the output in the server 47 | (console) log as you click the links below.
48 | 49 |50 | The behavior of this demo is the same as Demo 1 - 51 | just implemented differently. 52 |
53 | 54 |Note: After clicking on any of the links below, use your browser's back button to return to this page.
57 | 58 |81 | Demo 3: Using a 82 | Simple Mapping Exception Handler. 83 |
84 |85 | Demo 3: Using a Simple Mapping Exception 86 | Handler. 87 |
88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /src/main/resources/templates/header.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 |Main.java
activeProfile
data-member.
17 |
26 | Spring-Boot sets up a view called error
as the default error-page and we
27 | are using Thymeleaf to define it via the error.html template
28 | (if you don't configure your own error view you see the default
29 | "Whitelabel Error Page").
30 |
32 | Without Spring Boot you would see your container's default error page. 33 | Rename error.html to error2.html and restart to see this 34 | for yourself. 35 |
36 | 37 |The previous version of this demo tried to show everything on 38 | one page. It has been broken down into different pages for clarity.
39 | 40 |demo1.web.ExceptionHandlingController
@ExceptionHandler
49 | methods.demo2.web.ControllerWithoutExceptionHandlers
demo2.web.GlobalExceptionHandlingControllerAdvice
mvc-configuration.xml
demo.config.ExceptionConfiguration
demo3.config.DemoExceptionConfiguration
demo3.web.ExceptionThrowingController
error.html
.For those interested, Spring Boot provides RESTful URLs for 122 | interrogating your application (they return JSON format data):
123 | 124 |SimpleMappingExceptionResolver
14 |
16 | Handling exceptions in the Controller.
9 | 10 |ExceptionHandlingController
.
15 |
22 | The exceptions below are thrown and handled within the same Controller
23 | using its own
24 | @ExceptionHandler
25 | methods. The controller is
26 | demo1.web.ExceptionHandlingController
27 |
29 |
30 | .
31 |
To fully understand this demo, review the output in the server 33 | (console) log as you click the links below.
34 | 35 |36 | The behavior of this demo is the same as Demo 2 37 | - just implemented differently. 38 |
39 | 40 |Note: After clicking on any of the links below, use your browser's back button to return to this page.
43 | 44 |67 | Demo 2: Global Exception Handling - using a 68 | Controller Advice 69 |
70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /src/main/resources/templates/no-handler.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 |With no handlers enabled, everything defaults to Spring Boot. 12 | 13 |
SimpleMappingExceptionResolver
26 | created via Java Configuration - see ExceptionConfiguration
.
27 |
28 |
29 |
30 | passed to a SimpleMappingExceptionResolver
31 | created via XML Configuration - see mvc-configuration.xml
.
32 |
33 |
34 |
35 | passed to a switchable SimpleMappingExceptionResolver
36 | that may or may not choose to handle the exception - see DemoExceptionConfiguration
.
37 | The exceptions below have no handler defined for them when
58 | running the demo-config
profile. They will generate the default error-page
59 | supplied by Spring Boot. A non Spring-Boot application would generate
60 | the container's error page.
To fully understand this demo, review the output in the server 63 | (console) log as you click the links below.
64 | 65 |Note: After clicking on any of the links below, use your browser's back button to return to this page.
68 | 69 |88 | Demo 5: Working with Spring Boot. 89 |
90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /src/main/resources/templates/support.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 |15 | Page: Page URL 16 |
17 | 18 |19 | Occurred: Timestamp 20 |
21 | 22 |23 | Response Status: status-code error ... 25 |
26 | 27 |Application has encountered an error. Please contact support on 28 | ...
29 | 30 |Support may ask you to right click to view page source.
31 | 32 | 36 | 37 |Using the Simple Mapping Exception Resolver instead of annotations. 11 | 12 |
ExceptionConfiguration
mvc-configuration.xml
30 |
31 | DemoExceptionConfiguration
39 |
40 |
60 | The exceptions below show the use of a
61 | SimpleMappingExceptionResolver
62 | - the oldest way of handling exceptions in Spring dating back to V1
63 | and still commonly used. It is equivalent to using a ControllerAdvice
64 | - your choice.
65 |
To fully understand this demo, review the output in the server 68 | (console) log as you click the links below.
69 | 70 |Note: After clicking on any of the links below, use your browser's back button to return to this page.
73 | 74 |SimpleMappingExceptionResolver
.
79 | SimpleMappingExceptionResolver
.
83 | SimpleMappingExceptionResolver
.
86 |
87 | Note how uninformative it is. Subclass
88 | SimpleMappingExceptionResolver
89 | and override its
90 | doResolveException()
91 | method to make additional context available in the model and in the
92 | error page - see
93 | ExampleSimpleMappingExceptionResolver
94 |
96 |
97 | .
98 |
108 | Demo 4: No 109 | Handler. For comparison, the same exceptions with no Simple Mapping 110 | Exception Resolver. 111 |
112 |113 | Demo 5: Working with Spring Boot. 114 |
115 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /support.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulc4/mvc-exceptions/e72c53595671ab6b4e7d0d8e33efa56eae06e10c/support.png --------------------------------------------------------------------------------