Spring 基础篇 - Spring In Action 读书笔记

本贴最后更新于 2959 天前,其中的信息可能已经时过境迁

一、 Spring 之旅

1. 简化 Java 开发

为了降低 Java 开发的复杂性,Spring 采取了以下 4 种关键策略:

  • 基于 POJO 的轻量级和最小入侵性编程;
  • 通过依赖注入和面向接口实现松耦合;
  • 基于切面和惯例进行声明式编程;
  • 通过切面和模板减少样板式代码。

2. 依赖注入

通常,每个对象负责管理与自己相互协作的对象(即它所依赖的对象)的引用,这将会导致高度耦合和难以测试的代码。如下面的 Knight 类:

3. 应用切面

系统由许多不同组件组成,每一个组件各负责一块特定的功能。除了实现自身核心的功能之外,这些组件还经常承担着额外的职责。诸如之日、事务管理和安全此类的系统服务经常融入到自身核心业务逻辑的组件中去,这些系统服务通常被称为横切关注点,因为它们总是跨越系统的多个组件。

AOP 使这些服务模块化,并以声明的方式将它们应用到它们需要影响的组件中去。结果是这些组件具有更高内聚性以及更加关注自身业务,完全不需要了解可能涉及的系统服务的复杂性。如下图所示:

image

下面有个吟游诗人类以记载骑士的所有事迹:

然而,吟游诗人这种通用功能分散到多个组件中,将给代码引入双重复杂性:

  • 遍布系统的关注点实现代码将会重复出现在多个组件中。这意味如果要改变这些关注点的逻辑,你必须修改各个模块的相关实现。即使你把这些关注点抽象为一个独立的模块,其他模块只是调用它的方法,但方法的调用还是重复出现在各个模块中;
  • 你的组件会因为那些与自身核心业务无关的代码儿变得混乱。

把 Minstrel 抽象为一个切面,只需要在一个 Spring 配置文件中声明它:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="..." xmlns:xsi="..." xmlns:aop="..." xsi:schemaLocation="...">
 <bean id="knight" class="com.springinaction.knights.BraveKnight">
  <constructor-arg ref="quest"/>
 </bean>
 <bean id="quest" class="com.springinaction.knights.SlayDragonQuest"/>
 <bean id="minstrel" class="com.springinaction.knights.Minstrel"/>
 <aop:config>
  <aop:aspect ref="minstreal">
   <aop:pointcut id="embark" expression="execution(* *.embarkOnQuest(..))"/>
   <aop:before pointcut-ref="embark" method="singBeforeQuest"/>
   <aop:after pointcut-ref="embark" method="singAfterQuest"/>
  </aop:aspect>
 </aop:config>
</beans>

4. Spring 容器

在基于 Spring 的应用中,应用对象生存于 Spring 容器中,Spring 容器创建对象,装配它们,配置它们,管理它们的整个生命周期,从生存到死亡(或者从创建到销毁)。Spring 自带两类容器实现:Bean 工厂及应用上下文,后者更为常用,且分为以下三种:

  • ClassPathXmlApplicationContext:从类路径下的 XML 配置文件中加载上下文定义,把应用上下文定义文件当作类资源。
  • FileSystemXmlApplicationContext:读取文件系统下的 XML 配置文件并加载上下文定义。
  • XmlWebApplicationContext:读取 Web 应用下的 XML 配置文件并装载上下文定义。

通过现有的应用上下文引用,可以调用上下文的 getBean()方法从 Spring 容器中获取 Bean。

二、 装配 Bean

1. 声明 Bean

假设有一个选秀比赛,有各种各样的人上台表演。定义一个表演者接口如下:

Spring 配置文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="..." xmlns:xsi="..." xsi:schemaLocation="...">
 <bean id="duke" class="com.springinaction.springidol.Juggler">
  <!--构造器注入属性值-->
  <constructor-arg value="15"/>
 <bean>
</beans>

如此,可以通过上下文获取到这个 Bean 并调用其 perform 方法。

假设一个 Bean 中引用了另一个 bean,比如一个会朗诵诗歌的杂技演员,定义如下:

Poem 的一个实现如下:

可以在配置文件中,通过 factory-method 属性,调用一个指定的静态方法,从而代替构造方法来创建一个类的实例:

2. Bean 作用域

