spring IOC机制 制霸简结
spring的ioc基于反射机制实现。所以聊ioc之前,不得不提一下反射。
一、反射:
1.一个简单实例
在创建jdbc数据连接前,需要加载jdbc的驱动类:
1 Class.forName("com.mysql.jdbc.Driver");
这就是一个简单的反射。这与new 一个类不一样,Class.forName()中间是一个String,即加载类前,jvm会多做一件事 —— java.lang.String --> java.lang.Class 。
注意到这一点,我们在改进jdbc连接代码时就可以这么玩——将驱动的名字写进配置文件,然后读到数据库操作类中。
jdbc.properties
1 driver=com.mysql.jdbc.Drive
DataSourceUtil.java
1 import java.util.Properties; 2 import java.sql.DriverManager; 3 import java.io.*; 4 5 public class DataSourceUtil { 6 private static String driver; 7 8 static { 9 Properties properties = new Properties(); 10 InputStream is = DataSourceUtil.class.getClassLoader() 11 .getResourceAsStream("jdbc.properties"); 12 try { 13 properties.load(is); 14 } catch (IOException e) { 15 throw new RuntimeException(e); 16 } 17 driver = properties.getProperty("driver"); 18 } 19 20 Class.forName(driver);
21 ......
配置化的好处就不多说了。这里要说的是反射的好处:因为有反射机制,使得我们配置化的资源变多,大大减少硬编码(hard code)。
2.反射是在程序运行时发挥作用的。
这是反射调用类与我们在代码里写构造函数真正的区别。
现在我们在代码里new一个类 Haha ha = new Haha(); 。jvm在运行new Haha()前,会先检查类Haha.class是否加载,寻找类对应的class对象 ( 这个对象跟ha代表的对象意义不同——jvm启动后,类加载器会将class文件加载到jvm的方法区中,创建类的class对象到堆中,注意这个是类的类型对象,每个类只有一个class对象,作为方法区类的数据结构的接口 ),若加载好,则为你的对象分配内存,初始化Haha()。
回到反射的代码 Class.forName("com.mysql.jdbc.Driver"); 。jvm直接根据全类名"com.mysql.jdbc.Driver"找到并加载类。后面创建类的类型对象等等步骤不赘述。
简单的说,直接构造函数需要jvm事先将类加载到内存中,而反射则是在jvm运行时完成类加载的过程。由此可见反射是一个相当动态的过程,需要什么就加载什么,不要求jvm在启动时把所有资源都加载。而且在实际中,我们需要动态更改或者引用一些类(例如换个驱动类),这些类肯定没有事先加载,反射调用可以满足我们这些需要灵活处理的需求。
3.反射的弊端
凡事好坏相对,反射的动态加载过程必然牺牲性能,在某贴看过测试,反射调用相对于new耗费多几百倍的时间。当然就算是几百倍也是毫秒级的事情,通常不会成为制约运行时间的关键。另外,反射除了用在上面实例中的Lib类外,也可以用到直接的业务类(后面的ioc就是这么干的)中,这样除了性能(初次调用),还会减低代码可读性。
二、IOC
Inverse of Control,控制反转,是spring容器的内核。IOC是一种理念,目的是解除代码中的紧耦合关系。
1.紧耦合的例子
我们要调用某一个类的动态方法,首先要构造函数:
Test.java
Service s=new Service();
这里为了获取Service类的内容,首先new Service(),然后赋给Service类型的对象s。这种调用导致了紧耦合——
(1)上下级依赖性强。Service由Test创建,生命周期完全由Test控制,即控制权牢牢地在Test手里。
(2)生产机制效率低下,不可重用。假如在类Testtwo中也要调用Service,不得不自己构造函数。
2.spring的解决方案——IOC
spring的ioc机制就是为了解决紧耦合的问题。首先,spring在程序运行时帮我我们创建好所有类的实例,放在spring容器中。然后,当我们需要调用类时,让spring将实例注入到调用类中,完成实例化的过程。这样就解决了紧耦合——
(1)打破上下级的依赖关系。Service不再由调用类Test创建,而是spring创建(通过反射创建),并放到spring容器中。生命周期由spring控制。
(2)提高效用和重用性。spring默认用单例模式创建对象,比起每次new效率高,哪里需要调用便注入到哪里。
因为ioc,我们可以把spring看作是一个“管家式”框架,将层层耦合的程序打散成不同的小模块,统一由spring协调管理,完成实例化,调用,结束回收等过程。控制反转,可以这样字面理解,实现类的控制权从调用类中移除,“反转”移交给第三方spring容器。
3.spring的解决例子
在代码层面上,spring就是设法将 Service s = new Service() 两边的Service弄掉。
前面的好解决,spring面向接口编程,即多创建一个Service的接口类ServiceIfc(),这样代码就变成了 ServiceIfc s = new Service() 。
new Service()部分这样处理——
(1)在spring容器中创建实例,需要在spring配置文件里加入相应的bean。
applicationContext.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> 3 <beans> 4 <bean id="Service" class="demo.Service"> 5 </bean> 6 </beans>
(2)要从spring容器中拿出来,需要用到ApplicationContext。
BeanUtil.java
1 import org.springframework.context.*; 2 3 public class BeanUtil { 4 //单例创建ApplicationContext 5 private static ApplicationContext appContext = new ClassPathXmlApplicationContext("applicationContext.xml"); 6 private BeanUtil() {} 7 8 public static Object getBean(String name){ 9 return appContext.getBean(name); 10 } 11 }
(3)注入到调用类Test
ServiceIfc s=(ServiceIfc)BeanUtil.getBean("Service");
至此,一个创建到调用的过程就完成了。这是一个理解性的过程。在实际中,通常会采用spring的注解方式,扫描创建和自动注入bean,不需要自己写BeanUtil.java,配置文件也不用一个个添加bean,整个过程就像直接new一样优雅。