手写代码实现spring的ioc功能

本篇文章和大家一起交流一下有关spring的ioc知识,并自己通过代码实现一个简单版的spring的IOC功能。首先看一下IOC基础知识的分享:

一、分享Iteye的开涛对Ioc的精彩讲解

  首先要分享的是Iteye的开涛这位技术牛人对Spring框架的IOC的理解,写得非常通俗易懂,以下内容全部来自原文,原文地址:http://jinnianshilongnian.iteye.com/blog/1413846

1.1、IoC是什么

  Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。如何理解好Ioc呢?理解好Ioc的关键是要明确“谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了”,那我们来深入分析一下:

  ●谁控制谁,控制什么传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对 象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)

  ●为何是反转,哪些方面反转了有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。

  用图例说明一下,传统程序设计如图2-1,都是主动去创建相关对象然后再组合起来:

图1-1 传统应用程序示意图

  当有了IoC/DI的容器后,在客户端类中不再主动去创建这些对象了,如图2-2所示:

图1-2有IoC/DI容器后程序结构示意图

1.2、IoC能做什么

  IoC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是 松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。

  其实IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源了。

  IoC很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找我们,我们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。

1.3、IoC和DI

  DI—Dependency Injection,即“依赖注入”组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

  理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:

  ●谁依赖于谁:当然是应用程序依赖于IoC容器

  ●为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源

  ●谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象

  ●注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)

  IoC和DI由什么关系呢?其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”

  看过很多对Spring的Ioc理解的文章,好多人对Ioc和DI的解释都晦涩难懂,反正就是一种说不清,道不明的感觉,读完之后依然是一头雾水,感觉就是开涛这位技术牛人写得特别通俗易懂,他清楚地解释了IoC(控制反转) 和DI(依赖注入)中的每一个字,读完之后给人一种豁然开朗的感觉。我相信对于初学Spring框架的人对Ioc的理解应该是有很大帮助的。

二、分享Bromon的blog上对IoC与DI浅显易懂的讲解

2.1、IoC(控制反转)

  首先想说说IoC(Inversion of Control,控制反转)。这是spring的核心,贯穿始终。所谓IoC,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系。这是什么意思呢,举个简单的例子,我们是如何找女朋友的?常见的情况是,我们到处去看哪里有长得漂亮身材又好的mm,然后打听她们的兴趣爱好、qq号、电话号、ip号、iq号………,想办法认识她们,投其所好送其所要,然后嘿嘿……这个过程是复杂深奥的,我们必须自己设计和面对每个环节。传统的程序开发也是如此,在一个对象中,如果要使用另外的对象,就必须得到它(自己new一个,或者从JNDI中查询一个),使用完之后还要将对象销毁(比如Connection等),对象始终会和其他的接口或类藕合起来。

  那么IoC是如何做的呢?有点像通过婚介找女朋友,在我和女朋友之间引入了一个第三者:婚姻介绍所。婚介管理了很多男男女女的资料,我可以向婚介提出一个列表,告诉它我想找个什么样的女朋友,比如长得像李嘉欣,身材像林熙雷,唱歌像周杰伦,速度像卡洛斯,技术像齐达内之类的,然后婚介就会按照我们的要求,提供一个mm,我们只需要去和她谈恋爱、结婚就行了。简单明了,如果婚介给我们的人选不符合要求,我们就会抛出异常。整个过程不再由我自己控制,而是有婚介这样一个类似容器的机构来控制。Spring所倡导的开发方式就是如此,所有的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。

2.2、DI(依赖注入)

  IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的。比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了 spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖 Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。那么DI是如何实现的呢? Java 1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的。

  理解了IoC和DI的概念后,一切都将变得简单明了,剩下的工作只是在spring的框架中堆积木而已。

理论知识知道了,为了加深理解,我们自己可以手动写代码实现一个简单版的spring的IOC,看项目概况图:


annoation:定义常用的两个依赖注解,一个是标致service的,一个是service的属性依赖,看具体代码

package com.ioc.spring.annoation;

import java.lang.annotation.*;

/**
 * @Author 18011618
 * @Description 自定义服务的依赖注入
 * @Date 16:12 2018/6/18
 * @Modify By
 */
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface IocService {
    String name() default "";
}
 
 

