一、使用Spring框架进行依赖注入的好处
任何有实际意义的应用都会由两个或更多的类组成,这些类之间要相互协作来完成特定的业务逻辑。
传统做法
例子来自于《Sprng实战》,按照传统做法,每个对象负责管理与自己相互协作的对象(即它所依赖的对象)的引用,这将会导致高度耦合和难以测试的代码。
骑士接口
package com.aidata.old; public interface Knight { public void embarkOnQuest(); }
探险任务接口
package com.aidata.old; public class RescueDamselQuest implements Quest{ public void embark() { System.out.println("Embarking on quest to save the damsel."); } }
我们想要有一个承担拯救少女任务的骑士,创建拯救少女的骑士类
package com.aidata.old; public class DamselRescuingKnight implements Knight{ private RescueDamselQuest quest; public DamselRescuingKnight(){ this.quest = new RescueDamselQuest(); } public void embarkOnQuest() { quest.embark(); } }
你会发现,在 DamselRescuingKnight 的构造函数中,创建了 RescueDamselQuest 对象,因此 DamselRescuingKnight 和 RescueDamselQuest 是紧紧耦合在一起的。
这种耦合会有什么问题呢?
首先,不方便甚至没法测试。如果想要测试 DamselRescuingKnight,必须保证 RescueDamselQuest 的对象 是可以创建的,且其中的embark方法是可以被调用的。如果根本就还没有编写 RescueDamselQuest,只有Quest接口(像上面一样),那根本无法测试。我们本来只想测试 DamselRescuingKnight,却因为 RescueDamselQuest 无法完成该测试。
其次,没法复用。就算 DamselRescuingKnight 类都完备了,现在突然多出了“刺杀恶龙”的任务,这个只会拯救少女的骑士一点办法都没有,我们只能创建一个新的骑士。
最后,两个类纠缠在一起,增加了理解难道,这里只有两个类,如果有十几个类,依赖关系会错综复杂,出了错很难排查。
但是,我们又离不开耦合。两个类想要协调运作,总会产生关联,或多或少会有耦合。于是就产生了依赖注入的想法。
依赖注入的作用
我们不再在一个类中创建另一个类的对象了,而是将对象作为参数传入另一个对象中去,即注入进去。
依赖注入使类之间的关系由
变为
耦合度大大降低
此时,创建一个 BraveKnight 类
package com.aidata.spring; import com.aidata.old.Knight; import com.aidata.old.Quest; public class BraveKnight implements Knight { private Quest quest; public BraveKnight(Quest quest){ this.quest = quest; } public void embarkOnQuest() { quest.embark(); } }
发现,不再使用 RescueDamselQuest ,而是使用接口 Quest,然后构造函数接受实现了 Quest 接口的对象作为参数。不管你是想拯救少女还是击杀恶龙,只要实现了 Quest 接口,这个勇敢的骑士都能胜任了。
而且,尽管还是只有 Quest 接口,我们还可以对其进行测试了,使用mock实现。
pom.xml 中添加依赖
<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>2.7.12</version> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/junit/junit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency>
编写 BraveKnight 类
package com.aidata.spring; import com.aidata.old.Knight; import com.aidata.old.Quest; public class BraveKnight implements Knight { private Quest quest; public BraveKnight(Quest quest){ this.quest = quest; } public void embarkOnQuest() { quest.embark(); } }
选中该类,按下ctral+shift+T(使用的是IDEA),建立测试类。下图是已经建立了测试类的结果。
package com.aidata.spring; import com.aidata.old.Quest; import org.junit.Test; import static org.junit.Assert.*; import static org.mockito.Mockito.mock; public class BraveKnightTest { @Test public void embarkOnQuest() { Quest mockQuest = mock(Quest.class); BraveKnight knight = new BraveKnight(mockQuest); knight.embarkOnQuest(); } }
运行,可以进行测试。
从上面可知依赖注入和面向接口,可以实现松耦合。
Spring做法
上面的例子对比了传统做法和依赖注入做法,但我们只是按适合依赖注入的形式写了两个类,并没有实现依赖注入。
Spring框架可以帮我们实现依赖注入。Spring 容器创建应用程序中的bean并通过DI协调这些对象之间的关系。
什么叫Spring中的bean?Spring 中配置的类的对象,它们用来完成你想做的工作。
In Spring, the objects that form the backbone of your application and that are managed by the Spring IoC container are called beans. A bean is an object that is instantiated, assembled, and otherwise managed by a Spring IoC container.
现在 BraveKnight 可以接受任何的 Quest 实现。下面实现一个击杀恶龙的 Quest。
package com.aidata.spring; import com.aidata.old.Quest; import java.io.PrintStream; public class SlayDragonQuest implements Quest { private PrintStream stream; public SlayDragonQuest(PrintStream stream){ this.stream = stream; } public void embark() { stream.println("Embarking on quest to slay the dragon!"); } }
于是,我们的问题是:如何将 SlayDragonQuest 交给 BraveKnight ,如何将 PrintStream 交给 SlayDragonQuest。
也就是这几个类之间如何协作?Spring叫组件,组件之间如何协作呢。我们不用管,只需要让Spring知道它们的存在和它们的关系,具体的事情交给Spring来做就好了。
创建应用组件之间协作的行为通常称为装配(wiring)。
通过依赖注入(DI),对象的依赖关系将由系统中负责协调个对象的第三方组件在创建对象的时候进行设定。对象无需自行创建或管理它们的依赖关系,依赖关系将自动注入到需要它们的对象当中去。
下面使用Spring进行bean的配置,将SlayDragonQuest、BraveKnight和PrintStream联系起来,采用XML配置方式
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="knight" class="com.aidata.spring.BraveKnight"> <constructor-arg ref="quest"/> </bean> <bean id="quest" class="com.aidata.spring.SlayDragonQuest"> <constructor-arg value="#{T(System).out}"/> </bean> </beans>
关系建立起来之后,我们就可以获取并使用对象了
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; class Main{ public static void main(String[] args) { //1.创建IOC容器对象 ApplicationContext ctx = new ClassPathXmlApplicationContext("knights.xml"); //2.从IOC容器中获取Bean实例 Knight knight = (BraveKnight)ctx.getBean("knight"); //3.调用方法 knight.embarkOnQuest(); } }
运行结果
我们要告诉Spring创建哪些bean并且如何将其装配在一起。Spring提供了三种装配方式:
显示
- XML配置
- Java配置
隐式
- 自动装配
二 、基于XML文件配置
最早,XML是配置Spring的唯一方案。现在,却成了不推荐的方式。
创建XML配置文件
IDEA下新建Spring配置文件
声明bean
声明bean只有一种方式,使用<bean>元素并指定class属性。Spring会从这里获取必要的信息来创建bean。
<bean id="helloworld" class="com.aidata.spring.HelloWorld"> <property name="name" value="spring"></property> </bean>
如果没有给定id,这个bean将会根据全限定类名来进行命名。上面如果没有 id="helloworld" ,则bean的名称默认为 com.aidata.spring.HelloWorld#0 ,“#0”是一个计数的形式,用来区分相同类型的其他bean。
声明依赖注入(DI)
声明DI时,有多种方式:
- 构造器注入 constructor-arg
- 属性设置,通过setter对应的方法注入 property
构造器注入
通过构造方法注入Bean的属性值或依赖的对象,它保证了Bean实例在实例化后就可以使用。
有两种基本的配置方式:
- <constructor-arg>元素
- c-命名空间
字面值注入到构造器中
字面值:可用字符串表示的值,可用通过<value>元素标签或value属性进行注入。
基本数据类型及其封装类、String等类型都可以采取字面值注入的方式,若字面值中包含特殊字符,可以使用 <![CDATA[]]> 把字面值包裹起来。
<constructor-arg>元素
小车类
package com.aidata.spring.constructor; public class Car { private String band; private String place; private double price; public Car(String band, String place, double price) { super(); this.band = band; this.place = place; this.price = price; } @Override public String toString() { return "Car{" + "band='" + band + '\'' + ", place='" + place + '\'' + ", price=" + price + '}'; } }
配置
按照顺序指定参数,字符串可以自动转为数值型
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 通过构造方法配置bean --> <bean id="car" class="com.aidata.spring.constructor.Car"> <constructor-arg value="Audi" ></constructor-arg> <constructor-arg value="Shanghai" ></constructor-arg> <constructor-arg value="1000" ></constructor-arg> </bean> </beans>
可以指定index:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 通过构造方法配置bean --> <bean id="car" class="com.aidata.spring.constructor.Car"> <constructor-arg value="Audi" index="0"></constructor-arg> <constructor-arg value="Shanghai" index="1"></constructor-arg> <constructor-arg value="1000" index="2"></constructor-arg> </bean> </beans>
运行
package com.aidata.spring.constructor; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Main { public static void main(String[] args) { //1.创建IOC容器对象 ApplicationContext ctx1 = new ClassPathXmlApplicationContext("car.xml"); //2.从IOC容器中获取Bean实例 Car car = (Car)ctx1.getBean("car"); //3.调用方法 System.out.println(car); } }
结果
多个构造器
package com.aidata.spring.constructor; public class Car { private String band; private String place; private double price; private int maxSpeed; public Car(String band, String place, double price) { super(); this.band = band; this.place = place; this.price = price; } public Car(String band, String place, int maxSpeed) { super(); this.band = band; this.place = place; this.maxSpeed = maxSpeed; } @Override public String toString() { return "Car{" + "band='" + band + '\'' + ", place='" + place + '\'' + ", price=" + price + ", maxSpeed=" + maxSpeed + '}'; } }
两个构造器都有三个参数,没法只靠位置进行区分,此时使用参数类型
配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 通过构造方法配置bean --> <bean id="car1" class="com.aidata.spring.constructor.Car"> <constructor-arg value="Audi" ></constructor-arg> <constructor-arg value="Shanghai" ></constructor-arg> <constructor-arg value="1000"></constructor-arg> </bean> <bean id="car2" class="com.aidata.spring.constructor.Car"> <constructor-arg value="Audi" type="java.lang.String"></constructor-arg> <constructor-arg value="Shanghai" index="1"></constructor-arg> <constructor-arg value="240" type="int"/> </bean> </beans>
index和type可以混用
运行
package com.aidata.spring.constructor; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Main { public static void main(String[] args) { //1.创建IOC容器对象 ApplicationContext ctx1 = new ClassPathXmlApplicationContext("car.xml"); //2.从IOC容器中获取Bean实例 Car car1 = (Car)ctx1.getBean("car1"); //3.调用方法 System.out.println(car1); //2.从IOC容器中获取Bean实例 Car car2 = (Car)ctx1.getBean("car2"); //3.调用方法 System.out.println(car2); } }
结果
会发现,如果没有指定类型,会选用第一个构造函数,根据其参数的顺序传入,如car1.
还可以使用<value>标签,特殊字符
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 通过构造方法配置bean --> <bean id="car1" class="com.aidata.spring.constructor.Car"> <constructor-arg value="Audi" ></constructor-arg> <constructor-arg value="Shanghai" ></constructor-arg> <constructor-arg value="1000"></constructor-arg> </bean> <bean id="car2" class="com.aidata.spring.constructor.Car"> <constructor-arg value="Audi" type="java.lang.String"></constructor-arg> <constructor-arg index="1"><value><![CDATA[<ShangHai^>]]></value></constructor-arg> <constructor-arg type="int"><value>240</value></constructor-arg> </bean> </beans>
运行结果
c-命名空间
配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 通过构造方法配置bean --> <bean id="car1" class="com.aidata.spring.constructor.Car"> <constructor-arg value="Audi" ></constructor-arg> <constructor-arg value="Shanghai" ></constructor-arg> <constructor-arg value="1000"></constructor-arg> </bean> <bean id="car2" class="com.aidata.spring.constructor.Car"> <constructor-arg value="Audi" type="java.lang.String"></constructor-arg> <constructor-arg index="1"><value><![CDATA[<ShangHai^>]]></value></constructor-arg> <constructor-arg type="int"><value>240</value></constructor-arg> </bean> <bean id="car3" class="com.aidata.spring.constructor.Car" c:band="Benz" c:_1="Shanghai" c:maxSpeed="600" /> </beans>
运行
package com.aidata.spring.constructor; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Main { public static void main(String[] args) { //1.创建IOC容器对象 ApplicationContext ctx1 = new ClassPathXmlApplicationContext("car.xml"); //2.从IOC容器中获取Bean实例 Car car1 = (Car)ctx1.getBean("car1"); //3.调用方法 System.out.println(car1); //2.从IOC容器中获取Bean实例 Car car2 = (Car)ctx1.getBean("car2"); //3.调用方法 System.out.println(car2); //2.从IOC容器中获取Bean实例 Car car3 = (Car)ctx1.getBean("car3"); //3.调用方法 System.out.println(car3); } }
结果
将其他Bean注入到构造器中
<constructor-arg>元素
新增司机类
package com.aidata.spring.constructor; public class Driver { private String name; private int age; private Car car; public Driver(String name, int age, Car car){ this.name = name; this.age = age; this.car = car; } @Override public String toString() { return "Driver{" + "name='" + name + '\'' + ", age=" + age + ", car=" + car + '}'; } }
配置
使用 ref属性
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 通过构造方法配置bean --> <bean id="car1" class="com.aidata.spring.constructor.Car"> <constructor-arg value="Audi" ></constructor-arg> <constructor-arg value="Shanghai" ></constructor-arg> <constructor-arg value="1000"></constructor-arg> </bean> <bean id="car2" class="com.aidata.spring.constructor.Car"> <constructor-arg value="Audi" type="java.lang.String"></constructor-arg> <constructor-arg index="1"><value><![CDATA[<ShangHai^>]]></value></constructor-arg> <constructor-arg type="int"><value>240</value></constructor-arg> </bean> <bean id="car3" class="com.aidata.spring.constructor.Car" c:band="Benz" c:_1="Shanghai" c:maxSpeed="600" /> <bean id="driver" class="com.aidata.spring.constructor.Driver"> <constructor-arg index="0" value="Wang"/> <constructor-arg index="1" value="18"/> <constructor-arg index="2" ref="car3"/> </bean> </beans>
也可以使用 ref元素
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 通过构造方法配置bean --> <bean id="car1" class="com.aidata.spring.constructor.Car"> <constructor-arg value="Audi" ></constructor-arg> <constructor-arg value="Shanghai" ></constructor-arg> <constructor-arg value="1000"></constructor-arg> </bean> <bean id="car2" class="com.aidata.spring.constructor.Car"> <constructor-arg value="Audi" type="java.lang.String"></constructor-arg> <constructor-arg index="1"><value><![CDATA[<ShangHai^>]]></value></constructor-arg> <constructor-arg type="int"><value>240</value></constructor-arg> </bean> <bean id="car3" class="com.aidata.spring.constructor.Car" c:band="Benz" c:_1="Shanghai" c:maxSpeed="600" /> <bean id="driver" class="com.aidata.spring.constructor.Driver"> <constructor-arg index="0" value="Wang"/> <constructor-arg index="1" value="18"/> <constructor-arg index="2"><ref bean="car3"/></constructor-arg> </bean> </beans>
运行
package com.aidata.spring.constructor; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Main { public static void main(String[] args) { //1.创建IOC容器对象 ApplicationContext ctx1 = new ClassPathXmlApplicationContext("car.xml"); //2.从IOC容器中获取Bean实例 Car car1 = (Car)ctx1.getBean("car1"); //3.调用方法 System.out.println(car1); //2.从IOC容器中获取Bean实例 Car car2 = (Car)ctx1.getBean("car2"); //3.调用方法 System.out.println(car2); //2.从IOC容器中获取Bean实例 Car car3 = (Car)ctx1.getBean("car3"); //3.调用方法 System.out.println(car3); //2.从IOC容器中获取Bean实例 Driver driver = (Driver)ctx1.getBean("driver"); //3.调用方法 System.out.println(driver); } }
结果
c-命名空间
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 通过构造方法配置bean --> <bean id="car1" class="com.aidata.spring.constructor.Car"> <constructor-arg value="Audi" ></constructor-arg> <constructor-arg value="Shanghai" ></constructor-arg> <constructor-arg value="1000"></constructor-arg> </bean> <bean id="car2" class="com.aidata.spring.constructor.Car"> <constructor-arg value="Audi" type="java.lang.String"></constructor-arg> <constructor-arg index="1"><value><![CDATA[<ShangHai^>]]></value></constructor-arg> <constructor-arg type="int"><value>240</value></constructor-arg> </bean> <bean id="car3" class="com.aidata.spring.constructor.Car" c:band="Benz" c:_1="Shanghai" c:maxSpeed="600" /> <bean id="driver" class="com.aidata.spring.constructor.Driver" c:name="Wang" c:age="20" c:car-ref="car3"/> </beans>
c:参数名-ref="注入的bean的id"
装配集合
在Spring中通过内置XML标签配置集合属性:
<list>:配置java.util.List类型的属性,该标签可以使用<value>指定简单的常量值,使用<ref>指定其他Bean的引用,通过<null/>指定空元素,也可以内嵌其他集合。数组也使用该标签。
<set>:配置java.util.Set需要使用<set>标签,定义元素的方法与List一样。
<map>:配置java.util.Map类型的属性,该标签里可以使用多个<entry>作为子标签,每个条目包含一个键和一个值。必须在<key>标签里定义键,键和值可以自由指定<value>、<ref>、<bean>、<null>元素。键和值也可以作为<entry>属性定义。
<props>:定义java.util.Properties,该标签使用多个<prop>作为子标签,每个子标签必须有key属性。
举例
<!-- 测试如何配置集合属性 --> <bean id="person3" class="ja.sp1.collections.Person"> <property name="name" value="Mike"> </property> <property name="age" value="24"></property> <property name="cars" > <list> <ref bean="car"/> <ref bean="car2"/> </list> </property> </bean> <bean id="newperson" class="ja.sp1.collections.NewPerson"> <property name="name" value="Mike"> </property> <property name="age" value="24"></property> <property name="cars" > <map> <entry key="AA" value-ref="car"></entry> <entry key="BB" value-ref="car2"></entry> </map> </property> </bean> <!-- 配置DataSource属性值 --> <bean id="dataSource" class="ja.sp1.collections.DataSource"> <property name="properties"> <!--使用props和prop子节点来为Properties属性赋值--> <props> <prop key="usrer">root</prop> <prop key="pass">123</prop> <prop key="jdbcUrl">jdbc</prop> </props> </property> </bean>
属性注入
属性注入即通过setter方法注入Bean的属性值或依赖的对象,属性注入使用<property>元素,使用name属性指定Bean的属性名称,value属性或<value>子节点指定属性值。还可以使用p命名空间,属性注入是实际应用中最常用的注入方式。
字面值注入到属性中、其他Bean注入到属性中
司机类去掉构造函数,添加setter方法
package com.aidata.spring.property; import java.util.List; public class Driver { private String name; private int age; private Car car1; private Car car2; private List<Integer> days; public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } public void setCar1(Car car1) { this.car1 = car1; } public void setCar2(Car car2) { this.car2 = car2; } public void setDays(List<Integer> days) { this.days = days; } @Override public String toString() { return "Driver{" + "name='" + name + '\'' + ", age=" + age + ", car1=" + car1 + ", car2=" + car2 + ", days=" + days + '}'; } }
<property>元素
value属性或<value>子元素
装配集合
p-命名空间
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.1.xsd"> <!-- 通过构造方法配置bean --> <bean id="car1" class="com.aidata.spring.property.Car"> <constructor-arg value="Audi" ></constructor-arg> <constructor-arg value="Shanghai" ></constructor-arg> <constructor-arg value="1000"></constructor-arg> </bean> <bean id="car2" class="com.aidata.spring.property.Car"> <constructor-arg value="Audi" type="java.lang.String"></constructor-arg> <constructor-arg index="1"><value><![CDATA[<ShangHai^>]]></value></constructor-arg> <constructor-arg type="int"><value>240</value></constructor-arg> </bean> <bean id="driver1" class="com.aidata.spring.property.Driver"> <property name="name" value="Wang"></property> <property name="age"><value>20</value></property> <property name="car1" ref="car1"></property> <property name="car2"><ref bean="car2"></ref></property> <property name="days"> <list> <value>1</value> <value>2</value> <value>3</value> </list> </property> </bean> <bean id="driver2" class="com.aidata.spring.property.Driver" p:name="Ma" p:age="38" p:car1-ref="car1" p:car2-ref="car2"> <property name="days"> <list> <value>1</value> <value>2</value> <value>3</value> </list> </property> </bean> <!-- 不能使用p-命名空间来装配集合,可以使用util-命名空间创建集合类型的bean --> <util:list id="days"> <value>1</value> <value>2</value> <value>3</value> </util:list> <bean id="driver3" class="com.aidata.spring.property.Driver" p:name="Ma" p:age="38" p:car1-ref="car1" p:car2-ref="car2" p:days-ref="days"/> </beans>
运行
package com.aidata.spring.property; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; class Main{ public static void main(String[] args) { //1.创建IOC容器对象 ApplicationContext ctx1 = new ClassPathXmlApplicationContext("car.xml"); //2.从IOC容器中获取Bean实例 Driver driver1 = (Driver) ctx1.getBean("driver1"); Driver driver2 = (Driver) ctx1.getBean("driver2"); Driver driver3 = (Driver) ctx1.getBean("driver3"); System.out.println(driver1); System.out.println(driver2); System.out.println(driver3); } }
结果
"E:\Program Files\Java\jdk1.8.0_191\bin\java.exe" "-javaagent:G:\IntelliJ IDEA 2019.2\lib\idea_rt.jar=3915:G:\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath "E:\Program Files\Java\jdk1.8.0_191\jre\lib\charsets.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\deploy.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\access-bridge-64.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\cldrdata.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\dnsns.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\jaccess.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\jfxrt.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\localedata.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\nashorn.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\sunec.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\sunjce_provider.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\sunmscapi.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\sunpkcs11.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\zipfs.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\javaws.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\jce.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\jfr.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\jfxswt.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\jsse.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\management-agent.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\plugin.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\resources.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\rt.jar;C:\Users\JieZhao\Downloads\apache-flume-1.9.0-src\springlearning\target\classes;C:\Users\JieZhao\.m2\repository\org\springframework\spring-context\5.2.2.RELEASE\spring-context-5.2.2.RELEASE.jar;C:\Users\JieZhao\.m2\repository\org\springframework\spring-aop\5.2.2.RELEASE\spring-aop-5.2.2.RELEASE.jar;C:\Users\JieZhao\.m2\repository\org\springframework\spring-beans\5.2.2.RELEASE\spring-beans-5.2.2.RELEASE.jar;C:\Users\JieZhao\.m2\repository\org\springframework\spring-core\5.2.2.RELEASE\spring-core-5.2.2.RELEASE.jar;C:\Users\JieZhao\.m2\repository\org\springframework\spring-jcl\5.2.2.RELEASE\spring-jcl-5.2.2.RELEASE.jar;C:\Users\JieZhao\.m2\repository\org\springframework\spring-expression\5.2.2.RELEASE\spring-expression-5.2.2.RELEASE.jar;C:\Users\JieZhao\.m2\repository\commons-logging\commons-logging\1.2\commons-logging-1.2.jar" com.aidata.spring.property.Main Driver{name='Wang', age=20, car1=Car{band='Audi', place='Shanghai', price=1000.0, maxSpeed=0}, car2=Car{band='Audi', place='<ShangHai^>', price=0.0, maxSpeed=240}, days=[1, 2, 3]} Driver{name='Ma', age=38, car1=Car{band='Audi', place='Shanghai', price=1000.0, maxSpeed=0}, car2=Car{band='Audi', place='<ShangHai^>', price=0.0, maxSpeed=240}, days=[1, 2, 3]} Driver{name='Ma', age=38, car1=Car{band='Audi', place='Shanghai', price=1000.0, maxSpeed=0}, car2=Car{band='Audi', place='<ShangHai^>', price=0.0, maxSpeed=240}, days=[1, 2, 3]} Process finished with exit code 0
null值和级联属性
可以使用专用的<null/>元素标签为Bean的字符串或其对象类型的属性注入null值,Spring还支持级联属性的配置。
<bean id="person2" class="ja.sp1.Person"> <constructor-arg value="Jenny" ></constructor-arg> <constructor-arg value="24" ></constructor-arg> <!--<constructor-arg ref="car2" ></constructor-arg>--> <constructor-arg><null/></constructor-arg> <!--为级联属性赋值,注意属性需要先初始化后才可以为级联属性赋值,否则会异常--> <property name="car.price" value="2000"></property> </bean>
三种获得Bean的方式
上面我们将的方式是最常用的方式即通过全类名的方式,还有其他方式 工厂方法和FactoryBean,在用到第三方框架的时候会接触到。
通过全类名(反射)
一个小车类
package com.aidata.spring; public class Car { private String brand; private double price; public Car(String brand, double price) { super(); this.brand = brand; this.price = price; } public String getBrand() { return brand; } public void setBrand(String brand) { this.brand = brand; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } @Override public String toString() { return "Car [brand=" + brand + ", price=" + price + "]"; } }
配置
构造器注入
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="car" class="com.aidata.spring.Car"> <constructor-arg index="0" value="Benz"/> <constructor-arg index="1" value="1000"/> </bean> </beans>
使用
package com.aidata.spring; import com.aidata.old.Knight; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; class Main{ public static void main(String[] args) { //1.创建IOC容器对象 ApplicationContext ctx1 = new ClassPathXmlApplicationContext("applicationContext.xml"); //2.从IOC容器中获取Bean实例 Car car = (Car)ctx1.getBean("car"); //3.调用方法 System.out.println(car); } }
通过工厂方法(静态工厂方法 & 实例工厂方法)
静态工厂
调用静态工厂方法创建Bean是将对象的创建过程封装到静态方法中。当客户端需要对象时,只需要简单地调用静态方法,而不用关心创建对象的细节。
package com.aidata.springfactory; import java.util.HashMap; import java.util.Map; public class StaticCarFactory { private static Map<String, Car> cars = new HashMap<String, Car>(); //在静态代码块里进行初始化 static { cars.put("audi", new Car("audi", 30000)); cars.put("for", new Car("audi", 20000)); } //静态工厂方法 //在配置文件中配置car实例 public static Car getCar(String name) { return cars.get(name); } }
实例工厂
将对象的创建过程封装到另外一个对象实例的方法里。当客户端需要请求对象时,只需简单调用该实例方法而不需要关心对象的创建细节。
实例工厂方法:实例的工厂方法,即需要创建工厂本身,再调用工厂的实例方法来返回bean的实例。
package com.aidata.springfactory; import java.util.HashMap; import java.util.Map; public class InstanceCarFactory { private Map<String, Car> cars = null; public InstanceCarFactory() { cars = new HashMap<String, Car>(); cars.put("audi", new Car("audi", 500000)); cars.put("ford", new Car("ford", 100000)); } public Car getCar(String name) { return cars.get(name); } }
配置
静态工厂
class属性:指向静态工厂方法的全类名
factory-method:指向静态工厂方法的名字
<constructor-arg>:如果工厂方法需要传入参数,使用该标签来配置参数
实例工厂
factory-bean属性:指向实例工厂方法的bean
factory-method:指向工厂方法的名字
<constructor-arg>:如果工厂方法需要传入参数,使用该标签来配置参数
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 通过静态工厂方法配置bean,注意 不是配置静态工厂方法实例,而是bean实例--> <bean id="car1" class="com.aidata.springfactory.StaticCarFactory" factory-method="getCar"> <constructor-arg value="audi" ></constructor-arg> </bean> <!-- 配置工厂的实例 --> <bean id="carFactory" class="com.aidata.springfactory.InstanceCarFactory"></bean> <!-- 通过实例工厂方法来配置bean --> <bean id="car2" factory-bean="carFactory" factory-method="getCar"> <constructor-arg value="audi" ></constructor-arg> </bean> </beans>
使用
package com.aidata.springfactory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Main { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("fac.xml"); Car car1 = (Car) context.getBean("car1"); System.out.println(car1); Car car2 = (Car) context.getBean("car2"); System.out.println(car2); } }
FactoryBean
Factory Bean是Spring提供的Bean
三个方法:
- getObject 返回bean本身
- getObjectType 返回bean实例
- isSingleton 是不是单例
有时会用到IOC容器中的其他bean
实现 FactoryBean 接口
package com.aidata.springfactory; import org.springframework.beans.factory.FactoryBean; //自定义FactoryBean需要实现spring提供的FactoryBean接口 public class CarFactoryBean implements FactoryBean<Car> { private String brand; public void setBrand(String brand) { this.brand = brand; } public Car getObject() throws Exception { return new Car("BMW", 20000); } /** * 返回bean的类型 */ public Class<?> getObjectType() { // TODO Auto-generated method stub return null; } public boolean isSingleton() { // TODO Auto-generated method stub return false; } }
配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 通过FactoryBean来配置Bean的实例, class:指向Factrory Bean的全类名 property:配置FactoryBean的属性 但实际返回的实例却是FactoryBean的getObject()方法返回的实例 --> <bean id="car" class="com.aidata.springfactory.CarFactoryBean"> <property name="brand" value="BMW"></property> </bean> </beans>
使用
package com.aidata.springfactory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Main { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("fac.xml"); Car car = (Car) context.getBean("car"); System.out.println(car); } }
Spring表达式语言:SpEL
是一个支持运行时查询和操作对象图的强大的表达式语言。使用 #{...} 作为定界符,所有在大括号中的字符都将被认为是SpEL,其为bean属性的动态赋值提供便利。
通过SpEL可以实现:
- 通过bean的id对bean进行引用
- 调用方法以及引用对象中的属性
- 计算表达式的值
- 正则表达式的匹配
字面量
整数:<property name="count" value="#{5}"/> 小数:<property name="frequency" value="#{89.7}"/> 科学计数法:<property name="capacity" value="#{1e4}"/> String可以使用单引号或者双引号作为字符串的定界符号:<property name=“name” value="#{'Chuck'}"/> 或 <property name='name' value='#{"Chuck"}'/> Boolean:<property name="enabled" value="#{false}"/>
引用Bean、属性和方法
bean不必使用ref属性,使用value属性:
<property name="car1" value="#{car1}"></property>
引用其他对象的属性
<property name="name" value="#{car2.band}"></property>
上面的属性band必须是public的,否则娶不到
链式操作
<property name="name" value="#{car2.toString().toUpperCase()}"></property>
名字全成了大写
调用静态方法或静态属性:通过T()调用一个类的静态方法,它将返回一个Class Object,然后再调用相应的方法或属性:
<bean id="car" class="ja.sp1.spel.Car"> <property name="brand" value="AUDI"></property> <property name="price" value="500000"></property> <!-- 使用SpEL引用类的静态属性 --> <property name="tyrePerimeter" value="#{T(java.lang.Math).PI*80}"></property> </bean>
支持的运算符号
if-else运算符
<!-- 在SpEL中使用运算符 --> <property name="info" value="#{car.price > 30?'金领':'白领'}"></property>
if-else变体
正则表达式:matches
继承Bean配置
继承的Bean称为父Bean,继承这个父Bean的Bean是子Bean。子从父中继承配置,包括Bean的属性配置。子可以覆盖从父继承过来的配置。父可以作为配置模板,也可以作为Bean实例。若只想把父作为模板,可以设置<bean>的abstract属性为true,将不会实例化该Bean,抽象Bean。
并不是<bean>元素里所有属性都会被继承,如:autowire,abstract等。
也可以忽略父的class属性,让子指定自己的类,而共享相同的属性配置,此时abstract必须为true。
两个address有很多重复的地方:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="address" class="ja.sp1.autowire.Address" p:city="Beijing" p:street="Wudaokou"></bean> <bean id="address2" p:street="Qianmen" parent="address"></bean> </beans>
可以进行继承,用parent
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="address" class="ja.sp1.autowire.Address" p:city="Beijing" p:street="Wudaokou"></bean> <bean id="address2" p:street="Qianmen" parent="address"></bean> </beans>
抽象的Bean,只能继承,不能被实例化
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="address" class="ja.sp1.autowire.Address" p:city="Beijing" p:street="Wudaokou" abstract="true"></bean> <bean id="address2" p:city="Beijing" parent="address"></bean> </beans>
依赖Bean配置
通过depends-on属性设定Bean前置依赖的Bean,前置依赖的Bean会在本Bean实例化之前创建好。如果前置依赖多个Bean,则可以通过逗号、空格等方式配置Bean名称。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="address" class="ja.sp1.autowire.Address" p:city="Beijing" p:street="Wudaokou"></bean>
<bean id="address2" p:city="Beijing" parent="address"></bean> <bean id="car" class="ja.sp1.autowire.Car" p:brand="Audi" p:price="100000"></bean>
<bean id="person" class="ja.sp1.autowire.Person" p:name="Mamamiya" p:address-ref="address2" depends-on="car"></bean> </beans>
Bean的作用域
在<bean>元素的scope属性里设置Bean的作用域。默认情况下,Spring只为每个在IOC容器里声明的Bean创建唯一一个实例,整个IOC容器范围内共享该实例,即所有后续的getBean()调用和Bean引用都将返回这个唯一的Bean实例。该作用域被称为singleton,它是所有Bean的默认作用域。
默认是单例的,容器初始时创建bean实例,即在ApplicationContext ctx = new ClassPathXmlApplicationContext("beans-scope.xml");时,bean已经创建,在整个容器的声明周期内只创建这一个bean。
不配置bean的scope属性
public class Main { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("beans-scope.xml"); Car car = (Car)ctx.getBean("car"); Car car2 = (Car)ctx.getBean("car"); System.out.println(car==car2); }
打印结果为true
改变scope属性
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="car" class="ja.sp1.autowire.Car" scope="prototype"> <property name="brand" value="Audi"></property> <property name="price" value="300000"></property> </bean> </beans>
再次运行上面代码,打印false,容器初始化时不创建Bean实例,而是每次请求时创建一个新的Bean实例。
使用外部属性文件
再配置文件里配置Bean时,有时需要在Bean的配置里混入系统部属的细节,这些部属细节需要和Bean配置相分离。
Spring提供了一个PropertyPlaceholderConfigure的BeanFactory后置处理器,这个处理器运行用户将Bean配置的部分内容外移到属性文件中。可以在Bean配置文件里使用形式为 ${var} 的变量,PropertyPlaceholderConfigure从属性文件里加载属性,并使用这些属性来替换变量。
Spring还允许在属性文件中使用${propName},以实现属性之间的相互引用。
举例
不使用外部文件,mysql配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="root"></property> <property name="password" value="root"></property> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql:///test"></property> </bean> </beans>
使用外部文件 db.properties
user=root
password=root
driverClass=com.mysql.jdbc.Driver
jdbcUrl=jdbc:mysql:///test
通过<context:property-placeholder>元素
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <!-- 导入属性文件 --> <context:property-placeholder location="classpath:db.properties"/> <!--使用外部属性--> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="${user}"></property> <property name="password" value="${password}"></property> <property name="driverClass" value="${driverClass}"></property> <property name="jdbcUrl" value="${jdbcUrl}"></property> </bean> </beans>