Spring 的 AOP 支持
- Spring 的 AOP 代理由 Spring 的 IoC 容器负责生成、管理,其依赖关系也由 IoC 容器负责管理。即 AOP 代理可以直接使用容器中的其他 Bean 作为目标,这种关系由 IoC 容器的依赖注入提供
- Spring 和其他纯 Java AOP 框架一样,在运行时完成织入
- Spring 默认使用 Java 动态代理(代理接口)来创建 AOP 代理,也可以使用 CGLIB 代理(代理类)
- Spring 目前仅支持将方法调用作为连接点。如果需要把对 Field 的访问和更新也作为增强处理的连接点,则需要考虑使用 AspectJ
- Spring 可以无缝的整合 Spring AOP、Ioc 和 AspectJ,使得所有 AOP 应用完全融入基于 Spring 的框架
- Spring 有两种定义切入点和增强处理的方式
- 基于 Annotation:使用 @Aspect、@Pointcut 等 Annotation 来标注切入点和增强处理
- 基于 XML 配置文件:使用 Spring 配置文件来定义切入点和增强处理
基于 Annotation 的“零配置”方式
基于 Annotation 的“零配置”方式步骤:
-
引入 aop Schema
xmlns:aop="http://www.springframework.org/schema/aop" http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
-
在 配置文件中加入支持
<!-- 启动 @AspectJ 支持 --> <aop:aspect-autoproxy/>
这里实际上是利用 aop Schema 简化了配置,实际上配置了 AnnotationAwareAspectJAutoProxyCreator 这个 Bean 后处理器,为容器中 Bean 生成 AOP 代理(添加下面到 Spring 配置文件中是一样的)
<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator"/>
Spring 只是使用了和 AspectJ 一样的注解,但并没有 AspectJ 的编译器和织入器,底层仍然使用 Spring AOP,依然是在运行时动态生成 AOP 代理,并不依赖 AspectJ 的编译器或织入器
添加两个 AspectJ 库:
aspectjweaver.jar
和aspectjrt.jar
-
定义切面 Bean 和增强处理
- @Aspect 定义切面 Bean
- @Before 增强处理只能在目标方法执行之前织入增强
- @AfterReturning 增强处理在目标方法正常完成后被织入
- @AfterThrowing 增强处理主要用于处理程序中未处理的异常
- @After 增强处理不管目标方法如何结束,都会被织入
- @Around 增强处理近似等于 @Before + @AfterReturning,既可在执行目标方法前织入,也可在目标方法织入增强动作。它可决定目标方法执行时间、执行方式,甚至完全阻止执行。可改变目标方法的参数值,也可改变目标方法的返回值。@Around 增强处理需要在线程安全下执行
// 定义一个切面类 @Aspect public class SomeAspect { // 匹配 com.lian.service.impl 包下所有类的所有方法的执行作为切入点 @Before("execution(* com.lian.service.impl.*.*(..))") public void authority() { } // 指定一个返回值参数名,增强处理定义的方法可通过该形参名访问目标方法的返回值 // 匹配 com.lian.service.impl 包下所有类的所有方法的执行作为切入点 @AfterReturning(returing="rvt", pointcut="execution(* com.lian.service.impl.*.*(..))") public void log(Object rvt) { } // 指定一个返回值参数名,增强处理定义的方法可通过该形参名访问目标方法所抛出的异常对象 // 匹配 com.lian.service.impl 包下所有类的所有方法的执行作为切入点 @AfterThrowing(throwing="ex", pointcut="execution(* com.lian.service.impl.*.*(..))") public void doRecoveryActions(Throwable ex) { } // 匹配 com.lian.service.impl 包下所有类的所有方法的执行作为切入点 @After("execution(* com.lian.service.impl.*.*(..))") public void release() { } // Around 增强处理方法第一个形参必须是 ProceedingJoinPoint 类型 // 匹配 com.lian.service.impl 包下所有类的所有方法的执行作为切入点 @Around("execution(* com.lian.service.impl.*.*(..))") public Object processTx(ProceedingJoinPoint jp) throws Throwable { // ProceedingJoinPoint 对象的其他常用方法: // Object[] getArgs() 返回参数 // Signature getSignature() 返回被增强方法的相关信息 // Object getTarget() 返回被织入增强处理的目标对象 // Object getThis() 返回 AOP 框架为目标对象生成的代理对象 // proceed() 方法用于执行目标方法 // proceed() 方法返回值为目标方法执行后的返回值 // proceed(Object[] objs) 方法可传进一个 Object[] 对象,该 Object[] 数组中的值将被传入目标方法作为执行方法的实参 Object rvt = jp.proceed(new Object[]{"被改变的参数"}); return rvt + "新增的内容"; } }
增强处理的执行顺序:
* 进入目标方法时:Around -> Before
* 退出目标方法时:Around -> AfterReturning -> After
可自定义切面类的执行顺序
切入点的定义与切入点指示符
基于 XML 配置文件的管理方式
- 在 Spring 配置文件中,所有切面、切入点和增强处理都必须定义在
<aop:config.../>
元素内部,可以有多个<aop:config.../>
元素 - 一个
<aop:config.../>
元素可以包含 pointcut、advisor、aspect(<aop:aspect.../>
) 元素,且这三个元素必须按此顺序定义。aspect 元素下可以包含多个子元素
基于 XML 配置文件的管理方式步骤:
-
配置切面
使用<aop:aspect.../>
元素来定义切面,其实质是将一个已有的 Bean 转化为切面 Bean<aop:config> <!-- 将容器中的 afterAdviceBean 转换成切面 Bean ,切面 Bean 的新名称为 afterAdviceAspect --> <!-- id 属性 --> <!-- ref 属性 --> <!-- order 属性,指定该切面 Bean 的优先级,数值越小优先级越高 --> <aop:aspect id="afterAdviceAspect" ref="afterAdviceBean"> ... </aop:aspect> </aop:config>
-
配置增强处理
使用 XML 配置增强处理分别依赖于如下几个元素:<aop:before.../>
<aop:after.../>
<aop:after-returning.../>
<aop:after-throwing.../>
-
<aop:around.../>
这些元素可指定的属性有: - pointcut —— 指定一个切入表达式
- pointcut-ref —— 指定一个切入点名,与 pointcut 属性选其一
- method —— 指定方法名
- throwing —— 属性仅对
<aop:after-throwing.../>
元素有效,指定一个形参名 - returning —— 属性仅对
<aop:after-returning.../>
元素有效,指定一个形参名
当定义切入点表达式时,一样支持 execution、within、args、this、target 和 bean 等切入点指示符,但组合运算符为:and(替代 &&)、or(替代 ||)、not(替代 !)
<aop:config> <aop:aspect id="afterAdviceAspect" ref="afterAdviceBean" order="2"> <aop:before pointcut="execution(* com.lian.service.impl.*.*(..))" method="doBefore"/> </aop:aspect> </aop:config>
-
配置切入点
可以定义切入点来重用切入点表达式,使用<aop:pointcut.../>
元素来定义切入点
<aop:pointcut.../>
元素作为<aop:config.../>
的子元素时,表明切入点可以被多个切面共享
<aop:pointcut.../>
元素作为<aop:aspect.../>
的子元素时,表明切入点只能在对应切面有效
<aop:pointcut.../>
元素可指定的属性有:- id
- expression —— 切入点关联的表达式
增强处理元素只需要在 pointcut-ref 属性指定对应 id 的切入点即可
Spring 事务
- Spring 的事务管理不需要与任何特定事务 API 耦合
- 声明式事务基于 AOP 实现
- Java EE 应用的传统事务有两种策略:全局事务与局部事务
- 全局事务由应用服务器管理,需要底层服务器的 JTA 支持。可以跨多个事务性的资源(多个数据库)
- 局部事务与所采用的持久化技术有关(采用 JDBC 时,需要 Connection 对象来操作事务;采用 Hibernate 时,需要 Session 对象来操作事务)。应用服务器不需要参与事务管理,因此不能保证跨多个事务性资源的事务的正确性
- Spring 事务策略是通过 PlatformTransactionManager 接口体现的,是 Spring 事务策略的核心
public interface PlatformTransactionManager { // 平台无关的获取事务的方法 // TransactionStatus 对象表示一个事务,被关联在当前执行的线程上 // 如果当前执行的线程已经处在事务管理下,则返回当前线程的事务对象,否则,系统将新建一个事务对象后返回 // TransactionDefinition 接口定义了一个事务规则,包括:事务隔离、事务传播、事务超时、只读状态 TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; // 平台无关的事务提交方法 void commit(TransactionStatus status) throws TransactionException; // 平台无关的事务回滚方法 void rollback(TransactionStatus status) throws TransactionException; }
- PlatformTransactionManager 是一个与任何事务策略分离的接口,随着底层不同事务策略的切换,应用必须采用不同的实现类
- 当底层采用不同的持久化技术时,系统只需采用不同的 PlatformTransactionManager 实现类即可
- 即使使用容器管理的 JTA,代码依然无需执行 JNDI 查找,无需与特定的 JTA 资源耦合在一起,通过配置文件,JTA 资源传给 PlatformTransactionManager 实现类
- Spring 支持跨多个事务性资源的全局事务,前提是底层的应用服务器支持 JTA 全局事务
- Spring 支持两种事务管理方式:编程式事务管理(获取容器中的 transactionManager Bean 实例)、声明式事务管理(AOP)。一般采用声明式事务管理
一些不同持久层访问环境及对应 PlatformTransactionManager 实现类举例(可能有不同的实现类):
- JDBC 数据源的局部事务策略配置
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean>
- 容器管理的 JTA 全局事务策略配置
当配置 JtaTransactionManager 全局事务管理策略时,只需指定事务管理器实现类,无需传入额外的事务资源。因为全局事务的 JTA 资源由 Java EE 服务器提供,而 Spring 容器能自行从 Java EE 服务器中获取该事务资源<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>
- Hibernate 持久层技术的局部事务策略配置
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory"/> </bean>
- Hibernate 持久层技术的 JTA 全局事务策略配置
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>
声明式事务管理的事务配置
声明式事务管理无需在程序中书写任何的事务操作代码,而是通过在 XML 文件中为业务组件配置事务代理(AOP 代理的一种),AOP 为事务代理所织入的增强处理也由 Spring 提供:在目标方法执行之前,织入开始事务;在目标方法执行之后,织入结束事务
声明式事务管理的事务配置步骤:
- 使用 tx Schema
xmlns:tx="http://www.springframework.org/schema/tx" http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
- tx 命名空间下提供
<tx:advice.../>
元素来配置事务增强处理,然后在<aop:advisor.../>
元素中启用该自动代理
当采用<tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx-attributes> <!-- 为一批方法指定所需的事务语义,包括:事务传播属性、事务隔离属性、事务超时属性、只读属性、对指定异常回滚、对指定异常不回滚 --> <!-- name 属性,必选,与事务语义关联的方法名,支持通配符 --> <!-- propagation 属性,指定事务传播行为,默认为 Propagation.REQUIRED --> <!-- isolation 属性,指定事务的隔离级别,默认为 Isolation.DEFAULT --> <!-- timeout 属性,指定事务的超时时间(秒),默认为 -1 不超时 --> <!-- read-only 属性,指定事务是否只读,默认为 false --> <!-- rollback-for 属性,指定触发事务回滚的异常类(全限定类名),多个以 , 隔开 --> <!-- no-rollback-for 属性,指定不触发事务回滚的异常类(全限定类名),多个以 , 隔开 --> <tx:method name="get*" read-only="true"/> <tx method name="*"/> </tx-attributes> </tx:advice> <aop:config> <!-- 匹配 com.lian.dao.impl 包里所有以 Impl 结尾的类里所有方法 --> <aop:pointcut id="myPointcut" expression="execution(* com.lian.dao.impl.*Impl.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="myPointcut"/> </aop:config>
<aop:advisor.../>
元素将 Advice 和切入点绑定时,实际是由 Spring 提供的 Bean 后处理器完成的。BeanNameAutoProxyCreator、DefaultAdvisorAutoProxyCreator 两个 Bean 后处理器都可以后处理容器中的 Bean(为它们织入切面中包含的处理)
配置的属性相关说明:
- 事务隔离:当前事务与其他事务的隔离程度
- 事务传播:通常,在事务中执行的代码都会在当前事务中运行,但是,如果一个事务上下文已经存在,有几个选项可指定该事务性方法的执行行为
Spring 支持的事务传播规则:-
PROPAGATION_MANDATORY
—— 要求调用该方法的线程必须处于事务环境中,否则抛出异常 -
PROPAGATION_NESTED
—— 如果执行该方法的线程已处于事务环境下,依然启动新的事务,方法在嵌套的事务里执行。如果没有,就启动事务,然后执行方法 -
PROPAGATION_NEVER
—— 不允许调用该方法的线程处在事务环境下,如果是,则抛出异常 -
PROPAGATION_NOT_SUPPORT
—— 如果调用该方法的线程处在事务环境下,则先暂停当前事务,然后执行该方法 -
PROPAGATION_REQUIRED
—— 要求在事务的环境中执行该方法,如果调用该方法的线程处在事务环境下,则直接调用。如果没有,就启动事务,然后执行方法 -
PROPAGATION_REQUIRES_NEW
—— 该方法要求在新的事务环境中执行,如果当前执行线程已处于事务中,则先暂停当前事务,启动新事务后执行该方法。如果没有,就启动事务,然后执行方法 -
PROPAGATION_SUPPORTS
—— 如果当前执行线程处于事务中,则使用当前事务,否则不使用事务
-
- 事务超时:事务在超时前能运行多久,也就是事务的最长持续时间。如果事务一直没有被提交或回滚,则在超出该时间后,系统自动回滚事务
- 只读状态:只读事务不修改任何数据
使用 Annotation 来进行声明式事务管理的事务配置
使用 @Transactional 修饰 Bean 类,表明事务的设置对整个 Bean 类起作用,使用 @Transactional 修饰方法,表明事务的设置只对该方法有效
@Transactional 的属性有:
- isolation
- propagation
- readOnly
- timeout
- rollbackFor
- rollbackForClasssName
- noRollbackFor
- noRollbackForClassName
配置文件的设置
<!-- 根据 Annotation 来生成事务代理 -->
<tx:annotation-driven transaction-manager="transactionManager"/>