解析IOC的工做原理,以及代码逻辑。java
在解读以前先用本身的思路尝试实现一下Spring。初学过Spring的同窗应该都知道一个名词ApplicationContext,意为应用上下文。说的通俗一点,“应用上下文”就是描述一个应用内部对象之间的相互依赖关系,好比“对象A有一个属性是对象B”,那么称“对象A依赖一个对象B”,前半句来自于你的代码,后半句来自于Spring对你代码的解读。spring
那么Spring如何实现解读对象之间的依赖关系呢?咱们先不急着看代码,假如咱们本身实现的话,至少有如下一种方法能够实现:反射。好比咱们能够在配置文件中定义对象之间的依赖关系,而后经过反射实例化这个类。缓存
定义类A(其对象依赖一个B实例):app
package com.wuuyao.study; public class A { private B bInstance; }
定义类B(不依赖任何其它对象):less
package com.wuuyao.study; public class B { }
在类C中编写实例化化类A以及类B的方法:this
package com.wuuyao.study; public class C { public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException { // 能够写在配置文件中,此处不赘述 String aClasspath = "com.wuuyao.study.A"; String bClasspath = "com.wuuyao.study.B"; /** * 使用反射初始化类B,暂不设置属性。 */ // B类加载 Class classB = Class.forName(bClasspath); // B对象实例化 B bInstance = (B) classB.newInstance(); // A同操做 Class classA = Class.forName(aClasspath); A aInstance = (A) classA.newInstance(); } }
但上述代码并未实现A的属性注入,咱们能够经过增长一条配置来实现:spa
package com.wuuyao.study; import java.lang.reflect.Field; public class C { public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException { // 能够写在配置文件中,此处不赘述 String aClasspath = "com.wuuyao.study.A"; String bClasspath = "com.wuuyao.study.B"; // 新增配置,用于描述依赖关系 String aDependencyConfig = "A need field bInstance"; /** * 使用反射初始化类B,暂不设置属性。 */ Object B = instance(bClasspath); Object A = instance(aClasspath, aDependencyConfig); } /** * 经过完整类路径,以及依赖配置进行实例化对象 * * @param classpath 类路径 * @param dependencies 依赖关系 * @return */ private static final Object instance(String classpath, String... dependencies) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException { // 目标对象实例化 Class targetClass = Class.forName(classpath); Object instance = targetClass.newInstance(); /** * 为目标对象设置属性。咱们的配置格式是"A need field bInstance",即"类型A 须要属性 bInstance 的注入" */ for (String dependencyConfig : dependencies) { String injectFieldName = dependencyConfig.split(" need field ")[1]; Field injectField = targetClass.getDeclaredField(injectFieldName); // 将属性设置为可见 injectField.setAccessible(true); // 获取属性的类路径并实例化 String fieldClasspath = injectField.getType().getCanonicalName(); // TODO 经过递归调用instance方法实例化属性并设值,但此处有问题,由于咱们不知道属性bInstance是否有依赖 Object fieldObject = instance(fieldClasspath); injectField.set(instance, fieldObject); // 还原属性设置 injectField.setAccessible(false); } return instance; } }
上述代码是有缺陷的。首先,实际上咱们的目的是实例化后的B注入实例化的A,但上述代码是另外建立了一个B实例去注入A实例。所以咱们须要一个集合去保存实例化的结果。另外,不支持多个同类型实例注入,好比A中有两个不一样的B属性时,注入将会失败。所以,经过反射实例化的属性还须要一个标识(ID),而且在依赖配置声明时也须要一个标识(ID)。咱们能够以下实现。code
定义一个对象配置类对象
package com.wuuyao.study; import java.util.List; public class ObjectCreateConfig { private String id; private String instanceConfig; private List<String> dependencyConfigList; }
修改实例化流程递归
package com.wuuyao.study; import java.lang.reflect.Field; import java.util.*; public class C { /** * 缓存实例化任务 * key: 实例化对象的ID * val: 实例化对象配置清单 */ private static Map<String, ObjectCreateConfig> configMap = new HashMap<>(); /** * 缓存已经实例化的对象 * key: 实例ID * val: 实例对象 */ private static Map<String, Object> objectMap = new HashMap<>(); public static void main(String[] args) throws Exception { // 能够写在配置文件中,此处不赘述 String instanceConfigA = "com.wuuyao.study.A , id = instanceA01"; // 新增配置,用于描述依赖关系 String aDependencyConfig = "A need field bInstance, id=instanceB01"; // 将实例化配置添加到配置表中 addDependencyConfig(instanceConfigA, aDependencyConfig); String instanceConfigB = "com.wuuyao.study.B , id = instanceB01"; addDependencyConfig(instanceConfigB); // 根据配置进行装配 Iterator<Map.Entry<String, ObjectCreateConfig>> iterator = configMap.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<String, ObjectCreateConfig> config = iterator.next(); Object instance = instance(config.getKey()); if (instance != null) { iterator.remove(); } } } private static void addDependencyConfig(String classpath, String... dependencies) { ObjectCreateConfig config = new ObjectCreateConfig(); List<String> configList = new ArrayList<>(); // "A need field bInstance, id=bInstance01" for (String dependency : dependencies) { configList.add(dependency); } // 添加到配置缓存中 // "com.wuuyao.study.A , id = instanceA01" String[] objectInfo = classpath.split(" , id = "); config.setId(objectInfo[1]); config.setInstanceConfig(classpath); config.setDependencyConfigList(configList); configMap.put(config.getId(), config); } /** * 经过完整类路径,以及依赖配置进行实例化对象 * * @param instanceId 配置对象 * @return */ private static final Object instance(String instanceId) throws Exception { // 目标对象实例化 ObjectCreateConfig config = configMap.get(instanceId); if (config == null) { throw new Exception("未定义的对象"); } String classpath = config.getInstanceConfig().split(" , ")[0]; Class targetClass = Class.forName(classpath); Object instance = targetClass.newInstance(); // 若已经被初始化则直接返回该对象 if (objectMap.get(instanceId) != null) { return objectMap.get(instanceId); } /** * 为目标对象设置属性。咱们的配置格式是"A need field bInstance, id=bInstance01", * 即"类型A 须要属性 bInstance 的注入,属性ID为bInstanceId01" */ for (String dependencyConfig : config.getDependencyConfigList()) { String injectFieldName = dependencyConfig.split(" need field ")[1].split(", id=")[0]; String injectFieldObjectId = dependencyConfig.split(" need field ")[1].split(", id=")[1]; Field injectField = targetClass.getDeclaredField(injectFieldName); // 将属性设置为可见 injectField.setAccessible(true); // 获取属性的类路径并实例化 String fieldClasspath = injectField.getType().getCanonicalName(); // 尝试从缓存中获取对象 Object fieldObject = objectMap.get(injectFieldObjectId); if (fieldObject == null) { // 尝试建立子对象 fieldObject = instance(injectFieldObjectId); } injectField.set(instance, fieldObject); // 还原属性设置 injectField.setAccessible(false); } // TODO 放入缓存中。此处暂不讨论ID重复的状况 objectMap.put(instanceId, instance); return instance; } }
到此,咱们已经大体实现了IOC的原型,这个原型很是粗糙,仅为理解Spring提供一些思路,另外,为了和防止和Spring的概念重合,故意使用了自创的命名。
-------------------------------------------------------------------------------------------------------------------------------------------------
接下来咱们来看Spring是怎么实现的。
package org.springframework.boot; public class SpringApplication{ /** * Run the Spring application, creating and refreshing a new * {@link ApplicationContext}. * @param args the application arguments (usually passed from a Java main method) * @return a running {@link ApplicationContext} */ public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); configureHeadlessProperty(); SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); configureIgnoreBeanInfo(environment); Banner printedBanner = printBanner(environment); context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances( SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); prepareContext(context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } listeners.started(context); callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } try { listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } return context; } }
上述代码是Spring建立上下文的主要代码。能够一眼看见其中与ApplicationContext有关的代码段。
context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances( SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); prepareContext(context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); afterRefresh(context, applicationArguments);
// TODO 先写到这,明天再更