上一篇中对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工厂容器中获取.
下一篇将实现依赖注入的功能.