Spring 学习笔记

基于spring-framework-4.3.6.RELEASE
相关代码存放于码云上的hellospring4工程下:
https://git.oschina.net/laideju/hellospring4 以及 https://git.oschina.net/laideju/spring-struts


一、搭建Spring开发环境

1. 拷贝相关 jar 包。

Spring的架构如下图所示:

所以,基本的Spring开发环境搭建包括,添加 spring-framework-4.3.6.RELEASElibs 目录下的

  • spring-beans-4.3.6.RELEASE.jar
  • spring-context-4.3.6.RELEASE.jar
  • spring-core-4.3.6.RELEASE.jar
  • spring-expression-4.3.6.RELEASE.jar
  • commons-logging-1.2.jar

到Spring工程的Classpath下。

2. 配置

一个典型的 Spring 项目需要创建一个或多个 Bean 配置文件,这些配置文件用于在 Spring IOC 容器里配置 Bean。Bean 的配置文件可以放在 Classpath 下,也可以放在其它目录下。

 

二、配置Bean

配置形式:基于 XML 文件的方式;基于注解的方式。下文主要是基于 XML 的方式来配置Bean。

<bean id="helloWorld" class="hellospring.stepone.HelloWorld">
  <property name="name" value="laideju"></property>
</bean>

其中 id 指明了 Bean 的名称:在 IOC 容器中必须是唯一的;若 id 没有指定,Spring 自动将类的完全限定名作为 Bean 的名字;id 可以指定多个名字,名字之间可用英文的逗号、分号、或空格分隔。

三、Spring 容器

在 Spring IOC 容器读取 Bean 配置创建 Bean 实例之前,必须对它进行实例化。只有在容器实例化后,才可以从 IOC 容器里获取 Bean 实例并使用。

Spring 提供了两种类型的 IOC 容器实现:

  • BeanFactory:IOC 容器的基本实现。
  • ApplicationContext:提供了更多的高级特性。 是 BeanFactory 的子接口。

BeanFactory 是 Spring 框架的基础设施,面向 Spring 本身;ApplicationContext 面向使用 Spring 框架的开发者,几乎所有的应用场合都直接使用 ApplicationContext 而非底层的BeanFactory。无论使用何种方式,配置文件是相同的。

1. ApplicationContext

ApplicationContext 的主要实现类:

①. ClassPathXmlApplicationContext,从 类路径下加载配置文件;

②. FileSystemXmlApplicationContext,从文件系统中加载配置文件。

ConfigurableApplicationContext 扩展于 ApplicationContext,新增加两个主要方法:refresh() 和 close(), 让 ApplicationContext 具有启动、刷新和关闭上下文的能力。

ApplicationContext 在初始化上下文时就实例化所有单例的 Bean。

WebApplicationContext 是专门为 WEB 应用而准备的,它允许从相对于 WEB 根目录的路径中完成初始化工作。

2. 从 IOC 容器中获取 Bean

调用 ApplicationContext 的 getBean() 方法。

3. 依赖注入的方式

Spring 支持 3 种依赖注入的方式:属性注入、构造器注入、工厂方法注入(很少使用,不推荐)。

3.1. 属性注入

属性注入即通过 setter 方法注入Bean 的属性值或依赖的对象。属性注入使用 <property> 元素,使用 name 属性指定 Bean 的属性名称,value 属性或 <value> 子节点指定属性值。属性注入是实际应用中最常用的注入方式。

3.2. 构造器注入

通过构造方法注入Bean 的属性值或依赖的对象,它保证了 Bean 实例在实例化后就可以使用。

构造器注入在 <constructor-arg> 元素里声明属性,<constructor-arg> 中没有 name 属性。 需要注意的是,在 Bean 中必须有对应的构造器。

public class Car {
private String company;
private String brand;
private int maxSpeed;
private double price;

@Override
public String toString() {
return "Car [company=" + company + ", brand=" + brand + ", maxSpeed=" + maxSpeed + ", price=" + price + "]";
}

public Car(String brand) {
// 给对应的属性赋值
}
public Car(String brand, String company, double price) {
// 给对应的属性赋值
}

public Car(String brand, String company, int maxSpeed) {
// 给对应的属性赋值
}    
}
<bean id="car1" class="hellospring.stepone.Car">
  <constructor-arg value="Audi" />
</bean>

上面的Bean配置中将调用 Car 的只有一个参数的构造器来实例化 car1。若一个 bean 有多个构造器,可以根据 <constructor-arg>的 index(按索引匹配入参) 和 type(按类型匹配入参) 属性进行更加精确的定位。例如下面的配置将匹配到构造器:Car(String, String, double) 

<bean id="car2" class="hellospring.stepone.Car">
  <constructor-arg value="上海公司" index="1" />
  <constructor-arg value="大众" index="0" />
  <constructor-arg value="230000" type="double" />
</bean>

而下面的配置将匹配到构造器: Car(String, String, int)

<bean id="car3" class="hellospring.stepone.Car">
  <constructor-arg value="上海公司" index="1" />
  <constructor-arg value="大众" index="0" />
  <constructor-arg value="240" index="2" />
</bean>

3.3. 注入配置的一些细节

(1). 字面值

可用字符串表示的值称之为字面值。基本数据类型及其封装类、String 等类型都可以采取字面值注入的方式。字面值可以通过 <value> 元素标签或 value 属性进行注入,若字面值中包含特殊字符,可以使用 <![CDATA[]]> 把字面值包裹起来。

<bean id="car4" class="hellospring.stepone.Car">
  <constructor-arg index="2">
    <!-- 属性值可以通过value子节点进行设置 -->
    <value>240</value>
  </constructor-arg>
  <constructor-arg index="1">
    <!-- 若字面值中包含特殊字符,则可以使用 DCDATA 来进行赋值 -->
    <value><![CDATA[<武汉公司>]]></value>
  </constructor-arg>
  <constructor-arg value="大众" index="0" />
</bean>

(2). 引用其它 Bean、内部Bean

在 Bean 的配置文件中,可以通过 <ref> 标签或 ref 属性为 Bean 的属性或构造器参数指定对 Bean 的引用。

public class Person {
private String name;
private int age;    
private Car car;
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", car=" + car + "]";
}
// 默认构造器
public Person() {}

public Person(String name, int age, Car car) {
// 给对应的属性赋值    
}
}
<bean id="person1" class="hellospring.stepone.Person">
<property name="name" value="Tom" />
<property name="age" value="26" />

<!-- 通过 ref 属性值指定当前属性引用的 bean -->
<!-- <property name="car" ref="car4"/> -->

<!-- 或者也可以通过ref子标签来指明所引用的bean -->    
<property name="car">
<ref bean="car4"/>
</property>    
</bean>

也可以在属性(<property>标签)或构造器(<constructor-arg>标签)里包含 Bean 的声明,这样的 Bean 称为内部 Bean。内部 Bean 仅供特定的属性或构造器使用,不能在这之外引用,所以不需为其设置 id 或 name 属性(即使设置了也没什么作用)。

<bean id="person2" class="hellospring.stepone.Person">
  <constructor-arg value="laideju"/>
  <constructor-arg value="27"/>
  <constructor-arg>
    <bean class="hellospring.stepone.Car">
      <constructor-arg value="哈弗" />
      <constructor-arg value="长安汽车有限公司" />
      <constructor-arg value="120000" type="double" />
    </bean>
  </constructor-arg>
</bean>

(3). null 值

可以使用Spring专用的 <null/> 标签为 Bean 的字符串或其它对象类型的属性注入 null 值。

<bean id="person2" class="hellospring.stepone.Person">
<constructor-arg value="laideju"/>
<constructor-arg value="27"/>
<constructor-arg>    
<!-- 测试赋值为 null -->
<null />
</constructor-arg>
</bean>

(4). 级联属性

Spring支持级联属性(即属性的属性),但需要注意的是,需要先为上级属性赋值才能为级联属性赋值,否则会报空引用异常(而Struts2则不会,它会自动创建上级属性)。

<bean id="person2" class="hellospring.stepone.Person">
<constructor-arg value="laideju"/>
<constructor-arg value="27"/>
<constructor-arg>
<bean id="car3" class="hellospring.stepone.Car">
<constructor-arg value="哈弗" />
<constructor-arg value="长安汽车有限公司" />
<constructor-arg value="120000" type="double" />
</bean>    
</constructor-arg>
<!-- 测试级联属性,需要先为car属性赋值,再才能为car的maxSpeed属性赋值 -->
<property name="car.maxSpeed" value="230"></property>    
</bean>

(5). 集合属性

在 Spring中可以通过一组内置的 xml 标签(例如: <list>, <set> 或 <map>)来配置集合属性。

<list>标签

配置 java.util.List 类型的属性,需要指定 <list> 标签,在标签里包含一些子标签。这些子标签可以:

  • 通过 <value> 指定简单的常量值,通过 <ref> 指定对其他 Bean 的引用;
  • 通过<bean> 指定内置 Bean 定义;
  • 通过 <null/> 指定空元素。 甚至可以内嵌其他集合。