package com.ioc.spring.annoation;

import java.lang.annotation.*;

/**
 * @Author 18011618
 * @Description 自定义属性的依赖注入
 * @Date 16:13 2018/6/18
 * @Modify By
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface IocResource {
}

service:定义两个简单演示的service,并且增加对应的注解

订单服务:

package com.ioc.spring.service.impl;

import com.ioc.spring.annoation.IocService;
import com.ioc.spring.service.IOrderService;

/**
 * @Author 18011618
 * @Description
 * @Date 15:56 2018/6/18
 * @Modify By
 */
@IocService
public class OrderService implements IOrderService {
    public String findOrder(String username) {
        return "用户"+username+"的订单编号是:1001";
    }
}
 
 

用户服务:依赖于订单服务

package com.ioc.spring.service.impl;

import com.ioc.spring.annoation.IocResource;
import com.ioc.spring.annoation.IocService;
import com.ioc.spring.service.IOrderService;
import com.ioc.spring.service.IUserService;

/**
 * @Author 18011618
 * @Description
 * @Date 15:53 2018/6/18
 * @Modify By
 */
@IocService(name = "userbiz")
public class UserService implements IUserService {

    /*比较脆弱啊 这块的属性名称一定要用实现类来命名 且 按照第一个字母要小写的原则 否则很报错的*/
    @IocResource
    private IOrderService orderService;
    public String findOrder(String username) {
        return orderService.findOrder(username);
    }
}
 
 

util:定义一个工具类主要扫描某个包路径下面的所有class

package com.ioc.spring.util;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * 获取某个包下面的所有类信息
 */
public class ClassUtil {

   /**
    * 取得某个接口下所有实现这个接口的类
    */
   public static List<Class> getAllClassByInterface(Class c) {
      List<Class> returnClassList = null;

      if (c.isInterface()) {
         // 获取当前的包名
         String packageName = c.getPackage().getName();
         // 获取当前包下以及子包下所以的类
         List<Class<?>> allClass = getClasses(packageName);
         if (allClass != null) {
            returnClassList = new ArrayList<Class>();
            for (Class classes : allClass) {
               // 判断是否是同一个接口
               if (c.isAssignableFrom(classes)) {
                  // 本身不加入进去
                  if (!c.equals(classes)) {
                     returnClassList.add(classes);
                  }
               }
            }
         }
      }

      return returnClassList;
   }

   /*
    * 取得某一类所在包的所有类名 不含迭代
    */
   public static String[] getPackageAllClassName(String classLocation, String packageName) {
      // packageName分解
      String[] packagePathSplit = packageName.split("[.]");
      String realClassLocation = classLocation;
      int packageLength = packagePathSplit.length;
      for (int i = 0; i < packageLength; i++) {
         realClassLocation = realClassLocation + File.separator + packagePathSplit[i];
      }
      File packeageDir = new File(realClassLocation);
      if (packeageDir.isDirectory()) {
         String[] allClassName = packeageDir.list();
         return allClassName;
      }
      return null;
   }

