上一篇中对Spring的IOC概念进行了介绍, 本篇将通过代码来实现一个简易版的IOC. 在Spring中, IOC是一个容器, 主要负责对托管至Spring的Bean进行创建及保存. Spring IOC创建Bean可分为单例和原型两种. 由于篇幅所限, 本篇中的简易版IOC只实现对单例Bean的管理.
项目中的代码成千上万, Spring并不能准确的知道哪些Bean是需要由IOC容器创建并管理. 因此需要通过配置的方式将需要被管理的Bean告知Spring.
早期的Spring, 被管理的Bean需要XML文件中进行声明.
<bean id="userService" class="com.atd681.xc.ssm.ioc.demo.UserService"></bean> 复制代码
由于XML配置过于繁琐, 可读性较差. 为简化配置Spring推出了基于注解的配置. 在代码中对需要被管理的Bean添加指定注解即可.
package com.atd681.xc.ssm.ioc.demo; @Component public class UserService { } 复制代码
为了提升性能, 需要告知Spring哪些目录下有需要被加载的Bean, Spring会扫描这些目录并将含有注解的Bean进行管理
<component-scan package="com.atd681.xc.ssm.ioc.demo" /> 复制代码
确定需要被管理的Bean后, 就要对Bean进行解析. 由于有有XML和注解两种配置方式, 因此IOC容器需要分别解析XML配置及注解配置的Bean. 主要针对以下几项进行解析:
将解析得到的Bean描述信息注册到指定容器中.
将已注册到容器中的Bean依次实例化, 并统一保存. 根据Bean描述信息中的类型(Class)通过反射创建Bean的实例.
对外提供获取Bean的接口, 如果Bean不存在, 自动创建保存后返回.
BEAN描述类, 用来保存BEAN的基本信息, 包括名称, 类型, 属性等.
// 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工厂容器中获取.
下一篇将实现依赖注入的功能.