数组的定义和 List 一样,都使用 <list>。配置 java.util.Set 需要使用 <set> 标签,定义元素的方法与 List 一样。

<bean id="person3" class="hellospring.beancollection.Person">
<property name="name" value="lijingyu"></property>
<property name="age" value="26"></property>
<!-- cars是Person类中的类型为 List的属性 -->
<property name="cars">
<list>
<ref bean="car1" />
<bean class="hellospring.stepone.Car">
<constructor-arg value="神牛25" />
<constructor-arg value="中国铁机" />
<constructor-arg value="120" type="int" />
</bean>
<null/>
</list>
</property>
</bean>

<map>标签

Java.util.Map 通过 <map> 标签定义,<map> 标签里可以使用多个 <entry> 作为子标签。

每个条目包含一个键和一个值。必须在 <key> 标签里定义键。因为键和值的类型没有限制,所以可以自由地为它们指定 <value>,<ref>,<bean> 或 <null> 元素。

可以将 Map 的键和值作为 <entry> 的属性定义:简单常量使用 key 和 value 来定义;Bean 引用通过 key-ref 和 value-ref 属性定义。

<bean id="personWithMap" class="hellospring.beancollection.PersonWithMap">
<property name="name" value="ldj007"></property>
<property name="age" value="27"></property>
<property name="cars">
<map>
<entry key="AA" value-ref="car" />
<entry key="BB">
<bean class="hellospring.stepone.Car">
<property name="company" value="CN-01" />
<property name="brand" value="BRAND-01" />
<property name="price" value="120000" />
<property name="maxSpeed" value="120" />
</bean>
</entry>    
</map>
</property>
</bean>

PersonWithMap的定义如下:

public class PersonWithMap {
private String name;
private int age;    
private Map<String, Car> cars;

// getters, setters and override toString()
}

<props>标签

使用 <props> 定义 java.util.Properties,该标签使用多个 <prop> 作为子标签。每个 <prop> 标签必须定义 key 属性。

<bean id="dataSource" class="hellospring.beancollection.DataSource">
<property name="properties">
<props>
<prop key="user">root</prop>
<prop key="password">root</prop>
<prop key="jdbcUrl">jdbc:mysql:///test</prop>
<prop key="driverClass">com.mysql.jdbc.Driver</prop>
</props>
</property>
</bean>

DataSource的定义如下:

public class DataSource {
private Properties properties;

// getters, setters and override toString()
}

(6). 使用 utility scheme 定义集合

使用基本的集合标签定义集合时,不能将集合作为独立的 Bean 定义,导致其他 Bean 无法引用该集合,所以无法在不同 Bean 之间共享集合。

可以使用 util schema 里的集合标签定义独立的集合 Bean。 需要注意的是,必须在 <beans> 根元素里添加 util schema 定义:xmlns:util="http://www.springframework.org/schema/util"

定义独立集合Bean并引用的实例如下:

<!-- 定义独立的集合Bean,以供其他 Bean 引用 -->
<util:list id="common-cars">
<ref bean="car1"/>
<bean class="hellospring.stepone.Car">
<property name="company" value="AOA11" />
<property name="brand" value="POLO" />
<property name="price" value="340000" />
<property name="maxSpeed" value="300" />
</bean>
</util:list>
<bean id="person4" class="hellospring.beancollection.Person">
<property name="name" value="laijibo" />
<property name="age" value="28"/>
<!-- 引用独立定义的公共集合Bean -->
<property name="cars" ref="common-cars" />
</bean>

(7). 使用 p 命名空间

为了简化 XML 文件的配置,越来越多的 XML 文件采用属性而非子元素配置信息。Spring 从 2.5 版本开始引入了一个新的 p 命名空间,可以通过 <bean> 元素属性的方式配置 Bean 的属性。

需要注意的是,必须在 <beans> 根元素里添加 util schema 定义:xmlns:p="http://www.springframework.org/schema/p"。使用p命名空间的实例如下:

<!-- 使用p命名空间简化配置Bean的属性 -->
<bean id="person5" class="hellospring.beancollection.Person"
p:name="laijibo2"
p:age="29"
p:cars-ref="common-cars"
/>

3.4. Bean 自动装配

Spring IOC 容器可以自动装配 Bean,即只声明 bean,而把 bean 之间的关系交给 IOC 容器来完成。需要做的仅仅是在 <bean> 的 autowire 属性里指定自动装配的模式:

  • byType,根据类型进行自动装配,但要求 IOC 容器中只有一个类型对应的 bean,若有多个则无法完成自动装配;
  • byName,若 Bean 的某个属性名和某一个 <bean> 的 id 值一致,即可完成自动装配,若没有 id 一致的,则无法完成自动装配;
  • constructor,通过构造器自动装配,当 Bean 中存在多个构造器时,此种自动装配方式将会很复杂(不推荐使用)。// 自动装配Bean
public class Address {
private String city;
private String street;

// getters, setters and override toString()
}
public class Car {
private String brand;
private int price;

// getters, setters and override toString()
}
public class Person {
private String name;
private Address address;
private Car car;

// getters, setters and override toString()
}
<!-- <bean id="address" class="hellospring.autowire.Address" p:city="Wuhan" p:street="XiongChu" /> -->
<bean id="address1" class="hellospring.autowire.Address" p:city="Wuhan" p:street="XiongChu" />
<bean id="car" class="hellospring.autowire.Car" p:brand="Audi" p:price="300000" />
<bean id="person" class="hellospring.autowire.Person" p:name="laideju" autowire="byName"/>
<!-- <bean id="person" class="hellospring.autowire.Person" p:name="laideju" autowire="byType"/> -->

自动装配的缺点:

①. 在 Bean 配置文件里设置 autowire 属性进行自动装配将会装配 Bean 的所有属性。然而,若只希望装配个别属性时,autowire 属性就不够灵活了。

②. autowire 属性要么根据类型自动装配,要么根据名称自动装配,不能两者兼而有之。

③. 在使用 XML 配置时,自动转配用的不多,但在基于注解的配置时,自动装配使用的较多。

3.5. bean之间的关系:继承、依赖

继承 Bean 配置

Spring 允许继承 <bean> 的配置,被继承的 <bean> 称为父 <bean>,进行继承的 <bean> 称为子 <bean>。

以下涉及到的 JavaBean 参考 3.4. Bean 自动装配

(1). 通过parent属性实现<bean>之间的继承,子 <bean> 从父 <bean> 中继承配置,包括父 <bean> 的属性配置。

<bean id="address" class="hellospring.autowire.Address" p:city="WuHan" p:street="XiongChu" />
<!-- 通过设置 parent 属性为当前bean指明其需要继承的父bean,继承之后当前bean将获得父bean的所有可继承的属性 -->
<bean id="address2" class="hellospring.autowire.Address" parent="address" />

(2). 子 <bean> 也可以覆盖从父 <bean> 继承过来的配置。

<!-- 当前bean的street配置将被覆盖 -->
<bean id="address2" class="hellospring.autowire.Address" p:street="GuanShan" parent="address" />

(3). 父 <bean> 可以作为配置模板,也可以作为 <bean> 实例。若只想把父 <bean> 作为模板,可以设置 <bean> 的 abstract 属性为 true, IOC容器将不会实例化这样的 <bean>(称之为抽象bean)。

<bean id="address" class="hellospring.autowire.Address" p:city="WuHan" p:street="XiongChu" abstract="true" />

试图引用id=address的Bean将会抛出异常:

(4). 可以忽略父 <bean> 的 class 属性,让子 <bean> 指定自己的类,而共享相同的属性配置,但此时 abstract 必须设为 true。

<!-- 抽象bean可以不指定其class属性 -->
<bean id="address" p:city="WuHan" p:street="XiongChu" abstract="true" />
<bean id="address2" class="hellospring.autowire.Address" parent="address" />

(5). 并不是 <bean> 元素里的所有属性都会被继承。比如: autowire,abstract 等不会被继承。

依赖 Bean 配置

Spring 允许用户通过 depends-on 属性设定 <bean> 前置依赖的<bean>,前置依赖的 <bean> 会在当前 <bean> 实例化之前创建好。如果前置依赖于多个 <bean>,则可以通过逗号、空格方式配置 依赖 bean 的名称。

<bean id="person" class="hellospring.autowire.Person" p:name="ldj" p:address-ref="address2" 
depends-on="car1"/>    
<bean id="car1" class="hellospring.autowire.Car" p:brand="ABC" p:price="120000"/>

注意:虽然在<bean>配置中指明了person依赖于car1,但是这并非等同于 person.car=car1,而是通知 IOC 容器在创建 person 前先创建 car :


3.6. bean的作用域

