看完上一个章节,相信你已经掌握了一些思考的本领了。今天我们来聊一个新的话题。上一篇文章是一个简约的话题,希望简约而不简单。当然,如果觉得太浅了也请立刻告知猿人工厂君,可以考虑做一些调整来更好的帮助到你,另外真的很感谢大家的支持,和巨兽的斗争暂时进入僵持阶段,猿人工厂君已经说了,虽千万人,吾往矣。中间细节,猿人工厂君,会在方便的时候公开,程序猿鸭,且行且珍惜。
猿思考是一个原创系列文章,帮助你从一个小白快速掌握基础知识,很多基础知识,在于思考的变通,更多精彩内容,敬请大家关注公主号猿人工厂,点击猿人养成获取!
IOC是Inversion ofControl的英文缩写,中文译名控制反转。控制反转?哈哈,是不是看起来怪怪的?控制反转,也是面向对象的一种编程原则,是一种编程思想。它是指将创建对象的管理权交给容器,由容器来装配和创建对象,从而降低代码之间的耦合度。IOC思想的实现,一般有两种方式,一种是依赖查找(Dependency Lookup)一种就是最常见的依赖注入(Dependency Injection) 。依赖查找是指程序运行时,程序使用容器提供的回调接口和上下文条件来查找资源和对象,从而让程序获得相应资源。这个种方式应用不是很多,需要应用服务器的支持,也就是JAVAEE规范的套路,比如JNDI,EJB...本文就不详细讨论了。而依赖注入(Dependency Injection)是什么呢?依赖注入是指,程序在运行时,程序不用做依赖的定位查询,提供普通的java方法去让容器决定依赖关系,容器全权负责程序使用对象的装配,把符合依赖关系的对象,通过属性或者是构造器的方式传递给依赖的对象。
我们看个简单的例子,我们有三个类:分别是TestServiceTestManager TestDao.其中TestService种有一个test()方法,调用了TestManager种的test()方法,而TestManager的test方法又调用了TestDao的test()方法。如果我们要调用TestService的test()方法,在正常情况下我们编写java代码,必然需要new TestService() newTestManager(), new TestDao().然后将TestManager对象作为TestService的一个属性传递给TestService(testService.setXXX或者是构造函数的方式传递),TestDao对象作为TestManager的一个属性,那么在使用TestService的类中,必然就需要在代码中import TestManager,
这样新编写的这个类代码依赖程度就高,耦合性强,不好维护。那现在改为依赖注入的方式,我们要使用TestService,就找容器拿TestService,容器提供的 TestService对象里已经存在TestManager对象了,那么我们使用TestService时,就只用在代码里import TestService就好了!新编写的类耦合性就没那么强了,易于维护多了。
那怎么去做到它呢?
不就是提供一个容器,获取对象嘛,只是获取的对象里的属性,已经由容器设置好了嘛。怎么办?通过名字创建对象!嗯,说白了,还是反射。下面我们来看一个小例子,实现简单的依赖注入。
先定义两个annotation:
package com.pz.study.frame.ioc;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * 用于注解需要由容器管理的类 * * @author pangzi * */@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface Component { String value() default"";}
package com.pz.study.frame.ioc;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * 用于注解需要注入的属性 * @author pangzi * */@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public@interfaceAutowared {}
定义一个简单的容器类:
package com.pz.study.frame.ioc;import java.io.File;import java.io.IOException;import java.io.InputStream;import java.lang.reflect.Field;import java.net.URL;import java.util.ArrayList;import java.util.List;import java.util.Map;import java.util.Properties;import java.util.concurrent.ConcurrentHashMap;public class ApplicationContext { public static final Map<String,Object> applicationContext = new ConcurrentHashMap<String, Object>(); static { InputStream stream =ApplicationContext.class.getClassLoader().getResourceAsStream("ioc-frame.properties"); Propertiesproperties = new Properties(); try { properties.load(stream); } catch (IOException e) { e.printStackTrace(); } StringpackageName=properties.getProperty("package").toString(); try { instanceBean(packageName); } catch (Exception e) { e.printStackTrace(); } } /** * 根据annotation上的名字获取对象 * @param beanId * @return */ public static Object getBean(String beanId){ returnapplicationContext.get(beanId); } /** * 创建包名下的对象并完成依赖注入 * @param packageName * @throws Exception */ private static void instanceBean(StringpackageName) throws Exception { instanceByPackageName(packageName); autoWared(); } // 获取指定包路径下使用了 ComponentAnnotationBean的实例 private static void instanceByPackageName(String packageName) { try { List<String>classNames= getClassName(packageName,true); for(String className:classNames){ Class<?>clazz = Class.forName(className); if (clazz.isAnnotationPresent(Component.class)) { clazz.getAnnotation(Component.class).value(); applicationContext.put(clazz.getAnnotation(Component.class).value(), clazz.newInstance()); } } } catch (Exception e) { e.printStackTrace(); } } /** * 获取某包下所有类 * * @param packageName * 包名 * @param childPackage * 是否遍历子包 * @return类的完整名称 */ private static List<String> getClassName(String packageName, boolean childPackage) { List<String> fileNames= null; ClassLoader loader = Thread.currentThread().getContextClassLoader(); String packagePath =packageName.replace(".", "/"); URL url =loader.getResource(packagePath); if (url != null) { String type =url.getProtocol(); if (type.equals("file")) { fileNames = getClassNameByFile(url.getPath(), null, childPackage); } } return fileNames; } /** * 从项目文件获取某包下所有类 * * @param filePath * 文件路径 * @param className * 类名集合 * @param childPackage * 是否遍历子包 * @return类的完整名称 */ private static List<String> getClassNameByFile(String filePath,List<String> className, boolean childPackage) { List<String> myClassName= new ArrayList<>(); File file = new File(filePath); File[] childFiles =file.listFiles(); for (File childFile :childFiles) { if (childFile.isDirectory()) { if (childPackage) { myClassName.addAll(getClassNameByFile(childFile.getPath(),myClassName, childPackage)); } } else { String childFilePath= childFile.getPath(); if (childFilePath.endsWith(".class")) { childFilePath =childFilePath.substring(childFilePath.indexOf("/classes") + 9, childFilePath.lastIndexOf(".")); childFilePath =childFilePath.replace("/", "."); myClassName.add(childFilePath); } } } return myClassName; } /** * 遍历map注入属性 */ private static void autoWared() { Map<String, Object> map= applicationContext; try { for(StringbeanId:map.keySet()){ Object obj=map.get(beanId); Field[] fields =obj.getClass().getDeclaredFields(); for (Field field : fields) { if (field.isAnnotationPresent(Autowared.class)) { field.setAccessible(true); Object fieldObj= map.get(field.getName()); field.set(obj,fieldObj); } } } }catch(Exception e){ e.printStackTrace(); } }}
我们如果要使用容器创建的对象直接使用ApplicationContext.getBean()就好了。
接下来我们编写一单验证代码,使用我们的annotation和ApplicationContext提供的对象。
编写3个接口:
package com.pz.study.frame.test;public interface TestService { public void test();}
package com.pz.study.frame.test;public interface TestManager { public void test();}
package com.pz.study.frame.test;public interface TestDao { public void test();}
package com.pz.study.frame.test;import com.pz.study.frame.ioc.Autowared;import com.pz.study.frame.ioc.Component;@Component(value="testService")public class TestServiceImpl implements TestService { @Autowared private TestManager testManager; public void test(){ testManager.test(); }}
package com.pz.study.frame.test;import com.pz.study.frame.ioc.Autowared;import com.pz.study.frame.ioc.Component;@Component(value="testManager")public class TestManagerImpl implements TestManager { @Autowared private TestDao testDao; publicvoid test(){ testDao.test(); }}
package com.pz.study.frame.test;import com.pz.study.frame.ioc.Component;@Component(value="testDao")public class TestDaoImpl implements TestDao { public void test(){ System.out.println("ioc 小框架测试"); }}
最后编写运行程序
package com.pz.study.frame.test;import com.pz.study.frame.ioc.ApplicationContext;public class TestIoc { public static void main(String args[]){ TestService testService=(TestService) ApplicationContext.getBean("testService"); testService.test(); }}
运行程序,我们发现代码正确运行,而我们的程序只依赖了TestService这个接口,程序将需要依赖的对象交由自己定义的容器ApplicationContext去创建和装备。我们简单的实现了依赖注入的原理!