1. 概述
Spring 4.3 發佈版本對核心容器、緩存、JMS、Web MVC 和測試子模塊等進行了優化改進。
在本篇博文中,我們將討論其中一些改進,包括:
- 隱式構造函數注入
- Java 8 默認接口方法支持
- 依賴解析改進
- 緩存抽象優化
- 組合的 @RequestMapping 變體
- @Requestscope、@Sessionscope、@Applicationscope 註解
- @RequestAttribute 和 @SessionAttribute 註解
- 對庫/應用服務器版本支持
- InjectionPoint 類
2. 隱式構造器注入
請考慮以下服務類:
@Service
public class FooService {
private final FooRepository repository;
@Autowired
public FooService(FooRepository repository) {
this.repository = repository
}
}這是一個相當常見的用例,但如果忘記在構造函數上添加 @Autowired 註解,容器將嘗試查找默認構造函數,除非您明確進行依賴注入。
從 4.3 版本開始,在單構造函數場景中,您不再需要顯式指定注入註解。這對於不攜帶任何註解的類尤其優雅:
public class FooService {
private final FooRepository repository;
public FooService(FooRepository repository) {
this.repository = repository
}
}在 Spring 4.2 及以下版本中,對於此 Bean 的以下配置將不起作用,因為 Spring 將無法找到 FooService 的默認構造函數。Spring 4.3 更智能,它會自動注入構造函數:
<beans>
<bean class="com.baeldung.spring43.ctor.FooRepository" />
<bean class="com.baeldung.spring43.ctor.FooService" />
</beans>同樣,你可能注意到,@Configuration 類歷史上不支持構造函數注入。從 4.3 版本開始,它們支持了構造函數注入,並且在單構造函數場景中,它們也允許省略 @Autowired:
@Configuration
public class FooConfiguration {
private final FooRepository repository;
public FooConfiguration(FooRepository repository) {
this.repository = repository;
}
@Bean
public FooService fooService() {
return new FooService(this.repository);
}
}3. Java 8 默認接口方法支持
在 Spring 4.3 之前,默認接口方法未得到支持。
這主要是因為即使 JDK 的 JavaBean 反射器也無法將默認接口方法識別為訪問器。自 Spring 4.3 版本起,Spring 能夠識別作為默認接口方法實現的 getter 和 setter,從而允許將其用於實例的預處理,例如在以下示例中:
public interface IDateHolder {
void setLocalDate(LocalDate localDate);
LocalDate getLocalDate();
default void setStringDate(String stringDate) {
setLocalDate(LocalDate.parse(stringDate,
DateTimeFormatter.ofPattern("dd.MM.yyyy")));
}
}
這個 Bean 現在可能已經注入了 stringDate 屬性:
<bean id="dateHolder"
class="com.baeldung.spring43.defaultmethods.DateHolder">
<property name="stringDate" value="15.10.1982"/>
</bean>同樣適用於使用測試註解,例如在默認接口方法上使用 @BeforeTransaction 和 @AfterTransaction。JUnit 5 已經支持在默認接口方法上使用測試註解,Spring 4.3 緊隨其後。現在,您可以將常見的測試邏輯抽象到一個接口中,並在測試類中實現它。這是一個用於測試用例的接口,它會在測試中的事務前後記錄消息:
public interface ITransactionalTest {
Logger log = LoggerFactory.getLogger(ITransactionalTest.class);
@BeforeTransaction
default void beforeTransaction() {
log.info("Before opening transaction");
}
@AfterTransaction
default void afterTransaction() {
log.info("After closing transaction");
}
}另一個改進是關於註解 @BeforeTransaction, @AfterTransaction 和 @Transactional 的,即取消了註解方法必須是 public 的要求——現在它們可以具有任何可見性級別。
4. 依賴解析改進
最新版本還引入了 ObjectProvider,它是現有 ObjectFactory 接口的擴展,並提供了方便的簽名,例如 getIfAvailable 和 getIfUnique,用於僅在對象存在或確定唯一候選對象時檢索 Bean(特別是,在多個匹配 Bean 的情況下,優先選擇主 Bean)。
@Service
public class FooService {
private final FooRepository repository;
public FooService(ObjectProvider<FooRepository> repositoryProvider) {
this.repository = repositoryProvider.getIfUnique();
}
}您可以使用這樣的 ObjectProvider 在初始化過程中進行自定義解析,如上所示,或者將句柄存儲在字段中,以便在需要時按需進行解析(通常與 ObjectFactory 類似)。
5. 緩存抽象優化
緩存抽象主要用於緩存 CPU 和 IO 消耗的值。 在特定用例中,相同的鍵可能會由多個線程(即客户端)並行請求,尤其是在啓動時。 同步緩存支持是長期 richiest 的功能,現在已被實現。 假設如下:
@Service
public class FooService {
@Cacheable(cacheNames = "foos", sync = true)
public Foo getFoo(String id) { ... }
}請注意 sync = true 屬性,它指示框架在值計算過程中阻止任何併發線程。這確保了在併發訪問的情況下,這個耗時操作僅在一次執行。
Spring 4.3 還改進了緩存抽象,如下所示:
- SpEL 表達式現在可以在與緩存相關的註解中引用 Bean(例如:@beanName.method())。
- ConcurrentMapCacheManager 和 ConcurrentMapCache 現在通過新的 storeByValue 屬性支持緩存條目的序列化。
- @Cacheable、@CacheEvict、@CachePut 和 @Caching 現在可以作為元註解,用於創建具有屬性覆蓋的自定義組合註解。
6. 組合 @RequestMapping 變體
Spring Framework 4.3 引入了 @RequestMapping 註解的組合變體,這些變體可以簡化對常用 HTTP 方法的映射,並更好地表達註解的處理器方法的語義。
- @GetMapping
- @PostMapping
- @PutMapping
- @DeleteMapping
- @PatchMapping
例如,@GetMapping 是説 @RequestMapping(method = RequestMethod.GET) 的簡短形式。 下面的示例展示了一個使用組合 @GetMapping 註解簡化後的 MVC 控制器。
@Controller
@RequestMapping("/appointments")
public class AppointmentsController {
private final AppointmentBook appointmentBook;
@Autowired
public AppointmentsController(AppointmentBook appointmentBook) {
this.appointmentBook = appointmentBook;
}
@GetMapping
public Map<String, Appointment> get() {
return appointmentBook.getAppointmentsForToday();
}
}7. `<em @RequestScope>, <em @SessionScope>, <em @ApplicationScope> 註解
當使用註解驅動的組件或 Java Config 時,<em @RequestScope</em>>, <em @SessionScope</em>> 和 <em @ApplicationScope</em>> 註解可用於將組件分配到所需的範圍。這些註解不僅設置了 Bean 的範圍,還設置了 scoped proxy 模式為 <em ScopedProxyMode.TARGET_CLASS</em>>。
<em ScopedProxyMode.TARGET_CLASS</em>> 模式意味着 CGLIB 代理將被用於該 Bean 的代理,並確保它可以被注入到任何其他 Bean 中,即使具有更廣泛的範圍。<em ScopedProxyMode.TARGET_CLASS</em>> 模式允許代理不僅用於接口,還用於類。
@RequestScope
@Component
public class LoginAction {
// ...
}@SessionScope
@Component
public class UserPreferences {
// ...
}@ApplicationScope
@Component
public class AppPreferences {
// ...
}8. <em @RequestAttribute</em>> 和 <em @SessionAttribute</em>> 註解
兩個註解用於將 HTTP 請求的參數注入到 <em Controller</em>> 方法中,即 <em @RequestAttribute</em>> 和 <em @SessionAttribute</em>>. 它們允許您訪問全局管理的一些預先存在的屬性(即不在 <em Controller</em>> 內部管理的屬性)。 這些屬性的值可以由註冊的 <em javax.servlet.Filter</em>> 或 <em org.springframework.web.servlet.HandlerInterceptor</em>> 實例提供。
假設我們註冊了以下 <em HandlerInterceptor</em>> 實現,該實現解析請求並向會話中添加 <em login</em>> 參數以及向請求中的 <em query</em>> 參數:
public class ParamInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
request.getSession().setAttribute("login", "john");
request.setAttribute("query", "invoices");
return super.preHandle(request, response, handler);
}
}這些參數可以注入到 Controller 實例中,並通過方法參數上的對應註解進行標記:
@GetMapping
public String get(@SessionAttribute String login,
@RequestAttribute String query) {
return String.format("login = %s, query = %s", login, query);
}9. 支持的庫/應用服務器版本
Spring 4.3 支持以下庫版本和服務器生成:
- Hibernate ORM 5.2 (同時支持 4.2/4.3 和 5.0/5.1 版本,現在 3.6 版本已棄用)
- Jackson 2.8 (Spring 4.3 之後,最低版本要求 Jackson 2.6+)
- OkHttp 3.x (與 OkHttp 2.x 並存,仍提供支持)
- Netty 4.1
- Undertow 1.4
- Tomcat 8.5.2 以及 9.0 M6
此外,Spring 4.3 內嵌了更新的 ASM 5.1 和 Objenesis 2.4 在 spring-core.jar 中。
10.
InjectionPoint 類是 Spring 4.3 引入的一個新類,它提供有關特定 Bean 被注入的位置的信息,無論這些位置是方法/構造函數參數還是字段。
您可以使用此類找到的信息類型如下:
- 字段對象 – 通過使用 getField() 方法,如果 Bean 被注入到字段中,可以獲取被注入的點包裝為 Field 對象
- 方法參數 – 通過調用 getMethodParameter() 方法,如果 Bean 被注入到參數中,可以獲取被注入的點包裝為 MethodParameter 對象
- 成員 – 調用 getMember() 方法將返回包含注入的 Bean 的實體,並將其包裝在 Member 對象中
- 類<?> – 使用 getDeclaredType() 方法獲取參數或字段中 Bean 被注入的聲明類型
- Annotation[] – 通過使用 getAnnotations() 方法,可以檢索一個 Annotation 對象數組,這些對象代表與字段或參數關聯的 Annotation
- AnnotatedElement – 調用 getAnnotatedElement() 以獲取被注入的點包裝為 AnnotatedElement 對象
此類在我們需要根據其所屬的類創建 Logger Bean 時非常有用:
@Bean
@Scope("prototype")
public Logger logger(InjectionPoint injectionPoint) {
return Logger.getLogger(
injectionPoint.getMethodParameter().getContainingClass());
}豆子必須使用 prototype作用域進行定義,以便為每個類創建一個不同的日誌記錄器。如果創建了 singleton 豆子並將其注入到多個位置,Spring 將返回遇到的第一個注入點。
然後,我們可以將豆子注入到 AppointmentsController 中:
@Autowired
private Logger logger;11. 結論
在本文中,我們探討了 Spring 4.3 中引入的一些新功能。
我們介紹了有用的註解,這些註解消除了冗餘代碼,以及新的依賴查找和注入方法,以及在 Web 和緩存功能方面的諸多重大改進。