36
Spring MVC Introduction / Gore Ted Pennings NHJUG Co-Founder 16 November 2010

Spring MVC Intro / Gore - Nov NHJUG

Embed Size (px)

DESCRIPTION

A Spring MVC overview, followed by the important guts and gore.

Citation preview

Page 1: Spring MVC Intro / Gore - Nov NHJUG

Spring MVCIntroduction / Gore

Ted PenningsNHJUG Co-Founder

16 November 2010

Page 2: Spring MVC Intro / Gore - Nov NHJUG

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 / [email protected]

Page 3: Spring MVC Intro / Gore - Nov NHJUG

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.

Page 4: Spring MVC Intro / Gore - Nov NHJUG

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.

Page 5: Spring MVC Intro / Gore - Nov NHJUG

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

Page 6: Spring MVC Intro / Gore - Nov NHJUG

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();

}

Page 7: Spring MVC Intro / Gore - Nov NHJUG

Speaking of Bad Things

Oracle CEO Larry Ellison

Page 8: Spring MVC Intro / Gore - Nov NHJUG

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”; }

Page 9: Spring MVC Intro / Gore - Nov NHJUG

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”

Page 10: Spring MVC Intro / Gore - Nov NHJUG

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

Page 11: Spring MVC Intro / Gore - Nov NHJUG

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

Page 12: Spring MVC Intro / Gore - Nov NHJUG

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

Page 13: Spring MVC Intro / Gore - Nov NHJUG

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; }

}

Page 14: Spring MVC Intro / Gore - Nov NHJUG

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.

Page 15: Spring MVC Intro / Gore - Nov NHJUG

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

Page 16: Spring MVC Intro / Gore - Nov NHJUG

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

Page 17: Spring MVC Intro / Gore - Nov NHJUG

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>

Page 18: Spring MVC Intro / Gore - Nov NHJUG

Demo✦ Working forms

✦ Bean validation

✦ Error messages appear in browser automagically

Page 19: Spring MVC Intro / Gore - Nov NHJUG

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).

@Entitypublic 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;

}

Page 20: Spring MVC Intro / Gore - Nov NHJUG

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

Page 21: Spring MVC Intro / Gore - Nov NHJUG

Updated Controller@Controllerpublic 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);}

}

Page 22: Spring MVC Intro / Gore - Nov NHJUG

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

Page 23: Spring MVC Intro / Gore - Nov NHJUG

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

Page 24: Spring MVC Intro / Gore - Nov NHJUG

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.LocalValidatorFactoryBean" />

✦ Ensure a JSR-303-compliant validator is on your test classpath✦ Eg, Hibernate Validator

Page 25: Spring MVC Intro / Gore - Nov NHJUG

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); }}

Page 26: Spring MVC Intro / Gore - Nov NHJUG

The Guts of Spring MVC✦ Method Return Types

✦ View Resolvers

✦ Exception Handling

✦ File Uploads

✦ Servlet-level code (if you have to)

✦ URL Rewriting

Page 27: Spring MVC Intro / Gore - Nov NHJUG

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

Page 28: Spring MVC Intro / Gore - Nov NHJUG

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….

Page 29: Spring MVC Intro / Gore - Nov NHJUG

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

Page 30: Spring MVC Intro / Gore - Nov NHJUG

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

Page 31: Spring MVC Intro / Gore - Nov NHJUG

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)

Page 32: Spring MVC Intro / Gore - Nov NHJUG

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

@Componentpublic 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); }}

Page 33: Spring MVC Intro / Gore - Nov NHJUG

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).

Page 34: Spring MVC Intro / Gore - Nov NHJUG

Demo

✦ Creating a new project from the Maven archetype/template in STS IDE

✦ maven-tomcat-plugin, maven-jetty-plugin

✦ Live coding!

Page 35: Spring MVC Intro / Gore - Nov NHJUG

References

✦ Spring Docs (3.0.x)Manual: http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/

JavaDocs: http://static.springsource.org/spring/docs/3.0.x/javadoc-api/

✦ Maven Jetty Plugin (mvn jetty:run)http://docs.codehaus.org/display/JETTY/Maven+Jetty+Plugin

✦ Miscellaneous✦ Spring Roo Keynote

http://www.youtube.com/watch?v=GQHlhIIxCIc

Page 36: Spring MVC Intro / Gore - Nov NHJUG

Questions? Comments?