内容简介:}}}
1.构造参数依赖,上一篇中的几种构建对象都不涉及传入参数的问题 2.属性依赖,构建出来的对象属性会存在依赖 复制代码
Q2:依赖注入的本质是什么?
赋值,给入构造参数值,还有对属性的赋值 复制代码
Q3:参数值,属性值,可能是什么值?
直接赋予的值和bean依赖(使用另外的bean) 复制代码
Q4:直接赋予的值会有哪几种类型?
1.基本数据类型,String 2.数组,集合 3.Properties 4.map 5.对象 复制代码
结论:无论是参数值还是属性值,bean工厂在进行DI依赖注入时的本质就是进行赋值
Q1:如何告诉bean工厂该给入什么构造参数值?即如何来定义参数依赖?
Q2:如何来定义属性依赖?
二、DI实现
① DI依赖注入-构造参数依赖定义分析:
以一个Girl类为例
public class Girl { public Girl(String name,int age,char cup,Boy boyfriend){} } 复制代码
Q1:我们要创建一个Girl类是如何创建的?
Boy Mike = new Boy(); Girl beauty = new Girl("WT",24,'A',Mike); 这种时候直接赋值,非常简单 复制代码
Q2:这种时候的构造参数依赖是怎样的?
1.第一个参数值"WT" 2.第二个参数值"24" 3.第三个参数值是'A' 4.第四个参数值是一个Boy的bean 复制代码
② 此时我们可以进行一个DI的构造参数依赖设计了
Q1:参数可以多个,使用什么来进行存储?
集合或者数组 复制代码
Q2:参数有顺序,如何处理顺序?
按参数顺序放入List即可 复制代码
Q3:参数值可以为直接的赋值,也可以为bean依赖,如何去表示?
只能使用Object类型:List<Object> constructorArgumentValues 复制代码
Q4:如果使用了Object来表示值,如何区分是否为bean依赖?
此时我们需要为bean依赖定义一个数据类型BeanReference, bean工厂在构造bean实例时需要进行遍历参数是否为 BeanReference类型,如果是,替换成依赖的bean实例 复制代码
Q5:如果直接赋值中存在数组集合,它们中的某元素存在bean依赖,如何处理?
元素值还是使用BeanReference, bean工厂在使用时应该遍历此数组/集合,存在即替换 复制代码
Q6:这个BeanReference该是怎样的?
/** * 用于依赖注入中描述bean依赖 */ public class BeanReference { private String beanName; public BeanReference(String beanName){ super(); this.beanName = beanName; } /** * 获得引用的beanName * @return */ public String getBeanName(){ return this.beanName; } } 复制代码
这个类仅仅是作为说明bean依赖的,需要提供一个beanName参数和一个getBeanName方法即可
③ DI实现-构造参数依赖定义(可对照上一篇参考)
1.在BeanDefinition中增加获取构造参数值的接口,让用户在定义bean时能指定构造参数值
BeanDefinition---List<?> getConstructorArgumentValues(); 复制代码
2.在实现了BeanDefinition接口的GeneralBeanDefinition中添加实现,因为使用了lombok插件不再需要手写getter和setter,所以我们添加表示构造参数值的字段即可
private List<?> constructorArgumentValues; 复制代码
④ DI实现-BeanFactory中实现构造参数依赖注入1
(1)首先需要把bean定义中的构造参数引用转化为真实的值,在DefaultBeanFactory中增加一个方法getConstructorArgumentValues来完成这件事
private Object[] getConstructorArgumentValues(BeanDefinition beanDefinition) throws Exception{ return this.getRealValues(beanDefinition.getConstructorArgumentValues()); } 复制代码
此时我们把这个获取值的过程提取出来单独为一个方法
private Object[] getRealValues(List<?> defs) throws Exception{ if (CollectionUtils.isEmpty(defs)){return null;} Object[] values = new Object[defs.size()]; int i = 0; //values数组的元素 Object value = null; for (Object realValue : defs){ if (realValue == null){ value = null; }else if (realValue instanceof BeanReference){ value = this.doGetBean(((BeanReference) realValue).getBeanName()); ··· }else {value = realValue;} values[i++] = value; } return values; } 复制代码
tips:这里的CollectionUtils使用的是apache的,else if那里省略了一部分的方法,具体做法就是创建一个 工具 类,然后去处理不同数据类型的数据中的bean引用,分支有大概4个,数组Object[],集合Collection,properties,和Map
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-collections4</artifactId> <version>4.1</version> </dependency> 复制代码
⑤ DI实现-BeanFactory中实现构造参数依赖注入2
Q1:有参数了,如何断定是哪个构造方法,哪个工厂方法?
1.方法是可以重载的(参数数量和参数类型的判断) 2.形参定义时可能是接口或者父类,实参是具体的子实现 3.反射提供的获取的构造方法,方法的API(以下的Class指的都是**参数**的类型) (getConstrutors()和getConstructor(Class<?>...)) (getMethods()和getMethod(String,Class<?>...)) 复制代码
判断逻辑:
1.先根据参数的类型进行精确匹配查找,如果没有找到,则进行第二步查找 2.获取所有的构造方法遍历,通过参数数量过滤,再比对形参类型和实参类型 (为什么第一步无法找到,也就是上面提及到的 “形参定义时可能是接口或者父类,实参是具体的子实现” 这个原因) 复制代码
Q2:当我们判断出构造方法或者工厂方法后,对于原型bean(prototype),下次获取Bean是否可以省略判断过程(设置多例时每次获取需要重新构造)
对于prototype,我们可以缓存这个构造方法或工厂方法 我们在BeanDefinition中增加缓存的方法 //以下4个方法仅供BeanFactory使用 public Constructor<?> getConstructor(); public void setConstructor(Constructor<?> constructor); public Method getFactoryMethod(); public void setFactoryMethod(Method method); 复制代码
由于在BeanDefinition接口中增加了上面4个方法,所以在GeneralBeanDefinition添加实现
private Constructor<?> constructor; private Method factoryMethod; @Override public Constructor<?> getConstructor() { return this.constructor; } @Override public void setConstructor(Constructor<?> constructor) { this.constructor = constructor; } @Override public Method getFactoryMethod() { return this.factoryMethod; } @Override public void setFactoryMethod(Method method) { this.factoryMethod = factoryMethod; } 复制代码
接下来就可以去实现构造方法和工厂方法的代码
⑥ DI实现-BeanFactory中实现构造参数依赖注入3
(1) 在DefaultBeanFactory中增加查找构造方法的方法
private Constructor determineConstructor(BeanDefinition beanDefinition,Object[] args) throws Exception{ Constructor constructor = null; //当没有任何一个参数时直接获取无参构造方法 if (args == null){ return beanDefinition.getBeanClass().getConstructor(null); } //对于原型bean,第二次开始获取Bean实例时,可直接获取第一次缓存的构造方法 constructor = beanDefinition.getConstructor(); if (constructor != null){ return constructor; } //根据参数类型获取精确匹配的构造方法 Class<?>[] paramTypes = new Class[args.length]; int j = 0; for (Object paramType : args){ paramTypes[j++] = paramType.getClass(); } try { constructor = beanDefinition.getConstructor(); }catch (Exception e){ //此异常不需要进行处理 } if (constructor == null){ //把所有的构造器全部遍历出来一一比对 Outer: for (Constructor<?> allConstructor : beanDefinition.getBeanClass().getConstructors()){ Class<?>[] pTypes = allConstructor.getParameterTypes(); //此构造方法的参数长度等于提供参数长度 if (pTypes.length == args.length){ for (int i = 0;i<pTypes.length;i++){ //如果第一个参数的类型就已经不匹配了,就直接不再继续比对了,直接跳转到外循环 if (!pTypes[i].isAssignableFrom(args[i].getClass())){ continue Outer; } } //如果以上皆匹配的话,就直接获取到这个构造器,然后直接让循环终止 constructor = allConstructor; break Outer; } } } if (constructor != null){ if (beanDefinition.isPrototype()){ //对原型bean构造器进行缓存方便下次查找 beanDefinition.setConstructor(constructor); } return constructor; }else { throw new Exception("不存在对应的构造方法!"+beanDefinition); } } 复制代码
修改构造方法创建对象的实现
前一篇的构造方法
private Object createInstanceByConstructor(BeanDefinition beanDefinition) throws IllegalAccessException, InstantiationException { try{ return beanDefinition.getBeanClass().newInstance(); } catch (SecurityException e){ logger.error("创建bean的实例异常,beanDefinition"+beanDefinition,e); throw e; } } 复制代码
修改后版本
private Object createInstanceByConstructor(BeanDefinition beanDefinition) throws Exception { try { //获取真正的参数值 Object[] args = this.getConstructorArgumentValues(beanDefinition); if (args == null) { return beanDefinition.getBeanClass().newInstance(); } else { // 决定构造方法 return this.determineConstructor(beanDefinition, args).newInstance(args); } } catch (SecurityException e1) { logger.error("创建bean的实例异常,beanDefinition:" + beanDefinition, e1); throw e1; } } 复制代码
自然工厂方法也要进行同样的编写查找方法和决定方法的逻辑实现
工厂查找方法
private Method determineFactoryMethod(BeanDefinition bd, Object[] args, Class<?> type) throws Exception { if (type == null) { type = bd.getBeanClass(); } String methodName = bd.getFactoryMethodName(); if (args == null) { return type.getMethod(methodName, null); } Method m = null; // 对于原型bean,从第二次开始获取bean实例时,可直接获得第一次缓存的构造方法。 m = bd.getFactoryMethod(); if (m != null) { return m; } // 根据参数类型获取精确匹配的方法 Class[] paramTypes = new Class[args.length]; int j = 0; for (Object p : args) { paramTypes[j++] = p.getClass(); } try { m = type.getMethod(methodName, paramTypes); } catch (Exception e) { // 这个异常不需要处理 } if (m == null) { // 没有精确参数类型匹配的,则遍历匹配所有的方法 // 判断逻辑:先判断参数数量,再依次比对形参类型与实参类型 outer: for (Method m0 : type.getMethods()) { if (!m0.getName().equals(methodName)) { continue; } Class<?>[] paramterTypes = m.getParameterTypes(); if (paramterTypes.length == args.length) { for (int i = 0; i < paramterTypes.length; i++) { if (!paramterTypes[i].isAssignableFrom(args[i].getClass())) { continue outer; } } m = m0; break outer; } } } if (m != null) { // 对于原型bean,可以缓存找到的方法,方便下次构造实例对象。在BeanDefinfition中获取设置所用方法的方法。 if (bd.isPrototype()) { bd.setFactoryMethod(m); } return m; } else { throw new Exception("不存在对应的构造方法!" + bd); } } 复制代码
前一篇的静态工厂方法:
private Object createInstanceByStaticFactoryMethod(BeanDefinition beanDefinition) throws Exception{ Class<?> type = beanDefinition.getBeanClass(); Method method = type.getMethod(beanDefinition.getFactoryMethodName(),null); return method.invoke(type,null); } 复制代码
修改后的静态工厂方法:
private Object createInstanceByStaticFactoryMethod(BeanDefinition beanDefinition) throws Exception{ Class<?> type = beanDefinition.getBeanClass(); Object[] realArgs = this.getRealValues(beanDefinition.getConstructorArgumentValues()); Method m = this.determineFactoryMethod(beanDefinition, realArgs, null); return m.invoke(type, realArgs); } 复制代码
前一篇的工厂bean方法:
private Object createInstanceByFactoryBean(BeanDefinition beanDefinition) throws Exception{ Object factoryBean = this.doGetBean(beanDefinition.getFactoryBeanName()); Method method = factoryBean.getClass().getMethod(beanDefinition.getFactoryMethodName(),null); return method.invoke(factoryBean,null); } 复制代码
修改后的工厂bean方法:
private Object createInstanceByFactoryBean(BeanDefinition beanDefinition) throws Exception{ Object factoryBean = this.doGetBean(beanDefinition.getFactoryBeanName()); Object[] realArgs = this.getRealValues(beanDefinition.getConstructorArgumentValues()); Method m = this.determineFactoryMethod(beanDefinition, realArgs, factoryBean.getClass()); return m.invoke(factoryBean, realArgs); } 复制代码
修改完构造,静态工厂,工厂bean的方法后进行一下测试看能否正常工作了
⑦ DI实现-BeanFactory中实现构造参数依赖注入4
Q:循环依赖如何进行处理?
1.构造对象时可以循环依赖吗? A:不可以在构造实例对象时的循环依赖 2.如何发现循环依赖? A:加入一个正在构造的bean的记录,每个bean开始构造时加入到记录中,构造完成就移走, 如果有依赖,先看依赖的bean是否在构造中,如果是,就构成了循环,抛出异常 复制代码
代码实现:
DefaultBeanFactory.java---添加一个字段: private ThreadLocal<Set<String>> buildingBeans = new ThreadLocal<>(); 放进ThreadLocal的一个集合,是为了线程安全 复制代码
doGetBean方法内补充:
// 记录正在创建的Bean Set<String> ingBeans = this.buildingBeans.get(); if (ingBeans == null) { ingBeans = new HashSet<>(); this.buildingBeans.set(ingBeans); } // 检测循环依赖 if (ingBeans.contains(beanName)) { throw new Exception(beanName + " 循环依赖!" + ingBeans); } // 记录正在创建的Bean ingBeans.add(beanName); 复制代码
还有创建完成后···
// 创建好实例后,移除创建中记录 ingBeans.remove(beanName); 复制代码
DI实现-属性依赖设计&实现1
Q1:属性依赖是什么?
某个属性依赖某个值 复制代码
Q2:该如何来描述一个属性依赖?
属性名,值,定义一个类,表示这俩个值 复制代码
Q3:会有多个依赖该如何存放?
List 复制代码
Q4:属性值的情况和构造参数值一样吗?
一样 复制代码
定义属性依赖描述实体类PropertyValue
import lombok.Data; @Data public class PropertyValue { private String name; private Object value; } 复制代码
DI实现-属性依赖设计&实现2
在BeanDefinition中添加属性依赖定义的接口
List<PropertyValue> getPropertyValues(); 复制代码
在GeneralBeanDefinition中添加实现
private List<PropertyValue> propertyValues; public List<PropertyValue> getPropertyValues() { return propertyValues; } public void setPropertyValues(List<PropertyValue> propertyValues) { this.propertyValues = propertyValues; } 复制代码
在DefaultBeanFactory的doGetBean方法中增加对属性依赖的调用
// 创建好实例后,移除创建中记录 ingBeans.remove(beanName); // 给入属性依赖 this.setPropertyDIValues(bd, instance); // 执行初始化方法 this.doInit(bd, instance); 复制代码
setPropertyDIValues方法的实现(和getRealValues方法类似)
private void setPropertyDIValues(BeanDefinition bd, Object instance) throws Exception { if (CollectionUtils.isEmpty(bd.getPropertyValues())) { return; } for (PropertyValue pv : bd.getPropertyValues()) { if (StringUtils.isBlank(pv.getName())) { continue; } Class<?> clazz = instance.getClass(); Field p = clazz.getDeclaredField(pv.getName()); p.setAccessible(true); Object rv = pv.getValue(); Object v = null; if (rv == null) { v = null; } else if (rv instanceof BeanReference) { v = this.doGetBean(((BeanReference) rv).getBeanName()); } else if (rv instanceof Object[]) { // TODO 处理集合中的bean引用 } else if (rv instanceof Collection) { // TODO 处理集合中的bean引用 } else if (rv instanceof Properties) { // TODO 处理properties中的bean引用 } else if (rv instanceof Map) { // TODO 处理Map中的bean引用 } else { v = rv; } p.set(instance, v); } } 复制代码
以下是测试篇(可不看)
A,ABeanFactory,C,CC,D~F的bean设计(反正就是一堆测试用的类把,分别满足测试另外bean的引用,父类子类,循环引用和属性依赖)
ABean
package MySpring.DITestUtils; public class ABean { private String name; private CBean cb; public ABean(String name, CBean cb) { super(); this.name = name; this.cb = cb; System.out.println("调用了含有CBean参数的构造方法"); } public ABean(String name, CCBean cb) { super(); this.name = name; this.cb = cb; System.out.println("调用了含有CCBean参数的构造方法"); } public ABean(CBean cb) { super(); this.cb = cb; } public void doSomthing() { System.out.println(System.currentTimeMillis() + " " + this.name + " cb.name=" + this.cb.getName()); } public void init() { System.out.println("ABean.init() 执行了"); } public void destroy() { System.out.println("ABean.destroy() 执行了"); } } 复制代码
ABeanFactory
public class ABeanFactory { public static ABean getABean(String name, CBean cb) { return new ABean(name, cb); } public ABean getABean2(String name, CBean cb) { return new ABean(name, cb); } } 复制代码
CBean
public class CBean { private String name; public CBean(String name) { this.name = name; } public String getName() { return this.name; } 复制代码
}
CCBean
public class CCBean extends CBean { public CCBean(String name) { super(name); } 复制代码
}
DBean
public class DBean { private EBean ebean; public DBean(EBean ebean) { super(); this.ebean = ebean; } 复制代码
}
EBean
public class EBean { private DBean dbean; public EBean(DBean dbean) { super(); this.dbean = dbean; } 复制代码
}
FBean(为了省事用了@Data)
@Data public class FBean { private String name; private int age; private ABean aBean; 复制代码
}
测试类1---DITest
public class DITest { static PreBuildBeanFactory bf = new PreBuildBeanFactory(); @Test public void testConstructorDI() throws Exception { GeneralBeanDefinition bd = new GeneralBeanDefinition(); bd.setBeanClass(ABean.class); List<Object> args = new ArrayList<>(); args.add("abean"); args.add(new BeanReference("cbean")); bd.setConstructorArgumentValues(args); bf.registerBeanDefinition("abean", bd); bd = new GeneralBeanDefinition(); bd.setBeanClass(CBean.class); args = new ArrayList<>(); args.add("cbean"); bd.setConstructorArgumentValues(args); bf.registerBeanDefinition("cbean", bd); bf.preInstantiateSingletons(); ABean abean = (ABean) bf.getBean("abean"); abean.doSomthing(); } @Test public void testStaticFactoryMethodDI() throws Exception { GeneralBeanDefinition bd = new GeneralBeanDefinition(); bd.setBeanClass(ABeanFactory.class); bd.setFactoryMethodName("getABean"); List<Object> args = new ArrayList<>(); args.add("abean02"); args.add(new BeanReference("cbean02")); bd.setConstructorArgumentValues(args); bf.registerBeanDefinition("abean02", bd); bd = new GeneralBeanDefinition(); bd.setBeanClass(CBean.class); args = new ArrayList<>(); args.add("cbean02"); bd.setConstructorArgumentValues(args); bf.registerBeanDefinition("cbean02", bd); bf.preInstantiateSingletons(); ABean abean = (ABean) bf.getBean("abean02"); abean.doSomthing(); } @Test public void testFactoryMethodDI() throws Exception { GeneralBeanDefinition bd = new GeneralBeanDefinition(); bd.setFactoryBeanName("abeanFactory"); bd.setFactoryMethodName("getABean2"); List<Object> args = new ArrayList<>(); args.add("abean03"); args.add(new BeanReference("cbean02")); bd.setConstructorArgumentValues(args); bf.registerBeanDefinition("abean03", bd); bd = new GeneralBeanDefinition(); bd.setBeanClass(ABeanFactory.class); bf.registerBeanDefinition("abeanFactory", bd); bf.preInstantiateSingletons(); ABean abean = (ABean) bf.getBean("abean03"); abean.doSomthing(); } @Test public void testChildTypeDI() throws Exception { GeneralBeanDefinition bd = new GeneralBeanDefinition(); bd.setBeanClass(ABean.class); List<Object> args = new ArrayList<>(); args.add("abean04"); args.add(new BeanReference("ccbean01")); bd.setConstructorArgumentValues(args); bf.registerBeanDefinition("abean04", bd); bd = new GeneralBeanDefinition(); bd.setBeanClass(CCBean.class); args = new ArrayList<>(); args.add("Ccbean01"); bd.setConstructorArgumentValues(args); bf.registerBeanDefinition("ccbean01", bd); bf.preInstantiateSingletons(); ABean abean = (ABean) bf.getBean("abean04"); abean.doSomthing(); } } 复制代码
DITest的测试结果
调用了含有CCBean参数的构造方法 1555720164146 abean04 cb.name=Ccbean01 调用了含有CBean参数的构造方法 1555720164162 abean cb.name=cbean 调用了含有CBean参数的构造方法 1555720164162 abean02 cb.name=cbean02 调用了含有CBean参数的构造方法 1555720164162 abean03 cb.name=cbean02 复制代码
CirculationDITest的测试结果
java.lang.Exception: dbean 循环依赖![ebean, dbean] 复制代码
PropertyDITest的测试结果
调用了含有CBean参数的构造方法 FFBean01 18 1555720275503 abean01 cb.name=cbean01 复制代码
Tips:在GeneralBeanDefinition使用lombok插件时候曾经有报错,就是在测试类中设置的参数一直无法传入到DefaultBeanFactory的createInstanceByConstructor方法中(此时第一个if (args == null)会一直判断为正确),原因我也还没弄清楚,实在不好意思···
以上所述就是小编给大家介绍的《手写Spring---DI依赖注入(2)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
免费:商业的未来
Chris Anderson / 中信出版集团 / 2015-10-1 / 35.40
《免费》,这是一个商业模式不断被颠覆、被改写的时代。一种商业模式既可以统摄未来市场,也可以挤垮当前市场——在我们这个现代经济社会里,这并不是一件不可能的事情。“免费”就是这样的一种商业模式,它代表了互联网时代的商业未来。 “免费”商业模式是一种建立在以电脑字节为基础上的经济学,而非过去建立在物理原子基础上的经济学。在原子经济中,随着时间的推移,我们周围的物品都在逐渐升值。但是在字节经济的网络......一起来看看 《免费:商业的未来》 这本书的介绍吧!