Spring: IOC容器的实现

栏目: Java · 发布时间: 5年前

内容简介:上一篇中对Spring的IOC概念进行了介绍, 本篇将通过代码来实现一个简易版的IOC. 在Spring中, IOC是一个容器, 主要负责对托管至Spring的Bean进行创建及保存. Spring IOC创建Bean可分为单例和原型两种. 由于篇幅所限, 本篇中的简易版IOC只实现对单例Bean的管理.项目中的代码成千上万, Spring并不能准确的知道哪些Bean是需要由IOC容器创建并管理. 因此需要通过配置的方式将需要被管理的Bean告知Spring.早期的Spring, 被管理的Bean需要XML

上一篇中对Spring的IOC概念进行了介绍, 本篇将通过代码来实现一个简易版的IOC. 在Spring中, IOC是一个容器, 主要负责对托管至Spring的Bean进行创建及保存. Spring IOC创建Bean可分为单例和原型两种. 由于篇幅所限, 本篇中的简易版IOC只实现对单例Bean的管理.

设计思路

定位Bean

项目中的代码成千上万, Spring并不能准确的知道哪些Bean是需要由IOC容器创建并管理. 因此需要通过配置的方式将需要被管理的Bean告知Spring.

XML配置

早期的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后, 就要对Bean进行解析. 由于有有XML和注解两种配置方式, 因此IOC容器需要分别解析XML配置及注解配置的Bean. 主要针对以下几项进行解析:

  • 类型: 后续通过反射创建Bean时使用
  • 名称: 保存时作为Bean的别名使用
  • 属性: 依赖注入(下篇文字中实现)时使用

注册Bean

将解析得到的Bean描述信息注册到指定容器中.

创建Bean

将已注册到容器中的Bean依次实例化, 并统一保存. 根据Bean描述信息中的类型(Class)通过反射创建Bean的实例.

获取Bean

对外提供获取Bean的接口, 如果Bean不存在, 自动创建保存后返回.

接口与组件

BeanDefinition

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
    // ...

}

复制代码

BeanFactory

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 {
    }

}

复制代码

ElementParser

配置文件节点解析器接口

// 节点解析器接口
public interface ElementParser {

    // 解析节点
    public void parse(Element ele, BeanFactory factory) throws Exception;

}
复制代码

BeanElementParser

解析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);
    }

}
复制代码

ComponentScanElementParser

解析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);
    }

}
复制代码

Component

如果BEAN需要被Spring管理, 在类中添加该注解. 含有该注解的类在被ComponentScanElementParser扫描后会交由IOC容器管理.

// 托管Bean声明注解
@Documented
@Target({ ElementType.TYPE })
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
    String value() default "";
}
复制代码

ApplicationContext

应用程序上下文, 提供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;
    }

}
复制代码

流程

  1. 实例化ApplicationContext并设置配置文件路径
  2. 调用init方法初始化IOC容器
  3. 创建BeanFactory
  4. 初始化, 节点解析器
  5. 读取配置文件并解析配置文件中定义的BEAN
  6. 各节点解析器解析配置文件并向BeanFactory中注册Bean
  7. 调用BeanFactory的instanceBean方法实例化已注册的Bean
  8. IOC容器初始化完成

具体实现

IOC容器初始化

ApplicationContext作为IOC容器的初始化入口, init方法中需要初始化基础组件(节点解析器, BEAN工厂)并完成BEAN的注册及实例化.

  • 初始化操作基础流程
// 根据Servlet上下文初始化
public void init(ServletContext context) throws Exception {
    
    // 创建BEAN工厂
    createBeanFactory();
    // 初始化配置文件节点解析器
    initElementParser();
    // 解析配置文件中定义的BEAN
    parseBean();
    // 通知BEAN工厂实例化已注册的BEAN
    this.beanFactory.instanceBean();

}
复制代码
  • 创建BEAN工厂
// 创建BEAN工厂
private void createBeanFactory() {
    this.beanFactory = new BeanFactory();
}
复制代码
  • 初始化配置文件节点解析器
// 初始化配置文件节点解析器, KEY为节点的名称
// 解析文件时根据节点的名称就可以找到对应的解析器
private void initElementParser() {
    parserMap.put("bean", new BeanElementParser());
    parserMap.put("component-scan", new ComponentScanElementParser());
}
复制代码
  • 解析配置文件中定义的BEAN
// 解析配置文件中定义的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并实例化

// 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);
        }

    }

}
复制代码

测试

  • 创建BEAN
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配置文件
<?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工厂容器中获取.

下一篇将实现依赖注入的功能.


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Head First Web Design

Head First Web Design

Ethan Watrall、Jeff Siarto / O’Reilly Media, Inc. / 2009-01-02 / USD 49.99

Want to know how to make your pages look beautiful, communicate your message effectively, guide visitors through your website with ease, and get everything approved by the accessibility and usability ......一起来看看 《Head First Web Design》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

随机密码生成器
随机密码生成器

多种字符组合密码

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具