Spring系列之DI的原理及手动实现

目录 Spring系列之IOC的原理及手动实现 Spring系列之DI的原理及手动实现 前言 在上一章中,我们介绍和简单实现了容器的部分功能,但是这里还留下了很多的问题。比如我们在构造bean实例的时候如果bean是带参数的我们需要怎么来进行,当然这个是可以在init方法中进行的,再比如我们平时在Spring中获取一个对象通过一个注解即可获取到类的实例,这个实例是怎么注入的呢? IOC和DI 依赖注入(DI)和控制反转(IOC)其实基本上表达的是同一个东西,两者谁也离不开谁。 假设类a需要依赖类b,但a并不控制b的声明周期,仅仅在本类中使用b,而对于类b的创建和销毁等工作交给其他的组件来处理,这叫控制反转(IOC),而类a要依赖类b,则必须要获得类b的实例。这个获得类b的实例的过程则就是依赖注入(DI)。 注入分析 分析我们在那些地方可能会有注入这样一个行为? 要知道那些地方存在依赖注入首先得明白注入的是什么,结合上面分析这里很明显注入实际就是一个实例化的过程,更广泛的说是一个赋值的过程。而我们平时那些地方可能会存在赋值的动作呢?首先肯定是构造函数里,对类的实例化构造函数肯定是跑不了的,大多数赋初值的操作也都在构造函数中完成。然后还有就是另外执行过程中对属性值修改了。 那么需要进行依赖注入的地方就很明显了: 构造参数依赖 属性依赖 我们赋值的类型有那些呢? 很明显在Java中我们赋值类型包括基本数据类型(int,double...)和引用类型。 在分析清楚需要注入的行为和类型后又有另外的问题,我们虽然知道注入的类型是基本数据类型和引用类型,但是实际需要注入的类型是无法预料到的,我们事先并不知道某一个参数需要的是int还是boolean或者是引用类型。幸运的是Java实际上已经帮我们解决了这件事情,java中的所有类型都是继承自Object,我们只需要使用Object来接受值即可。 我们在对值进行注入的时候肯定是需要知道我们注入的具体的类型的,instanceof关键字可以帮助我们确定具体的某个类型,但是实际上也就仅仅限于基本数据类型了,因为引用类型实在太多了,用户自己定义的引用类型我们是没有办法事先确定的。所以我们要想办法能让我们知道注入的具体是哪一个类型。 BeanReference 我们定义一个用来标识bean类型的类,接口只包含一个标识bean的类型的参数beanName,在注入参数的时候只需要定义好beanName的值即可直接从IOC容器中取相应的bean,而对于基本数据类型和String则直接赋值即可。 当然这样还不能解决所有问题,因为传入的参数可能携带多个引用值,比如引用数组,List,Map以及属性文件读取(Properties)等,解决这些问题和上面差不多,引用类型还是使用BeanReference,多个值则进行遍历即可。 构造参数依赖分析 构造参数个数的问题 实际上上面的分析已经将我们的问题解决的差不多了,还存在一个问题就是构造参数的个数是没有办法确定的,我们怎么来存储一个bean实例化所需的全部参数,又如何值和参数对应。 很明显需要一个对应关系的话我们立刻能想到的就是key-value形式的Map,实际上我们还能使用List,根据顺序来存储,取得时候也依然是这个顺序。 如何匹配构造函数 一个类中方法的重载的个数可以是有多个的,我们如何精确的找到我们需要的方法呢? 在JDK中Class类中为我们提供了一系列的方法: method 介绍 Constructor 返回一个 Constructor对象,该对象反映 Constructor对象表示的类的指定的公共 类函数。 Method getMethod(String name, Class... parameterTypes) 返回一个 方法对象,它反映此表示的类或接口的指定公共成员方法 类对象。 Method[] getMethods() 返回包含一个数组 方法对象反射由此表示的类或接口的所有公共方法 类对象,包括那些由类或接口和那些从超类和超接口继承的声明。 Constructor[] getConstructors() 返回包含一个数组 Constructor对象反射由此表示的类的所有公共构造 类对象。 前面我们已经取到了参数: 根据参数个数匹配具有同样个数参数的方法 根据参数类型精确匹配步骤一种筛选出的方法 单例or原型 简单来讲,单例(Singleton)是指在容器运行过程中,一个bean只创建一次,后面需要使用都是同一个对象。原型(Prototype)在容器运行时不进行创建,只有在使用时才创建,没用一次就新创建一个。 对于单例模式只创建一次,那么上面的匹配过程也只会进行一次,对程序的运行不会有影响。但是原型模式每次创建都重新匹配一次这会在一定程度上拖慢程序的运行。所以这里我们可以考虑将原型bean实例化对应的方法缓存起来,那么后面在同一个地方使用创建时不用重复去匹配。 需要的接口 很明显上面的分析都是和bean定义有关,相应的方法也应该加在bean定义接口上了。 构造参数的注入应当是在bean创建的时候,在前面我们定义类几种不同的bean创建方式,现在应该在这些方法中加上构造参数了。 代码: BeanReference public class BeanReference { private String beanName; public String getBeanName() { return beanName; } public void setBeanName(String beanName) { this.beanName = beanName; } } DefaultBeanDefinition添加代码: public class DefaultBeanDefinition implements BeanDefinition{ ... private Constructor constructor; private Method method; private List constructorArg; ... //getter setter } DefaultBeanFactory添加代码 public class DefaultBeanFactory implements BeanFactory, BeanDefinitionRegistry, Closeable { //other method /** * 解析传入的构造参数值 * @param constructorArgs * @return */ private Object[] parseConstructorArgs(List constructorArgs) throws IllegalAccessException, InstantiationException { if(constructorArgs==null || constructorArgs.size()==0){ return null; } Object[] args = new Object[constructorArgs.size()]; for(int i=0;i matchConstructor(BeanDefinition bd, Object[] args) throws Exception { if(args == null){ return bd.getBeanClass().getConstructor(null); } //如果已经缓存了 则直接返回 if(bd.getConstructor() != null) return bd.getConstructor(); int len = args.length; Class[] param = new Class[len]; //构造参数列表 for(int i=0;i firstFilterAfter = new LinkedList<>(); Constructor[] constructors = bd.getBeanClass().getConstructors(); //按参数个数匹配 for(Constructor cons:constructors){ if(cons.getParameterCount() == len){ firstFilterAfter.add(cons); } } if(firstFilterAfter.size()==1){ return firstFilterAfter.get(0); } if(firstFilterAfter.size()==0){ log.error("不存在对应的构造函数:" + args); throw new Exception("不存在对应的构造函数:" + args); } //按参数类型匹配 //获取所有参数类型 boolean isMatch = true; for(int i=0;i> initialedBeans = new ThreadLocal<>(); public Object doGetBean(String beanName) throws InstantiationException, IllegalAccessException { //other operation // 记录正在创建的Bean Set beans = this.initialedBeans.get(); if (beans == null) { beans = new HashSet<>(); this.initialedBeans.set(beans); } // 检测循环依赖 if (beans.contains(beanName)) { throw new Exception("检测到" + beanName + "存在循环依赖:" + beans); } // 记录正在创建的Bean beans.add(beanName); //other operation //创建完成 移除该bean的记录 beans.remove(beanName); return instance; } } 实际上在单例bean中,对于已经创建好的bean是直接从容器中获取实例,不需要再次实例化,所以也不会有循环依赖的问题。但是对于原型bean,创建好的实例并不放到容器中,而是每一次都重新创建初始化,才会存在循环依赖的问题。 属性依赖 除了在构造函数中初始化参数外,我们还可以对属性进行赋值,对属性赋值的好处在于可以在运行中动态的改变属性的值。 和构造参数依赖有什么不同 整体来说没有什么差别,不同在于对构造参数依赖时有具体的对应方法,可以根据参数的个数和顺序来确定构造方法,所以在注入是我们可以使用上面选择的List根据存入顺序作为参数的顺序。而对于属性依赖,我们必须要根据属性的名称来注入值才可以,所以在使用list就不行了。 解决: 使用一个Map容器,key为属性名,value为属性值,使用时解析map即可 自定义一个包裹属性的类,参数为属性名和属性值,然后使用list容纳包裹属性的类,实际上和上面的map差不多。 这里我使用map类。 然后其他的地方都基本一样,对于引用类型依旧使用BeanReference。在BeanDefinition中添加获取和设置属性值得方法: //属性依赖 Map getPropertyKeyValue(); void setPropertyKeyValue(Map properties); 在BeanFactory的实现中加入解析属性的方法: private void parsePropertyValues(BeanDefinition bd, Object instance) throws Exception { Map propertyKeyValue = bd.getPropertyKeyValue(); if(propertyKeyValue==null || propertyKeyValue.size()==0){ return ; } Class aClass = instance.getClass(); Set> entries = propertyKeyValue.entrySet(); for(Map.Entry entry:entries){ //获取指定的字段信息 Field field = aClass.getDeclaredField(entry.getKey()); //将访问权限设置为true field.setAccessible(true); Object arg = entry.getValue(); Object value = null; if(arg instanceof BeanReference){ String beanName = ((BeanReference) arg).getBeanName(); value = this.doGetBean(beanName); }else if(arg instanceof List){ List param = parseListArg((List) arg); value = param; }else if(arg instanceof Map){ //todo 处理map }else if(arg instanceof Properties){ //todo 处理属性文件 }else { value = arg; } field.set(instance, value); } } 相关代码已经托管到github:myspring 完整类图 标签: java, Spring 好文要顶 关注我 收藏该文 宁愿呢 关注 - 0 粉丝 - 1 +加关注 0 0 « 上一篇:Spring系列之IOC的原理及手动实现 posted @ 2018-12-17 08:54 宁愿呢 阅读(109) 评论(0) 编辑 收藏 刷新评论刷新页面返回顶部 注册用户登录后才能发表评论,请 登录 或 注册,访问网站首页。 【推荐】超50万VC++源码: 大型组态工控、电力仿真CAD与GIS源码库! 【活动】华为云12.12会员节全场1折起 满额送Mate20 【活动】华为云会员节云服务特惠1折起 【推荐】服务器100%基准CPU性能,1核1G首年168元,限时特惠! 腾讯云1129 相关博文: · Spring系列之IOC的原理及手动实现 · spring之IOC和DI实现 · 四、spring之DI · spring框架DI(IOC)和AOP 原理及方案 · Spring系列(二):Spring IoC/DI的理解 最新新闻: · 酒店卫生问题曝光者个人信息遭泄露:悬赏10万维权 · 苹果印度的至暗时刻 · 一大批VR/AR专利组合将被出售:明年1月份进行 · 押金成无解之痛 消费者为何要替创业者买单? · 共享单车“烂尾”:危机悄然而至 时代已经结束了 » 更多新闻... 公告 昵称:宁愿呢 园龄:1个月 粉丝:1 关注:0 +加关注 < 2018年12月 > 日 一 二 三 四 五 六 25 26 27 28 29 30 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 1 2 3 4 5 搜索 常用链接 我的随笔 我的评论 我的参与 最新评论 我的标签 我的标签 java(8) hashmap(3) 多线程(3) Spring(2) volatile(1) 随笔档案 2018年12月 (1) 2018年11月 (6) 文章分类 Java 阅读排行榜 1. Spring系列之IOC的原理及手动实现(212) 2. Java多线程之一(111) 3. Spring系列之DI的原理及手动实现(108) 4. Java多线程之三volatile与等待通知机制示例(68) 5. HashMap在JDK1.7中可能出现的并发问题https://www.cnblogs.com/liyus/p/10129457.html
50000+
5万行代码练就真实本领
17年
创办于2008年老牌培训机构
1000+
合作企业
98%
就业率

联系我们

电话咨询

0532-85025005

扫码添加微信