使用<bean>的scope属性可以设置 Bean实例的作用域。

  • singleton:是scope的默认值,表示在整个IOC容器的生命周期中只会创建一个该Bean的实例,而且是在IOC初始化时即创建该Bean的实例。
  • prototype:表示在每次请求(例如调用容器的getBean方法)时才创建一个该Bean的实例。
  • request:每次http请求都会创建一个新的Bean实例,该作用域仅适用于WebApplicationContext环境。
  • session:同一个http session共享一个Bean实例,不同的http session使用不同的Bean,该作用域仅适用于WebApplicationContext环境。

3.7. 使用外部属性文件

在配置文件里配置 Bean 时,有时需要在 Bean 的配置里混入系统部署的细节信息(例如文件路径,数据源配置信息等)。而这些部署细节实际上需要和 Bean 配置相分离。Spring 提供了一个 PropertyPlaceholderConfigurer 的 BeanFactory 后置处理器,这个处理器允许用户将 Bean 配置的部分内容外移到属性文件中。可以在 Bean 配置文件里使用形式为 ${var} 的变量,PropertyPlaceholderConfigurer 从属性文件里加载属性,并使用这些属性来替换变量。Spring 还允许在属性文件中使用 ${propName},以实现属性之间的相互引用。

使用步骤:

定义所需的properties文件:

user=root
password=ldj
jdbcUrl=jdbc://mysql///test
driverClass=com.mysql.jdbc.Driver

在bean配置文件中引入context命名空间:

xmlns:context="http://www.springframework.org/schema/context"

在bean配置文件中进行配置:

<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="jdbcUrl" value="${jdbcUrl}"></property>
<property name="driverClass" value="${driverClass}"></property>
</bean>

3.8. 使用SpEL

Spring 表达式语言(简称SpEL),是一个支持运行时查询和操作对象图的表达式语言。语法类似于 EL,SpEL 使用 #{…} 作为定界符,所有在大框号中的字符都将被认为是 SpEL。SpEL 为 <bean> 的属性进行动态赋值提供了便利。

本节讨论中将用到的JavaBean定义如下:

public class Address {
private String city;
private String street;
// getters, setters, toString method and default contructor
}
public class Car {
private String brand;
private int price;
private double tyrePerimeter;

// getters, setters, toString method and default contructor
}
public class Person {
private String name;    
private Car car;
private String city;

// 当car.price > 30w 时为金领,否则为白领
private String info; 

// getters, setters, toString method and default contructor
}

(1). SpEL 字面量的表示

整数:<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}"/>

(2). 引用 Bean、属性和方法

为属性赋值:

<bean id="address" class="hellospring.spel.Address">
<property name="street" value="雄楚大道" />
<!-- 使用SpEL为属性赋一个字面值 -->
<property name="city" value="#{'武汉市'}" />
</bean>

调用静态方法或静态属性:通过 T() 调用一个类的静态方法,它将返回一个 Class Object,然后再调用相应的方法或属性: 

<bean id="car" class="hellospring.spel.Car">
<property name="brand" value="Audi"/>
<property name="price" value="450000" />
<!-- 使用 SpEL 引用类的静态属性 -->
<property name="tyrePerimeter" value="#{T(java.lang.Math).PI * 80}" />
</bean>

引用 Bean、属性和方法:

<bean id="person" class="hellospring.spel.Person">
<property name="name" value="Tom" />
<!-- 使用SpEL引用其它Bean -->
<property name="car" value="#{car}" />
<!-- 使用SpEL引用其它Bean的属性 -->
<property name="city" value="#{address.city}" />
<!-- 在SpEL中使用运算符 -->
<property name="info" value="#{car.price > 300000 ? '金领' : '白领'}"></property>
</bean>

(3). SpEL支持的运算符号

算数运算符:+, -, *, /, %, ^:

加号还可以用作字符串连接:

比较运算符: <, >, ==, <=, >=, lt, gt, eq, le, ge

逻辑运算符号: and, or, not, |

if-else 运算符:?: (ternary), ?: (Elvis)

if-else 的变体

正则表达式:matches


3.9. IOC 容器中 Bean 的生命周期

Spring IOC 容器可以管理 Bean 的生命周期,在<bean>里通过设置 init-method 和 destroy-method 属性为当前Bean指定初始化和销毁方法。

示例如下:

// JavaBean
public class Cat {
private String name;

public void setName(String name) {
System.out.println("name setter called...");
this.name = name;
}

public String getName() {
return name;
}

public Cat() {
System.out.println("ctor called...");
}

public String toString() {
return "Cat [name=" + name + "]";
}
public void init2(){
System.out.println("init-method called...");
}

public void destroy1(){
System.out.println("destroy-method called...");
}
}
<bean id="cat" class="hellospring.lifecycle.Cat" init-method="init2" destroy-method="destroy1">
<property name="name" value="Tom cat"></property>
</bean>

注意:这里的初始化和销毁方法不是JavaBean本身的,而是 IOC 容器在管理该Bean实例时用到的。例如容器在对外提供当前Bean实例之前会先调用为该Bean设置的init方法进行初始化,或者容器在关闭前调用为该Bean设置的destroy方法进行销毁。

在引用配置的 Cat Bean 后,输出效果如下:

public static void main(String[] args) {

ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans-lifecycle.xml");
Cat cat = (Cat) ctx.getBean("cat");

// 关闭 IOC 容器
ctx.close();
}
ctor called...
name setter called...
init-method called...
destroy-method called...

另一方面,Spring 还提供了Bean的后置处理器机制,允许为Bean配置的初始化方法在被调用的前后进行额外的处理。

Bean 后置处理器会对 IOC 容器里的所有 Bean 实例逐一处理,而非单一实例。其典型应用是:检查 Bean 属性的正确性或根据特定的标准更改 Bean 的属性。

通过实现 org.springframework.beans.factory.config.BeanPostProcessor 接口可以定义自己的Bean后置处理器。在初始化方法被调用前后,Spring 将把每个 Bean 实例分别传递给上述接口的以下两个方法:

  • Object postProcessAfterInitialization(Object bean, String beanName); // init-method 之前被调用
  • Object postProcessBeforeInitialization(Object bean, String beanName); // init-method 之后被调用

在上述两个方法中,形参 bean 是指当前被前置处理器处理的Bean实例本身;beanName是指当前被处理的Bean在<bean>配置中的id属性的值。这两个方法的返回值是实际上返回给请求Bean的方法(例如getBean()方法)的值,可以在这两个方法中修改返回的Bean,甚至返回一个新的Bean。后置处理器的配置:类似普通<bean>的配置,只是不需要id属性,因为 IOC 容器会自行识别。

一个应用自定义后置处理器的示例如下:

// 自定义 Bean 前置处理器
public class MyPostBeanProcessor implements BeanPostProcessor {

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessAfterInitialization: " + beanName + ", " + bean);
if("cat".equals(beanName)){
Cat cat = (Cat) bean;
cat.setName("LDJ");
}
return bean;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessBeforeInitialization: " + beanName + ", " + bean);
return bean;
}
}
<!-- 配置自定义的Bean后置处理器 -->
<bean class="hellospring.lifecycle.MyPostBeanProcessor"/>
// 引用配置了前置处理器的Bean实例
public static void main(String[] args) {    
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans-lifecycle.xml");
Cat cat = (Cat) ctx.getBean("cat");
System.out.println(cat);
// 关闭 IOC 容器
ctx.close();
}

输出结果如下:

总的来说,IOC 容器对于Bean的生命周期管理的过程:

  • 通过构造器或工厂方法创建 Bean 实例;
  • 为 Bean 的属性设置值和对其它 Bean 的引用;
  • 将 Bean 实例传递给 Bean 后置处理器的 postProcessBeforeInitialization 方法;
  • 调用 Bean 的初始化方法;
  • 将 Bean 实例传递给 Bean 后置处理器的 postProcessAfterInitialization方法;
  • Bean 可以使用了;
  • 当容器关闭时, 调用 Bean 的销毁方法。

3.10. 再论Bean的配置方式

前文所述中,对于Bean的配置,是通过全类名(<bean>中的class属性)基于反射的方式进行的。下面将学习通过工厂方法(静态工厂方法和实例工厂方法)、FactoryBean等方式来配置Bean。

用到的JavaBean:

public class Car {
private String brand;
private int price;

// getters setters and toString method

public Car() {}
public Car(String brand, int price) {
this.brand = brand;
this.price = price;
}
}

3.10.1 静态工厂方法

所谓“静态工厂方法”是指,通过调用某一个类的静态方法即可返回一个Bean实例的方法。例如下面的 StaticCarFactory.getCar():

public class StaticCarFactory {
private static Map<String, Car> cars = new HashMap<>();
static{
cars.put("Audi", new Car("Audi", 300000));
cars.put("Ford", new Car("Ford", 330000));    
}    
// 静态工厂方法
public static Car getCar(String name){
return cars.get(name);
}    
}

要声明通过静态方法创建的Bean,需要在<bean>的class属性里指定拥有该工厂方法的类,同时在factory-method属性里指定工厂方法的名称,最后使用<constrctor-arg>子标签为该静态工厂方法传递所需参数。