所有的 Spring Bean 默认都是单例。当容器分配一个 Bean 时(不论通过装配还是调用 getBean()方法),它总是返回 Bean 的同一个实例。

在配置文件中,可以通过指定 scope 属性的值来设置 Bean 的作用域:
作用域 定义
singleton 在每一个 Spring 容器中,一个 Bean 定义只有一个对象实例(默认)
prototype 允许 Bean 的定义可以被实例化任意次(每次调用都创建一个实例)
request 在一次 HTTP 请求中,每个 Bean 定义对应一个实例
session 在一个 HTTP Session 中,每个 Bean 定义对应一个实例
global-session 在一个全局 HTTP Session 中,每个 Bean 定义对应一个实例。

3. 初始化和销毁 Bean

为 Bean 定义初始化和销毁操作,只需要使用 init-method 和 destroy-method 参数来配置元素。假设有一个 Auditoium 类,有 turnOnLights()和 turnOffLights()两种方法,可以进行如下配置:

<bean id="auditorium" class="com.springinaction.springidol.Auditorium"
 init-method="turnOnLights" destory-method="turnOffLights"/>

如果很多 Bean 都拥有相同名字的初始化和销毁方法,可以定义一个默认公共属性:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="..." xmlns:xsi="..." xsi:schemaLocation="..."
default-init-method="commonInit"
default-destory-method="commonDestory">
...
</beans>

4. 注入 Bean 属性

之前是构造器注入,接下来演示另一种注入方法,setter 方法注入:

5. 使用 Spring 的命名空间 p 装配属性

只是写法上的不同,在 Spring 的 XML 配置前加上特定的声明即可:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="..." xmlns:p="http://www.springframework.org/schema/p"
 xmlns:xsi="..." xsi:schemaLocation="...">
 <bean id="kenny" class="com.springinaction.springidol.Instrumentalist"
  p:song="Jingle Bells"
  p:instrument-ref="saxophone"/>
<beans>

6. 装配集合

Spring 提供了 4 种类型的集合配置元素

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

假设有如下表演者定义:

配置如下:

<bean id="hank" class="com.springinaction.springido.OneManBand">
 <property name="instruments">
  <map>
   <entry key="GUITAR" value-ref="guitar"/>
   <entry key="CYMBAL" value-ref="cymbal"/>
   <entry key="HARMONICA" value-ref="harmonica"/>
  </map>
 </property>
</bean>

键可能是其他 bean 的引用,此时可用属性 key-ref 指定,值可能为简单值,可用 value 指定。

装配 Properties 集合:

如果所配置的 Map 的每一个 entry 键和值都为 String 类型时,可以考虑使用 java.util.Properties 代替。其功能和 Map 大致相同,但是限定键值必须为 String 类型。修改 instruments 属性如下:

private Properties instruments;
public void setInstruments(Properties instruments){
this.instruments=instruments;
}

配置如下:

<bean id="hank" class="com.springinaction.springidol.OneManBand">
 <property name="instruments">
  <props>
   <prop key="GUITAR">STRUM STRUM STRUM</prop>
   <prop key="CYMBAL">CRASH CRASH CRASH</prop>
   <prop key="HARMONICA">HUM HUM HUM</prop>
  </props>
 </property>
</bean>

装配空值:

使用元素

7. SpEL 表达式

SpEL 是一种强大、简洁的装配 Bean 的方式,它通过运行期执行的表达式将值装配到 Bean 的属性或构造器参数中。

字面值:

<property name="count" value="#{5}"/>
<!--可以与非SpEL表达式混用-->
<property name="message" value="The value is #{5}"/>
<!--使用浮点数-->
<proeprty name="frequency" value="#{89.7"/>
<!--使用科学计数法-->
<property name="capacity" value="#{1e4}"/>
<!--使用字符串-->
<proeprty name="name" value="#{'Chuck'}"/>
<!--使用布尔值-->
<property name="enabled" value="#{false}"/>

引用 Bean、Properties 和方法:

