Spring 4支持的Java 8新特性一覽
有眾多新特性和函數庫的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);}</pre>
Spring框架里的函數式接口:
public interface ConnectionCallback<T> { T doInConnection(Connection con) throws SQLException, DataAccessException;}public interface RowMapper<T>{ T mapRow(ResultSet rs, int rowNum) throws SQLException;}</pre>
在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;
}});</pre>
我們這么寫:
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;});</pre> <p> 我們注意到Java 8中這段代碼使用了lambda表達式,這比之前的版本中使用匿名內部類的方式緊湊、簡潔得多。</p>
涵蓋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(); }}</pre>
上面的例子中,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); }}</pre> <h2> Java 8的Optional<>與Spring 4.1</h2>
忘記檢查空值引用是應用代碼中一類常見的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);}</pre>
用如下有缺陷的代碼來調用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 }
}}</pre>
替換成:
public class MyService {@Autowired Optional<OtherService> otherService; public doSomething() { otherService.ifPresent( s -> { // use s to do something }); }}</pre> <p> 另一個能用Optional的地方是Spring MVC框架,可以用于表示某個處理方法的參數是可選的。例如:</p>
@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新特性的出色框架。
查看英文原文:Spring 4 and Java 8
來自:http://www.infoq.com/cn/articles/spring-4-java-8本文由用戶 dwd4 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!