<bean id="car1" class="hellospring.factory.StaticCarFactory" factory-method="getCar">
<constructor-arg value="Ford" />
</bean>

3.10.2 实例工厂方法

所谓“实例工厂方法”,是指先实例化工厂本身,在通过工厂实例的相关方法来返回Bean实例的方法。

public class InstanceCarFactory {
private Map<String, Car> cars;

public InstanceCarFactory(){
cars = new HashMap<>();
cars.put("ford", new Car("Ford", 340000));
cars.put("audi", new Car("Audi", 360000));
}

// 实例工厂方法
public Car getCar(String name){
return cars.get(name);
}
}

要声明通过实例工厂方法创建的Bean,需要:

(1). 先声明一个工厂实例的Bean;

(2). 再声明需要通过实例工厂产生的Bean,包括在 bean 的 factory-bean 属性里指定拥有该工厂方法的Bean,然后在 factory-method 属性里指定该实例工厂方法的名称,最后使用<construtor-arg>子标签为实例工厂方法传递方法参数。

<bean id="instanceCarFactory" class="hellospring.factory.InstanceCarFactory" />
<bean id="car2" factory-bean="instanceCarFactory" factory-method="getCar">
<constructor-arg value="audi"/>
</bean>

3.10.3 实现FactoryBean接口在Spring IOC容器中配置Bean

Spring中有两种类型的Bean,一种是普通Bean,另一种是工厂Bean,即FactoryBean。工厂Bean跟普通Bean不同,其返回的对象不是指定类的一个实例,而是该工厂Bean的getObject方法所返回的对象。

import org.springframework.beans.factory.FactoryBean;
public class MyBeanFactory implements FactoryBean<Car>{

private String brand;

public String getBrand() {
return brand;
}

public void setBrand(String brand) {
this.brand = brand;
}    

// 返回Bean的对象
@Override
public Car getObject() throws Exception {
Car car = new Car();
car.setBrand(brand);
return car;
}
// 返回的Bean的对象的类型
@Override
public Class<?> getObjectType() {
return Car.class;
}
// 决定返回的Bean实例是否是单例的
@Override
public boolean isSingleton() {    
return true;
}
}

通过FactoryBean来配置Bean实例,其中class属性指定某个实现了FactoryBean接口的类,property子标签配置该类的属性(而不是待返回的Bean实例自身的属性)。

<bean id="car1" class="hellospring.beanfactory.MyBeanFactory">
<!-- 注意,这里设置的property是MyBeanFactory的而非Car自身的 -->
<property name="brand" value="BMW"></property>
</bean>

3.11 基于注解的方式配置Bean

1. 在classpath中扫描组件

组件扫描(component scanning):Spring能够从classpath下自动扫描,侦测和实例化具有特定注解的组件(类)。特定组件包括:

  • @Component,基本注解,标识了一个受Spring管理的组件
  • @Repository,标识持久层组件
  • @Service,标识服务层(业务层)组件
  • @Controller,标识表现层组件

注意:以上四个注解其实可以混用,即同一个组件既可以用@Service标注也可以用@Repository标注,因为Spring并不能识别出某个组件是否真的是某个层面的。

import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;
@Component
public class TestObject {
// empty class
}
public interface UserRespository {
void save();
}
@Repository("userRepository")
public class UserRespositoryImpl implements UserRespository {
@Override
public void save() {
System.out.println("UserRespository save...");
}
}
@Controller
public class UserController {
public void execute(){
System.out.println("UserController execute...");
}
}
@Service
public class UserService {
public void add(){
System.out.println("UserService add...");
}
}

当在组件类上使用了特定的注解之后,还需要在Spring的配置文件中声明<context:component-scan>(注意要导入 aop 对应的jar包)。该配置节中base-package属性指定一个需要扫描的基类包,Spring容器将会扫描这个基类包里及其子包中的所有类(当需要扫描多个包时,可以使用逗号分隔)。

<!-- 配置自动扫描的包,需要加入 aop 对应的 jar 包 -->
<context:component-scan base-package="hellospring.annotation"></context:component-scan>

对于扫描到的组件,Spring 有默认的命名策略:使用非限定类名且第一个字母小写。也可以在注解中通过value属性值标识组件的名称。

public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans-annotation.xml");

TestObject testObject = (TestObject) ctx.getBean("testObject");
System.out.println(testObject);

UserController userController = (UserController) ctx.getBean("userController");
System.out.println(userController);

UserRespository useRepository = (UserRespository) ctx.getBean("userRepository");
System.out.println(useRepository);

UserService userService = (UserService) ctx.getBean("userService");
System.out.println(userService);
}

如果仅希望扫描特定的类而非基包下的所有类,可使用 resource-pattern 属性过滤特定的类,示例:

<!-- 如下配置将只会扫描 hellospring.annotation.controller 包下的所有类 -->
<context:component-scan base-package="hellospring.annotation" 
resource-pattern="controller/*.class">
</context:component-scan>

<context:component-scan>还可以包含若干个以下的子节点:<context:include-filter>子节点表示要包含的目标类;<context:exclude-filter>子节点表示要排除在外的目标类。
<context:component-scan base-package="hellospring.annotation">
<context:exclude-filter type="annotation" 
expression="org.springframework.stereotype.Repository"/>    
</context:component-scan>
<context:component-scan base-package="hellospring.annotation" use-default-filters="false">
<!-- 指定要包含的目标类,需指定 <context:component-scan> 的 use-default-filters=false -->
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Repository"/>    
</context:component-scan>

<context:include-filter>和<context:exclude-filter>子节点支持多种类型的过滤表达式:

类别 示例 说明
annotation  hellospring.XxxAnnotation 所有标注了XxxAnnotation的类。该类型采用目标类是否标注了某个注解进行过滤。
assignable  hellospring.XxxService 所有继承或扩展XxxService的类。该类型采用目标类是否继承或扩展某个特定类进行过滤。
aspectj  hellospring.*Service+  所有类名以Service结束的类及继承或扩展它们的类。该类型采用AspectJ表达式进行过滤。
regex  hellospring.xxx.anno..* 所有hellospring.xxx.anno包下的类。该类型采用正则表达式根据类的类名进行过滤。
custom  hellospring.XxxTypeFilter 采用XxxTypeFilter通过代码的方式定义过滤规则。该类必须实现org.springframework.core.type.filter.TypeFilter接口

注:上表中蓝色标注的,是最常用的两种类型。

2. 组件装配

<context:component-scan>元素还会自动注册AutowiredAnnotationBeanPostProcessor实例,该实例可以自动装配具有@Autowired、@Resource、@Inject注解的属性。

2.1 使用@Autowired自动装配Bean

@Autowired注解自动装配具有兼容类型的单个Bean属性。
(1). 构造器,普通字段(即使是非public),一切具有参数的方法都可以应用@Autowired注解。

@Controller
public class UserController {

//    @Autowired
private UserService userService;

@Autowired
public void confUserService(UserService userService) {
this.userService = userService;
}
public void execute(){
System.out.println("UserController execute...");
userService.add();
}
}

(2). 默认情况下,所有使用@Autowired注解的属性都需要被设置。当Spring找不到匹配的Bean装配属性时,会抛出异常,若某一属性允许不被设置,可以设置@Autowired注解的required属性为false。

(3). 默认情况下,当 IOC 容器里存在多个类型兼容的Bean时,通过类型的自动装配将无法工作。此时可以在@Qualifier注解里提供Bean的名称。Spring允许对方法的入参标注@Qualifiter以指定注入Bean的名称。

@Repository
public class UserJdbcRepositoryImpl implements UserRespository {
@Override
public void save() {
System.out.println("UserJdbcRepositoryImpl save...");
}
}
@Service
public class UserService {
@Autowired
//    @Qualifier("userRespositoryImpl")
@Qualifier("userJdbcRepositoryImpl")
private UserRespository userRepository;
public void add(){
System.out.println("UserService add...");
userRepository.save();
}
}

(4). @Autowired注解也可以应用在数组类型的属性上,此时Spring将会把所有匹配的Bean进行自动装配。

(5). @Autowired注解也可以应用在集合属性上,此时Spring读取该集合的类型信息,然后自动装配所有与之兼容的Bean。

(6). @Autowired注解用在java.util.Map上时,若该Map的键为String,那么Spring将自动装配与之Map值类型兼容的Bean,此时Bean的名称作为键值。

2.2 使用@Resource或@Inject自动装配Bean

Spring还支持@Resource和@Inject注解,这两个注解和@Autowired注解的功用类似。

(1). @Resource注解要求提供一个Bean名称的属性,若该属性为空,则自动采用标注处的变量或方法名作为Bean的名称。

(2). @Inject和@Autowired注解一样也是按类型匹配注入的Bean,但没有reqired属性。

建议使用@Autowired注解。

3.12 泛型依赖注入

Spring 4.x 中可以为子类注入子类对应的泛型类型的成员变量的引用。

