前言
Spring 是 Java 後端程式員必須掌握得一門架構技術,Spring 的橫空出世,大大簡化了企業級應用開發的複雜性。
Spring 架構中最核心的技術就是:
- IOC (控制反轉):是面向對象程式設計中的一種設計原則,可以用來減低計算機代碼之間的耦合度(百度百科)。通俗的說,轉移對象建立的控制權,原本對象建立的控制權在開發者,現在通過 IOC 将控制權交給 Spring ,由 Spring 統一管理對象的建立、銷毀等。
- AOP (切面程式設計):通過預編譯方式和運作期間動态代理實作程式功能的統一維護的一種技術(百度百科)。通俗的說,将一些具有公共行為(如權限校驗、日志記錄等)封裝到可重用的公共子產品中,從何降低耦合度,提高程式的可重用性。
本文将主要介紹 Spring 中的 IOC 的依賴注入。
控制反轉IOC
IOC 主要由兩種實作方式:
-
依賴查找(Dependency Lookup)
容器中的受控對象通過容器的API來查找自己所依賴的資源和協作對象。通俗的說,容器幫我們建立好了對象,開發者需要什麼就去容器中取。
-
依賴注入(Dependency Injection,簡稱 DI ,IOC 最常見的方式)
是對依賴查找的優化,即無需開發者手動去容器中查找對象,隻要告訴容器需要什麼對象,容器就會将建立好的對象進行注入。
依賴注入DI
在 Spring 中依賴注入的形式主要有兩種形式:
- 基于 xml 的形式
- 基于注解的形式
基于注解 DI 有三種表現形式:
- 基于屬性注入
- 基于屬性 setter 注入
- 基于構造器注入
三種正常注入
基于屬性注入
日常開發中最常使用的方式:
@Service
public class UserServiceImpl implements UserService {
@Autowired
private PhoneService phoneService;
}
@Service
public class UserServiceImpl implements UserService {
@Resource
private PhoneService phoneService;
}
@Autowired
注解和
@Resource
注解的差別:
@Autowired | @Resource |
---|---|
Spring 的注解,它的包是 org.springframework.beans.factory.annotation.Autowired | 不是Spring的注解,它的包是 javax.annotation.Resource |
隻按照 byType 注入 | 預設按照 byName 自動注入 |
無屬性 | 有兩個重要的屬性:name 和 type |
@Resource
的裝配順序:
- 如果同時指定了 name 和 type ,則從 Spring 上下文中找到唯一比對的 bean 進行裝配,找不到則抛出異常。
- 如果指定了 name ,則從上下文中查找名稱比對的 bean 進行裝配,找不到則抛出異常。
- 如果指定了 type ,則從上下文中查找類型比對的唯一 bean 進行裝配,找不到或是找到多個,都會抛出異常。
- 如果既沒有指定 name ,又沒有指定 type ,則自動按照 byName 方式進行裝配;如果沒有比對,則回退為一個原始類型進行比對,如果比對則自動裝配。
基于setter方法注入
@Service
public class UserServiceImpl implements UserService {
private PhoneService phoneService;
@Autowired
public void setPhoneService(PhoneService phoneService) {
this.phoneService = phoneService;
}
}
基于構造器注入( Spring 官方推薦)
@Service
public class UserServiceImpl implements UserService {
private final PhoneService phoneService;
@Autowired
public UserServiceImpl(PhoneService phoneService) {
this.phoneService = phoneService;
}
}
為何 Spring 官方推薦使用構造器注入呢?
- 保證依賴不可變(final關鍵字)
- 保證依賴不為空(省去了程式啟動因注入對象為空而報異常)
- 避免循環依賴
- 提升了代碼的可複用性(非 IOC 環境下,可使用 new 執行個體化該類的對象)
接口注入
當我們用上面三種方式注入接口時,接口有多個實作類時,程式啟動就會報錯,因為 Spring 不知道要注入哪個實作類。

