• 第一章为源码解析。
  • 第二章为实现一个简单的 IOC 容器。
  • 第三章进阶 Spring 插件开发。

手动实现一个 IOC/DI 容器

上一篇文章里我们已经对 Spring 的源码有了一个大概的认识,对于 Spring 中的一些组件,比如 Aware,PostProcessor,ApplicationContext 有了一定的认识,下面我们就来手动实现一个 IOC/DI 容器。

项目整体用 maven 构建,里面有两个模块,MySpring 为 IOC/DI 的核心,Demo 为测试项目。

  1. 先来看看整体的项目结构,目前为第一个版本,好多需要完善的地方。最近好忙。

  2. 首先我们把几个重要的注解定义出来。

    @Autowired,自动注入注解,用来实现 DI 功能。

    @Controller,控制层注解,暂时不实现 SpringMVC 相关的功能。后续分析完 SpringMVC 源码后会实现。

    @Service,服务层注解,与 Spring 中的 @Component 作用相同,就是将这个 Bean 交给 Spring 来管理。

    @Repository,持久层注解,将这个 Bean 交给 Spring 来管理。

    暂时先定义这几个注解。

    这几个注解的定义与代码都是一样的,就是将注解定义出来。

     @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Service { String value() default "";
    }
  3. 首先我们也定义一个 BeanFactory 工厂方法作为最上层的容器。里面主要有一个 getBean 方法用来从容器中获取 Bean,当然这里面已经包含了 Bean

    的实例化过程。getBean 方法调用抽象的 doGetBean 方法,最后交给子类实现。

     package org.springframework.ioc.factory;
    
     /**
    * 容器对象的工厂类,生产容器对象
    *
    */
    public abstract class BeanFactory { public Object getBean(String beanName){
    return doGetBean(beanName);
    } protected abstract Object doGetBean(String beanName); }
  4. 定义一个 ApplicationContext 继承 BeanFactory,在里面添加 xml

    处理工具类和包的扫描路径。这个类也是一个抽象类。里面包含两个实例参数,配置文件路径和 xml 处理工具。

     package org.paul.springframework.ioc.bean;
    
     import org.paul.springframework.ioc.factory.BeanFactory;
    import org.paul.springframework.ioc.xml.XmlUtil; public abstract class ApplicationContext extends BeanFactory { protected String configLocation;
    protected XmlUtil xmlUtil = null; public ApplicationContext(){
    } public ApplicationContext(String configLocations){
    this.configLocation = configLocations;
    xmlUtil = new XmlUtil();
    } }
  5. 在看容器的最终实现类之前,我们先把 xmlUtil 和 配置文件的结构给大家看一下。

    xmlUtil 的作用就是解析配置文件获得类的所描路径。

     package org.springframework.ioc.xml;
    
     import org.dom4j.Document;
    import org.dom4j.DocumentException;
    import org.dom4j.Element;
    import org.dom4j.io.SAXReader;
    import java.io.InputStream; /**
    * 解析容器的配置文件中扫描包的路径
    *
    */
    public class XmlUtil {
    public String handlerXMLForScanPackage(String configuration){
    InputStream ins = this.getClass().getClassLoader().getResourceAsStream(configuration);
    SAXReader reader = new SAXReader();
    try{
    Document document = reader.read(ins);
    Element root = document.getRootElement();
    Element element = root.element("package-scan");
    String res = element.attributeValue("component-scan");
    return res;
    }catch (DocumentException e){
    e.printStackTrace();
    }
    return null;
    }
    }
     <?xml version="1.0" encoding="UTF-8" ?>
    <beans>
    <package-scan component-scan="com.spring.demo" />
    </beans>
  6. 容器的最终实现类,基于注解的 xml 扫描容器配置类。

    • 首先定义两个线程安全的 List 和 一个 ConcurrentHashMap,分别用来保存扫描 类的路径,类和实例对象。
    • 在 AnnotationApplicationContext 的构造函数里,分别实现了以下的功能。

      调用父类的初始化方法,将 xml 工具实例化。

      使用 xmlUtil 和 配置文件路径获取到扫描的包路径。

      获取到包路径后,执行包的扫描操作。

      将里面有对应注解的 Bean 注入到容器中。

      将对象创建出来,先忽略依赖关系。

      执行容器实例管理和对象运行期间的依赖装配。
     package org.springframework.ioc.bean;
    
     import java.io.File;
    import java.io.FileFilter;
    import java.lang.reflect.Field;
    import java.net.URISyntaxException;
    import java.net.URL;
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    import java.util.Map;
    import java.util.Map.Entry;
    import java.util.concurrent.ConcurrentHashMap; import org.springframework.ioc.annotation.Autowired;
    import org.springframework.ioc.annotation.Component;
    import org.springframework.ioc.annotation.Controller;
    import org.springframework.ioc.annotation.Repository;
    import org.springframework.ioc.annotation.Service; public class AnnotationApplicationContext extends ApplicationContext { //保存类路径的缓存
    private static List<String> classCache = Collections.synchronizedList(new ArrayList<String>()); //保存需要注入的类的缓存
    private static List<Class<?>> beanDefinition = Collections.synchronizedList(new ArrayList<Class<?>>()); //保存类实例的容器
    private static Map<String,Object> beanFactory = new ConcurrentHashMap<>(); public AnnotationApplicationContext(String configuration) {
    super(configuration);
    String path = xmlUtil.handlerXMLForScanPackage(configuration);
    System.out.println(path); //执行包的扫描操作
    scanPackage(path);
    //注册bean
    registerBean();
    //把对象创建出来,忽略依赖关系
    doCreateBean();
    //执行容器管理实例对象运行期间的依赖装配
    diBean();
    } @Override
    protected Object doGetBean(String beanName) {
    return beanFactory.get(beanName);
    } /**
    * 扫描包下面所有的 .class 文件的类路径到上面的List中
    *
    */
    private void scanPackage(final String path) {
    URL url = this.getClass().getClassLoader().getResource(path.replaceAll("\\.", "/"));
    try {
    File file = new File(url.toURI()); file.listFiles(new FileFilter(){
    //pathname 表示当前目录下的所有文件
    @Override
    public boolean accept(File pathname) {
    //递归查找文件
    if(pathname.isDirectory()){
    scanPackage(path+"."+pathname.getName());
    }else{
    if(pathname.getName().endsWith(".class")){
    String classPath = path + "." + pathname.getName().replace(".class","");
    classCache.add(classPath);
    }
    }
    return true;
    } });
    } catch (URISyntaxException e) {
    e.printStackTrace();
    } } /**
    * 根据类路径获得 class 对象
    */
    private void registerBean() {
    if(classCache.isEmpty()){
    return;
    } for(String path:classCache){
    try {
    //使用反射,通过类路径获取class 对象
    Class<?> clazz = Class.forName(path);
    //找出需要被容器管理的类,比如,@Component,@Controller,@Service,@Repository
    if(clazz.isAnnotationPresent(Repository.class)||clazz.isAnnotationPresent(Service.class)
    ||clazz.isAnnotationPresent(Controller.class)|| clazz.isAnnotationPresent(Component.class)){
    beanDefinition.add(clazz);
    }
    } catch (ClassNotFoundException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
    } } /**
    *
    * 根据类对象,创建实例
    */
    private void doCreateBean() {
    if(beanDefinition.isEmpty()){
    return;
    } for(Class clazz:beanDefinition){
    try {
    Object instance = clazz.newInstance();
    //将首字母小写的类名作为默认的 bean 的名字
    String aliasName = lowerClass(clazz.getSimpleName());
    //先判断@ 注解里面是否给了 Bean 名字,有的话,这个就作为 Bean 的名字
    if(clazz.isAnnotationPresent(Repository.class)){
    Repository repository = (org.springframework.ioc.annotation.Repository) clazz.getAnnotation(Repository.class);
    if(!"".equals(repository.value())){
    aliasName = repository.value();
    }
    }
    if(clazz.isAnnotationPresent(Service.class)){
    Service service = (org.springframework.ioc.annotation.Service) clazz.getAnnotation(Service.class);
    if(!"".equals(service.value())){
    aliasName = service.value();
    }
    }
    if(clazz.isAnnotationPresent(Controller.class)){
    Controller controller = (org.springframework.ioc.annotation.Controller) clazz.getAnnotation(Controller.class);
    if(!"".equals(controller.value())){
    aliasName = controller.value();
    }
    }
    if(clazz.isAnnotationPresent(Component.class)){
    Component component = (org.springframework.ioc.annotation.Component) clazz.getAnnotation(Component.class);
    if(!"".equals(component.value())){
    aliasName = component.value();
    }
    }
    if(beanFactory.get(aliasName)== null){
    beanFactory.put(aliasName, instance);
    } //判断当前类是否实现了接口
    Class<?>[] interfaces = clazz.getInterfaces();
    if(interfaces == null){
    continue;
    }
    //把当前接口的路径作为key存储到容器中
    for(Class<?> interf:interfaces){
    beanFactory.put(interf.getName(), instance);
    } } catch (InstantiationException e) {
    e.printStackTrace();
    } catch (IllegalAccessException e) {
    e.printStackTrace();
    }
    } for (Entry<String, Object> entry : beanFactory.entrySet()) {
    System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
    } } /**
    * 对创建好的对象进行依赖注入
    */
    private void diBean() {
    if(beanFactory.isEmpty()){
    return;
    } for(Class<?> clazz:beanDefinition){
    String aliasName = lowerClass(clazz.getSimpleName());
    //先判断@ 注解里面是否给了 Bean 名字,有的话,这个就作为 Bean 的名字
    if(clazz.isAnnotationPresent(Repository.class)){
    Repository repository = (org.springframework.ioc.annotation.Repository) clazz.getAnnotation(Repository.class);
    if(!"".equals(repository.value())){
    aliasName = repository.value();
    }
    }
    if(clazz.isAnnotationPresent(Service.class)){
    Service service = (org.springframework.ioc.annotation.Service) clazz.getAnnotation(Service.class);
    if(!"".equals(service.value())){
    aliasName = service.value();
    }
    }
    if(clazz.isAnnotationPresent(Controller.class)){
    Controller controller = (org.springframework.ioc.annotation.Controller) clazz.getAnnotation(Controller.class);
    if(!"".equals(controller.value())){
    aliasName = controller.value();
    }
    }
    if(clazz.isAnnotationPresent(Component.class)){
    Component component = (org.springframework.ioc.annotation.Component) clazz.getAnnotation(Component.class);
    if(!"".equals(component.value())){
    aliasName = component.value();
    }
    } //根据别名获取到被装配的 bean 的实例
    Object instance = beanFactory.get(aliasName);
    try{
    //从类中获取参数,判断是否有 @Autowired 注解
    Field[] fields = clazz.getDeclaredFields();
    for(Field f:fields){
    if(f.isAnnotationPresent(Autowired.class)){
    System.out.println("12312312312123");
    //开启字段的访问权限
    f.setAccessible(true);
    Autowired autoWired = f.getAnnotation(Autowired.class);
    if(!"".equals(autoWired.value())){
    System.out.println("111111111111111111111");
    //注解里写了别名
    f.set(instance, beanFactory.get(autoWired.value())); }else{
    //按类型名称
    String fieldName = f.getType().getName();
    f.set(instance, beanFactory.get(fieldName));
    }
    }
    }
    }catch(Exception e){
    e.printStackTrace();
    } } } private String lowerClass(String simpleName) {
    char[] chars = simpleName.toCharArray();
    chars[0] += 32;
    return chars.toString();
    } }
  7. 在 Demo 模块中定义类似与 SpringMVC 的三层架构,并在 Service 层注入 Dao,在 Dao 层我们只打印了一句话,为了验证 DI 成功。

     package org.Demo;
    
     import static org.junit.Assert.assertTrue;
    
     import org.junit.Test;
    import org.springframework.ioc.bean.AnnotationApplicationContext;
    import org.springframework.ioc.bean.ApplicationContext; import com.spring.demo.Service.BookService; /**
    * Unit test for simple App.
    */
    public class AppTest
    {
    public static void main(String[] args){ ApplicationContext ctx = new AnnotationApplicationContext("applicationContext.xml");
    BookService bookService = (BookService) ctx.getBean("bookService");
    bookService.action();
    }
    }
  8. 测试结果:

     // 容器中的 Bean
    Key = bookDao, Value = com.spring.demo.Dao.BookDaoImpl@3d494fbf
    Key = [C@4fca772d, Value = com.spring.demo.controller.BookController@1ddc4ec2
    Key = bookService, Value = com.spring.demo.Service.BookServiceImpl@133314b
    Key = com.spring.demo.Dao.BookDao, Value = com.spring.demo.Dao.BookDaoImpl@3d494fbf
    Key = com.spring.demo.Service.BookService, Value = com.spring.demo.Service.BookServiceImpl@133314b // service 调用 dao 层的方法,成功打印。
    我在读书

    这样一个 IOC/DI 容器就构建成功了,整个项目源码在 github,希望大家 star 一下,一起改进(多提 pull request)。

    项目源码

手动实现一个 IOC/DI 容器的更多相关文章

  1. StructureMap经典的IoC/DI容器

    StructureMap是一款很老的IoC/DI容器,从2004年.NET 1.1支持至今. 一个使用例子 //创建业务接口 public interface IDispatchService { } ...

  2. String框架搭建的基本步骤,及从 IOC & DI 容器中获取 Bean(spring框架bean的配置)--有实现数据库连接池的链接

    Spring框架的插件springsource-tool-suite-3.4.0.RELEASE-e4.3.1-updatesite(是一个压缩包)导入步骤: eclipse->help-> ...

  3. 深入理解IoC/DI

    ------------------------------------------------------------------------ 理解IoC/DI 1.控制反转 --> 谁控制谁 ...

  4. IoC/DI基本思想的演变

    ---------------------------------------------------------------------------------- (1)IoC/DI的概念 IoC ...

  5. 工厂方法模式与IoC/DI

    IoC——Inversion of Control  控制反转 DI——Dependency Injection   依赖注入 1:如何理解IoC/DI        要想理解上面两个概念,就必须搞清 ...

  6. spring--学习之IOC DI

    2.1.1  IoC是什么 Ioc-Inversion of Control,即"控制反转",不是什么技术,而是一种设计思想.在Java开发中,Ioc意味着将你设计好的对象交给容器 ...

  7. spring ioc DI 理解

    下面是我从网上找来的一些大牛对spring ioc和DI的理解,希望也能让你对Spring ioc和DI的设计思想有更进一步的认识. 一.分享Iteye的开涛对Ioc的精彩讲解 Ioc—Inversi ...

  8. 工厂方法模式与IoC/DI控制反转和依赖注入

    IoC——Inversion of Control  控制反转 DI——Dependency Injection   依赖注入 要想理解上面两个概念,就必须搞清楚如下的问题: 参与者都有谁? 依赖:谁 ...

  9. IoC/DI

    From:http://jinnianshilongnian.iteye.com/blog/1471944 我对IoC/DI的理解 博客分类: spring杂谈 IoCDI  IoC IoC: Inv ...

随机推荐

  1. echarts 堆叠折线

    option = { title: { text: '折线图堆叠' }, tooltip: { trigger: 'axis' }, legend: { data:['邮件营销','联盟广告','视频 ...

  2. 1.跟着微软 https://docs.microsoft.com/zh-cn/dotnet/core/ 学习.net core

    10分钟快速使用 安装之后 打开cmd 第一步. dotnet new console -o firstApp 第二步. cd firstApp 第三部.dotnet run 这样就运行了hello ...

  3. XMPPFramework

    XMPP Extensible Messaging and Present Protocol 可扩展消息处理现场协议 特征: XMPP使用tcp传XML流程, 做IM xmpp.org objcio. ...

  4. qt的应用层主要是大型3d,vr,管理软件和器械嵌入软件(有上千个下一代软件黑科技项目是qt的,美国宇航局,欧洲宇航局,超级战舰DDG1000)

    作者:Nebula.Trek链接:https://www.zhihu.com/question/24316868/answer/118944490来源:知乎著作权归作者所有.商业转载请联系作者获得授权 ...

  5. SecureCRT 专题

    SecureCRT在同一窗口打开多个标签:选中“在标签页中打开”即可 SecureCRT同时向多个tab窗口发送相同的命令 Step by step: 作为管理N台服务器,而又要执行相同命令又不想用脚 ...

  6. 中英文对照 —— 手机 App/PC 端软件(系统)、互联网

    0. 经典 & 缩略词 SMS:short message service,短信息服务, SMS code,短信验证码: swipe:vt. 猛击:偷窃:刷-卡 Swipe up/down/r ...

  7. Cocos2d-x移植WP8时间CCScrollView问题

    cocos2d-x 2.2中的CCScrollView和CCTableView存在bug.导致区域裁剪错误 我是这样解决的. 在CCEGLView::setScissorInPoints里.依据不同旋 ...

  8. NetCore使用Jwtbearer给WebAPI添加访问控制

    原文:NetCore使用Jwtbearer给WebAPI添加访问控制 现在JWT代替session来做访问控制已经成为大部分webapi的做法,今天我们也来尝试一下 WebAPI使用NetCore2. ...

  9. 在mac中如何清除.svn文件

    有些时候在开发一个应用程序我们需要用到版本控制,它可以帮助我们很好的控制我们程序的代码,尤其在多人开发的时候,优点尤为突出. 但是在有些情况下我们又认为这些.svn真的很麻烦,那么我们怎么把他们一下子 ...

  10. openFrameworks 是一个旨在助力你进行开创性工作的开源 C++ 工具箱(是很多其它类库的组合)

    openFrameworks 是一个旨在助力你进行开创性工作的开源 C++ 工具箱,提供了简单且直观的实验框架.该工具箱具有常见的工具,并集合了众多常见的库: OpenGL, GLEW, GLUT,  ...