<!--等价于ref="saxophone"-->
<proeprty name="instrument" value="#{saxophone}"/>
<!--获取Bean属性-->
<property name="song" value="#{kenny.song}"/>
<!--调用Bean的某个方法-->
<property name="song" value="#{songSelector.selectSong()}"/>
<!--可以对方法的返回值再次调用一个方法(应该是类型相关)-->
<property name="song" value="#{songSelector.selectSong().toUpperCase()}"/>
<!--使用null-safe存取器保证SpEL表达式值不为NULL-->
<property name="song" value="#{songSelector.selectSong()?.toUpperCase()}"/>

装配指定类的静态方法和常量:

8. 在 SpEL 值上执行操作

  • 算术运算:+、-、*、/、%、^
  • 关系运算:<、>、==、<=、>=、lt、gt、eq、le、ge
  • 逻辑运算:and、or、not、|
  • 条件运算:?:
  • 正则表达式:matches

数值运算:

<!--使用+加运算符-->
<property name="adjustedAmount" value="#{counter.total+42}"/>
<!--使用-减运算符-->
<property name="adjustedAmount" vlaue="#{counter.total-20}"/>
<!--使用*乘运算符-->
<property name="circumference" value="#{2*T(java.lang.Math).PI*circle.radius}"/>
<!--使用%求余运算符-->
<property name="remainder" value="#{counter.total%counter.count}"/>
<!--使用^乘方运算符-->
<property name="area" value="#{T(java.lang.Math).PI*circle.radius^2}"/>
<!--使用+字符串连接-->
<property name="fullName" value="#{performer.firstName+' '+performer.lastName}"/>

比较值:

可以使用符号,也可以使用文本替代,因为 XML 中小于等于和大于等于符号有特殊含义,故而推荐后者:

<property name="equal" value="#{counter.total==100}"/>
<property name="hasCapacity" value="#{counter.total le 100000}"/>

逻辑表达式:

<proeprty name="largeCircle" value="#{shape.kind=='circle' and shape.perimeter gt 10000}"/>
<property name="outOfStock" value="#{!product.available}"/>
<property name="outOfStock" vlaue="#{not product.available}"/>

条件表达式:

<property name="song" value="#{kenny.song!=null?kenny.song:'Greensleeves'}"/>
<!--判断是否为NULL时,可以省略中间的选项-->
<property name="song" value="#{kenny.song ?: 'Greensleeves'}"/>

正则表达式:

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

9. 在 SpEL 中筛选集合

假设有如下 City 类(省略 setter 和 getter 方法)

util:list 元素定义一个 City 的 List 集合如下:

<util:list id="cities">
 <bean class="com.habuma.spel.cities.City"
  p:name="Chicago" p:state="IL" p:population="2853114"/>
 <bean class="com.habuma.spel.cities.City"
  p:name="Atlanta" p:state="GA" p:population="537958"/>
 <bean class="com.habuma.spel.cities.City"
  p:name="Dallas" p:state="IL" p:population="1279910"/>
 <bean class="com.habuma.spel.cities.City"
  p:name="Houston" p:state="IL" p:population="2242193"/>
 <bean class="com.habuma.spel.cities.City"
  p:name="Odessa" p:state="IL" p:population="90943"/>
 <bean class="com.habuma.spel.cities.City"
  p:name="El Paso" p:state="IL" p:population="613190"/>
 <bean class="com.habuma.spel.cities.City"
  p:name="Jal" p:state="IL" p:population="1996"/>
 <bean class="com.habuma.spel.cities.City"
  p:name="Las Cruces" p:state="IL" p:population="91865"/>
</util:list>

访问集合成员:

[]运算符也可以用来获取 java.util.Map 集合中的成员,假设 City 对象以其名字作为键放入 Map 集合中:

<property name="chosencity" value="#{cities['Dallas']}"/>

[]运算符的另一种用法是从 java.util.Properties 集合中获取值,如通过 元素在 Spring 中加在一个 properties 配置文件如下:

<util:properties id="setttings" location="classpath:settings.properties"/>

访问属性如下:

<property name="accessToken" value="#{settings['twitter.accessToken']}"/>

Spring 提供了两种特殊的选择属性方式:SystemEnvironment 和 SystemProperties,这两个都是一种 properties。

[]运算符同样可以通过索引来得到字符串的某个字符,如'This is a test'[3]将返回”s“。

使用.?[]查询运算符:

<property name="bigCities" value="#{cities.?[population gt 100000]}"/>

.^[]查询第一个匹配项:

<property name="aBigCity" value="#{cities.^[population gt 100000]}"/>

