Spring4——AOP——20200704

尚硅谷首套_Spring4 视频教程

AOP前奏

  • 代码混乱:越来越多的非业务需求(日志和验证等)加入后,原有的业务方法急剧膨胀,每个方法在处理核心逻辑的同时还必须兼顾其他多个关注点。
  • 代码分散:以日志需求为例,只是为了满足这一单一需求,就不得不在多个模块(方法)里多次重复相同的日志代码。如果日志需求发生变化,必须修改所有模块。
  • 可以采用动态代理,但相对来说难度比较高。

AOP简介

  • AOP(面向切面编程,Aspect-Oriented Programming):是一种新的方法论,是对传统OOP(面向对象编程,Object-Oriented Programming)的补充。
  • AOP的主要编程对象是切面(aspect),而切面模块化横切关注点。
  • 在应用AOP编程时,仍然需要定义公共功能,但可以明确的定义这个功能在哪里,以什么方式应用,并且不必修改受影响的类。这样一来横切关注点就被模块化到特殊的对象(切面)里。
  • AOP的好处:
    • 每个事务逻辑位于一个位置,代码不分散,便于维护和升级。
    • 业务模块更简洁,只包含核心业务代码

在这里插入图片描述

AOP术语

  • 切面(Aspect):横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象
  • 通知(Advice):切面必须要完成的工作
  • 目标(Target):被通知的对象
  • 代理(Proxy):向目标对象应用通知之后创建的对象
  • 连接点(Joinpoint):程序执行的某个特定位置。
      如类某个方法调用前、调用后、方法抛出异常后等。连接点由两个信息确定:方法表示的程序执行点,相对点表示的方位
      例如ArithmeticCalculator#add()方法执行前的连接点,执行点为ArithmeticCalculator#add();方位为该方法执行前的位置。
  • 切点(Pointcut):每个类都拥有多个连接点:例如rithmeticCalculator中所有方法实际上都是连接点,即连接点是程序类中客观存在的事务。AOP通过切点定位到特定的连接点。类比:连接点相当于数据库中的记录,切点相当于查询条件。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件。

Spring AOP

  • AspectJ:Java社区里最完整最流行的AOP框架
  • 在Spring2.0以上版本中,可以使用AspectJ注解或基于XML配置的AOP

在Spring中启用AspectJ注解支持

  • 要在Spring应用中使用AspectJ注解,必须在classpath下包含AspectJ类库:aopalliance.jar、aspectj.jar和spring-aspects.jar
  • 将aop Schema添加到<beans>根元素中。
  • 要在Spring IOC容器中启用AspectJ注解支持,只要在bean配置文件中定义一个空的XML元素<aop:aspectj-autoproxy>
  • 当SpringIOC容器侦测到Bean配置文件 中的 <aop:aspectj-autoproxy>元素时,会自动为与AspectJ切面匹配的Bean创建代理。

用AspectJ注解声明切面

  • 要在Spring中声明AspectJ切面,只需要在IOC容器中将切面声明为Bean实例。当在SpringIOC容器中初始化AspectJ切面之后,SpringIOC容器就会为那些与AspectJ切面相匹配的Bean创建代理。
  • 在AspectJ注解中,切面只是一个带有@Aspect注解的java类
  • 通知是标注有某种注解的简单java方法
  • AspectJ支持5种类型的通知注解
    • @Before
    • @After
    • @AfterRunning
    • @AfterThrowing
    • @Around

测试Demo——前置通知

  • 该类声明为切面:
@Component
@Aspect
public class LoggingAspect {
    @Before("execution(* aopimpl.ArithmeticCalculator.*(int,int))")
    public void beforeMethod(){
        System.out.println("The method begins");
    }
}
    <aop:aspectj-autoproxy />
bug记录

中间报NoSuchBeanDefinitionExceptionNoClassDefFoundError 怀疑是Aspectj导包的问题。

AspectJ导入jar包

  • aopalliance
  • aspectj.weaver
  • spring-aop
  • spring-aspects

参考链接——AOP开发——AspectJ的使用
Aspectj下载地址——https://www.eclipse.org/aspectj/downloads.php
Spring框架的AOP — AspectJ支持包下载的与安装——https://blog.csdn.net/Angelia620/article/details/85006254