public class BaseService<T> {
// 在此处指明自动装配,以使得子类能继承得到
@Autowired
protected BaseRepository<T> repository;

public void add(){
System.out.println("BaseService add...");
System.out.println(repository);    
}
}
public class BaseRepository<T> {
public void save(){
System.out.println("BaseRepository save...");
}
}
public class User {
// nothing more...
}
@Service
public class UserService extends BaseService<User>{
// nothing more...
}
@Repository
public class UserRepository extends BaseRepository<User>{
// nothing more...
}
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans-generic-di.xml");
UserService userService = (UserService) ctx.getBean("userService");
userService.add();
}

beans-generic-di.xml中的配置如下:(仅仅是指明了自动装配)

<context:component-scan base-package="hellospring.generic.di"></context:component-scan>

3.13 整合多个配置文件

Spring允许通过<import>将多个配置文件引入到一个文件中,进行配置文件的集成。这样在启动Spring容器时,仅需要指定这个合并好的配置文件即可。

<import>标签的resource属性支持Spring的标准的路径资源,如下表所示:

地址前缀 示例 对应资源类型
classpath:  classpath:spring-mvc.xml  从类路径下加载资源,classpath:和classpath:/是等价的
file:  file:/conf/security/spring-shiro.xml  从文件系统目录中加载资源,可采用绝对或相对路径
http://  http://www.xxx.com/resource/beans.xml 从WEB服务器中加载资源
ftp:// ftp://www.xxx.com/resource/beans.xml  从FTP服务器中加载资源




四、Spring AOP

1. 使用动态代理实现 AOP 编程思想

假设有如下的Java类

现在为了实现如下两个功能:

  需求1-日志,在程序执行期间追踪正在发生的活动;

  需求2-验证,希望计算器只能处理正数的运算。

需要对代码做修改,如下所示:

如此一来,会多出如下的问题。

代码混乱:越来越多的非业务需求(日志和验证等)加入后,原有的业务方法急剧膨胀。每个方法在处理核心逻辑的同时还必须兼顾其他多个关注点。 

代码分散:以日志需求为例,只是为了满足这个单一需求,就不得不在多个模块(方法)里多次重复相同的日志代码。如果日志需求发生变化,必须修改所有模块。

为了解决上面的问题,可以使用动态代理(设计模式)。其原理是,使用一个代理将对象包装起来,然后用该代理对象取代原始对象,任何对原始对象的调用都要通过代理,代理对象决定是否以及何时将方法调用转到原始对象上。

具体实现和测试代码如下。

public interface ArithmeticCalculator {
int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j);
}
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
@Override
public int add(int i, int j) {
return i+j;
}

