Spring 基础5 —— AOP 与事务

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 的“零配置”方式步骤:

  1. 引入 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
    
  2. 在 配置文件中加入支持

    <!-- 启动 @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 的编译器或织入器

  3. 添加两个 AspectJ 库:aspectjweaver.jaraspectjrt.jar

  4. 定义切面 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 配置文件的管理方式步骤:

  1. 配置切面
    使用 <aop:aspect.../> 元素来定义切面,其实质是将一个已有的 Bean 转化为切面 Bean

    <aop:config>
        <!-- 将容器中的 afterAdviceBean 转换成切面 Bean ,切面 Bean 的新名称为 afterAdviceAspect -->
        <!-- id 属性 -->
        <!-- ref 属性 -->
        <!-- order 属性,指定该切面 Bean 的优先级,数值越小优先级越高 -->
        <aop:aspect id="afterAdviceAspect" ref="afterAdviceBean">
            ...
        </aop:aspect>
    </aop:config>
    
  2. 配置增强处理
    使用 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>
    
  3. 配置切入点
    可以定义切入点来重用切入点表达式,使用 <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 提供:在目标方法执行之前,织入开始事务;在目标方法执行之后,织入结束事务

声明式事务管理的事务配置步骤:

  1. 使用 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
    
  2. 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"/>
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,598评论 18 139
  • Spring简介 spring框架由Rod Johnson开发,2004年发布了Spring框架的第一版。Spri...
    qiuqiu_hz阅读 1,075评论 0 15
  • AOP实现可分为两类(按AOP框架修改源代码的时机): 静态AOP实现:AOP框架在编译阶段对程序进行修改,即实现...
    数独题阅读 2,300评论 0 22
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,739评论 6 342
  • 什么是Spring Spring是一个开源的Java EE开发框架。Spring框架的核心功能可以应用在任何Jav...
    jemmm阅读 16,439评论 1 133