一 简介
1.Spring为企业应用的开发提供了一个轻量级的解决方案,该解决方案包括:基于依赖注入的核心机制、基于AOP的声明式事务管理、与多种持久层技术
的整合,以及优秀的Web MVC框架等等。可以说,Spring是企业应用开发的“一站式”选择,它贯穿表现层、业务层、持久层。
2.下载:http://projects.spring.io/spring-framework/
当使用Spring框架时,必须使用Spring Core
Container(即Spring容器),它代表了Spring框架的核心机制。Spring Core
Container主要由org.framework.core、org.framework.beans、
org.framework.context、org.framework.expression四个包及其子包组成,主要由Spring
IoC容器支持。
使用Spring框架的Java Web项目,需要将Spring框架的JAR包和commons-logging-1.1.3.jar添加到Web应用的WEB-INF路径下。
3.描述
轻量级:Spring是非侵入性的,基于Spring开发的应用中的对象可以不依赖于Spring的API
依赖注入(DI--dependency injection),控制反转(IOC--Inversion of Control)
面向切面编程(AOP--aspect oriented programming)
容器:Spring是一个容器,因为它可以包含并管理应用对象的生命周期
框架:Spring实现了使用简单的组件配置组合成一个复杂的应用,在Spring中使用xml和java注解组合这些对象
一站式:在IOC和AOP的基础上可以整合各种企业应用的开源框架和优秀的第三方类库
二 使用Spring管理bean
Spring核心容器的理论很简单:Spring核心容器就是一个超级大工厂,所有的对象(包括数据源、Hibernate
SessionFactory等基础性)都会被当成Spring核心容器管理的对象。Spring把容器中的一切对象统称为Bean,Spring对
bean没有任何要求,只要是一个java类,Spring就可以管理该java类,并把它当做Bean处理。
先定义一个简单的类Axe.java
public class Axe {
public String chop(){
return "使用斧子砍柴";
}
}
再定义一个Person类
public class Perosn {
private Axe axe;
public void setAxe(Axe axe){
this.axe=axe;
}
public void useAxe(){
System.out.println("打算去砍柴点火");
System.out.println(axe.chop());
}
}
Perosn对象需要调用Axe对象的方法,这种情形成为依赖。
Spring通过xml配置文件来管理Bean,示例:
在项目的src下建立ApplicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:jaxws="http://cxf.apache.org/jaxws"
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-3.2.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">
<bean id="person" class="mySpring.Person">
<property name="axe" ref="axe"></property>
</bean>
<bean id="axe" class="mySpring.Axe" />
</beans>
配置文件中的<bean>元素默认以反射方式来调用该类无参数的构造器来创建实例,并将该实例作为Spring容器中的Bean.zai
Spring配置文件中配置bean时,class属性的值必须是Bean实现类的完整类名(必须带包名),不能是接口,不能是抽象类(除非有特殊配
置),否则Spring无法使用反射创建该类的实例。
<property>子元素会驱动Spring在底层以反射执行一次setter方法,name属性值会决定执行哪个setter方法,而
value或ref决定执行setter方法的传入参数。一旦bean被创建处理,Spring会立即根据<property>子元素执行
setter方法。也就是说,<bean>元素驱动Spring调用构造器创建对象,<property>子元素驱动
Spring执行setter方法,这两步是先后执行的,中间几乎没有任何间隔。
访问容器中的bean:
public class test {
public static void main(String[] args) {
ApplicationContext ctx=new ClassPathXmlApplicationContext("ApplicationContext.xml");
Perosn p=ctx.getBean("person",Perosn.class);
p.useAxe();
}
}
三 Spring的核心机制--依赖注入
1.在java应用中存在大量A对象调用B对象方法的情形,这种情形被Spring称为依赖,即A对象依赖B对象。Spring框架的核心功能有两个:
Spring容器作为超级大工厂,负责创建、管理所有的java对象,这些java对象被称为bean。
Spring容器通过依赖注入管理容器中Bean之间的依赖关系
2.什么是依赖注入
当某个java对象需要调用另一个java对象的方法时,在传统模式下通常有两种做法:
原始做法:调用者主动创建被依赖对象,即new一个对象,再调用对象的方法。这种做法主要有两个坏处:可扩展性差,当修改了被依赖对象时,还需要修改调用者;职责不清,调用者还需要关心被依赖对象的创建过程,实际上,它只需要调用其中的某些方法即可。
简单工厂模式:调用者找到被依赖对象的工厂,通过工厂去获取被依赖对象,调用其中的方法。这种模式要注意三点:调用者面向被依赖对象的接口编程;被依赖对象的创建交给工厂完成;调用者通过工厂来获得被依赖组件。
使用Spring框架后,调用者只需要被动接受Spring容器为调用者的成员变量赋值即可(配置一个<property>子元
素,Spring就会执行对应的setter方法为调用者的成员变量赋值)。控制反转和依赖注入是同一个行为的两种表述,只是描述的角度不同而已。依赖注
入通常有两种方式:
设值注入:IoC容器使用成员变量的setter方法来注入被依赖对象。
构造注入:IoC容器使用构造器来注入被依赖对象。
3.属性(设值)注入:
属性注入即通过setter方法注入Bean的属性值或依赖的对象,是实际应用中最常用的注入方式
属性注入使用<property>元素,使用name属性指定Bean的属性名称,value属性或<value>子节点指定属性值
构造方法注入:
通过构造方法注入Bean的属性值或依赖的对象,它保证了Bean实例在实例化后就可以使用
构造器注入在<constructor-arg>元素里声明属性,<constructor-arg>中没有name属性
Spring IoC容器有三个基本要点:
(1)应用程序的各组件面向接口编程,从而可以将组件之间的耦合关系提升到接口层次,有利于后期的扩展。
(2)应用程序的各组件不再由程序主动创建,而是由Spring容器负责产生并初始化。
(3)Spring采用配置文件或注解来管理bean的实现类、依赖关系,Spring容器则根据配置文件或注解,利用反射来创建实例,并为之注入依赖关系。
4.构造注入
Car.java
public class Car {
private String brand;
private String corp;
private double price;
private int maxSpeed;
public Car(String brand, String corp, double price) {
super();
this.brand = brand;
this.corp = corp;
this.price = price;
}
@Override
public String toString() {
return "Car [brand=" + brand + ", corp=" + corp + ", price=" + price
+ ", maxSpeed=" + maxSpeed + "]";
}
}ApplicationContext.xml
<!-- 通过构造方法来配置bean的属性 -->
<bean id="car" class="a.Car">
<constructor-arg value="Audi" index="0"></constructor-arg>
<constructor-arg value="ShangHai" index="1"></constructor-arg>
<constructor-arg value="300000" index="2"></constructor-arg>
</bean>
myTest.java:
public class myTest {
public static void main(String[] args) {
ApplicationContext ctx=new ClassPathXmlApplicationContext("a/ApplicationContext.xml");
Car car=ctx.getBean(Car.class);
System.out.println(car);
}
}
构造注入的本质是:驱动Spring在底层以反射方式执行带指定参数的构造器,当执行带参数的构造器时,就可以利用构造器参数对成员变量执行初始化。也就
是说,设值注入与构造器注入的区别在于:设值注入是先通过无参数的构造器创建一个Bean实例,然后调用对应的setter方法注入依赖关系;而构造器注
入则直接调用有参数的构造器,当Bean实例创建完成后,已经完成了依赖关系的注入。
若在car.java中再增加如下的构造器,并希望通过该构造器来配置bean的属性:
public Car(String brand, String corp, int maxSpeed) {
super();
this.brand = brand;
this.corp = corp;
this.maxSpeed = maxSpeed;
}
则相应的bean的属性为:
<bean id="car2" class="a.Car">
<constructor-arg value="BaoMa"></constructor-arg>
<constructor-arg value="ShangHai"></constructor-arg>
<constructor-arg value="240"></constructor-arg>
</bean>
由于有两个构造方法,相应的test.java中只能用id定位到相应的bean:
Car car=(Car)ctx.getBean("car");
System.out.println(car);
Car car2=(Car)ctx.getBean("car2");
System.out.println(car2);
结果为:Car [brand=Audi, corp=ShangHai, price=300000.0, maxSpeed=0]
Car [brand=BaoMa, corp=ShangHai, price=240.0, maxSpeed=0]
此时两个构造器就会发生歧义,仅靠顺序无法解决。可以靠参数列表解决,根据参数个数和参数类型来区分
<bean id="car2" class="a.Car">
<constructor-arg value="BaoMa" type="java.lang.String"></constructor-arg>
<constructor-arg value="ShangHai" type="java.lang.String"></constructor-arg>
<constructor-arg value="240" type="int"></constructor-arg>
</bean>
结果为:Car [brand=Audi, corp=ShangHai, price=300000.0, maxSpeed=0]
Car [brand=BaoMa, corp=ShangHai, price=0.0, maxSpeed=240]
也就是说,使用构造器注入属性值可以指定参数的个数和参数的类型,来区分重载的构造器
5.两种注入方式的对比
设值注入的优点:
对于复杂的依赖关系,如果采用构造注入,会导致构造器过于臃肿,难以阅读。Spring在创建Bean实例时,需要同时实例化其依赖的所有实例,从而导致性能下降,而使用设值注入则能避免这些问题。
在某些成员变量可选的情况下,多参数的构造器更加笨重。
构造注入的优点:
可以在构造器中决定依赖关系的注入顺序,优先依赖的优先注入。如组件中其他依赖关系的注入常常需要依赖于Datasource的注入。
对于依赖关系无须变化的Bean,构造注入更有用处。
建议采用设值注入为主,构造注入为辅的注入策略。
四 Spring容器
1.Spring有两个核心接口:BeanFactory和ApplicationContext。其中,ApplicationContext是
BeanFactory的子接口。它们都可以代表Spring容器,Spring容器是生成Bean实例的工厂,并管理容器中的Bean。由于
ApplicationContext本身就是BeanFactory的子接口,因此ApplicationContext完全可以作为Spring容器
来使用,而且功能更强,也被称为Spring上下文。
2.BeanFactory常用的实现类是DefaultListableBeanFactory
ApplicationContext的常用实现类是:
ClassPathXmlApplicationContext:从类路径下加载配置文件
FileSystemXmlApplicationContext:从文件系统中加载配置文件
AnnotationConfigApplicationContext
如果是在Web应用中使用Spring容器,则通常有XmlWebApplicationContext、AnnotationConfigWebApplicationContext两个实现类。
创建Spring容器的实例时,必须提供Spring容器管理的bean的详细配置信息,通常采用XML配置文件来设置,因此在创建ApplicationContext实例时,应该提供XML配置文件作为参数,XML配置文件通常采用Resource对象传入。
BeanFactory接口包含以下几个基本方法:
boolean containsBean(String name):判断Spring容器是否包含id为name的bean实例
<T> T getBean(Class<T> requiredType):获取Spring容器中属于requiredType类型的、唯一的Bean实例
Object getBean(String name):返回容器id为name的bean实例
<T> T getBean(String name,Class<T> requiredType):返回容器id为name且类型为requiredType的bean实例
Class<?> getType(String name):返回容器中id为name的Bean实例的类型
ApplicationContext除了BeanFactory所支持的全部功能外,它还有如下额外的功能:
(1)默认会预初始化所有的singleton Bean,也可以通过配置取消预初始化;
(2)继承了MessageSource接口,因此可以提供国际化支持;
(3)资源访问,比如访问URL和文件;
(4)事件机制;
(5)同时加载多个配置文件;
(6)以声明式方式启动并创建Spring容器。
当系统创建ApplicationContext容器时,默认会预初始化所有的singleton
Bean。这就意味着,系统前期创建ApplicationContext时将有较大的系统开销,但一旦ApplicationContext初始化完
成,程序后面获取singleton Bean实例时将有较好的性能。
一般情况下,对于一个普通的Bean,如果没有特殊配置,该bean就是singleton Bean,如果不想预初始化该bean,可以在<bean>元素内指定lazy-init="true"