...
}
public class ArithmeticCalculatorLoggingProxy {

// 要代理的对象
private ArithmeticCalculator target;

public ArithmeticCalculatorLoggingProxy(ArithmeticCalculator target) {
this.target = target;
}

public ArithmeticCalculator getLoggingProxy(){
ArithmeticCalculator proxy = null;
// 负责加载被代理对象的类加载器
ClassLoader loader = target.getClass().getClassLoader();
// 代理对象的类型,即其中所包含的方法
Class[] interfaces = new Class[]{ArithmeticCalculator.class};
// 当代理对象中的方法被调用时,额外执行的代码
InvocationHandler h = new InvocationHandler() {

/*
* proxy: 正在返回的代理对象,一般情况下,在invoke方法中都不使用该对象。
* method:正在被调用的方法
* args: 调用方法时,传入的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName=method.getName();
// logging goes here
System.out.println("The method " + methodName + " begins with " + Arrays.asList(args));
try{
// 前置通知执行
Object result = method.invoke(target, args);
// 返回通知执行
}catch(Exceptoin e){
// 异常通知执行
}
// 后置通知执行
System.out.println("The method " + methodName + " ends");
return result;
}
};

proxy = (ArithmeticCalculator) Proxy.newProxyInstance(loader, interfaces, h);
return proxy;
}
}
public static void main(String[] args) {
ArithmeticCalculator target = new ArithmeticCalculatorImpl();
ArithmeticCalculator proxy = new ArithmeticCalculatorLoggingProxy(target).getLoggingProxy();

int result = proxy.add(1, 2);
System.out.println("-->"+result);

result = proxy.div(4, 2);
System.out.println("-->"+result);
}

2. AOP 简介

AOP(Aspect-Oriented Programming,面向切面编程):是一种新的方法论,是对传统 OOP(Object-Oriented Programming,面向对象编程)的补充。AOP的主要编程对象是切面(aspect),而切面即模块化横切关注点。

在应用AOP编程时,仍然需要定义公共功能,但可以明确的定义这个功能在哪里,以什么方式应用,并且不必修改受影响的类。这样一来横切关注点就被模块化到特殊的对象(切面)里。AOP的好处:每个事物逻辑位于一个位置,代码不分散,便于维护和升级;业务模块更简洁,只包含核心业务代码。

AOP相关的术语

  • 切面(Aspect):横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象。
  • 通知(Advice):切面必须要完成的工作。
  • 目标(Target):被通知的对象。
  • 代理(Proxy):向目标对象应用通知之后创建的对象。
  • 连接点(Joinpoint):程序执行的某个特定位置,如类某个方法调用前、调用后、方法抛出异常后等。连接点由两个信息确定:方法表示的程序执行点;相对点表示的方位。例如 ArithmethicCalculator.add() 方法执行前的连接点,执行点为 ArithmethicCalculator.add(),方位为该方法执行前的位置。
  • 切点(pointcut):每个类都拥有多个连接点,例如 ArithmethicCalculator 的所有方法实际上都是连接点,即连接点是程序类中客观存在的事务。AOP 通过切点定位到特定的连接点。类比:连接点相当于数据库中的记录,切点相当于查询条件。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件。

3. Spring中的AOP

推荐使用AspectJ,它是Java社区里最完整最流行的AOP框架。在Spring2.0以上版本中,可以使用基于AspectJ注解或基于XML配置的 AOP。

3.1 在Spring中启用AspectJ注解支持

要在Spring应用中使用AspectJ注解,必须:

①. 在类路径下包含AspectJ类库:aopalliance.jar,aspectj.weaver.jar,spring-aspects.jar;

②. 将AOP Schema添加到<beans>根元素中;

③. 启用AspectJ注解支持,在Bean配置文件中定义一个空的XML元素:<aop:aspectj-autoproxy />。

当Spring IOC容器侦测到Bean配置文件中的<aop:aspectj-autoproxy>元素时,会自动为与AspectJ切面匹配的Bean创建代理。


3.2 用 AspectJ 注解声明切面

要在Spring中声明AspectJ切面,只需要在IOC容器中将切面声明为Bean实例。当在Spring IOC容器中初始化AspectJ切面之后,Spring IOC容器就会为那些与AspectJ切面相匹配的Bean创建代理。

在AspectJ注解中,切面只是一个带有@Aspect注解的Java类。通知是标注有某种注解的简单的Java方法,AspectJ支持5种类型的通知注解:

  • @Before,前置通知,在方法执行之前执行。
  • @After,后置通知,在方法执行之后执行而不管目标方法是否正常执行完,所以在该通知中无法访问目标方法的返回值。
  • @AfterRunning,返回通知,在方法返回结果之后执行,在该通知中可以访问到返回值。
  • @AfterThrowing,异常通知,在方法抛出异常之后
  • @Around,环绕通知,围绕着方法执行
  • 引入通知,不常用

示例如下

定义日志切面 LoggingAspect 类,为了使其被 IOC 容器管理,需要添加 @Component 注解,而要让其成为一个切面,需要添加 @Aspect 注解。

package hellospring.aop.annotation;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
/*
* 定义日志切面 LoggingAspect 类,为了使其被 IOC 容器管理,需要添加 @Component 注解,
* 而要让其成为一个切面,需要添加 @Aspect 注解。
*/
@Component
@Aspect
public class LoggingAspect {

// 前置通知,logBefore称为通知方法,通过其形参JoinPoint可以访问到目标方法的细节,如方法名、参数、返回值等。
@Before("execution(int hellospring.aop.annotation.ArithmeticCalculator.*(int, int))")
public void logBefore(JoinPoint jp){
String methodName = jp.getSignature().getName();    
System.out.println("The method " + methodName + " starts with " + Arrays.asList(jp.getArgs()));
}

// 后置通知
@After("execution(int hellospring.aop.annotation.ArithmeticCalculator.*(int, int))")
public void logAfter(JoinPoint jp){
String methodName = jp.getSignature().getName();    
System.out.println("The method " + methodName + " ends");
}

// 返回通知,returning参数指明目标方法返回值对象的名字(引用),需要和通知方法中的形参对应,以便在通知方法中可以访问到目标方法的返回值对象
@AfterReturning(value="execution(int hellospring.aop.annotation.ArithmeticCalculator.*(int, int))",returning="result")
public void logAfterReturning(JoinPoint jp, Object result){
String methodName = jp.getSignature().getName();    
System.out.println("The method " + methodName + " ends with " + result);
}

// 异常通知,throwing参数指明目标方法中抛出的异常对象的名字(引用),需要注意的是:
// 1. throwing参数的值需要同通知方法中形参名一致,以便在通知方法中访问到目标方法抛出的异常;
// 2. 通知方法形参中,异常的类型需要兼容目标方法中实际抛出异常的类型,否则无法初始化该形参。
@AfterThrowing(value="execution(int hellospring.aop.annotation.ArithmeticCalculator.*(int, int))", throwing="ex")
public void logException(JoinPoint jp, Exception ex){
String methodName = jp.getSignature().getName();    
System.out.println("The method " + methodName + " occurs exception: " + ex);
}

/* 环绕通知,需携带ProceedingJoinPoint类型参数
* 环绕通知类似于动态代理的全过程: ProceedingJoinPoint 类型的参数可以决定是否执行目标方法。
* 且环绕通知必须有返回值,否则会出现空指针异常,返回值即为目标方法的返回值。
*/
/*    @Around(value="execution(* hellospring.aop.annotation.ArithmeticCalculator.*(..))")
public Object around(ProceedingJoinPoint pjd){
Object result = null;
String methodName = pjd.getSignature().getName();
try {
//前置通知
System.out.println("The method " + methodName + " starts with " + Arrays.asList(pjd.getArgs()));
//执行目标方法
result = pjd.proceed();
//返回通知
System.out.println("The method " + methodName + " ends with" + result);
} catch (Throwable e) {
//异常通知
System.out.println("The method " + methodName + " occurs exception: " + e);
throw new RuntimeException(e);
}
//后置通知
System.out.println("The method " + methodName + " ends");
return result;
}*/
}

配置文件中:

<context:component-scan base-package="hellospring.aop.annotation"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

3.2.1 切入点表达式

在LogginAspect类中,形如“execution(int hellospring.aop.annotation.ArithmeticCalculator.*(int, int))”称为切入点表达式。在这个示例中,切入点表达式是根据方法的签名来匹配各种方法的:

①. execution(* hellospring.aop.annotation.ArithmeticCalculator.*(..)),匹配hellospring.aop.annotation.ArithmeticCalculator中声明的所有方法,第一个*代表任意修饰符以及任意返回值,第二个*代表任意方法,而“..”匹配任意数量任意类型的参数。如果目标类、接口与该切面类在同一个包中,则可以省略包名。

②. execution(public * ArithmeticCalculator.*(..)),匹配ArithmeticCalculator接口中的所有公共方法。

③. execution(public double ArithmeticCalculator.*(..)),匹配ArithmeticCalculator接口中的所有返回double类型的公共方法。

④. execution(public double ArithmeticCalculator.*(double, ..)),匹配ArithmeticCalculator接口中的所有第一个参数为double型,且返回值为double型的公共方法,“..”匹配任意数量任意类型的参数。

⑤. execution(public double ArithmeticCalculator.*(double, double)),匹配ArithmeticCalculator接口中所有只有两个double类型参数且返回值为double型的公共方法。

可以使用操作符&&、||、!将多个切入点表达式结合起来:

@Before("execution(* *.add(int, ..)) || execution(* *.sub(int, ..))")
public void logBefore(JoinPoint jp){
//...
}

3.2.2 指定切面的优先级

在同一个连接点上应用不止一个切面时,除非明确指定,否则它们的优先级是不确定的。切面的优先级可以通过实现 Ordered 接口或利用 @Order 注解指定。实现 Ordered 接口,getOrder() 方法的返回值越小,优先级越高。若使用 @Order 注解,序号出现在注解中,值越小优先级越高。

@Order(1)
@Aspect
@Component
public class ValidatorAspect {
@Before("execution(* hellospring.aop.annotation.ArithmeticCalculator.*(..))")
public void validate(JoinPoint jp){
System.out.println("--> validate: " + Arrays.asList(jp.getArgs()));
}
}

3.2.3 重用切入点定义

在编写 AspectJ 切面时,可以直接在通知注解中书写切入点表达式。但同一个切点表达式可能会在多个通知中重复出现,为了重复利用切点表达式,可以通过 @Pointcut 注解将一个切入点声明成简单的方法。该切入点的方法体通常是空的,因为将切入点定义与应用程序逻辑混在一起是不合理的。

切入点方法的访问控制符同时也控制着这个切入点的可见性。如果切入点要在多个切面中共用,最好将它们集中在一个公共的类中。在这种情况下,它们必须被声明为 public。在引入这个切入点时,必须将类名也包括在内。如果类没有与这个切面放在同一个包中,还必须包含包名。其他通知可以通过方法名称引入该切入点。

@Order(2)
@Component
@Aspect
public class LoggingAspect {

/**
* 定义一个方法,用于声明切入点表达式。 一般地, 该方法中再不需要添入其他的代码。 
* 使用 @Pointcut 来声明切入点表达式。 
* 后面的其他通知直接使用方法名来引用当前的切入点表达式。 
*/
@Pointcut("execution(int hellospring.aop.annotation.ArithmeticCalculator.*(..))")
public void declareJointPointExpression(){}

/*
* 同一个类中复用切入点方法
*/
@Before("declareJointPointExpression()")
public void logBefore(JoinPoint jp){
String methodName = jp.getSignature().getName();    
System.out.println("The method " + methodName + " starts with " + Arrays.asList(jp.getArgs()));
}

// ...
}
@Order(1)
@Aspect
@Component
public class ValidatorAspect {
// 不同类总复用切入点方法
@Before("hellospring.aop.annotation.LoggingAspect.declareJointPointExpression()")
public void validate(JoinPoint jp){
System.out.println("--> validate: " + Arrays.asList(jp.getArgs()));
}
}

3.3 基于 XML 的配置声明切面

通过Spring Bean的配置文件也可以实现切面的声明。但是,通过 AspectJ 注解,切面可以与 AspectJ 兼容,而基于 XML 配置的声明则是 Spring 专有。由于 AspectJ 得到越来越多的 AOP 框架支持,所以以注解风格编写的切面将会有更多重用的机会。正常情况下,基于注解的声明要优先于基于 XML 的声明。当使用XML声明切面时,需要在 <beans> 根元素中导入 aop Schema:xmlns:aop="http://www.springframework.org/schema/aop" 。

在 Bean 配置文件中,所有的 Spring AOP 配置都必须定义在 <aop:config> 元素内部。对于每个切面而言,都要创建一个 <aop:aspect> 元素来为具体的切面实现引用后端 Bean 实例。切面 Bean 必须有一个标示符,供 <aop:aspect> 元素引用。

<!-- 配置Bean -->
<bean id="arithmeticCalculator" class="hellospring.aop.xml.ArithmeticCalculatorImpl"></bean>
<!-- 配置切面Bean -->
<bean id="loggingAspect" class="hellospring.aop.xml.LoggingAspect" />
<bean id="validatorAspect" class="hellospring.aop.xml.ValidatorAspect" />
<!-- 配置AOP -->
<aop:config>
<!-- 配置切入点表达式 -->
<aop:pointcut expression="execution(int hellospring.aop.xml.ArithmeticCalculator.*(..))" id="pointcut1"/>

<!-- 配置切面及通知 -->
<aop:aspect ref="loggingAspect" order="2">
<!-- 指定各个通知的细节 -->
<aop:before method="logBefore" pointcut-ref="pointcut1"/>
<aop:after method="logAfter" pointcut-ref="pointcut1"/>
<aop:after-returning method="logAfterReturning" pointcut-ref="pointcut1" returning="result"/>
<aop:after-throwing method="logException" pointcut-ref="pointcut1" throwing="e"/>
<!-- 
<aop:around method="around" pointcut-ref="pointcut1" />
-->
</aop:aspect>
<aop:aspect ref="validatorAspect" order="1">
<aop:before method="validate" pointcut-ref="pointcut1"/>
</aop:aspect>
</aop:config>

需要注意的是:

(1)切入点(<aop:pointcut>)必须定义在 <aop:aspect> 元素下,或者直接定义在 <aop:config> 元素下,定义在 <aop:aspect> 元素下则只对当前切面有效,定义在 <aop:config> 元素下是对所有切面都有效;

(2)基于 XML 的 AOP 配置不允许在切入点表达式中用名称引用其他切入点。

五、Spring对JDBC的支持

1. JdbcTemplate

为了使 JDBC 更加易于使用,Spring 在 JDBC API 上定义了一个抽象层,以此建立一个 JDBC 存取框架。作为 Spring JDBC 框架的核心,JDBC 模板的设计目的是为不同类型的 JDBC 操作提供模板方法。每个模板方法都能控制整个过程,并允许覆盖过程中的特定任务。通过这种方式,可以在尽可能保留灵活性的情况下,将数据库存取的工作量降到最低。

JdbcTemplate 类被设计成为线程安全的,所以可以在IOC容器中声明它的单个实例,并将这个实例注入到所有的 DAO 实例中。

实例,使用JdbcTemplate

配置

<!-- 导入资源文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 配置c3p0数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.user}" />
<property name="password" value="${jdbc.password}" />
<property name="jdbcUrl" value="${jdbc.url}" />
<property name="driverClass" value="${jdbc.driverClass}" />

<property name="initialPoolSize" value="5"/>
<property name="maxPoolSize" value="10"/>
</bean>
<!-- 配置Spring的JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" >
<property name="dataSource" ref="dataSource" />
</bean>

db.properties中的内容如下:

jdbc.user=root
jdbc.password=123
jdbc.url=jdbc:mysql:///hellospring
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.initialPoolSize=5
jdbc.maxPoolSize=5

实体类:

public class Employee {
private Integer id;
private String lastName;
private String email;

// ...
}
public class Department {
private Integer id;
private String name;

// ...
}

Junit 测试类:

public class SpJdbcTest {
private ApplicationContext ctx = null;
private JdbcTemplate jdbcTemplate = null;

{
ctx = new ClassPathXmlApplicationContext("spring-jdbc-test.xml");
jdbcTemplate = (JdbcTemplate) ctx.getBean("jdbcTemplate");
}

@Test
public void testDataSource() throws SQLException {
DataSource ds = (DataSource) ctx.getBean(DataSource.class);
System.out.println(ds.getConnection());
}
/**
* 执行 INSERT, UPDATE, DELETE
*/
@Test
public void testUpdate(){
String sql = "UPDATE employees SET last_name = ? WHERE id = ?";
jdbcTemplate.update(sql, "Laideju", 1);
}

/**
* 执行批量更新: 批量的 INSERT, UPDATE, DELETE
* 最后一个参数是 Object[] 的 List 类型: 因为修改一条记录需要一个 Object 的数组, 那么多条不就需要多个 Object 的数组吗
*/
@Test
public void testBatchUpdate(){
String sql = "INSERT INTO employees(last_name, email) values(?,?)";
List<Object[]> batchArgs = new ArrayList<>();
batchArgs.add(new Object[]{"AA", "aa@sina.com"});
batchArgs.add(new Object[]{"BB", "bb@sina.com"});
batchArgs.add(new Object[]{"CC", "cc@sina.com"});
batchArgs.add(new Object[]{"DD", "dd@sina.com"});
batchArgs.add(new Object[]{"EE", "ee@sina.com"});
jdbcTemplate.batchUpdate(sql, batchArgs);    
}

/**
* 单行查询
* 从数据库中获取一条记录,实际得到对应的一个对象。
* 注意不是调用 queryForObject(String sql, Class<Employee> requiredType, Object... args) 方法!
* 而需要调用 queryForObject(String sql, RowMapper<Employee> rowMapper, Object... args)
* 1. 其中的 RowMapper 指定如何去映射结果集的行,常用的实现类为 BeanPropertyRowMapper
* 2. 使用 SQL 中列的别名完成列名和类的属性名的映射,例如 last_name lastName
* 3. 不支持级联属性,JdbcTemplate 到底是一个 JDBC 的小工具,而不是 ORM 框架
*/
@Test
public void testQueryForOneRow(){
String sql = "SELECT id, last_name lastName, email FROM employees WHERE id = ?";
RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class);
Employee emp = jdbcTemplate.queryForObject(sql, rowMapper, 1);
System.out.println(emp);
}

/**
* 获取单个列的值,或做统计查询
* 使用 queryForObject(String sql, Class<Long> requiredType) 
*/
@Test
public void testQueryForField(){
String sql = "SELECT count(id) FROM employees";
long count = jdbcTemplate.queryForObject(sql, Long.class);
System.out.println(count);
}

/**
* 查到实体类的集合
* 注意调用的不是 queryForList 方法
*/
@Test
public void testQueryForList(){
String sql = "SELECT id, last_name lastName, email FROM employees WHERE id > ?";
RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class);
List<Employee> emps = jdbcTemplate.query(sql, rowMapper, 8);
System.out.println(emps);
}

@Test
public void testQueryForList2(){
String sql = "SELECT email FROM employees WHERE id > ?";
List<String> emails = jdbcTemplate.queryForList(sql, String.class, 8);
System.out.println(emails);
}
}

