古道长亭

Contact me with ixiaoqiang0011@gmail.com


  • 首页

  • 归档

  • 分类

  • 关于

  • Book

  • 搜索

Aop应用

时间: 2022-10-06   |   分类: Spring   | 字数: 5041 字 | 阅读约: 11分钟 | 阅读次数:

Spring AOP应用

一、aop概念

img

img

名词解释
Joinpoint 连接点它指的是那些可以⽤于把增强代码加⼊到业务主线中的点,那么由上图中我们可以看出,这些点指的就是⽅法。在⽅法执⾏的前后通过动态代理技术加⼊增强的代码。在Spring框架AOP思想的技术实现中,也只⽀持⽅法类型的连接点。
Pointcut 切入点它指的是那些已经把增强代码加⼊到业务主线进来之后的连接点。由上图中,我们看出表现层 transfer⽅法就只是连接点,因为判断访问权限的功能并没有对其增强。
Advice 通知/增加它指的是切⾯类中⽤于提供增强功能的⽅法。并且不同的⽅法增强的时机是不⼀样的。⽐如,开启事务肯定要在业务⽅法执⾏之前执⾏;提交事务要在业务⽅法正常执⾏之后执⾏,⽽回滚事务要在业务⽅法执⾏产⽣异常之后执⾏等等。那么这些就是通知的类型。其分类有:前置通知 后置通知 异常通知 最终通知 环绕通知。
Target 目标对象它指的是代理的⽬标对象。即被代理对象。
Proxy 代理它指的是⼀个类被AOP织⼊增强后,产⽣的代理类。即代理对象。
Weaving 织入它指的是把增强应⽤到⽬标对象来创建新的代理对象的过程。spring采⽤动态代理织⼊,⽽AspectJ采⽤编译期织⼊和类装载期织⼊。
Aspect 切面它指定是增强的代码所关注的⽅⾯,把这些相关的增强代码定义到⼀个类中,这个类就是切⾯类。例如,事务切⾯,它⾥⾯定义的⽅法就是和事务相关的,像开启事务,提交事务,回滚事务等等,不会定义其他与事务⽆关的⽅法。我们前⾯的案例中 TrasnactionManager就是⼀个切⾯。

连接点:⽅法开始时、结束时、正常运⾏完毕时、⽅法异常时等这些特殊的时机点,我们称之为连接点,项⽬中每个⽅法都有连接点,连接点是⼀种候选点

切⼊点:指定AOP思想想要影响的具体⽅法是哪些,描述感兴趣的⽅法

Advice增强:

  • 第⼀个层次:指的是横切逻辑

  • 第⼆个层次:⽅位点(在某⼀些连接点上加⼊横切逻辑,那么这些连接点就叫做⽅位点,描述的是具体的特殊时机)

Aspect切⾯:切⾯概念是对上述概念的⼀个综合

Aspect切⾯= 切⼊点+增强= 切⼊点(锁定⽅法) + ⽅位点(锁定⽅法中的特殊时机)+ 横切逻辑

众多的概念,⽬的就是为了锁定要在哪个地⽅插⼊什么横切逻辑代码

二、代理的选择

spring根据代理对象实现接口情况选择,当代理对象:

  • 未实现接口:cglib代理

  • 实现接口:jdk代理

    Tips: 可通过配置强制使用cglib

三、aop配置方式

  • xml配置
  • xml+注解组合
  • 纯注解

四、Spring 中 aop实现

