spring的ioc容器的实现原理(附测试代码)

 spring如今在java开源框架中大行其道,很受欢迎,是轻量级JAVA EE中的核心框架,企业级应用信息系统开发的首选框架,它不愧是是JAVA中重量级框架EJB强大挑战对手。因为其灵活的扩展性和伸缩性,灵活简单的配置(采用xml文件和Annotation注解等),可以和众多的开源框架进行有效的整合资源,让它既可以和struts,jsf这些表现层框架,也能和hibernate,ibatis这些中间件框架可以无缝链接,还有dwr这些ajax框架!总之,spring真是个神奇而又伟大的东西!而其中,它的IOC和AOP则是spring中的核心应用.阿堂一直对spring中的IOC功能的实现,有些好奇,想探个究竟,看看这些世界顶级高手是如何实现的.阿堂在幸读到了疯狂java书籍李刚先生的《轻量级JAVA EE企业应用实战》一书中,讲架构模式时,给出sping 的ioc容器相关原理的简单代码实现后,让阿堂看了后,豁然开朗,原来spring的底层的实现原理是这样的,觉得很有收获,如下,就有如下这遍文章的出现了。看高手写的东西,会让你以后开发时,会从另一个新的高度上来看问题!多看看开源世界的成熟框架的代码,是非常重要的,这也许是高手学习必须经历的过程吧!
  下面,我就来和朋友一起来分享一下了!(在本篇文章,阿堂会附上详细的测试代码)
  通常情况下,我们使用spring的时候,会将spring配置成Listener写在web.xml中,让web容器初始化的时候,也同时加载了spring了容器,sping容器在加载的时候,实际上,它也是要进行初始化,数据源的初始化,完成bean类的实例化,bean类的依赖注入的属性的初始化等工作,下面我要分享的代码,正是基于这样的思路来进行的
   在附上如下测试代码之前,我先对李刚先生的spring的ioc代码的实现原理的代码功能,作一下简单的说明,要不然,估计会有一些朋友可能不一定能看懂这些代码的执行,因为里面有很多java反射方面的知识!
主要的几点,阿堂说明如下
(1)这些代码中,关键就是YeekuXmlApplicationContext类,它里面有两个重要的初始化方法,一个是initPool(),它主要是初始化容器中所有singleton Bean,另一个是initProp(),它主要是初始化容器中singleton Bean的属性
(2)如下代码中的ioc容器,也同样采用bean.xml配置文件的方式来管理bean,其中,主要讲了两种常见的bean类,其作用域是singleton和prototype类型
(3)在加载的时候,首先会用一个Map来封装bean对应的id号和Class类的实例(对象)
(4)其中,有如下一段代码,需要认真体会,刚开始,阿堂还真没看懂,后来,我查了jdk的api才真正弄懂(api说明我附在后面)
      Method setter = null;
      //遍历target对象所所实现的所有接口
      for (Class superInterface : target.getClass().getInterfaces())
      {
       try
       {
        //获取设值注入所需的setter方法
        setter = bean.getClass().getMethod(
         "set" + propNameCamelize , superInterface);
        //如果成功取得该接口对应的方法,直接跳出循环
        break;
       }
       catch (NoSuchMethodException ex)
       {
        //如果没有找到对应的setter方法,继续下次循环
        continue;
       }
      }
     

实际上,对于此例中,上面的break语句,可以注释掉,因为BetterPrinter implements Output本来就只实现了一个接口,如果是多个接口,此处的break是不能省掉的

 


在jdk的api中,有如下说明(我就不解释了)
public Class[] getInterfaces()确定此对象所表示的类或接口实现的接口。
如果此对象表示一个类,则返回值是一个数组,它包含了表示该类所实现的所有接口的对象。数组中接口对象的顺序与此对象所表示的类的声明的 implements 子句中的接口名顺序一致。例如,给定声明:

 class Shimmer implements FloorWax, DessertTopping { ... }
设 s 的值为 Shimmer 的一个实例;表达式:
 s.getClass().getInterfaces()[0]
 的值为表示FloorWax 接口的 Class 对象;
 s.getClass().getInterfaces()[1]
 的值为表示 DessertTopping 接口的 Class 对象。
如果此对象表示一个接口,则该数组包含表示该接口扩展的所有接口的对象。数组中接口对象的顺序与此对象所表示的接口的声明的 extends 子句中的接口名顺序一致。

如果此对象表示一个不实现任何接口的类或接口,则此方法返回一个长度为 0 的数组。

