Spring AOP及事务控制小结

AOP和OOP区别:

AOP和OOP定义:
AOP:(Aspect Oriented Programming)面向切面编程
OOP:(Object Oriented Programming )面向对象编程
面向切面编程:基于OOP基础之上新的编程思想,指在程序运行期间,将某段代码动态的切入到指定方法指定位置进行运行的编程方式

JAVA动态代理基本语法

动态代理的本质是实现了与被代理类同一组接口的类,所以要保证代理类和被代理类实现了同一组接口
举例:

//接口
public interface Calculatorable {
    Integer add(int a,int b);
    Integer sub(int a,int b);
}

//被代理类
public class Calculator implements Calculatorable {
    public Integer add(int a, int b) {
        System.out.println("加法...");
        return a+b;
    }

    public Integer sub(int a, int b) {
        System.out.println("减法...");
        return a-b;
    }
}

//代理类
public class CalculatorProxy {
    public static Calculatorable getProxy(final Calculatorable cal){
        InvocationHandler ih = new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object o = null;
                try {
                    System.out.println("方法执行前...");
                   o= method.invoke(cal, args);
                    System.out.println("方法执行后");
                } catch (Exception e) {
                    System.out.println("方法有异常");

                }  finally {
                    System.out.println("finally语句块...");
                }
                return o;
            }
        };
        return (Calculatorable) Proxy.newProxyInstance(cal.getClass().getClassLoader(), cal.getClass().getInterfaces(), ih);
    }
}
//测试
    @Test
    public void test01(){
        Calculatorable p = CalculatorProxy.getProxy(new Calculator());
        p.add(4, 5);

    }
   

打印结果:

方法执行前...
加法...
方法执行后
finally语句块...

动态代理的缺点:代码量多,且必须要实现一组接口

Spring简化面向切面编程

1.AOP基本概念

1)专业术语:

连接点:每一个方法的每一个位置
横切关注点:每一个方法的同一位置下的连接点
切入点:需要真正执行切入的方法的位置
切入点表达式:通过表达式筛选切入点
通知方法:每一个横切关注点下的方法
切面类:横切关注点+通知方法

2.需要导入的包

Spring AOP需要导入的包:
spring核心包:
commons-logging-1.1.3.jar
spring-aop-4.0.0.RELEASE.jar
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE.jar
spring-aspects-4.0.0.RELEASE.jar:(Spring支持面向切面编程的包)

加强版的面向切面编程,即不用实现接口也能实现动态代理(使用cglib)
com.springsource.net.sf.cglib-2.2.0.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
maven工程的pom配置(加强版):

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

3.注解配置AOP过程

spring实现AOP其实就是依靠切面类,实现一个切面类,切面类由通知方法和切面表达式构成

1.spring中共有五种通知:

@Before:前置通知,在方法执行之前执行
@After:后置通知,在方法执行之后执行
@AfterRunning:返回通知,在方法返回结果之后执行
@AfterThrowing:异常通知,在方法抛出异常之后执行
@Around:环绕通知,围绕着方法执行

2.五种通知的执行顺序

方法正常运行:
环绕前置–前置通知–目标方法–环绕返回–环绕后置–后置通知–返回通知
方法异常运行:
环绕前置–前置通知–目标方法–环绕异常–环绕后置–后置通知–异常通知
执行顺序:
try{
环绕前置
[普通前置]
环绕执行:目标方法执行
环绕返回
}catch(){
环绕出现异常
}finally{
环绕后置
}
[普通后置]
[普通方法返回/方法异常]

总之,就是环绕通知要优先比其他的通知先执行,而且执行顺序是正常的,即返回通知在后置通知之前执行

3.多切面类下通知的执行顺序

与Filter一样,先进来的切面类后出去,且各个切面类的环绕通知相互独立运行,互不影响。即只会优先本类的其他通知,但并不会影响其他切面类的通知。

4.切面表达式写法

固定格式: execution(访问权限符 返回值类型 方法全类名(参数表))

通配符:

  • *:

     	1)匹配一个或者多个字符:execution(public int com.atguigu.impl.MyMath*r.*(int, int)
     	----MyMath*r表示类名为以mymath开头,以r结尾
     	2)匹配任意一个参数:第一个是int类型,第二个参数任意类型;(匹配两个参数)
     		execution(public int com.atguigu.impl.MyMath*.*(int, *))
     	3)只能匹配一层路径
     	4)访问权限位置不能写*;权限位置不写就行就默认表示public
    
  • …:

     	1)匹配任意多个参数,任意类型参数
     	2)匹配任意多层路径:
     		execution(public int com.atguigu..MyMath*.*(..));
    
  • &&”、“||”、“!

