1. 概述
Spring是一个Java领域的开源框架,它为简化企业级应用开发而生,可以使用简单的JavaBean实现以前只有EJB才能实现的功能。
1.1. 特性
- 轻量级(非侵入性):基于Spring开发的时候,我们不需要实现Spring提供的任何接口,也不需要继承Spring提供的任何类,然后我们就可以使用Spring给我们提供的功能
- 一站式框架:在IOC的基础上可以整合各种企业级开源框架和优秀的第三方类库
- 组件组合:Spring实现了使用简单的组件组合成一个复杂的应用,可以使用XML和Java注解组合这些组件
1.2. 模块
1.3. 配置文件
一个典型的Spring项目需要创建一个或多个Bean配置文件,Bean配置文件可以放在类路径下,也可以放在其它目录下。
2. Hello World
HelloWorld.java
package org.lin.stu.spring;
public class HelloWorld {
private String name;
public void setName(String name) {
this.name = name;
}
public void say() {
System.out.println("Hello " + name);
}
}
2.1. 传统方式
//创建HelloWorld的一个对象
HelloWorld helloWorld = new HelloWorld();
//为name属性赋值
helloWorld.setName("World");
//调用方法
helloWorld.say();//Hello World
2.2. 使用Spring
<?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">
<!--初始化IOC容器时,Spring会调用HelloWorld.java的无参构造创建名为helloWorld的对象,然后调用setName(String name)方法为name属性赋值-->
<bean id="helloWorld" class="org.lin.stu.spring.HelloWorld">
<!--name属性值为setter风格的属性名-->
<property name="name" value="World"/>
</bean>
</beans>
//创建Spring的IOC容器,ClassPathXmlApplicationContext表示配置以XML文件的形式存放在类路径下
//创建IOC容器后会立即初始化(即创建并初始化配置的Bean)
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//从IOC容器中获取Bean
HelloWorld helloWorld = (HelloWorld) ctx.getBean("helloWorld");
//调用方法
helloWorld.say();//Hello World
3. Bean配置
3.1. 在XML文件中通过bean节点来配置Bean
<!--class属性值为全类名,即Spring通过反射的方式创建Bean-->
<bean id="helloWorld" class="org.lin.stu.spring.HelloWorld">
<property name="name" value="World"/>
</bean>
id(Bean的名称):
- 在IOC容器中必须是唯一的
- 若id没有指定,Spring将自动生成,格式为:全类名#数字(例如:org.lin.stu.spring.HelloWorld#0)
3.2. BeanFactory&ApplicationContext
BeanFactory是IOC容器的基本实现,ApplicationContext提供了更多的高级特性,是BeanFactory的子接口。
BeanFactory是Spring框架的基础设施,面向Spring本身,而ApplicationContext面向使用Spring框架的开发者,几乎所有的应用场合都直接使用ApplicationContext而非底层的BeanFactory。
3.3. ApplicationContext
- ApplicationContext的主要实现类:
- ClassPathXmlApplicationContext:从类路径下加载配置文件
- FileSystemXmlApplicationContext:从文件系统中加载配置文件
- IOC容器初始化时就创建并初始化所有的单例Bean
- WebApplicationContext是专门为WEB应用而准备的
3.4. 通过类型获取Bean
HelloWorld helloWorld = ctx.getBean(HelloWorld.class);
如果IOC容器中有多个同类型的Bean,则抛出NoUniqueBeanDefinitionException异常。
3.5. 依赖注入
Spring支持3种依赖注入方式:
- 属性注入
- 构造器注入
- 工厂方法注入(很少使用,不推荐)
3.5.1. 属性注入
- 属性注入即通过set方法注入Bean的属性值或依赖的对象
- 属性注入使用<property>元素,name属性值为setter风格的属性名,value属性或<value>子节点指定属性值
- 属性注入是实际应用中最常用的注入方式
3.5.2. 构造器注入
通过构造器注入Bean的属性值或依赖的对象
Car.java
package org.lin.stu.spring;
public class Car {
private String brand;//品牌
private Double price;
private Integer maxSpeed;
public Car() {
}
public Car(String brand, Double price) {
this.brand = brand;
this.price = price;
}
public Car(String brand, Integer maxSpeed) {
this.brand = brand;
this.maxSpeed = maxSpeed;
}
//getters and setters are omitted
}
<bean id="car1" class="org.lin.stu.spring.Car">
<constructor-arg value="Audi"/>
<constructor-arg value="300000"/>
</bean>
<bean id="car2" class="org.lin.stu.spring.Car">
<constructor-arg value="Audi"/>
<constructor-arg value="240"/>
</bean>
以上代码等价于:
<bean id="car1" class="org.lin.stu.spring.Car">
<constructor-arg value="Audi" index="0"/>
<constructor-arg value="300000" index="1"/>
</bean>
<bean id="car2" class="org.lin.stu.spring.Car">
<constructor-arg value="Audi" index="0"/>
<constructor-arg value="240" index="1"/>
</bean>
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Car car1 = (Car) ctx.getBean("car1");
Car car2 = (Car) ctx.getBean("car2");
System.out.println(car1);//Car{brand='Audi', price=null, maxSpeed=300000}
System.out.println(car2);//Car{brand='Audi', price=null, maxSpeed=240}
因为300000和240既可以转成Double也可以转成Integer,此时仅靠顺序无法实现目的。
可以通过type属性避免上述问题:
<bean id="car1" class="org.lin.stu.spring.Car">
<constructor-arg value="Audi"/>
<constructor-arg value="300000" type="java.lang.Double"/>
</bean>
<bean id="car2" class="org.lin.stu.spring.Car">
<constructor-arg value="Audi" index="0"/>
<constructor-arg type="java.lang.Integer">
<value>240</value>
</constructor-arg >
</bean>
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Car car1 = (Car) ctx.getBean("car1");
Car car2 = (Car) ctx.getBean("car2");
System.out.println(car1);//Car{brand='Audi', price=300000.0, maxSpeed=null}
System.out.println(car2);//Car{brand='Audi', price=null, maxSpeed=240}
3.6. 字面值
- 可用字符串表示的值(基本数据类型及其封装类型、String等类型),可以通过<value>标签或value属性进行注入
- 若字面值中包含特殊字符(例如‘<’等字符),可以使用<![CDATA[]]>把字面值包裹起来,但只能通过<value>标签方式注入,例如:<value><![CDATA[<Audi>]]></value>
3.7. 引用其它Bean
- 组成应用程序的Bean经常需要相互协作以完成应用程序的功能
- 在Bean的配置文件中,可以通过<ref>标签或ref属性为Bean的属性指定引用的Bean
- 也可以在<property>标签或<constructor-arg>标签里包含Bean的声明,这样的Bean称为内部Bean
package org.lin.stu.spring;
public class Person {
private String name;
private Car car;
//getters and setters are omitted
}
package org.lin.stu.spring;
public class Car {
private String brand;
private Double price;
private Integer maxSpeed;
//getters and setters are omitted
}
<bean id="car" class="org.lin.stu.spring.Car">
<property name="brand" value="Audi"/>
<property name="price" value="300000"/>
</bean>
<bean id="person" class="org.lin.stu.spring.Person">
<property name="name" value="Alan"/>
<property name="car">
<ref bean="car"/>
</property>
</bean>
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Person person = (Person) ctx.getBean("person");
System.out.println(person);//Person{name='Alan', car=Car{brand='Audi', price=300000.0, maxSpeed=null}}
3.8. 内部Bean
- 当某个Bean仅在某个地方使用时,可以将其声明为内部Bean,内部Bean声明直接包含在<property>或<constructor-arg>标签里,不需要设置id属性
- 内部Bean不能使用在任何其他地方
package org.lin.stu.spring;
public class Person {
private String name;
private Car car;
//getters and setters are omitted
}
package org.lin.stu.spring;
public class Car {
private String brand;
private Double price;
private Integer maxSpeed;
//getters and setters are omitted
}
<bean id="person" class="org.lin.stu.spring.Person">
<property name="name" value="Alan"/>
<property name="car">
<bean class="org.lin.stu.spring.Car">
<property name="brand" value="Audi"/>
<property name="price" value="300000"/>
</bean>
</property>
</bean>
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Person person = (Person) ctx.getBean("person");
System.out.println(person);//Person{name='Alan', car=Car{brand='Audi', price=300000.0, maxSpeed=null}}
3.9. 空值和级联属性
- 可以使用<null/>标签为Bean的属性注入null值
- Spring支持级联属性的配置
<bean id="person" class="org.lin.stu.spring.Person">
<property name="name" value="Alan"/>
<!--级联属性赋值前需要先指定引用-->
<property name="car" ref="car"/>
<property name="car.brand" value="Ford"/>
</bean>
3.10. 集合属性
- 可以使用一组内置的XML标签配置集合属性
- java.util.List类型的属性需要使用<list>标签配置,在标签里配置一些元素,这些元素可以是<value>元素(配置字面值),也可以是<ref>元素(指定对其他Bean的引用),还可以是<bean>元素(配置内置Bean),还可以是<null/>元素,甚至可以内嵌其他集合
- 数组和List一样,也使用<list>标签
- java.util.Set类型的属性需要使用<set>标签配置,配置方法与List一样
- java.util.Map类型的属性需要使用<map>标签配置,<map>标签里可以使用多个<entry>标签,每个<entry>标签包含一个键和一个值,因为键和值的类型没有限制,所以可以自由地使用<value>,<ref>,<bean>或<null/>标签为它们指定值,可以通过属性指定键和值,简单值通过key和value属性指定,Bean引用通过key-ref和value-ref属性指定
- java.util.Properties类型的属性需要使用<props>标签配置,<props>标签里可以使用多个<prop>标签,每个<prop>标签必须指定key属性
3.10.1. List
package org.lin.stu.spring;
public class Car {
private String brand;
private Double price;
private Integer maxSpeed;
//getters and setters are omitted
}
package org.lin.stu.spring;
import java.util.List;
public class Person {
private String name;
private List<Car> cars;
//getters and setters are omitted
}
<bean id="car1" class="org.lin.stu.spring.Car">
<property name="brand" value="Audi"/>
<property name="price" value="300000"/>
</bean>
<bean id="person" class="org.lin.stu.spring.Person">
<property name="name" value="Mike"/>
<property name="cars">
<list>
<ref bean="car1"/>
</list>
</property>
</bean>
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Person person = (Person) ctx.getBean("person");
System.out.println(person);//Person{name='Mike', cars=[Car{brand='Audi', price=300000.0, maxSpeed=null}]}
3.10.2. Map
package org.lin.stu.spring;
public class Car {
private String brand;
private Double price;
private Integer maxSpeed;
//getters and setters are omitted
}
package org.lin.stu.spring;
import java.util.Map;
public class Person {
private String name;
private Map<String, Car> cars;
//getters and setters are omitted
}
<bean id="car1" class="org.lin.stu.spring.Car">
<property name="brand" value="Audi"/>
<property name="price" value="300000"/>
</bean>
<bean id="person" class="org.lin.stu.spring.Person">
<property name="name" value="Mike"/>
<property name="cars">
<map>
<entry key="A" value-ref="car1"/>
</map>
</property>
</bean>
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Person person = (Person) ctx.getBean("person");
System.out.println(person);//Person{name='Mike', cars={A=Car{brand='Audi', price=300000.0, maxSpeed=null}}}
3.10.3. Properties
package org.lin.stu.spring;
import java.util.Properties;
public class DataSource {
private Properties properties;
//getters and setters are omitted
}
<bean id="dataSource" class="org.lin.stu.spring.DataSource">
<property name="properties">
<props>
<prop key="username">root</prop>
<prop key="password">123</prop>
<prop key="url">jdbc:mysql///test</prop>
<prop key="driverClass">com.mysql.jdbc.Driver</prop>
</props>
</property>
</bean>
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
DataSource dataSource = (DataSource) ctx.getBean("dataSource");
System.out.println(dataSource);
//DataSource{properties={password=123, driverClass=com.mysql.jdbc.Driver, url=jdbc:mysql///test, username=root}}
3.11. 使用util scheme配置集合
- 基本的集合配置不能将其作为独立的定义,导致其他Bean无法引用,无法在不同Bean之间共享
- 可以使用util schema配置独立的集合
package org.lin.stu.spring;
public class Car {
private String brand;
private Double price;
private Integer maxSpeed;
//getters and setters are omitted
}
<bean id="car" class="org.lin.stu.spring.Car">
<property name="brand" value="Audi"/>
<property name="price" value="300000"/>
</bean>
<bean id="person" class="org.lin.stu.spring.Person">
<property name="name" value="Mike"/>
<property name="cars" ref="cars"/>
</bean>
<util:list id="cars">
<ref bean="car"/>
</util:list>
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Person person = (Person) ctx.getBean("person");
System.out.println(person);//Person{name='Mike', cars=[Car{brand='Audi', price=300000.0, maxSpeed=null}]}
3.12. 使用p命名空间
为了简化配置,越来越多的XML文件采用属性而非子元素。Spring从2.5开始引入了一个新的p命名空间,可以通过属性的方式配置Bean的属性,使用p命名空间后,基于XML的配置将进一步简化。
3.13. 自动装配
Spring IOC容器可以自动装配Bean,需要做的仅仅是通过<bean>的autowire属性指定自动装配的模式。
- byType(根据类型自动装配):若IOC容器中有多个类型匹配的Bean,则抛出NoUniqueBeanDefinitionException异常
- byName(根据名称自动装配):必须将目标Bean的名称和setter风格的属性名设成一样
- constructor(根据构造器自动装配):当类中存在多个构造器时,此种自动装配方式将会很复杂,不推荐使用
package org.lin.stu.spring.autowire;
public class Person {
private String name;
private Address address;
private Car car;
//getters and setters are omitted
}
package org.lin.stu.spring.autowire;
public class Address {
private String city;
private String street;
//getters and setters are omitted
}
package org.lin.stu.spring.autowire;
public class Car {
private String brand;
private Double price;
//getters and setters are omitted
}
3.13.1. byName
<bean id="addr" class="org.lin.stu.spring.autowire.Address" p:city="ChengDu" p:street="ShengBangJie"/>
<bean id="car" class="org.lin.stu.spring.autowire.Car" p:brand="Audi" p:price="300000"/>
<bean id="person" class="org.lin.stu.spring.autowire.Person" p:address-ref="addr" p:name="Tom" autowire="byName"/>
ApplicationContext ctx = new ClassPathXmlApplicationContext("autowire.xml");
Person person = (Person) ctx.getBean("person");
System.out.println(person);//Person{name='Tom', address=Address{city='ChengDu', street='ShengBangJie'}, car=Car{brand='Audi', price=300000.0}}
3.13.2. byType
<bean id="addr" class="org.lin.stu.spring.autowire.Address" p:city="ChengDu" p:street="ShengBangJie"/>
<bean id="car" class="org.lin.stu.spring.autowire.Car" p:brand="Audi" p:price="300000"/>
<bean id="person" class="org.lin.stu.spring.autowire.Person" p:name="Tom" autowire="byType"/>
ApplicationContext ctx = new ClassPathXmlApplicationContext("autowire.xml");
Person person = (Person) ctx.getBean("person");
System.out.println(person);//Person{name='Tom', address=Address{city='ChengDu', street='ShengBangJie'}, car=Car{brand='Audi', price=300000.0}}
3.13.3. 自动装配的缺点
- 在Bean配置里设置autowire属性进行自动装配将会装配Bean的所有属性
- 要么根据类型自动装配,要么根据名称自动装配,不能两者兼有
- 一般情况下,在实际的项目中很少使用自动装配功能,因为和自动装配功能所带来的好处比起来,明确清晰的配置文档更有说服力
3.14. Bean之间的继承关系
- Spring允许Bean之间进行继承,被继承的Bean称为父Bean,继承的Bean称为子Bean
- 子Bean继承父Bean的配置,包括属性配置
- 子Bean也可以覆盖从父Bean继承过来的配置
- 父Bean可以作为配置模板,也可以作为Bean实例,若只想把父Bean作为配置模板,可以设置abstract属性为true,这样Spring将不会实例化这个Bean
- 并不是<bean>元素的所有属性都会被继承,比如:autowire,abstract等
- 也可以忽略父Bean的class属性,让子Bean自己指定,而共享相同的属性配置,但此时该Bean必须是抽象Bean
package org.lin.stu.spring.relation;
public class Address {
private String city;
private String street;
//getters and setters are omitted
}
<bean id="address1" class="org.lin.stu.spring.relation.Address" p:city="ChengDu" p:street="YuLinLu"/>
<bean id="address2" p:street="BaiCaoLu" parent="address1"/>
ApplicationContext ctx = new ClassPathXmlApplicationContext("relation.xml");
Address address1 = (Address) ctx.getBean("address1");
Address address2 = (Address) ctx.getBean("address2");
System.out.println(address1);//Address{city='ChengDu', street='YuLinLu'}
System.out.println(address2);//Address{city='ChengDu', street='BaiCaoLu'}
3.15. Bean之间的依赖关系
Spring允许通过depends-on属性设置依赖的Bean,依赖的Bean会在本Bean实例化之前装配好,如果容器中没有依赖的Bean,则会抛出NoSuchBeanDefinitionException异常。
3.16. Bean的作用域
在Spring中,可以通过<bean>标签的scope属性设置Bean的作用域,取值如下:
- singleton:默认值,容器初始化时创建该Bean,在整个容器的生命周期内只创建这一次
- prototype:容器初始化时不创建,每次调用getBean(...)都会创建并返回一个新实例
- request:每次HTTP请求都会创建一个新实例,该作用域仅适用于WebApplicationContext环境
- session:同一个HttpSession共享一个实例,该作用域仅适用于WebApplicationContext环境
package org.lin.stu.spring.scope;
public class Car {
private String brand;
private Double price;
//getters and setters are omitted
}
<bean id="car" class="org.lin.stu.spring.scope.Car" p:brand="Audi" p:price="300000"/>
ApplicationContext ctx = new ClassPathXmlApplicationContext("scope.xml");
Car car1 = (Car) ctx.getBean("car");
Car car2 = (Car) ctx.getBean("car");
System.out.println(car1 == car2);//true
3.17. 使用外部属性文件
- Spring提供了一个PropertyPlaceholderConfigurer的BeanFactory后置处理器,它允许用户将Bean配置的部分内容(例如:文件上传目录、数据源信息等)外移到属性文件中,在Bean配置文件里使用${propName}代替
- Spring还允许在外部属性文件中使用${propName},以实现属性之间的相互引用
3.17.1. Spring2.5之前
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:db.properties"/>
</bean>
3.17.2. Spring2.5之后
<context:property-placeholder location="classpath:db.properties"/>
示例:
package org.lin.stu.spring.properties;
public class DataSource {
private String user;
private String password;
private String driverClass;
private String jdbcUrl;
//getters and setters are omitted
}
db.properties
user=root
password=${user}
driverClass=com.mysql.jdbc.Driver
jdbcUrl=jdbc:mysql:///test
<context:property-placeholder location="classpath:db.properties"/>
<bean id="dataSource" class="org.lin.stu.spring.properties.DataSource">
<property name="user" value="${user}"/>
<property name="password" value="${password}"/>
<property name="driverClass" value="${driverClass}"/>
<property name="jdbcUrl" value="${jdbcUrl}"/>
</bean>
ApplicationContext ctx = new ClassPathXmlApplicationContext("properties.xml");
DataSource dataSource = (DataSource) ctx.getBean("dataSource");
System.out.println(dataSource);//DataSource{user='root', password='root', driverClass='com.mysql.jdbc.Driver', jdbcUrl='jdbc:mysql:///test'}
3.18. SpEL(Spring表达式语言)
SpEL使用#{}作为定界符,在大括号中的内容被认为是SpEL,SpEL为bean的属性进行动态赋值提供了便利。
3.18.1. 字面值
<!--整数-->
<property name="age" value="#{25}"/>
<!--小数-->
<property name="weight" value="#{120.5}"/>
<!--科学计数法-->
<property name="height" value="#{1e2}"/>
<!--String可以使用单引号或者双引号作为定界符号-->
<property name="truename" value="#{'唐僧'}"/>
<property name='nickname' value='#{"小唐"}'/>
<!--布尔值-->
<property name="isMale" value="#{false}"/>
3.18.2. 引用其他Bean
<property name="car" value="#{car}"/>
3.18.3. 引用其他Bean的属性
<property name="city" value="#{address.city}"/>
3.18.4. 调用其他Bean的方法(支持链式调用)
<property name="info" value="#{user.toString()}"/>
<property name="upperCaseInfo" value="#{user.toString().toUpperCase()}"/>
//TODO
3.18.?. 示例
package org.lin.stu.spring.spel;
public class Address {
private String city;
private String street;
//getters and setters are omitted
}
package org.lin.stu.spring.spel;
public class Car {
private String brand;
private Double price;
private Double tyrePerimeter;
//getters and setters are omitted
}
package org.lin.stu.spring.spel;
public class Person {
private String name;
private Car car;
private String city;
//根据car的price确定,如果price>=300000,info为金领,否则为白领
private String info;
//getters and setters are omitted
}
<bean id="address" class="org.lin.stu.spring.spel.Address">
<!--使用SpEL为属性赋字面值-->
<property name="city" value="#{'ChengDu'}"/>
<property name="street" value="BaiCaoLu"/>
</bean>
<bean id="car" class="org.lin.stu.spring.spel.Car">
<property name="brand" value="Audi"/>
<property name="price" value="300000"/>
<!--使用SpEL访问类的静态属性(也可以调用类的静态方法)-->
<property name="tyrePerimeter" value="#{T(java.lang.Math).PI * 80}"/>
</bean>
<bean id="person" class="org.lin.stu.spring.spel.Person">
<property name="name" value="Tom"/>
<!--使用SpEL引用其他的Bean-->
<property name="car" value="#{car}"/>
<!--使用SpEL引用其他Bean的属性-->
<property name="city" value="#{address.city}"/>
<property name="info" value="#{car.price >= 300000 ? '金领' : '白领'}"/>
</bean>
ApplicationContext ctx = new ClassPathXmlApplicationContext("spel.xml");
Person person = (Person) ctx.getBean("person");
System.out.println(person);//Person{name='Tom', car=Car{brand='Audi', price=300000.0, tyrePerimeter=251.32741228718345}, city='ChengDu', info='金领'}