==============================
Spring 容器中 Bean 的名稱
==============================
聲明 bean 有兩個方式, 一個是 @Bean, 另一個是 @Component 和它的子類 (包括 @Service/@Controller/@Repository/@Configuration), Spring 容器中 bean 名生成規則分兩大類, 分別是:
一. @Component 和它的子注解是用來注解 Class 的. 這些注解標注 Bean 的命名規則是:
1. 如果 @Component 指定了參數值的話, 參數值就是 bean 名稱.
2. 如果 @Component 未指定參數值的話, bean 名就要看被注解的類名, 如果類名開頭是兩個或兩個大寫字母, bean 名同類名完全一致; 如果開頭只有一個大寫字母, bean 名是類名首字母小寫版.
@Component 和它的子注解雖然可以加在接口上, 但不推薦這樣, 應該直接加在具體的實現類上, 實際項目中, Service 層往往有 Interface 和 impl, 應該在 impl 上加 @Service 注解.
二. @Bean 注解往往用來標注一個函數, @Bean 注解標注 Bean 的命名規則是:
1. 如果 @Bean 指定了 name 參數, 以參數為准.
2. 未指定 name 參數的情況下, 以方法名作為 bean 的名稱.
==============================
Bean 作用域
==============================
參考: https://www.jianshu.com/p/502c40cc1c41
Bean 常用的作用域有下面 5 類,
@Scope("singleton")
@Scope("prototype")
@Scope("request")
@Scope("session")
@Scope("global-session")
1. @Scope("singleton") 標注的 bean, 表示在 Spring 容器中只創建一個 bean 實例, 一般情況下在應用程序啟動的時候, 就已經完成 bean 實例化了. 在程序運行過程中, 每次調用這個 bean 將使用同一個實例. 這也是 Spring 默認的 scope.
2. @Scope("prototype") 標注的 bean, 每次獲取這個 bean 都創建一個新的實例.
3. @Scope("request"), 僅適用於 Web 項目, 給每個 http request 新建一個 Bean 實例.
4. @Scope("session"), 僅適用於 Web 項目, 給每個 http session 新建一個 Bean 實例.
5. @Scope("global-session"), 僅適用於 portal Web 項目, 給每個 global http session 新建一個 Bean 實例.
singleton bean 默認都是在容器創建后即初始化, 基本上等同於啟動一個 JVM 后即創建 bean 實例, 如果應用很關注啟動時間, 可以在聲明 bean 的時候再加一個 @Lazy 注解, bean 實例化將延遲到第一次獲取 bean 對象時. 一般情況下, 強烈建議所有的 Singleton bean 不要啟用延遲加載, 這樣就能在程序啟動的時候發現問題.
==============================
不同 scope bean 的線程安全性問題
==============================
Struts2 每次請求過來都會實例化一個對象來響應, 所以沒有線程安全問題, 而 Spring MVC 是基於視圖函數做攔截的, 每次web請求過來后, Spring 都是使用 controller的指定方法去響應, 而不是新實例化一個controller. Spring 這樣做的好處是: 避免頻繁對象實例化和GC, 效率會比Struts2要高; 但也不是沒有缺點的, 會有線程不安全的潛在危險. 潛在的風險點是, 如果我們的Controller/Service/Repository類中包含實例變量, 就很可能會引起線程不安全. 避免線程不安全的方法有兩個: 第一, Controller/Service/Repository類中不要加實例變量, 這是推薦做法. 第二, 默認情況下Spring IOC管理的bean, 其生命周期是 singleton, 我們可以修改 Controller/Service/Repository 的聲明周期為 request.
Spring 默認的 scope 時 singleton, 因為不需要頻繁的對象創建和內存回收, 性能最好, 但需要注意線程安全問題.
prototype 類型的 bean, 每次注入的都是一個全新對象, 顯然沒有線程安全問題.
request 和 session 對象, 除非我們在同一個 request 或 session 中顯式地使用了多線程, 需要注意一下線程安全問題, 在絕大多數情形下, 這兩個 scope 類型的 bean 是線程安全的.
對於 singleton 類型的 bean, 如何做到線程安全呢? 一般有兩個方法:
1. bean 類中壓根不定義成員變量, 所有的bean都是無狀態的, 這樣就杜絕了線程不安全的隱患.
2. 如果 bean 類必須有成員變量, 一定要將成員變量加到 ThreadLocal 中.
或者, 干脆將 singleton scope 改成 prototype.
==============================
Bean 對象的初始化鈎子
==============================
設想如下場景:
1. 我們需要注入一個 bean 對象, 並要對該 bean 對象做一些特別設置, 而這個 bean 對象類的代碼又不歸我們管.
2. 對於 Boss 這個對象, 注入很多種 bean 對象 (比如 car/office 等), 如何在一處代碼中集中為 car/office 對象做一些特別設置?
顯然這些場景, 無法通過 bean 類的構造子實現, Spring 項目中, 我們可以使用下面兩種方式實現:
1. JSR-250 定義的標准有 @PostConstruct 以及 @PreDestroy 注解, 這兩個注解一般和 @Component 搭配使用.
2. Spring 提供的 @Bean 注解有 initMethod 和 destroyMethod 屬性. 更推薦使用 @PostConstruct
==============================
@Autowired Bean 注入
==============================
@Autowired 可以對類成員變量、方法和構造函數加注解, Spring會自動掃描所有打了@Autowired注解的變量/方法/構造子, 完成自動裝配任務. 換句話講, 所有打了@Autowired注解的方法/構造子, Spring會在合適的時機自動調用它們, 一般情況下不需要我們再手工調用.
1. 在類成員變量上加標注, 可以省去 setter 方法.
public class Boss { @Autowired private Car car; }
2. 在類的方法上加標注, Spring 自動完成"所有 bean"實參裝配, 該方法會被Spring在合適的時機自動調用.
public class Boss { private Car car; private Office office; @Autowired public void Init(Car car, Office office) { this.car = car; this.office = office ; } }
3. 在類的構造函數上加標注, Spring 自動完成"所有 bean"實參裝配.
public class Boss { private Car car; private Office office; @Autowired public Boss(Car car, Office office) { this.car = car; this.office = office ; } }
4. @Autowired 注解標在集合上或數組上的含義: 是用來獲取同一個接口的多個實現.
摘自 https://www.chkui.com/article/spring/spring_core_auto_inject_of_annotation
interface A {} @Component class implA1 implements A{} @Component class implA2 implements A{} class MyClass { @Autowired private A[] a; @Autowired private Set<A> set; @Autowired private Map<String, A> map; }
使用 Map 時,key 必須聲明為 String,在運行時會 key 是注入 Bean 的 name/id.
==============================
JSR-250/JSR-330 的 Bean 注入
==============================
JSR-250 標准的注入注解是 @Resource.
JSR-330 標准的注入注解是 @Inject.
Spring 專有的注入注解是 @Autowired.
@Inject 注解和 @Autowired 幾乎一樣, 其功能比 @Autowired 稍弱一些 (比如沒有 required 屬性), 所以不推薦使用.
在實際項目中, 經常使用到的是 @Autowired 和 @Resource.
@Autowired 默認是 byType 注入的, 而 @Resource 本身有 name 和 type 屬性, 默認是按照 byName 注入的.
@Autowired 和 @Inject 注入 bean 對象的執行路徑是:
1.Match by Type : 優先 byType 注入
2.Restricts by Qualifier : 依照 @Qualifier 指定的 name 來注入, 但如果沒有注入成功, 直接報錯.
3.Match by Name : 最后按照默認的 name 注入.
@Resource 注入 bean 對象的執行路徑是:
1. 如果同時指定了 name 和 type, 則在容器中找 name 和 type 都匹配的 bean 進行裝配, 找不到則拋出異常.
2. 如果指定了 name, 則在容器中查找名稱 (id) 匹配的 bean 進行裝配, 找不到則拋出異常.
3. 如果指定了 type, 則在容器中找到類型匹配的唯一 bean 進行裝配, 找不到或者找到多個, 都會拋出異常.
4. 如果既沒有指定 name,又沒有指定 type 時, 將按照下面執行路徑:
4.1 按照默認的名字注入.
4.2 按照類型注入.
4.3 依照 @Qualifier 的修飾來注入.
參考:
http://javainsimpleway.com/autowired-resource-and-inject-2/
http://einverne.github.io/post/2017/08/autowired-vs-resource-vs-inject.html
==============================
@Primary 解決多個子類無法注入問題
==============================
有時候在我們的項目中, 同一個接口可能會有多個實現 (或子類), 在使用 @Autowired 注入 bean 實例時, 會報錯. 原因很簡單, @Autowired 默認是按照 type 注入的, 現在有多個子類, Spring IoC 容器不知道該如何注入了.
解決方式式在聲明 Bean 的時候, 可以再多加一個 @Primary 注解. @Primary 既可和 @Component 搭配使用, 也可以和 @Bean 搭配使用.
@Component @Primary public class BenzCar implements ICar { public void run() { System.out.println("Benz run"); } @Override public String toString() { return "Brand: Benz, price:1000"; } } @Component public class VWCar implements ICar { public void run() { System.out.println("VW run"); } @Override public String toString() { return "Brand: Volkwargon, price:1000"; } } @Component public class Boss { @Autowired private ICar car; } @Configuration @ComponentScan("javaTestMaven.demo2") public class SomeConfig { } public class App { public static void main(String[] args) { App app=new App(); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.register(SomeConfig.class); context.refresh(); Boss boss = context.getBean(Boss.class); System.out.println(boss); context.close(); } }
==============================
@Qualifier 的使用
==============================
上個例子中, 使用了 @Primary 注解來解決多個子類無法注入的情況, 其實 Spring 還提供其他辦法, @Qualifier 注解就是其中之一.
一旦加上了 @Qualifier 之后, @Autowired 在 byType 注入失敗后, 將按照 @Qualifier 設定的參數來注入. @Qualifier 可以和 @Autowired/ @Resource/ @Inject 一起使用, 但多數情況下是和 @Autowired 一起使用.
@Autowired 的標注對象是成員變量、方法、構造函數.
@Qualifier 的標注對象是成員變量、方法入參、構造函數入參.
上面表述看上去是好像一樣, 其實是有差別的, 重點是兩個注解修飾的主體是不同的, 以注解方法為例, @Qualifier 修飾的是單個實參, 而 @Autowired 修飾的是方法體. 如果該方法有多個 bean 類的形式參數, 每一個參數都可使用 @Qualifier 修飾.
下面是一個 @Qualifier 用在構造參數實參的示例.
public class Boss { private Car car; private Office office; @Autowired public Boss(Car car, @Qualifier("office") Office office){ this.car = car; this.office = office ; } }
==============================
@Conditional 實現條件注入
==============================
@Primary 的缺點: 通過類型方式將接口和某個具體實現綁定死了, 好處:我們可以隨時切換 Primary 類, 達到切換具體實現.
@Qualifier 的缺點: 通過名稱方式將接口和具體實現的名稱綁定死了, 好處:我們可以隨時調整名稱, 達到切換具體實現.
但總體來講 @Primary 和 @Qualifier 都有點 hard code 味道, 有什么更好的方案呢? 答案就是 @Conditional, 使用起來稍微復雜些, Spring Boot 中大量使用了 @Conditional 注解, 比如多 profile 環境.
首先我們需要實現 Spring 的 Condition 接口, 然后在 Config 類中使用 @Conditional + @Bean 組合聲明 bean 類型. 加上 @Conditional 之后, 將在 bean 實例化時按實際的條件 evaluate 來確定使用哪個子類.
public class LinuxCondition implements Condition { public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { boolean result=context.getEnvironment().getProperty("os.name").contains("Linux"); return result ; } } public class WindowsCondition implements Condition { public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { boolean result=context.getEnvironment().getProperty("os.name").contains("Windows"); return result ; } } @Configuration @ComponentScan("javaTestMaven.demo2") public class SomeConfig { @Bean @Conditional(LinuxCondition.class) public ICar getBenzVWCar() { return new VWCar(); } @Bean @Conditional(WindowsCondition.class) public ICar getBenzCar() { return new BenzCar(); } } public class App { public static void main(String[] args) { App app=new App(); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.register(SomeConfig.class); context.refresh(); ICar car = context.getBean(ICar.class); System.out.println(car); context.close(); } }
==============================
Spring 的其他注解
==============================
一. @Autowired(required=false)
@Autowired() 如果不指定 required 屬性, 相當於 required 屬性為 true, 意思是被注入的對象不能為 null, 如果設定 required=false, 被注入的對象可以為 null.
因為 @Autowired(required=false) 容許注入 null, 會給程序帶來潛在的問題, 所以僅僅在開發和測試階段使用, 比如被依賴的類還沒被聲明成 bean.
二. @Required
依賴注入主要是兩種方式, 通過構造子注入或通過 Setter 函數注入, 一般地我們會將必需的依賴項通過構造子注入,對於非必需的依賴項通過 Setter 函數注入.
Spring 提供了 @Required 注解, 可以將某個 Setter 注入上升到必需級別.
@Required 用來注解 Setter 方法, 只適用於基於 XML 配置的 setter 注入方式, 效果和 @Autowired(required=true) 一樣, 推薦使用后者.
三. @DependsOn
直接依賴關系推薦使用構造子和 Setter 函數設置, 但對於沒有直接依賴的對象或依賴關系不明顯, 可以使用 @DependsOn.
四. @Order
@Order 一般用在控制多個子類的 bean 對象實例化的順序, @Order() 注解的參數值越小, 實例化越早.
在下面例子中, lst 中的第一個元素是 implA2 對象, 第二個元素是 implA1 對象.
interface A {} @Component @Order(2) class implA1 implements A{} @Component @Order(1) class implA2 implements A{} class MyClass { @Autowired private List<A> lst; }
===============================
參考
===============================
https://www.chkui.com/article/spring/spring_core_auto_inject_of_annotation
https://www.ibm.com/developerworks/cn/java/j-lo-spring25-ioc/
https://www.ibm.com/developerworks/cn/java/j-lo-spring25-mvc/
https://www.baeldung.com/spring-autowire