1_Spring的核心


Spring简介


Spring的根本使命是:简化Java的开发,采用了四种策略:

  • 基于POJO的轻量级和最小浸入性编程,避免了大量的不必要的冗余代码
  • 通过依赖注入和面向接口事项松耦合,每个对象负责管理与自己相互协作的对象的引用会造成高度的耦合性,测试比较复杂,另一种方式是采用依赖注入,对象的依赖关系将由负责协调系统中各个对象的第三方组件在创建对象时设定,对象无需自行创建或者管理器依赖关系,依赖关系会自动的注入到需要的对象中去。
  • 基于切面和惯例进行声明式编程
  • 通过切面和模板减少样式代码,如JDBC查询模板语句

Spring容器

  • Spring应用中,对象存在于Spring容器中,容器创建对象,装配对象,配置对象,管理对象的声明周期。
  • Spring容器可以分为两种类型:
    • Bean工厂:最简单的容器,提供基本的DI支持
    • 应用上下文:基于beanFactory之上构建,提供面向应用的服务,可以从属性文件解析文本信息
      • ClassPathXMLApplicationContext: 从类路径下的XML配置文件中加载上下文定义,把应用上下文定义文件当做类资源
      • FileSystemXmlApplocationcontext:读取文件系统下的XML配置文件并加载上下文定义
      • XmlWebApplocationContext:读取web应用下的XML配置文件并装载上下文定义。
  • Bean的生命周期:
    • Spring对Bean进行实例化
    • Spring将值和Bean的引用注入进bean对应的属性中去
    • 如果Bean实现了BeanNameAware接口,Spring将Bean的ID传递给setBeanName()接口方法
    • 如果Bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()接口方法,将BeanFactory容器实例传入。
    • 如果Bean实现了ApplicationContextAweare接口,Spring将调用他们的setApplicationContext()接口方法,将应用上下文的引用传入
    • 如果Bean实现了BeanPostProcessor接口,Spring将调用他们的postProcessBeforeInitialization()接口方法
    • 如果Bean实现了InitializingBean接口,Spring将调用他们的afterPropertiesSet()接口方法。类似的,如果Bean使用init-method声明了初始化方法,该方法也会被调用。
    • 如果Bean实现了BeanPostProcessor接口,Spring将调用他们的postProcessAfterInitialization()接口方法
    • 此时此刻,Bean已经准备就绪,可以被应用程序调用,Bean会一直驻留在应用上下文中,直到该应用上下文被销毁
    • 如果Bean实现了DisposableBea接口,Spring将调用他们的destroy()接口方法。同样如果Bean使用destroy-method声明了销毁方法,该方法也会被调用。

Spring的六大模块

  • 核心Spring容器:负责Bean的创建,配置和管理。BeanFactory提供了依赖注入,应用上下文及其他企业服务。
  • AOP模块:对面向切面编程提供了丰富的支持。
  • 数据访问与集成:JDBC和DAO层模块封装了数据库样板,提供了ORM模块,
  • Web和远程调用
  • 测试

装配Bean


声明Bean

XML命名空间

  • Java自带了许多XML命名空间,通过这些命名空间可以配置Spring

    • aop:为声明切面以及将@AspectJ注解的类代理为Spring切面提供了配置元素
    • beans:支持声明Bean和装配Bean,是Spring最核心也是最原始的命名空间
    • context:为配置Spring应用上下文提供了配置元素,包括自动检测和自动装配bean,注入非Spring直接管理的对象
    • jee:提供了与Java EE API的继承,例如JNDI和EJB
    • jms:为声明消息驱动的POJO提供了配置元素
    • lang:支持配置由Groovy、JRuby或BeanShell等脚本实现的Bean
    • mvc:启动Spring MVC的能力,例如面向注解的控制器、视图控制器和拦截器
    • oxm:支持Spring的对象到XML的映射配置
    • tx:提供声明式事务配置
    • util:提供各种各样的工具类元素,包括把集合配置为Bean,支持属性占位符元素
  • 创建Spring配置:

    1
    2
    3
    4
    5
    6
    7
    <?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"
    >
    <!--Bean declarations go here -->
    </beans>

beans 元素中放置所有的Spring配置文件。

声明Bean