那麼要如何解決接口多實作類的注入問題呢?
- 通過
注解結合@Autowired
注解@Qualifier
@Service public class UserServiceImpl implements UserService { @Autowired @Qualifier("applePhoneServieImpl") private PhoneService phoneService; }
指定要引入的具體實作類。@Qualifier("applePhoneServieImpl")
- 通過
注解動态擷取@Resource
@Service public class UserServiceImpl implements UserService { @Resource(name = "applePhoneServieImpl") private PhoneService phoneService; }
- 通過
注解優先注入@Primary
@Service @Primary public class ApplePhoneServieImpl implements PhoneService { }
注解表示當有多個 bean 滿足注入條件時,會優先注入該注解修飾的 bean 。@Primary
- 通過
注解結合配置檔案注入@ConditionalOnProperty
@Service @ConditionalOnProperty(prefix = "phone", name = "impl", havingValue = "vivo") public class VivoPhoneServieImpl implements PhoneService { }
意指當配置檔案中@ConditionalOnProperty(prefix = "phone", name = "impl", havingValue = "vivo")
時,phone.impl=vivo
才會注入到容器中。VivoPhoneServieImpl
- 通過其他 @Condition 條件注解
-
:當存在某一個 Bean 時,初始化此類到容器。@ConditionalOnBean
-
:當存在某一個類時,初始化此類的容器。@ConditionalOnClass
-
:當不存在某一個 Bean 時,初始化此類到容器。@ConditionalOnMissingBean
-
:當不存在某一個類時,初始化此類到容器。@ConditionalOnMissingClass
- ...
-
- 通過集合注入
@Service public class UserServiceImpl implements UserService { @Autowired private List<PhoneService> phoneServiceList; @Autowired private Map<String, PhoneService> phoneServiceMap; }
擷取Bean的方式
在 Spring 項目中,有時候需要手動去擷取 bean,手動擷取的方式需要通過
applicationContext
,有以下幾種形式:
- 通過
擷取applicationContext
@Service public class UserServiceImpl implements UserService { @Autowired private ApplicationContext applicationContext; public Object getBean() { return applicationContext.getBean("appleService"); } }
- 實作
接口ApplicationContextAware
@Component public class SpringContextUtil implements ApplicationContextAware { public static ApplicationContext applicationContext; public void setApplicationContext(ApplicationContext applicationContext) { SpringContextUtil.applicationContext = applicationContext; } public static Object getBean(String name) { return applicationContext.getBean(name); } public static <T> T getBean(Class<T> clazz) { return applicationContext.getBean(clazz); } public static <T> T getBean(String name, Class<T> clazz) { return applicationContext.getBean(name, clazz); } public static Boolean containsBean(String name) { return applicationContext.containsBean(name); } public static Boolean isSingleton(String name) { return applicationContext.isSingleton(name); } public static Class<? extends Object> getType(String name) { return applicationContext.getType(name); } }
PhoneService phoneService = SpringContextUtil.getBean(PhoneService.class);
- 繼承自抽象類
ApplicationObjectSupport
@Component public class SpringContextHelper extends ApplicationObjectSupport { public Object getBean(String name) { return getApplicationContext().getBean(name); } public <T> T getBean(Class<T> clazz) { return getApplicationContext().getBean(clazz); } public <T> T getBean(String name, Class<T> clazz) { return getApplicationContext().getBean(name, clazz); } public Boolean containsBean(String name) { return getApplicationContext().containsBean(name); } public Boolean isSingleton(String name) { return getApplicationContext().isSingleton(name); } public Class<? extends Object> getType(String name) { return getApplicationContext().getType(name); } }
- 繼承自抽象類
WebApplicationObjectSupport
繼承了WebApplicationObjectSupport
,是以使用方法和上面一樣。ApplicationObjectSupport
總結
本文主要記錄了 Spring 中基于注解的 DI 幾種方式,接口多個實作的注入方式,如何通過 applicationContext 手動擷取 bean 。