RestController
- Convenience annotation for annotating a Controller and ResponseBody at the same time
- Assumes each RequestMapping is going to return the body of it's request
- less verbose than having to define each method and it's response type
- no longer need to use the
ContentNegotiatingViewResolver- but still can for fine tuning
- Example: EventsReportController.java
package com.pluralsight.controller;
import com.pluralsight.model.Event;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
@RestController
public class EventsReportController {
@RequestMapping("/events")
public List<Event> getEvents() {
List<Event> events = new ArrayList<>();
Event event1 = new Event();
event1.setName("Java User Group");
events.add(event1);
Event event2 = new Event();
event2.setName("Angular User Group");
events.add(event2);
return events; // [{"name":"Java User Group"},{"name":"Angular User Group"}]
}
}
Angular frontent addition
- Example: create a function that retrieves data and binds it to angular scope
function Events($scope, $http) {
$http.get('events.json').
success(function(data) {
$scope.events = data;
})
}
- Example: create a simple angular UI
<!doctype html>
<html ng-app>
<head>
<title>Hello Events Angular</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.min.js"></script>
<script src="events.js"></script> // will retrieve the events
</head>
<body>
<div ng-controller="Events">
I have {{events.length}} events!
<ul class="events-container">
<li ng-repeat="event in events"> // loops over events
{{event.name}}
</li>
</ul>
</div>
</body>
</html>
- already have proper form tags inside of JSP pages
- just need to add Maven Dependencies and use the BindingResult method signature
Adding Validation
- Example: pom.xml, add two dependencies
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.0.3.Final</version>
</dependency>
- Example: Attendee.java without custom errors
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotEmpty;
import javax.validation.constraints.Size;
public class Attendee {
@Size(min=2, max=30)
private String name;
@NotEmpty @Email
private String emailAddress;
...
}
- Example: AttendeeController.java
@RequestMapping(value = "/attendee", method = RequestMethod.POST)
// signature must go in this order or will not work
public String processAttendee(@Valid Attendee attendee, BindingResult result, Model m) {
System.out.println(attendee);
// must check if there are errors
if(result.hasErrors()) return "attendee";
return "redirect:index.html";
}
- Example: custom error messages
// messages_es.properties
// corresponds to the validation constraint annotations
// Size is special since it can be specific to class and method
Size.attendee.name=El nombre debe estar entre {2} y {1} caracteres
// email denotes the same error message for all email addresses throughout the app using this validator
Email=¡¡¡La dirección de email no es válida!!! UG!
NotEmpty=¡El campo no se puede dejar en blanco!
Custome phone validation
- Example: Phone.java interface
package com.pluralsight.view;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Documented
@Constraint(validatedBy = PhoneConstraintValidator.class)
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Phone {
// refers to key inside of messages.properties
String message() default "{Phone}";
// generic array
Class<?>[] groups() default {};
// pass in values associated with annotation
Class<? extends Payload>[] payload() default{};
}
- Example: PhoneConstraintValidator.java
package com.pluralsight.view;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class PhoneConstraintValidator implements ConstraintValidator<Phone, String> {
@Override
public void initialize(Phone phone) {
}
@Override
public boolean isValid(String phoneField, ConstraintValidatorContext cxt) {
if(phoneField == null) return false;
return phoneField.matches("[0-9()-]*");
}
}
- Example: Attendee.java add phone field and validation
public class Attendee {
@Size(min=2, max=30)
private String name;
@NotEmpty @Email
private String emailAddress;
@Phone
private String phone;
...
}
- Example: add to attendee.jsp
<label for="textinput3"><spring:message code="attendee.phone"/>:</label>
<form:input path="phone" cssErrorClass="error"/>
<form:errors path="phone" cssClass="error"/>
WebMvcConfigurerAdapter
- used to aid in configuration with out
WebConfig.java - helps replace items that were simplified with schemas
- like the
xmlns="http://www.springframework.org/shema"and more - can be replaced with
extends WebMvcConfigurerAdapter
- like the
Static Resources
- more verbose in java configuration, but just as clear
<mvc:resources location="assets" mapping="/assets/**"/> - vs:
// WebConfig.java
@Override
public void addResourceHanlders(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/pdfs/**").addResourceLocations("/WEB-INF/pdf/");
}
// WebApplicationInitializer.java
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
...
dispatcher.addMapping("*.pdf");
}
Recap techniques
- Session variables can be stored using
@SessionAttribute - when to use forward vs redirect
index.htmlvsindex.jsp- more secure to not expose our backend technology of jsp pages
forward vs redirect
- Example: redirect in EventController.java
@RequestMapping(value="/event", method = RequestMethod.POST)
public String processEvent(@ModelAttribute("event") Event event) {
System.out.println(event);
// will redirect as opposed to using the internal resource view resolver
return "redirect:index.html";
}
- will change the url
- Example: forward in HelloController.java
// forward request along to jsp
// different than just returning index.jsp since response is continuing
// our response object, building onto it (not creating a new request)
@RequestMapping(value="/index")
public String index(Model model) {
return "forward:index.jsp";
}
- will leave the url as it was so users cannot bookmark the page in the process!
SessionAttributes
- Example: SessionAttribute persists the event object
@Controller
// saves the "event" model through page navigation / sessions
// references the same model object in processEvent
@SessionAttributes("event")
public class EventController {
@RequestMapping(value="/event", method = RequestMethod.GET)
public String displayEventPage(Model model) {
Event event = new Event();
event.setName("Jave User Group");
model.addAttribute("event", event);
return "event";
}
@RequestMapping(value="/event", method = RequestMethod.POST)
// links up with the SessionAttribute
public String processEvent(@ModelAttribute("event") Event event) {
System.out.println(event);
// will redirect as opposed to using the internal resource view resolver
return "redirect:index.html";
}
}
Attendee Build out
- Example: Attendee.java
package com.pluralsight.model;
public class Attendee {
private String name;
private String emailAddress;
public String getEmailAddress() {
return emailAddress;
}
public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
- Example: AttendeeController.java
package com.pluralsight.controller;
import com.pluralsight.model.Attendee;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class AttendeeController {
@RequestMapping(value = "/attendee", method = RequestMethod.GET)
public String displayAttendeePage(Model model) {
Attendee attendee = new Attendee();
model.addAttribute("attendee", attendee);
return "attendee";
}
@RequestMapping(value = "/attendee", method = RequestMethod.POST)
public String processAttendee(@ModelAttribute("attendee") Attendee attendee) {
System.out.println(attendee);
return "redirect:index.html";
}
}
- Example: attendee.jsp
<%--
Created by IntelliJ IDEA.
User: gprovost
Date: 2019-05-16
Time: 09:10
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html>
<html>
<head>
<title>Attendee Page</title>
<style type="text/css">
.error {
color: #ff0000;
}
.errorBlock {
color: #000;
background-color: #ffEEEE;
border: 3px solid #ff0000;
padding: 8px;
margin: 16px;
}
</style>
</head>
<body>
<form:form commandName="attendee">
<form:errors path="*" cssClass="errorBlock" element="div"/>
<label for="textinput1">Enter Name:</label>
<form:input path="name" cssErrorClass="error"/>
<form:errors path="name" cssClass="error"/>
<br>
<label for="textinput2">Enter Email Address:</label>
<form:input path="emailAddress" cssErrorClass="error"/>
<form:errors path="emailAddress" cssClass="error"/>
<br>
<input type="submit" class="btn" value="Enter Attendee" />
</form:form>
</body>
</html>
Internationalization
- Example: attendee.jsp
...
<body>
<a href="?language=en">
English
</a>
<br/>
<a href="?language=es">
Spanish
</a>
<form:form commandName="attendee">
<form:errors path="*" cssClass="errorBlock" element="div"/>
<%-- align with the keys in the properties files--%>
<label for="textinput1"><spring:message code="attendee.name"/> :</label>
<form:input path="name" cssErrorClass="error"/>
<form:errors path="name" cssClass="error"/>
<br>
<%-- align with the keys in the properties files--%>
<label for="textinput2"><spring:message code="attendee.email.address"/> :</label>
<form:input path="emailAddress" cssErrorClass="error"/>
<form:errors path="emailAddress" cssClass="error"/>
<br>
<input type="submit" class="btn" value="Enter Attendee" />
</form:form>
</body>
</html>
- Example: WebConfig.java
// loads message properties
@Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
// prefix of the file that will store name / value properties for internationalization
messageSource.setBasename("messages");
return messageSource;
}
// sees if there is a current local instead of requesting it every time
@Bean
public LocaleResolver localeResolver() {
SessionLocaleResolver resolver = new SessionLocaleResolver();
// can use a type-safe enum for java config instead of a string "en" in xml config
resolver.setDefaultLocale(Locale.ENGLISH);
return resolver;
}
// detects when there is a change in the url
@Override
public void addInterceptors(InterceptorRegistry registry) {
LocaleChangeInterceptor changeInterceptor = new LocaleChangeInterceptor();
changeInterceptor.setParamName("language");
registry.addInterceptor(changeInterceptor);
}
- Example: messags.properties
// messags_en.properties
attendee.name=Enter Name
attendee.email.address=Enter Email Address
// messages_es.properties
attendee.name=Introducir nombre
attendee.email.address=Introduzca la dirección de correo electrónico
Conventions
- place
.jsppages underWEB-INFfolder - force users to go through application instead of allowing them to directly see
.jsppages
Resolving a View
- create a
Beanwithin theWebConfig.javafile containing anInternalResourceViewResolver- removes the need for the controller to return the file as a string (
hello.jsp), instead just the name of the file (hello)
- removes the need for the controller to return the file as a string (
- Example:
WebConfig.java
// creates the same as serverlet-config in web.xml and appContext.xml
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.pluralsight")
public class WebConfig {
@Bean
public InternalResourceViewResolver getInternalresourceViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/jsp/");
resolver.setSuffix(".jsp");
return resolver;
}
}
Application
- Creating an Event:
EventController.java
@Controller
public class EventController {
@RequestMapping(value="/event", method = RequestMethod.GET)
public String displayEventPage(Model model) {
Event event = new Event();
event.setName("Jave User Group");
model.addAttribute("event", event);
return "event";
}
}
event.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html>
<html>
<head>
<title>Event Page</title>
<style type="text/css">
.error {
color: #ff0000;
}
.errorBlock {
color: #000;
background-color: #ffEEEE;
border: 3px solid #ff0000;
padding: 8px;
margin: 16px;
}
</style>
</head>
<body>
<form:form commandName="event">
<form:errors path="*" cssClass="errorBlock" element="div"/>
<label for="textinput1">Enter Minutes:</label>
<form:input path="name" cssErrorClass="error"/>
<form:errors path="name" cssClass="error"/>
<br>
<input type="submit" class="btn" value="Enter Event" />
</form:form>
</body>
</html>
- controllers are the "verbs" in the system that do an action on something
Responsibilities
- interpret user input and transform that input into a model
- provide access to business logic
- determine view based off logic
- interprets exceptions from business logic / service layer
- act as traffic cop
controller annotations
@Controller: works similar to MVC 3@RestController: new in MVC 4- makes it easier to expose RESTful services outside of the controller
@Configuration: signifies a configuration class- how to step outside of needed xml config files
@EnableWebMvc: Enables Java Config@ComponentScan: Override the default scan location for controllers- Example: Controller annotation
package com.pluralsight.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class HelloController {
@RequestMapping(value = "/greeting")
public String sayHello(Model model) {
model.addAttribute("greeting", "Hello World");
return "hello";
}
}
EnableWebMvc
- convenience annotation for WebMvcConfigurationSupport
- Only used for Java configuration of Spring MVC webapps
- customizable by extending WebMvcConfigurerAdapter
web.xml
- does not abandon Java config
- still need
Dispatcher Servlet- some people prefer to do this in web.xml as opposed to java config
contextConfigurationLocationpoints to class rather than XML file- Example:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<servlet>
<servlet-name>springDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</init-param>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.pluralsight.WebConfig</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springDispatcherServlet</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
</web-app>
Without web.xml
- still need a mapping somewhere
WebApplicationInitializeris needed- uses Servlet 3.0 hooks
- builds app context for us
- Example: WebAppInitializer
// hooks into the servlet engine
public class WebAppInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
WebApplicationContext context = getContext();
// add a listener to listen to registration events from the servlet context
servletContext.addListener(new ContextLoaderListener(context));
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("DispatcherServlet", new DispatcherServlet(context));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("*.html");
}
private AnnotationConfigWebApplicationContext getContext() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.setConfigLocation("com.pluralsight.WebConfig");
return context;
}
}
Patterns
- MVC still sound pattern, but there are others
- MVP, MVVM, MV*
- to help handle different JS frameworks and mobile applications
ModelView-ViewModel
- originated from
.NET - RESTful backend, rich JS frontend
- layers cleanly separated
- view can receive notifications and give commands to the
ViewModeland there is data binding between the two- View <===> ViewModel <===> Model
Controllers
- XML config is reduced, can get away with none
- new
@RestControllerannotation- more easily expose RESTful services from code without boilerplate inside of xml config
- set up using
@EnableWebMvc
Service
- same as in Spring MVC 3, but java config no longer requires
appContext.xmlorservlet-config.xml - use annotations for various configuration elements
- comonent-scan, interceptors, handlers, formatters, converters
- convenience annotations similar to namespaces
Repository
- Java config does not require an
appContext.xmlor aservlet-config.xml- you can though, personal preference
- different types of Repositories if using Spring Data JPA
- convention over configuration
Container-less
- can run without a container with MVC 4
- instead run embedded withing Tomcat
java configuration
- do not need
appContext.xmlorservlet-config.xml- same stuff moved into java code rather than xml file
- Annotations will be used instead for various configuration elements / namespaces
- Example: WebConfig.java
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
// these annotations create the same as serverlet-config in web.xml and appContext.xml
@Configuration
@EnableWebMvc
public class WebConfig {
}
- no need for
web.xmlconfiguration- servlet 3+ spec negates need for it
- spring helps by doing initialization interfaces
- can create one if needed (legacy code)
- inside of
src/main/webapp/WEB-INF
- inside of
- Spring facilitates any configuration needs
- order does matter in the
pom.xmlfile for dependencies.- If one dependency is a lower version than another and they have the same transient dependencies, order in the
pom.xmlwill dictate which version is downloaded
- If one dependency is a lower version than another and they have the same transient dependencies, order in the