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);
}
原文地址:https://www.cnblogs.com/Dm920/p/12076090.html