.$[]查询最后一个匹配项:

<property name="aBigCity" value="#{cities.$[population gt 100000]}"/>

.![]投影运算符:

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

三、 最小化 Spring XML 配置

1. 自动装配 Bean 属性

Spring 提供了 4 种自动装配策略:

  • byName:把与 Bean 的属性具有相同名字(或者 ID)的其他 Bean 自动装配到 Bean 的对应属性中。如果没有,则该属性不装配。
<bean id="kenny" class="com.springinaction.springidol.Instrumentalist" autowire="byName">
 <property name="song" value="Jingle Bells"/>
</bean>
<bean id="instrument" class="com.springinaction.springidol.Saxophone"/>

如此,kenny 中的 instrument 属性可以自动将 id 为 instrument 的 bean 装配进来。

  • byType:把与 Bean 的属性具有相同类型的其他 Bean 自动装配到 Bean 的对应属性中。如果没有,则该属性不装配。

与 byName 类似,只不过 autowire 属性值设置为"byType"。如果 Spring 寻找到多个 Bean,它们的类型与需要自动装配的属性的类型都相匹配,则 Spring 将抛出异常。如此,可以设置其中一个为首选 Bean,默认都是首选,故反向用之,设置为非首选。也可以将其他 Bean 设置为非候选:

<bean id="saxophone" class="com.springinaction.springidol.Saxophone" primary="false"/>
<bean id="saxophone" class="com.springinaction.springidol.Saxophone" autowire-candidate="false"/>
  • constructor:把与 Bean 的构造器入参具有相同类型的其他 Bean 自动装配到 Bean 构造器的对应参中。

类似于 byType,只不过是根据构造函数的入参类型进行匹配:

<bean id="duke" class="com.springinaction.springidol.PoteicJugger" autowire="constructor"/>
  • autodetect:首先尝试使用 constructor 进行自动装配,如果失败,再尝试使用 byType 进行自动装配

2. 默认自动装配及混合装配

如果要为 Spring 应用上下文中的每一个 Bean 或大多数配置相同的 autowire 属性,那么可以设置默认自动装配策略:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="..." xmlns:xsi="..." xsi:schemaLocation="..." default-autowire="byType">
</beans>

可以混合使用自动装配和手工装配,手动装配将覆盖自动装配,如此可以解决 byType 可能产生的装配不确定性问题。

<bean id="kenny" class="com.springinaction.springidol.Instrumentalist" autowire="byType">
 <property name="song" value="Jingle Bells"/>
 <property name="instrument" ref="saxophone"/>
</bean>

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

3. 使用注解装配

Spring 容器默认禁用注解装配。启用需要在 Spring 配置中,如下:

<?xml version="1.0" encoding="UTF-8"?> 
<beans xmlns="..." xmlns:xsi="..." xmlns:context="..." xsi:schemaLocation="..."> 
 <context:annotation-config/> 
</beans>

有如下三种注解:

  • Spring 自带的 @Autowired 注解;
  • JSR-330 的 @Inject 注解;
  • JSR-250 的 @Resource 注解。

@Autowired

可以对 setter 方法进行注解:

也可以标注需要自动装配 Bean 引用的任意方法:

@Autowired 
public void heresYourInstrument(Instrument instrument){ 
 this.instrument=instrument; 
}

甚至可以标注构造器:

@Autowired 
public Instrumentalist(Instrument instrument){ 
 this.instrument=instrument; 
}

此时,即便配置文件中没有使用配置 Bean,该构造器也需要进行自动装配。

还可以直接标注属性,并删除 setter 方法:

@Autowired 
private Instrument instrument;

应用中必须只能有一个 Bean 适合装配到 @Autowired 注解所标注的属性或参数中。否则就会遇到麻烦。

如果属性不一定非要装配,null 值也是可以接受的,则可通过设置 @Autowired 的 required 属性为 false 来配置自动装配是可选的,如:

@Autowired(required=false) 
private Instrument instrument;

required 属性可以用于 @Autowired 注解所使用的任意地方。但是当使用构造器装配时,只有一个构造器可以将 @Autowired 的 required 属性设置为 true,其他只能为 false。此外,当使用 @Autowired 标注多个构造器时,Spring 就会从所有满足装配条件的构造器中选择入参最多的那个构造器。

