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配置:
1234567<?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接口:
新建一个Bean,实现了Performer接口,从而可以进行表演扔袋子的节目:
|
|
在Spring配置文件中声明一个Bean:
打印结果:JUGGLING 3 BEANBAGS
构造器注入
XML文件中,让duke成为可以抛15个袋子的杂技师:
当不配置constructor-arg标签时,Spring将使用默认的构造方法,配置之后会使用另外的构造方法来实例化对象。
- 构造器注入对象的引用
Poem接口:
|
|
注入对象引用:
|
|
实现Poem接口的类:
|
|
XML中进行配置:
|
|
这相当于执行了如下的Java语句:
|
|
打印输出为:
|
|
- 工厂方法构造Bean
Spring支持通过元素的factory-method属性来装配工厂创建的Bean,Stage实例如下,没有公开的构造方法:
|
|
|
|
Bean的作用域
Spring的Bean作用域允许用户配置所创建的Bean属于哪一种作用域:
* singleton:在每一个Spring容器中,一个Bean定义只有一个对象实例(默认)
* prototype:允许Bean的定义可以被实例化任意次
* request:在一次HTTP请求中,每个Bean定义对应一个实例。
* session:在依次HTTP Session中,每个Bean定义对应一个实例。
* global-session:在一个全局的HTTP Session中,每个Bean定义对应一个实例。
初始化和销毁Bean
实现开关灯的实体类:
XML配置:
Audiorium实例化后会立刻调用init-method,该Bean移除之后会调用destroy-method。
默认的init-method和destroy-method:
123456 <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注入:
其有两个属性,song和Instrument,Instrument接口如下:
XML中配置其默认的构造方法生成Bean kenny:
此时kenny没有歌曲也没有乐器,需要对其进行注入属性。
注入简单值
使用
引用其他Bean
定义两个实现了Instrument接口的类:
|
|
Spring支持将其他Bean注入到当前Bean:
- 注入内部Bean
注入的Bean仅当前Bean可以使用,此时可以采用内部Bean的注入方法:1234567<!-- 内部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>
除了
Spring命名空间的P装配属性
XML声明:
p标签和property功能类似,但是更加简洁:
装配集合
- :装配list类型的值,不允许重复
:装配set类型的值,不允许重复 :装配properties类型的值,名称和值必须都是String类型
属性实际定义的集合类型和选择、
装配list Set Array分别为:
list实际上可以包含另一个list作为其成员构成多维列表,无论还是
装配map类型:
|
|
使用表达式装配
Spring引入了表达式语言Spring Expression Language,SpEL通过运行期执行的表达式将值装配到Bean的属性和构造器参数中去。SpEL的特性包括:
* 使用Bean的ID来引用Bean
* 调用方法和访问对象的属性
* 对值进行算数、关系和逻辑运算
* 正则表达式匹配
* 集合操作
基本要素
#{}会提示Spring这个标记里的内容时SpEL表达式,其格式可以为整型数,浮点数,科学计数法,字符串类型(单引号或者双引号都可以),bool类型
字面值:
123456<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的属性和方法:123<bean id="carl" class="com.springinaction.springidol.Instrumentalist"><property name="song" value="#{kenny.song}"/></bean>
|
|
返回值
为NULL会报空指针错误,解决空指针错误的方法为使用null-safe存取器:
- 操作类
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类:
通过Spring的
- 访问集合成员:12<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]}"/>.^[]
:从集合中查询出第一个匹配项.$[]
:从集合中查询出最后一个匹配项
投影集合
.![]
:从集合中的每一个成员中取出特定的属性放入一个新的集合中123<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:1234<bean id="kenny" class="com.springinaction.springidol.Instrumentalist" ><property name="song" value="Jingle Bells" /><property name="instrument" ref="saxophone"/></bean>等价于:
1234<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
* 取消某个Bean自动装配的资格。1<bean id="instrument" class="com.springinaction.springidol.saxophone" primary="true"/>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进行自动装配。
默认自动装配
|
|
该参数的默认值为none,特定Bean可以设定其自动装配方式来覆盖默认的装配方式。
混合使用自动装配和显示装配
对某个Bean选择了自动装配策略,同时也可以进行显示装配。可以通过显示装配来解决自动装配中存在的不确定的问题,覆盖掉其自动装配的值。
- NOTE:不能混合使用constructor自动装配策略和
元素
使用注解装配
Spring默认不使用注解,使用注解需要在XML中进行配置:
Spring支持几种不同的用于自动装配的注解:
* Spring自带的@Autowired注解
* JSR-330的@Inject注解
* JSR-250的@Resource注解
使用@Autowired注解
对属性的setter方法添加注解:
Autowired注解可以用在setter方法,任意方法以及构造方法。当对构造器进行标注时,Autowired注解表示当创建Bean时,即使在Spring XML文件中没有使用
可选的自动装配:@Autowired所标注的属性和参数必须是可装配的,如果没有Bean装配到所标注的属性和参数时,会报错。设定required参数来限定@Autowired,当没有找到可装载的Bean时,属性和参数的值为NULL。
12@Autowired(required = false)private Instrument instrument;但是当其注解构造方法时,只能有一个构造器的required的值为true。
限定歧义性的依赖:当有同时满足装配的条件的多个Bean存在时,Spring会抛出NoSuchBeanDefineException错误,表明装配失败。使用@Qualifier注解来制定装配的具体Bean:
123@AutoWired@Qualifier("guitar")private Instrument instrument;XML配置也可以同样使用qualifier标签来缩小Bean搜索范围:
123<bean class="com.springinaction.springidol.Piano"><qualifier value="piano"/></bean>自定义的限定器
使用@Qualifier作为自定义注解的元注解:@123@Qualifierpublic @Interface StringedInstrument{}这样就可以用@StringedInstrument注解来标注guitar
123@StringedInstrumentpublic class Guitar implements Instrument{}自动装配时对Instrument进行限定,从而查找所有被StringedInstrument标注的Bean,不能确定到一个Bean会再进行其他限定。
使用@Autowired的缺点在于引入了对Spring注解的依赖,可以使用Java的@Inject注解来解决这个问题
@
Inject注解
与Spring的@Autowired注解类似,JCP的@Inject注解可以用来自动装配属性、方法和构造器;区别在于@Inject没有required属性,所有其所标注的依赖关系必须存在!
限定@Inject使用注解@Named
123@Inject@Named("guitar")private Instrument Instrument;@Named注解表明选择了一个Bean,@Qualifier表明满足条件的Bean
自定义的JSR-330 Qualifier
123@Qualifierpublic @Interface StringedInstrument{}区别在于这里的@Qualifier来自JSR-330而不是Spring
注解中使用表达式
@Value
可以用来装配String类型的值和基本类型的值:
SpEL和注解结合,实现注解驱动的装配方式:
自动检测Bean
<context:annotation-config>
可以消除Bean里面的配置,<context:component-scan>
还可以完成自动检测Bean的功能,从而XML中不需要创建Bean:
标注Bean
context:component-scan默认查找构造型注解所标注的类:
@Component
通用的构造型注解,标示该类为Spring组件@Controller
标示该类定义为SpringMVC controller@Repository
标识将该类定义为数据仓库@Service
标识将该类定义为服务@Component
也可以标注自定义注解
|
|
Spring扫描com.springinaction.springidol包时,会发现@Component注解标识的Guitar类,将其自动注册为Spring Bean。默认ID为guitar。
过滤组件扫描
Spring默认会扫描包下的所有类的@Component注解来生成Bean,我们可以自己定义过滤规则来实现自动注册特定的类:
可以自动将所有派生与Instrument的类注册到Spring Bean中。
过滤器类型包含5种:
- annotation:顾虑器扫描使用指定注解所标注的那些类,通过expression属性指定要扫描的注解
- assignable:过滤器通过扫描派生于expression属性所指定的那些类
- aspectj:过滤器扫描与expression属性所指定的AspectJ表达式所匹配的类
- custom:使用自定义的org.springframework.core.type.TypeFilter实现类,该类由expression属性指定
- regex:过滤器扫描类的名称与expression属性所指定的正则表达式所匹配的那些类。
context:exclude-filter表示不注册那些类:
使用Spring基于Java的配置
启动Java配置
|
|
context:component-scan会自动加载@Configuration注解的类。
定义一个配置类
|
|
@
Configuration注解会告知Spring,这个类将包含一个或者多个Bean的定义。
声明一个Bean
|
|
等价于XML中配置
使用Spring的基于Java的配置进行注入
|
|
|
|
面向切面的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配置元素,大多数的 |
|
为被通知的对象引入额外的接口,并透明的实现 | |
定义切点 |
选秀节目需要装配观众,每一个选手都需要装配同样的观众,创建一个观众类:
|
|
将其注册为Spring上下文的一个Bean:<bean id="audience" class="com.springinaction.springidol.Audience"/>