1.XML模式

  • 开启aop支持

    <!--开启spring对注解aop的⽀持-->
    <aop:aspectj-autoproxy/>
    
  • 添加约束头

    xmlns:aop="http://www.springframework.org/schema/aop"
    
    http://www.springframework.org/schema/aop
    https://www.springframework.org/schema/aop/spring-aop.xsd
    
  • 配置步骤(由于xml现在已经不主流,此处仅做简单示例,不加demo代码了)

    <!--把通知bean交给spring来管理-->
    <bean id="logUtil" class="com.example.demo.utils.LogUtil"></bean>
    
    <!--开始aop的配置-->
    <aop:config>
      <!--配置切⾯-->
      <aop:aspect id="logAdvice" ref="logUtil">
    
      	<!--配置前置通知-->
      	<aop:before method="printLog" pointcut="execution(public *
                 com.lagou.service.impl.TransferServiceImpl
                          .updateAccountByCardNo(com.lagou.pojo.Account))">
        </aop:before>
      </aop:aspect>
    </aop:config>
    
  • 细节

    1) 关于切入点表达式

    全限定⽅法名 访问修饰符 返回值 包名.包名.包名.类名.⽅法名(参数列表)
    
    1.全匹配方式
    public void com.demo.service.impl.TransferServiceImpl.updateAccountByCardNo(com.demo.pojo.Account)
    
    2.访问修饰符可以省略
    void com.demo.service.impl.TransferServiceImpl.updateAccountByCardNo(com.demo.pojo.Account)
    
    3.返回值可以使⽤*,表示任意返回值
    * com.demo.service.impl.TransferServiceImpl.updateAccountByCardNo(com.demo.pojo.Account)
    
    4.包名可以使⽤.表示任意包,但是有⼏级包,必须写⼏个
    * ....TransferServiceImpl.updateAccountByCardNo(com.demo.pojo.Account)
    
    5.包名可以使⽤..表示当前包及其⼦包
    * ..TransferServiceImpl.updateAccountByCardNo(com.demo.pojo.Account)
    
    6.类名和⽅法名,都可以使⽤.表示任意类,任意⽅法
    * ...updateAccountByCardNo(com.demo.pojo.Account)
    
    7.参数列表,可以使⽤具体类型
    	基本类型直接写类型名称 如: int
    	引⽤类型必须写全限定类名,如:java.lang.String
    
    8.参数列表可以使⽤*,表示任意参数类型,但是必须有参数
    * *..*.*(*)
    
    9.参数列表可以使⽤..,表示有⽆参数均可。有参数可以是任意类型
    * *..*.*(..)
    
    10.全通配⽅式:
    * *..*.*(..)
    

    2)改变代理的方式

    <!--使⽤aop:config标签配置-->
    <aop:config proxy-target-class="true">
    
    <!--使⽤aop:aspectj-autoproxy标签配置-->
    <!--此标签是基于XML和注解组合配置AOP时的必备标签,表示Spring开启注解配置AOP的⽀持-->
    <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectjautoproxy>
    

    3)五种通知类型

    1.前置通知:aop:before标签,前置通知可以获取切⼊点⽅法的参数,并对其进⾏增强,属性:
    	  method:⽤于指定前置通知的⽅法名称
    		pointcut:⽤于指定切⼊点表达式
    		pointcut-ref:⽤于指定切⼊点表达式的引⽤
    
    2.正常执⾏时通知:aop:after-returning标签
    3.异常通知:op:after-throwing标签,异常通知不仅可以获取切⼊点⽅法执⾏的参数,也可以获取切⼊点⽅法执⾏产	 ⽣的异常信息
    4.最终通知:aop:after
      最终通知的执⾏时机是在切⼊点⽅法(业务核⼼⽅法)执⾏完成之后,切⼊点⽅法返回之前执⾏。
      换句话说,⽆论切⼊点⽅法执⾏是否产⽣异常,它都会在返回之前执⾏
    5.环绕通知:aop:around
    	环绕通知,它是有别于前⾯四种通知类型外的特殊通知。前⾯四种通知(前置,后置,异常和最终)
    	它们都是指定何时增强的通知类型。⽽环绕通知,它是Spring框架为我们提供的⼀种可以通过编码的
    	⽅式,控制增强代码何时执⾏的通知类型。它⾥⾯借助的ProceedingJoinPoint接⼝及其实现类,
    	实现⼿动触发切⼊点⽅法的调⽤
    

2.组合模式

  • 开启支持-很有必要

    <!--开启spring对注解aop的⽀持-->
    <aop:aspectj-autoproxy/>
    
  • 示例代码

    /**
     * @author zhaojianqiang
     */
    @Component
    @Aspect
    public class LogUtils {
    
    
        @Pointcut("execution(* com.example.demo.service.impl.TransferServiceImpl.*(..))")
        public void pt1() {
    
        }
    
    
        /**
         * 业务逻辑开始之前执行
         */
        @Before("pt1()")
        public void beforeMethod(JoinPoint joinPoint) {
            Object[] args = joinPoint.getArgs();
            for (int i = 0; i < args.length; i++) {
                Object arg = args[i];
                System.out.println(arg);
            }
            System.out.println("业务逻辑开始执行之前执行.......");
        }
    
    
        /**
         * 业务逻辑结束时执行(无论异常与否)
         */
        @After("pt1()")
        public void afterMethod() {
            System.out.println("业务逻辑结束时执行,无论异常与否都执行.......");
        }
    
    
        /**
         * 异常时时执行
         */
        @AfterThrowing("pt1()")
        public void exceptionMethod() {
            System.out.println("异常时执行.......");
        }
    
    
        /**
         * 业务逻辑正常时执行
         */
        @AfterReturning(value = "pt1()", returning = "retVal")
        public void successMethod(Object retVal) {
            System.out.println("业务逻辑正常时执行.......");
        }
    
    
        /**
         * 环绕通知
         */
        /*@Around("pt1()")*/
        public Object arroundMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            Object result = null;
            try {
                System.out.println("环绕通知中的 beforeMethod....");
                Object[] args = proceedingJoinPoint.getArgs();
                result = proceedingJoinPoint.proceed(args);
                System.out.println("环绕通知中的 afterMethod....");
            } catch (Exception e) {
                System.out.println("环绕通知中的 exceptionMethod....");
            } finally {
                System.out.println("环绕通知中的 successMethod....");
            }
    
            return result;
        }
    
    }
    