定义一个Perfomer接口:

1
2
3
4
5
package com.springinaction.springidol;
public interface Performer {
void perform() throws Exception;
}

新建一个Bean,实现了Performer接口,从而可以进行表演扔袋子的节目:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.springinaction.springidol;
public class Juggler implements Performer{
private int beanBags = 3;
public Juggler() {
}
public Juggler(int beanBags) {
this.beanBags = beanBags;
}
@Override
public void perform() throws Exception {
System.out.println("JUGGLING " + beanBags + " BEANBAGS");
}
}

在Spring配置文件中声明一个Bean:

1
<bean id="duke" class="com.springinaction.springidol.Juggler" />

元素是Spring中最基本的配置单元,通过该元素,Spring可以创建一个对象,duke相当于Juggler的一个实例,main函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class main {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"com/springinaction/springidol/spring-idol.xml"
);
Performer performer = (Performer) ctx.getBean("duke");
try {
performer.perform();
} catch (Exception e) {
e.printStackTrace();
}
}
}

打印结果:JUGGLING 3 BEANBAGS

构造器注入

XML文件中,让duke成为可以抛15个袋子的杂技师:

1
2
3
<bean id="duke" class="com.springinaction.springidol.Juggler" >
<constructor-arg value="15"/>
</bean>

当不配置constructor-arg标签时,Spring将使用默认的构造方法,配置之后会使用另外的构造方法来实例化对象。

  • 构造器注入对象的引用
    Poem接口:
1
2
3
4
5
package com.springinaction.springidol;
public interface Poem {
void recite();
}

注入对象引用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.springinaction.springidol;
public class PoeticJuggler extends Juggler{
private Poem poem;
public PoeticJuggler(Poem poem) {
super();
this.poem = poem;
}
public PoeticJuggler(int beanBags, Poem poem) {
super(beanBags);
this.poem = poem;
}
@Override
public void perform() throws Exception {
super.perform();
System.out.println("While reciting...");
poem.recite();
}
}

实现Poem接口的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.springinaction.springidol;
public class Sonnet29 implements Poem{
private static String[] LINES = {
"when I was young",
"I am a boy.....",
"end of the poem"
};
public Sonnet29() {
}
@Override
public void recite() {
for (int i = 0; i < LINES.length; i++)
System.out.println(LINES[i]);
}
}

XML中进行配置:

1
2
3
4
5
<bean id="duke" class="com.springinaction.springidol.PoeticJuggler" >
<constructor-arg value="15"/>
<constructor-arg ref="sonnet29"/>
</bean>
<bean id="sonnet29" class="com.springinaction.springidol.Sonnet29"/>

这相当于执行了如下的Java语句:

1
2
Poem sonnet29 = new Sonnet29();
Performer duke = new PoeticJuggler(15, sonnet29);

打印输出为:

1
2
3
4
5
JUGGLING 15 BEANBAGS
While reciting...
when I was young
I am a boy.....
end of the poem
  • 工厂方法构造Bean
    Spring支持通过元素的factory-method属性来装配工厂创建的Bean,Stage实例如下,没有公开的构造方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.springinaction.springidol;
public class Stage {
public Stage() {
}
//返回实例
public static Stage getInstance() {
return StageSingletonHolder.instance;
}
//延迟加载实例
private static class StageSingletonHolder {
static Stage instance = new Stage();
}
}
1
2
<!--通过工厂方法创建Bean-->
<bean id="theStage" class="com.springinaction.springidol.Stage" factory-method="getInstance"/>

Bean的作用域

Spring的Bean作用域允许用户配置所创建的Bean属于哪一种作用域:

* singleton:在每一个Spring容器中,一个Bean定义只有一个对象实例(默认)
* prototype:允许Bean的定义可以被实例化任意次
* request:在一次HTTP请求中,每个Bean定义对应一个实例。
* session:在依次HTTP Session中,每个Bean定义对应一个实例。
* global-session:在一个全局的HTTP Session中,每个Bean定义对应一个实例。

初始化和销毁Bean

实现开关灯的实体类:

1
2
3
4
5
6
7
8
9
10
11
package com.springinaction.springidol;
public class Auditorium {
public void turnOnLights(){
System.out.println("trun on the lights!!!!");
}
public void trunOffLights() {
System.out.println("turn off the lights!!!!");
}
}