Spring JDBC 框架还提供了一个 JdbcDaoSupport 类来简化 DAO 实现。该类声明了 jdbcTemplate 属性,它可以从 IOC 容器中注入,或者自动从数据源中创建。但用的不多!

1.1 在 JDBC 模板中使用命名参数

在经典的 JDBC 用法中,SQL 参数是用占位符 ? 表示,并且受到位置的限制。定位参数的问题在于,一旦参数的顺序发生变化,就必须改变参数绑定。在 Spring JDBC 框架中,绑定 SQL 参数的另一种选择是使用命名参数。

命名参数:SQL 按名称(以冒号开头)而不是按位置进行指定。命名参数更易于维护,也提升了可读性。命名参数由框架类在运行时用占位符取代。命名参数只在 NamedParameterJdbcTemplate 中得到支持。

在 SQL 语句中使用命名参数时,可以在一个 Map 中提供参数值,参数名为键。

@Test
public void testNamedParameterJdbcTemplate(){
String sql = "INSERT INTO employees(last_name, email) VALUES(:ln, :email)";
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("ln", "FF");
paramMap.put("email", "ff@sina.com");
// namedParameterJdbcTemplate 已在Spring Bean配置文件中定义
namedParameterJdbcTemplate.update(sql, paramMap); 
}

上例中namedParameterJdbcTemplate事前已在Spring Bean配置文件中定义:

<!-- 配置 NamedParameterJdbcTemplate,该对象可以使用具名参数,其没有无参数的构造器,所以必须为其构造器指定参数 -->
<bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
<constructor-arg ref="dataSource" />
</bean>

使用命名参数时也可以使用SqlParameterSource参数。

@Test
public void testNamedParameterJdbcTemplate2(){
String sql = "INSERT INTO employees(last_name, email) VALUES(:lastName, :email)";
Employee employee = new Employee();
employee.setLastName("XYZ");
employee.setEmail("xyz@sina.com");

SqlParameterSource paramSource = new BeanPropertySqlParameterSource(employee);
namedParameterJdbcTemplate.update(sql, paramSource);
}

使用SqlParameterSource参数时,需要注意:

①. SQL 语句中的参数名和类的属性名要一致;

②. 使用 SqlParameterSource 的 BeanPropertySqlParameterSource 实现类作为参数。

批量更新时可以提供 Map 或 SqlParameterSource 的数组。

2. Spring中的事务管理

Spring 既支持编程式事务管理,也支持声明式的事务管理。

编程式事务管理:将事务管理代码嵌入到业务方法中来控制事务的提交和回滚。在编程式管理事务时,必须在每个事务操作中包含额外的事务管理代码。

声明式事务管理:大多数情况下比编程式事务管理更好用。它将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。事务管理作为一种横切关注点,可以通过 AOP 方法模块化。Spring 通过 Spring AOP 框架支持声明式事务管理。

Spring 声明式事务管理支持注解和基于 XML 配置的两种方式来设置声明式事务。

2.1 基于注解的方式

(1). 在Spring配置文件中配置事务管理器

<!-- 配置事务管理器 -->
<bean id="transactionManager" 
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>

注意:①. class属性指定的类应根据事务底层的具体实现来选取,例如使用JDBC原生的事务管理class应指定DataSourceTransactionManager,而使用Hibernate的事务管理则应将class设置为HibernateTransactionManager,如果使用JPA的事务管理则class应指定为JpaTransactionManager,等。②. 需要为事务管理器Bean配置必须的dataSource属性。

(2). 为Spring应用启用事务注解

<!-- 启用事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>

(3). 使用事务注解

在需要添加事务的方法上添加@Transactional注解:

@Transactional(propagation=Propagation.REQUIRES_NEW, isolation=Isolation.READ_COMMITTED)
@Override
public void purchase(String username, String isbn) {
// 1. 获取书的单价
int price = bookShopDao.findBookPriceByIsbn(isbn);
// 2. 更新数的库存
bookShopDao.updateBookStock(isbn);
// 3. 更新用户余额
bookShopDao.updateUserAccount(username, price);    
}

@Transactional注解的可选参数如下:

  • propagation:指定事务的传播行为,即当前的事务方法被另外一个事务方法调用时如何使用事务。默认取值为 REQUIRED,即使用调用方法的事务,也常使用REQUIRES_NEW,即使用当前事务方法自身的事务,此时调用方法的事务会被挂起。
  • isolation:指定事务的隔离级别,最常用的取值为 READ_COMMITTED 。
  • noRollbackFor:指定对哪些异常不进行回滚,默认情况下Spring的声明式事务对所有的运行时异常进行回滚。通常情况下取默认值即可。
  • rollbackFor:指定对对哪些异常必须进行回滚。noRollbackFor 和 rollbackFor都是Class[]类型的属性,所以可以同时指定多个异常类。
  • readOnly:指定事务是否为只读,表示这个事务只读取数据但不更新数据,这样可以帮助数据库引擎优化事务。若真的是一个只读取数据库值的方法,应设置readOnly=true 。
  • timeout:指定强制回滚之前事务可以占用的时间,以秒为单位。