3.注解模式

在使⽤注解驱动开发aop时,我们要明确的就是,是注解替换掉配置⽂件中的下⾯这⾏配置:

<!--开启spring对注解aop的⽀持-->
<aop:aspectj-autoproxy/>
@Configuration
@ComponentScan("com.example.demo")
@EnableAspectJAutoProxy //开启spring对注解AOP的⽀持
public class SpringConfiguration {
}

五、声明式事务的支持

1. 事务回顾

1.1 事务概念

事务指逻辑上的⼀组操作,组成这组操作的各个单元,要么全部成功,要么全部不成功。从⽽确保了数

据的准确与安全。

1.2 事务四大特性

  • 原⼦性(Atomicity)

    原⼦性是指事务是⼀个不可分割的⼯作单位,事务中的操作要么都发⽣,要么都不发⽣。从操作的⻆度来描述,事务中的各个操作要么都成功要么都失败

  • ⼀致性(Consistency)

    事务必须使数据库从⼀个⼀致性状态变换到另外⼀个⼀致性状态。例如转账前A有1000,B有1000。转账后A+B也得是2000。⼀致性是从数据的⻆度来说的,(1000,1000) (900,1100),不应该出现(900,1000)

  • 隔离性(Isolation)

    事务的隔离性是多个⽤户并发访问数据库时,数据库为每⼀个⽤户开启的事务,每个事务不能被其他事务的操作数据所⼲扰,多个并发事务之间要相互隔离。

    ⽐如:事务1给员⼯涨⼯资2000,但是事务1尚未被提交,员⼯发起事务2查询⼯资,发现⼯资涨了2000块钱,读到了事务1尚未提交的数据(脏读)

  • 持久性(Durability)

    持久性是指⼀个事务⼀旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发⽣故障也不应该对其有任何影响。

1.3 事务隔离级别

不考虑隔离级别,会出现以下情况:(以下情况全是错误的),也即为隔离级别在解决事务并发问题

  • 脏读:⼀个线程中的事务读到了另外⼀个线程中未提交的数据。

  • 不可重复读:⼀个线程中的事务读到了另外⼀个线程中已经提交的update的数据(前后内容不⼀样)

    场景:

    员⼯A发起事务1,查询⼯资,⼯资为1w,此时事务1尚未关闭

    财务⼈员发起了事务2,给员⼯A张了2000块钱,并且提交了事务

    员⼯A通过事务1再次发起查询请求,发现⼯资为1.2w,原来读出来1w读不到了,叫做不可重复读

  • 虚读(幻读):⼀个线程中的事务读到了另外⼀个线程中已经提交的insert或者delete的数据(前后条数不⼀样)

    场景:

    事务1查询所有⼯资为1w的员⼯的总数,查询出来了10个⼈,此时事务尚未关闭

    事务2财务⼈员发起,新来员⼯,⼯资1w,向表中插⼊了2条数据,并且提交了事务

    事务1再次查询⼯资为1w的员⼯个数,发现有12个⼈,⻅了⻤了

数据库共定义了四种隔离级别:

  • Serializable(串⾏化):可避免脏读、不可重复读、虚读情况的发⽣。(串⾏化) 最⾼

  • Repeatable read(可重复读):可避免脏读、不可重复读情况的发⽣。(幻读有可能发⽣) 第⼆

    该机制下会对要update的⾏进⾏加锁

  • Read committed(读已提交):可避免脏读情况发⽣。不可重复读和幻读⼀定会发⽣。 第三

  • Read uncommitted(读未提交):最低级别,以上情况均⽆法保证。(读未提交) 最低

注意:级别依次升⾼,效率依次降低

MySQL的默认隔离级别是:REPEATABLE READ

查询当前使⽤的隔离级别: select @@tx_isolation;

设置MySQL事务的隔离级别: set session transaction isolation level xxx; (设置的是当前mysql连接会话的,并不是永久改变的)

1.4 事务传播行为