XML配置:

1
2
<!--初始化和销毁bean-->
<bean id="auditorium" class="com.springinaction.springidol.Auditorium" init-method="turnOnLights" destroy-method="trunOffLights" />

Audiorium实例化后会立刻调用init-method,该Bean移除之后会调用destroy-method。

默认的init-method和destroy-method:

1
2
3
4
5
6
<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"
default-init-method="turnOnLights"
default-destroy-method="trunOffLights"
>

为上下文所有的Bean配置默认的初始化和销毁方法。

注入Bean属性

JavaBean的属性通常是私有的,Spring可以借助其set方法配置属性的值,实现setter注入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.springinaction.springidol;
public class Instrumentalist implements Performer{
public Instrumentalist() {
}
@Override
public void perform() throws Exception {
System.out.println("Playing " + song + " : ");
instrument.play();
}
private String song;
public String getSong() {
return song;
}
//注入歌曲
public void setSong(String song) {
this.song = song;
}
public String screanSong() {
return song;
}
private Instrument instrument;
// 注入乐器
public void setInstrument(Instrument instrument) {
this.instrument = instrument;
}
}

其有两个属性,song和Instrument,Instrument接口如下:

1
2
3
4
5
package com.springinaction.springidol;
public interface Instrument {
public void play();
}

XML中配置其默认的构造方法生成Bean kenny:

1
<bean id="kenny" class="com.springinaction.springidol.Instrumentalist"/>

此时kenny没有歌曲也没有乐器,需要对其进行注入属性。

注入简单值

使用元素配置Bean的属性。一旦Instrumentalist被实例化,Spring就会调用元素所指定属性的setter方法为该属性注入值。

1
2
3
4
<!--注入Bean属性-->
<bean id="kenny" class="com.springinaction.springidol.Instrumentalist" >
<property name="song" value="Jingle Bells" />
</bean>

引用其他Bean

定义两个实现了Instrument接口的类:

1
2
3
4
5
6
7
8
9
10
11
package com.springinaction.springidol;
public class saxophone implements Instrument{
public saxophone() {
}
@Override
public void play() {
System.out.println("tttttttoooooooo");
}
}

1
2
3
4
5
6
7
8
9
10
11
package com.springinaction.springidol;
public class Piano implements Instrument{
public Piano() {
}
@Override
public void play() {
System.out.println("play the piano!!!");
}
}

Spring支持将其他Bean注入到当前Bean:

1
2
3
4
5
6
7
<bean id="kenny" class="com.springinaction.springidol.Instrumentalist" >
<property name="song" value="Jingle Bells" />
<property name="instrument" ref="saxophone"/>
<property name="instrument" ref="piano" />
</bean>
<bean id="saxophone" class="com.springinaction.springidol.saxophone" />
<bean id="piano" class="com.springinaction.springidol.Piano" />

  • 注入内部Bean
    注入的Bean仅当前Bean可以使用,此时可以采用内部Bean的注入方法:
    1
    2
    3
    4
    5
    6
    7
    <!-- 内部BEAN -->
    <bean id="kenny" class="com.springinaction.springidol.Instrumentalist">
    <property name="song" value="Jingle Bells" />
    <property name="instrument">
    <bean class="com.springinaction.springidol.saxophone" />
    </property>
    </bean>

除了标签外,标签同样可以注入内部Bean

Spring命名空间的P装配属性

XML声明:

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
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"
default-init-method="turnOnLights"
default-destroy-method="trunOffLights"
>

p标签和property功能类似,但是更加简洁:

1
<bean id="kenny" class="com.springinaction.springidol.Instrumentalist" p:song = "Jingle Bells" p:instrument-ref="saxophone"/>

装配集合

  • :装配list类型的值,不允许重复
  • :装配set类型的值,不允许重复
  • :装配map类型的值,名称和值可以是任意类型
  • :装配properties类型的值,名称和值必须都是String类型

