首先來看下在 Java 中對于泛型類型,比如這樣簡單的類定義
1
|
class Processor<T> {} |
如果直接初始化時要指定具體類型的話,我們可以這么寫
1
|
Processor<String> processor = new Processor<>(); //Java 7 及以上版本 |
Spring 對基本泛型的初始化
如果我們要用 Spring 容器來初始化這個類,比如給上面那個類加個 @Named 注解
1
2
3
|
@Named class Processor<T> { } |
這時候我們通過 beanFactory.getBean(Processor.class)
得到的是一個什么樣的實例呢?Spring 怎么知道要指定什么具體類型呢?很簡單,任何不確定的情況都是 Object。所以通過容器得到的 Processor 實例相當于用下面代碼構造出來的
1
|
Processor processor = new Processor(); //更準確來講是 Processor<Object> processor = new Processor<>(); |
再進一步,對于有上限約束的泛型定義,Spring 才如何應對呢?像
1
2
3
|
@Named class Processor<T extends Number> { } |
類似的,class Processor<T>
相當于 class Processor<T extends Object>
, 因此 Spring 在具體類型未明的情況下也是要用最頂層可接受類型,Spring 將會針對上面的代碼實例出下面的對象
1
|
Processor<Number> processor = new Processor<>(); |
再復雜一些,泛型的子類型仍然是泛型的情況,如下代碼
首先定義了一個泛型接口
1
2
3
|
public interface Service<T> { String process(T t); } |
然后要求 Spring 容器來初始下面的 NumberService 實例
1
2
3
4
5
6
7
8
|
@Named public class NumberService<R extends Number> implements Service<R> { @Override public String process(R number) { return "Process Number: " + number; } } |
Spring 在初始化 NumberService 實例同樣是要取用最頂層可接受類型,通過下面的代碼來初始化
1
|
NumberService<Number> numberService = new NumberService<>(); |
再終極一些,泛型類型并且類型也是泛型的,Spring 該如何拿捏?
1
2
3
4
5
6
|
@Named public class Processor<T> { @Inject Private Service<T> service; } |
此時 Spring 該如何確定上面的類型 T 呢?因為有了 Service<T> service
屬性的存在而不能再籠統的想像 Spring 會采用下面的代碼來初始化 Processor 實例
1
|
Processor<Object> processor = new Processor<>(); |
而是 Processor 的具體類型必須通過被注入的 Service<T>
實例的具體類型來推斷的,這就取決于在 Spring 容器中存在什么樣的 Service<T>
實例。舉兩個例子
如果 Spring 中有初始化
1
2
3
4
5
6
7
|
@Named public class StringService implements Service<String> { @Override public String process(String string) { return "Process String: " + string; } } |
那么前面的 Processor<T>
實例就相當于
1
2
|
Processor<String> processor = new Processor<>(); processor.service = new StringService(); |
如果 Spring 中初始化的 Service<T>
是前面那個 NumberService<R extends Number> implements Service<R>
, 那么 Spring 容器中的 Processor<T>
實例相當于
1
2
|
Processor<Number> processor = new Processor<>(); processor.service = new NumberService<Number>(); |
那如果前面的 NumberService 和 StringService 同時在 Spring 容器中注冊了呢?Spring 同樣要為難了,在沒有 @Primary 的情況下無法確定使用哪個實例來注入 Service<T> service
屬性了,出現類似錯誤
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
2016 - 12 - 09 00 : 56 : 50.922 WARN 4950 --- [ main] s.c.a.AnnotationConfigApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'processor' : Unsatisfied dependency expressed through field 'service' ; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'cc.unmi.Service<?>' available: expected single matching bean but found 2 : numberService,stringService 2016 - 12 - 09 00 : 56 : 50.941 ERROR 4950 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPLICATION FAILED TO START *************************** Description: Field service in cc.unmi.Processor required a single bean, but 2 were found: - numberService: defined in file [/Users/Yanbin/Workspaces/github/spring-generic-demo/target/classes/cc/unmi/NumberService. class ] - stringService: defined in file [/Users/Yanbin/Workspaces/github/spring-generic-demo/target/classes/cc/unmi/StringService. class ] Action: Consider marking one of the beans as @Primary , updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed |
這和普通屬性的注入時有多個可選實例時是一樣的錯誤。
總結一下
如果 Spring 在初始化泛型類時,未提供任何具體類型則會采用最上限的類型來初始化實例
-
@Named class Processor<T> -> new Processor<Object>()
-
@Named class Processor<T extends Number> -> new Processor<Number>();
如果泛型類型與被注入的屬性的具體類型有關聯,則由屬性類型推斷出主類型
1
2
3
|
@Named class Processor<T> { @Inject Service<T> service; } |
此時 Spring 容器中存在 class StringService implements Service<String>
的實例,則會由屬性 service(StringService 實例)
推斷出 Processor 的具體類型是 Processor<String>
當然這個 Processor 類也是可以定義的稍復雜一些,如
1
2
3
|
@Named class Processor<T extends Number> { @Inject Service<T> service; } |
關于本文的示例代碼可參考 https://github.com/yabqiu/spring-generic-demo, 請運行 mvn spring-boot:run 查看輸出結果來理解 Spring 怎么去初始化泛型類實例的。
好了,以上就是這篇文章的全部內容了,希望本文的內容對大家學習或者使用Spring能帶來一定的幫助,如果有疑問大家可以留言交流。
原文鏈接:http://unmi.cc/how-spring-initialize-generic-object/