关于事务的传播行为

假设有如下的调用:

@Transactional
public void checkout(String username, List<String> isbns) {
for(String isbn : isbns){
bookShopService.purchase(username, isbn);
}
}

其中bookShopService.purchase()方法即上例中的purchase()方法。当purchase()使用“propagation=REQUIRED”时,事务的传播示意图如下所示:

当purchase()使用“propagation=REQUIRED”时,事务的传播示意图如下所示:

2.2 基于XML配置文件的方式

不同于注解的方式,基于XML配置的方式只需要在Spring配置文件中进行恰当配置即可,示例如下:

<!-- 1.配置Spring的事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 2.配置事务属性,需要事务管理器 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 根据方法名指定事务的属性 -->
<tx:method name="purchase" propagation="REQUIRES_NEW"/>
<!-- 以get和find开头的方法一般是只读取数据库的方法 -->
<tx:method name="get*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<!-- 其余方法采用默认值 -->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- 3.配置事务切入点,以及把事务切入点和事务属性关联起来 -->
<aop:config>
<aop:pointcut expression="execution(* hellospring.tx.xmlway.services.*.*(..))" 
id="txPointCut"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>

事务的诸多属性,如isolation、timeout、rollbackFor等等,可以在<tx:method>节点上进行配置。

六、Spring整合Hibernate

Spring支持大多数流行的ORM框架,包括Hibernate JDO、TopLink、Mybatis和JPA。并且Spring对这些ORM框架的支持是一致的,因此可以把和Hibernate整合技术应用到其他ORM框架上。Spring 2.0同时支持Hibernate 2.x 和 3.x,但 Spring 2.5只支持Hibernate 3.1或更高版本。Spring整合Hibernate的目的,即让IOC容器来管理Hibernate的SessionFactory,让Hibernate使用上Spring的声明式事务。

1.整合的步骤

(1). 加入Hibernate

①. 导入Hibernate的jar包。
②. 添加Hibernate的配置文件:hibernate.cfg.xml。因为Hibernate中使用到的DataSource对象也将由Spring IOC进行管理,所以在hibernate的配置文件中不再需要关于数据源的配置。

<hibernate-configuration>
<session-factory>

<!-- 配置 hibernate 的基本属性 -->
<!-- 1. 数据源需配置到 IOC 容器中, 所以在此处不再需要配置数据源 -->
<!-- 2. 关联的 .hbm.xml 也在 IOC 容器配置 SessionFactory 实例时再进行配置 -->
<!-- 3. 配置 hibernate 的基本属性: 方言, SQL 显示及格式化, 生成数据表的策略以及二级缓存等. -->
<property name="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>
<property name="hibernate.show_sql">true</property>
<property name="hibernate.format_sql">true</property>
<property name="hibernate.hbm2ddl.auto">update</property>

</session-factory>
</hibernate-configuration>

③. 编写了持久化类对应的“.hbm.xml”文件。

(2). 加入Spring

①. 导入Spring相关的jar包。

②. 加入Spring的配置文件。

(3). 整合:在Srping的配置文件中进行配置。

<!-- 配置自动扫描的包 -->
<context:component-scan base-package="hellospring.hibernate"></context:component-scan>
<!-- 配置数据源 -->
<!-- 导入资源文件 -->
<context:property-placeholder location="classpath:db-spring-hibernate.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
<property name="initialPoolSize" value="${jdbc.initialPoolSize}"></property>
<property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property>
</bean>
<!-- 配置 Hibernate 的 SessionFactory 实例: 通过 Spring 提供的 LocalSessionFactoryBean 进行配置 -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<!-- 配置数据源属性 -->
<property name="dataSource" ref="dataSource" />

<!-- 配置 hibernate 配置文件的位置及名称 -->
<property name="configLocation" value="classpath:hibernate.cfg.xml"></property>
<!-- 也可以使用 hibernateProperties 来配置 Hibernate 原生的属性 -->
<!--
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
-->

<!-- 配置 hibernate 映射文件的位置及名称, 可以使用通配符 -->
<property name="mappingLocations" value="classpath:hellospring/hibernate/entities/*.hbm.xml" />
</bean>
<!-- 配置 Spring 的声明式事务 -->
<bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"></property>
</bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="purchase" propagation="REQUIRES_NEW"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut expression="execution(* hellospring.hibernate.services.*.*(..))" id="txPointCut"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>

2. 注意小项

Spring hibernate 事务的流程
(1)在方法开始之前:

①. 获取Session;

②. 把Session和当前线程绑定,这样就可以在Dao中使用SessionFactory的getCurrentSession()方法来获取Session了;

③. 开启事务。

(2)若方法正常结束,即没有出现异常,则:

①. 提交事务;

②. 使和当前线程绑定的Session解除绑定;

③. 关闭Session。

(3)若方法出现异常,则:

①. 回滚事务;

②. 使和当前线程绑定的Session解除绑定;

③. 关闭Session。

不推荐使用 HibernateTemplate 和 HibernateDaoSupport,因为这样会导致 Dao 和 Spring 的 API 进行耦合,可以移植性变差。

七、Spring整合Struts2

Spring整合Struts2实际上涉及到另外一个问题:如何在Web环境下使用Spring。下面将先解释这个问题,整合Struts2本质上与将Spring应用到Web环境中并无不同。

1. 在WEB应用中使用Spring

(1). 为Spring加入WEB环境必须的jar包:spring-web-4.0.0.RELEASE.jar,spring-webmvc-4.0.0.RELEASE.jar。

(2). WEB环境下Spring的配置文件与普通的Spring应用的配置文件一致。

(3). 创建IOC容器。普通的Spring应用可以直接在main方法中创建容器,而对于WEB应用来说,一般是在WEB应用被服务器加载时创建容器,具体而言是在ServletContextListener.contextInitialized(ServletContextEvent sce)方法中创建。为了使创建IOC容器的监听器生效,还需在web.xml中进行如下配置:

<!-- 启动 IOC 容器的 ServletContextListener -->
<listener>
<listener-class>springstruts2.listeners.SpringServletContextListener</listener-class>
</listener>

(4). 在WEB应用的其他组件中访问IOC容器:在监听器中创建IOC容器后,就把其放入到ServletContext(即application域)的一个属性中。实际上,Spring 配置文件的名字和位置应该也是可配置的,将其配置到当前 WEB 应用的初始化参数中较为合适。

<!-- 配置Spring配置文件的名字和位置 -->
<context-param>
<param-name>configLocation</param-name>
<param-value>applicationContext.xml</param-value>
</context-param>
public class SpringServletContextListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent arg0) {
//1. 获取 Spring 配置文件的名称. 
ServletContext servletContext = arg0.getServletContext();
String config = servletContext.getInitParameter("configLocation");

//1. 创建 IOC 容器
ApplicationContext ctx = new ClassPathXmlApplicationContext(config);

//2. 把 IOC 容器放在 ServletContext 的一个属性中. 
servletContext.setAttribute("ApplicationContext", ctx);
}
public void contextDestroyed(ServletContextEvent arg0) { }

public SpringServletContextListener() { }
}

事实上,Spring已经提供相关的组件实现了上述思想,直接配置使用即可从而避免再次编写监听器。

<!-- 配置 Spring 配置文件的名称和位置 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<!-- 因为是给Spring内部组件使用,所以要使用classpath前缀 -->
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!-- 启动 IOC 容器的 ServletContextListener -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

2. Spring整合Struts2

整合目的:使IOC容器来管理Struts2的Action。

整合步骤:

(1). 正常加入Struts2,即导入其所需的jar包。

(2). 在Spring的IOC容器中配置Struts2的Action。注意:在IOC容器中配置Struts2的Action时,需要配置scope属性,其值必须为prototype,否则Action就成为单例的了。

<bean id="personAction" class="springstruts2.actions.PersonAction" scope="prototype">
<property name="personService" ref="personService"></property>
</bean>

(3). 配置Struts2的配置文件:action节点的class属性需要指向IOC容器中该bean的id。

<!-- 
Spring 整合 Struts2 时, 在 Struts2 中配置的 Spring 的 Action 的 class 需要指向 IOC 容器中该 bean 的 id
-->
<action name="person-save" class="personAction">
<result>/success.jsp</result>
</action>

(4). 加入struts2-spring-plugin-2.3.15.3.jar。

整合原理:通过添加struts2-spring-plugin-2.3.15.3.jar后,Struts2会先从IOC容器中获取Action的实例。


SPRING 学习笔记 END at 2017-02-18 12:21:00.

原文地址:https://www.cnblogs.com/itfky/p/13716419.html