属性实际定义的集合类型和选择没有任何的关系。新建一人可以同时演奏多种乐器的实体类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.springinaction.springidol;
import java.util.Collection;
public class OneManBand implements Performer{
public OneManBand() {
}
@Override
public void perform() throws Exception {
for (Instrument instrument : instruments) {
instrument.play();
}
}
private Collection<Instrument> instruments;
public void setInstruments(Collection<Instrument> instruments) {
this.instruments = instruments;
}
}

装配list Set Array分别为:

1
2
3
4
5
6
7
8
<bean id="hank" class="com.springinaction.springidol.OneManBand">
<property name="instruments">
<list>
<ref bean="saxophone"/>
<ref bean="piano"/>
</list>
</property>
</bean>

list实际上可以包含另一个list作为其成员构成多维列表,无论还是都可以用来装配类型为Collection的任意实现或者数组的属性。

1
2
3
4
5
6
7
8
<bean id="hank2" class="com.springinaction.springidol.OneManBand2">
<property name="instruments">
<map>
<entry key="PIANO" value-ref="piano"/>
<entry key="SAX" value-ref="saxophone"/>
</map>
</property>
</bean>

装配map类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.springinaction.springidol;
import java.util.Collection;
import java.util.Map;
public class OneManBand2 implements Performer{
public OneManBand2() {
}
@Override
public void perform() throws Exception {
for (String key : instruments.keySet()) {
System.out.println(key + " : ");
instruments.get(key).play();
}
}
private Map<String, Instrument> instruments;
public void setInstruments(Map<String, Instrument> instruments) {
this.instruments = instruments;
}
}

1
2
3
4
5
6
7
8
<bean id="hank2" class="com.springinaction.springidol.OneManBand2">
<property name="instruments">
<map>
<entry key="PIANO" value-ref="piano"/>
<entry key="SAX" value-ref="saxophone"/>
</map>
</property>
</bean>

使用表达式装配

Spring引入了表达式语言Spring Expression Language,SpEL通过运行期执行的表达式将值装配到Bean的属性和构造器参数中去。SpEL的特性包括:

* 使用Bean的ID来引用Bean
* 调用方法和访问对象的属性
* 对值进行算数、关系和逻辑运算
* 正则表达式匹配
* 集合操作

基本要素

#{}会提示Spring这个标记里的内容时SpEL表达式,其格式可以为整型数,浮点数,科学计数法,字符串类型(单引号或者双引号都可以),bool类型

  • 字面值:

    1
    2
    3
    4
    5
    6
    <property name="count" value="#{5}"/>
    <property name="frequency" value="#{89.22}"/>
    <property name="capacity" value="#{1e6}"/>
    <property name="name" value="#{"Chuck"}"/>
    <property name="name" value="#{'Chuck'}"/>
    <property name="enable" value="#{false}"/>
  • 引用Bean属性和方法
    SpEL表达式不仅可以装配字面值,还可以装配Bean的属性和方法:

    1
    2
    3
    <bean id="carl" class="com.springinaction.springidol.Instrumentalist">
    <property name="song" value="#{kenny.song}"/>
    </bean>
1
2
3
<bean id="carl" class="com.springinaction.springidol.Instrumentalist">
<property name="song" value="#{songSelector.selectSong().toUpperCase()}"/>
</bean>

返回值

1
2
3
<bean id="carl" class="com.springinaction.springidol.Instrumentalist">
<property name="song" value="#{songSelector.selectSong().toUpperCase()}"/>
</bean>

为NULL会报空指针错误,解决空指针错误的方法为使用null-safe存取器:

1
2
3
<bean id="carl" class="com.springinaction.springidol.Instrumentalist">
<property name="song" value="#{songSelector.selectSong()?.toUpperCase()}"/>
</bean>

  • 操作类
    T()运算符会调用类作用域的方法和常量。例如T(java.lang.Math),该运算符可以调用指定类的静态方法和常量,当需要把PI装配到Bean中去:
    1
    <property name="PI" value="#{T(java.lang.Math).PI}"/>