&&:我们要切入的位置满足这两个表达式
MyMathCalculator.add(int,double)
execution(public int com.atguigu…MyMath*.(…))&&execution( .(int,int))
||:满足任意一个表达式即可
execution(public int com.atguigu…MyMath*.(…))&&execution( .(int,int))
!:只要不是这个位置都切入
!execution(public int com.atguigu…MyMath*.*(…))

注意:不能以…开头,如果要写成最模糊匹配,则可写成execution(* *.*(…)):表示任意包下任意类,如果以*开头叫表示多层任意路径

5.示例

目标类:

@Service
public class MyCalculator /*implements MyCalculatorable  可写可不写 */ {

    public Integer add(int a, int b) {
        System.out.println("------加法运行了------");
        return a+b;
    }
    public Integer sub(int a, int b) {
        System.out.println("------减法运行了------");
        return a-b;
    }
}

切面类A:

@Order(1)  //如果有多切面类,可以指定顺序,值越小顺序越优先
@Aspect  //表明为切面类
@Component
public class AopTestClass {
    //切面表达式可重用:切面表达式同一写在一个无返回值的方法上,用@Pointcut标注
    @Pointcut("execution(Integer com.proxytest.MyCalculator.*(..))")
    public void pointCut(){};


    @Before("pointCut()")
    public void logBefore(JoinPoint jp){
        System.out.println(jp.getSignature().getName()+"方法"+"前置通知开始执行");
    }

    @After("pointCut()")
    public void logAfter(JoinPoint jp){
        System.out.println(jp.getSignature().getName()+"方法"+"后置通知开始执行");
    }

    //在通知方法中写的参数spring要能识别,所以不能写spring不能识别的参数
    //JoinPoint:封装了当前目标方法的详细信息
    //throwing:告诉Spring哪个参数是用来接收异常
    //returning:告诉Spring这个result用来接收返回值
    @AfterThrowing(value = "pointCut()",throwing = "ex")
    public void logAfterThrowing(JoinPoint jp,Exception ex){
        System.out.println(jp.getSignature().getName()+"方法"+"异常通知开始执行");
    }

    @AfterReturning(value = "pointCut()",returning = "res")
    public void logAfterReturning(JoinPoint jp,Integer res){
        System.out.println(jp.getSignature().getName()+"方法"+"返回通知开始执行"+"返回值为"+res);
    }

    //如果在环绕通知中就把异常捕获而不抛出,外界是感受不到异常的,所以一般需要抛出,同理,如果方法有返回值,也需要return
    //注意此处的返回值不能写在finally语句中,否则外界还是会将感受不到异常
    @Around("pointCut()")
    public Object logAround(ProceedingJoinPoint jp)  {
       Object res = null;
        try {
            System.out.println(jp.getSignature().getName()+"方法"+"环绕通知的前置通知开始执行.....");
            res = (Integer) jp.proceed(jp.getArgs());
            System.out.println(jp.getSignature().getName()+"方法"+"环绕通知的返回通知开始执行.....");
            return res;
        } catch (Throwable ex) {
            System.out.println(jp.getSignature().getName()+"方法"+"环绕通知的异常通知开始执行.....");
            throw new RuntimeException(ex);

        }finally {
            System.out.println(jp.getSignature().getName()+"方法"+"环绕通知的后置通知开始执行.....");

        }
    }
}

切面类B:

@Order(2)
@Aspect
@Component
public class AopTest02Class {
    @Pointcut("execution(Integer com.proxytest.MyCalculator.*(..))")
    public void pointCut() {}
    @Before("pointCut()")
    public void logBefore(JoinPoint jp) {
        System.out.println(jp.getSignature().getName() + "方法" + "AopTest02Class前置通知开始执行");
    }

    @After("pointCut()")
    public void logAfter(JoinPoint jp) {
        System.out.println(jp.getSignature().getName() + "方法" + "AopTest02Class后置通知开始执行");
    }

    @AfterThrowing(value = "pointCut()", throwing = "ex")
    public void logAfterThrowing(JoinPoint jp, Exception ex) {
        System.out.println(jp.getSignature().getName() + "方法" + "AopTest02Class异常通知开始执行");
    }

    @AfterReturning(value = "pointCut()", returning = "res")
    public void logAfterReturning(JoinPoint jp, Integer res) {
        System.out.println(jp.getSignature().getName() + "方法" + "AopTest02Class返回通知开始执行" + "返回值为" + res);
    }
}

方法正常运行控制台打印结果:

add方法环绕通知的前置通知开始执行.....
add方法前置通知开始执行
add方法AopTest02Class前置通知开始执行
------加法运行了------
add方法AopTest02Class后置通知开始执行
add方法AopTest02Class返回通知开始执行返回值为11
add方法环绕通知的返回通知开始执行.....
add方法环绕通知的后置通知开始执行.....
add方法后置通知开始执行
add方法返回通知开始执行返回值为11

