1. 引言
在本快速教程中,我們將探討 Spring 框架中通過 @Lookup 註解提供的基於方法的依賴注入支持。
2. 為什麼使用 >?
標註了 的方法告訴 Spring 在調用該方法時,返回其返回類型的實例。
本質上,Spring 會覆蓋我們標註的方法,並使用其返回類型和參數作為 BeanFactory#getBean 的參數。
@Lookup 有以下用途:
- 將原型作用域的 Bean 注入到單例 Bean 中(類似於 )
- 程序化注入依賴
此外,@Lookup 是 Java 中 XML 元素的對應項。
3. 使用 @Lookup 標籤
3.1. 將原型作用域的 Bean 注入到單例 Bean 中
如果我們決定擁有一個原型作用域的 Spring Bean,那麼我們幾乎立刻就會面臨一個問題:我們的單例 Spring Bean 將如何訪問這些原型作用域的 Spring Bean?
現在,Provider 確實是一種方法,但 @Lookup 在某些方面更具靈活性。
首先,讓我們創建一個原型 Bean,稍後將其注入到單例 Bean 中:
@Component
@Scope("prototype")
public class SchoolNotification {
// ... prototype-scoped state
}如果創建一個使用 @Lookup</em/> 的單例 Bean,則:
@Component
public class StudentServices {
// ... member variables, etc.
@Lookup
public SchoolNotification getNotification() {
return null;
}
// ... getters and setters
}使用 @Lookup,我們可以通過我們的單例 Bean 獲取 SchoolNotification 實例:
@Test
public void whenLookupMethodCalled_thenNewInstanceReturned() {
// ... initialize context
StudentServices first = this.context.getBean(StudentServices.class);
StudentServices second = this.context.getBean(StudentServices.class);
assertEquals(first, second);
assertNotEquals(first.getNotification(), second.getNotification());
}請注意,在 StudentServices 中,我們保留了 getNotification 方法為佔位符。
這是因為 Spring 通過調用 beanFactory.getBean(StudentNotification.class) 覆蓋了該方法,因此我們可以將其留空。
3.2. 流程注入依賴
更強大的是,@Lookup 允許我們流程注入依賴,這與 Provider 無法實現的功能不同。
讓我們為 StudentNotification 添加一些狀態:
@Component
@Scope("prototype")
public class SchoolNotification {
@Autowired Grader grader;
private String name;
private Collection<Integer> marks;
public SchoolNotification(String name) {
// ... set fields
}
// ... getters and setters
public String addMark(Integer mark) {
this.marks.add(mark);
return this.grader.grade(this.marks);
}
}現在,這取決於一些 Spring 上下文以及我們程序化提供的額外上下文。
我們可以向 StudentServices 添加一個方法,該方法接受學生數據並將其持久化:
public abstract class StudentServices {
private Map<String, SchoolNotification> notes = new HashMap<>();
@Lookup
protected abstract SchoolNotification getNotification(String name);
public String appendMark(String name, Integer mark) {
SchoolNotification notification
= notes.computeIfAbsent(name, exists -> getNotification(name)));
return notification.addMark(mark);
}
}
在運行時,Spring 將以相同的方式執行該方法,並使用一些額外的技巧。
首先,請注意,它還可以調用複雜的構造函數,並注入其他 Spring Bean,從而使 SchoolNotification 更加像一個具有 Spring 意識的方法。
它通過實現 getSchoolNotification 方法,並通過調用 beanFactory.getBean(SchoolNotification.class, name) 來完成。
其次,我們有時可以將帶有 @Lookup- 註解的方法聲明為抽象方法,如上面的示例所示。
使用 abstract 比使用 stub 更美觀,但我們只能在不進行 component-scan 或使用 @Bean 管理周圍 Bean 的情況下使用它。
@Test
public void whenAbstractGetterMethodInjects_thenNewInstanceReturned() {
// ... initialize context
StudentServices services = context.getBean(StudentServices.class);
assertEquals("PASS", services.appendMark("Alex", 89));
assertEquals("FAIL", services.appendMark("Bethany", 78));
assertEquals("PASS", services.appendMark("Claire", 96));
}通過這種配置,我們可以將 Spring 依賴項以及方法依賴項添加到 SchoolNotification 中。
4. 侷限性
儘管 @Lookup 具有很高的靈活性,但仍有一些值得注意的侷限性:
- @Lookup 註解的方法,例如 getNotification,當週圍的類(例如 Student)進行組件掃描時,必須是具體的。這是因為組件掃描會跳過抽象的 Bean。
- 使用 @Lookup 註解的方法在周圍的類是 @Bean 管理的類中將無法正常工作。
在這些情況下,如果我們需要將原型 Bean 注入到單例中,我們可以考慮使用 Provider 作為替代方案。
5. 結論
在本文中,我們學習了何時以及如何使用 Spring 框架中的 <em @Lookup</em> 註解,包括如何使用它將原型作用域的 Bean 注入到單例 Bean 中,以及如何使用它通過程序注入依賴。