控制反轉的型別
控制反轉(IOC)旨在提供一種更簡單的機制,來設定元件的依賴項,並在整個生命週期管理這些依賴項。通常,控制反轉可以分成兩種子型別:依賴注入(DI)和依賴查詢(DL),這些子型別各自又可以被進一步分解為 IOC 服務的具體實現
1. 依賴查詢
1.1 依賴拉取
依賴拉取(Dependency Pull),即根據需要,從登錄檔中提取依賴項,以下程式碼顯示了基於 Spring 的依賴拉取
public class DependencyPull {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring/app-context.xml");
ctx.getBean("renderer", MessageRenderer.class);
}
}
1.2 上下文依賴查詢
上下文依賴查詢(contextualized dependency lookup,CDL),同樣屬於依賴查詢的子型別,和依賴拉取有點類似,但在 CDL 中,查詢是針對管理資源的容器執行的,這個容器通常由應用程式伺服器或框架(Tomcat、JBoss、Spring)提供,比如以下程式碼顯示了一個提供依賴查詢服務的容器介面
public interface Container {
// 根據key獲取相應的依賴項
Object getDependency(String key);
}
CDL 通過讓元件實現以下程式碼介面來進行工作
public interface ManagedComponent {
void performLookup(Container container);
}
元件需要實現該介面,當容器準備好將依賴項傳遞給元件時,會依次呼叫每個元件的 performLookup()
方法,然後元件就可以使用 Container
介面查詢所需的依賴項
public class ContextualizedDependencyLookup implements ManagedComponent {
private Dependency dependency;
@Override
public void performLookup(Container container) {
this.dependency = (Dependency) container.getDependency("myDependency");
}
@Override
public String toString() {
return dependency.toString();
}
}
2. 依賴注入
2.1 建構函式注入
當在元件的建構函式中提供依賴項時,就會發生建構函式依賴注入
public class ConstructorInjection {
private Dependency dependency;
public ConstructorInjection(Dependency dependency) {
this.dependency = dependency;
}
@Override
public String toString() {
return dependency.toString();
}
}
2.2 setter 函式注入
Ioc 容器通過 JavaBean 樣式的 setter 方法注入元件的依賴項
public class SetterInjection {
private Dependency dependency;
public void setDependency(Dependency dependency) {
this.dependency = dependency;
}
@Override
public String toString() {
return dependency.toString();
}
}
在 Spring 中,還支援另一種被稱為欄位注入(field injection)的注入型別,在後面學習使用 @Autowire 註解進行自動裝配時將介紹該注入型別
Spring 中的控制反轉
1. Bean 和 BeanFactory
Spring 的依賴注入容器的核心是 BeanFactory,它負責管理元件,包括依賴項以及它們的生命週期。如果我們想獲得一個元件(Bean),就必須建立一個實現了 BeanFactory 介面的例項,並對其進行配置
雖然 BeanFactory 可以通過程式設計方式配置,但更常見的做法是使用某種配置檔案在外部對其進行配置。Bean 配置可以由實現 BeanDefinition 介面的類的例項來表示,對於任何實現了 BeanDefinitionReader 介面的 BeanFactory 實現類來說,都可以使用 PropertiesBeanDefinitionReader 或 XmlBeanDefinitionReader 從配置檔案讀取 BeanDefinition 資料
定義一組介面:
public interface Oracle {
String output();
}
public class OracleImpl implements Oracle {
@Override
public String output() {
return "hello world";
}
}
接下來我們來看一看,Spring 的 BeanFactory 如何被初始化並用於獲取 Bean 例項
public class XmlConfigWithBeanFactory {
public static void main(String[] args) {
// DefaultListableBeanFactory是Spring提供的兩個主要BeanFactory實現之一
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader rdr = new XmlBeanDefinitionReader(factory);
// 使用XmlBeanDefinitionReader從XML檔案讀取BeanDefinition資訊
rdr.loadBeanDefinitions(new ClassPathResource("spring/xml-bean-factory-config.xml"));
// 使用在XML配置檔案中配置的名稱oracle來獲取bean
Oracle oracle = (Oracle) factory.getBean("oracle");
System.out.println(oracle.getInfo());
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="oracle" name="oracle" class="com.example.OracleImpl"/>
</beans>
ApplicationContext 介面是 BeanFactory 的一個擴充套件,除了 DI 服務外,還提供其他如事務和 AOP 等服務。在開發基於 Spring 的應用程式時,建議通過 ApplicationContext 介面與 Spring 互動
2. 設定 Spring 配置
2.1 XML 配置
對於 XML 配置,需要宣告應用程式需要的由 Spring 提供的名稱空間基礎資訊,下面所示配置僅宣告用於定義 bean 的名稱空間
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="provider" class="com.example.HelloWorldMessageProvider"/>
<bean id="render" class="com.example.StandardOutMessageRender"
p:messageProvider-ref="provider"/>
</beans>
2.2 註解配置
要想在應用程式使用 Spring 的註解支援,需要在 XML 配置中宣告
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.example" />
</beans>
<context:component-scan>
標記告訴 Spring 掃描程式碼,從而找到 @Component
等註解注入的 bean,以及支援在指定包(及其所有子包)下使用 @Autowire
等註解的 bean
2.3 Java 配置
配置類使用 @Configuration
註解,幷包含用 @Bean
註解的方法,這些方法由 IOC 容器直接呼叫來例項化 bean,bean 名稱與用於建立它的方法的名稱相同
@Configuration
public class HelloWorldConfiguration {
@Bean
public MessageProvider provider() {
return new HelloWorldMessageProvider();
}
@Bean
public MessageRender render() {
StandardOutMessageRender render = new StandardOutMessageRender();
render.setMessageProvider(provider());
return render;
}
}
如果想從該類中讀取配置資訊,需要一個不同的 ApplicationContext 實現
public class HelloWorldSpringAnnotated {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx
= new AnnotationConfigApplicationContext(HelloWorldConfiguration.class);
MessageRender render = ctx.getBean("render", MessageRender.class);
render.render();
}
}
3. setter 注入
使用 XML 配置來配置 setter 注入,需要在 <bean>
標記下指定 <property>
標記,為其注入一個依賴項
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="provider" class="com.example.HelloWorldMessageProvider"/>
<bean id="render" class="com.example.StandardOutMessageRender">
<property name="messageProvider" ref="provider"/>
</bean>
</beans>
如果使用註解,只需要向 setter 方法新增一個 @Autowired
註解
@Service("render")
public class StandardOutMessageRender implements MessageRender {
...
@Override
@Autowired
public void setMessageProvider(MessageProvider messageProvider) {
this.messageProvider = messageProvider;
}
}
4. 建構函式注入
public class ConfigurableMessageProvider implements MessageProvider {
private String message;
public ConfigurableMessageProvider(String message) {
this.message = message;
}
@Override
public String getMessage() {
return null;
}
}
使用 XML 方式注入
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="oracle" name="oracle" class="com.example.OracleImpl"/>
<!-- 使用 <constructor-arg> 標記 -->
<bean id="messageProvider" class="com.example.ConfigurableMessageProvider">
<constructor-arg value="hello world" />
</bean>
<!-- 使用c名稱空間 -->
<bean id="provider" class="com.example.ConfigurableMessageProvider"
c:message="hello world"/>
</beans>
使用註解方式
@Service
public class ConfigurableMessageProvider implements MessageProvider {
private String message;
@Autowired
public ConfigurableMessageProvider(
@Value("hello world") String message) {
this.message = message;
}
@Override
public String getMessage() {
return null;
}
}