方法异常运行控制台打印结果:

add方法环绕通知的前置通知开始执行.....
add方法前置通知开始执行
add方法AopTest02Class前置通知开始执行
------加法运行了------
add方法AopTest02Class后置通知开始执行
add方法AopTest02Class异常通知开始执行
add方法环绕通知的异常通知开始执行.....
add方法环绕通知的后置通知开始执行.....
add方法后置通知开始执行
add方法异常通知开始执行

测试类:

  @Test
    public void test02(){
        ApplicationContext ac= new ClassPathXmlApplicationContext("spring.xml");
        MyCalculator bean = (MyCalculator)ac.getBean(MyCalculator.class);
        Integer i = bean.add(5,6);

    }

开始AOP注解配置:

<context:component-scan base-package="com.*"/>
<aop:aspectj-autoproxy/>

注意:
从ioc容器中拿到目标对象时:
1)如果实现了接口
如果用类类型获取实例,一定用他的接口类型,不要用它的实现类,也可以通过真实对象的id来获取
MyCalculatorablebean = ioc.getBean(MyCalculatorable.class);
MyCalculatorbean = (MyCalculator) ioc.getBean(“myCalculator”);

2)如果直接写的目标类,则根据目标类类型和id名称获取对象都可
MyCalculatorbean = (MyCalculator) ioc.getBean(MyCalculator.class);
MyCalculatorbean = (MyCalculator) ioc.getBean(“myCalculator”);

4.AOPxml配置

    <!--  1.配置切面类和目标类的Bean-->
    <bean id="myCalculator" class="com.proxytest.MyCalculator"></bean>
    <bean id="aopTestClass" class="com.aoptest.AopTestClass"></bean>
    <bean id="aopTest02Class" class="com.aoptest.AopTest02Class"></bean>


    <!-- 2.引入AOP名称空间,配置切入点表达式 -->
    <aop:config>
        <aop:pointcut expression="execution(Integer com.proxytest.MyCalculator.*(..))" id="globalPoint"/>


        <!-- 普通前置  ===== 目标方法  =====(环绕执行后置/返回)====s普通后置====普通返回    -->
        <!-- 指定切面:@Aspect -->
        <aop:aspect ref="aopTestClass" order="1">
            <!-- 配置哪个方法是前置通知;method指定方法名,logStart@Before("切入点表达式")-->

            <!-- 如果切入点定义在当前切面类中,就只能当前切面能用 -->
            <aop:pointcut expression="execution(Integer com.proxytest.MyCalculator.*(..))" id="mypoint"/>
            <aop:around method="logAround" pointcut-ref="mypoint"/>
            <aop:before method="logBefore" pointcut="execution(Integer com.proxytest.MyCalculator.*(..))"/>
            <aop:after-returning method="logAfterReturning" pointcut-ref="mypoint" returning="res"/>
            <aop:after-throwing method="logAfterThrowing" pointcut-ref="mypoint" throwing="ex"/>
            <aop:after method="logAfter" pointcut-ref="mypoint"/>
        </aop:aspect>


        <aop:aspect ref="aopTest02Class" order="3">
            <aop:before method="logBefore" pointcut-ref="globalPoint"/>
            <aop:after-returning method="logAfterReturning" pointcut-ref="globalPoint" returning="res"/>
            <aop:after-throwing method="logAfterThrowing" pointcut-ref="globalPoint" throwing="ex"/>
            <aop:after method="logAfter" pointcut-ref="globalPoint"/>
        </aop:aspect>
        <!-- 在切面类中使用五个通知注解来配置切面中的这些通知方法都何时何地运行 -->
    </aop:config>

 tips:注意,在xml中配置,环绕前置执行的位置不一定会比普通前置要前面,这取决于aop:around和aop:before标签的位置顺序,但是环绕后置一定比普通后置先

Spring事物控制

编程式事务
TransactionFilter{
try{
//获取连接
//设置非自动 提交
chain.doFilter();
//提交
}catch(Exception e){
//回滚
}finllay{
//关闭连接释放资源
}
}
声明式事务
    事务管理代码的固定模式作为横切关注点,可以通过AOP方法模块化,进而借助Spring AOP框架实现声明式事务管理,这个切面类Spring已经实现,这个切面类称为事物管理器

1.事物管理器注解配置

因为事物控制依赖于切面编程,所以之前AOP导入的包,事物控制也需要
maven的POM配置:

 <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.32</version>
        </dependency>
1)事物管理器注解配置内容
<context:component-scan base-package="com.*"></context:component-scan>
  <!-- 0、引入外部配置文件 -->
  <context:property-placeholder location="classpath:pro.properties" />
