Java 8附带了新的语言功能和库,而Spring 4.x已经支持其中的许多功能。 Java 8的某些新功能不会对Spring产生影响,只能按原样使用,而其他Java 8功能则需要Spring明确支持它们。 本文将向您介绍Spring 4.0和4.1支持的Java 8新功能。
Spring 4支持Java 6、7和8
使用Java 8编译器编译的代码会生成.class文件,这些文件至少需要运行Java 8虚拟机。 由于Spring大量使用反射和其他字节码操作库(例如ASM,CGLIB等),因此确保这些库能够理解新的Java 8类文件非常重要。 因此,Spring现在使用jar jar(https://code.google.com/p/jarjar/)将ASM,CGLIB和其他库嵌入到Spring框架中,以便Spring拥有一组可以与Java 6一起使用的可预测库。 7和8字节代码,而不会触发运行时错误。
使用带有命令行选项的Java 8编译器来编译Spring框架本身,以产生Java 6字节代码。 因此,您可以使用Java 6、7或8编译和运行Spring 4.x应用程序。
Spring和Java 8 Lambda表达式
Java 8的设计人员希望确保其向后兼容,以便Java 8 lambda表达式可与以前版本编译的代码一起使用。 通过定义功能接口的概念实现了向后兼容性。
基本上,Java 8设计人员分析了现有的Java代码主体,并注意到大多数Java程序员使用具有单一方法的接口来表示函数的思想。 例如,以下是JDK和Spring中仅使用一种方法的接口列表,这些接口被称为“功能接口”。
JDK功能接口:
public interface Runnable {
public abstract void run();
}
public interface Comparable<T> {
public int compareTo(T o);
}
Spring框架功能接口:
public interface ConnectionCallback<T> {
T doInConnection(Connection con) throws SQLException, DataAccessException;
}
public interface RowMapper<T>
{
T mapRow(ResultSet rs, int rowNum) throws SQLException;
}
在Java 8中,可以在将函数接口传递给方法或从方法返回的任何地方使用lambda表达式。例如,Spring JdbcTemplate类包含具有以下签名的方法
public <T> List<T> query(String sql, RowMapper<T> rowMapper)
throws DataAccessException
请注意,查询方法的第二个参数需要RowMapper接口的实例。 使用Java 8,我们可以编写一个lambda表达式作为查询方法第二个参数的值。
而不是像这样编写代码:
jdbcTemplate.query("SELECT * from products", new RowMapper<Product>(){
@Override
public Product mapRow(ResultSet rs, int rowNum) throws SQLException {
Integer id = rs.getInt("id");
String description = rs.getString("description");
Integer quantity = rs.getInt("quantity");
BigDecimal price = rs.getBigDecimal("price");
Date availability = rs.getDate("available_date");
Product product = new Product();
product.setId(id);
product.setDescription(description);
product.setQuantity(quantity);
product.setPrice(price);
product.setAvailability(availability);
return product;
}
});
我们编写如下代码:
jdbcTemplate.query("SELECT * from queries.products", (rs, rowNum) -> {
Integer id = rs.getInt("id");
String description = rs.getString("description");
Integer quantity = rs.getInt("quantity");
BigDecimal price = rs.getBigDecimal("price");
Date availability = rs.getDate("available_date");
Product product = new Product();
product.setId(id);
product.setDescription(description);
product.setQuantity(quantity);
product.setPrice(price);
product.setAvailability(availability);
return product;
});
请注意,此代码的Java 8版本正在使用lambda表达式,这比使用匿名内部类的先前版本中的代码更加紧凑和简洁。
涵盖Java 8功能接口的所有详细信息超出了本文的范围,强烈建议您在其他地方学习功能接口的详细信息。 但是,关键要点在于,如果方法接受功能接口,则可以将Java 8 lambda表达式传递给使用Java 7和更早版本编译的方法。
Spring代码库包含许多功能接口,因此lambda表达式可轻松用于Spring。 即使将Spring框架本身编译为Java 6 .class文件格式,您仍然可以使用Java 8 lambda表达式编写应用程序代码,使用Java 8编译器对其进行编译,然后在Java 8 VM上运行它就可以了。
总之,由于在Java 8正式定义功能接口之前,Spring框架一直在使用功能接口,因此在Spring中使用lambda表达式很容易。
Spring 4和Java 8 Time and Date API
Java开发人员长期以来一直抱怨java.util.Date类的设计缺陷,最后,对于Java 8,提供了一个全新的Date and Time API,可以解决这些长期存在的问题。 新的日期和时间API是对得起它的自己的文章 ,因此我们不会掩盖它比值得注意的是,新java.time包引入了许多新的类,如LOCALDATE的,本地时间和LocalDateTime其他细节。
Spring附带了一个数据转换框架,该框架可以将字符串从Java数据类型转换为Java数据类型。 Spring 4更新了转换框架,以支持Java 8 Date and Time API中的所有类。 因此,您可以编写这样的代码。
@RestController
public class ExampleController {
@RequestMapping("/date/{localDate}")
public String get(@DateTimeFormat(iso = ISO.DATE) LocalDate localDate)
{
return localDate.toString();
}
}
请注意,在上面的示例中,get方法的参数为Java 8中的LocalDate类型,Spring 4将能够采用诸如2014-02-01之类的字符串并将其转换为Java 8 LocalDate的实例。
重要的是要注意,Spring常与其他库一起使用,例如Hibernate用于数据持久性,而Jackson用于将Java对象转换为JSON。
尽管Spring 4支持Java 8日期和时间库,但这并不意味着像Hibernate和Jackson这样的第三方框架都能够支持Java 8日期和时间。 在发布时,Hibernate JIRA包含一个开放票证HHH-8844,请求在Hibernate中支持Java 8日期和时间API。 。
Spring 4和重复注释
Java 8添加了对重复注释的支持,而Spring 4支持这些注释。 特别是Spring 4支持重复@Scheduled和@PropertySource批注。 例如,请注意下面的代码片段中重复的@PropertySource批注。
@Configuration
@ComponentScan
@EnableAutoConfiguration
@PropertySource("classpath:/example1.properties")
@PropertySource("classpath:/example2.properties")
public class Application {
@Autowired
private Environment env;
@Bean
public JdbcTemplate template(DataSource datasource) {
System.out.println(env.getProperty("test.prop1"));
System.out.println(env.getProperty("test.prop2"));
return new JdbcTemplate(datasource);
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Java 8 Optional <>和Spring 4.1
忘记检查空引用是应用程序代码中错误的常见来源。 消除NullPointerExceptions的一种方法是确保方法始终返回非null值。 例如,考虑如下方法:
public interface CustomerRepository extends CrudRepository<Customer, Long> {
/**
* returns the customer for the specified id or
* null if the value is not found
*/
public Customer findCustomerById(String id);
}
CustomerRepository的用户可以编写如下错误代码:
Customer customer = customerRepository.findCustomerById(“123”);
customer.getName(); // get a null pointer exception
而不是正确的版本,如下所示:
Customer customer = customerRepository.findCustomerById(“123”);
if(customer != null) {
customer.getName(); // avoid a null pointer exception
}
理想情况下,如果我们不检查值是否可以为null,我们希望编译器抱怨。 java.util.Optional类使编写这样的接口成为可能。
public interface CustomerRepository extends CrudRepository<Customer, Long> {
public Optional<Customer> findCustomerById(String id);
}
因此,错误的代码版本将不再编译,程序员必须通过编写这样的代码来显式检查Optional是否包含值。
Optional<Customer> optional = customerRepository.findCustomerById(“123”);
if(optional.isPresent()) {
Customer customer = optional.get();
customer.getName();
}
因此,Optional的关键思想是确保程序员知道方法可以返回null,或者可以在无需读取Javadoc的情况下将null值传递给方法。 方法签名和编译器将有助于使您清楚该值是Optional。 找到有关Optional类的更详细的描述 在这里 。
Spring 4.1通过两种方式支持Java Optional。 Spring @Autowired批注包含“必需”属性,因此诸如
@Service
public class MyService {
@Autowired(required=false)
OtherService otherService;
public doSomething() {
if(otherService != null) {
// use other service
}
}
}
可以替换为
public class MyService {
@Autowired
Optional<OtherService> otherService;
public doSomething() {
otherService.ifPresent( s -> {
// use s to do something
});
}
}
可以使用Optional的另一个地方是Spring MVC,它可以指示处理程序方法参数是可选的。 例如:
@RequestMapping(“/accounts/{accountId}”,requestMethod=RequestMethod.POST)
void update(Optional<String> accountId, @RequestBody Account account)
会告诉Spring accountId是可选的。
综上所述,Java 8 Optional类使编写代码变得更加容易,同时减少了NullPointerException错误,并且Spring与Java 8 Optional类可以很好地协同工作。
参数名称发现模型
Java 8增加了对在已编译代码中保留方法参数名称的支持。 这意味着Spring 4可以从方法中提取参数的名称,这使SpringMVC代码更加紧凑。 例如:
@RequestMapping("/accounts/{id}")
public Account getAccount(@PathVariable("id") String id)
可以写成
@RequestMapping("/accounts/{id}")
public Account getAccount(@PathVariable String id)
注意,我们将@PathVariable(“ id”)替换为@PathVariable,因为Spring 4可以从编译的Java 8代码中获取参数名称id。 如果使用–parameters标志调用,则Java 8编译器会将参数名称写到.class文件中。 在Java 8之前的版本中,如果代码是使用–debug选项编译的,则Spring能够从编译的代码中提取参数名称。
对于Java 7和更早版本,–debug选项不会在抽象方法上保留参数名称。 因此,对于像Spring Data这样基于Java接口自动生成存储库实现的项目,这会引起问题。 例如,考虑下面的界面。
interface CustomerRepository extends CrudRepository<Customer, Long> {
@Query("select c from Customer c where c.lastname = :lastname")
List<Customer> findByLastname(@Param("lastname") String lastname);
}
请注意,在findByLastname签名中需要@Param(“ lastname”),因为即使使用–debug Java 7及更早版本,由于findByLastname是抽象方法,因此也不会保留参数名称。 借助Java 8 –parameters选项,Spring Data能够发现抽象方法上的参数名称,因此我们可以将接口重写为。
interface CustomerRepository extends CrudRepository<Customer, Long> {
@Query("select c from Customer c where c.lastname = :lastname")
List<Customer> findByLastname(String lastname);
}
请注意,我们不再需要@Param(“ lastname”)来使我们的代码更简洁,更易于阅读。 因此,在使用Java 8时,最好使用–parameters标志来编译代码。
结论
Spring 4在Java 6、7和8上运行,作为开发人员,您可以使用Java 6、7、8中的任何一种编写应用程序代码。如果您使用Java 8,则可以使用lambda表达式编写更简洁的代码。有功能接口的任何地方都可以使用更紧凑的代码。 由于Spring使用了大量的功能接口,因此在Spring 4中使用lambda表达式的机会很多。
Java 8附带了改进的库,例如新的java.time包和Spring能够使用的Optional类,使编写清晰/简单的代码变得更加容易。
最后,使用–parameters选项编译Java 8代码将保留方法中的参数名称,并使编写更紧凑的Spring MVC方法处理程序和Spring Data查询方法成为可能。
如果您准备开始在项目中使用Java 8,您会发现Spring 4是一个很好的框架,可以利用Java 8的功能。