第18章 Spring核心之IOC
18.1 Sring概述
18.1.1 初识Sring
Spring 有可能成为所有企业应用程序的一站式服务点,然而,Spring 是模块化的,允许你挑选和选择适用于你的模块,不必要把剩余部分也引入。下面的部分对在 Spring 框架中所有可用的模块给出了详细的介绍。
Spring 框架提供约 20 个模块,可以根据应用程序的要求来使用。
Spring 体系结构
Spring 模块可以分类成:数据访问和集成、Web远程调用、面向切面编程、Instrumentation,Spring核心容器、测试(参考资料Spring4实战)
总共六大模块,可以分类成以下。
- 数据访问和集成:
JDBC:spring-jdbc-4.3.18.RELEASE.jar
Transaction:spring-tx-4.3.18.RELEASE.jar
ORM:spring-orm-4.3.18.RELEASE.jar
OXM:spring-oxm-4.3.18.RELEASE.jar
Messaging:spring-messaging-4.3.18.RELEASE.jar
JMS:spring-jms-4.3.18.RELEASE.jar
- Web远程调用:
Web:spring-web-4.3.18.RELEASE.jar
Web Servlet:spring-webmvc-4.3.18.RELEASE.jar
Web portlet:spring-webmvc-portlet-4.3.18.RELEASE.jar
Web Socket:spring-websocket-4.3.18.RELEASE.jar
- 面向切面编程:(核心)
Aop:spring-aop-4.3.18.RELEASE.jar
Aspects:spring-aspects-4.3.18.RELEASE.jar
- Instrumentation(工具):这个使用场景非常有限,主要提供了为JVM添加代理的功能
Instrumentation:spring-instrument-4.3.18.RELEASE.jar
Instrument Tomcat:spring-instrument-tomcat-4.3.18.RELEASE.jar
- Spring 核心容器:(核心)
Beans:spring-beans-4.3.18.RELEASE.jar
Core:spring-core-4.3.18.RELEASE.jar
Context:spring-context-4.3.18.RELEASE.jar
Expression:spring-expression-4.3.18.RELEASE.jar
Context support:spring-context-support-4.3.18.RELEASE.jar
- 测试:
Test:spring-test-4.3.18.RELEASE.jar
18.1.2 Spring的获取
从 http://repo.spring.io/release/org/springframework/spring
下载最新版本的 Spring 框架的二进制文件。
18.1.3 简单配置Spring
Spring的jar包相关功能说明
jar包名称 | 说 明 |
---|---|
spring.jar | 整个Spring模块,包含表中所有jar的功能 |
spring-core.jar | Spring的核心模块,包含Ioc容器 |
spring-aop.jar | Spring的Aop模块 |
spring-context.jar | Sping的上下文,包含ApplicationContext容器 |
spring-dao.jar | Spring的DAO模块,包含对DAO与JDBC的支持 |
spring-orm.jar | Sping的ORM模块,支持Hibernate、JDBC的支持 |
spring-web.jar | Sping的Web模块,包含Web application context |
spring-webmvc.jar | Spring的MVC框架模块 |
18.1.4 使用BeanFactory管理bean
- 1.创建web项目,导入jar包
commons-logging-1.0.4.jar
、spring.jar
- 2.创建User.java 实体类
public class User {
private String name;//用户姓名
private Integer age;//年龄
private String sex;//性别
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
- 3.创建操作类Manger.java,此处使用Junit Test测试
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
public class Manger {
@Test
public void main() {
Resource resource = new ClassPathResource("applicationContext.xml"); //装载配置文件
BeanFactory factory = new XmlBeanFactory(resource);
User user = (User) factory.getBean("user"); //获取Bean
System.out.println("用户姓名——"+user.getName());//输出用户的姓名
System.out.println("用户年龄——"+user.getAge());//输出用户的年龄
System.out.println("用户性别——"+user.getSex());//输出用户的性别
}
}
- 4.创建applicationContext.xml,在源码目录下Src目录
<?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-2.5.xsd">
<!-- User Bean -->
<bean name="user" class="com.hwp.spring.User">
<property name="name">
<value>小强</value>
</property>
<property name="age">
<value>26</value>
</property>
<property name="sex">
<value>男</value>
</property>
</bean>
</beans>
<property> 中的name要和实体类对应上,<bean>中class要完整的包名、类名
18.1.5 ApllicationContext的应用
Spring ApplicationContext 容器
Application Context 是 BeanFactory 的子接口,也被成为 Spring 上下文。
Application Context 是 spring 中较高级的容器。和 BeanFactory 类似,它可以加载配置文件中定义的 bean,将所有的 bean 集中在一起,当有请求的时候分配 bean。 另外,它增加了企业所需要的功能,比如,从属性文件中解析文本信息和将事件传递给所指定的监听器。这个容器在 org.springframework.context.ApplicationContext interface 接口中定义。
ApplicationContext 包含 BeanFactory 所有的功能,一般情况下,相对于 BeanFactory,ApplicationContext 会更加优秀。当然,BeanFactory 仍可以在轻量级应用中使用,比如移动设备或者基于 applet 的应用程序。
最常被使用的 ApplicationContext 接口实现:
FileSystemXmlApplicationContext:该容器从 XML 文件中加载已被定义的 bean。在这里,你需要提供给构造器 XML 文件的完整路径。
ClassPathXmlApplicationContext:该容器从 XML 文件中加载已被定义的 bean。在这里,你不需要提供 XML 文件的完整路径,只需正确配置 CLASSPATH 环境变量即可,因为,容器会从 CLASSPATH 中搜索 bean 配置文件。
WebXmlApplicationContext:该容器会在一个 web 应用程序的范围内加载在 XML 文件中已被定义的 bean。
实例
- 1.创建web工程,导包
commons-logging-1.0.4.jar和spring.jar
2.创建package,com.hwp.spring,在文件夹下创建HelloWorld.java和MainApp.java两个类
HelloWorld.java
public class HelloWorld {
private String message;
public void setMessage(String message){
this.message = message;
}
public void getMessage(){
System.out.println("Your Message : " + message);
}
}
- 2.MainApp.java
public class Manger {
//此处使用Junit测试
@Test
public void main() {
ApplicationContext context = new FileSystemXmlApplicationContext
("src\\Beans.xml");
HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
obj.getMessage();
}
}
- 3.在源码目录下即Src目录下,创建Beans.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-3.0.xsd">
<bean id="helloWorld" class="com.hwp.spring.HelloWorld">
<property name="message" value="Hello World!"/>
</bean>
</beans>
- 4.运行后控制台输出
Your Message : Hello World!
18.2 依赖注入
18.2.1 什么是控制反转与依赖注入
依赖注入有3种实现类型
- 1.接口注入(Spring不支持)
基于接口将调用与实现分离。这种依赖注入方式必须实现容器所规定的接口,使程序代码和容器的API绑定在一起,这不是理想的依赖注入方式。 - 2.Setter注入(Spring支持)
基于JavaBean的Setter方法为属性赋值。在实际开发中得到了最广泛的应用。
public class HelloWorld {
private String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
- 3.构造器注入(Spring支持)
基于构造方法为属性赋值。容器通过调用类的构造方法
public class HelloWorld {
private String message;
public HelloWorld(String message) {
super();
this.message = message;
}
}
18.2.2 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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="helloWorld" class="com.hwp.spring.HelloWorld">
<property name="message" value="Hello World!"/>
</bean>
</beans>
id属性为bean的名称,class属性为对应的类名,这样通过BeanFactory容器的getBean("test")方法就可以获取到该类的实例。
18.2.3 Setter注入
- 1.创建User.java 实体类
public class User {
private String name;//用户姓名
private Integer age;//年龄
private String sex;//性别
... //省略Setter()和Getter()方法
}
- 2.创建applicationContext.xml,在源码目录下Src目录
<?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-2.5.xsd">
<!-- User Bean -->
<bean name="user" class="com.hwp.spring.User">
<property name="name">
<value>小强</value>
</property>
<property name="age">
<value>26</value>
</property>
<property name="sex">
<value>男</value>
</property>
</bean>
</beans>
- 3.创建操作方法
public class Manger {
@Test
public void main() {
Resource resource = new ClassPathResource("applicationContext.xml"); //装载配置文件
BeanFactory factory = new XmlBeanFactory(resource);
User user = (User) factory.getBean("user"); //获取Bean
System.out.println("用户姓名——"+user.getName());//输出用户的姓名
System.out.println("用户年龄——"+user.getAge());//输出用户的年龄
System.out.println("用户性别——"+user.getSex());//输出用户的性别
}
}
控制台输出信息
用户姓名——小强
用户年龄——26
用户性别——男
如果当JavaBean的某个元素是List集合类型或数组时,需要使用<list>标签为List集合类型或数组的每一个元素赋值
示例代码
List
<!-- 配置List属性 -->
<bean id="person3" class="com.kejian.spring.bean.collectionbean.PersonForList">
<property name="name" value="Peter"></property>
<property name="age" value="30"></property>
<property name="cars">
<list>
<ref bean="car1"/>
<ref bean="car2"/>
</list>
</property>
</bean>
Map
<!-- 配置Map属性 -->
<bean id="person4" class="com.kejian.spring.bean.collectionbean.PersonForMap">
<property name="name" value="Mike"></property>
<property name="age" value="35"></property>
<property name="cars">
<map>
<entry key="ACar" value-ref="car1"></entry>
<entry key="BCar" value-ref="car2"></entry>
</map>
</property>
</bean>
properties
<!-- 通过props来配置properties -->
<bean id="datasource" class="com.kejian.spring.bean.collectionbean.Datasource">
<property name="properties">
<props>
<prop key="user">root</prop>
<prop key="password">root</prop>
<prop key="url">jdbc:mysql.///test</prop>
<prop key="driver">jdbc.mysql.driver</prop>
</props>
</property>
</bean>
18.2.4 构造器注入
- 1.Javabean
public class User{
private String name;
private int age;
private String sex;
public User(String name,int age,String sex){
this.name=name;
this.age=age;
this.sex=sex;
}
public void printlnfo(){
System.out.println("用户姓名——"+name);//输出用户的姓名
System.out.println("用户年龄——"+age);//输出用户的年龄
System.out.println("用户性别——"+sex);//输出用户的性别
}
}
- 2.创建applicationContext.xml,在源码目录下Src目录
<?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-2.5.xsd">
<!-- User Bean -->
<bean name="user" class="com.hwp.spring.User">
<constructor-arg>
<value>小强</value>
</constructor-arg>
<constructor-arg>
<value>20</value>
</constructor-arg>
<constructor-arg>
<value>男</value>
</constructor-arg>
</bean>
</beans>
容器通过多个<constructor-arg>标签完成了对构造方法的传参,但是如果标签的赋值顺序与构造方法中参数的顺序类型不同,程序会产生异常,可以使用<constructor-arg>元素的index属性和type属性解决此类问题。
示例代码
<beans>
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="2001 index="0"/>
<constructor-arg type="java.lang.String" value="Zara" index="1"/>
</bean>
</beans>
18.2.5 引用其他的bean
- 1.创建web项目,导入jar包,commons-logging-1.0.4.jar、spring-webmvc.jar和spring.jar
- 2.创建javabean,User.java
public class User {
private String name;//用户姓名
private Integer age;//年龄
private String sex; //性别
... //省略Setter、Getter方法
public void println() {
System.out.println("名字---"+name);
System.out.println("年纪---"+age+"");
System.out.println("性别---"+sex);
}
}
- 创建Manger.java 继承于AbstractController类
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;
public class Manger extends AbstractController {
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest arg0,
HttpServletResponse arg1) throws Exception {
user.println();
return null;
}
}
- 4.创建applicationContext.xml
<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-2.5.xsd">
<!-- User Bean -->
<bean id="user" class="com.hwp.spring.User">
<property name="name" >
<value>小强</value>
</property>
<property name="age">
<value>26</value>
</property>
<property name="sex">
<value>男</value>
</property>
</bean>
<!-- 注入JavaBean -->
<bean name="/main.do" class="com.hwp.spring.Manger">
<property name="user">
<ref local="user"/>
</property>
</bean>
</beans>
- 5.设置web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
<display-name>18_3spring</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
- 6.编写index.jsp
<!-- 主要代码 -->
<body>
<a href="http://localhost:8080/18_3spring/main.do">执行JavaBean的注入</a>
</body>
代码部署后,点击超链接,控制台输出信息
名字---小强
年纪---26
性别---男
18.2.6 匿名内部javabean的创建
18.3 自动装配
18.3.1 按bean名称装配
<bean>元素的byname属性以属性名区分自动装配,
- 1.定义User类
public class User{
private String name; //用户名
private int age; //年龄
private String sex; //性别
... // 省略的Setter()和Getter()方法
}
- 2.定义PrintlInfo类,将User对象注入到PrintInfo对象中,按我的个人理解是实体类嵌套一层实体类
public class PrintlInfo{
private User user;
... //省略Setter()、Getter()方法
public void PrintUser(){
System.out.println("打印的属性");
System.out.println("----------");
System.out.println("姓名---"+user.getName()); //输出用户的姓名
System.out.println("年纪---"+user.getAge()); //输出用户的年龄
System.out.println("性别---"+user.getSex()); //输出用户的性别
}
}
- 3.applicationContext.xml,存放在在源码目录下即Src目录下
<?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-2.5.xsd">
<!-- User Bean -->
<bean id="user" class="com.mr.user.User">
<property name="name">
<value>小强</value>
</property>
<property name="age">
<value>26</value>
</property>
<property name="sex">
<value>男</value>
</property>
</bean>
<bean autowire="byName" id="printInfo" class="com.mr.user.PrintInfo" />
</beans>
- 4.测试类,此处使用junit测试
public class Manger {
@Test
public void main() {
Resource resource = new ClassPathResource("applicationContext.xml"); //装载配置文件
BeanFactory factory = new XmlBeanFactory(resource);
PrintInfo printInfo = (PrintInfo) factory.getBean("printInfo"); //获取Bean
printInfo.PrintUser();//输出JavaBean的属性值
}
}
测试,控制台输出
PrintInfo打印的User属性
用户姓名--小强
用户年龄--26
用户性别--男
18.3.2 按bean类型装配
按类型装配,除了applicationContext.xml中以下代码变动,其他的代码一样
<bean autowire="byType" id="printInfo" class="com.mr.user.PrintInfo" />
18.3.3 自动装配的其他方式
在Spring中还有另外3种自动装配方式,通过设置autowire的不同属性值来实现。
- 1.no属性
即不启用自动装配。Autowire默认的值。不使用Autowire,引用关系显示声明,spring的reference也建议不用autoware,因为这会破坏模块关系的可读性。 - 2.constructor属性
通byType一样,也是通过类型查找依赖对象。与byType的区别在于它不是使用Seter方法注入,而是使用构造子注入。如果容器中没有找到与构造器参数类型一致的bean,那么抛出异常。 - 3.autodetect属性
在byType和constructor之间自动的选择注入方式。通过bean类的自省机制(introspection)来决定是使用constructor还是byType方式进行自动装配。如果发现默认的构造器,那么将使用byType方式,否则采用
constructor。
Sping自动装配的优缺点
优点 - 自动装配可以大大地减少属性和构造器参数的指派。
- 自动装配也可以在解析对象时更新配置。
缺点
- 在property和constructor-arg设置中的依赖总是重载自动装配,我们无法对原始类型(如int,long,boolean等就是首字母小写的那些类型),还有String,Classes做自动装配。这是受限于设计。
- 自动装配跟直接装配(explicit wiring)相比较,在准确性方便还是差那么点,虽然没有明确地说明,但是Spring还是尽量避免这种模棱两可的情况,导致出现没预料到的结果。
- Spring容器生成文档的工具可能会不能使用装配的信息。
- 容器中多个bean的定义可能要对setter和构造器参数做类型匹配才能做依赖注入,虽然对于array,collection和map来说不是啥问题,但是对于只有单一值的依赖来讲,这就有点讲不清楚了,所以如果没有唯一的bean定义,那只能抛出异常
18.4 bean的作用域
18.4.1 了解Spring中的bean
Bean 定义
被称作 bean 的对象是构成应用程序的支柱也是由 Spring IoC 容器管理的。bean 是一个被实例化,组装,并通过 Spring IoC 容器所管理的对象。这些 bean 是由用容器提供的配置元数据创建的,例如,已经在先前章节看到的,在 XML 的表单中的 定义。
bean 定义包含称为配置元数据的信息,下述容器也需要知道配置元数据:
- 如何创建一个 bean
- bean 的生命周期的详细信息
bean 的依赖关系
上述所有的配置元数据转换成一组构成每个 bean 定义的下列属性。
属性 | 描述 |
---|---|
class | 这个属性是强制性的,并且指定用来创建 bean 的 bean 类。 |
name | 这个属性指定唯一的 bean 标识符。在基于 XML 的配置元数据中,你可以使用 ID 和/或 name 属性来指定 bean 标识符。 |
scope | 这个属性指定由特定的 bean 定义创建的对象的作用域,它将会在 bean 作用域的章节中进行讨论。 |
constructor-arg | 它是用来注入依赖关系的,并会在接下来的章节中进行讨论。 |
properties | 它是用来注入依赖关系的,并会在接下来的章节中进行讨论。 |
autowiring mode | 它是用来注入依赖关系的,并会在接下来的章节中进行讨论。 |
lazy-initialization mode | 延迟初始化的 bean 告诉 IoC 容器在它第一次被请求时,而不是在启动时去创建一个 bean 实例。 |
initialization 方法 | 在 bean 的所有必需的属性被容器设置之后,调用回调方法。它将会在 bean 的生命周期章节中进行讨论。 |
destruction 方法 | 当包含该 bean 的容器被销毁时,使用回调方法。它将会在 bean 的生命周期章节中进行讨论。 |
18.4.2 singleton的作用域
singleton 是默认的作用域,也就是说,当定义 Bean 时,如果没有指定作用域配置项,则 Bean 的作用域被默认为 singleton。
当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。
也就是说,当将一个bean定义设置为singleton作用域的时候,Spring IoC容器只会创建该bean定义的唯一实例。
Singleton是单例类型,就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。注意,Singleton作用域是Spring中的缺省作用域。你可以在 bean 的配置文件中设置作用域的属性为 singleton,如下所示:
<!-- A bean definition with singleton scope -->
<bean id="..." class="..." scope="singleton">
<!-- collaborators and configuration for this bean go here -->
</bean>
18.4.3 prototype的作用域
18.5 对bean的特殊处理
18.5.1 初始化与销毁
18.5.2 自定义属性编辑器
Spring内置的属性编辑器说明
属性名称 | 说 明 | 属性名称 | 说 明 |
---|---|---|---|
ByteArrayPropertyEdior | 字节数组编辑器 | CustomDateEditor | Date类型编辑器 |
CharacterEdior | 字符类型编辑器 | CustomNumberEditor | 数值类型编辑器 |
CharArrayEdior | 字符编辑器 | FileEditor | 文件类型编辑器 |
ClassEdiotor | 字节数组编辑器 | InputStramEditor | 输入流类型编辑器 |
CustomBooleanEditor | Boolean类型编辑器 | LocaleEditor | 地域信息编辑器 |
CustomCollectionEditor | Collection集合类型编辑器 | PropertiesEditor | 属性类型编辑器 |