<!--  配置数据源-->
   <bean id="pooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
       <property name="user" value="${jdbc.user}"></property>
       <property name="password" value="${jdbc.password}"></property>
       <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
       <property name="driverClass" value="${jdbc.driverClass}"></property>
   </bean>

   <!-- 配置JdbcTemplate -->
   <bean class="org.springframework.jdbc.core.JdbcTemplate">
       <property name="dataSource" ref="pooledDataSource"></property>
   </bean>

   <!-- 事务控制 -->

   <bean id="tm" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
       <!-- 控制住数据源 -->
       <property name="dataSource" ref="pooledDataSource"></property>
   </bean>
   <!--2:开启基于注解的事务控制模式;依赖tx名称空间  -->
   <tx:annotation-driven transaction-manager="tm"/>
   <!--3:给事务方法加注解@Transactional,在service类中的某个方法上加入-->  
2)@Transactional属性
  • isolation:事物的隔离级别
    事物出现的几个问题:脏读、幻读、不可重复读
    分别对应以下几个隔离级别:
    Isolation.READ_UNCOMMITTED:读出脏数据
    Isolation.READ_COMMITTED:解决脏读
    Isolation.REPEATABLE_READ:解决脏读、幻读
    Isolation.SERIALIZABLE:都解决,但效率低,不常用

  • 异常回滚机制
    运行时异常(非检查异常):可以不用处理;默认都回滚;
    编译时异常(检查异常):要么try-catch,要么在方法上声明throws,默认不回滚;
    noRollbackFor:哪些异常事务可以不回滚,可以让原来默认回滚的异常给他不回滚
    noRollbackFor={ArithmeticException.class,NullPointerException.class}
    noRollbackForClassName:String全类名:
    rollbackFor:哪些异常事务需要回滚,原本不回滚(原本编译时异常是不回滚的)的异常指定让其回滚
    rollbackForClassName:全类名

  • readOnly-boolean:设置事务为只读事务:可以进行事务优化;
         readOnly=true:加快查询速度;不用管事务那一堆操作了

  • timeout-int(秒为单位):超时:事务超出指定执行时长后自动终止并回滚

  • propagation-Propagation:事务的传播行为如下表,常用为required和required_new,默认事物都是required
    在这里插入图片描述
    Tips:
         1.如果任何处崩,已经执行的REQUIRES_NEW都会成功;
         2.如果子事物是REQUIRED,事务的属性都是继承于大事务的。即在子事物上设置的如超时时间等无效,只有在超事物上设置对子事物才有影响;如果子事物是REQUIRES_NEW则可以自定义事物属性

2.事物管理器XML配置

<context:component-scan base-package="com.*"></context:component-scan>
  <!-- 0、引入外部配置文件 -->
  <context:property-placeholder location="classpath:pro.properties" />
<!--  配置数据源-->
   <bean id="pooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
       <property name="user" value="${jdbc.user}"></property>
       <property name="password" value="${jdbc.password}"></property>
       <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
       <property name="driverClass" value="${jdbc.driverClass}"></property>
   </bean>

   <!-- 配置JdbcTemplate -->
   <bean class="org.springframework.jdbc.core.JdbcTemplate">
       <property name="dataSource" ref="pooledDataSource"></property>
   </bean>

   <!-- 事务控制 -->

   <bean id="tm" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
       <!-- 控制住数据源 -->
       <property name="dataSource" ref="pooledDataSource"></property>
   </bean>
 <!--
基于xml配置的事务;依赖tx名称空间和aop名称空间
  1)、Spring中提供事务管理器(事务切面),配置这个事务管理器
  2)、配置出事务方法的属性;
  3)、告诉Spring哪些方法是事务方法;
     (事务切面按照我们的切入点表达式去切入事务方法)
 -->

<aop:config>
  <aop:pointcut expression="execution(* com.atguigu.ser*.*.*(..))" id="txPoint"/>
  <!-- 事务建议;事务增强     advice-ref:指向事务管理器的配置 -->
  <aop:advisor advice-ref="myAdvice" pointcut-ref="txPoint"/>
</aop:config>

<!--  配置事务管理器:事务建议又称事务增强;配置的是事务属性;
transaction-manager="transactionManager":指定是配置哪个事务管理器;-->
<tx:advice id="myAdvice" transaction-manager="transactionManager">
     <!--事务属性  -->
     <tx:attributes>
        <!-- 指明哪些方法是事务方法;切入点表达式只是说,事务管理器要切入这些方法,哪些方法加事务使用tx:method指定的 -->
        <tx:method name="*"/> <!--可以把所有的切入方法都指定为事物-->
        <tx:method name="checkout" propagation="REQUIRED" timeout="-1"/><!--还可以另外配置某些具体的方法-->
        <tx:method name="get*" read-only="true"/>
     </tx:attributes>
</tx:advice>

对于重要的事物/通知方法用XML配置,不重要的用注解配置

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章