   /**
    * 从包package中获取所有的Class
    * @param packageName
    * @return
    */
   public static List<Class<?>> getClasses(String packageName) {

      // 第一个class类的集合
      List<Class<?>> classes = new ArrayList<Class<?>>();
      // 是否循环迭代
      boolean recursive = true;
      // 获取包的名字 并进行替换
      String packageDirName = packageName.replace('.', '/');
      // 定义一个枚举的集合 并进行循环来处理这个目录下的things
      Enumeration<URL> dirs;
      try {
         dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
         // 循环迭代下去
         while (dirs.hasMoreElements()) {
            // 获取下一个元素
            URL url = dirs.nextElement();
            // 得到协议的名称
            String protocol = url.getProtocol();
            // 如果是以文件的形式保存在服务器上
            if ("file".equals(protocol)) {
               // 获取包的物理路径
               String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
               // 以文件的方式扫描整个包下的文件 并添加到集合中
               findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes);
            } else if ("jar".equals(protocol)) {
               // 如果是jar包文件
               // 定义一个JarFile
               JarFile jar;
               try {
                  // 获取jar
                  jar = ((JarURLConnection) url.openConnection()).getJarFile();
                  // 从此jar包 得到一个枚举类
                  Enumeration<JarEntry> entries = jar.entries();
                  // 同样的进行循环迭代
                  while (entries.hasMoreElements()) {
                     // 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
                     JarEntry entry = entries.nextElement();
                     String name = entry.getName();
                     // 如果是以/开头的
                     if (name.charAt(0) == '/') {
                        // 获取后面的字符串
                        name = name.substring(1);
                     }
                     // 如果前半部分和定义的包名相同
                     if (name.startsWith(packageDirName)) {
                        int idx = name.lastIndexOf('/');
                        // 如果以"/"结尾 是一个包
                        if (idx != -1) {
                           // 获取包名 把"/"替换成"."
                           packageName = name.substring(0, idx).replace('/', '.');
                        }
                        // 如果可以迭代下去 并且是一个包
                        if ((idx != -1) || recursive) {
                           // 如果是一个.class文件 而且不是目录
                           if (name.endsWith(".class") && !entry.isDirectory()) {
                              // 去掉后面的".class" 获取真正的类名
                              String className = name.substring(packageName.length() + 1, name.length() - 6);
                              try {
                                 // 添加到classes
                                 classes.add(Class.forName(packageName + '.' + className));
                              } catch (ClassNotFoundException e) {
                                 e.printStackTrace();
                              }
                           }
                        }
                     }
                  }
               } catch (IOException e) {
                  e.printStackTrace();
               }
            }
         }
      } catch (IOException e) {
         e.printStackTrace();
      }

      return classes;
   }

   /**
    * 以文件的形式来获取包下的所有Class
    * 
    * @param packageName
    * @param packagePath
    * @param recursive
    * @param classes
    */
   public static void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive,
         List<Class<?>> classes) {
      // 获取此包的目录 建立一个File
      File dir = new File(packagePath);
      // 如果不存在或者 也不是目录就直接返回
      if (!dir.exists() || !dir.isDirectory()) {
         return;
      }
      // 如果存在 就获取包下的所有文件 包括目录
      File[] dirfiles = dir.listFiles(new FileFilter() {
         // 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
         public boolean accept(File file) {
            return (recursive && file.isDirectory()) || (file.getName().endsWith(".class"));
         }
      });
      // 循环所有文件
      for (File file : dirfiles) {
         // 如果是目录 则继续扫描
         if (file.isDirectory()) {
            findAndAddClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive,
                  classes);
         } else {
            // 如果是java类文件 去掉后面的.class 只留下类名
            String className = file.getName().substring(0, file.getName().length() - 6);
            try {
               // 添加到集合中去
               classes.add(Class.forName(packageName + '.' + className));
            } catch (ClassNotFoundException e) {
               e.printStackTrace();
            }
         }
      }
   }
}

context:定义模拟spring的上下文,初始化bean对象和对应的属性对象

package com.ioc.spring.context;

import com.ioc.spring.annoation.IocResource;
import com.ioc.spring.annoation.IocService;
import com.ioc.spring.util.ClassUtil;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Author 18011618
 * @Description 模拟spring容器 管理对象的依赖注入
 * @Date 16:17 2018/6/18
 * @Modify By
 */
public class SpringContext {
    private String path;
    /*String :beanId Object:serviceimpl*/
    ConcurrentHashMap<String,Object>initBean = null;

    public SpringContext(String path){
        this.path = path;
    }


    /**
     * 根据beanid获取对应的bean
     * @param beanId
     * @return
     * @throws Exception
     */
    public Object getBean(String beanId) throws Exception{
        List<Class> classes = findAnnoationService();
        if (classes == null || classes.isEmpty()) {
            throw new Exception("no found anything bean is useding initial..");
        }
        initBean = initBean(classes);
        if (initBean == null || initBean.isEmpty()) {
            throw new Exception("initial bean is empty or null");
        }
        Object object = initBean.get(beanId);
        //初始化属性的依赖
        initAttribute(object);
        return object;
    }

