Spring概述及IOC
本文将首先对Spring进行了简要的介绍,然后重点学习了SpringIOC,包括IOC基本概念、环境搭建、使用IOC容器创建对象,着重对获取bean的两种方式(id、Class)以及为属性赋值的三种方式(property属性、constructor-arg属性、p名称空间)进行了学习,同时也学习了对象的创建时机以及null值的使用。
1.为什么要学习Spring
因为Spring可以整合其他的框架,例如Struts2和Hibernate。
1. Spring中包含两个主要功能:IOC和AOP,可以使用IOC创建Struts2的Action类的对象以及Hibernate的SessionFactory对象,并自动装配。
2. 同时,对于数据库事物的问题,Spring以AOP为基础提供了声明式事务功能,在实际项目中,可以将事物操作交给Spring的声明式事务管理。
3. 为了学习声明式事务,需要先学习JdbcTemplate,JdbcTemplate是Spring提供的一个简单的访问数据库的功能。
2.什么是IOC
- IOC(Inversion Of Control)又称“反转控制”。
- 传统的资源获取方向:应用去找环境要资源
- IOC理念:环境主动把资源注入应用
- 虽然目前我们还没有学习Spring的IOC,但是已经接触过一些由环境或者容器注入资源的例子。
【例1】例如Servlet接口的init(ServletConfig config)方法,该方法由Servlet容器(例如Tomcat)负责调用,参数ServletConfig config是由Servlet容器负责创建对象并传入的,这就是一个典型的注入资源的例子。ServletConfig config这个资源并不是由我们创建,而是由Servlet容器创建并通过init()方法注入给我们。
【例2】例如Servlet.service(request,response);方法。
【例3】Struts2中Action类实现XxxAware接口,可以享受到Struts主动注入的Web资源对象。 - IOC的另一个表述方式:DI(Dependency Injection)又称“依赖注入”。
- IOC最主要的用途:创建组件对象并自动装配。
3.IOC应用开发环境搭建
- 导入IOC容器所需要的jar包
commons-logging-1.1.1.jar:日志
spring-beans-4.0.0.RELEASE.jar:创建bean相关
spring-context-4.0.0.RELEASE.jar:上下文先关
spring-core-4.0.0.RELEASE.jar:核心
spring-expression-4.0.0.RELEASE.jar:表达式相关 - 创建Spring配置文件:Spring Bean Configuration File
在eclipse中选择“Window”菜单->“Perspective”->“Customisze Perspective…”
在弹出的窗口中选择“Menu Visibility”选项卡,将“File”->“New”下的“Spring Bean Configuration File”勾选,在右键“New”菜单中添加“Spring Bean Configuration File”选项。(不同版本的elipse这个设置的位置可能不同)
在src中新建“Spring Bean Configuration File”文件,文件名为“beans.xml”,创建完毕后如下图所示。
4.IOC容器创建对象步骤(根据id获取Bean)
- 创建IOC容器对象
在eclipse中按“ctrl shift” + “T”调出“Open Type”窗口,输入“ApplicationContext”,然后关联源码。
在接口名上按“ctrl+ T”,发现ApplicationContext接口有如下实现类,其中包含:
- ApplicationContext:顶级接口。
- ConfigurableApplicationContext:带有声明周期方法的接口,在涉及到带有生命周期的Bean时会使用。
- ClassPathXmlApplicationContext:使用类路径下XML作为配置文件的实现类,通常使用这个类创建IOC容器对象
- FileSystemXmlApplicationContext
- GenericApplicationContext
- 调用IOC容器对象的getBean()方法即可获取IOC容器中配置的对应的Bean对象。
getBean()方法包含两种常用的重载形式,分别为:
//1.形参为Bean的ic,根据配置文件中指定的Bean的Id获取Bean。
getBean(String id)
//2.形参为Bean的class对象,根据Bean的类型获取Bean,如果想要根据类型获取Bean,
//需要保证IOC容器中指定类型的Bean只有一个。
getBean(Class<?> clazz)
测试:
//步骤1:在Spring01_IOC工程的src包下新建com.atguigu.ioc.bean包,在该包中新建Book.java
package com.atguigu.ioc.bean;
public class Book {
//说明:在Hibernate中,要求持久化类必须提供OID属性,但是SpringIOC容器没有此要求,
//这里只是为了照顾习惯使用了bookId属性。
private Integer bookId;
private String bookName;
private String author;
private double prive;
public Book() {}
public Book(Integer bookId, String bookName, String author, double prive) {
super();
this.bookId = bookId;
this.bookName = bookName;
this.author = author;
this.prive = prive;
}
public Integer getBookId() {
return bookId;
}
public void setBookId(Integer bookId) {
this.bookId = bookId;
}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public double getPrive() {
return prive;
}
public void setPrive(double prive) {
this.prive = prive;
}
@Override
public String toString() {
return "Book [bookId=" + bookId + ", bookName=" + bookName + ", author=" + author + ", prive=" + prive + "]";
}
}
//步骤2:在配置文件(beans.xml)中配置这个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.xsd">
<!-- 实验1:通过IOC容器创建对象,并为属性复制 -->
<!-- id:Bean的Id -->
<!-- class:Bean的全类名 -->
<!-- 如果安装Spring插件的话,在填写全类名时只需要输入类名,就可以自动补全,非常方便 -->
<!-- 同时id的值按alt + /,也可以自动生成 -->
<bean id="book01" class="com.atguigu.ioc.bean.Book">
<property name="bookId" value="1" />
<property name="bookName" value="bookName01"/>
<property name="author" value="author01" />
<property name="prive" value="100" />
</bean>
</beans>
//步骤3:测试,在src下新建com.atguigu.ioc.test包,在这个包下新建Junit Test Case类,类名:IOCTest
package com.atguigu.ioc.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class IOCTest {
@Test
public void test01() {
//1.创建IOC容器对象
ApplicationContext myIoc = new ClassPathXmlApplicationContext("beans.xml");
//2.通过IOC容器对象获取Bean,使用getBean(String id)的重载形式
Object bean = myIoc.getBean("book01");
System.out.println(bean);
}
}
运行该测试类,结果如下:
可以发现,在SpringIOC容器中,可以通过配置的方式创建对象,需要使用对象的时候从IOC容器中获取就可以。
5.对象在什么时候被创建
结论:默认情况下,对象在IOC容器创建的时候就被创建了
测试(只需要对上面的代码稍微修改)
- 在Book类的空参构造器中添加如下代码
System.out.println("Book对象被创建了");
- 将IOCTest类的test01方法改写为
@Test
public void test01() {
ApplicationContext myIoc = new ClassPathXmlApplicationContext("beans.xml");
//在IOC容器创建后,获取对象之前,添加下面一行代码。
System.out.println("===Ioc容器已经被创建===");
Object bean = myIoc.getBean("book01");
System.out.println(bean);
}
运行该测试类,结果如下:
可以发现,Book对象在IOC容器创建后便已经创建了。可以得到如下结论:
- 默认情况下:在创建和初始化IOC容器本身的时候,IOC容器就会把所有配置好的Bean都创建出来。
- 其他情况:是存在的
6.根据类型获取Bean
刚才我们提到,getBean()方法存在两种常用的重载形式,分别为:
getBean(String id)
getBean(Class<?> clazz)
在上面的例子中,我们使用的是第一种形式的getBean(String id)方法,该方法通过Bean的id获取Bean对象,下面我们来测试第二种方法:通过Bean的类型获取对象。
测试(只需要对上面代码稍微修改)
//步骤1:在Junit Test Case 单元测试类IOCTest.java中,添加一个新的方法,test02()
@Test
public void test02()
{
//如果想要根据类型获取Bean,需要保证IOC容器中指定类型的Bean只有一个
Book book = ioc.getBean(Book.class);
System.out.println(book);
}
同时,为了方便测试,我们将原先在test01()中定义的局部变量ApplicationContext ioc更改为一个私有的成员变量,以便被各个测试方法调用。
//步骤2:声明ApplicationContext局部变量
private ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
运行该测试方法,结果如下:
至此,我们已经成功的通过Bean的类型获取Bean对象,且Junit没有抛出异常。
一个问题:在使用getBean(String id)方法获取Bean对象时,传入的参数为Bean的id,这个Id是惟一的,可以保证获取Bean的唯一性。但是,当使用getBean(Class<?> clazz)
方法时,由于在SpringIOC配置文件中,同一个类型的实体,可以配置多个Bean,当这种情况发生的时候,会出现什么问题呢,下面我们进行测试。
//步骤:在beans.xml中添加如下配置,配置了Book类的另一个bean,id为book02。
<!-- 实验2:根据Bean的类型从IOC容器中获取Bean的实例 -->
<bean id="book02" class="com.atguigu.ioc.bean.Book">
<property name="bookId" value="2" />
<property name="bookName" value="bookName02"/>
<property name="author" value="author02" />
<property name="prive" value="200" />
</bean>
重新运行test02()单元测试方法,结果如下:
可以发现,Junit抛出名为org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 的异常,该异常指出,Bean的配置不是惟一的。
因此可以得到结论:
- 如果想要根据类型获取Bean,需要保证IOC容器中指定类型的Bean只有一个。
- 该方法比较适合用于在一个工程中只存在一个实例的类
6.使用构造器创建对象
前面的例子中,beans.xml配置文件中的每一个bean都是通过设置property属性来确定对象的初始化值,这种方法实际上是调用了对象的无参构造器创建对象,并通过setXxx()方法给属性赋值,下面我们来学习一种新的创建对象的方法,该方法使用对象的有参构造器初始化对象,例子如下:
//步骤1:在beans.xml中添加新的配置
<!-- 实验3:通过构造器为bean属性赋值 -->
<bean id="book03" class="com.atguigu.ioc.bean.Book">
<!-- 严格根据有参构造器的参数顺序创建 -->
<constructor-arg value="3"/>
<constructor-arg value="bookName3"/>
<constructor-arg value="author03"/>
<constructor-arg value="300"></constructor-arg>
</bean>
//步骤2:在IOCTest类中添加新的单元测试方法test03()
@Test
public void test03()
{
Object bean = ioc.getBean("book03");
System.out.println(bean);
}
运行该单元测试方法,结果如下:
几个问题:
问题1:若bean按照上述格式进行配置,则每一个constructor-arg属性必须严格按照有参构造器的参数顺序赋值,否则会出现将作者姓名赋值给bookName的情况,所以通常需要采用如下改进型的配置:
<!-- 实验3:通过构造器为bean属性赋值 -->
<!-- 实验4:通过index属性指定参数位置-->
<bean id="book03" class="com.atguigu.ioc.bean.Book">
<!-- 根据有参构造器的参数顺序创建,同时使用index属性声明该constructor-arg是给构造器中
第几个参数赋值 -->
<constructor-arg value="3" index="0"/>
<constructor-arg value="bookName3" index="1"/>
<constructor-arg value="author03" index="2"/>
<constructor-arg value="300" index="3"/>
</bean>
问题2:如果出现重载的构造方法怎么办?
//步骤1:将Book.java重写,添加isbn属性(该属性目前没有get set方法),并创建重载的构造器
package com.atguigu.ioc.bean;
public class Book {
private Integer bookId;
private String bookName;
private String author;
private double prive;
private String isbn;
public Book() {
System.out.println("Book对象被创建了");
}
public Book(Integer bookId, String bookName, String author, double prive) {
super();
this.bookId = bookId;
this.bookName = bookName;
this.author = author;
this.prive = prive;
}
//重载构造器
public Book(Integer bookId, String bookName, String author, String isbn) {
super();
this.bookId = bookId;
this.bookName = bookName;
this.author = author;
this.isbn = isbn;
}
public Integer getBookId() {
return bookId;
}
public void setBookId(Integer bookId) {
this.bookId = bookId;
}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public double getPrive() {
return prive;
}
public void setPrive(double prive) {
this.prive = prive;
}
//重新toString()
@Override
public String toString() {
return "Book [bookId=" + bookId + ", bookName=" + bookName + ", author=" + author + ", prive=" + prive
+ ", isbn=" + isbn + "]";
}
}
//步骤2
<!-- 实验3:通过构造器为bean属性赋值 -->
<!-- 实验4:通过index属性指定参数位置-->
<!-- 实验5:通过类型的不同指定重载的构造器 -->
<bean id="book03" class="com.atguigu.ioc.bean.Book">
<constructor-arg value="3" index="0"/>
<constructor-arg value="bookName3" index="1"/>
<constructor-arg value="author03" index="2"/>
<!-- 使用type属性指定类型,可以为包装类型(Double)也可以为基本类型(double) -->
<constructor-arg value="300" index="3" type="double"/>
</bean>
运行test03(),结果如下:
更改type值为String,尝试使用重载的有参构造器创建对象:
<bean id="book03" class="com.atguigu.ioc.bean.Book">
<!-- 根据有参构造器的参数顺序创建 -->
<constructor-arg value="3" index="0"/>
<constructor-arg value="bookName3" index="1"/>
<constructor-arg value="author03" index="2"/>
<!-- 更改type值为String,尝试使用重载的有参构造器创建对象 -->
<constructor-arg value="300" index="3" type="java.lang.String"/>
</bean>
运行test03(),结果如下:
疑问:目前Book类中没有给新添加的isbn属性创建get set方法,但是测试发现依旧可以通过book03配置创建对象,我觉得应该是因为book03配置是通过重载的有参构造去创建对象的,没有使用set方法。在Book类中,声明了两个重载的有参构造方法,IOC容器通过判断constructor-arg属性的type来判断具体执行哪一个构造方法,既可创建对象。
7.通过p命名空间给属性赋值
在前面的例子中我们学习了两种给属性赋值的方法,分别为property方式和constructor-arg方式,接下来介绍另一种给属性赋值的方法,即通过p命名空间给属性赋值。
//步骤1:给beans.xml添加p名称空间
//步骤2:在beans.xml中添加如下配置
<!-- 实验6:通过p名称空间为bean赋值 -->
<bean id="book06" class="com.atguigu.ioc.bean.Book"
p:bookId="6"
p:bookName="bookName06"
p:author="author06"
p:prive="600"
p:isbn="isbn06"
/>
//步骤3:给Book类的isbn属性添加get set方法(之前没有添加)。
在设置p名称空间时,同时按下alt + /可以看到,通过这种方法给属性赋值实际上是通过调用Book类的setXxx方法实现的,如下图所属。故每一个属性都需要定义set方法。
//步骤4:在IOCTest类中创建Junit Test case方法
@Test
public void test06()
{
Object bean = ioc.getBean("book06");
System.out.println(bean);
}
测试结果如下:
总结一下,目前共有三种方式给属性赋值,分别为:
1. property属性:通过setXxx()方法
2. constructor-arg属性:通过调用构造器
3. p名称空间:通过setXxx()方法
8.使用null值
测试null值的使用:
<!-- 实验7:测试使用null值,在beans.xml中添加配置如下 -->
<bean id="book07" class="com.atguigu.ioc.bean.Book">
<property name="bookId" value="7" />
<property name="bookName" value="bookName07"/>
<property name="author">
<null/>
</property>
<!-- 对于基本数据类型,此处不设置为默认值 -->
<!-- <property name="prive" value="700" /> -->
<!-- 对于引用数据类型,此处不设置也是null -->
<!-- <property name="isbn">
<null/>
</property> -->
</bean>
//添加测试方法如下
@Test
public void test07()
{
Object bean = ioc.getBean("book07");
System.out.println(bean);
}
运行结果如下:
可以发现,对于基本数据类型来说,
- 如果不显示设置属性值,IOC容器会将默认值赋给它。
对于引用数据类型来说
- 如果不显示设置属性值或者使用了
<null/>
,IOC容器会将null值赋给它。
注意:不能给基本数据类型属性赋null值,否则会抛出org.springframework.beans.factory.BeanCreationException异常。