spring 3: what's new
DESCRIPTION
TRANSCRIPT
Spring 3An overview of changes from version 2.5
Ted Pennings
8 December 2010
Overview of Changes
Annotations
Fewer interfaces
MVC overhaul
Dropped binary compatibility with Java 1.4
Java 5 generics, for(), autoboxing, etc
Spring 3 = AnnotationsSpring 3 emphasizes annotation config
@Component, @Configuration, etc
JSR 250 Common Annotations
JSR 299/330 Bean Injection
JSR 303 Bean Validation
Java Persistence API (JPA)
Enabling Annotation Configuration
<context:component-scan base-package=”com.foo.bar”>
<mvc:annotation-driven>
also, <aspectj:auto-proxy /> for @Aspects
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>
Annotation Taxonomy
@Component and its specializations
@Service, @Repository
@Controller
@Configuration + @Bean
@Autowired
@Value
@Component Example
@Component("rssView")public class RomeRssView extends AbstractView {
@Override protected void renderMergedOutputModel(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception { doStuff();
}
}
@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; }
}
@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
@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); }
}
MVC ChangesController interfaced dropped
@RequestMapping instead of XML config
@Controller instead of explicit XML config
Lots of return types possible
Simplicity
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 InheritanceAnnotation caveat
@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; }
}
MVC Annotations
@Controller – an MVC Controller
@RequestMapping
@ModelAttribute
@RequestParam and @PathVariable
@SessionAttributes
DemoAnnotation Config
Working forms
Bean validation (JSR-303)
Controller Method Return Types
@Controller methods can return many different types. Often:ModelModelAndView (use Model.setView(View) or Model.setViewName(String))
Distinction between View and View NameString 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
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 @ValidCan be applied inside domain to validate child/2nd degree objects
BindingResult for errorsMUST be argument after @Valid argument (quirk)RuntimeException if nottoString() is your friend.
Validating a large object graph is risky.Complicates future changesOn 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 @ControllerWhen returning edit view, ensure all needed model attributes are presentThis 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 objectsVALIDATION ERRORS ARE NOT EXCEPTIONS
There is a Hibernate option to validate pre-persist, but this is nuancedLegacy objectsMay 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 validatorImplement 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 RunnerPoint 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); }}
Even More Annotations
JSR 250 Resource Management
JSR 299/330 Bean Injection
JPA!
JSR 250 Resource Management
@Resource (typically fields)
@PostConstruct (method)
Replaces InitializingBean + afterPropertiesSet()
@PreDestroy (method)
Replaces DisposableBean + destroy()
Bean Injection JSRjavax.inject package:
@Inject (equivalent to @Autowired)
@Qualifier (to resolve ambiguities)
@Named
@Scope
@Singleton
JPA Annotations
@PersistenceContext / @PersistenceUnit
@Entity
@Column, @Id, @Enumerated, @ManyToOne, etc
Mixes well with Spring-tx and @Transactional
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
Hibernate as JPA Provider
HibernateJpaVendorAdapter + Properties <bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> <property name="showSql" value="true" /> <property name="generateDdl" value="true" /> <property name="databasePlatform" value="org.hibernate.dialect.MySQL5Dialect" /> </bean>
<bean id="hibernateProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean"> <property name="locations"> <list> <value>classpath:hibernate.properties</value> </list> </property> </bean>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/yourdb" /> <property name="username" value="user" /> <property name="password" value="pass" /> </bean>
hibernate.properties:
hibernate.hbm2ddl.auto=updatehibernate.connection.autocommit=truehibernate.show.sql=truehibernate.generate_statistics=truejavax.persistence.validation.mode=ddlhibernate.dialect=org.hibernate.dialect.MySQL5Dialect
Resources
http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/
http://static.springsource.org/spring/docs/3.0.x/javadoc-api/
Spring In Action, 3rd Edition by Craig Walls (Manning). Excellent survey.
Questions?
http://ted.pennin.gs /[email protected]
@thesleepyvegan on twitter
not always Java related