如果此对象表示一个基本类型或 void,则此方法返回一个长度为 0 的数组。


返回:
该类所实现的接口的一个数组。

测试效果图

淡淡spring的ioc容器的实现原理(附测试代码) - zhang8mss - zhang8mss的博客


下面附上完整代码

测试类IoCTest

public class IoCTest
{
 public static void main(String[] args)
  throws Exception
 {
  //创建IoC容器
  ApplicationContext ctx =
   new YeekuXmlApplicationContext("bean.xml");
  //从IoC容器中取出computer Bean
  Computer c = (Computer)ctx.getBean("computer");
  //测试Computer对象
  c.keyIn("疯狂Java讲义");
  c.keyIn("Struts2权威指南");
  c.print();
  System.out.println(ctx.getBean("now"));
 }
}

 

关键的实现类YeekuXmlApplicationContext

public class YeekuXmlApplicationContext
 implements ApplicationContext
{
 //保存容器中所有单例模式的Bean实例
 private Map<String , Object> objPool
  = Collections.synchronizedMap(new HashMap<String , Object>());
 //保存配置文件对应的Document对象
 private Document doc;
 //保存配置文件里的根元素
 private Element root;
 public YeekuXmlApplicationContext(String filePath)
  throws Exception
 {
  SAXReader reader = new SAXReader();
  String path=System.getProperties().getProperty("user.dir")+"\\src\\";
  doc = reader.read(path+filePath);
  root = doc.getRootElement();
  initPool();
  initProp();
 }

 public Object getBean(String name)
  throws Exception
 {
  Object target = objPool.get(name);
  //对于singleton Bean,容器已经初始化了所有Bean实例
  if (target.getClass() != String.class)
  {
   return target;
  }
  else //这种情况对于非singlton Bean
  {
   String clazz = (String)target;
   //对于prototype并未注入属性值
   return Class.forName(clazz).newInstance();
  }
 }
 //初始化容器中所有singleton Bean
 private void initPool()
  throws Exception
 {
  //遍历配置文件里的每个<bean.../>元素
  for (Object obj : root.elements())
  {
   Element beanEle = (Element)obj;
   //取得<bean.../>元素的id属性
   String beanId = beanEle.attributeValue("id");
   //取得<bean.../>元素的class属性
   String beanClazz = beanEle.attributeValue("class");
   //取得<bean.../>元素的scope属性
   String beanScope = beanEle.attributeValue("scope");
   //如果<bean.../>元素的scope属性不存在,或为singleton
   if (beanScope == null ||
    beanScope.equals("singleton"))
   {
    //以默认构造器创建Bean实例,并将其放入objPool中
    objPool.put(beanId , Class.forName(beanClazz).newInstance());
   }
   else
   {
    //对于非singlton Bean,存放该Bean实现类的类名。
    objPool.put(beanId , beanClazz);
   }
  }
 }
 //初始化容器中singleton Bean的属性
 private void initProp()
  throws Exception
 {
  //遍历配置文件里的每个<bean.../>元素
  for (Object obj : root.elements())
  {
   Element beanEle = (Element)obj;
   //取得<bean.../>元素的id属性
   String beanId = beanEle.attributeValue("id");
   //取得<bean.../>元素的scope属性
   String beanScope = beanEle.attributeValue("scope");
   //如果<bean.../>元素的scope属性不存在,或为singleton
   if (beanScope == null ||
    beanScope.equals("singleton"))
   {
    //取出objPool的指定的Bean实例
    Object bean = objPool.get(beanId);
    
    //遍历<bean.../>元素的每个<property.../>子元素
    for (Object prop : beanEle.elements())
    {
     Element propEle = (Element)prop;
     //取得<property.../>元素的name属性 
     String propName = propEle.attributeValue("name");
     //取得<property.../>元素的value属性
     String propValue = propEle.attributeValue("value");
     //取得<property.../>元素的ref属性 
     String propRef = propEle.attributeValue("ref");
     //将属性名的首字母大写
     String propNameCamelize = propName.substring(0 , 1).toUpperCase()
      + propName.substring(1 , propName.length());
     
     //如果<property.../>元素的value属性值存在
     if (propValue != null && propValue.length() > 0)
     {
      //获取设值注入所需的setter方法
      Method setter = bean.getClass().getMethod(
       "set" + propNameCamelize , String.class);
      //执行setter注入
      setter.invoke(bean , propValue);
     }
     if (propRef != null && propRef.length() > 0)
     {
      //取得需要被依赖注入的Bean实例
      Object target = objPool.get(propRef);
      //objPool池中不存在指定Bean实例
      if (target == null)
      {
       //此处还应处理Singleton Bean依赖prototype Bean的情形
      }
      //定义设值注入所需的setter方法
      Method setter = null;
      //遍历target对象所所实现的所有接口
      for (Class superInterface : target.getClass().getInterfaces())
      {
       try
       {
        //获取设值注入所需的setter方法
        setter = bean.getClass().getMethod(
         "set" + propNameCamelize , superInterface);
        //如果成功取得该接口对应的方法,直接跳出循环
        break;
       }
       catch (NoSuchMethodException ex)
       {
        //如果没有找到对应的setter方法,继续下次循环
        continue;
       }
      }
      //如果setter方法依然为null,
      //则直接取得target实现类对应的setter方法
      if (setter == null)
      {
       setter = bean.getClass().getMethod(
        "set" + propNameCamelize , target.getClass());
      }
      //执行setter注入
      setter.invoke(bean , target);
     }
    }
   }
  }
 }
}