测试Demo——后置通知

  • 后置通知是在连接点完成之后执行的,即连接点返回结果或抛出异常的时候,下方的后置通知记录了方法的终止。
  • 一个切面可以包含一个或多个通知。
  • 后置通知中还不能访问目标方法执行的结果。
//后置通知
    @After("execution (* aopimpl.ArithmeticCalculator.*(int,int))")
    public void afterMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        System.out.println("The method "+methodName+" ends ");
    }

返回通知与异常通知

/** 
     * 返回通知
     * @description:可以获取方法返回的参数
     * @param joinPoint
     * @param result 
     * @return: void
     * @author: 大颗
     * @time: 2020/7/4 17:20
     */
    @AfterReturning(value = "execution (* aopimpl.ArithmeticCalculator.*(int,int))",returning = "result")
    public void afterReturning(JoinPoint joinPoint,Object result){
        
        String methodName = joinPoint.getSignature().getName();
        System.out.println("The method "+methodName+" ends with "+result);
    }

异常通知

  • 只有连接点抛出异常时才执行异常通知
  • 将throwing属性添加到@AfterThrowing注解中,也可以访问连接点抛出的异常。Throwable是所有错误和异常类的超类。所以在异常通知方法可以捕获到任何错误和异常。
  • 如果只对某种特殊的异常类型感兴趣,可以将参数声明为其他异常的参数类型,然后通知就只在抛出这个类型及其子类的异常时才被执行。

    /**
     * 
     * 
     * @description:异常通知,可以访问到方法抛出的异常
     * @param joinPoint
     * @param ex
     * @return: void
     * @author: km
     * @time: 2020/7/4 17:16
     */
    @AfterThrowing(value = "execution (* aopimpl.ArithmeticCalculator.*(int,int))", throwing= "ex")
    public void afterThrowing(JoinPoint joinPoint,Throwable ex){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("The method "+methodName+" occurs exception: "+ex);
    }

环绕通知

/**
     * 环绕通知
     * @description: 需要携带ProceedingJoinPoint类型的参数。
     *               类似于动态代理的全过程:ProceedingJoinPoint类型的参数可以决定是否
     *               执行目标方法。
     *               且环绕通知必须要有返回值,返回值即为目标方法的返回值
     * @param pjd
     * @return: Object
     * @author: 大颗
     * @time: 2020/7/4 17:28
     */
    @Around("execution (* aopimpl.ArithmeticCalculator.*(int,int))")
    public Object aroundMethod(ProceedingJoinPoint pjd){
        String methodName = pjd.getSignature().getName();
        Object result = null;

        try{
            //前置通知
            System.out.println("The method "+methodName+" begins with "+Arrays.asList(pjd.getArgs()));
            result = pjd.proceed();
            //返回通知
            System.out.println("The method "+methodName+" ends with "+result);
        } catch (Throwable throwable) {
            //异常通知
            throwable.printStackTrace();
        }
        //后置通知
        System.out.println("The method "+methodName+" ends ");
        return result;
    }

切面优先级

可以用@Order(1)指定执行的顺序

@Order(1)
@Aspect
@Component

切入点

/**
     * 定义一个方法,用于声明切入点表达式,一般地,该方法中再不需要添入其他的代码
     * 使用@Pointcut来声明切入点表达式
     * 后面的其它通知直接使用方法名来引入当前的切入点
     */
    @Pointcut("execution(int aopimpl.ArithmeticCalculator.*(int,int))")
    public void declareJointPointExpression(){}

    /**
     * 使用方法名来引入切入点
     * @param joinPoint
     */
    @Before("declareJointPointExpression()")
    public void validateArgs(JoinPoint joinPoint){
        System.out.println("-->validate: " + Arrays.asList(joinPoint.getArgs()));
    }

基于配置文件的方式配置AOP

<!--    配置bean-->
    <bean id="arithmeticCalculator" class="aopimpl.ArithmeticCalculatorImpl">
    </bean>
<!--    配置切面bean-->
    <bean id="loggingAspect" class="aopimpl.LoggingAspect"></bean>
    <bean id="validationAspect" class="aopimpl.ValidationAspect"></bean>

