Spring的核心
相对于EJB(Enterprise JaveBean),Spring提供了更加轻量级和简单的编程模型。
Spring可以简化Java开发:1.基于pojo的轻量级和最小侵入式编程;2.通过依赖注入和面向接口实现松耦合;3.基于切面和惯例进行声明式编程;4.通过切面和模板减少样板式代码。
- 非侵入编程
很多框架通过强迫应用继承它们的类或实现它们的接口从而导致应用与框架绑死。Spring竭力避免自身API与应用代码的耦合。Spring不会强迫你实现Spring规范的接口或继承Spring规范的类。
- 依赖注入
假如两个类相互协作完成特定的业务,按照传统的做法,每个对象负责管理与自己相互协作的对象的引用,这会导致高度耦合。
1 | public class Knight { |
通过DI,对象无需自行创建或管理它们的依赖关系,依赖关系将被自动注入到对象当中。对象只通过接口来表明依赖关系,可以传入不同的具体实现。
1 | public class Knight { |
- 面向切面编程
aop允许你将遍布应用各处的功能(日志,事务管理)分离出来形成可重用的组件。

- 模板技术
编程中会遇到许多样板式代码,如使用JDBC访问数据库查询数据时,首先需要创建数据库连接(Connection),然后再创建一个语句对象(PrepareStatement),然后进行查询,可能需要处理SQL异常,最后关闭数据库连接、语句和结果集。Spring通过模板封装来消除样板式代码。Spring的JDBCTemplate可以避免传统的JDBC样板代码。
装配bean
Spring bean装配机制:1.自动装配;2.使用spring基于Java的配置(JavaConfig);3.xml配置。
我们应当尽可能使用自动装配的机制,显式配置越少越好。当自动装配行不通时,如使用第三方库的组件装配到应用,则需采用显式装配的方式,此时推荐使用类型安全并且比xml更为强大的JavaConfig。只有当需要使用便利的xml命名空间,并且在JavaConfig中没有同样的实现时,才应该使用xml。
自动装配bean
实现自动装配bean 需要两点:1.开启组件扫描;2.给bean加自动装配的注解。
1 | public interface CD { |
@Component表明该类会作为组件类,并告知Spring为这个类创建bean。@Component(“cd”)可以为bean命名,没有配置默认是类名首字母小写。@Named注解与@Component作用类似。
CDPlayer类中,在构造器上加了注解@Autowired,表明当Spring创建CDPlayer bean的时候,会通过这个构造器进行实例化并传入一个类型为CD的bean。@Inject注解和@Autowired作用类似。
1 | import org.springframework.beans.factory.annotation.Autowired; |
组件扫描默认不启动,需要显式配置一下Spring,开启组件扫描。通过@ComponentScan注解启动了组件扫描,扫描包下是否有@Component标注的类,若有,则会在Spring中自动为其创建bean。
1 | import org.springframework.context.annotation.ComponentScan; |
验证自动装配
1 | import com.tyson.config.CDPlayerConfig; |
也可以在applicationContext.xml使用<context:component-scan/>来启动组件扫描。
1 | <beans xmlns="http://www.springframework.org/schema/beans" |
测试代码。
1 | import org.junit.Assert; |
通过Java代码装配bean
有些情况下无法使用自动装配,如要将第三方类库的组件装配到我们的应用,便无法使用自动装配。下面使用JavaConfig显式配置Spring。(JavaConfig是配置代码,应该放在单独的包中,与其他应用程序逻辑分离开。)
CDPlayerConfig配置类
1 | import com.tyson.pojo.CD; |
通过AnnotationConfigApplicationContext从Java配置类加载Spring应用上下文。
1 | //AnnotationConfigApplicationContext:从一个或多个基于Java的配置类加载Spring应用上下文 |
通过xml装配bean
构造器注入
借助构造器注入初始化bean有两种方式:constructor-arg元素或者Spring3.0引入的c-命名空间。
- 将引用注入构造器。
EasonCD.java
1 | public class EasonCD implements CD { |
CDPlayer.java
1 | import org.springframework.beans.factory.annotation.Autowired; |
applicationContext.xml使用构造器注入创建bean。
1 |
|
测试类
1 | /** |
c-命名空间元素组成如下图,可以看到使用了构造器参数。

也可以使用索引识别构造器参数。
1 | <bean id="cdPlayer" class="com.tyson.pojo.CDPlayer" c:_0-ref="cd"/> |
- 将字面量注入构造器
1 | public class PuthCD implements CD { |
利用constructor-arg元素的value进字面量注入构造器。
1 | <!--将字面量注入构造器--> |
也可以使用c-空间。
1 | <!--将字面量注入构造器方式1--> |
测试类
1 |
|
- 装配集合
1 | public class EuropeCD implements CD { |
使用list元素装配集合。这里c-命名空间没有响应的实现,只能用constructor-arg元素。
1 | <bean id="europeCD" class="com.tyson.pojo.EuropeCD"> |
属性注入
除了可以通过构造器注入初始化bean以外,也可以通过属性注入初始化bean。通过属性注入,则类中不能有带参数的构造器,或者要显式声明无参的构造器。
- 将引用注入属性中
1 | import org.springframework.beans.factory.annotation.Autowired; |
applicationContext.xml中进行属性注入创建bean。有两种方式,使用property元素或者p-命名空间。
1 |
|
p命名空间元素组成如下:

- 将字面量和集合注入属性中
EuropeCD.java
1 | import java.util.List; |
applicationContext.xml
1 |
|
引入util命名空间。

混合配置
在JavaConfig中引用xml配置
假如CD使用xml配置,CDPlayer使用JavaConfig配置。
1 | import com.tyson.pojo.CD; |
装配CD定义在cd-config.xml中。
1 |
|
新建一个SoundSystemConfig.java类,通过@Import和@ImportResource引入JavaConfig配置类和xml配置文件。
1 | import org.springframework.context.annotation.Configuration; |
测试代码
1 | /** |
在xml配置中引用JavaConfig
假如CDPlayer使用player-config.xml配置,CD使用cd-config.xml配置,则在player-config.xml中需要用import元素引用cd-config文件,从而将CD注入到CDPlayer。
player-config.xml
1 |
|
如果CDPlayer使用player-config.xml配置,而CD使用JavaConfig来配置,则需要这样声明bean:
1 |
|
CDConfig.java
1 | import com.tyson.pojo.CD; |
测试代码:
1 |
|
也可以使用第三个配置文件,将JavaConfig和xml配置组合起来。
soundSystem-config.xml
1 |
|
故无论是JavaConfig还是xml配置,都可以先创建一个根配置,在根配置里将多个装配类或者xml文件组合起来,同时可以在根配置打开组件扫描(通过<context:component-scan/>或者@ComponentScan)。
高级装配
自动装配的歧义性
编写Dessert接口,有三个实现Dessert的类:Cake、IceCream和Cookie。假如三个类都使用@Component注解,Spring在进行组件扫描时会为它们创建bean,这时如果Spring试图自动装配setDessert里面的Dessert参数时,这时Spring便不知道该使用哪个bean。
1 |
|
标识首选的bean
通过设置其中某个bean为首选的bean可避免自动装配的歧义性。
定义Bean时使用@Primary注解。
1 | import org.springframework.context.annotation.Primary; |
或者在JavaConfig类中使用@Primary注解。
1 | import com.tyson.pojo.Cookie; |
xml配置使用primary属性。
1 | <bean id="cookie" class="com.tyson.pojo.Cookie" primary="true"/> |
限定自动装配的bean
@Primary只能标识一个首选的bean,当首选的bean存在有多个时,这种方法便失效。Spring的限定符可以在可选的bean进行缩小范围,最终使得只有一个bean符合限定条件。
@Qualifier注解是使用限定符的主要方式。它与@Autowired和@Inject协同使用,在注入的时候指定注入哪个bean。
1 |
|
这种方式setDessert方法上所指定的限定符和要注入的bean名称是紧耦合的。如果类名称修改则会导致限定符失效。
创建自定义的限定符
为bean设置自己的限定符。
1 | import org.springframework.beans.factory.annotation.Qualifier; |
在自动装配的地方引入自定义限定符。@Autowired和@Qualifier组合,按byName方式自动装配。
1 |
|
当使用JavaConfig装配bean时,@Qualifier可以和@Bean注解一起使用。
1 | import com.tyson.pojo.Cookie; |
使用自定义的限定符注解
当有多个Dessert都具有cold特征时,此时需要添加更多的限定符来避免歧义性的问题。
1 | import com.tyson.pojo.Dessert; |
然而在一个类上出现多个相同类型的注解是不被允许的,会报编译错误。可以通过创建自定义的限定符注解解决这个问题。下面是自定义@Cold注解的例子:
1 | import org.springframework.beans.factory.annotation.Qualifier; |
自定义@Creamy注解。
1 | import org.springframework.beans.factory.annotation.Qualifier; |
使用@Cold和@Creamy。
1 | import com.tyson.annotation.Cold; |
使用自定义注解更为安全。
bean的作用域
默认情况下,Spring应用上下文所有的bean都是以单例的形式创建。不管给定的bean注入到其他bean多少次,每次注入的都是同一个实例。
Spring定义了多种定义域:
单例(Singleton):在整个应用,只创建bean一个实例。
原型(prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例。
会话(session):在web应用中,为每个会话创建一个bean实例。
请求(request):在web应用中,为每个请求创建一个bean实例。
单例是默认的作用域,使用@Scope选择其他的作用域,它可以跟@Component和@Bean一起使用。
使用@Scope声明CDPlayer为原型bean。
1 | import org.springframework.beans.factory.annotation.Autowired; |
在JavaConfig中声明CDPlayer为原型bean。
1 |
|
若是通过xml配置bean,可以通过
1 | <bean id="cd" class="com.tyson.pojo.EasonCD" scope="prototype"/> |
运行时值注入
依赖注入,是将一个bean引用注入到另一个bean的属性或者构造器参数, 是一个对象和另一个对象进行关联。bean装配是将一个值注入到bean的属性或者构造器参数中。
注入外部的值
使用JavaConfig的方式装配EasonCD bean。通过Environment类获得app.properties定义的属性值。
1 | import com.tyson.pojo.CD; |
通过@PropertySource引用了类路径中一个名为app.properties的文件。
1 | song = ten years |
测试代码
1 |
|
如果使用组件扫描和自动装配创建bean,可以通过@Value注解引用外部的值。
1 | import org.springframework.beans.factory.annotation.Value; |
测试代码
1 |
|
使用xml配置的话,Spring的<context:propertyplaceholder>元素可以为我们生成PropertySourcesPlaceholderConfigurer bean,进而可以使用占位符。
1 |
|
解析外部属性可以将值的处理推迟到运行时,但是它的关注点在于根据名称解析来自于Spring Environment和属性源的属性。
<context:property-placeholder/> 只能导入一个 properties 文件,若需要导入多个 properties 文件,则使用下面的方法:
1 | <bean id="placeHolder" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> |
使用Spring表达式语言进行装配
Spring3引入了Spring表达式语言SpEL,它在将值装配到bean属性和构造器参数过程中所使用的表达式会在运行时计算得到值。
Spring表达式要放到”#{…}”之中。
1 | public EasonCD( String song, |
使用xml配置。
1 | <bean id="easonCD" class="com.tyson.pojo.EasonCD"> |
面向切面
依赖注入实现了对象之间的解耦。AOP可以实现横切关注点和它们所影响的对象之间的解耦。
切面可以帮我们模块化横切关注点。安全就是一个横切关注点,应用中的许多方法都会涉及到安全规则。

横切关注点可以被模块化为特殊的类,这些类称之为切面。这样做有两个好处:每个关注点都集中到一个地方,不会分散到多处代码;服务模块更简洁,因为它们只包含核心功能的代码,而非核心功能的代码被转移到切面了。
AOP术语

通知(advice):切面的工作被称为通知。通知定义了切面是什么以及何时使用。除了描述切面要完成的工作,通知还解决了何时执行这个工作的问题,它应该在某个方法被调用之前还是之后调用,或者在方法抛异常的时候才调用。
Spring切面定义了五种类型的通知:前置通知,后置通知,返回通知,异常通知,环绕通知。
连接点(join point):连接点是应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至是修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。
切点(point cut):匹配通知所要织入的一个或多个连接点,缩小切面所通知的连接点的范围。通常使用明确的类和方法名称,或者利用正则表达式定义所匹配的类和方法名称来指定这些切点。有些AOP框架允许我们创建动态的切点,可以在运行时(通过方法的参数值等)决定是否应用通知。
切面(aspect):切面是通知和切点的结合,通知和切点共同定义了切面的全部内容。
引入(introduction):在无需修改原有类的情况下,引入允许我们向类添加新的方法或者属性(新的行为或状态)。
织入(weaving):织入是把切面应用到目标对象并创建代理对象的过程。切面在指定的连接点被织入到目标对象中。在目标对象的生命周期有多个点可以进行织入:
- 编译期:切面在目标对象编译时被织入。这种方式需要特殊的编译器。Aspect的织入编译器就是以这种方式织入切面的。
- 类加载期:切面在目标对象被加载到JVM时被织入。这种方式需要特殊的类加载器,它可以在目标类被引入应用之前增强目标类的字节码。
- 运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态创建一个代理对象。Spring AOP就是以这种方式织入切面的。
Spring对aop的支持
Spring提供了4种类型的aop支持:
1.基于代理的经典Spring aop;2.纯pojo切面(借助Spring aop命名空间,xml配置);3.@AspectJ注解驱动的切面;4.注入式AspectJ切面(适用于Spring各版本)
Spring aop构建在动态代理基础之上,因此Spring对aop的支持局限在方法拦截。如果应用的aop需求超过了简单的方法调用(构造器或属性拦截),就需要考虑使用AspectJ来实现切面。这种情况下,第四种类型能够帮助我们将值注入到AspectJ驱动的切面中。AspectJ通过特有的aop语言,我们可以获得更为强大和细粒度的控制,以及更丰富的aop工具集。
Spring在运行时通知对象
通过在代理类中包裹切面,Spring在运行时将切面织入到Spring管理的bean中。代理类封装了目标类,并拦截被通知方法的调用,执行额外的切面逻辑,并调用目标方法。

通过切面选择连接点
在Spring aop中,要使用AspectJ的切点表达式语言来定义切点。Spring仅支持AspectJ切点指示器的一个子集。下表列出Spring AOP支持的AspectJ切点指示器。
| AspectJ指示器 | 描述 |
|---|---|
| arg() | 限制连接点匹配参数为指定类型的执行方法 |
| @args() | 限制连接点匹配参数由指定注解标注的执行方法 |
| execution() | 用于匹配是连接点的执行方法 |
| this() | 限制连接点匹配AOP代理的bean引用为指定类型的类 |
| target | 限制连接点匹配目标对象为指定类型的类 |
| @target() | 限制连接点匹配特定的执行对象,这些对象对应的类要具有指定类型的注解 |
| within() | 限制连接点匹配指定的类型 |
| @within() | 限制连接点匹配指定注解所标注的类型(当使用Spring AOP时,方法定义在由指定的注解所标注的类里) |
| @annotation | 限定匹配带有指定注解的连接点 |
在Spring中尝试使用AspectJ其他指示器时,将会抛出IllegalArgument-Exception异常。
编写切点
定义一个Performance接口:
1 | public interface Performance { |
使用AspectJ切点表达式来选择Performance的perform()方法:
1 | execution(* com.tyson.aop.Performance.perform(..)) |
使用execution()指示器选择Performance的perform()方法。

使用within()指示器限制切点范围:

因为“&”在XML中有特殊含义,所以在Spring的XML配置里面描述切点时,我们可以使用and来代替“&&”。同样,or和not可以分别用来代替“||”和“!”。
在切点中选择bean
Spring引入了一个新的bean()指示器,它允许我们在切点表达式使用bean的ID作为参数来限定切点只匹配特定的bean。
1 | execution(* com.tyson.aop.Performance.perform(..)) |
在执行Performance的perform()方法时应用通知,当限定bean的ID为drum。
1 | execution(* com.tyson.aop.Performance.perform(..)) |
使用注解创建切面
使用注解创建切面是AspectJ 5引入的关键特性,通过少量的注解便可以把Java类转变为切面。
定义切面
使用aop相关注解需先导入依赖。
1 |
|
Audience类定义了一个切面。
1 | import org.aspectj.lang.annotation.*; |
启动自动代理功能。若使用JavaConfig,可以在配置类的类级别使用@EnableAspectJAutoProxy注解启动自动代理。
1 | import org.springframework.context.annotation.Bean; |
若使用xml装配bean,则可以用<aop:aspectj-autoproxy>元素启动自动代理。concert-config.xml如下:
1 |
|
测试代码:
1 | import org.junit.Test; |
测试结果:
1 | silencing cell phones |
创建环绕通知
使用环绕通知重新实现Audience切面。
1 | import org.aspectj.lang.ProceedingJoinPoint; |
处理通知中的参数
被通知方法含有参数,切面可以访问和使用传给被通知方法的参数。下面使用TrackCounter类记录磁道播放的次数。其中被通知方法playTrack方法有形参trackNum。
1 | import org.aspectj.lang.annotation.Aspect; |
BlankDisc类:
1 | public class BlankDisc { |
TrackCounterConfig开启自动代理,装配BlankDisc和TrackCounter bean。
1 | import com.tyson.pojo.BlankDisc; |
测试代码:
1 | import com.tyson.pojo.BlankDisc; |
通过注解引入新功能
利用引入的功能,切面可以为Spring bean添加新方法。

假如为Performance实现类引入Encoreable(再次表演)接口。
1 | public interface Encoreable { |
为了实现该功能,我们需要创建一个切面:
1 | import org.aspectj.lang.annotation.Aspect; |
ConcertConfig类如下,当Spring发现一个bean使用了@Aspect注解,Spring就会创建一个代理,在运行时会将调用委托给被代理的bean或者被引入的实现。
1 | import org.springframework.context.annotation.Bean; |
测试代码:
1 |
|
在xml中声明切面
在不能为通知类添加注解的时候,就只能使用xml配置。Spring的AOP配置元素如下:
| aop配置元素 | 用途 |
|---|---|
| <aop:advisor> | 定义aop通知器 |
| <aop:aspect> | 定义aop切面 |
| <aop:pointcut> | 定义切点 |
| <aop:declare-parents> | 以透明的方式为被通知对象引入新的接口 |
| <aop:config> | 顶层的aop配置元素,大多数<aop:*>配置元素必须包含在<aop:config>内 |
| <aop:before> | 前置通知 |
| <aop:after> | 后置通知 |
| <aop:after-returning> | 返回通知 |
| <aop:after-throwing> | 异常通知 |
| <aop:around> | 环绕通知 |
重新定义Audience类。
1 | public class Audience { |
声明通知
通过xml将无注解的Audience声明为切面。
1 |
|
创建环绕通知
1 | import org.aspectj.lang.ProceedingJoinPoint; |
声明Audience切面
1 |
|
为通知传递参数
同样使用TrackCounter记录磁道播放的次数。无注解的TrackCounter如下:
1 | import java.util.HashMap; |
在xml中将TrackCounter配置为参数化的切面。xml中&符号会被解析成实体的开始,故用and代替。
1 |
|
通过切面引入新的功能
concert-config.xml
1 |
|
测试代码:
1 |
|
注入AspectJ切面
Spring AOP是功能比较弱的AOP解决方案,AspectJ提供了Spring AOP所不支持的许多类型的切点。如当我们需要在创建对象时应用通知,使用构造器切点很容易实现,而Spring AOP不支持构造器切点,所以基于代理的Spring AOP不能把通知应用于对象的创建过程。此时可以使用AspectJ实现。
使用AspectJ实现的表演评论员:
1 | package com.tyson.aop; |
CriticismEngine类以及实现类
1 | public class CriticismEngine { |
在concert-config.xml中将CriticismEngine bean注入到CritisicAspect。CritisicAspect bean的声明使用了factory-method属性。通常情况下,Spring bean由Spring容器初始化,而Aspect切面是由AspectJ在运行期创建的。在Spring为CriticAspect注入CriticismEngine之前,CriticAspect已经被实例化了。故我们需要一种方式为Spring获得已经由AspectJ创建的CriticAspect实例的句柄,从而可以注入CriticismEngine。AspectJ切面提供了一个静态的aspectOf()方法,该方法返回切面的一个单例。Spring需要通过aspectOf()工厂方法获得切面的引用,然后进行依赖注入。
1 |
|
测试代码:
1 |
|
测试结果:
1 | performance构造器调用之前 |
后端中的 Spring
数据访问模板化
Spring 将数据访问过程分为两个部分:模板(template)和回调(callback),Spring 的模板类处理数据访问的固定部分,如事务控制、资源管理和处理异常;应用程序相关的数据访问,如语句、参数绑定以及处理结果集,在回调的实现中处理。
针对不同的持久化平台,Spring 提供了不同的持久化模板。如果直接使用 JDBC,则可以使用 JdbcTemplate。如果想要使用对象关系映射框架,可以使用 JpaTemplate 和 HibernateTemplate。
配置数据源
Spring 提供了配置数据源 bean 的多种方式:
- 通过 JDBC 驱动程序定义的数据源
- 通过 JNDI (Java Naming and Directory Interface)查找的数据源
- 连接池的数据源
在 Spring 中集成 Hibernate
本节参考自:spring+springmvc+hibernate 整合
Hibernate 是开源的持久化框架,不仅提供了基本的对象关系映射,还提供了 ORM 工具的复杂功能,如缓存、延迟加载、预先抓取(eager fetching)等。
引入依赖:
1 | <dependency> |
声明 Hibernate 的 Session 工厂
Session 接口提供了基本的数据访问功能,获取 Session 对象的标准方式是借助于 Hibernate SessionFactory 接口的实现类。SessionFactory 主要负责 Hibernate Session 的打开、关闭以及管理。
从Spring 3.1版开始,Spring 提供了三个 SessionFactorybean :
1 | org.springframework.orm.hibernate3.LocalSessionFactoryBean |
如果使用高于 Hibernate 3.1版本,低于4.0版本,并且使用 xml 定义映射的话,则需要定义org.springframework.orm.hibernate3.LocalSessionFactoryBean。
如果倾向于使用注解来定义映射的话,并且没有使用 Hibernate 4的话,那么需要使用 AnnotationSessionFactoryBean。
当使用 Hibernate 4时,就应该使用org.springframework.orm.hibernate4.LocalSessionFactoryBean,Spring 3.1引入的这个 SessionFactoryBean 类似于 Hibernate 3中的 LocalSessionFactoryBean 和 AnnotationSessionFactoryBean 的结合体。
datasource.xml 文件如下:
1 |
|
datasource.properties 如下:
1 | #database connection config |
beans.xml 如下:
1 |
|
实体类(使用注解来定义映射):
1 | import java.util.Set; |
Spring 与 Java 持久化 API
JPA 基于 POJO 的持久化机制。JPA 是一种规范,而 Hibernate 是它的一种实现。除了 Hibernate,还有 EclipseLink,OpenJPA 等可供选择,所以使用 JPA 的一个好处是,可以更换实现而不必改动太多代码。
导入依赖包
1 | <!--spring--> |
在 Spring 中使用 JPA 的第一步是要在 Spring 应用的上下文将实体管理器工厂(entity manager factory)按照 Bean 的形式进行配置。
配置实体管理器工厂
基于 JPA 的应用程序需要通过 EntityManegerFactory 获取 EntityManager 实例。JPA 定义了两种类型的EntityManagerFactory:
- 应用程序管理类型(Application-managed):当应用程序向实体管理器工厂直接请求实体管理器时,工厂会创建一个实体管理器。在这种模式下,程序要负责打开或关闭实体管理器并在事务中对其进行控制。这种方式的实体管理器适合于不运行在 Java EE 容器中的独立应用程序。
- 容器管理类型(container-managed):实体管理器由 Java EE 创建和管理。应用程序根本不与实体管理器工厂打交道。相反,实体管理器直接通过注入或 JNDI 来获取。容器负责配置实体管理器工厂。这种类型的实体管理器最适用于 Java EE 容器。
两种实体管理器实现了同一个 EntityManager 接口。这两种实体管理器工厂分别由对应的Spring工厂Bean创建:
- LocalEntityManagerFactoryBean 生成应用程序管理类型的 EntityManager-Factory
- LocalContainerEntityManagerFactoryBean 生成容器管理类型的 Entity-ManagerFactory
使用容器管理的 EntityManagerFactory
配置容器管理类型的 EntityManagerFactory:
1 |
|
LocalContainerEntityManager 的 DataSource 属性 dataSource 可以是 java.sql.DataSource 的任何实现,dataSource 还可以在 persistence.xml 中进行配置,但是这个属性指定的数据源具有更高的优先级。
jpaVendorAdapter 属性用于指定所使用的哪一个厂商的 JPA 实现。Spring提供了多个 JPA 厂商适配器。
- EclipseLinkJpaVendorAdapter
- HibernateJpaVendorAdapter
- OpenJpaVendorAdapter
- TopLinkJpaVendorAdapter
配置容器管理类型的 JPA 完整代码:
1 | import com.mchange.v2.c3p0.ComboPooledDataSource; |
src/main/resources 目录下的 db.properties:
1 | c3p0.driverClass=com.mysql.jdbc.Driver |
编写 JPA Repository
UserRepository 接口代码:
1 | package com.tyson.db.jpa; |
不使用 Spring 模板的纯 JPA Repository:
1 | package com.tyson.db.jpa; |
@Repository 可以减少显式配置,通过组件扫描自动创建。JpaTemplate 会捕获平台相关的异常,然后使用 Spring 统一的非检查型异常的形式重新抛出。为了给不使用模板的 Jpa Repository 添加异常转化功能,我们只需在 Spring 应用上下文添加一个 PersistenceExceptionTranslationPostProcessor bean,这是一个 bean 后置处理器,它会在所有拥有@Repository 注解的类上添加一个通知器(advisor),这样就会捕获平台相关的异常并以 Spring 非检查型数据访问异常的形式重新抛出。
@Transactional 表明这个 Repository 中的持久化方法是在事务上下文中执行的。不添加@Transactional 注解 会抛异常:javax.persistence.TransactionRequiredException: No transactional EntityManager available。
使用@PersistenceContext 并不会真正注入 EntityManager,它没有把真正的 EntityManager 设置给 Repository,而是给了它一个 EntityManager 的代理。真正的 EntityManager 是与当前事务相关联的那个,如果不存在这样的 EntityManager 的话,就会创建一个新的。这样便能以线程安全的方式使用 EntityManager。
@PersistenceContext 并不是 Spring 的注解,它是由 JPA 规范提供的,为了让 Spring 理解这些注解,并注入EntityManager,我们需要配置 Spring 的 PersistenceAnnotationBeanPostProcessor。如果已经使用 <context:annotation-config>或者<context:component-scan>,会自动注册 PersistenceAnnotationBeanPostProcessor bean。否则需要显式注册这个bean。
1 |
|
实体类 User:
1 | package com.tyson.domain; |
测试代码:
1 | import com.tyson.config.JpaConfig; |
借助 Spring Data 实现自动化的 JPA Repository
Spring Data JPA 是Spring基于ORM框架、JPA规范封装的一套 JPA 应用框架,可使开发者用极简的代码即可实现对数据的访问和操作。
Spring Data JPA 的核心接口
- Repository:最顶层的接口,是一个空的接口,目的是为了统一所有Repository的类型,且能让组件扫描的时候自动识别。
- CrudRepository :是Repository的子接口,提供CRUD的功能
- PagingAndSortingRepository:是CrudRepository的子接口,添加分页和排序的功能
- JpaRepository:是PagingAndSortingRepository的子接口,增加了一些实用的功能,如批量操作等
- JpaSpecificationExecutor:用来做负责查询的接口
- Specification:是 Spring Data JPA 提供的一个查询规范,Criteria 查询
定义数据访问层
引入依赖:
1 | <!--spring--> |
只需定义一个继承 JpaRepository 的接口即可:
1 | package com.tyson.db.springdatajpa; |
继承了 JpaRepository 接口默认已经有了下面的数据访问操作方法:
1 |
|
Spring Data 自动为我们生成 UserRepository 的实现类。我们需要在 Spring 配置中添加一个元素启用 Spring Data JPA。下面使用 xml 配置方式启用Spring Data JPA:
1 |
|
<jpa:repositories>元素会扫描它的基础包来查找扩展自 Spring Data JPA Repository 接口的所有接口。如果发现有扩展自 Repository 的接口,它会在应用启动时自动生成该接口的实现类。
也可以使用 Java 配置方式启用 Spring Data JPA:
1 |
|
UserReposity 扩展了 Repository 接口,当Spring Data 扫描到它后,会为它创建 UserRepository 的实现类,其中包含了继承自 JpaRepository、PagingAndSortingRepository 和 CrudRepository 的18个方法。Repository 的 实现类是在应用启动时生成的,不是在构建时通过代码生成技术生成,也不是在接口调用时才创建的。
定义查询方法
根据属性名查询
将 UserRepository 接口修改如下:
1 | import com.tyson.domain.User; |
通过方法签名已经告诉 Spring Data JPA 怎样实现这个方法了。Repository 方法是由一个动词、一个可选的主题、关键字 By 和一个断言所组成的。

Spring Data 允许在方法名中使用四种名词:get/read/find/count。get/read/find是同义的。
如果主题的名称以 Distinct 开头的话,那么返回的结果集不包含重复记录。
断言指定了限制结果集的属性。断言中会有一个或者多个限制条件,并且每个条件可以指定一种比较操作,如果省略比较操作,则默认是相等比较操作。条件中可能包含 IgnoringCase 或者 IgnoresCase.
1 | List<User> findByFirstnameIgnoresCaseOrLastnameIgnoresCase(String first, String last); |
- 在方法名称结尾处添加 OrderBy,实现结果集排序。如果要根据多个属性排序的话,只需将其依序添加到 OrderBy 中即可。下面的代码会根据 Lastname 升序排列,然后根据 Firstname 降序排列。
1 | List<User> readByFirstnameOrLastnameOrderByLastnameAscFirstnameDesc(String first, String last); |
- 限制结果数量,如 findFirst10ByUsername。
排序和分页查询
1 | //分页 |
测试代码:
1 |
|
使用@Query 查询
当所需的数据无法通过方法名描述时,如查找邮箱地址是不是 QQ 邮箱时,使用findQQMailUser方法不符合 Spring Data 方法命名约定,这时我们可以使用@Query 注解,为 Spring Data 提供要执行的查询。
1 |
|
Specification
有时我们在查询某个实体的时候,给定的条件是不固定的,这时我们就需要动态构建相应的查询语句,在JPA2.0中我们可以通过Criteria接口查询。在Spring data JPA中相应的接口是JpaSpecificationExecutor,这个接口基本是围绕着Specification 复杂查询接口来定义的。
(1)定义。接口类需实现 JpaSpecificationExecutor 接口
1 | public interface UserRepository extends JpaRepository<User, Long> , JpaSpecificationExecutor<User> { |
(2)定义 Criteria 查询(Criteria 查询采用面向对象的方式封装查询条件)
1 | import com.tyson.domain.User; |
使用 Root 俩获得需要查询的属性,通过 CriteriaBuilder 构造条件。CriteriaBuilder 包含的条件有:exists、and、or、isTrue、isNull、greaterThan、greaterThanOrEqualTo、between等。
(3)使用。注入 userRepository 的 bean 后:
1 |
|
混合自定义的功能
当所需的功能无法用 Spring Data 的方法命名约定来描述,并且无法用@Query 注解来实现,这时我们只能使用较低层级的 JPA 的方法。
当 Spring Data JPA 为 Repository 接口生成实现时,它会查找名字与接口相同,并且添加后缀 Impl 的一个类(本例是 UserRepositoryImpl),如果这个类存在,那么 Spring Data JPA 会将它的方法和 Spring Data JPA 生成的方法合并在一起。
UserRepositoryImpl 实现了 MixUserRepository 接口。
1 | package com.tyson.db.springdatajpa; |
我们还需要确保 updateUser 方法被声明在 UserRepository 接口中,让 UserRepository 扩展 MixUserRepository。
1 | package com.tyson.db.springdatajpa; |
Spring Data 将实现类与接口关联起来是基于接口的名称。Impl 后缀只是默认的做法,如果想到使用其他后缀,只需在配置@EnableJpaReposities 的时候,设置 repositoryImplementationPostfix 属性即可:
1 |
如果在 xml 使用 <jpa:repositories>元素来配置 Spring Data JPA 的话,可以借助 repository-impl-postfix 属性指定后缀:
1 | <!--启动Spring Data JPA--> |
设置成 Helper 后缀的话,则 Spring Data JPA 会查找名为 UserRepositoryHelper 的类,用它来匹配 UserRepository 接口。
Spring Security
基于 Spring AOP 和 Servlet 规范中的 Filter 实现的安全框架。它能够在 web 请求级别和方法调用级别处理身份认证和授权。Spring Security 使用了依赖注入和面向切面的技术。
添加依赖:
1 | <spring.security.version>3.2.3.RELEASE</spring.security.version> |
基于内存的用户存储
1 | package com.tyson.config; |
基于数据库表进行认证
1 |
|
拦截请求
对每个请求进行细粒度安全性控制的关键在于重载configure(HttpSecurity)方法。
1 |
|
保护请求的配置方法:

使用Spring表达式进行安全保护
上表提供的方法都是一维的,如果使用了hasRole() 方法限制特定角色,就无法使用 hasIpAddress() 限制特定的 ip 地址。借助 access() 和 SpEL 表达式可以实现多维的访问限制:
1 | .antMatchers("/splitter/me").access("hasRole('ROLE_SPLITTER') and hasIpAddress('192.168.2.1')") |
安全通道
requiresChannel()方法会为选定的 URL 强制使用 HTTPS。
1 |
|
有些页面不需要通过 HTTPS 传送,如首页(不包含敏感信息),可以声明始终通过 HTTP 传送。此时尽管通过 https 访问首页,也会重定向到 http 通道。
1 | .antMatchers("/").requiresInsecure(); |
认证用户
1、启动默认的登录页。
2、自定义登录页
3、拦截/logout请求
4、logout成功后重定向到首页
5、rememberMe 功能,通过在 cookie 存储 一个 token 实现。
1 | http |
方法级别安全
Spring Security 使用Spring AOP保护方法调用——借助于对象代理和使用通知,能够确保只有具备适当权限的用户才能访问安全保护的方法。
Spring Security提供了三种不同的安全注解:
- Spring Security自带的@Secured注解;
- JSR-250的@RolesAllowed注解;
- 表达式驱动的注解,包括@PreAuthorize、@PostAuthorize、@PreFilter和@PostFilter。
@Secured和@RolesAllowed方案类似,基于用户所授予的权限限制对方法的访问。@PreFilter/@和ostFilter能够过滤方法返回和传入方法的集合。
@Secured
要启用基于注解的方法安全性,关键之处在于要在配置类上使用@EnableGlobalMethodSecurity。
1 |
|
GlobalMethodSecurityConfiguration 能够为方法级别的安全性提供更精细的配置。securedEnabled 属性的值为true,将会创建一个切点,Spring Security切面就会包装带有@Secured注解的方法。
用户必须具备ROLE_SPITTER或ROLE_ADMIN权限才能触发addSpittle:
1 |
|
如果方法被没有认证的用户或没有所需权限的用户调用,保护这个方法的切面将抛出一个Spring Security异常(非检查型异常)。
@RolesAllowed
开启 @RolesAllowed 注解功能:
1 |
|
@Secured注解的不足之处在于它是Spring特定的注解。如果更倾向于使用Java标准定义的注解,应该考虑使用@RolesAllowed注解。
1 |
|
使用表达式实现方法级别的安全性
开启方法调用前后注解:
1 |
|

方法调用前检查,#spittle.text.length()检查传入方法的参数:
1 |
|
进入方法以前过滤输入值:
1 |
@PreFilter注解能够保证传递给deleteSpittles()方法的列表中,只包含当前用户有权限删除的Spittle。targetObject是Spring Security提供的另外一个值,它代表了要进行计算的当前列表元素。
阅读量