(spring-第3回【IoC基础篇】)spring的依赖注入-属性、构造函数、工厂方法等的注入(基于XML)
Spring要把xml配置中bean的属性实例化为具体的bean,"依赖注入"是关卡。所谓的"依赖注入",就是把应用程序对bean的属性依赖都注入到spring容器中,由spring容器实例化bean然后交给程序员。spring的依赖注入有属性注入、构造函数注入、工厂方法注入等多种方式,下面用几个简单的栗子来一一道来。
一、首先是属性注入:
代码001
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://www.springframework.org/schema/beans 5 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> 6 <bean id="car" class="com.mesopotamia.AttrInject.Car" lazy-init="default"> 7 <property name="brand"> 8 <value>红旗CA72</value> 9 </property> 10 </bean> 11 </beans>
代码001表示配置了汽车的brand属性。对应的bean:
代码002
1 package com.mesopotamia.AttrInject; 2 3 import org.apache.commons.logging.Log; 4 import org.apache.commons.logging.LogFactory; 5 6 public class Car { 7 public String brand; 8 private Log log=LogFactory.getLog(Car.class); 9 10 public Car(){ 11 log.info("加载Car构造函数。。"); 12 13 } 14 public String getBrand() { 15 return brand; 16 } 17 public void setBrand(String brand) { 18 this.brand = brand; 19 } 20 21 }
main:
1 //代码003 2 3 public class Main { 4 private static Log log=LogFactory.getLog(Main.class); 5 public static void main(String args[]){ 6 ApplicationContext ctx = new ClassPathXmlApplicationContext("com/mesopotamia/AttrInject/*.xml"); 7 Car car=ctx.getBean("car",Car.class); 8 log.info("初始化Car,brand="+car.brand); 9 } 10 }
执行结果:
1 <!--代码004--> 2 2015-11-09 19:16:01,269 INFO [main] (AbstractApplicationContext.java:456) - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@1ff5ea7: startup date [Mon Nov 09 19:16:01 CST 2015]; root of context hierarchy 3 2015-11-09 19:16:01,336 INFO [main] (XmlBeanDefinitionReader.java:315) - Loading XML bean definitions from file [C:\MySoftware\workspace\SpringTest\WebRoot\WEB-INF\classes\com\mesopotamia\AttrInject\beans.xml] 4 2015-11-09 19:16:01,471 INFO [main] (DefaultListableBeanFactory.java:555) - Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@d19bc8: defining beans [car]; root of factory hierarchy 5 2015-11-09 19:16:01,472 INFO [main] (Car.java:11) - 加载Car构造函数。。 6 2015-11-09 19:16:01,532 INFO [main] (Main.java:14) - 初始化Car,brand=红旗CA72
代码001中的property是属性,多个属性往后接着添加就行。由于我妈喊我回家吃饭了,就捡要紧的讲一下:
- 代码001中,name与value分别对应什么,结合代码002一目了然,我不语。
- name书写有一定的规范,比如你写个cARBrand可能会出问题。前两个字母,要么全部大写,要么全部小写。
- value中不能掺杂xml的特殊符号,如:& < > “ ‘ 如果必须要写这些字符,要用<![CDATA[XXX]]>来转义。
- value里面如果要设置null值,不能什么都不写,代码001的7-9行要变为:<property name="brand"><null/></property>
- 假如bean类是Vehicle,Vehicle类有一个属性是Car,那么你想直接在Vehicle的配置文件中定义Car的brand属性,就酱紫:<property name="car.brand" value="xxx"/>
- 如果是集合类型,配置里面要这样写:
1 <property name="favorites"> 2 <set> 3 <value>抽烟</value> 4 <value>喝酒</value> 5 <value>烫头</value> 6 </set> 7 </property>
下雨天这样的写法跟bean中的set属性这么配,你应该能看懂吧?(自己换成list再测测)
- 如果是哈希马噗属性呢,是HashMap,不好意思。那么你就这么干:
1 <property name="weekends"> 2 <map> 3 <entry > 4 <key> 5 <value>Saturday</value> 6 </key> 7 <value>约妹子</value> 8 </entry> 9 <entry> 10 <key> 11 <value>Sunday</value> 12 </key> 13 <value>发呆</value> 14 </entry> 15 </map> 16 </property>
你应该写过不少java代码了,对于map的entry、key、value之类的东西应该信手拈来了吧?(稍微注释一下吧,就是说java代码中如果bean的一个属性是map类型的,那么这个属性在xml中配置的时候就按上面的格式,entry、key、value是什么你应该清楚了吧?)
- 还有一种类型叫Properties,虾米?它是一种特殊的Map,键值都是String类型。遇到它,又有花样:
1 <property name="mails"> 2 <props> 3 <prop key="jobMail">john-office@baobaotao.com</prop> 4 <prop key="lifeMail">john-life@baobaotao.com</prop> 5 </props> 6 </property>
哎呦,脱了马甲就不认识啦?
二、OK,属性注入大概就酱紫,下面介绍构造函数注入:
在代码002中的第11行,我如果直接打印brand,打印出来必然是null。因为实例化不等于初始化,这个时候,属性都还没有赋值(实例化只是跑了个构造函数,除非你在bean的构造函数中就给属性附了值,否则属性就都没初始化。)。构造函数的注入可以让属性在实例化的过程中就有了值(其实就相当于java在bean的构造函数中给属性赋值,只不过现在是在xml配置中实现)。来,走你:
代码005
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://www.springframework.org/schema/beans 5 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> 6 <bean id="car3" class="com.mesopotamia.AttrInject.Car3"> 7 <constructor-arg type="java.lang.String"> 8 <value>红旗CA72</value> 9 </constructor-arg> 10 <constructor-arg type="double"> 11 <value>20000</value> 12 </constructor-arg> 13 </bean> 14 </beans>
1 //代码006 2 package com.mesopotamia.AttrInject; 3 4 import org.apache.commons.logging.Log; 5 import org.apache.commons.logging.LogFactory; 6 7 public class Car3 { 8 private String brand; 9 private double price; 10 private Log log=LogFactory.getLog(Car3.class); 11 12 public Car3(String brand,double price){ 13 this.brand=brand; 14 this.price=price; 15 log.info("加载Car3构造函数,加载后,brand="+brand+" & price="+price); 16 17 } 18 public String getBrand() { 19 return brand; 20 } 21 public void setBrand(String brand) { 22 this.brand = brand; 23 } 24 public void setPrice(double price) { 25 this.price = price; 26 } 27 public double getPrice() { 28 return price; 29 } 30 31 }
1 //代码007 2 package com.mesopotamia.AttrInject; 3 4 import org.apache.commons.logging.Log; 5 import org.apache.commons.logging.LogFactory; 6 import org.springframework.context.ApplicationContext; 7 import org.springframework.context.support.ClassPathXmlApplicationContext; 8 9 10 public class Main { 11 private static Log log=LogFactory.getLog(Main.class); 12 public static void main(String args[]){ 13 ApplicationContext ctx = new ClassPathXmlApplicationContext("com/mesopotamia/AttrInject/*.xml"); 14 Car3 car=ctx.getBean("car3",Car3.class); 15 log.info("初始化Car,brand="+car.getBrand()); 16 } 17 }
还是老模式,代码005是配置,代码006是bean,代码007跑起来(这三大块基本就是spring运转的三座擎天柱了)。观察代码005的7-12行,对比代码006的构造函数,应该可以对号入座吧?在代码002的第11行如果你打印brand属性值,是空的,null的。但是在代码006的第15行,你会发现它是有值的,这就是构造函数的注入起到的效果。来看打印的结果:
代码008
5 2015-11-09 22:52:18,573 INFO [main] (XmlBeanDefinitionReader.java:315) - Loading XML bean definitions from file [C:\MySoftware\workspace\SpringTest\WebRoot\WEB-INF\classes\com\mesopotamia\AttrInject\beans3.xml] 6 2015-11-09 22:52:18,624 INFO [main] (DefaultListableBeanFactory.java:555) - Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1e8a1f6: defining beans [car,car3]; root of factory hierarchy 7 2015-11-09 22:52:18,705 INFO [main] (Car3.java:14) - 加载Car3构造函数,加载后,brand=红旗CA72 & price=20000.0 8 2015-11-09 22:52:18,708 INFO [main] (Main.java:14) - 初始化Car,brand=红旗CA72
我只把有用的日志抠出来了。注意代码008的第7行的结果。
村民们请注意了,从代码005可以看出,spring是通过入参(形参)的类型来作为区分的(一个是java.lang.String,一个是double),那么,如果某bean的构造函数两个入参的类型相同肿么办?换做你,自然而然的就能想到来个index就完事儿了。木有错,这个时候,给代码005的两个constructor-arg分别加属性index="0",index="1"就好了。在有些情况下,可能要两种方式结合起来使用,双管齐下才能药到病除,自己举个栗子玩玩。
三、下面开始介绍工厂方法的注入:
工厂类负责创建一个或者多个bean实例,调用工厂方法即可获取该实例。就是说,我们想要使用某个Bean类,这个Bean类在工厂中就已经实例化好了,只需要调用工厂把实例拿出来就行了。再换言之,一旦在XML中注册了某工厂,那么调用工厂的过程中已经把该Bean实例化了。算了,说不清,道不明,上干货:
代码009
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://www.springframework.org/schema/beans 5 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> 6 <bean id="car1" factory-bean="carFactory" factory-method="createHongQiCar"/> 7 <bean id="carFactory" class="com.mesopotamia.AttrInject.CarFactory" /> 8 </beans>
看6、7行,聪明的你一定会看出第7行是Factory的Bean配置(也就是说,CarFactory这个java写的工厂类本身也是一个bean ),而第6行的factory-bean指向第7行的id,第6行的factory-method就是Factory要调用的方法。看factory类:
1 package com.mesopotamia.AttrInject; 2 3 public class CarFactory { 4 public Car createHongQiCar(){ 5 Car car = new Car(); 6 car.setBrand("红旗CA72"); 7 return car; 8 } 9 10 }
直接在main中调用createHongQiCar就可以获取car的实例。(当然,在这之前你必须先像代码007第13、14行那样获取CarFactory实例。)
代码009举的是非静态方法的栗子,如果工厂内的方法是静态的,在配置文件中就不需要6-7两行了,直接像下面这样:
1 <bean id="car2" class="com.mesopotamia.AttrInject.CarFactory" 2 factory-method="createCar"></bean>
class直接指定工厂类,然后调用factory-method就可以了。
三、XML配置的简化:
像这样:
1 <bean id="car" class="com.mesopotamia.AttrInject.Car2" lazy-init="default"> 2 <property name="brand"> 3 <value>红旗CA72</value> 4 </property> 5 </bean>
麻烦。简单点:
1 <bean id="car" class="com.mesopotamia.AttrInject.Car2" lazy-init="default"> 2 <property name="brand" value="红旗CA72"/> 3 </bean>
你注意观察,它直接把property的内置标签value变成proterty的属性来用了。(请注意我这样的描述,有助于你深入理解)
忘记说个东西,配置文件中的lazy-init="default"或者="false",意思是,该bean在spring容器启动时就实例化。而lazy-init="true"表示该bean在spring容器启动时不会实例化,而在需要这个bean时才实例化。(皇帝是一开始就让某位大将拥有兵权还是在准备打仗的时候才把兵权交给他)。
再来看个栗子:
1 <property name="jobs"> 2 <map> 3 <entry > 4 <key> 5 <value>AM</value> 6 </key> 7 <value>会见客户</value> 8 </entry> 9 <entry> 10 <key> 11 <value>PM</value> 12 </key> 13 <value>公司内部会议</value> 14 </entry> 15 </map> 16 </property>
麻烦。简单点:
1 <property name="jobs"> 2 <map> 3 <entry key="AM" value="会见客户"/> 4 <entry key="PM" value="公司内部会议"/> 5 </map> 6 </property>
把entry的两个子标签变成了entry的属性。
但是这还不够简单,spring使用p命名空间进一步作了简化。看这个,原始版:
1 <bean id="car" class="com.baobaotao.attr.Car"> 2 <property name="brand" value="吉利CT5" /> 3 <property name="maxSpeed" value="100" /> 4 <property name="price" value="1000.00" /> 5 </bean>
再看P命名空间的:
1 <bean id="car" class="com.baobaotao.ditype.Car" 2 p:brand="红旗CA72" 3 p:maxSpeed="200" 4 p:price="20000.00"/>
这个简化有点多,是遵循p规则的。p+':'+property的每个属性变成了bean的属性,消退bean的子标签。(每一种变化都尽显哲学之美。)
介绍不详尽之处还请读者自己翻书再看。
文中有些细节叙述较为概括,后续章节会有专题介绍,比如实例化的详细过程,比如工厂方法,比如方法注入等,敬请期待。本文旨在让读者大体了解这三种注入在XML中的格式。
靡不有初,鲜克有终。
--《诗经》