Spring 4
06 Dec 2014A bit of history
The Spring Framework was first released in 2004; since then there have been significant major revisions:
- Spring 2.0 provided XML namespaces and AspectJ support
- Spring 2.5 embraced annotation-driven configuration
- Spring 3.0 introduced a strong Java 5+ foundation across the framework codebase, and features such as the Java-based @Configuration model.
Version 4.0 is the latest major release of the Spring Framework and the first to fully support Java 8 features. You can still use Spring with older versions of Java, however, the minimum requirement has now been raised to Java SE 6. We have also taken the opportunity of a major release to remove many deprecated classes and methods.
Highlights
- Java 8
- Conditional bean definitions
- Autowiring with Generics
- New baseline (JDK6) & deprecated stuff drops
- Spec upgrades: JMS 2.0, JTA 1.2, JPA 1.2, BeanValidation 1.1, JSR-236 Concurrency
- Lambda support taking place of annonymous function blocks
- Repeatable annotations (only possible with JDK8)
- Spring formatting framework supporting new Java Date & Time API
- Messaging and WebSocket support
- Testing annotations as Meta Annotations
Java 8 support
- Support for lambda expressions & method references with Spring callback interfaces.
- First class support to java.time
- JSR-310 Date and Time
- Repeatable annotations
- Parameter name discovery
- Compatibility with Java 6 & 7 maintained.
Conditional
Scenario: If ‘servicedefault’ environment variable is set, use Impl1
otherwise use Impl2
Using @Configuration
@Configuration
public static class ContextConfig {
@Bean
public CustomerService customerService() {
if (System.getProperty("servicedefault")!=null) return new CustomerServiceImpl1();
return new CustomerServiceImpl2();
}
}
Using BeanProfiles (Spring 3.1)
@Bean
@Profile("default")
public CustomerService service1() {
return new CustomerServiceImpl1();
}
@Bean
@Profile("prod")
public CustomerService service2() {
return new CustomerServiceImpl2();
}
Using @Conditional. Depends on a set of Condition
classes to specify the predicates.
class HardCodedPropertyPresentCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return (System.getProperty("servicedefault") != null);
}
}
class HardCodedPropertyAbsentCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return (System.getProperty("servicedefault") == null);
}
}
@Bean
@Conditional(HardCodedPropertyPresentCondition.class)
public CustomerService service1() {
return new CustomerServiceImpl1();
}
@Bean
@Conditional(HardCodedPropertyAbsentCondition.class)
public CustomerService service2() {
return new CustomerServiceImpl2();
}
Testing meta annotations
If we discover that we are repeating the following configuration across our JUnit-based test suite:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public class OrderRepositoryTests { }
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public class UserRepositoryTests { }
We can reduce it by introducing a custom composed annotation:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public @interface TransactionalDevTest { }
Then we can use our custom @TransactionalDevTest annotation to simplify the configuration of individual test classes:
@RunWith(SpringJUnit4ClassRunner.class)
@TransactionalDevTest
public class OrderRepositoryTests { }
@RunWith(SpringJUnit4ClassRunner.class)
@TransactionalDevTest
public class UserRepositoryTests { }
Groovy Bean Definition DSL
- Bean configuration using Groovy DSL
Sample:
def reader = new GroovyBeanDefinitionReader(myApplicationContext)
reader.beans {
dataSource(BasicDataSource) {
driverClassName = "org.hsqldb.jdbcDriver"
url = "jdbc:hsqldb:mem:grailsDB"
username = "sa"
password = ""
settings = [mynew:"setting"]
}
sessionFactory(SessionFactory) {
dataSource = dataSource
}
myService(MyService) {
nestedBean = { AnotherBean bean ->
dataSource = dataSource
}
}
}
Removed deprecated stuff
Generics-based injection matching
- Spring 3.x ignores the generic type.
- Readability + Expressiveness
@Service
public class MyBookAdminService implements BookAdminService{
@Autowired
public MyBookAdminService(MyRepository<Account> repo){
...
}
}
@Bean
public MyRepository<Account> myAccountRepository(){
return new MyAccountRepositoryImpl();
}
Container improvements
- Composable annotation with overridable attributes (e.g. custom scope annotation with proxyModule attribute)
- Conditional bean definitions
@Conditional
(e.g. Enable for specific profiles). Foundation for Spring Boot @Autowired
and@Lazy
on injection points - Requesting a lazy-initialization proxy individually per injection point- Target-class proxies for classes with arbitrary constructors - CGLib proxies using Objenesis, not invoking any constructor
WebSocket, SockJS, and STOMP Messaging
org.springframework.messaging
module
- Message and Channels abstraction
- Endpoints using generic messaging patterns
Websockets support on Spring MVC:
- Support for raw WebSocket handling
- Flexible endpoints through native server support (Tomcat 7/8, Jetty 9, Glassfish 4, Wildfly 8)
- Transparent SockJS fallback option
- STOMP for higher-level messaging on top of WebSocket channel
Spring API examples
JDBCTemplate
- PreparedStatementSetter
void setValues(PreparedStatement ps) throws SQLException
- RowMapper
Object mapRow(ResultSet rs, int rowNum) throws SQLException
Lambda
// V1 - Inline lambda style
JdbcTemplate jt = new JdbcTemplate(datasource);
jt.query(
"SELECT name, age FROM person WHERE department = ?",
ps -> ps.setString(1, "Sales"),
(rs, rowNum) -> new Person(rs.getString(1), rs.getInt(2))
)
// V2 style
JdbcTemplate jt = new JdbcTemplate(datasource);
jt.query(
"SELECT name, age FROM person WHERE department = ?",
ps -> {
ps.setString(1, "Sales")
},
(rs, rowNum) -> {
new Person(rs.getString(1), rs.getInt(2))
}
)
Method reference
public List<Person> getPersonList(String department){
JdbcTemplate jt = new JdbcTemplate(datasource);
jt.query(
"SELECT name, age FROM person WHERE department = ?",
ps -> ps.setString(1, "Sales"),
this::mapPerson)
)
}
Person mapPerson(ResultSet rs, int rowNum) throws SQLException{
return new Person(rs.getString(1), rs.getInt(2));
}
Date and Time
- Spring formatting framework supports java 8 date and time (JSR-310)
import java.time.*;
import org.springframework.format.annotation.*;
public class Customer{
// @DateTimeFormat(iso=ISO.DATE)
private LocalDate birthDate;
@DateTimeFormat(pattern="M/d/yy h:mm")
private LocalDate lastContact;
...
}
Repeatable annotations
// java 7
@Schedules({
@Scheduled(cron = "0 0 12 * * ?"),
@Scheduled(cron = "0 0 18 * * ?")
})
public void performTempFileCleanup(){ ... }
// java 8
@Scheduled(cron = "0 0 12 * * ?")
@Scheduled(cron = "0 0 18 * * ?")
public void performTempFileCleanup(){ ... }
Parameter name discovery
- Advanced reflection support
- Parameter names on interfaces
- Spring’s
DefaultParameterNameDiscoverer
- ASM (bytecode manipulation based)
- Java 8 parameter reflection
@Controller
public class MyMvcController{
@RequestMapping(value="/books/{id}", method="GET")
public Book findBook(){
return this.bookAdminService.findBook(id);
}
}
Web improvements
spring-websocket
modulespring-messaging
module adds support for STOMP
Testing Improvements
- Almost all annotations in the spring-test module can be used as meta-annotations to create composed annotations (reduce configuration duplication) (e.g.,
@ContextConfiguration
,@WebAppConfiguration
,@ContextHierarchy
,@ActiveProfiles
, etc.) - Active Bean definition profiles can be resolved programatically implementing a custom
ActiveProfilesResolver
and registering it via theresolver
attribute of@ActiveProfiles
- New
SocketUtils
class was introduced tospring-core
. Enables scan free TCP / UDP ports on localhost. org.springframework.mock.web
based on the Servlet 3.0 API (e.g.,MockHttpServletRequest
,MockServletContext
, etc.)
Reference
- http://docs.spring.io/spring/docs/current/spring-framework-reference/html/new-in-4.0.html
- http://www.infoq.com/presentations/spring-4-java8
- http://dsyer.com/presos/decks/spring4.html
- http://www.javacodegeeks.com/2013/10/spring-4-conditional.html
- http://docs.spring.io/spring/docs/current/spring-framework-reference/html/testing.html#integration-testing-annotations-meta