5. Annotation Config
Example
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://
www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/
beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/
spring-mvc-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/
context/spring-context-3.0.xsd">
<!--
Scans within the base package of the application for @Components to
configure as beans
-->
<context:component-scan base-package="com.tedpennings.bu.cs667" />
<mvc:annotation-driven />
</beans>
6. Annotation
Taxonomy
@Component and its specializations
@Service, @Repository
@Controller
@Configuration + @Bean
@Autowired
@Value
7. @Component
Example
@Component("rssView")
public class RomeRssView extends AbstractView {
@Override
protected void renderMergedOutputModel(Map model,
HttpServletRequest request, HttpServletResponse
response) throws Exception {
doStuff();
}
}
8. @Service Example
@Service
public class TwitterStatusProvider implements StatusProvider {
@Autowired
private CacheRepository cache;
@Override
public String getStatus(boolean skipCache) {
String status = cache.findString(CACHE_KEY);
if (skipCache || status == null) {
status = getStatusFromTwitter();
cache.cacheString(CACHE_KEY, status, DEFAULT_CACHE_EXPIRY);
}
return status;
}
}
9. @Configuration
Classes
Java classes that do the work of XML
Application Context files
Allow much more control over object creation
Instantiated before XML beans
@Configuration @Beans can be referenced by
XML beans/config
Can also reference XML beans
10. @Config Example
@Configuration
public class MongoConfiguration {
@Value("${db.host}")
private String dbHost;
@Value("${db.port}")
private int dbPort;
@Bean
@Lazy
public Mongo mongo() throws UnknownHostException {
return new Mongo(dbHost, dbPort);
}
}
11. MVC Changes
Controller interfaced dropped
@RequestMapping instead of XML config
@Controller instead of explicit XML config
Lots of return types possible
Simplicity
12. The Simplicity of
Controllers
@Controller makes a class a controller “bean”
Specialization of @Component
@RequestMapping defines the URL paths handled by a
class and/or method.
It is possible to nest paths, as in example on next slide.
Many different RequestMethods allowed in a @Controller
{} in @RM path define @PathVariable/@ReqParam
Value of Inheritance
Annotation caveat
13. @Controller Example
@Controller
@RequestMapping({ "/yacht", "/yachts", "/mah-boats" })
public class YachtController {
@Autowired
private YachtService service;
private static final String YACHT_PAGE_VIEW = "yachts/view";
@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. MVC Annotations
@Controller – an MVC Controller
@RequestMapping
@ModelAttribute
@RequestParam and @PathVariable
@SessionAttributes
16. 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
17. Bean Validation
(JSR-303)
Constraints are defined by @Entity
public class Yacht {
annotations on the actual data @Id
@GeneratedValue(strategy = GenerationType.AUTO)
entities private Long id;
Validation is executed @Size(min = 4, max = 35, message = "Key must be
between {min} and {max} characters long.")
automagically by framework @NotEmpty(message = "Key is required.")
@Pattern(regexp = "[A-Za-z0-9_-]*", message = "Only
letters, numbers, underscores and hyphens may be used.")
User-friendly errors appear private String key;
automagically on the view.
@Size(min = 4, max = 35, message = "Name must be
between {min} and {max} characters long.")
Object graphs can be traversed @NotEmpty(message = "Name is required.")
private String name;
and nested objects are validated
@ValidDate
(or not, if you wish). private Date acquisitionDate;
}
18. 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
19. 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);
}
}
20. 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
21. 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
22. 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.Loca
lValidatorFactoryBean" />
Ensure a JSR-303-compliant validator is on your test
classpath
Eg, Hibernate Validator
23. 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);
}
}
24. Even More
Annotations
JSR 250 Resource Management
JSR 299/330 Bean Injection
JPA!
27. JPA Annotations
@PersistenceContext / @PersistenceUnit
@Entity
@Column, @Id, @Enumerated, @ManyToOne, etc
Mixes well with Spring-tx and @Transactional
28. Basic JPA Configuration
in Spring 3.0
PersistenceAnnotationBeanPostProcessor for
@PersistenceContext/Unit EntityManagers
LocalContainerEntityManagerFactoryBean to
bootstrap JPA and read persistence.xml
Still need to configure provider, eg, Hibernate
Need to provide data source, either as a
constructed bean or JNDI reference
\nThe createRedirect shown is a hack to avoid nasty exposed model attributes. Credit for this code goes to David Ehringer:\n\n private ModelAndView createRedirect(Yacht yacht) {\n String yachtKey = yacht.getKey();\n RedirectView redirect = new RedirectView("/yacht/" + yachtKey, true);\n // We are doing this rather than simply returning the String version of\n // the view (i.e. redirect:/groups/groupKey) because in the current\n // version of Spring all the model attributes get appended to the URL\n // which is nasty.\n redirect.setExposeModelAttributes(false);\n return new ModelAndView(redirect);\n }\n
\n
\n
Possibilities for ESAPI!\n
You can also use <mvc:annotation-driven />, but this often registers two conflicting instances of javax.validation.Validator, and causing an ambiguous bean reference exception on the @Autowired injection line in this example. It is easier to explicitly define an instance as above.\n