Spring 4支援的Java 8新特性一覽
轉載於:http://www.infoq.com/cn/articles/spring-4-java-8?utm_source=infoq&utm_medium=related_content_link&utm_campaign=relatedContent_articles_clk
有眾多新特性和函式庫的Java 8釋出之後,Spring 4.x已經支援其中的大部分。有些Java 8的新特性對Spring無影響,可以直接使用,但另有些新特性需要Spring的支援。本文將帶您瀏覽Spring 4.0和4.1已經支援的Java 8新特性。
Spring 4支援Java 6、7和8
Java 8編譯器編譯過的程式碼生成的.class檔案需要在Java 8或以上的Java虛擬機器上執行。由於Spring對反射機制和ASM、CGLIB等位元組碼操作函式庫的重度使用,必須確保這些函式庫能理解Java 8生成的新class檔案。因此Spring將ASM、CGLIB等函式庫通過jar jar(https://code.google.com/p/jarjar/)嵌入Spring框架中,這樣Spring就可以同時支援Java6、7和8的位元組碼程式碼而不會觸發執行時錯誤。
Spring框架本身是由Java 8編譯器編譯的,編譯時使用的是生成Java 6位元組碼的編譯命令選項。因此你可以Java6、7或者8來編譯執行Spring 4.x的應用。
Spring和Java 8的Lambda表示式
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或更早的JDK編譯的、接受函式式介面作為引數的方法中。
Spring的程式碼裡有很多函式式介面,因此lambda表示式很容易與Spring結合使用。即便Spring框架本身被編譯成Java 6的.class檔案格式,你仍然可以用Java 8的lambda表示式編寫應用程式碼、用Java 8編譯器編譯、並且在Java 8虛擬機器上執行,你的應用可以正常工作。
總之,因為Spring框架早在Java 8正式給函式式介面下定義之前就已經實際使用了函式式介面,因此在Spring裡使用lambda表示式非常容易。
Spring 4和Java 8的時間與日期API
Java開發者們一直痛恨java.util.Date類的設計缺陷,終於,Java 8帶來了全新的日期與時間API,解決了那些久被詬病的問題。這個新的日期與時間API值得用一整篇文章的篇幅來講述,因此我們在本文不會詳述其細節,而是重點關注新的java.time包中引入的眾多新類,如LocalDate、LocalTime和 LocalDateTime。
Spring有一個數據轉換框架,它可以使字串和Java資料型別相互轉換。Spring 4升級了這個轉換框架以支援Java 8日期與時間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等也能支援它。到本文發表時,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
忘記檢查空值引用是應用程式碼中一類常見的bug來源。消除NullPointerExceptions的方式之一是確保方法總是返回一個非空值。例如如下方法:
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(); // 得到空指標錯誤
這段程式碼的正確寫法應該是:
Customer customer = customerRepository.findCustomerById(“123”);if(customer != null) {
customer.getName(); // 避免空指標錯誤
}
理想狀態下,如果我們沒有檢查某個值能否為空,我們希望編譯器及時發現。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的關鍵點在於確保開發者不用查閱Javadoc就能知道某個方法可以返回null,或者可以把一個null值傳給某方法。編譯器和方法簽名有助於開發者明確知道某個值是Optional型別。關於Optional類思想的詳細描述請參考這裡。
Spring 4.1有兩種方式支援Java Optional。Spring的@Autowired註解有一個屬性"required",使用之後我們可以把如下程式碼:
@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類通過減少空指標錯誤相關的缺陷簡化了程式碼編寫,同時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釋出之前,Spring也可以從使用-debug選項編譯之後的程式碼中提取出引數名。
在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”),這是因為findByLastname是個抽象方法,而在Java 7及之前的版本里就算用了-debug選項也不會保留其引數名。而在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表示式,使程式碼更為簡潔易讀。
Java 8對某些庫做了改進,比如新的java.time包和Optional類,Optional類使得用Spring編寫的程式碼更加簡單明瞭。
最後,用–parameters選項編譯Java 8程式碼會在編譯時保留方法的引數名,使得開發者得以編寫更為緊湊的Spring MVC方法和Spring Data查詢方法。
如果你已經準備在專案中使用Java 8,你會發現Spring 4是個很好地利用了Java 8新特性的出色框架。