值操作

  • 运算符

    • 算数运算:+、-、*,/, %, ^

      1
      <property name="adjustedAmount" value="#{counter.total-20}"/>
    • 关系运算:<、>、==、 >=、 <=、lt、 gt、 eq、 le、 ge

      1
      <property name="equal" value="#{counter.total == 100}"/>
    • 逻辑运算:and、 or、 not、 |

      1
      <property name="largeCircle" value="#{shape.kind == 'circle' and shape.perimeter gt 100000}"/>
    • 条件运算:?:(ternary)、?:(Elvis)

      1
      <property name="instrument" value="#{songSelector.selectSong()=='Jingle Bells'?piano:saxophone}"/>
    • 正则表达式:matches

      1
      <property name="validEmail" value="#{admin.email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.com'}"/>

SpEL中筛选集合

设定一个City类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.springinaction.springidol;
public class City {
private String name;
private String state;
private int population;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public int getPopulation() {
return population;
}
public void setPopulation(int population) {
this.population = population;
}
}

通过Spring的元素定义一个City的List集合:

1
2
3
4
5
<util:list id="cities">
<bean class="com.springinaction.springidol.City" p:name="Chicago" p:state="IL" p:population="285114"/>
<bean class="com.springinaction.springidol.City" p:name="Atlanta" p:state="GA" p:population="537958"/>
<bean class="com.springinaction.springidol.City" p:name="Dallas" p:state="TX" p:population="1279910"/>
</util:list>

  • 访问集合成员:
    1
    2
    <property name="chosenCity" value="#{cities[1]}"/>
    <property name="chosenCity" value="#{cities[T(java.lang.Math).random * cities.size()]}"/>

选择属性的方式:

  • systemEnvironment:包含应用程序所在机器上的所有的环境变量,Propert集合
    <property name="homePath" value="#{systemEnvironment['HOME']}"/>
  • systemProperties:包含了Java应用程序启动时所设置的所有的属性
  • 查询集合成员
    查询运算符.?[],会创建一个新的集合存放符合条件的CITY:

    1
    <property name="bigCities" value="#{cities.?[population gt 100000]}"/>
    • .^[]:从集合中查询出第一个匹配项
    • .$[]:从集合中查询出最后一个匹配项
  • 投影集合
    .![]:从集合中的每一个成员中取出特定的属性放入一个新的集合中

    1
    2
    3
    <property name="CityName" value="#{cities.![name]}"/>
    <property name="CityName" value="#{cities.![name + ', ' + state]}"/>
    <property name="bigCities" value="#{cities.?[population gt 100000].![name + ', ' + state]}" />

最小化Spring XML配置


Spring提供了几种技巧解决大量Bean下XML文件的配置:

  • 自动装配(autowiring):有助于减少甚至消除配置元素和元素,让Spring自动识别如何装配Bean的依赖关系。
  • 自动检测(autodiscovery):比自动装配更进一步,让Spring能够自动识别哪些类需要被配置成Spring Bean,减少对的使用。

自动装配Bean属性

四种类型的自动装配

  • byName:把与Bean的属性具有相同名字(ID)的其他Bean自动装配到Bean的对应属性中。如果没有和属性名字相匹配的Bean,则该属性不装配。
    kenny Bean中,使用property标签显式配置了instrument的属性,如果将saxophone Bean的名字改为instrument:

    1
    2
    3
    4
    <bean id="kenny" class="com.springinaction.springidol.Instrumentalist" >
    <property name="song" value="Jingle Bells" />
    <property name="instrument" ref="saxophone"/>
    </bean>

    等价于:

    1
    2
    3
    4
    <bean id="kenny3" class="com.springinaction.springidol.Instrumentalist" autowire="byName">
    <property name="song" value="Jingle Bells" />
    </bean>
    <bean id="instrument" class="com.springinaction.springidol.saxophone"/>

    byName自动装配遵循的一项约定:为属性自动装配ID与该属性的名字相同的Bean

  • byType:把与Bean的属性具有相同类型的其他Bean自动装配到Bean的对应属性中,如果没有跟属性的类型相匹配的Bean,则该属性不装配。
    与byName类似,不过是检查属性的类型,Spring会寻找哪一个Bean的类型与属性的类型相匹配,但是当Spring找到多个Bean的类型与属性相同时,会抛出错误,应用只允许一个与属性类型相同的Bean。解决方法是为:

    * 自动装配标示一个首选Bean,有且仅有一个Bean的primary=true
    
    1
    <bean id="instrument" class="com.springinaction.springidol.saxophone" primary="true"/>
    * 取消某个Bean自动装配的资格。
    1
    <bean id="instrument" class="com.springinaction.springidol.saxophone" autowire-candidate="false" />
  • constructor:把与Bean的构造器入参具有相同类型的其他Bean自动装配到Bean构造器对应入参中去。

    1
    <bean id="duke1" class="com.springinaction.springidol.PoeticJuggler" autowire="constructor"/>

    Spring会去寻找和Bean的构造方法中的属性类型相同的Bean注入到当前Bean,局限性和byType一样。多个合适的Bean或者多个构造器都会出错。

  • autodetect:首先尝试用constructor进行自动装配,如果失败,再尝试用byType进行自动装配。