<!--    配置aop-->
    <aop:config>
        <!--配置切点-->
        <aop:pointcut id="pointcut" expression="execution(int aopimpl.ArithmeticCalculator.*(int,int))"/>
        <aop:aspect ref="loggingAspect" order="1">
            <aop:before method="beforeMethod" pointcut-ref="pointcut"/>
            <aop:after method="afterMethod" pointcut-ref="pointcut"/>
            <aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"/>
            <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="ex" />
            <aop:around method="aroundMethod" pointcut-ref="pointcut"/>
        </aop:aspect>
        <aop:aspect ref="validationAspect" order="2">
            <aop:after method="validateArgs" pointcut-ref="pointcut"></aop:after>
        </aop:aspect>
    </aop:config>

Spring对JDBC的支持

配置JdbcTemplate

<!-- 导入资源文件-->
    <context:property-placeholder location="classpath:db.properties"/>
    <bean id="dataSourse" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="${jdbc.user}"></property>
        <property name="password" value="${jdbc.password"/>
        <property name="jdbcUrl" value="${jdbc.jdbcUrl"/>
        <property name="driverClass" value="${jdbc.driverClass}"/>
        <property name="initialPoolSize" value="${jdbc.initPoolSize}"/>
        <property name="maxPoolSize" value="${jdbc.maxPoolSize}"/>
        <property name="maxStatements" value="0"/>
        <property name="checkoutTimeout" value="100"/>
    </bean>

    <!-- 配置Spring的JdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSourse"></property>
    </bean>

Spring事务

示例:

事务注解

@Transactional(propagation = Propagation.REQUIRES_NEW,
            isolation = Isolation.READ_COMMITTED,
            timeout = 50,
            readOnly = true,
            rollbackFor = {IllegalAccessError.class})

事务传播行为(略)

使用propagation 指定事务的传播行为

事务传播行为描述
PROPAGATION_ REQUIRED需要事务处理。有则使用,无则新建。这是 Spring 默认的事务传播行为。该级别的特性是,如果 Context 中已经存在事务,那么就将当前需要使用事务的代码加入到 Context 的事务中执行,如果当前 Context 中不存在事务,则新建一个事务执行代码。这个级别通常能满足大多数的业务场景。
PROPAGATION_ SUPPORTS支持事务处理。该级别的特性是,如果 Context 存在事务,则将代码加入到 Context 的事务中执行,如果 Context 中没有事务,则使用 非事务 的方式执行。
PROPAGATION_ MANDATORY强制性要求事务。该级别的特性是,当要以事务的方式执行代码时,要求 Context 中必须已经存在事务,否则就会抛出异常!使用 MANDATORY 强制事务,可以有效地控制 “必须以事务执行的代码,却忘记给它加上事务控制” 这种情况的发生。举个简单的例子:有一个方法,对这个方法的要求是一旦被调用,该方法就必须包含在事务中才能正常执行,那么这个方法就适合设置为 PROPAGATION_MANDATORY 强制事务传播行为,从而在代码层面加以控制。
PROPAGATION_ REQUIRES_NEW每次都新建一个事务。该级别的特点是,当执行到一段需要事务的代码时,先判断 Context 中是否已经有事务存在,如果不存在,就新建一个事务;如果已经存在,就 suspend 挂起当前事务,然后创建一个新事务去执行,直到新事务执行完毕,才会恢复先前挂起的 Context 事务。
PROPAGATION_ NOT_SUPPORTED不支持事务。该级别的特点是,如果发现当前 Context 中有事务存在,则挂起该事务,然后执行逻辑代码,执行完毕后,恢复先前挂起的 Context 事务。这个传播行为的事务,可以缩小事务处理过程的范围。举个简单例子,在一个事务中,需要调用一段非核心业务的逻辑操作 1000 次,如果将这段逻辑放在事务中,会导致该事务的范围变大、生命周期变长,为了避免因事务范围扩大、周期变长而引发一些的事先没有考虑到的异常情况发生,可以将这段逻辑设置为 NOT_SUPPORTED 不支持事务传播行为。
PROPAGATION_ NEVER对事务要求更严格,不能出现事务!该级别的特点是,设置了该级别的代码,在执行前一旦发现 Context 中有事务存在,就会抛出 Runtime 异常,强制停止执行,有我无他!
PROPAGATION_ NESTED嵌套事务。该级别的特点是,如果 Context 中存在事务 A,就将当前代码对应的事务 B 加入到 事务 A 内部,嵌套执行;如果 Context 中不存在事务,则新建事务执行代码。换句话说,事务 A 与事务 B 之间是父子关系,A 是父,B 是子。理解嵌套事务的关键点是:save point。