也可以对自动装配的 Bean 添加限定来保证 @Autowired 能够自动装配,如制定 ID 为 guitar 的 Bean:

@Autowired 
@Qualifier("guitar")
private Instrument instrument;

除了通过 Bean 的 ID 来缩小选择范围,也可通过在 Bean 上直接使用 qualifier 来缩小范围:

<bean class="com.springinaction.springidol.Guitar"> 
 <qualifier value="stringed"/> 
</bean>

又或者在类中添加限定注解如下:

@Qualifier("stringed") 
public class Guitar implements Instrument{ 
 ...
}

可以创建一个自定义的限定器,如此只有添加了该自定义限定的 Bean 类才能被装配:

4. 自动检测 Bean

使用 除了完成与 一样的工作,还允许 Spring 自动检测 Bean 和定义 Bean:

<beans xmlns="..." xmlns:xsi="..." xmlns:context="..." xsi:schemaLocation="...">
<context:component-scan base-package="com.springinaction.springidol">
</beans>

使用如下注解为自动检测标注 Bean:

  • @Component:通用的构造型注解,标识该类为 Spring 组件。
  • @Controller:标识将该类定义为 Spring MVC controller。
  • @Repository:标识将该类定义为数据仓库
  • @Service:标识将该类定义为服务

使用 @Component 标注的 Bean,默认 ID 为小写类名,可以显示指定 ID 如:@Component("eddie")

过滤组件扫描:

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

上述配置实现了自动注册所有的 Instrument 实现类。过滤器类型如下:

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

除了使用 告知 哪些类需要注册为 Spring Bean 以外,还可以使用 来告知 哪些类不需要注册为 Spring Bean。

5. 使用 Spring 基于 Java 的配置

首先需要少量的 XML 启用 Java 配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="..." xmlns:xsi="..." xmlns:context="..." xsi:schemaLocation="...">
<context:component-scan base-package="com.springination.springidol"/>
</beans>

也即自动注册相关 Bean,作为配置的 Java 类使用 @Configuration 标注,其通过该配置被扫描到。

被 @Configuration 注解的 Java 类就等价于 XML 配置中的:

四、 面向切面的 Spring

1. 定义 AOP 术语

通知(Advice):

通知定义了切面是什么以及何时使用。Spring 切面可以应用 5 种类型的通知

  • Before:在方法被调用之前调用通知
  • After:在方法完成之后调用通知,无论方法执行是否成功
  • After-returning:在方法成功执行之后调用通知
  • After-throwwing:在方法抛出异常后调用通知
  • Around:在方法调用之前和之后执行通知自定义的行为。

连接点(Joinpoint):

连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。

切点(Poincut):

切点的定义会匹配通知所要织入的一个或多个连接点。通常使用明确的类和方法名称来指定这些切点,或是利用正则表达式定义匹配的类和方法名称模式来指定这些切点。

切面(Aspect):

切面是通知和切点的结合。通知和切点共同定义了关于切面的全部内容。

引入(Introduction):

引入允许我们向现有的类添加新方法或属性。

织入(Weaving):

织入是将切面应用到目标对象来创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。在目标对象的生命周期里有多个点可以进行织入。

Spring 提供了 4 种各具特色的 AOP 支持:

  • 基于代理的经典 AOP;
  • @AspectJ 注解驱动的切面;
  • 纯 POJO 切面;
  • 注入式 AspectJ 切面(适合 Spring 各版本)

Spring 所创建的通知都是用标准的 Java 类编写的,定义通知所应用的切点通常在 Spring 配置文件里采用 XML 来编写的。通过在代理类中包裹切面,Spring 在运行期将切面织入到 Spring 管理的 Bean 中。Spring 只支持方法连接点。

2. 使用切点选择连接点

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

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

编写切点:

下面的切点表达式表示当 Instrument 的 play()方法执行时会触发通知:

execution(* com.springinaction.springidol.Instrument.play(..))
&&within(com.springinaction.springidol.*)

&&操作符后面的 within 限定了匹配范围,添加 and 条件。还可以使用 || 操作符标识 or 关系,!操作符标识非操作。在基于 XML 配置来描述切点时,可以使用 and、or 和 not 代替。

使用 Spring 的 bean()指示器:

