一.AOP简介
1.概念 : AOP为Aspect Oriented Programming的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统 一维护的一种技术。
2.作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强
3.优势:减少重复代码,提高开发效率,并且便于维护
4.底层:实际上,AOP的底层是通过Spring提供的的动态代理技术实现的。在运行期间,Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入,在去调用目标对象的方法,从而完成功能的增强。
二.AOP核心概念
1.通知(Advice): 指拦截到Joinpoint之后要做的事, 即对切入点增强的内容
2.切入点(Pointcut): 需要增加该功能的方法, 必须定义成切入点
3.连接点(JoinPoint): 可以增加该功能的方法, 即程序中任意位置的方法, 连接点包含着切入点
4.切面(Aspect): 连接通知和切入点之间的桥梁, 描述两者之间的关系
5.目标对象(Target):代理的目标对象
6.代理(Proxy):一个类被AOP织入增强后,就产生一个结果代理类
7.织入(Weaving):是指把增强应用到目标对象来创建新的代 理对象的过程。spring采用动态代理织入,而AspectJ采用编 译期织入和类装载期织入
8.引入(Introduction):用来给一个类型声明额外的方法或属性(也被称为连接类型声明(inter-type declaration))。Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。例如,你可以使用引入来使一个bean实现IsModified
接口,以便简化缓存机制
三.AOP工作流程
1.Spring容器启动
2.读取所有切面配置中的切入点
3.初始化bean, 判定bean对应的类中的方法是否匹配到任意切入点 匹配失败, 创建对象 匹配成功, 创建原始对象(目标对象)的代理对象 System.out.println(bookDao.getClass()) 出来的结果是代理对象class com.sun.proxy.$Proxy20
4.获取bean执行方法 获取bean, 调用方法并执行, 完成操作 获取的bean是代理对象时, 根据代理对象的运行模式运行原始方法与增强的内容, 完成操作
四.基于XML的APO开发
1、切点表达式的写法
语法 execution([修饰符] 返回值类型 包名.类名.方法名(参数))
(1)访问修饰符可以省略 (2)返回值类型、包名、类名、方法名可以使用*号代表任意 (3)包名与类名之间一个点,代表当前包下的类,两个点,表示当前包下的类 (4)参数列表可以使用两个点…表示任意个数,任意类型
例:
execution(public void com.itcast.aop.Tartget.save()) execution(void com.itcast.aop.Tartget.(…))–任意参数的所有方法 execution( com.itcast.aop..(…)) execution(* com.itcast.aop….(…)) execution(* ….*(…))
2、通知类型
配置语法:
<aop:通知类型 method=“切面类总方法名” pointcut=”切点表达式“></aop:通知类型 >
通知 | 说明 |
---|---|
before(前置通知) | 通知方法在目标方法调用之前执行 |
after(后置通知) | 通知方法在目标方法返回或异常后调用。相当于finally块的内容 |
afterReturning(返回后通知) | 通知方法会在目标方法返回后调用 |
afterThrowing(抛出异常通知) | 通知方法在目标方法抛出异常后调用 |
around(环绕通知) | 通知方法会将目标方法封装起来 |
3.操作步骤
(1)导入AOP的相关坐标
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.3.RELEASE</version> </dependency> <!--aspectj框架--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.13</version> </dependency>
(2)创建目标接口和目标类
public interface TargetInterface { public void save(); } public class Tartget implements TargetInterface { public void save() { System.out.println("save running....."); } }
(3)创建切面类
public class MyAspect { public void before(){ System.out.println("前置增强Aspect....."); } public void after(){ System.out.println("后置增强Aspect....."); } }
(4)将目标类和切面类的对象创建权交给spring
<!--目标对象--> <bean id="target" class="com.itcast.aop.Tartget"></bean> <!--切面对象--> <bean id="myAspect" class="com.itcast.aop.MyAspect" ></bean>
(5)在applicationContext.xml中配置织入关系
<!--织入,告诉spring框架,哪些方法(切点)需要进行哪些增强(前置、后置。。。。)--> <aop:config> <!--声明切面--> <aop:aspect ref="myAspect"> <!--切面,切点,通知--> <aop:before method="before" pointcut="execution(public void com.itcast.aop.Tartget.save())"></aop:before> </aop:aspect> </aop:config>
(6)测试代码
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") public class AOPTest { @Autowired private TargetInterface targetInterface; @Test public void test(){ targetInterface.save(); } }
五.基于注解的AOP开发
(1)创建接口和实现类
public interface TargetInt { public void save(); } @Component("target") public class TargetAnno implements TargetInt{ public void save() { System.out.println("save running......"); } }
(2)创建切面类
@Component("myAspect") //标注当前类是一个切面类 @Aspect public class MyAspect { //定义切点表达式 @Pointcut("execution(public void com.itcast.anno.*.*(..))") public void pointcut() } @Before(value="MyAspect.pointcut()") public void before(){ System.out.println("前置增强Aspect....."); } @AfterReturning(value = "execution(public void com.itcast.anno..*.*(..))") public void afterReturing(){ System.out.println("后置增强Aspect....."); } //正在执行的连接点 @Around("execution(public void com.itcast.anno..*.*(..))") public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("环绕前增强"); //切入方法 Object proceed = pjp.proceed(); System.out.println("后置增强"); return proceed; } //异常抛出 @AfterThrowing("execution(public void com.itcast.anno..*.*(..))") public void afterThrowing(){ System.out.println("异常抛出增强....."); } //最终增强 @After("execution(public void com.itcast.anno..*.*(..))") public void after(){ System.out.println("最终增强....."); } }
(3)将目标类和切面类的对象创建权交给spring,并在配置文件中开启组件扫描和AOP的自动代理
@Configuration @ComponentScan(basePackages = {"com.itcast.anno"}) @EnableAspectJAutoProxy //AOP自动代理 public class AnnoConfig { }
(4)测试
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = AnnoConfig.class) public class AnnoTest { @Autowired private TargetInt targetInterface; @Test public void test(){ targetInterface.save(); } }
六.事务管理
1.概念
事务是应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所作的所有更改都会被撤消。也就是事务具有原子性,一个事务中的一系列的操作要么全部成功,要么一个都不做。事务的结束有两种,当事务中的所以步骤全部成功执行时,事务提交。如果其中一个步骤失败,将发生回滚操作,撤消撤消之前到事务开始时的所以操作。
2.事务的ACID原则
事务具有4个基本特性:原子性、一致性、隔离性、持久性。也就是我们常说的ACID原则。
-
原子性(Atomicity):
一个事务已经是一个不可再分割的工作单位。事务中的全部操作要么都做;要么都不做
例如:A和B两个人一共1000元,A给B转账100元,A付款100元,B收款100元, A的付款行为和B的收款行为要么都成功,要么都失败
-
一致性(Consistency):
事务的执行使数据从一个状态转换为另一个状态,但是对于整个数据的完整性保持稳定。
例如:A和B两个人一共1000元,无论A,B两人互相转账多少次,A和B两个人总额都应该是1000元
-
隔离性(Isolation):
事务允许多个用户对同一个数据进行并发访问,而不破坏数据的正确性和完整性。同时,并行事务的修改必须与其他并行事务的修改相互独立。
例如:万达影院有《叶问4》电影票100张,允许所有人同时去淘票票上购票,当第100张电影票被A,B,C3人同时购买,如果A拿到第100张电影票,但是还在犹豫要不要付钱,则B,C必须等待A的决定,如果A决定付钱,B.C就无法抢到票,如果A超时不付钱,则这第100张电影票回归票池,从新分配。
-
持久性(Durability):
一个事务一旦提交,它对数据库中数据的改变会永久存储起来。其他操作不会对它产生影响
例如:万达影院有《叶问4》电影票100张,100张电影票销售完毕,对于每个购买者来说,他的购买记录已经产生,即使退票,他之前的购买记录也不会消失。
3.事务隔离级别
隔离级别自上而下级别越来越高,性能越来越低,隔离级别分别是:
-
读未提交:一个事务还没提交时,它做的变更就能被别的事务看到。
-
读已提交:一个事务提交之后,它做的变更才会被其他事务看到。
-
可重复读(默认):一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。
-
串行化:“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交(read-uncommitted) | 是 | 是 | 是 |
不可重复读(read-committed) | 否 | 是 | 是 |
可重复读(repeatable-read) | 否 | 否 | 是 |
串行化(serializable) | 否 | 否 | 否 |
3.1 引发的问题
-
脏读 脏读是指一个事务读取了另一个事务未提交的数据
-
不可重复读 不可重复读是指一个事务对同一数据的读取结果前后不一致。脏读和不可重复读的区别在于:前者读取的是事务未提交的脏数据,后者读取的是事务已经提交的数据,只不过因为数据被其他事务修改过导致前后两次读取的结果不一样
-
幻读 幻读是指事务读取某个范围的数据时,因为其他事务的操作导致前后两次读取的结果不一致。幻读和不可重复读的区别在于,不可重复读是针对确定的某一行数据而言,而幻读是针对不确定的多行数据。因而幻读通常出现在带有查询条件的范围查询中
4.事务的管理方式
-
编程式事务管理 :编程式事务管理是通过编写代码实现的事务管理。可以根据需求规定事务从哪里开始,到哪里结束,拥有很高的灵活性。但是这种方式,会使业务代码与事务规则高度耦合,难以维护,因此我们很少使用这种方式对事务进行管理。
-
声明式事务管理: 采用声明的方式来处理事务,可以通过 2 种方式实现,分别是 XML和注解方式。Spring 在进行声明式事务管理时,底层使用了 AOP 。事务管理不侵入开发的组件。