在Spring里,创建被调用者的工作不再由调用者来完成,因此称为控制反转;创建被调用者实例的工作通常由Spring容器来完成,然后注入调用者,因此也称为依赖注入。
我们首先先来了解一下控制二字,也就是在控制“正”转的情况下,在任何一个有请求作用的系统当中,至少需要有两个类互相配合工作,在一个入口类下使用new关键字创建另一个类的对象实例,这就好比在面向对象编程的思想下,“我“充当一个入口类,在这个入口类中,我每次吃饭的时候都要买一双一次性筷子(每一次使用都要new一次),在这样的关系下,是”我“(即调用者)每次都要”主动“去买一次性筷子(另一个类),我对筷子说你老老实实的过来我的手上,是我控制了筷子,那好,在这种控制正转的关系下,放在现实生活当中,肯定是不现实的,而且人是懒惰的,他总会去创造出更加方便自己生活的想法,更确切的做法是,买一双普通的筷子(非一次性),把他放在一个容器当中(在Spring中叫做IOC容器),你需要使用的时候就对容器说:IOC我想要用筷子(向容器发出请求),接着筷子就会”注入“到的手上,而在这个过程当中,你不再是控制方,反而演变成一名请求者(虽然本身还是调用者),依赖于容器给予你资源,控制权坐落到了容器身上,于是这就是人们俗称的控制反转。
同样接着上面的例子,在控制反转的统一下,筷子是怎么来到我的手上(即我们是如何获得请求的类),这就是一个依赖注入的过程。
设计原则中好莱坞原则描述到,“别找我们,我们找你”,百度百科上对这点描述是“不要给我们打电话,我们会给你打电话(don‘t call us, we‘ll call you)”这是著名的好莱坞原则。在好莱坞,把简历递交给演艺公司后就只有回家等待。由演艺公司对整个娱乐项的完全控制,演员只能被动式的接受公司的差使,在需要的环节中,完成自己的演出。这一点完美的提现了在IOC身上,IOC所注重的是设计思想上,从一个常规的创建对象的做法,即new一个对象,转变成向IOC容器递交”简历“,被动的等待IOC容器返回资源给你。控制反转即指的是”演艺公司控制演员“,而说到依赖,则是“演员需要公司混饭”,我们所需求的对象,需要依赖容器来获得,这个过程即是依赖注入。本质上IOC和DI是同一思想下不同维度的表现。
1)一般情况下的类耦合
Main.java
public class Main { public static void main(String[] args) { /******** 一般写法,Main类与Chinese类和American类之间的强耦合 ***********/ // Chinese和American,当类和方法修改时,此处的类和方法也需要修改 Chinese chinese = new Chinese(); chinese.sayHelloWorld("张三"); American american = new American(); american.sayHelloWorld("Jack"); } } /******************** 一般方法 ***************************/ interface Human { public void sayHelloWorld(String name); } class Chinese implements Human { public void sayHelloWorld(String name) { String helloWorld = "你好," + name; System.out.println(helloWorld); } } class American implements Human { public void sayHelloWorld(String name) { String helloWorld = "Hello," + name; System.out.println(helloWorld); } }通过上面代码可以看出:Main类与Chinese类和American类之间存在着强耦合 , Chinese和American类和方法修改时,此处的类和方法也需要修改。不容易扩展和维护。
2)工厂方法来解耦合
public class Main { public static void main(String[] args) { /******** 工厂方法, Main类与类Chinese和American不再耦合,仅仅和其接口Human耦合 ***********/ // 修改时还需要修改在Main类中修改这些字符串 // Chinese和American,当类和方法修改时,只有方法需要修改 HumanFactory humanFactory = new HumanFactory(); Human human1 = humanFactory.getHuman("chinese"); human1.sayHelloWorld("张三"); Human human2 = humanFactory.getHuman("american"); human2.sayHelloWorld("Jack"); } } /******************** 工厂方法 ***************************/ interface Human { public void sayHelloWorld(String name); } class HumanFactory { public Human getHuman(String type) { if ("chinese".equals(type)) { return new Chinese(); } else { return new American(); } } }通过上面代码可以看出:Main类与类Chinese和American不再耦合,仅仅和其接口Human耦合,修改时还需要修改在Main类中修改这些字符串,当类和方法修改时,只有方法需要修改。这一定程度上降低了Main类和Chinese、American类的耦合
3)依赖注入和控制反转
public class Main { public static void main(String[] args) { /******************** IOC控制反转和依赖注入 ***************************/ // 利用容器,通过xml文件直接注入属性值,在Main类中只添加需要的 // Chinese和American,当类和方法修改时,代码完全不用修改,只需要修改xml文件即可,彻底实现了解耦 BeanFactory beanFactory = new BeanFactory(); beanFactory.init("/config.xml"); UserBean userBean = (UserBean) beanFactory.getBean("userBean"); System.out.println("userName=" + userBean.getUserName()); System.out.println("password=" + userBean.getPassword()); } } /******************** IOC控制反转和依赖注入 ***************************/ // 下面是Spring的IOC实现:Bean工厂 class BeanFactory { private Map<String, Object> beanMap = new HashMap<String, Object>(); public void init(String fileName) { try { // 读取指定的配置文件 SAXReader reader = new SAXReader(); // System.out.println(xmlpath); String realPathString = new File("").getCanonicalPath(); Document document = reader.read(new File(realPathString + "/src/com/devin/") + fileName); Element root = document.getRootElement(); Element foo; // 遍历bean for (Iterator i = root.elementIterator("bean"); i.hasNext();) { foo = (Element) i.next(); // 获取bean的属性id和class Attribute id = foo.attribute("id"); Attribute cls = foo.attribute("class"); // 利用Java反射机制,通过class的名称获取Class对象 Class bean = Class.forName(cls.getText()); // 获取对应class的信息 java.beans.BeanInfo info = java.beans.Introspector.getBeanInfo(bean); // 获取其属性描述 java.beans.PropertyDescriptor pd[] = info.getPropertyDescriptors(); // 设置值的方法 Method mSet = null; // 创建一个对象 Object obj = bean.newInstance(); // 遍历该bean的property属性 for (Iterator ite = foo.elementIterator("property"); ite.hasNext();) { Element foo2 = (Element) ite.next(); // 获取该property的name属性 Attribute name = foo2.attribute("name"); String value = null; // 获取该property的子元素value的值 for (Iterator ite1 = foo2.elementIterator("value"); ite1.hasNext();) { Element node = (Element) ite1.next(); value = node.getText(); break; } for (int k = 0; k < pd.length; k++) { if (pd[k].getName().equalsIgnoreCase(name.getText())) { mSet = pd[k].getWriteMethod(); mSet.invoke(obj, value); } } } // 将对象放入beanMap中,其中key为id值,value为对象 beanMap.put(id.getText(), obj); } } catch (Exception e) { System.out.println(e.toString()); } } // 通过bean的id获取bean的对象. public Object getBean(String beanName) { Object obj = beanMap.get(beanName); return obj; } } UserBean.java public class UserBean { private String userName; private String password; public String getPassword() { return password; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public void setPassword(String password) { this.password = password; } } config.xml <?xml version="1.0" encoding="UTF-8"?> <beans> <bean id="userBean" class="com.devin.UserBean"> <property name="userName"> <value>张三</value> </property> <property name="password"> <value>Jack</value> </property> </bean> </beans>说明:模拟了Spring中IOC的实现,虽然只是完成了Spring中依赖注入的一小部分工作,但是很好的展现了Java反射机制在Spring中的应用,能使我们能更好的从原理上了解IOC的实现。