在执行 Instrument 的 play()方法应用通知,但限定 Bean 的 ID 为 eddie,可以定义切点如下:

execution(* com.springinaction.springidol.Instrument.play()) and bean(eddie)

还可以定义为除了 eddie 的 Bean:

execution(* com.springinaction.springidol.Instrument.play()) and !bean(eddie)

3. 在 XML 中声明切面

  • :定义 AOP 通知器
  • :定义 AOP 后置通知(不管被通知的方法是否执行成功)
  • :定义成功执行后通知
  • :定义执行失败后通知
  • :定义 AOP 环绕通知
  • :定义切面
  • :启用 @AspectJ 注解驱动的切面
  • :定义 AOP 前置通知
  • :顶层 AOP 配置元素
  • :为被通知的对象引入额外的接口,并透明地实现
  • :定义切点

定义如下观众类:

有如下实现类:

思想者实现类如下:

4. 注解切面

创建新的 Audience 类如下,通过注解将其标注为一个切面:

aop:aspectj-autoproxy/

为了使用该元素,需要在 Spring 的配置文件中包含 aop 命名空间:

<beans xmlns=http://www.springframework.org/schema/beans 
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 xmlns:aop="http://www.springframework.org/schema/beans 
 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
 http://www.springframework.org/schema/aop 
 http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

注解环绕通知

@Around("performance()") 
public void watchPerformance(ProceedingJoinPoint joinpoint){ 
 try{ 
  System.out.println("The audience is taking their seats."); 
  System.out.println("The audience is turning off their cellphones"); 
  long start=System.currentTimeMillis(); 
  joinpoint.proceed(); 
  long end=System.currentTimeMillis(); 
  System.out.println("CLAP CLAP CLAP CLAP CLAP"); 
  System.out.println("The performance took "+(end-start)+" milliseconds."); 
 }catch(Throwable t){ 
  System.out.println("Boo! We want our money back!"); 
 } 
}

传递参数给所标注的通知

五、 Spring JDBC

1. 配置数据源

使用 JNDI 数据源:

利用 Spring,可以像使用 Spring Bean 那样配置 JNDI 中数据源的引用并将其配置到需要的类中:

<jee:jndi-lookup id="dataSource" jndi-name="/jdbc/SpitterDS" resource-ref="true"/>

其中,jndi-name 属性用于指定 JNDI 中资源的名称。如果应用程序运行在 Java 应用程序服务器中,需要将 resource-ref 设置为 true,这样给定的 jndi-name 将自动添加 java:comp/env/前缀。

使用数据源连接池:

