Basic Exception Handling
Exception Handling is a cross cutting concern
Do not handle exceptions in Controllers or Services, if you cannot add value to them.
Bit of refactoring on the controllers
Whitelabel Error Page provided by default by Spring Boot
You can see a few details of the errors
We can customize if we would want to
@ControllerAdvice and Controller Specific Exception Handling
Handling Errors thrown from Views
@Controller("error")
public class ExceptionController {
private Log logger = LogFactory.getLog(ExceptionController.class);
@ExceptionHandler(Exception.class)
public ModelAndView handleError(HttpServletRequest req, Exception ex) {
logger.error("Request: " + req.getRequestURL() + " raised " + ex);
ModelAndView mav = new ModelAndView();
mav.addObject("exception", ex);
mav.addObject("url", req.getRequestURL());
mav.setViewName("error");
return mav;
}
}
<?xml version =" 1.0" encoding =" UTF-8" ?>
<project xmlns =" http://maven.apache.org/POM/4.0.0" xmlns : xsi =" http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=" http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" >
<modelVersion >4.0.0</modelVersion >
<groupId >com.in28minutes.springboot.web</groupId >
<artifactId >spring-boot-first-web-application</artifactId >
<version >0.0.1-SNAPSHOT</version >
<packaging >jar</packaging >
<name >spring-boot-first-web-application</name >
<description >Demo project for Spring Boot</description >
<parent >
<groupId >org.springframework.boot</groupId >
<artifactId >spring-boot-starter-parent</artifactId >
<version >1.4.3.RELEASE</version >
<relativePath /> <!-- lookup parent from repository -->
</parent >
<properties >
<project .build.sourceEncoding>UTF-8</project .build.sourceEncoding>
<project .reporting.outputEncoding>UTF-8</project .reporting.outputEncoding>
<java .version>1.8</java .version>
</properties >
<dependencies >
<dependency >
<groupId >org.springframework.boot</groupId >
<artifactId >spring-boot-starter-web</artifactId >
</dependency >
<dependency >
<groupId >org.springframework.boot</groupId >
<artifactId >spring-boot-starter-security</artifactId >
</dependency >
<dependency >
<groupId >javax.servlet</groupId >
<artifactId >jstl</artifactId >
</dependency >
<dependency >
<groupId >org.webjars</groupId >
<artifactId >bootstrap</artifactId >
<version >3.3.6</version >
</dependency >
<dependency >
<groupId >org.webjars</groupId >
<artifactId >bootstrap-datepicker</artifactId >
<version >1.0.1</version >
</dependency >
<dependency >
<groupId >org.webjars</groupId >
<artifactId >jquery</artifactId >
<version >1.9.1</version >
</dependency >
<dependency >
<groupId >org.apache.tomcat.embed</groupId >
<artifactId >tomcat-embed-jasper</artifactId >
<scope >provided</scope >
</dependency >
<dependency >
<groupId >org.springframework.boot</groupId >
<artifactId >spring-boot-devtools</artifactId >
<scope >runtime</scope >
</dependency >
<dependency >
<groupId >org.springframework.boot</groupId >
<artifactId >spring-boot-starter-test</artifactId >
<scope >test</scope >
</dependency >
</dependencies >
<build >
<plugins >
<plugin >
<groupId >org.springframework.boot</groupId >
<artifactId >spring-boot-maven-plugin</artifactId >
</plugin >
</plugins >
</build >
</project >
src/main/java/com/in28minutes/springboot/web/controller/ErrorController.java
package com .in28minutes .springboot .web .controller ;
import javax .servlet .http .HttpServletRequest ;
import javax .servlet .http .HttpServletResponse ;
import org .springframework .stereotype .Controller ;
import org .springframework .web .bind .annotation .ExceptionHandler ;
import org .springframework .web .servlet .ModelAndView ;
@ Controller ("error" )
public class ErrorController {
@ ExceptionHandler (Exception .class )
public ModelAndView handleException
(HttpServletRequest request , Exception ex ){
ModelAndView mv = new ModelAndView ();
mv .addObject ("exception" , ex .getLocalizedMessage ());
mv .addObject ("url" , request .getRequestURL ());
mv .setViewName ("error" );
return mv ;
}
}
src/main/java/com/in28minutes/springboot/web/controller/LogoutController.java
package com .in28minutes .springboot .web .controller ;
import javax .servlet .http .HttpServletRequest ;
import javax .servlet .http .HttpServletResponse ;
import org .springframework .security .core .Authentication ;
import org .springframework .security .core .context .SecurityContextHolder ;
import org .springframework .security .core .userdetails .UserDetails ;
import org .springframework .security .web .authentication .logout .LogoutHandler ;
import org .springframework .security .web .authentication .logout .SecurityContextLogoutHandler ;
import org .springframework .stereotype .Controller ;
import org .springframework .ui .ModelMap ;
import org .springframework .web .bind .annotation .RequestMapping ;
import org .springframework .web .bind .annotation .RequestMethod ;
@ Controller
public class LogoutController {
@ RequestMapping (value = "/logout" , method = RequestMethod .GET )
public String logout (HttpServletRequest request ,
HttpServletResponse response ) {
Authentication authentication = SecurityContextHolder .getContext ()
.getAuthentication ();
if (authentication != null ) {
new SecurityContextLogoutHandler ().logout (request , response ,
authentication );
}
return "redirect:/" ;
}
}
src/main/java/com/in28minutes/springboot/web/controller/TodoController.java
package com .in28minutes .springboot .web .controller ;
import java .text .SimpleDateFormat ;
import java .util .Date ;
import javax .validation .Valid ;
import org .springframework .beans .factory .annotation .Autowired ;
import org .springframework .beans .propertyeditors .CustomDateEditor ;
import org .springframework .security .core .context .SecurityContextHolder ;
import org .springframework .security .core .userdetails .UserDetails ;
import org .springframework .stereotype .Controller ;
import org .springframework .ui .ModelMap ;
import org .springframework .validation .BindingResult ;
import org .springframework .web .bind .WebDataBinder ;
import org .springframework .web .bind .annotation .InitBinder ;
import org .springframework .web .bind .annotation .RequestMapping ;
import org .springframework .web .bind .annotation .RequestMethod ;
import org .springframework .web .bind .annotation .RequestParam ;
import com .in28minutes .springboot .web .model .Todo ;
import com .in28minutes .springboot .web .service .TodoService ;
@ Controller
public class TodoController {
@ Autowired
TodoService service ;
@ InitBinder
public void initBinder (WebDataBinder binder ) {
// Date - dd/MM/yyyy
SimpleDateFormat dateFormat = new SimpleDateFormat ("dd/MM/yyyy" );
binder .registerCustomEditor (Date .class , new CustomDateEditor (
dateFormat , false ));
}
@ RequestMapping (value = "/list-todos" , method = RequestMethod .GET )
public String showTodos (ModelMap model ) {
String name = getLoggedInUserName (model );
model .put ("todos" , service .retrieveTodos (name ));
return "list-todos" ;
}
private String getLoggedInUserName (ModelMap model ) {
Object principal = SecurityContextHolder .getContext ()
.getAuthentication ().getPrincipal ();
if (principal instanceof UserDetails ) {
return ((UserDetails ) principal ).getUsername ();
}
return principal .toString ();
}
@ RequestMapping (value = "/add-todo" , method = RequestMethod .GET )
public String showAddTodoPage (ModelMap model ) {
model .addAttribute ("todo" , new Todo (0 , getLoggedInUserName (model ),
"Default Desc" , new Date (), false ));
return "todo" ;
}
@ RequestMapping (value = "/delete-todo" , method = RequestMethod .GET )
public String deleteTodo (@ RequestParam int id ) {
if (id ==1 )
throw new RuntimeException ("Something went wrong" );
service .deleteTodo (id );
return "redirect:/list-todos" ;
}
@ RequestMapping (value = "/update-todo" , method = RequestMethod .GET )
public String showUpdateTodoPage (@ RequestParam int id , ModelMap model ) {
Todo todo = service .retrieveTodo (id );
model .put ("todo" , todo );
return "todo" ;
}
@ RequestMapping (value = "/update-todo" , method = RequestMethod .POST )
public String updateTodo (ModelMap model , @ Valid Todo todo ,
BindingResult result ) {
if (result .hasErrors ()) {
return "todo" ;
}
todo .setUser (getLoggedInUserName (model ));
service .updateTodo (todo );
return "redirect:/list-todos" ;
}
@ RequestMapping (value = "/add-todo" , method = RequestMethod .POST )
public String addTodo (ModelMap model , @ Valid Todo todo , BindingResult result ) {
if (result .hasErrors ()) {
return "todo" ;
}
service .addTodo (getLoggedInUserName (model ), todo .getDesc (), todo .getTargetDate (),
false );
return "redirect:/list-todos" ;
}
}
src/main/java/com/in28minutes/springboot/web/controller/WelcomeController.java
package com .in28minutes .springboot .web .controller ;
import org .springframework .security .core .context .SecurityContextHolder ;
import org .springframework .security .core .userdetails .UserDetails ;
import org .springframework .stereotype .Controller ;
import org .springframework .ui .ModelMap ;
import org .springframework .web .bind .annotation .RequestMapping ;
import org .springframework .web .bind .annotation .RequestMethod ;
@ Controller
public class WelcomeController {
@ RequestMapping (value = "/" , method = RequestMethod .GET )
public String showWelcomePage (ModelMap model ) {
model .put ("name" , getLoggedinUserName ());
return "welcome" ;
}
private String getLoggedinUserName () {
Object principal = SecurityContextHolder .getContext ()
.getAuthentication ().getPrincipal ();
if (principal instanceof UserDetails ) {
return ((UserDetails ) principal ).getUsername ();
}
return principal .toString ();
}
}
src/main/java/com/in28minutes/springboot/web/model/Todo.java
package com .in28minutes .springboot .web .model ;
import java .util .Date ;
import javax .validation .constraints .Size ;
public class Todo {
private int id ;
private String user ;
@ Size (min =10 , message ="Enter at least 10 Characters..." )
private String desc ;
private Date targetDate ;
private boolean isDone ;
public Todo () {
super ();
}
public Todo (int id , String user , String desc , Date targetDate ,
boolean isDone ) {
super ();
this .id = id ;
this .user = user ;
this .desc = desc ;
this .targetDate = targetDate ;
this .isDone = isDone ;
}
public int getId () {
return id ;
}
public void setId (int id ) {
this .id = id ;
}
public String getUser () {
return user ;
}
public void setUser (String user ) {
this .user = user ;
}
public String getDesc () {
return desc ;
}
public void setDesc (String desc ) {
this .desc = desc ;
}
public Date getTargetDate () {
return targetDate ;
}
public void setTargetDate (Date targetDate ) {
this .targetDate = targetDate ;
}
public boolean isDone () {
return isDone ;
}
public void setDone (boolean isDone ) {
this .isDone = isDone ;
}
@ Override
public int hashCode () {
final int prime = 31 ;
int result = 1 ;
result = prime * result + id ;
return result ;
}
@ Override
public boolean equals (Object obj ) {
if (this == obj ) {
return true ;
}
if (obj == null ) {
return false ;
}
if (getClass () != obj .getClass ()) {
return false ;
}
Todo other = (Todo ) obj ;
if (id != other .id ) {
return false ;
}
return true ;
}
@ Override
public String toString () {
return String .format (
"Todo [id=%s, user=%s, desc=%s, targetDate=%s, isDone=%s]" , id ,
user , desc , targetDate , isDone );
}
}
src/main/java/com/in28minutes/springboot/web/security/SecurityConfiguration.java
package com .in28minutes .springboot .web .security ;
import org .springframework .beans .factory .annotation .Autowired ;
import org .springframework .context .annotation .Configuration ;
import org .springframework .security .config .annotation .authentication .builders .AuthenticationManagerBuilder ;
import org .springframework .security .config .annotation .web .builders .HttpSecurity ;
import org .springframework .security .config .annotation .web .configuration .WebSecurityConfigurerAdapter ;
@ Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
//Create User - in28Minutes/dummy
@ Autowired
public void configureGlobalSecurity (AuthenticationManagerBuilder auth )
throws Exception {
auth .inMemoryAuthentication ().withUser ("in28Minutes" ).password ("dummy" )
.roles ("USER" , "ADMIN" );
}
@ Override
protected void configure (HttpSecurity http ) throws Exception {
http .authorizeRequests ().antMatchers ("/login" ).permitAll ()
.antMatchers ("/" , "/*todo*/**" ).access ("hasRole('USER')" ).and ()
.formLogin ();
}
}
src/main/java/com/in28minutes/springboot/web/service/TodoService.java
package com .in28minutes .springboot .web .service ;
import java .util .ArrayList ;
import java .util .Date ;
import java .util .Iterator ;
import java .util .List ;
import org .springframework .stereotype .Service ;
import com .in28minutes .springboot .web .model .Todo ;
@ Service
public class TodoService {
private static List <Todo > todos = new ArrayList <Todo >();
private static int todoCount = 3 ;
static {
todos .add (new Todo (1 , "in28Minutes" , "Learn Spring MVC" , new Date (),
false ));
todos .add (new Todo (2 , "in28Minutes" , "Learn Struts" , new Date (), false ));
todos .add (new Todo (3 , "in28Minutes" , "Learn Hibernate" , new Date (),
false ));
}
public List <Todo > retrieveTodos (String user ) {
List <Todo > filteredTodos = new ArrayList <Todo >();
for (Todo todo : todos ) {
if (todo .getUser ().equalsIgnoreCase (user )) {
filteredTodos .add (todo );
}
}
return filteredTodos ;
}
public Todo retrieveTodo (int id ) {
for (Todo todo : todos ) {
if (todo .getId ()==id ) {
return todo ;
}
}
return null ;
}
public void updateTodo (Todo todo ){
todos .remove (todo );
todos .add (todo );
}
public void addTodo (String name , String desc , Date targetDate ,
boolean isDone ) {
todos .add (new Todo (++todoCount , name , desc , targetDate , isDone ));
}
public void deleteTodo (int id ) {
Iterator <Todo > iterator = todos .iterator ();
while (iterator .hasNext ()) {
Todo todo = iterator .next ();
if (todo .getId () == id ) {
iterator .remove ();
}
}
}
}
src/main/java/com/in28minutes/springboot/web/SpringBootFirstWebApplication.java
package com .in28minutes .springboot .web ;
import org .springframework .boot .SpringApplication ;
import org .springframework .boot .autoconfigure .SpringBootApplication ;
import org .springframework .context .annotation .ComponentScan ;
@ SpringBootApplication
@ ComponentScan ("com.in28minutes.springboot.web" )
public class SpringBootFirstWebApplication {
public static void main (String [] args ) {
SpringApplication .run (SpringBootFirstWebApplication .class , args );
}
}
src/main/resources/application.properties
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp
logging.level.org.springframework.web=INFO
src/main/webapp/WEB-INF/jsp/common/footer.jspf
<script src="webjars/jquery/1.9.1/jquery.min.js"></script>
<script src="webjars/bootstrap/3.3.6/js/bootstrap.min.js"></script>
<script
src="webjars/bootstrap-datepicker/1.0.1/js/bootstrap-datepicker.js"></script>
<script>
$('#targetDate').datepicker({
format : 'dd/mm/yyyy'
});
</script>
</body>
</html>
src/main/webapp/WEB-INF/jsp/common/header.jspf
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<html>
<head>
<title>First Web Application</title>
<link href="webjars/bootstrap/3.3.6/css/bootstrap.min.css"
rel="stylesheet">
</head>
<body>
src/main/webapp/WEB-INF/jsp/common/navigation.jspf
<nav role="navigation" class="navbar navbar-default">
<div class="">
<a href="http://www.in28minutes.com" class="navbar-brand">in28Minutes</a>
</div>
<div class="navbar-collapse">
<ul class="nav navbar-nav">
<li class="active"><a href="/">Home</a></li>
<li><a href="/list-todos">Todos</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a href="/logout">Logout</a></li>
</ul>
</div>
</nav>
src/main/webapp/WEB-INF/jsp/error.jsp
<%@ include file="common/header.jspf"%>
<%@ include file="common/navigation.jspf"%>
<div class="container">
An exception occurred! Please contact Support!
</div>
<%@ include file="common/footer.jspf"%>
src/main/webapp/WEB-INF/jsp/list-todos.jsp
<%@ include file="common/header.jspf" %>
<%@ include file="common/navigation.jspf" %>
<div class="container">
<table class="table table-striped">
<caption>Your todos are</caption>
<thead>
<tr>
<th>Description</th>
<th>Target Date</th>
<th>Is it Done?</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<c:forEach items="${todos}" var="todo">
<tr>
<td>${todo.desc}</td>
<td><fmt:formatDate value="${todo.targetDate}" pattern="dd/MM/yyyy"/></td>
<td>${todo.done}</td>
<td><a type="button" class="btn btn-success"
href="/update-todo?id=${todo.id}">Update</a></td>
<td><a type="button" class="btn btn-warning"
href="/delete-todo?id=${todo.id}">Delete</a></td>
</tr>
</c:forEach>
</tbody>
</table>
<div>
<a class="button" href="/add-todo">Add a Todo</a>
</div>
</div>
<%@ include file="common/footer.jspf" %>
src/main/webapp/WEB-INF/jsp/todo.jsp
<%@ include file="common/header.jspf" %>
<%@ include file="common/navigation.jspf" %>
<div class="container">
<form:form method="post" commandName="todo">
<form:hidden path="id" />
<fieldset class="form-group">
<form:label path="desc">Description</form:label>
<form:input path="desc" type="text" class="form-control"
required="required" />
<form:errors path="desc" cssClass="text-warning" />
</fieldset>
<fieldset class="form-group">
<form:label path="targetDate">Target Date</form:label>
<form:input path="targetDate" type="text" class="form-control"
required="required" />
<form:errors path="targetDate" cssClass="text-warning" />
</fieldset>
<button type="submit" class="btn btn-success">Add</button>
</form:form>
</div>
<%@ include file="common/footer.jspf" %>
src/main/webapp/WEB-INF/jsp/welcome.jsp
<%@ include file="common/header.jspf"%>
<%@ include file="common/navigation.jspf"%>
<div class="container">
Welcome ${name}!! <a href="/list-todos">Click here</a> to manage your
todo's.
</div>
<%@ include file="common/footer.jspf"%>
src/test/java/com/in28minutes/springboot/web/SpringBootFirstWebApplicationTests.java
package com .in28minutes .springboot .web ;
import org .junit .Test ;
import org .junit .runner .RunWith ;
import org .springframework .boot .test .context .SpringBootTest ;
import org .springframework .test .context .junit4 .SpringRunner ;
@ RunWith (SpringRunner .class )
@ SpringBootTest
public class SpringBootFirstWebApplicationTests {
@ Test
public void contextLoads () {
}
}
Implementing Server Side Validation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Command Bean or Form Backing Bean
Add Validation
Use Validation on Controller
Display Errors in View
Command Bean
~~~~~~~~~~~~
Controller
View - Spring Form Tags
LoginController -> adds name to model
welcome.jsp -> shows ${name}
TodoController -> redirects to list-todos.jsp
${name} is empty
Component, Service, Repository, Controller
Autowired
ComponentScan
Field dummyService in com.in28minutes.springboot.web.controller.LoginController
required a bean of type 'com.in28minutes.dummy.DummyService'
that could not be found.
Spring Boot Starter Parent
Spring Boot Starter Web
@SpringBootApplication
Auto Configuration
Dispatcher Servlet
/login => "login"
"login" => src/main/webapp/WEB-INF/jsp/login.jsp
Search for a view named "login"
/login => LoginController