1.什么是IOC(Inversion of Control 控制反转)?java
IoC不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导咱们如何设计出松耦合、更优良的程序。传统应用程序都是由咱们在类内部主动建立依赖对象,从而致使类与类之间高耦合,难于测试;有了IoC容器后,把建立和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,因此对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得很是灵活。web
其实IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序本来是老大,要获取什么资源都是主动出击,可是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来建立并注入它所须要的资源了。spring
IoC很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找咱们,咱们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。编程
2.什么是DI(Dependency Injection 依赖注入)?tomcat
DI—Dependency Injection,即“依赖注入”:是组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并不是为软件系统带来更多功能,而是为了提高组件重用的频率,并为系统搭建一个灵活、可扩展的平台。经过依赖注入机制,咱们只须要经过简单的配置,而无需任何代码就可指定目标须要的资源,完成自身的业务逻辑,而不须要关心具体的资源来自何处,由谁实现。app
3.IOC和DI什么关系?框架
IoC和DI由什么关系呢?其实它们是同一个概念的不一样角度描述,因为控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),因此2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。ide
4.什么是依赖?工具
传统应用程序设计中所说的依赖通常指“类之间的关系”,那先让咱们复习一下类之间的关系:测试
泛化:表示类与类之间的继承关系、接口与接口之间的继承关系;
实现:表示类对接口的实现;
依赖:当类与类之间有使用关系时就属于依赖关系,不一样于关联关系,依赖不具备“拥有关系”,而是一种“相识关系”,只在某个特定地方(好比某个方法体内)才有关系。
关联:表示类与类或类与接口之间的依赖关系,表现为“拥有关系”;具体到代码能够用实例变量来表示;
聚合:属因而关联的特殊状况,体现部分-总体关系,是一种弱拥有关系;总体和部分能够有不同的生命周期;是一种弱关联;
组合:属因而关联的特殊状况,也体现了体现部分-总体关系,是一种强“拥有关系”;总体与部分有相同的生命周期,是一种强关联;
Spring IoC容器的依赖有两层含义:Bean依赖容器和容器注入Bean的依赖资源:
Bean依赖容器:也就是说Bean要依赖于容器,这里的依赖是指容器负责建立Bean并管理Bean的生命周期,正是因为由容器来控制建立Bean并注入依赖,也就是控制权被反转了,这也正是IoC名字的由来,此处的有依赖是指Bean和容器之间的依赖关系。
容器注入Bean的依赖资源:容器负责注入Bean的依赖资源,依赖资源能够是Bean、外部文件、常量数据等,在Java中都反映为对象,而且由容器负责组装Bean之间的依赖关系,此处的依赖是指Bean之间的依赖关系,能够认为是传统类与类之间的“关联”、“聚合”、“组合”关系。
5.依赖注入的好处?
动态替换Bean依赖对象,程序更灵活:替换Bean依赖对象,无需修改源文件:应用依赖注入后,因为能够采用配置文件方式实现,从而能随时动态的替换Bean的依赖对象,无需修改java源文件;
更好实践面向接口编程,代码更清晰:在Bean中只需指定依赖对象的接口,接口定义依赖对象完成的功能,经过容器注入依赖实现;
更好实践优先使用对象组合,而不是类继承:由于IoC容器采用注入依赖,也就是组合对象,从而更好的实践对象组合。
采用对象组合,Bean的功能可能由几个依赖Bean的功能组合而成,其Bean自己可能只提供少量功能或根本无任何功能,所有委托给依赖Bean,对象组合具备动态性,能更方便的替换掉依赖Bean,从而改变Bean功能;
而若是采用类继承,Bean没有依赖Bean,而是采用继承方式添加新功能,,并且功能是在编译时就肯定了,不具备动态性,并且采用类继承致使Bean与子Bean之间高度耦合,难以复用。
增长Bean可复用性:依赖于对象组合,Bean更可复用且复用更简单;
下降Bean之间耦合:因为咱们彻底采用面向接口编程,在代码中没有直接引用Bean依赖实现,所有引用接口,并且不会出现显示的建立依赖对象代码,并且这些依赖是由容器来注入,很容易替换依赖实现类,从而下降Bean与依赖之间耦合;
代码结构更清晰:要应用依赖注入,代码结构要按照规约方式进行书写,从而更好的应用一些最佳实践,所以代码结构更清晰。
从以上咱们能够看出,其实依赖注入只是一种装配对象的手段,设计的类结构才是基础,若是设计的类结构不支持依赖注入,Spring IoC容器也注入不了任何东西,从而从根本上说“如何设计好类结构才是关键,依赖注入只是一种装配对象手段”。
标记配置分为集中式管理和分散式管理,即xml文件方式和注解方式配置元数据信息,手写ioc我采用注解配置元数据方式来实现ioc的大体运行过程。
1.定义元数据配置信息
@Component/@Controller/@Repository@Service
扫描什么样的class装载到ioc容器中进行管理。
@Autowired
什么样的对象进行依赖注入。
/** * @Classname Component * @Description * @Date 2020/8/6 14:54 * @Created by zhangtianci */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Component { }
/** * @Classname Controller * @Description TODO * @Date 2020/8/6 14:56 * @Created by zhangtianci */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Controller { }
/** * @Classname Repository * @Description TODO * @Date 2020/8/6 14:58 * @Created by zhangtianci */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Repository { }
/** * @Classname Service * @Description TODO * @Date 2020/8/6 14:57 * @Created by zhangtianci */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Service { }
/** * @Classname Autowired * @Description 自动注入注解 * @Date 2020/8/6 14:28 * @Created by zhangtianci */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Autowired { String value() default ""; }
2.实现ioc容器
核心功能:加载被配置标记的class文件,交给ioc容器管理。
package org.simplespring.core; import lombok.extern.slf4j.Slf4j; import org.simplespring.core.annotation.Component; import org.simplespring.core.annotation.Controller; import org.simplespring.core.annotation.Repository; import org.simplespring.core.annotation.Service; import org.simplespring.util.ClassUtil; import org.simplespring.util.ValidationUtil; import java.lang.annotation.Annotation; import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** * @Classname BeanContainer * @Description bean容器 * <p> * BeanContainer应该是单例的 采用内部枚举的方式实现。 * * 应该拥有的实例方法: * 1.boolean isLoad() 是否加载 * 2.int getSize() 获取bean个数 * 3.loadBeans(String packageName) 根据包名加载全部被配置标记的class文件/ * 4.获取/删除/增长bean及提供一些便利的方法 * * @Date 2020/8/6 14:41 * @Created by zhangtianci */ @Slf4j public class BeanContainer { /** * 存放全部被配置标记(xml/注解)的class,并new出一个实例对象bean * 存放在一个map中 */ private final Map<Class<?>, Object> beanMap = new ConcurrentHashMap<>(); /** * 加载bean的注解列表 */ private static final List<Class<? extends Annotation>> BEAN_ANNOTATION = Arrays.asList(Component.class, Controller.class, Service.class, Repository.class); /** * 是否已经加载过bean */ private boolean loaded = false; /** * 获取bean容器 * * @return */ public static BeanContainer getInstance() { return ContainerHolder.HOLDER.instance; } private enum ContainerHolder { HOLDER; private BeanContainer instance; ContainerHolder() { instance = new BeanContainer(); } } /** * 是否已经加载过bean */ public boolean isLoad(){ return loaded; } /** * 获取容器中bean的个数 */ public int getSize(){ return beanMap.size(); } /** * 加载指定路径下的全部class文件 * 并将全部被配置标记(xml/注解)的class对象和new出一个实例对象放入容器 */ public synchronized void loadBeans(String packageName){ //判断容器是否已经加载过 if (loaded){ log.warn("Container has loaded!"); return; } // 导出全部的class对象 Set<Class<?>> classSet = ClassUtil.extractPackageClass(packageName); //为空直接return if (ValidationUtil.isEmpty(classSet)) { log.warn("extract nothing from packageName" + packageName); return; } classSet.stream().forEach(clazz -> { for (Class<? extends Annotation> annotationClazz : BEAN_ANNOTATION) { if (clazz.isAnnotationPresent(annotationClazz)){ //将目标类自己做为键,目标类的实例做为值,放入到beanMap中 beanMap.put(clazz, ClassUtil.newInstance(clazz, true)); } } }); loaded = true; } /** * 添加一个class对象及其Bean实例 * * @param clazz Class对象 * @param bean Bean实例 * @return 原有的Bean实例, 没有则返回null */ public Object addBean(Class<?> clazz, Object bean) { return beanMap.put(clazz, bean); } /** * 移除一个IOC容器管理的对象 * * @param clazz Class对象 * @return 删除的Bean实例, 没有则返回null */ public Object removeBean(Class<?> clazz) { return beanMap.remove(clazz); } /** * 根据Class对象获取Bean实例 * * @param clazz Class对象 * @return Bean实例 */ public Object getBean(Class<?> clazz) { return beanMap.get(clazz); } /** * 获取容器管理的全部Class对象集合 * * @return Class集合 */ public Set<Class<?>> getClasses(){ return beanMap.keySet(); } /** * 获取全部Bean集合 * * @return Bean集合 */ public Set<Object> getBeans(){ return new HashSet<>( beanMap.values()); } /** * 根据注解筛选出Bean的Class集合 * * @param annotation 注解 * @return Class集合 */ public Set<Class<?>> getClassesByAnnotation(Class<? extends Annotation> annotation){ //1.获取beanMap的全部class对象 Set<Class<?>> keySet = getClasses(); if(ValidationUtil.isEmpty(keySet)){ log.warn("nothing in beanMap"); return null; } //2.经过注解筛选被注解标记的class对象,并添加到classSet里 Set<Class<?>> classSet = new HashSet<>(); for(Class<?> clazz : keySet){ //类是否有相关的注解标记 if(clazz.isAnnotationPresent(annotation)){ classSet.add(clazz); } } return classSet.size() > 0? classSet: null; } /** * 经过接口或者父类获取实现类或者子类的Class集合,不包括其自己 * * @param interfaceOrClass 接口Class或者父类Class * @return Class集合 */ public Set<Class<?>> getClassesBySuper(Class<?> interfaceOrClass){ //1.获取beanMap的全部class对象 Set<Class<?>> keySet = getClasses(); if(ValidationUtil.isEmpty(keySet)){ log.warn("nothing in beanMap"); return null; } //2.判断keySet里的元素是不是传入的接口或者类的子类,若是是,就将其添加到classSet里 Set<Class<?>> classSet = new HashSet<>(); for(Class<?> clazz : keySet){ //判断keySet里的元素是不是传入的接口或者类的子类 if(interfaceOrClass.isAssignableFrom(clazz) && !clazz.equals(interfaceOrClass)){ classSet.add(clazz); } } return classSet.size() > 0? classSet: null; } }
3.定义依赖注入器
核心功能:扫描被管理的bean对象,进行依赖注入。
package org.simplespring.inject; import lombok.extern.slf4j.Slf4j; import org.simplespring.core.BeanContainer; import org.simplespring.inject.annotation.Autowired; import org.simplespring.util.ClassUtil; import org.simplespring.util.ValidationUtil; import java.lang.reflect.Field; import java.util.Set; /** * @Classname DependencyInjector * @Description 依赖注入器 * @Date 2020/8/6 14:38 * @Created by zhangtianci */ @Slf4j public class DependencyInjector { /** * 拥有一个Bean容器 */ private BeanContainer beanContainer; public DependencyInjector(){ beanContainer = BeanContainer.getInstance(); } /** * 执行依赖注入 * * 1.遍历Bean容器中全部的Class对象 * 2.遍历Class对象的全部成员变量 * 3.找出被Autowired标记的成员变量 * 4.获取这些成员变量的类型 * 5.获取这些成员变量的类型在容器里对应的实例 * 6.经过反射将对应的成员变量实例注入到成员变量所在类的实例里 */ public void doIoc(){ if(ValidationUtil.isEmpty(beanContainer.getClasses())){ log.warn("empty classset in BeanContainer"); return; } //1.遍历Bean容器中全部的Class对象 for(Class<?> clazz : beanContainer.getClasses()){ //2.遍历Class对象的全部成员变量 Field[] fields = clazz.getDeclaredFields(); if (ValidationUtil.isEmpty(fields)){ continue; } for(Field field : fields){ //3.找出被Autowired标记的成员变量 if(field.isAnnotationPresent(Autowired.class)){ Autowired autowired = field.getAnnotation(Autowired.class); String autowiredValue = autowired.value(); //4.获取这些成员变量的类型 Class<?> fieldClass = field.getType(); //5.获取这些成员变量的类型在容器里对应的实例 Object fieldValue = getFieldInstance(fieldClass, autowiredValue); if(fieldValue == null){ throw new RuntimeException("unable to inject relevant type,target fieldClass is:" + fieldClass.getName() + " autowiredValue is : " + autowiredValue); } else { //6.经过反射将对应的成员变量实例注入到成员变量所在类的实例里 Object targetBean = beanContainer.getBean(clazz); ClassUtil.setField(field, targetBean, fieldValue, true); } } } } } /** * 根据Class在beanContainer里获取其实例或者实现类 */ private Object getFieldInstance(Class<?> fieldClass, String autowiredValue) { Object fieldValue = beanContainer.getBean(fieldClass); if (fieldValue != null){ return fieldValue; } else { Class<?> implementedClass = getImplementedClass(fieldClass, autowiredValue); if(implementedClass != null){ return beanContainer.getBean(implementedClass); } else { return null; } } } /** * 获取接口的实现类 */ private Class<?> getImplementedClass(Class<?> fieldClass, String autowiredValue) { Set<Class<?>> classSet = beanContainer.getClassesBySuper(fieldClass); if(!ValidationUtil.isEmpty(classSet)){ if(ValidationUtil.isEmpty(autowiredValue)){ if(classSet.size() == 1){ return classSet.iterator().next(); } else { //若是多于两个实现类且用户未指定其中一个实现类,则抛出异常 throw new RuntimeException("multiple implemented classes for " + fieldClass.getName() + " please set @Autowired's value to pick one"); } } else { for(Class<?> clazz : classSet){//别名采用clazz.getSimpleName()简单实现 if(autowiredValue.equals(clazz.getSimpleName())){ return clazz; } } } } return null; } }
4.工具包
做为框架 类加载/反射/校验等功能。
package org.simplespring.util; import lombok.extern.slf4j.Slf4j; import java.io.File; import java.io.FileFilter; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.net.URL; import java.util.Arrays; import java.util.HashSet; import java.util.Set; /** * @Classname ClassUtil * @Description * @Date 2020/8/8 15:20 * @Created by zhangtianci */ @Slf4j public class ClassUtil { public static final String FILE_PROTOCOL = "file"; /** * 获取类加载器 * 由于项目部署在tomcat等一些web容器中 * 这些web容器加载部署在里面的apps 因此须要自定义classLoader去加载class文件 * 当咱们写框架须要去经过类加载器去加载class文件时 就须要经过Thread.currentThread().getContextClassLoader() * 拿到tomcat的自定义的classLoader去加载项目里面的class文件 * 详情 参考我写的classLoader加载相关文章 * @return */ public static ClassLoader getClassLoader(){ return Thread.currentThread().getContextClassLoader(); } /** * 经过包名加载class * * 点进去Class.forName()方法进去看看 * * @CallerSensitive * public static Class<?> forName(String className) * throws ClassNotFoundException { * Class<?> caller = Reflection.getCallerClass(); * return forName0(className, true, ClassLoader.getClassLoader(caller), caller); * } * * 获取到调用这个方法的class对象 而后用这个class对象的classLoader去加载这个class文件 * 因此归根结底是 拿到tomcat的自定义的classLoader去加载的class文件 * 因此没问题 * @param className class全名=package + 类名 * @return */ public static Class<?> loadClass(String className){ try { return Class.forName(className); } catch (ClassNotFoundException e) { log.error("loadClass failed!",e); throw new RuntimeException(e); } } /** * 经过一个class对象实例化一个实例对象(经过默认构造器) * @param clazz * @param accessible * @param <T> * @return */ public static <T> T newInstance(Class<?> clazz, boolean accessible){ try { //clazz.getDeclaredConstructor() 获取指定参数类表的构造器 //这里获取默认构造器 Constructor constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(accessible); return (T)constructor.newInstance(); } catch (Exception e) { log.error("new instance failed!"); throw new RuntimeException(e); } } /** * 设置类的属性值 * * @param field 成员变量 * @param target 类实例 * @param value 成员变量的值 * @param accessible 是否容许设置私有属性 */ public static void setField(Field field, Object target, Object value, boolean accessible){ field.setAccessible(accessible); try { field.set(target, value); } catch (IllegalAccessException e) { log.error("setField error", e); throw new RuntimeException(e); } } /** * 加载包路径下的全部的class文件 * 将class对象放进set集合中 * * 1.获取classLoader加载器 * 2.递归加载路径下的全部class文件 * @param packageName * @return */ public static Set<Class<?>> extractPackageClass(String packageName){ //1.获取classLoader加载器 ClassLoader classLoader = getClassLoader(); //2.获取资源文件的的url URL url = classLoader.getResource(packageName.replace(".","/")); if (url == null){ log.warn("unable to retrieve anything from package: " + packageName); return null; } //3.依据不一样的资源类型,采用不一样的方式获取资源的集合 Set<Class<?>> classSet = null; //过滤出文件类型的资源 if (url.getProtocol().equalsIgnoreCase(FILE_PROTOCOL)){ classSet = new HashSet<Class<?>>(); File packageDirectory = new File(url.getPath()); extractClassFile(classSet, packageDirectory, packageName); } //TODO 此处能够加入针对其余类型资源的处理 return null; } /** * 递归加载包路径下的class文件 * @param classSet * @param packageDirectory * @param packageName */ private static void extractClassFile(Set<Class<?>> classSet, File packageDirectory, String packageName) { if(!packageDirectory.isDirectory()){ return; } //若是是一个文件夹,则调用其listFiles方法获取文件夹下的文件或文件夹 File[] files = packageDirectory.listFiles(new FileFilter() { @Override public boolean accept(File pathname) { if(pathname.isDirectory()){ return true; } else{ //获取文件的绝对值路径 String absoluteFilePath = pathname.getAbsolutePath(); if(absoluteFilePath.endsWith(".class")){ //如果class文件,则直接加载 addToClassSet(absoluteFilePath); } } return false; } //根据class文件的绝对值路径,获取并生成class对象,并放入classSet中 private void addToClassSet(String absoluteFilePath) { //1.从class文件的绝对值路径里提取出包含了package的类名 //如/Users/zhangtc/springframework/simple-spring/target/classes/com/zhangtianci/entity/dto/MainPageInfoDTO.class //须要弄成com.zhangtianci.entity.dto.MainPageInfoDTO absoluteFilePath = absoluteFilePath.replace(File.separator, "."); String className = absoluteFilePath.substring(absoluteFilePath.indexOf(packageName)); className = className.substring(0, className.lastIndexOf(".")); //2.经过反射机制获取对应的Class对象并加入到classSet里 Class targetClass = loadClass(className); classSet.add(targetClass); } }); if(files == null){ return; } Arrays.stream(files).forEach( file -> { extractClassFile(classSet,file,packageName); }); } }
package org.simplespring.util; import java.util.Collection; import java.util.Map; public class ValidationUtil { /** * String是否为null或"" * * @param obj String * @return 是否为空 */ public static boolean isEmpty(String obj) { return (obj == null || "".equals(obj)); } /** * Array是否为null或者size为0 * * @param obj Array * @return 是否为空 */ public static boolean isEmpty(Object[] obj) { return obj == null || obj.length == 0; } /** * Collection是否为null或size为0 * * @param obj Collection * @return 是否为空 */ public static boolean isEmpty(Collection<?> obj){ return obj == null || obj.isEmpty(); } /** * Map是否为null或size为0 * * @param obj Map * @return 是否为空 */ public static boolean isEmpty(Map<?, ?> obj) { return obj == null || obj.isEmpty(); } }