Spring 并没有提供数据源连接池实现,可以使用 DBCP(http://jakarta.apache.org/commons/dbcp)项目。可以如下配置:

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
 <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
 <property name="url" value="jdbc:hsqldb.hsql://localhost/spitter/spitter"/>
 <property name="username" value="sa"/>
 <property name="password" value=""/>
 <property name="initialSize" value="5"/>
 <property name="maxActive" value="10"/>
</bean>

BasicDataSource 的池配置属性

  • initialSize:池启动时创建的连接数量
  • maxActive:同一时间可从池中分配的最多连接数。如果设置为 0,表示无限制
  • maxIdle:池里不会被释放的最多空闲连接数。如果设置为 0,表示无限制
  • maxOpenPreparedStatements:在同一时间能够从语句池中分配的预处理语句的最大数量。如果设置为 0,表示无限制。
  • maxWait:在抛出异常之前,池池等待连接回收的最大时间(当没有可用连接时)。如果设置为-1,表示无限等待
  • minEvictableIdleTimeMillis:连接在池中保持空闲而不被回收的最大时间
  • minIdle:在不创建新连接的情况下,池中保持空闲的最小连接数
  • poolPreparedStatements:是否对预处理语句进行池管理(布尔值)

基于 JDBC 驱动的数据源:

Spring 提供了两种数据源对象(均位于 org.springframework.jdbc.datasource 包中):

  • DriverManagerDataSource:在每个连接请求时都会返回一个新建的连接。
  • SingleConnectionDataSource:在每个连接请求时都会返回同一个连接。

配置如下:

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
 <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
 <property name="url" value="jdbc:hsqldb:hsql://localhost/spitter/spitter"/>
 <property name="username" value="sa"/>
 <property name="password" value=""/>
</bean>

2. 在 Spring 中使用 JDBC

使用 SimpleJdbcTemplate 访问数据

在 Spring 中作如下配置:

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.simple.SimpleJdbcTemplate">
 <constructor-arg ref="dataSource"/>
</bean>
<bean id="spitterDao" class="com.habuma.spitter.persistence.SimpleJdbcTemplateSpitterDao">
 <property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>

将 jdbcTemplate 装配到 DAO 中并使用它来访问数据库:

public class JdbcSpitterDAO implements SpitterDAO{
…
 private SimpleJdbcTemplate jdbcTemplate;
 public void setJdbcTemplate(SimpleJdbcTemplate jdbcTemplate){
  this.jdbcTemplate=jdbcTemplate;
 }
}

添加、查询方法如下:

public void addSpitter(Spitter spitter){
 jdbcTemplate.update(SQL_INSERT_SPITTER,
  spitter.getUsername(),
  spitter.getPassword(),
  spitter.getFullName(),
  spitter.getEmail(),
  spitter.isUpdateByEmail());
 spitter.setId(queryForIdentity());
}
public Spitter getSpitterById(long id){
 return jdbcTemplate.queryForObject(
  SQL_SELECT_SPITTER_BY_ID,
  new ParameterizedRowMapper<Spitter>(){
   public Spitter mapRow(ResultSet rs,int rowNum) throws SQLException{
    Spitter spitter=new Spitter();
    spitter.setId(rs.getLong(1));
    spitter.setUsername(rs.getString(2));
    spitter.setPassword(rs.getString(3));
    spitter.setFullName(rs.getString(4));
    return spitter;
   }
  },
  id
 );
}

上例中,queryForObject()方法有 3 个参数:

  • String,包含了要从数据库中查找数据的 SQL;
  • ParameterizedRowMappter 对象,用来从 ResultSet 中提取值并构建域对象
  • 可变参数列表,列出了要绑定到查询上的索引参数值

使用命名参数

SQL 语句应当如下定义:

private static final String SQL_INSERT_SPITTER=
 "insert into spitter (username,password,fullname) "+
 "values(:username,:password,:fullname)";

方法可以改写如下:

public void addSpitter(Spitter spitter){
 Map<String,Object> params=new HashMap<String,Object>();
 params.put("username",spitter.getUsername());
 params.put("password",spitter.getPassword());
 params.put("fullname",spitter.getFullName());
 jdbcTemplate.update(SQL_INSERT_SPITTER,params);
 spitter.setId(queryForIdentity());
}

六、 Spring MVC

1. 搭建 SpringMVC

在 web.xml 中添加如下 servlet 声明:

<servlet>
 <servlet-name>spitter</servlet-name>
 <servlet-class>
  org.springframework.web.servlet.DispatcherServlet
 </servlet-class>
 <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
 <servlet-name>spitter</servlet-name>
 <url-pattern>/</url-pattern>
</servlet-mapping>
<listener>
 <listener-class>
  org.springframework.web.context.ContextLoaderListener
 </listener-class>
</listener>
<context-param>
 <param-name>contextConfigLocation</param-name>
 <param-value>
  /WEB-INF/spitter-security.xml
  classpath:service-context.xml
  classpath:persistence-context.xml
  classpath:dataSource-context.xml
 </param-value>
</context-param>

默认情况下,DispatcherServlet 在加载时会从一个基于配置的 servlet-name 为名字的 XML 文件中加载 Spring 应用上下文,如 spitter-servlet.xml(位于应用程序的 WEB-INF 目录下)。

创建 spitter-servlet.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="…" xmlns:xsi="…" xmlns:mvc="…" xmlns:schemaLocation="…">
 <mvc:resources mapping="/resources/**" location="/resources/"/>
 <!--注解驱动-->
 <mvc:annotation-driven/>
 <!--配置发现Controller类-->
 <context:component-scan base-package="com.habuma.spitter.mvc"/>
 <!--配置InternalResourceViewResolver-->
 <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <property name="prefix" value="/WEB-INF/views/"/>
  <proeprty name="suffix" value=".jsp"/>
 </bean>
 <!--注册TilesViewResolver-->
 <bean class="org.springframework.web.servlet.view.tiles2.TilesViewResolver"/>
 <!--添加TilesConfigurer-->
 <bean class="org.springframework.web.servlet.view.tiles2.TilesConfigurer">
  <property name="definitions">
   <lsit>
    <value>/WEB-INF/views/**/views.xml</value>
   </list>
  </property>
 <bean>
</beans>

建立了一个服务于静态资源的处理器。以上配置表明,所有以/resources 路径开头的请求都会自动由应用程序根目录下的/resources 目录提供服务。因此,所有照片、样式表、JavaScript 以及其他静态资源都必须放在应用程序的 resources 目录下。

2. 编写基本的控制器

开发面向资源的控制器,即为应用程序所提供的每一种资源编写一个单独的控制器,而不是为每个用力编写一个控制器。

配置注解驱动的 Spring MVC

Spring 提供如下处理器映射实现:

  • BeanNameUrlHandlerMapping:根据控制器 Bean 的名字将控制器映射到 URL
  • ControllerBeanNameHandlerMapping:类似于上面,但 Bean 的名字不需要遵循 URL 约定
  • ControllerClassNameHandlerMapping:通过使用控制器的类名作为 URL 基础将控制器映射到 URL
  • DefaultAnnotationHandlerMapping:将请求映射给使用 @RequestMapping 注解的控制器和控制器方法
  • SimpleUrlHandlerMapping:使用定义在 Spring 应用上下文的属性集合将控制器映射到 URL

定义首页的控制器

定义首页的视图

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="s" uri="http:www.springframework.org/tags"%>
<%@ tagllib prefix="t" uri="http://tiles.apache.org/tags-tiles"%>
<%@ taglib prefiex="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<div>
 <h2>A global community of friends and stragers sfksjsldfsldj</h2>
 <h3>Look at what thes people are spittling right now</h3>
 <ol class="spittle-list">
  <c:forEach var="spittle" items="${spittles}">
   <s:url value="/spitters/{spitterName}" var="spitter_url">
    <s:param name="spitterName" value="${spittle.spitter.username}"/>
   </s:url>
   <li>
    <span class="spittleListImage">
     <img src="http://s3.amazonaws.com/spitterImages/${spittle.spitter.id}.jpg"
      width="48" border="0" align="middle"
      onError="this.src='<s:url value="/resources/images"/>/spitter_avatar.png';"/>
    </span>
   </li>
  </c:forEach>
 </ol>
</div>

完成 Spring 应用上下文

DispatcherServlet 为名为 spitter-servlet.xml 的文件中加载 Bean,其他的需要另一种方式加载,即使用 ContextLoaderListener,配置如上面 web.xml。通过 contextConfigLocation 参数为 ContextLoaderListner 加载哪些配置文件。如果没有指定,则默认查找/WEB-INF/applicationContext.xml。

3. 处理控制器的输入

SpitterController 实现如下:

其中,list.jsp 如下:

<%@taglib prefix="s" uri="http://www.springframework.org/tags"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<div>
 <h2>Spittles for ${spitter.username}</h2>
 <table cellspacing="15">
  <c:forEach items="${spittleList}" var="spittle">
   <tr>
    <td>
     <img src="<s:url value="/resources/images/spitter_avatar.png"/>" width="48" height="48"/>
    </td>
    <td>
     <a href="<s:url value="/spitters/${spittle.spitter.username}"/>">
      ${spittle.spitter.username}
     </a>
     <c:out value="${spittle.text}"/><br/>
     <c:out value="${spittle.when}"/>
    </td>
   </tr>
  </c:forEach>
 </table>
</div>

4. 处理表单

展现注册表单

@RequestMapping(method=RequestMethod.GET, params="new")
//该方法只处理HTTP GET请求并要求请求中必须包含名为new的查询参数
public String createSpitterProfile(Model model){
 model.addAttribute(new Spitter());
 return "spitters/edit";
}

访问路径如:http://localhost:8080/Spitter/spitters?new

定义表单视图:

  • Spring

    Spring 是一个开源框架,是于 2003 年兴起的一个轻量级的 Java 开发框架,由 Rod Johnson 在其著作《Expert One-On-One J2EE Development and Design》中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 JavaEE 应用程序开发提供集成的框架。

    943 引用 • 1460 回帖 • 3 关注
  • 阅读
    85 引用 • 242 回帖 • 4 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...