2. Who’s Ted
✦ Developer with Fortune 100 financial services firm
since July 2008.
✦ Working with Java since late 2008.
✦ Web developer as freelance/hobby since 2001
(LAMP)
✦ Lots of freelancing and web hosting during college
✦ http://ted.pennin.gs / @thesleepyvegan / ted@pennin.gs
3. What is MVC?
✦ MVC spits code into three areas
✦ Model – data/persistence
layer
✦ View – presentation layer, UI
and client-side scripts
✦ Controller – business logic
that controls data and
execution
✦ Strong design pattern that
emphasizes separation.
4. MVC Cleanliness
✦ Application logic goes in the controllers.
✦ Data persistence goes in the model.
✦ UI presentation and JavaScript varnish goes in the
views.
✦ Do not try to fight MVC code separation. If you
feel the need to break convention, refactor.
5. MVC Done Badly – Views
✦Doing actual work in JSPs
<%
try {
String url="jdbc:mysql://localhost/companies?user=larry_ellison&password=yachts";
con=DriverManager.getConnection(url);
stmt=con.createStatement();
} catch(Exception e){ System.err.println(“Unable to acquire companies!”);
e.printStackTrace(); }
if (request.getParameter("action") != null){
String bookname = request.getParameter("company_name");
String author = request.getParameter("price");
stmt.executeUpdate("insert into acquisitions(company_name,price) values
('"+company_name+"','"+price+"')");
rst=stmt.executeQuery("select * from acquisitions");
%>
<html> <body> <center>
<h2>Victim List</h2>
<table border="1" cellspacing="0" cellpadding="0">
Adapted from http://www.roseindia.net/jsp/Accessingdatabase-fromJSP.shtml
6. MVC Done Badly – Controllers
✦HTML in the controllers
@RequestMapping(“/acquisitions/new”)
public void createAcquisition(Model model) {
Acquisition v = new Acquisition();
model.addAttribute(“newVictim”, v);
model.addAttribute(“newVictimForm”, collectVictimVitalStats(v));
}
private String renderVictimInfoForm(Acquisition v) {
StringBuilder sb = new StringBuilder()
.append("<form action='").append(SERVLET_PATH)
.append("' method='post'><h1>Please enter your information</h1>");
sb.append("<p><label for='name'>Victim Name</label>")
.append("<input type='text' name='name' value='")
.append(v.getName()).append("'/>").append("</p>");
[...]
return sb.toString();
}
8. MVC Done Badly – Models
✦Business logic in the model
public class Victim {
private String name;
private short numberOfYachts;
[. . . ]
public Victim(String name, short numberOfYachts) {
if (numberOfYachts < 4) {
throw new VictimUnworthyException(“Too few yachts. Larry Ellison will not be sated.”);
}
}
[. . . ]
}
✦HTML in methods
public String toString() {
return “<h3 class=‘name’>Why Hello ” + this.name + “!</h3><br/>I admire your”
+ “<strong><font color=“pink”>” + this.numberOfYachts + “</font></strong> yachts”;
}
9. Spring MVC is Still Spring
✦ Full Dependency Injection and Inversion of Control
✦ XML- and Annotation-based configuration
✦ Heavy use of annotation-based configuration
✦ Application context files are still available, but mostly
unnecessary
✦ <mvc:annotation-driven> and <content:component-scan>
✦ Maven dependencies and archetypes
✦ Convention over configuration
✦ Very few interfaces to implement
“Spring Goodness”
10. The Basic Annotations
✦ Spring MVC is mostly annotation driven, so here is your
vernacular:
✦ Standard Spring annotations:
✦ @Component, @Service, @Repository,
✦ @Autowired, @Value, @Configuration
✦ @Transactional, @Aspect and JSR 284 + 330
✦ @Controller – an MVC Controller
✦ @RequestMapping
✦ @ModelAttribute
✦ @RequestParam and @PathVariable
✦ @SessionAttributes
11. How does Spring do MVC?
✦ All requests are sent to Spring’s DispatcherServlet
✦ DispatcherServlet sends requests to @Controllers
✦ THERE ARE NO DEVELOPER-WRITTEN SERVLETS
Diagram from Spring docs on their website
12. The Simplicity of Controllers
✦ @Controller makes a class a controller “bean”
✦ Specialization of @Component
✦ @RequestMapping defines the URL paths handled by a
controller or method.
✦ It is possible to nest paths, as in example.
✦ Many different RequestMethods can be serviced.
✦ {} define @PathVariable/@ReqParam
✦ Value of Inheritance
✦ Annotation caveat
13. Controller Example
@Controller
@RequestMapping({ "/yachts", "/mah-boats" })
public class YachtController extends BaseController {
@RequestMapping(value = "/{yachtKey}", method = GET)
public String displayYacht( @PathVariable(“yachtKey”) String
yachtKey, Model model) {
LOG.debug("Displaying Yacht " + yachtKey);
Yacht yacht = service.findYachtByKey(yachtKey);
model.addAttribute("yacht", yacht);
return YACHT_PAGE_VIEW;
}
}
14. Reading User Input
✦ Typically happens through method parameter annotations
✦ @PathVariable(“var”) / @RequestParam(“var”)
✦ Corresponds to a {var} value in a @RequestMapping
✦ Best practice to be explicit
✦ @ModelAttribute
✦ @RequestBody
✦ Any type can be used.
✦ Be wary of type conversion issues
✦ See PropertyEditorSupport and Spring’s Converter SPI
✦ Use @InitBinder in a controller superclass
✦ JSON payloads can often be unmarshalled on the fly.
15. Populating The Model
✦ In MVC, dynamic data only appears in views through the model.
✦ Spring MVC enforces this pattern constraint.
✦ Each request receives a fresh new Model (or equivalent).
✦ Two basic approaches
✦ In @Controller method, receive a Model or ModelAndView object
✦ Add directly to it.
✦ No need to return Model/MV object received. (More on that later.)
✦ In @Controller class, annotate methods with @ModelAttribute(“name”)
✦ Used in a @Controller superclass, can be used for template data
✦ User privileges
✦ Pseudo-security hack until full security is implemented
16. Creating Forms (1/2)
✦ Creating a new object submission form requires manually instantiating your
target object
✦ if you want to create a com.oracle.acquisition.timekeeper.model.TimeEntry(),
in your controller method you must
✦ model.addAttribute(“timeEntry”, new
com.oracle.acquisition.timekeeper.model.TimeEntry());
Assuming:
package com.oracle.acquisition.timekeeper.model;
public class TimeEntry {
private String victimName;
private String projectCode;
private double hours;
private Date timestamp;
[…getters, setters, etc…]
}
✦ This exact object will be used by your JSP (or similar) in the
next step… getter/setter round-tripping on POST
17. Creating Forms (2/2)
Most new object views can function as an edit view. Conserve code.
<form:form method="post" modelAttribute=“timeEntry"
cssClass="form">
<form:label path=“projectCode">
Project Code <form:errors path=“projectCode"
cssClass="error" />
</form:label>
<form:select items="${userProjectCodes}"
path=“projectCode“ itemLabel=“projectName"/>
<form:label path=“hours">
Hours <form:errors path=“hours" cssClass="error" />
</form:label>
<form:input path=“hours“ />
<button type="submit">Submit</button>
</form:form>
19. Bean Validation (JSR-303)
✦ Constraints are defined by
annotations on the actual data
entities
✦ Validation is executed
automagically by framework
✦ User-friendly errors appear
automagically on the view.
✦ Object graphs can be traversed
and nested objects are validated
(or not, if you wish).
@Entity
public class Yacht {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Size(min = 4, max = 35, message = "Key must be
between {min} and {max} characters long.")
@NotEmpty(message = "Key is required.")
@Pattern(regexp = "[A-Za-z0-9_-]*", message = "Only
letters, numbers, underscores and hyphens may be used.")
private String key;
@Size(min = 4, max = 35, message = "Name must be
between {min} and {max} characters long.")
@NotEmpty(message = "Name is required.")
private String name;
@ValidDate
private Date acquisitionDate;
}
20. Setting Up And Using Bean
Validation
✦ As simple as @Valid
✦ Can be applied inside domain to validate child/2nd degree
objects
✦ BindingResult for errors
✦ MUST be argument after @Valid argument (quirk)
✦ RuntimeException if not
✦ toString() is your friend.
✦ Validating a large object graph is risky.
✦ Complicates future changes
✦ On the other hand, very hard to merge BindingResults
21. Updated Controller
@Controller
public class YachtController {
@RequestMapping(method = POST)
public ModelAndView saveYacht(@Valid Yacht yacht, BindingResult result,
ModelAndView mv) {
LOG.debug("Attempting to save Yacht: " + yacht.getKey());
if (result.hasErrors()) {
LOG.debug("Submission has errors " + result);
mv.setViewName(MODIFY_YACHT_VIEW);
return mv;
}
service.store(yacht);
FlashMap.setSuccessMessage("Successfully saved Yacht");
return createRedirect(yacht);
}
}
22. Handling Validation Errors
✦ Ensure that you have a BindingResult.hasErrors() check in @Controller
✦ When returning edit view, ensure all needed model attributes are
present
✦ This includes the object being validated if you construct a new Model/
MV
✦ You may need a call to the root-level form:errors in order to capture object-
level errors (not field-level).
✦ <form:errors path=“” />
✦ Be sure you interrupt flow so you don’t persist invalid objects
✦ VALIDATION ERRORS ARE NOT EXCEPTIONS
✦ There is a Hibernate option to validate pre-persist, but this is nuanced
✦ Legacy objects
✦ May be incompatible with Spring-managed dependencies
23. Writing Your Own Constraints
✦ Constraints can be combined and composed
✦ For example, @NotEmpty actually means @NotNull, @Size(min=1) &
@ReportAsSingleViolation
✦ Write an annotation class
✦ @Target(ElementType.FIELD)
✦ @Retention(RetentionPolicy.RUNTIME)
✦ @Documented (or not)
✦ @Constraint(validatedBy = YourCustomValidator.class)
✦ Be sure to add a default message
✦ These can be Java properties read from a file (Internationalization/
i18n)
✦ Write a validator
✦ Implement ConstraintValidator<AnnotationType, TargetClass>
✦ Return boolean for isValid(TargetClass object, …)
✦ Statefulness (via initialize() method)
✦ Dependency Injection, Constructors, and Hibernate ORM issue
24. Writing Unit Tests For Validation
Magic
✦ A lot of JSR-303 is wizardry and magic beans.
✦ Write unit tests so you ensure code execution is predictable.
✦ Easiest to write using Spring’s JUnit Test Runner
✦ Point to an application context field that contains
✦ <bean id="validator“
class="org.springframework.validation.beanvalidation.LocalVa
lidatorFactoryBean" />
✦ Ensure a JSR-303-compliant validator is on your test
classpath
✦ Eg, Hibernate Validator
25. Example Unit Test
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({ "classpath:your-persistence.xml",
"classpath:your-persistence-test.xml" })
public class YachtValidationTest {
@Autowired
private javax.validation.Validator validator;
private Yacht emptyYacht;
@Before
public void setUpEmptyYacht() {
emptyYacht = new Yacht();
}
@Test
public void theKeyFieldIsRequired() {
Set<ConstraintViolation<Yacht>> constraintViolations = validator
.validate(emptyYacht);
boolean containsYachtKeyViolation = false;
for (ConstraintViolation<Yacht> violation : constraintViolations) {
if ("key".equals(violation.getPropertyPath().toString())
&& violation.getMessageTemplate().contains("required")) {
containsYachtKeyViolation = true;
}
}
assertTrue(containsYachtKeyViolation);
}
}
26. The Guts of Spring MVC
✦ Method Return Types
✦ View Resolvers
✦ Exception Handling
✦ File Uploads
✦ Servlet-level code (if you have to)
✦ URL Rewriting
27. Controller Method Return Types
✦ @Controller methods can return many different types. Often:
✦ Model
✦ ModelAndView (use Model.setView(View) or Model.setViewName
(String))
✦ Distinction between View and View Name
✦ String for the view name to display
✦ Can return void if you’ve drank plenty of convention over configuration
koolaid.
✦ Dangerous if you refactor a lot (or the next person does).
✦ Methods can also be annotated with @RequestBody
✦ Used to return a value without going through MVC apparatus
✦ Generally bad form, but good for testing or mock apps.
✦ @ModelAttribute has the same two distinct meanings as
@ModelAttribute
28. View Resolvers
✦ What is a ViewResolver and how does this differ from a View Name?
✦ Apache Tiles and TilesViewResolver
✦ InternalResourceViewResolver (default)
✦ ContentNegotiatingViewResolver
✦ JSON
✦ XML (if needed)
✦ RSS/Atom
✦ And many, many others… ViewResolver overload….
29. View options and Apache Tiles
✦ Spring MVC is does not attempt to implement its own view layer
✦ JSTL and JSP/JSPX
✦ Apache Tiles is commonly used for page modularization and
templating
✦ org.springframework.web.servlet.view.tiles2.TilesViewResolver
✦ org.springframework.web.servlet.view.tiles2.TilesConfigurer
✦ Similar options are available for Freemarker, Velocity and others
✦ JSON can be rendered easily via MappingJacksonJsonView
✦ Don’t forget ContentNegotiatingViewResolver and ignoreAcceptHeaders
(true)
✦ Straight JSPs are possible too
✦ Convention over configuration, but often creates complexity/duplication
30. Handling Exceptions
✦ Implement a HandlerExceptionResolver for application-wide errors
public interface HandlerExceptionResolver {
ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex);
}
✦ @ExceptionHandler can annotate a method for @Controller-specific
exceptions, such as an IOException in a particular method. This only
applies to the controller in which it is declared.
✦ Consider adding web.xml <error-page> handlers too (if your exception
handler throws an exception, or if you think everything may break.)
✦ A separate error @Controller is a good idea, with minimal
dependencies
31. File Uploads
✦ Uses Commons File Upload
✦ org.springframework.web.multipart.commons.CommonsMultipartResolver
✦ Three easy steps (plus background Spring magic)
✦ Instantiate CommonsMultipartResolver in app context or @Config
✦ Add enctype=“multipart/form-data” to your <form> tag in your view
✦ Add a MultipartFile to your POST @Controller method argument
✦ Use @ModelAttribute for form submission specificity
✦ MultipartFile is your friend
✦ getBytes()*
✦ getContentType() – don’t forget about mime types!
✦ getOriginalFilename()
✦ transferTo(File dest)*
(* but IOException is not)
32. Burrowing Down To The
Servlets
✦ Sometimes it is necessary to write output
directly to servlets.
✦ Extend AbstractView
✦ In your controller, specify your custom
view
✦ ModelAndView.setViewName(String
name)
✦ ModelAndView.setView(View view)
✦ This is useful for user data in binary form,
such as images.
✦ In general, avoid this approach
@Component
public class ByteArrayView extends AbstractView {
public ByteArrayView() { }
@Override
protected final void renderMergedOutputModel(Map
model,
HttpServletRequest request, HttpServletResponse
response)
throws Exception {
byte[] bytes = (byte[]) model.get("data");
String contentType = (String) model.get
("contentType");
response.setContentType(contentType);
response.setContentLength(bytes.length);
ServletOutputStream out =
response.getOutputStream();
FileCopyUtils.copy(bytes, out);
}
}
33. URL Rewriting
✦ URL rewriting implemented as a servlet-level filter (web.xml):
✦ org.tuckey.web.filters.urlrewrite.UrlRewriteFilter
✦ Uses a urlrewrite.xml file, typically in the WEB-INF:
<rule>
<from>/sun/*</from>
<to>/oracle/$1</to>
</rule>
✦ Rules are standard regular expressions (java.util.regex.Pattern).
✦ Used extensively to separate dynamic content requests from static content
requests (eg, css, images, javascript).
✦ Can be used to route multiple different request paths to the same
DispatcherServlet instance (thereby conserving memory).
34. Demo
✦ Creating a new project from the Maven
archetype/template in STS IDE
✦ maven-tomcat-plugin, maven-jetty-plugin
✦ Live coding!