默认自动装配

1
<beans ... default-autowire="byType">

该参数的默认值为none,特定Bean可以设定其自动装配方式来覆盖默认的装配方式。

混合使用自动装配和显示装配

对某个Bean选择了自动装配策略,同时也可以进行显示装配。可以通过显示装配来解决自动装配中存在的不确定的问题,覆盖掉其自动装配的值。

1
2
3
4
<bean id="kenny4" class="com.springinaction.springidol.Instrumentalist" autowire="byType">
<property name="song" value="Jingle Bell" />
<property name="instrument"><null /></property>
</bean>

  • NOTE:不能混合使用constructor自动装配策略和元素

使用注解装配

Spring默认不使用注解,使用注解需要在XML中进行配置:

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<context:annotation-config />
</beans>

Spring支持几种不同的用于自动装配的注解:

* Spring自带的@Autowired注解
* JSR-330的@Inject注解
* JSR-250的@Resource注解

使用@Autowired注解

对属性的setter方法添加注解:

1
2
3
4
@Autowired
public void setInstrument(Instrument instrument) {
this.instrument = instrument;
}

Autowired注解可以用在setter方法,任意方法以及构造方法。当对构造器进行标注时,Autowired注解表示当创建Bean时,即使在Spring XML文件中没有使用元素配置Bean,该构造器也需要进行自动装配。我们也可以使用@AutoWired注解直接标注属性,并删除setter方法。@AutoWired存在问题在于没有Bean装配到注解属性或者多个Bean装配到注解属性:

  • 可选的自动装配:@Autowired所标注的属性和参数必须是可装配的,如果没有Bean装配到所标注的属性和参数时,会报错。设定required参数来限定@Autowired,当没有找到可装载的Bean时,属性和参数的值为NULL。

    1
    2
    @Autowired(required = false)
    private Instrument instrument;

    但是当其注解构造方法时,只能有一个构造器的required的值为true。

  • 限定歧义性的依赖:当有同时满足装配的条件的多个Bean存在时,Spring会抛出NoSuchBeanDefineException错误,表明装配失败。使用@Qualifier注解来制定装配的具体Bean:

    1
    2
    3
    @AutoWired
    @Qualifier("guitar")
    private Instrument instrument;

    XML配置也可以同样使用qualifier标签来缩小Bean搜索范围:

    1
    2
    3
    <bean class="com.springinaction.springidol.Piano">
    <qualifier value="piano"/>
    </bean>
  • 自定义的限定器
    使用@Qualifier作为自定义注解的元注解:@

    1
    2
    3
    @Qualifier
    public @Interface StringedInstrument{
    }

    这样就可以用@StringedInstrument注解来标注guitar

    1
    2
    3
    @StringedInstrument
    public class Guitar implements Instrument{
    }

    自动装配时对Instrument进行限定,从而查找所有被StringedInstrument标注的Bean,不能确定到一个Bean会再进行其他限定。

    使用@Autowired的缺点在于引入了对Spring注解的依赖,可以使用Java的@Inject注解来解决这个问题

@Inject注解

与Spring的@Autowired注解类似,JCP的@Inject注解可以用来自动装配属性、方法和构造器;区别在于@Inject没有required属性,所有其所标注的依赖关系必须存在!

  • 限定@Inject使用注解@Named

    1
    2
    3
    @Inject
    @Named("guitar")
    private Instrument Instrument;

    @Named注解表明选择了一个Bean,@Qualifier表明满足条件的Bean

  • 自定义的JSR-330 Qualifier

    1
    2
    3
    @Qualifier
    public @Interface StringedInstrument{
    }

    区别在于这里的@Qualifier来自JSR-330而不是Spring

