Upload
ted-pennings
View
3.740
Download
3
Embed Size (px)
DESCRIPTION
A Spring MVC overview, followed by the important guts and gore.
Citation preview
Spring MVCIntroduction / Gore
Ted PenningsNHJUG Co-Founder
16 November 2010
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]
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.
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.
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
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();
}
Speaking of Bad Things
Oracle CEO Larry Ellison
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”; }
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”
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
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
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
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; }
}
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.
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
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
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>
Demo✦ Working forms
✦ Bean validation
✦ Error messages appear in browser automagically
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;
}
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
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);}
}
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
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
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
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); }}
The Guts of Spring MVC✦ Method Return Types
✦ View Resolvers
✦ Exception Handling
✦ File Uploads
✦ Servlet-level code (if you have to)
✦ URL Rewriting
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
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….
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
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
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)
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); }}
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).
Demo
✦ Creating a new project from the Maven archetype/template in STS IDE
✦ maven-tomcat-plugin, maven-jetty-plugin
✦ Live coding!
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
Questions? Comments?