zoukankan      html  css  js  c++  java
  • Spring

    一、Spring AOP 简介

    如果说 IoC 是 Spring 的核心,那么面向切面编程就是 Spring 最为重要的功能之一了,在数据库事务中切面编程被广泛使用。

    AOP 即 Aspect Oriented Program 面向切面编程

    首先,在面向切面编程的思想里面,把功能分为核心业务功能,和周边功能。

    ● 所谓的核心业务,比如登陆,增加数据,删除数据都叫核心业务
    ● 所谓的周边功能,比如性能统计,日志,事务管理等等

    周边功能在 Spring 的面向切面编程AOP思想里,即被定义为切面。

    在面向切面编程AOP的思想里面,核心业务功能和切面功能分别独立进行开发,然后把切面功能和核心业务功能 "编织" 在一起,这就叫AOP。

    AOP 的目的

    AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

    二、aop概念介绍(面向切面的编程)

    AOP行话

    ● 连接点(JointPoint):目标对象中,所有可以增强的方法
    ● 切入点(Pointcut):目标对象中,已经增强的方法
    ● 通知(Advice):增强的代码
    ● 目标对象(Target Object):被代理的对象
    ● 织入(Weaving):将通知应用到切入点的过程
    ● 代理(AOP Proxy):将通知织入到目标对象之后,形成的对象
    ● 切面(Aspect):切入点+通知

    三、AOP配置使用

    AOP所需的包:

    aop核心包:spring-aspects 和 spring-aop
    aop联盟包:springsource.org.aopalliance
    aop织入包:springsource.org.aspectj.weaver

    1. XML配置AOP:

    ① 准备目标对象(被代理对象,被通知的对象,被增强的类对象)

    package com.sikiedu.service;
    
    public interface UserService {
    
        //
        void save();
    
        //
        void delete();
    
        //
        void update();
    
        //
        void find();
    
    }
    UserService.java
    package com.sikiedu.service;
    
    public class UserServiceImpl implements UserService {
    
        @Override
        public void save() {
            System.out.println("save...run");
        }
    
        @Override
        public void delete() {
            System.out.println("delete...run");
        }
    
        @Override
        public void update() {
            System.out.println("update...run");
        }
    
        @Override
        public void find() {
            System.out.println("find...run");
        }
    
    }
    UserServiceImpl.java

    ② 准备通知(被增强方法的代码,想要实现功能的方法代码)

    package com.sikiedu.aop;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    
    /**
     * 自定义通知类
     * 
     * @author ASUS
     *
     */
    public class MyAdvice {
    
        // before 前置通知 - 在目标方法前调用
        public void befor() {
            System.out.println("前置通知");
        }
    
        // afterReturning 成功通知(后置通知)- 在目标方法执行后,并且执行成功,如果方法出现异常则不调用
        public void afterReturning() {
            System.out.println("成功通知");
        }
    
        // around 环绕通知 - 需要我们手动调用目标方法,并且可以设置通知
        public Object around(ProceedingJoinPoint pjp) throws Throwable {
            System.out.println("环绕通知 - 前");
            Object proceed = pjp.proceed();
            System.out.println("环绕通知 - 后");
            return proceed;
        }
    
        // afterThrowing 异常通知(后置通知)- 在目标方法执行出现异常的时候才会调用
        public void afterThrowing() {
            System.out.println("异常通知");
        }
    
        // after 最终通知(后置通知)- 在目标方法后调用,无论是否出现异常都会执行,finally
        public void after() {
            System.out.println("最终通知");
        }
    }
    MyAdvice.java

    ③ 配置 applicationContext.xml

    1 - 导入aop(约束)命名空间
    2 - 配置目标对象
    3 - 配置通知对象
    4 - 配置将通知织入目标对象
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
    
        <!-- 配置目标对象 -->
        <bean name="userService" class="com.sikiedu.service.UserServiceImpl">
        </bean>
    
        <!-- 配置通知对象 -->
        <bean name="myAdvice" class="com.sikiedu.aop.MyAdvice"></bean>
        
        <!-- 织入 - 将通知织入到目标对象中 -->
        <aop:config>
            <!-- 切入点 
                1、expression:切入点表达式,可以配置要增强的方法
                    ① public void com.sikiedu.service.UserServiceImpl.save()
                      - 增强单一方法save,必须为无参、public类型、返回值为void
                    ② * com.sikiedu.service.*.*(..)
                      - 增强service包下所有类的所有方法
                2、id:唯一标识        -->
            <aop:pointcut expression="execution(* com.sikiedu.service.*.*(..))" id="servicePc" />
            
            <!-- 切面:通知+切入点-->
            <aop:aspect ref="myAdvice">
            <!-- 通知类型 -->
                <!-- 前置通知 -->
                <aop:before method="befor" pointcut-ref="servicePc"/>
                <!-- 成功通知 -->
                <aop:after-returning method="afterReturning" pointcut-ref="servicePc"/>
                <!-- 环绕通知 -->
                <aop:around method="around" pointcut-ref="servicePc"/>
                <!-- 异常通知 -->
                <aop:after-throwing method="afterThrowing" pointcut-ref="servicePc"/>
                <!-- 最终通知 -->
                <aop:after method="after" pointcut-ref="servicePc"/>
            </aop:aspect>
            
        </aop:config>
    
    </beans>
    applicationContext.xml

    ④ 测试

    package com.sikiedu.test;
    
    import javax.annotation.Resource;
    
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    import com.sikiedu.service.UserService;
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:applicationContext.xml")
    public class AopTest {
    
        @Resource(name = "userService")
        UserService us;
    
        @Test
        public void test1() {
    
            us.delete();
    
        }
    }
    AopTest

    运行结果:

     - 非异常:,异常:

    2. 注解配置AOP

    ① 配置applicationContext.xml - 配置目标对象、通知对象、开启使用注解完成织入

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
    
        <!-- 配置目标对象 -->
        <bean name="userService" class="com.sikiedu.service.UserServiceImpl">
        </bean>
    
        <!-- 配置通知对象 -->
        <bean name="myAdvice" class="com.sikiedu.aop.MyAdvice"></bean>
    
        <!-- 开启使用注解完成织入 -->
        <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    
    </beans>
    applicationContext.xml

    ② 书写通知类

    @Aspect:指定切面类
    @JoinPoint:作为函数的参数传入切面方法,可以得到目标方法的相关信息
    声明一个切入点:@Pointcut(
    "execution(返回值 全类名.方法名(参数))")
    package com.sikiedu.aop;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.AfterReturning;
    import org.aspectj.lang.annotation.AfterThrowing;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    
    /**
     * 自定义通知类
     * 
     * @author ASUS
     *
     */
    @Aspect // 通知类 - 使用Aspect表示是一个通知类
    public class MyAdvice {
    
        // 声明一个切入点 - servicePc为切入点名称
        @Pointcut("execution(* com.sikiedu.service.*.*(..))")
        public void servicePc() {
        };
    
        @Before("MyAdvice.servicePc()") // before 前置通知 - 在目标方法前调用
        public void befor() {
            System.out.println("前置通知");
        }
    
        @AfterReturning("MyAdvice.servicePc()") // afterReturning 成功通知(后置通知)- 在目标方法执行后,并且执行成功,如果方法出现异常则不调用
        public void afterReturning() {
            System.out.println("成功通知");
        }
    
        @Around("MyAdvice.servicePc()") // around 环绕通知 - 需要我们手动调用目标方法,并且可以设置通知。
        public void around(ProceedingJoinPoint pjp) throws Throwable {
            System.out.println("环绕通知 - 前");
            pjp.proceed();
            System.out.println("环绕通知 - 后");
        }
    
        @AfterThrowing("MyAdvice.servicePc()") // afterThrowing 异常通知(后置通知)- 在目标方法执行出现异常的时候才会调用。
        public void afterThrowing() {
            System.out.println("异常通知");
        }
    
        @After("MyAdvice.servicePc()") // after 最终通知(后置通知)- 在目标方法后调用,无论是否出现异常都会执行。
        public void after() {
            System.out.println("最终通知");
        }
    }
    Test

    ● 注意环绕通知:需要我们手动调用目标方法

     proceed()表示对拦截的方法进行放行,若注释proceed()则不会执行被AOP匹配的方法。

    public void around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕通知 - 前")
        pjp.proceed();
        System.out.println("环绕通知 - 后")
    }

    ③ 测试:

    package com.sikiedu.test;
    
    import javax.annotation.Resource;
    
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    import com.sikiedu.service.UserService;
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:applicationContext2.xml")
    public class AopTest {
    
        @Resource(name = "userService")
        UserService us;
    
        @Test
        public void test1() {
    
            us.delete();
    
        }
    }
    Test

    运行结果:

     - 非异常:,异常:

    通知类型介绍

    ① 前置通知(前置通知):目标方法运行之前调用
      @Before:在目标方法被调用之前做增强处理,@Before只需要指定切入点表达式即可

    ② 成功通知(后置通知):目标方法运行之后调用(如果出现异常不调用)  
    @AfterReturning:在目标方法正常完成后做增强,@AfterReturning除了指定切入点表达式后,还可以指定一个返回值形参名returning,代表目标方法的返回值

    ④ 异常拦截通知(后置通知):如果出现异常,就会调用  
    @AfterThrowing:主要用来处理程序中未处理的异常,@AfterThrowing除了指定切入点表达式后,还可以指定一个throwing的返回值形参名,可以通过该形参名来访问目标方法中所抛出的异常对象

    ④ 最终通知(后置通知):目标方法运行之后调用(无论是否出现异常都会调用)  
    @After:在目标方法完成之后做增强,无论目标方法是否成功完成。@After可以指定一个切入点表达式

    ③ 环绕通知:目标方法之前和之后都调用 - 需要我们手动调用目标方法,并且可以设置通知  
    @Around:环绕通知,在目标方法完成前后做增强处理,环绕通知是最重要的通知类型,像事务,日志等都是环绕通知,注意编程中核心是一个ProceedingJoinPoint

    四、Spring - AOP事务

    Spring封装了事务管理的代码(打开,提交,回滚事务)

    事务操作对象,因为在不同平台,操作事务的代码各不相同。

    Spring提供了一个接口:PlatformTransactionManager接口,在不同平台,实现不同的接口即可。

    为不同平台提供对应的事务管理器的实现:

    - JDBC & Mybatis:DataSourceTransactionManager
    - Hibernate:HibernateTransactionManager

    在Spring事务管理,最为核心的对象就是TransactionManager对象。

    事物的四大特性 - ACID

    ● 原子性(Atomicity):事务包含的所有操作,要么全部成功,要么全部失败回滚,成功全部应用到数据库,失败不能对数据库有任何影响;
    ● 一致性(Consistency):事务在执行前和执行后必须一致;例如A和B一共有100块钱,无论A、B之间如何转账,他们的钱始终相加都是100;
    ● 隔离性(Isolation):多用户并发访问同一张表时,数据库为每一个用户开启新的事务,该事务不能被其他事务所影响,相互有隔离;
    ● 持久性(Durability):一个事务一旦提交,则对数据库中数据的改变是永久的,即便系统故障也不会丢失;

     ⁽⁽ଘ( - 事务回顾 - )ଓ⁾⁾*

    1. Spring事务分类

    Spring中事务可以分为编程式事务控制和声明式事务控制。

    ● 编程式事务控制

    自己手动控制事务,可以对指定的方法,指定的方法的某几行添加事务控制。
    比较灵活,但开发较为繁琐,每次都要开启、提交、回滚;
    - Jdbc:Conn.setAutoCommit(
    false);// 设置手动控制事务 - Hibernate:Session.beginTransaction();// 开启一个事务
    注意编程式事务控制开启事物后一定要手动释放(提交或回滚),否则长期占用内存,有可能报事务异常。

    ● 声明式事务控制 - 核心实现基于Aop

    Spring声明式事务管理,只能给整个方法应用事务,不可以对方法中的某几行应用事务(因为aop拦截的是方法)。
    Spring提供了对事务控制的实现。用户如果想要用Spring的声明式事务管理,只需要在配置文件中配置即可;不想要使用时直接移除配置。这个实现了对事务控制的最大程度的解耦。
    Spring声明式事务管理器:
    - Jdbc事务:DataSourceTransactionManager
    - Hibernate事务:HibernateTransactionManager

    2. Spring管理事务的属性介绍

    ● 事务的隔离级别

    隔离级别 含义

    数据库默认

    ISOLATION_DEFAULT

    使用底层数据库的默认隔离级别,大部分数据库,默认隔离级别都是READ_COMMITED

    读以提交

    ISOLATION_READ_UNCOMMITTED

    最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读

    读未提交

    ISOLATION_READ_COMMITTED

    允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生

    可重复读

    ISOLATION_REPEATABLE_READ

    对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生

    串行化

    ISOLATION_SERIALIZABLE

    最高的隔离级别,完全服从ACID的隔离级别,确保阻止脏读、不可重复读以及幻读,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的

    ● 是否只读

    true:不可改变数据库中的数据,查询操作推荐,
    false:可以改变数据库数据;

    ● 事务的传播行为 - 多个事务方法调用时,如何定义方法间事务的传播

    PROPAGATION_REQUIRED如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置② PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务;
    ③ PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行;
    ④ PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起(暂停);
    ⑤ PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常;
    ⑥ PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常;
    ⑦ PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与REQUIRED类似的操作。

    3. Spring事务使用

    ① 使用事务需要额外带入 tx 包和 tx 约束

    ② 配置Jdbc事务的核心管理器,它封装了所有事务,依赖于连接池

    <!-- JDBC - 事务核心管理器 -->
    <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
       <!-- 依赖注入数据源 -->
       <property name="dataSource" ref="dataSource" />
    </bean>

    ● XML配置 - 配置通知、将通知织入目标对象

    配置事务通知 - tx:advice

    <!-- 配置通知 -->
    <tx:advice id="advice" transaction-manager="transactionManager">
        <tx:attributes>
            <!-- 以方法为单位 指定什么方法使用什么属性
                isolation:事务隔离级别
                read-only:是否只读
                propagation:传播行为
             -->
            <tx:method name="save*" isolation="REPEATABLE_READ" read-only="false" propagation="REQUIRED" />
            <tx:method name="delete*" isolation="REPEATABLE_READ" read-only="false" propagation="REQUIRED" />
            <tx:method name="update*" isolation="REPEATABLE_READ" read-only="false" propagation="REQUIRED" />
            <tx:method name="select*" isolation="REPEATABLE_READ" read-only="true" propagation="REQUIRED" />
        </tx:attributes>
    </tx:advice>

    配置aop将通知织入目标

    <!-- 配置织入:将上面的通知织入到目标对象 -->
    <aop:config>
        <!-- 配置切点表达式 -->
        <aop:pointcut expression="execution(* com.sikiedu.service.*ServiceImpl.*(..))" id="pointcut" />
        <!-- 配置切面(通知+切点) -->
        <aop:advisor advice-ref="advice" pointcut-ref="pointcut" />
    </aop:config>

    ● 注解配置

    在spring配置文件中开启注解事务

    <!-- 开启注解事务 -->
    <tx:annotation-driven />

    在需要管理的方法或者类中使用@Transactiona()声明配置事务管理

    //所有方法开启事务
    @Transactional(isolation = Isolation.REPEATABLE_READ, readOnly = false, propagation = Propagation.REQUIRED)
    public class UserServiceImpl implements UserService {
      // Code...  
    }
    // 当前方法使用注解管理spring中的aop事务
    @Transactional(isolation = Isolation.REPEATABLE_READ, readOnly = false, propagation = Propagation.REQUIRED)
    public void updateBalance() {
    
        double money = 1000;
        // 转出钱的方法
        userDao.subBalance(2, money);
        // int i = 1 / 0;
        // 转入钱的方法
        userDao.addBalance(1, money);
    }
  • 相关阅读:
    大咖们如何评判优秀架构师?
    腾讯会议大规模任务调度系统架构设计
    计算压力倍增,携程度假起价引擎架构演变
    快手春节红包背后,高并发存储架构设计
    日均20亿流量:携程机票查询系统的架构升级
    我是如何一步步的在并行编程中将lock锁次数降到最低实现无锁编程
    OGRE
    CMake
    深入理解C++11【5】
    深入理解C++11【4】
  • 原文地址:https://www.cnblogs.com/Dm920/p/12076090.html
Copyright © 2011-2022 走看看