上一篇中對Spring的IOC概念進行了介紹, 本篇將經過代碼來實現一個簡易版的IOC. 在Spring中, IOC是一個容器, 主要負責對託管至Spring的Bean進行建立及保存. Spring IOC建立Bean可分爲單例和原型兩種. 因爲篇幅所限, 本篇中的簡易版IOC只實現對單例Bean的管理.java
項目中的代碼成千上萬, Spring並不能準確的知道哪些Bean是須要由IOC容器建立並管理. 所以須要經過配置的方式將須要被管理的Bean告知Spring.緩存
早期的Spring, 被管理的Bean須要XML文件中進行聲明.bash
<bean id="userService" class="com.atd681.xc.ssm.ioc.demo.UserService"></bean>
複製代碼
因爲XML配置過於繁瑣, 可讀性較差. 爲簡化配置Spring推出了基於註解的配置. 在代碼中對須要被管理的Bean添加指定註解便可.dom
package com.atd681.xc.ssm.ioc.demo;
@Component
public class UserService {
}
複製代碼
爲了提高性能, 須要告知Spring哪些目錄下有須要被加載的Bean, Spring會掃描這些目錄並將含有註解的Bean進行管理ide
<component-scan package="com.atd681.xc.ssm.ioc.demo" />
複製代碼
肯定須要被管理的Bean後, 就要對Bean進行解析. 因爲有有XML和註解兩種配置方式, 所以IOC容器須要分別解析XML配置及註解配置的Bean. 主要針對如下幾項進行解析:性能
將解析獲得的Bean描述信息註冊到指定容器中.測試
將已註冊到容器中的Bean依次實例化, 並統一保存. 根據Bean描述信息中的類型(Class)經過反射建立Bean的實例.ui
對外提供獲取Bean的接口, 若是Bean不存在, 自動建立保存後返回.this
BEAN描述類, 用來保存BEAN的基本信息, 包括名稱, 類型, 屬性等.spa
// BEAN描述信息
public class BeanDefinition {
// 名稱
private String name;
// CLASS
private Class<?> clazz;
// 經過名稱和CLASS實例化, 默認使用CLASS名做爲BEAN的名稱
public BeanDefinition(String name, Class<?> clazz) {
this.clazz = clazz;
this.name = BeanUtil.isEmpty(name) ? BeanUtil.getName(clazz) : name;
}
// Getter & Setter
// ...
}
複製代碼
BEAN工廠, IOC容器的核心類. 負責統一建立及管理BEAN(包括描述信息和實例), 對外提供獲取BEAN的接口. 由IOC容器管理的BEAN的全部操做都由BeanFactory完成.
// BEAN工廠, 提供BEAN的建立及獲取
public class BeanFactory {
// 保存全部BEAN的信息. K: BEAN名稱, V: BEAN描述信息
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>();
// 保存全部BEAN的實例化對象. K: BEAN名稱, V: BEAN實例化對象
private final Map<String, Object> beanObjectMap = new ConcurrentHashMap<String, Object>();
// 註冊BEAN
public void registerBean(BeanDefinition bean) {
beanDefinitionMap.put(bean.getName(), bean);
}
// 獲取全部已註冊BEAN的名稱
public Set<String> getBeanNames() {
return this.beanDefinitionMap.keySet();
}
// 根據名稱獲取BEAN的類型
public Class<?> getBeanType(String name) {
return this.beanDefinitionMap.get(name).getClazz();
}
// 根據名稱獲取BEAN的實例
@SuppressWarnings("unchecked")
public <T> T getBean(String name) throws Exception {
return null;
}
// 實例化BEAN
public void instanceBean() throws Exception {
}
}
複製代碼
配置文件節點解析器接口
// 節點解析器接口
public interface ElementParser {
// 解析節點
public void parse(Element ele, BeanFactory factory) throws Exception;
}
複製代碼
解析XML配置文件中的節點, 將解析到的BEAN信息封裝成BeanDefinition, 註冊至BeanFactory中.
// Bean節點解析器,解析XML配置文件中的<bean>節點
public class BeanElementParser implements ElementParser {
// 解析<bean>節點
@SuppressWarnings("unchecked")
@Override
public void parse(Element ele, BeanFactory factory) throws Exception {
// 解析<bean>節點, 將Bean描述信息封裝
BeanDefinition bd = null;
// 向BEAN工廠註冊Bean
factory.registerBean(bd);
}
}
複製代碼
解析XML配置文件中的節點, 獲取package屬性中的包目錄, 掃描目錄下的類並解析, 將須要被管理的BEAN信息封裝成BeanDefinition, 註冊至BeanFactory中.
// <component-scan>節點解析器
public class ComponentScanElementParser implements ElementParser {
// 解析<component-scan>節點
@Override
public void parse(Element ele, BeanFactory factory) throws Exception {
// 掃描package屬性中定義的目錄
String basePackage = ele.getAttributeValue("package");
// 解析目錄下的Bean並註冊至BEAN工廠
BeanDefinition bd = null;
factory.registerBean(bd);
}
}
複製代碼
若是BEAN須要被Spring管理, 在類中添加該註解. 含有該註解的類在被ComponentScanElementParser掃描後會交由IOC容器管理.
// 託管Bean聲明註解
@Documented
@Target({ ElementType.TYPE })
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
String value() default "";
}
複製代碼
應用程序上下文, 提供IOC容器初始化入口及統一獲取BEAN的接口.
package com.atd681.xc.ssm.ioc.framework;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletContext;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import com.atd681.xc.ssm.ioc.framework.parser.BeanElementParser;
import com.atd681.xc.ssm.ioc.framework.parser.ComponentScanElementParser;
import com.atd681.xc.ssm.ioc.framework.parser.ElementParser;
// 應用程序上下文
public class ApplicationContext {
// 配置文件路徑
private String configLocation;
// BEAN工廠
private BeanFactory beanFactory;
// 節點解析器容器
private Map<String, ElementParser> parserMap = new HashMap<String, ElementParser>();
// 無參構造
public ApplicationContext() {
}
// 根據配置文件路徑實例化上下文
public ApplicationContext(String configLocation) {
this.setConfigLocation(configLocation);
}
// 設置配置文件路徑
public void setConfigLocation(String configLocation) {
this.configLocation = configLocation;
}
// 初始化
public void init() throws Exception {
this.init(null);
}
// 根據Servlet上下文初始化
public void init(ServletContext context) throws Exception {
// 建立BEAN工廠
// 初始化配置文件節點解析器
// 解析配置文件中定義的BEAN
// 通知BEAN工廠實例化已註冊的BEAN
}
// 獲取名稱獲取BEAN實例
public <T> T getBean(String beanName) throws Exception {
return this.beanFactory.getBean(beanName);
}
// 獲取BEAN工廠
public BeanFactory getBeanFactory() {
return beanFactory;
}
}
複製代碼
ApplicationContext做爲IOC容器的初始化入口, init方法中須要初始化基礎組件(節點解析器, BEAN工廠)並完成BEAN的註冊及實例化.
// 根據Servlet上下文初始化
public void init(ServletContext context) throws Exception {
// 建立BEAN工廠
createBeanFactory();
// 初始化配置文件節點解析器
initElementParser();
// 解析配置文件中定義的BEAN
parseBean();
// 通知BEAN工廠實例化已註冊的BEAN
this.beanFactory.instanceBean();
}
複製代碼
// 建立BEAN工廠
private void createBeanFactory() {
this.beanFactory = new BeanFactory();
}
複製代碼
// 初始化配置文件節點解析器, KEY爲節點的名稱
// 解析文件時根據節點的名稱就能夠找到對應的解析器
private void initElementParser() {
parserMap.put("bean", new BeanElementParser());
parserMap.put("component-scan", new ComponentScanElementParser());
}
複製代碼
// 解析配置文件中定義的BEAN
@SuppressWarnings("unchecked")
private void parseBean() throws Exception {
// 開始加載配置文件(JDom解析XML)
String classpath = getClass().getClassLoader().getResource("").getPath();
Document doc = new SAXBuilder().build(new File(classpath, this.configLocation));
// 獲取根節點(<beans>)下全部子節點並依次解析
List<Element> elementList = doc.getRootElement().getChildren();
for (Element ele : elementList) {
// 節點名稱
String eleName = ele.getName();
// 無對應的節點解析器
if (!this.parserMap.containsKey(eleName)) {
throw new RuntimeException("節點[" + eleName + "]配置錯誤,沒法解析");
}
// 根據節點名稱找到對應的節點解析器解析節點
this.parserMap.get(eleName).parse(ele, this.beanFactory);
}
}
複製代碼
// Bean節點解析器,解析XML配置文件中的<bean>節點
public class BeanElementParser implements ElementParser {
// 解析<bean>節點
@SuppressWarnings("unchecked")
@Override
public void parse(Element ele, BeanFactory factory) throws Exception {
// <bean>節點中的id和class屬性
String cls = ele.getAttributeValue("class");
String id = ele.getAttributeValue("id");
// 類型
Class<?> clazz = Class.forName(cls);
// 封裝成類描述信息
BeanDefinition bd = new BeanDefinition(id, clazz);
// 向BEAN工廠註冊Bean
factory.registerBean(bd);
}
}
複製代碼
// <component-scan>節點解析器
public class ComponentScanElementParser implements ElementParser {
// 解析<component-scan>節點
@Override
public void parse(Element ele, BeanFactory factory) throws Exception {
// package屬性(掃描目錄)
String basePackage = ele.getAttributeValue("package");
if (basePackage == null) {
throw new RuntimeException("<component-scan>必須配置package屬性");
}
// 獲取掃描目錄絕對路徑
String baseDir = getClass().getClassLoader().getResource(basePackage.replace('.', '/')).getPath();
// 掃描目錄,獲取目錄下的全部類文件
for (File file : new File(baseDir).listFiles()) {
// 獲取CLASS的路徑(包目錄+類名)並加載CLASS
String classPath = basePackage + "." + file.getName().replaceAll("\\.class", "");
Class<?> clazz = Class.forName(classPath);
// 只處理含有@Component的BEAN
if (!clazz.isAnnotationPresent(Component.class)) {
continue;
}
// 獲取類的@Component註解
Component c = clazz.getAnnotation(Component.class);
// 封裝成類描述信息
BeanDefinition bd = new BeanDefinition(c.value(), clazz);
// 向BEAN工廠註冊Bean
factory.registerBean(bd);
}
}
}
複製代碼
// BEAN工廠, 提供BEAN的建立及獲取
public class BeanFactory {
// BEAN描述信息容器, 保存全部BEAN的信息. K: BEAN名稱, V: BEAN描述信息
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>();
// BEAN實例容器, 保存全部BEAN的實例化對象. K: BEAN名稱, V: BEAN實例化對象
private final Map<String, Object> beanObjectMap = new ConcurrentHashMap<String, Object>();
// 註冊BEAN
public void registerBean(BeanDefinition bean) {
beanDefinitionMap.put(bean.getName(), bean);
}
// 獲取全部已註冊BEAN的名稱
public Set<String> getBeanNames() {
return this.beanDefinitionMap.keySet();
}
// 根據名稱獲取BEAN的類型
public Class<?> getBeanType(String name) {
return this.beanDefinitionMap.get(name).getClazz();
}
// 根據名稱獲取BEAN的實例
@SuppressWarnings("unchecked")
public <T> T getBean(String name) throws Exception {
// 根據名稱從容器獲取BEAN
Object bean = this.beanObjectMap.get(name);
// 容器中存在直接返回
if (bean != null) {
return (T) bean;
}
// 未獲取到時自動建立
// 查看緩存中是否有BEAN描述
if (!this.beanDefinitionMap.containsKey(name)) {
throw new RuntimeException("未定義BEAN[" + name + "]");
}
// 存在BEAN描述時根據描述信息實例化BEAN
BeanDefinition beanDef = this.beanDefinitionMap.get(name);
bean = beanDef.getClazz().newInstance();
// 將BEAN實例化保存至容器
this.beanObjectMap.put(name, bean);
// 返回新建立BEAN
return (T) bean;
}
// 實例化BEAN
public void instanceBean() throws Exception {
// 根據緩存的BEAN描述信息依次建立BEAN
for (String beanName : this.beanDefinitionMap.keySet()) {
getBean(beanName);
}
}
}
複製代碼
package com.atd681.xc.ssm.ioc.demo.service;
import com.atd681.xc.ssm.ioc.framework.annotation.Component;
// 經過註解聲明BEAN
@Component
public class ServiceX {
public void test() {
System.out.println("ServiceX.test start...");
}
}
複製代碼
// 經過配置文件配置BEAN
public class ManagerX {
public void test() {
System.out.println("ManagerX.test start...");
}
}
複製代碼
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<!-- 配置BEAN所在目錄, IOC容器會掃描該目錄, 加載含有@Component註解的Bean -->
<component-scan package="com.atd681.xc.ssm.ioc.demo.service" />
<!-- 配置BEAN -->
<bean id="managerX" class="com.atd681.xc.ssm.ioc.demo.ManagerX"></bean>
</beans>
複製代碼
// IOC測試類
public class Test {
// 測試IOC容器
public static void main(String[] args) throws Exception {
// 實例化應用上下文並設置配置文件路徑
ApplicationContext context = new ApplicationContext("context.xml");
// 初始化上下文(IOC容器)
context.init();
// 從IOC容器中獲取BEAN並執行
ServiceX serviceX = context.getBean("serviceX");
ManagerX managerx = context.getBean("managerX");
serviceX.test();
managerx.test();
}
}
複製代碼
從IOC容器中獲取BEAN並執行後輸出以下結果: BEAN的實例對象已經保存在IOC容器中.
ServiceX.test start...
ManagerX.test start...
複製代碼
IOC容器未找到對應的BEAN(未配置或配置錯誤)時會拋出異常: BEAN的實例對象沒有在IOC容器中.
Exception in thread "main" java.lang.RuntimeException: 未定義BEAN[managerX1]
at com.atd681.xc.ssm.ioc.framework.BeanFactory.getBean(BeanFactory.java:54)
at com.atd681.xc.ssm.ioc.framework.ApplicationContext.getBean(ApplicationContext.java:117)
at com.atd681.xc.ssm.ioc.demo.Test.main(Test.java:19)
複製代碼
Spring IOC實現對BEAN控制權的反轉, 將BEAN統一將由IOC容器建立及管理. 只有IOC容器統一管理BEAN後才能完成對各BEAN依賴屬性的自動注入.
Spring的IOC容器經過配置文件獲取到須要被管理的BEAN後, 將BEAN的信息解析封裝並註冊至BEAN工廠(BEAN工廠緩存BEAN描述信息). 全部BEAN註冊完成後依次對BEAN進行實例化並保存在BEAN工廠的容器中, 以此來實現對BEAN的統一管理. 當須要獲取BEAN時, 統一從BEAN工廠容器中獲取.
下一篇將實現依賴注入的功能.