本篇文章和大家一起交流一下有关spring的ioc知识,并自己通过代码实现一个简单版的spring的IOC功能。首先看一下IOC基础知识的分享:
首先要分享的是Iteye的开涛这位技术牛人对Spring框架的IOC的理解,写得非常通俗易懂,以下内容全部来自原文,原文地址:http://jinnianshilongnian.iteye.com/blog/1413846
Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。如何理解好Ioc呢?理解好Ioc的关键是要明确“谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了”,那我们来深入分析一下:
●谁控制谁,控制什么:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对 象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。
●为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。
用图例说明一下,传统程序设计如图2-1,都是主动去创建相关对象然后再组合起来:
图1-1 传统应用程序示意图
当有了IoC/DI的容器后,在客户端类中不再主动去创建这些对象了,如图2-2所示:
图1-2有IoC/DI容器后程序结构示意图
IoC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是 松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。
其实IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源了。
IoC很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找我们,我们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。
DI—Dependency Injection,即“依赖注入”:组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:
●谁依赖于谁:当然是应用程序依赖于IoC容器;
●为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;
●谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;
●注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。
IoC和DI由什么关系呢?其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。
看过很多对Spring的Ioc理解的文章,好多人对Ioc和DI的解释都晦涩难懂,反正就是一种说不清,道不明的感觉,读完之后依然是一头雾水,感觉就是开涛这位技术牛人写得特别通俗易懂,他清楚地解释了IoC(控制反转) 和DI(依赖注入)中的每一个字,读完之后给人一种豁然开朗的感觉。我相信对于初学Spring框架的人对Ioc的理解应该是有很大帮助的。
首先想说说IoC(Inversion of Control,控制反转)。这是spring的核心,贯穿始终。所谓IoC,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系。这是什么意思呢,举个简单的例子,我们是如何找女朋友的?常见的情况是,我们到处去看哪里有长得漂亮身材又好的mm,然后打听她们的兴趣爱好、qq号、电话号、ip号、iq号………,想办法认识她们,投其所好送其所要,然后嘿嘿……这个过程是复杂深奥的,我们必须自己设计和面对每个环节。传统的程序开发也是如此,在一个对象中,如果要使用另外的对象,就必须得到它(自己new一个,或者从JNDI中查询一个),使用完之后还要将对象销毁(比如Connection等),对象始终会和其他的接口或类藕合起来。
那么IoC是如何做的呢?有点像通过婚介找女朋友,在我和女朋友之间引入了一个第三者:婚姻介绍所。婚介管理了很多男男女女的资料,我可以向婚介提出一个列表,告诉它我想找个什么样的女朋友,比如长得像李嘉欣,身材像林熙雷,唱歌像周杰伦,速度像卡洛斯,技术像齐达内之类的,然后婚介就会按照我们的要求,提供一个mm,我们只需要去和她谈恋爱、结婚就行了。简单明了,如果婚介给我们的人选不符合要求,我们就会抛出异常。整个过程不再由我自己控制,而是有婚介这样一个类似容器的机构来控制。Spring所倡导的开发方式就是如此,所有的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。
IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的。比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了 spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖 Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。那么DI是如何实现的呢? Java 1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的。
理解了IoC和DI的概念后,一切都将变得简单明了,剩下的工作只是在spring的框架中堆积木而已。
理论知识知道了,为了加深理解,我们自己可以手动写代码实现一个简单版的spring的IOC,看项目概况图:annoation:定义常用的两个依赖注解,一个是标致service的,一个是service的属性依赖,看具体代码
package com.ioc.spring.annoation; import java.lang.annotation.*; /** * @Author 18011618 * @Description 自定义服务的依赖注入 * @Date 16:12 2018/6/18 * @Modify By */ @Documented @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface IocService { String name() default ""; }
package com.ioc.spring.annoation; import java.lang.annotation.*; /** * @Author 18011618 * @Description 自定义属性的依赖注入 * @Date 16:13 2018/6/18 * @Modify By */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface IocResource { }
service:定义两个简单演示的service,并且增加对应的注解
订单服务:
package com.ioc.spring.service.impl; import com.ioc.spring.annoation.IocService; import com.ioc.spring.service.IOrderService; /** * @Author 18011618 * @Description * @Date 15:56 2018/6/18 * @Modify By */ @IocService public class OrderService implements IOrderService { public String findOrder(String username) { return "用户"+username+"的订单编号是:1001"; } }
用户服务:依赖于订单服务
package com.ioc.spring.service.impl; import com.ioc.spring.annoation.IocResource; import com.ioc.spring.annoation.IocService; import com.ioc.spring.service.IOrderService; import com.ioc.spring.service.IUserService; /** * @Author 18011618 * @Description * @Date 15:53 2018/6/18 * @Modify By */ @IocService(name = "userbiz") public class UserService implements IUserService { /*比较脆弱啊 这块的属性名称一定要用实现类来命名 且 按照第一个字母要小写的原则 否则很报错的*/ @IocResource private IOrderService orderService; public String findOrder(String username) { return orderService.findOrder(username); } }
util:定义一个工具类主要扫描某个包路径下面的所有class
package com.ioc.spring.util; import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.net.JarURLConnection; import java.net.URL; import java.net.URLDecoder; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarFile; /** * 获取某个包下面的所有类信息 */ public class ClassUtil { /** * 取得某个接口下所有实现这个接口的类 */ public static ListgetAllClassByInterface(Class c) { List returnClassList = null; if (c.isInterface()) { // 获取当前的包名 String packageName = c.getPackage().getName(); // 获取当前包下以及子包下所以的类 List > allClass = getClasses(packageName); if (allClass != null) { returnClassList = new ArrayList (); for (Class classes : allClass) { // 判断是否是同一个接口 if (c.isAssignableFrom(classes)) { // 本身不加入进去 if (!c.equals(classes)) { returnClassList.add(classes); } } } } } return returnClassList; } /* * 取得某一类所在包的所有类名 不含迭代 */ public static String[] getPackageAllClassName(String classLocation, String packageName) { // 将packageName分解 String[] packagePathSplit = packageName.split("[.]"); String realClassLocation = classLocation; int packageLength = packagePathSplit.length; for (int i = 0; i < packageLength; i++) { realClassLocation = realClassLocation + File.separator + packagePathSplit[i]; } File packeageDir = new File(realClassLocation); if (packeageDir.isDirectory()) { String[] allClassName = packeageDir.list(); return allClassName; } return null; } /** * 从包package中获取所有的Class * @param packageName * @return */ public static List > getClasses(String packageName) { // 第一个class类的集合 List > classes = new ArrayList >(); // 是否循环迭代 boolean recursive = true; // 获取包的名字 并进行替换 String packageDirName = packageName.replace('.', '/'); // 定义一个枚举的集合 并进行循环来处理这个目录下的things Enumeration dirs; try { dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName); // 循环迭代下去 while (dirs.hasMoreElements()) { // 获取下一个元素 URL url = dirs.nextElement(); // 得到协议的名称 String protocol = url.getProtocol(); // 如果是以文件的形式保存在服务器上 if ("file".equals(protocol)) { // 获取包的物理路径 String filePath = URLDecoder.decode(url.getFile(), "UTF-8"); // 以文件的方式扫描整个包下的文件 并添加到集合中 findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes); } else if ("jar".equals(protocol)) { // 如果是jar包文件 // 定义一个JarFile JarFile jar; try { // 获取jar jar = ((JarURLConnection) url.openConnection()).getJarFile(); // 从此jar包 得到一个枚举类 Enumeration entries = jar.entries(); // 同样的进行循环迭代 while (entries.hasMoreElements()) { // 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件 JarEntry entry = entries.nextElement(); String name = entry.getName(); // 如果是以/开头的 if (name.charAt(0) == '/') { // 获取后面的字符串 name = name.substring(1); } // 如果前半部分和定义的包名相同 if (name.startsWith(packageDirName)) { int idx = name.lastIndexOf('/'); // 如果以"/"结尾 是一个包 if (idx != -1) { // 获取包名 把"/"替换成"." packageName = name.substring(0, idx).replace('/', '.'); } // 如果可以迭代下去 并且是一个包 if ((idx != -1) || recursive) { // 如果是一个.class文件 而且不是目录 if (name.endsWith(".class") && !entry.isDirectory()) { // 去掉后面的".class" 获取真正的类名 String className = name.substring(packageName.length() + 1, name.length() - 6); try { // 添加到classes classes.add(Class.forName(packageName + '.' + className)); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } } } } catch (IOException e) { e.printStackTrace(); } } } } catch (IOException e) { e.printStackTrace(); } return classes; } /** * 以文件的形式来获取包下的所有Class * * @param packageName * @param packagePath * @param recursive * @param classes */ public static void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive, List > classes) { // 获取此包的目录 建立一个File File dir = new File(packagePath); // 如果不存在或者 也不是目录就直接返回 if (!dir.exists() || !dir.isDirectory()) { return; } // 如果存在 就获取包下的所有文件 包括目录 File[] dirfiles = dir.listFiles(new FileFilter() { // 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件) public boolean accept(File file) { return (recursive && file.isDirectory()) || (file.getName().endsWith(".class")); } }); // 循环所有文件 for (File file : dirfiles) { // 如果是目录 则继续扫描 if (file.isDirectory()) { findAndAddClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive, classes); } else { // 如果是java类文件 去掉后面的.class 只留下类名 String className = file.getName().substring(0, file.getName().length() - 6); try { // 添加到集合中去 classes.add(Class.forName(packageName + '.' + className)); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } } }
context:定义模拟spring的上下文,初始化bean对象和对应的属性对象
package com.ioc.spring.context; import com.ioc.spring.annoation.IocResource; import com.ioc.spring.annoation.IocService; import com.ioc.spring.util.ClassUtil; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentHashMap; /** * @Author 18011618 * @Description 模拟spring容器 管理对象的依赖注入 * @Date 16:17 2018/6/18 * @Modify By */ public class SpringContext { private String path; /*String :beanId Object:serviceimpl*/ ConcurrentHashMap,Object>initBean = null; public SpringContext(String path){ this.path = path; } /** * 根据beanid获取对应的bean * @param beanId * @return * @throws Exception */ public Object getBean(String beanId) throws Exception{ List classes = findAnnoationService() ; if (classes == null || classes.isEmpty()) { throw new Exception("no found anything bean is useding initial.."); } initBean = initBean(classes); if (initBean == null || initBean.isEmpty()) { throw new Exception("initial bean is empty or null"); } Object object = initBean.get(beanId); //初始化属性的依赖 initAttribute(object); return object; } /** * 初始化依赖的属性 * @param object * @throws IllegalArgumentException * @throws IllegalAccessException */ private void initAttribute(Object object)throws Exception{ //获取object的所有类型 Class extends Object> classinfo = object.getClass(); //获取所有的属性字段 Field[] fields = classinfo.getDeclaredFields(); //遍历所有字段 for(Field field : fields){ //查找字段上有依赖的注解 boolean falg = field.isAnnotationPresent(IocResource.class); if (falg){ IocResource iocResource = field.getAnnotation(IocResource.class); if (iocResource!=null){ //获取属性的beanid String beanId = field.getName(); //获取对应的object Object attrObject = getBean(beanId); if (attrObject!=null){ //访问私有字段 field.setAccessible(true); //赋值 field.set(object,attrObject); continue; } } } } } /** * 初始化bean * @param classes * @return * @throws IllegalAccessException * @throws InstantiationException */ public ConcurrentHashMap,Object>initBean(List classes) throws IllegalAccessException,InstantiationException{ ConcurrentHashMap,Object> map = new ConcurrentHashMap , Object>(); String beanId=""; for(Class clazz :classes){ Object object = clazz.newInstance(); IocService annotation =(IocService)clazz.getDeclaredAnnotation(IocService.class); if (annotation!=null){ //如果定义了name属性 以实现的name属性为主否则以默认的规则为主 String value = annotation.name(); if (value!=null && !value.equals("")){ beanId = value; } else { beanId = toLowerCaseFirstOne(clazz.getSimpleName()); } } //存储值 map.put(beanId,object); } return map; } /** * 查找包路径下面所有添加注解的类 @IocService * @return * @throws Exception */ private List findAnnoationService()throws Exception{ if (path==null || path.equals("")){ throw new Exception("scan package address is null or empty.."); } //获取包下面所有的类 List > classes = ClassUtil. getClasses(path); if (classes==null || classes.size()==0){ throw new Exception("not found service is added annoation for @iocservice"); } ListannoationClasses = new ArrayList() ; for (Class clazz:classes){ //通过反射机制 查找增加了注解的类 IocService iocService = (IocService) clazz.getDeclaredAnnotation(IocService.class); if (iocService!=null){ annoationClasses.add(clazz); continue; } } return annoationClasses; } /** * 首字母转换为小写 * @param s * @return */ public static String toLowerCaseFirstOne(String s) { if (Character.isLowerCase(s.charAt(0))){ return s; } else{ return (new StringBuilder()).append(Character.toLowerCase(s.charAt(0))).append(s.substring(1)).toString(); } } }
ok,到此为止一个简单版的spring的IOC功能就完成了,我们可以写个测试类来测试一下:
package com.ioc.spring.test; import com.ioc.spring.context.SpringContext; import com.ioc.spring.service.IUserService; /** * @Author 18011618 * @Description * @Date 16:45 2018/6/18 * @Modify By */ public class SpringIocTest { public static void main(String[] args)throws Exception { String path = "com.ioc.spring.service.impl"; SpringContext context = new SpringContext(path); IUserService userService = (IUserService) context.getBean("userbiz"); System.out.println(userService.findOrder("lyl")); } }
代码很少,我们总结一下实现的思落过程
1 首先需要实现类似于spring的componetscan:自动包扫描
2 找到该包下面哪些类是加了对应的注解
3 找到这些加了注解的类,进行模拟spring初始化功能,beanid和bean实例化功能
4 对应的依赖属性初始化:主要通过反射+已经实例化好的bean集合
注意:上面的代码只是帮助我们更好的理解IOC机制,但和原始的spring的IOC相比是不可相提并论的,建议大家还是去看看spring源码.