父、子事务嵌套、save point 的说明:

  • 父事务会在子事务进入之前创建一个 save point;
  • 子事务 rollback ,父事务只会回滚到 save
    point,而不会回滚整个父事务;
  • 父事务 commit 之前,必须先 commit 子事务。

作者:uzip柚子皮 链接:https://www.jianshu.com/p/760399781b78 来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

首先准备如下两个 Service:

 class ServiceA {
     void methodA() {
         ServiceB.methodB();
     } }

class ServiceB {
     void methodB() {
     } }     
  1. 若 ServiceB.methodB() 的传播行为定义为 PROPAGATION_REQUIRED , 那么在执行
    ServiceA.methodA() 的时候,若 ServiceA.methodA() 已经开启了事务,这时调用
    ServiceB.methodB(),ServiceB.methodB() 将会运行在 ServiceA.methodA()
    的事务内部,而不再开启新的事务。而假如 ServiceA.methodA()
    运行的时候发现自己没有在事务中,就会为它分配一个新事务。这样,在 ServiceA.methodA() 或者在
    ServiceB.methodB() 内的任何地方出现异常,事务都会被回滚。即使 ServiceB.methodB() 的事务已经被
    提交,但是 ServiceA.methodA() 在接下来的过程中 fail 要回滚,ServiceB.methodB()
    也会跟着一起回滚。
  2. 假如 ServiceA.methodA() 的传播行为设置为
    PROPAGATION_REQUIRED,ServiceB.methodB() 的传播行为为
    PROPAGATION_REQUIRES_NEW,那么当执行到 ServiceB.methodB()
    的时候,ServiceA.methodA() 所在的事务就会挂起,而 ServiceB.methodB() 会起一个新的事务,等待
    ServiceB.methodB() 的事务完成以后,A的事务才会继续执行。PROPAGATION_REQUIRED与
    PROPAGATION_REQUIRES_NEW 的事务区别在于事务的回滚程度。因为 ServiceB.methodB
    是新起一个事务,那么就是存在两个不同的事务。如果 ServiceB.methodB 已经提交,那么 ServiceA.methodA
    失败回滚,ServiceB.methodB 是不会回滚的。如果 ServiceB.methodB 失败回滚,如果它抛出的异常被
    ServiceA.methodA 捕获,ServiceA.methodA 事务仍然可能会提交。
  3. 假如 ServiceA.methodA
    的事务传播行为是 PROPAGATION_REQUIRED,而 ServiceB.methodB 的事务传播行为是
    PROPAGATION_NOT_SUPPORTED,那么当执行到 ServiceB.methodB 时,ServiceA.methodA
    的事务挂起,而ServiceB.methodB 以非事务的状态运行完之后,再继续 ServiceA.methodA 的事务。
  4. 假如
    ServiceA.methodA 的事务传播行为是 PROPAGATION_REQUIRED, 而 ServiceB.methodB
    的事务级别是 PROPAGATION_NEVER ,那么 ServiceB.methodB 执行时就会抛出异常。

作者:uzip柚子皮 链接:https://www.jianshu.com/p/760399781b78 来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

REQUIRED

@Transactional(propagation = Propagation.REQUIRED)

REQUIRES_NEW

@Transactional(propagation = Propagation.REQUIRES_NEW)

事务隔离级别

  • 脏读:一个事务读到另一个事务未提交的更新数据。(打算提交但是数据回滚了,读取了提交的数据)
  • 幻读:是指当事务不是独立执行时发生的一种现象。(读取了插入前的数据)
  • 不可重复读:在一个事务里面的操作中发现了未被操作的数据。(读取了修改前的数据)
隔离界别是否会发生脏读不可重复读幻读
ISOLATION_DEFAULT使用数据库本身的隔离级别
ISOLATION_READ_UNCOMITTED读未提交
ISOLATION_READ_COMMITED读已提交
ISOLATION_REPEATABLE_READ可重复读
ISOLATION_SERLALIZABLE串行化

xml方式配置

<!--1. 配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSourse"></property>
    </bean>
    <!--2. 配置事务属性-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes >
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
    <!--3.配置事务切入点-->
    <aop:config>
        <aop:pointcut id="" expression="execution(* * )"/>
    </aop:config>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值