注解中使用表达式

@Value可以用来装配String类型的值和基本类型的值:

1
2
@value("Eruption")
private String song;

SpEL和注解结合,实现注解驱动的装配方式:

1
2
@value("#{systemProperties.myFavoriteSong}")
private String song;

自动检测Bean

<context:annotation-config>可以消除Bean里面的配置,<context:component-scan>还可以完成自动检测Bean的功能,从而XML中不需要创建Bean:

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<context:component-scan base-package="com.springinaction.springidol"/>
</beans>

标注Bean

context:component-scan默认查找构造型注解所标注的类:

  • @Component 通用的构造型注解,标示该类为Spring组件
  • @Controller 标示该类定义为SpringMVC controller
  • @Repository 标识将该类定义为数据仓库
  • @Service 标识将该类定义为服务
  • @Component也可以标注自定义注解
1
2
3
4
5
6
7
8
9
10
11
package com.springinaction.springidol;
import org.springframework.stereotype.Component;
@Component
public class Guitar implements Instrument{
@Override
public void play() {
System.out.println("ssss ddd fff .");
}
}

Spring扫描com.springinaction.springidol包时,会发现@Component注解标识的Guitar类,将其自动注册为Spring Bean。默认ID为guitar。

1
2
3
4
5
6
7
8
9
package com.springinaction.springidol;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component("eddie")
public class Instrumentalist implements Performer{
...
}

过滤组件扫描

Spring默认会扫描包下的所有类的@Component注解来生成Bean,我们可以自己定义过滤规则来实现自动注册特定的类:

1
2
3
<context:component-scan base-package="com.springinaction.springidol">
<context:include-filter type="assignable" expression="com.springinaction.springidol.Instrument"
</context:component-scan>

可以自动将所有派生与Instrument的类注册到Spring Bean中。
过滤器类型包含5种:

  • annotation:顾虑器扫描使用指定注解所标注的那些类,通过expression属性指定要扫描的注解
  • assignable:过滤器通过扫描派生于expression属性所指定的那些类
  • aspectj:过滤器扫描与expression属性所指定的AspectJ表达式所匹配的类
  • custom:使用自定义的org.springframework.core.type.TypeFilter实现类,该类由expression属性指定
  • regex:过滤器扫描类的名称与expression属性所指定的正则表达式所匹配的那些类。

context:exclude-filter表示不注册那些类:

1
2
3
4
<context:component-scan base-package="com.springinaction.springidol">
<context:include-filter type="assignable" expression="com.springinaction.springidol.Instrument"/>
<context:exclude-filter type="assignable" expression="com.springinaction.springidol.Instrumentalist"/>
</context:component-scan>

使用Spring基于Java的配置

启动Java配置

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<context:component-scan base-package="com.springinaction.springidol"/>
</beans>

context:component-scan会自动加载@Configuration注解的类。

定义一个配置类

1
2
3
4
5
6
7
8
package com.springinaction.springidol;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SpringIdolConfig {
}

@Configuration注解会告知Spring,这个类将包含一个或者多个Bean的定义。

声明一个Bean

1
2
3
4
@Bean
public Performer duke() {
return new Juggler();
}

等价于XML中配置标签。@Bean告知Spring这个方法将返回一个对象,该对象应该被注册到Spring应用上下文中的一个Bean。

使用Spring的基于Java的配置进行注入

1
2
3
4
@Bean
public Performer duke10() {
return new Juggle(15);
}
1
2
3
4
5
6
7
8
9
@Bean
private Poem sonnet29(){
return new sonnet29();
}
@Bean
private Performer pueticDuke(){
return new PoeticJuggler(sonnet29());
}

面向切面的Spring

两个概念:

  • 横切关注点:分布于应用中的多处的功能,例如安全、日志、事务管理等。
  • AOP:将横切关注点和业务逻辑进行分离

面向切面编程

Spring中首先在一个地方定义通用功能,然后通过声明的方式定义一个功能以何种方式在何处应用,而无需修改受影响的类。横切关注点可以被模块化为特殊的类,这些类成为切面。其好处有两点:

  • 每个关注点集中与移除
  • 服务模块更简洁

