再谈依赖注入(依赖注入的简单实现)
之前说过,依赖注入就是为了解决依赖的问题的,在Spring中,本来应该自己入new的对象自己不来new了,交给bean去new。其实使用反射可以实现依赖注入。
下面就是简单实现的方式:
使用反射可以new出新的实例,我们可以这么做:
1 public Object getInstance(String ClassName){ 2 private Object result=null; 3 try { 4 result=Class.forName(ClassName).newInstance(); 5 } catch (ClassNotFoundException e) { 6 e.printStackTrace(); 7 } catch (InstantiationException e) { 8 e.printStackTrace(); 9 } catch (IllegalAccessException e) { 10 e.printStackTrace(); 11 } 12 return result; 13 }
为了保证通用性,这里使用了返回Object类型。之后可以直接在代码中调用这个方法,在反射中实现依赖注入(假设有一个User接口和他的实现类):
1 TestIOC testIOC=new TestIOC(); 2 User user=(User)testIOC.getInstance("com.smile.UserImpl"); 3 user.sayHey();
第一行为刚刚封装反射方法的类。好像还是没有达到预知的效果,这里开始修改:
将实现类放到外面的配置文件中,到使用的时候载注入到其中。
那么我们加入了以下配置文件:
1 <?xml version="1.0" encoding="utf-8"?> 2 <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> 3 <properties> 4 <entry key="className">com.smile.User</entry> 5 </properties>
然后在读取配置文件:
1 InputStream inputStream=ClassLoader.getSystemResourceAsStream("testxml.xml"); 2 Properties properties=new Properties(); 3 properties.loadFromXML(inputStream); 4 String ClassName=properties.getProperty("className"); 5 System.out.println(ClassName);
这里好像有点似曾相识了,之前使用Spring的时候,就有类似的代码:
1 public void UseTest(){ 2 ApplicationContext context=new ClassPathXmlApplicationContext("SpringConfig.xml"); 3 Hello hello=(Hello)context.getBean("hello"); 4 hello.sayHello(); 5 }
读取玩配置文件还会进行下一步的操作,也会有类似的代码:
1 TestIOC testIOC=new TestIOC(); 2 User user=(User)testIOC.getInstance(ClassName); 3 user.sayHey();
是不是和Spring的代码中惊人的相似了,我们在程序中没有自己去new对象,而是交给了我们自己写出来的方法去new,我们的代码会根据我们自己写的配置文件得到配置文件的信息,然后去new配置文件中写好的对象,将依赖注入到这里,如果我们要使用的实现类出现了变化,那么只需要修改xml配置文件中的信息就好了,大大减少了修改代码的成本。通过以上就简单的实现了依赖注入。
这种实现方式实际上使用了一种设计模式——抽象工厂。
那么抽象工厂是一种什么样的设计模式呢?提供一个创建一系列相关或相互依赖的接口对象。而无需指定具体的类。
在刚刚的代码中User就是抽象,之所以是抽象,是因为User可能会有不同的实现,而我们利用反射而得到的对象的方法——getInstance()就是工厂。之所以没有看到抽象工厂的影子是因为使用反射的时候不需要再代码中显示的new出对象,而类名是字符串,可以随意修改,反射取代了抽象工厂,完成了依赖注入。
如果这里使用纯粹的抽象工厂的话会出现大量的类来new我们需要的实例,反射只需要一个方法可以new出来很多我们需要的实例。
当然因为类过多的时候我们也可以使用简单工厂来改进抽象工厂,就是讲new的过程放到switch中new,在new的时候根据输入的字符来判断使用哪一种方式,但是这里如果我们增加一个实现类的时候,就要再次修改代码,在switch中加入case,这样还是会很麻烦,而利用反射也改进了这里的缺点。