spring的ioc容器的配置文件bean.xml

<?xml version="1.0" encoding="GBK"?>
<beans> 
 <bean id="computer" class="lee.Computer">
  <!-- 为name属性注入普通属性值 -->
  <property name="name" value="网络时空的电脑"/>
  <!-- 为out属性注入普通属性值 -->
  <property name="out" ref="betterPrinter"/>
 </bean>
 <!-- 配置两个Bean实例 -->
 <bean id="printer" class="lee.Printer"/>
 <bean id="betterPrinter" class="lee.BetterPrinter"/>
 <!-- 配置一个prototype行为的Bean实例 -->
    <bean id="now" class="java.util.Date" scope="prototype"/>
</beans>

 

接口类Output

public interface Output
{
 //接口里定义的属性只能是常量
 int MAX_CACHE_LINE = 50;
 //接口里定义的只能是public的抽象实例方法
 void out();
 void getData(String msg);
}

接口类

public interface ApplicationContext
{
 //获取指定Bean实例的方法
 Object getBean(String name)
  throws Exception;
}

实现类BetterPrinter

public class BetterPrinter implements Output
{
 private String[] printData = new String[MAX_CACHE_LINE * 2];
 //用以记录当前需打印的作业数
 private int dataNum = 0;
 public void out()
 {
  //只要还有作业,继续打印
  while(dataNum > 0)
  {
   System.out.println("高速打印机正在打印:" + printData[0]);
   //把作业队列整体前移一位,并将剩下的作业数减1
   System.arraycopy(printData , 1, printData, 0, --dataNum);
  }
 }
 public void getData(String msg)
 {
  if (dataNum >= MAX_CACHE_LINE * 2)
  {
   System.out.println("输出队列已满,添加失败");
  }
  else
  {
   //把打印数据添加到队列里,已保存数据的数量加1。
   printData[dataNum++] = msg;
  }
 }
}

实现类Printer

public class Printer implements Output
{
 private String[] printData = new String[MAX_CACHE_LINE];
 //用以记录当前需打印的作业数
 private int dataNum = 0;
 public void out()
 {
  //只要还有作业,继续打印
  while(dataNum > 0)
  {
   System.out.println("打印机打印:" + printData[0]);
   //把作业队列整体前移一位,并将剩下的作业数减1
   System.arraycopy(printData , 1, printData, 0, --dataNum);
  }
 }
 public void getData(String msg)
 {
  if (dataNum >= MAX_CACHE_LINE)
  {
   System.out.println("输出队列已满,添加失败");
  }
  else
  {
   //把打印数据添加到队列里,已保存数据的数量加1。
   printData[dataNum++] = msg;
  }
 }
}

 

普通类Computer

public class Computer
{
 private Output out;
 private String name;

 public Computer(){}

 //out属性的setter和getter方法
 public void setOut(Output out)
 {
  this.out = out;
 }
 //name属性的setter和getter方法
 public void setName(String name)
 {
  this.name = name;
 }
 //定义一个模拟获取字符串输入的方法
 public void keyIn(String msg)
 {
  out.getData(msg);
 }
 //定义一个模拟打印的方法
 public void print()
 {
  System.out.println(name + "开始打印...");
  out.out();
 }
}

转载于:https://my.oschina.net/candiesyangyang/blog/204217

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值