定义AOP术语

  • 通知(Advice):通知定义了切面是什么以及如何使用。Spring切面可以应用的5中类型的通知:

    • Before:在方法被调用之前调用通知
    • After:在方法完成之后调用通知,无论方法执行是否成功
    • After-retruning:在方法成功执行之后调用通知
    • After-throwing:在方法抛出异常后调用通知
    • Around:通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
  • 连接点(Joinpoint):在应用执行过程中能够插入切面的一个点。

  • 切点(Poincut):指定了切面应用的范围。
  • 切面(Aspect):切面是切点和通知的结合。
  • 引入(Introduction):引用允许我们向现有的类添加新方法和属性。
  • 织入(Weaving):织入是将切面应用到目标对象来创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中去。在目标对象的声明周期中有多个点可以进行织入:
    • 编译期:需要特殊的编译器,AspectJ的织入编译器就是以这种方式织入的。
    • 类加载期:切面在目标类加载到JVM时被织入。这种方式需要特殊的类加载器(ClassLoader),在引入之前增强该类的字节码。
    • 运行期:切面在应用运行的某个时刻被织入。一般在织入切面时,AOP容器会为目标对象动态的创建一个代理对象。Spring AOP就是这样。

Spring对AOP的支持

Spring提供了4种AOP支持:

  • 基于代理的经典AOP
  • @AspectJ注解驱动的切面
  • 纯POJO切面
  • 注入式AspectJ切面

一些关键点:

  • Spring通知是Java编写的
  • Spring在运行期间通知对象
  • Spring只支持方法连接点

使用切点选择连接点

Spring AOP中需要使用AspectJ的切点表达式语言来定义切点。

AspectJ指示器 描述
arg() 限制连接点匹配参数为指定类型的执行方法
@args() 限制连接点匹配参数由指定注解的执行方法
execution() 用于匹配是连接点的执行方法
this() 限制连接点匹配AOP代理的Bean引用为指定类型的类
target() 限制连接点匹配目标对象为指定类型的类
@target() 限制连接点匹配特定的执行对象,这些对象对应的类要具备指定类型的注解
within() 限制连接点匹配指定的类型
@within() 限制连接点匹配指定注解所标注的类型
@annotation() 限制匹配带有指定注解连接点

只有execution指示器是唯一的执行匹配,其他都是限制匹配。

编写切点

execution(* com.springinaction.springidol.Instrument.play(..))
我们使用execution()指示器去选择Instrument的play()方法。方法表达式以开始,标示了方法返回值的类型,然后指定全限定类名和方法名,使用(..)标示切点选择任意的play方法。
如果需要限定匹配该包下的play方法,需要添加限定
`execution(
com.springinaction.springidol.Instrument.play(..)) and withi n(com.springinaction.springidol.*)`

使用Spring的bean()指示器

bean()指示器允许我们在切点表达式中使用Bean的ID来标识Bean。
execution(* com.springinaction.springidol.Instrument.play() and bean(eddie))表示限定Bean的ID为Eddie。

在XML中声明切面

Spring的AOP配置元素简化了基于POJO切面的声明:

AOP配置元素 描述
定义AOP通知器
定义AOP后置通知,不管通知的方法是否执行成功
定义AOP After-retruning通知
定义After-throwing通知
定义AOP环绕通知
定义切面
启用@AspectJ注解驱动的切面
定义AOP前置通知
顶层的AOP配置元素,大多数的元素必须在元素内部
为被通知的对象引入额外的接口,并透明的实现
定义切点

选秀节目需要装配观众,每一个选手都需要装配同样的观众,创建一个观众类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.springinaction.springidol;
public class Audience {
//before perform
public void takeSeats() {
System.out.println("The Audience is taking their seats!");
}
public void turnOffCellPhone() {
System.out.println("The audience is turning off their cell phones.");
}
//after perform
public void applaud() {
System.out.println("Clap! Clap! Clap!");
}
//after perform failure
public void demandRefund() {
System.out.println("We want our money back!");
}
}

将其注册为Spring上下文的一个Bean:<bean id="audience" class="com.springinaction.springidol.Audience"/>

Donate comment here