    /**
     * 初始化依赖的属性
     * @param object
     * @throws IllegalArgumentException
     * @throws IllegalAccessException
     */
    private void initAttribute(Object object)throws Exception{
        //获取object的所有类型
        Class<? extends Object> classinfo = object.getClass();
        //获取所有的属性字段
        Field[] fields = classinfo.getDeclaredFields();
        //遍历所有字段
        for(Field field : fields){
            //查找字段上有依赖的注解
            boolean falg = field.isAnnotationPresent(IocResource.class);
            if (falg){
                IocResource iocResource = field.getAnnotation(IocResource.class);
                if (iocResource!=null){
                    //获取属性的beanid
                    String beanId = field.getName();
                    //获取对应的object
                    Object attrObject = getBean(beanId);
                    if (attrObject!=null){
                        //访问私有字段
                        field.setAccessible(true);
                        //赋值
                        field.set(object,attrObject);
                        continue;
                    }
                }
            }
        }
    }

    /**
     * 初始化bean
     * @param classes
     * @return
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    public ConcurrentHashMap<String,Object>initBean(List<Class>classes) throws IllegalAccessException,InstantiationException{
        ConcurrentHashMap<String,Object> map = new ConcurrentHashMap<String, Object>();
        String beanId="";
        for(Class clazz :classes){
            Object object = clazz.newInstance();
            IocService annotation =(IocService)clazz.getDeclaredAnnotation(IocService.class);
            if (annotation!=null){
                //如果定义了name属性 以实现的name属性为主否则以默认的规则为主
                String value = annotation.name();
                if (value!=null && !value.equals("")){
                    beanId = value;
                }
                else {
                    beanId = toLowerCaseFirstOne(clazz.getSimpleName());
                }
            }

           //存储值
            map.put(beanId,object);
        }
        return map;
    }

    /**
     * 查找包路径下面所有添加注解的类 @IocService
     * @return
     * @throws Exception
     */
    private List<Class>findAnnoationService()throws Exception{
        if (path==null || path.equals("")){
            throw new Exception("scan package address is null or empty..");
        }
        //获取包下面所有的类
        List<Class<?>> classes = ClassUtil.getClasses(path);
        if (classes==null || classes.size()==0){
            throw new Exception("not found service is added annoation for @iocservice");
        }
        List<Class> annoationClasses = new ArrayList<Class>();
        for (Class clazz:classes){
            //通过反射机制 查找增加了注解的类
            IocService iocService = (IocService) clazz.getDeclaredAnnotation(IocService.class);
            if (iocService!=null){
                annoationClasses.add(clazz);
                continue;
            }
        }
        return annoationClasses;
    }


    /**
     * 首字母转换为小写
     * @param s
     * @return
     */
    public static String toLowerCaseFirstOne(String s) {
        if (Character.isLowerCase(s.charAt(0))){
            return s;
        }
        else{
            return (new StringBuilder()).append(Character.toLowerCase(s.charAt(0))).append(s.substring(1)).toString();
        }
    }


}
 
 

ok,到此为止一个简单版的spring的IOC功能就完成了,我们可以写个测试类来测试一下:

package com.ioc.spring.test;

import com.ioc.spring.context.SpringContext;
import com.ioc.spring.service.IUserService;

/**
 * @Author 18011618
 * @Description
 * @Date 16:45 2018/6/18
 * @Modify By
 */
public class SpringIocTest {
    public static void main(String[] args)throws Exception {
        String path = "com.ioc.spring.service.impl";
        SpringContext context = new SpringContext(path);
        IUserService userService = (IUserService) context.getBean("userbiz");
        System.out.println(userService.findOrder("lyl"));
    }
}
 
 

代码很少,我们总结一下实现的思落过程

1 首先需要实现类似于spring的componetscan:自动包扫描

2 找到该包下面哪些类是加了对应的注解

3 找到这些加了注解的类,进行模拟spring初始化功能,beanid和bean实例化功能

4 对应的依赖属性初始化:主要通过反射+已经实例化好的bean集合

注意:上面的代码只是帮助我们更好的理解IOC机制,但和原始的spring的IOC相比是不可相提并论的,建议大家还是去看看spring源码.

猜你喜欢

转载自blog.csdn.net/qq_18603599/article/details/80725330