事务往往在service层进⾏控制,如果出现service层⽅法A调⽤了另外⼀个service层⽅法B,A和B⽅法本身都已经被添加了事务控制,那么A调⽤B的时候,就需要进⾏事务的⼀些协商,这就叫做事务的传播⾏为。

A调⽤B,我们站在B的⻆度来观察来定义事务的传播⾏为

PROPAGATION_REQUIRED如果当前没有事务,就新建⼀个事务,如果已经存在⼀个事务中,加⼊到这个事务中。这是最常⻅的选择。
PROPAGATION_SUPPORTS⽀持当前事务,如果当前没有事务,就以⾮事务⽅式执⾏。
PROPAGATION_MANDATORY使⽤当前的事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED以⾮事务⽅式执⾏操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER以⾮事务⽅式执⾏,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED如果当前存在事务,则在嵌套事务内执⾏。如果当前没有事务,则执⾏与PROPAGATION_REQUIRED类似的操作。

2. Spring中事务API

  • mybatis: sqlSession.commit();
  • hibernate: session.commit();

PlatformTransactionManager

package org.springframework.transaction;

import org.springframework.lang.Nullable;

public interface PlatformTransactionManager {

	TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
			throws TransactionException;

	void commit(TransactionStatus status) throws TransactionException;

	void rollback(TransactionStatus status) throws TransactionException;

}

此接⼝是Spring的事务管理器核⼼接⼝。Spring本身并不⽀持事务实现,只是负责提供标准,应⽤底层

⽀持什么样的事务,需要提供具体实现类。

此处也是策略模式的具体应⽤。在Spring框架中,也为我们内置了⼀些具体策略

如:DataSourceTransactionManager(mybatis) , HibernateTransactionManager(hibernate)

DataSourceTransactionManager 归根结底是横切逻辑代码,声明式事务要做的就是使⽤Aop(动态代

理)来将事务控制逻辑织⼊到业务代码

3. Spring声明式事务配置

3.1 纯xml

此种方式目前很少使用,我们不做代码示例

  • 依赖

    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.1.12.RELEASE</version>
    </dependency>
    <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
    </dependency>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.1.12.RELEASE</version>
    
  • xml配置

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
        <tx:advice id="txAdvice" transaction-manager="transactionManager">
            <!--定制事务细节,传播⾏为、隔离级别等-->
            <tx:attributes>
                <!--⼀般性配置-->
                <tx:method name="*" read-only="false" propagation="REQUIRED" isolation="DEFAULT" timeout="-1"/>
                <!--针对查询的覆盖性配置-->
                <tx:method name="query*" read-only="true" propagation="SUPPORTS"/>
            </tx:attributes>
        </tx:advice>
        <aop:config>
            <!--advice-ref指向增强=横切逻辑+⽅位-->
            <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.example.demo.service.impl.TransferServiceImpl.*(*))"/>
        </aop:config>
    

3.2 xml+注解

  • xml配置

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
        <!--开启spring对注解事务的⽀持-->
        <tx:annotation-driven transaction-manager="transactionManager"/>
    
  • 在接口、类或者方法上添加@Transactional注解

    //注解参数可自定义
    @Transactional(readOnly = true,propagation = Propagation.SUPPORTS, rollbackFor = Exception.class)
    

3.3 纯注解

Spring基于注解驱动开发的事务控制配置,只需要把 xml 配置部分改为注解实现。只是需要⼀个注解替换掉xml配置⽂件中的<tx:annotation-driven transactionmanager=“transactionManager”/> 配置。

在注解类上配置如下即可:

@EnableTransactionManagement//开启spring注解事务的⽀持
public class SpringConfiguration {
}
#Spring# #Aop#
QQ扫一扫交流

标题:Aop应用

作者:古道长亭

声明: 欢迎加群交流!

如有帮助,欢迎多多交流 ^_^

微信打赏

支付宝打赏

架构设计基本原则
分布式服务治理
  • 文章目录
  • 站点概览
古道长亭

古道长亭

Always remember that your present situation is not your final destination. The best is yet to come.

226 日志
57 分类
104 标签
GitHub Gitee
友情链接
  • 古道长亭的BOOK
  • JAVA学习
标签云
  • Mysql
  • 搜索引擎
  • Mybatis
  • 容器
  • 架构
  • 消息队列
  • Flink
  • Sharding sphere
  • 流处理
  • 缓存
  • 一、aop概念
  • 二、代理的选择
  • 三、aop配置方式
  • 四、Spring 中 aop实现
    • 1.XML模式
    • 2.组合模式
    • 3.注解模式
  • 五、声明式事务的支持
    • 1. 事务回顾
    • 2. Spring中事务API
    • 3. Spring声明式事务配置
© 2019 - 2024 京ICP备19012088号-1
0%