这片文章主要讲述SpringAOP的动态代理和SpringAOP的事务管理
一、动态代理
概念: AOP是一种面向切面编程思想,是OOP的延续。其核心思想就是:横向重复,纵向抽取。Spring AOP中的动态代理很好的体现了这种思想。
如何去理解这种思想呢?如下图所示:
AOP作用:不修改源码的情况下对程序进行增强。AOP可以进行权限校验、日志记录、性能监控、事务控制。
Spring底层AOP实现原理:动态代理(增强一个类中方法)
1,JDK动态代理(优先)
被代理对象必须实现接口,才能产生代理对象
2,cglib动态代理(第三方代理技术)
可以对任何类代理,代理原理就是对目标对象进行继承代理。如果目标对象类被final(不可被继承)修饰,则不能用cdlib代理
如何理解增强一个类中方法呢?案例如下:
比如一个类中有4个方法,我想在每个方法执行之前开启事务,方法结束后提交事务。这个时候就可以用动态代理实现。
例子:
以下代码模仿AOP手写了个代理对象生成工厂,看看就可以(AOP底层已经实现,只需要配置即可)。其目的是将通知织入切入点
UserService 接口
public interface UserService {
void save();
void delete();
void update();
void find();
}
UserServiceImpl 被代理对象
public class UserServiceImpl implements UserService {
@Override
public void save() {
System.out.println("保存用户!");
//int i = 1/0;
}
@Override
public void delete() {
System.out.println("删除用户!");
}
@Override
public void update() {
System.out.println("更新用户!");
}
@Override
public void find() {
System.out.println("查找用户!");
}
UserServiceProxyFactory 代理工厂
public class UserServiceProxyFactory implements InvocationHandler {
public UserServiceProxyFactory(UserService us) {
super();
this.us = us;
}
private UserService us;
public UserService getUserServiceProxy(){
//生成动态代理
UserService usProxy = (UserService) Proxy.newProxyInstance(UserServiceProxyFactory.class.getClassLoader(),
UserServiceImpl.class.getInterfaces(),
this);
//返回
return usProxy;
}
@Override
public Object invoke(Object arg0, Method method, Object[] arg2) throws Throwable {
System.out.println("打开事务!");
Object invoke = method.invoke(us, arg2);
System.out.println("提交事务!");
return invoke;
}
Demo 测试类
public void fun1(){
UserService us = new UserServiceImpl();
UserServiceProxyFactory factory = new UserServiceProxyFactory(us);
UserService usProxy = factory.getUserServiceProxy();
usProxy.save();
usProxy.delete();
这个例子就很形象的展示了AOP思想,代理对象中对4个方法统一实现事务处理(增强了UserServiceImpl 类中的方法)
AOP开发中的相关术语:
Pointcut(切入点):目标对象,待/已经增强的方法
通知类:
public class MyAdvice {
//前置通知
// |-目标方法运行之前调用
//后置通知(如果出现异常不会调用)
// |-在目标方法运行之后调用
//环绕通知
// |-在目标方法之前和之后都调用
//异常拦截通知
// |-如果出现异常,就会调用
//后置通知(无论是否出现 异常都会调用)
// |-在目标方法运行之后调用
//----------------------------------------------------------------
//前置通知
public void before(){
System.out.println("这是前置通知!!");
}
//后置通知
public void afterReturning(){
System.out.println("这是后置通知(如果出现异常不会调用)!!");
}
//环绕通知
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("这是环绕通知之前的部分!!");
Object proceed = pjp.proceed();//调用目标方法
System.out.println("这是环绕通知之后的部分!!");
return proceed;
}
//异常通知
public void afterException(){
System.out.println("出事啦!出现异常了!!");
}
//后置通知
public void after(){
System.out.println("这是后置通知(出现异常也会调用)!!");
}
}
Spring的aopxml配置-配置将通知织入目标对象:
<!-- 1.配置目标对象 -->
<bean name="userService" class="cn.itcast.service.UserServiceImpl" ></bean>
<!-- 2.配置通知对象 -->
<bean name="myAdvice" class="cn.itcast.d_springaop.MyAdvice" ></bean>
<!-- 3.配置将通知织入目标对象 -->
<aop:config>
<!-- 配置切入点
public void cn.itcast.service.UserServiceImpl.save()
void cn.itcast.service.UserServiceImpl.save()
* cn.itcast.service.UserServiceImpl.save()
* cn.itcast.service.*ServiceImpl.*(..)
* cn.itcast.service..*ServiceImpl.*(..)
-->
<aop:pointcut expression="execution(* cn.itcast.service.*ServiceImpl.*(..))" id="pc"/>
<aop:aspect ref="myAdvice" >
<!-- 指定名为before方法作为前置通知 -->
<aop:before method="before" pointcut-ref="pc" />
<!-- 后置 -->
<aop:after-returning method="afterReturning" pointcut-ref="pc" />
<!-- 环绕通知 -->
<aop:around method="around" pointcut-ref="pc" />
<!-- 异常拦截通知 -->
<aop:after-throwing method="afterException" pointcut-ref="pc"/>
<!-- 后置 -->
<aop:after method="after" pointcut-ref="pc"/>
</aop:aspect>
</aop:config>
Spring的aop注解配置:
<!-- 1.配置目标对象 -->
<bean name="userService" class="cn.itcast.service.UserServiceImpl" ></bean>
<!-- 2.配置通知对象 -->
<bean name="myAdvice" class="cn.itcast.e_annotationaop.MyAdvice" ></bean>
<!-- 3.开启使用注解完成织入 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
Myadvice类(通知类)
//通知类
@Aspect
//表示该类是一个通知类
public class MyAdvice {
@Pointcut("execution(* cn.itcast.service.*ServiceImpl.*(..))")
public void pc(){}
//前置通知
//指定该方法是前置通知,并制定切入点
@Before("MyAdvice.pc()")
public void before(){
System.out.println("这是前置通知!!");
}
//后置通知
@AfterReturning("MyAdvice.pc()")
public void afterReturning(){
System.out.println("这是后置通知(如果出现异常不会调用)!!");
}
//环绕通知
@Around("MyAdvice.pc()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("这是环绕通知之前的部分!!");
Object proceed = pjp.proceed();//调用目标方法
System.out.println("这是环绕通知之后的部分!!");
return proceed;
}
//异常通知
@AfterThrowing("MyAdvice.pc()")
public void afterException(){
System.out.println("出事啦!出现异常了!!");
}
//后置通知
@After("MyAdvice.pc()")
public void after(){
System.out.println("这是后置通知(出现异常也会调用)!!");
}
}
测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:cn/itcast/e_annotationaop/applicationContext.xml")
public class Demo {
@Resource(name="userService")
private UserService us;
@Test
public void fun1(){
us.save();
}
}
二、事务
1,概念:事务逻辑上的一组操作,组成这组操作的各个逻辑单元,要么一起成功,要么一起失败。
2,事务特性(ACID):
1)原子性(Atomicity)原子性是指事务是一个不可分割的工作单位,事务中的操作 要么都发生,要么都不发生。
2)一致性(Consistency)一个事务中,事务前后数据的完整性必须保持一致。
3)隔离性(Isolation)多个事务,事务的隔离性是指多个用户并发访问数据库时, 一个用户的 事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离。
4)持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变 就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。
3,并发访问问题----由隔离性引起,
如果不考虑隔离性,事务存在3中并发访问问题。
1)脏读:B事务读取到了A事务尚未提交的数据 ------ 要求B事务要读取A事 务提交的数据
2)不可重复读:一个事务中 两次读取的数据的内容不一致 ----- 要求的是一个事 务中多次读取时数据是一致的 --- unpdate
例子:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。
3)幻读/虚读:一个事务中 两次读取的数据的数量不一致 ----- 要求在一个事务多 次读取的数据的数量是一致的 --insert delete
例子:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。
小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表
4,事务的隔离级别
1)read uncommitted : 读取尚未提交的数据 :哪个问题都不能解决
2)read committed:读取已经提交的数据 :可以解决脏读 ---- oracle默认的
3)repeatable read:重复读取:可以解决脏读 和 不可重复读 ---mysql默认的
4)serializable:串行化:可以解决 脏读 不可重复读 和 虚读---相当于锁表
隔离级别越高,并发访问性能越低
三、Spring对事物的封装管理
事务三大接口:
1)PlatformTransactionManager:平台事务管理器.
因为在不同的平台上,操作事务的代码各不相同,spring提供了一个接口PlatformTransactionManager(jdbc平台实现类为DataSourceTransactionManager,Hibernate平台HibernateTransactionManager)
2)TransactionDefinition:事务定义信息
事务定义信息: 隔离级别、传播行为、超时信息、是否只读
3)TransactionStatus:事务的状态
如是否一个新的事务、是否已被标记为回滚
注:如何理解 事务的传播行为
如上图所示,若两个service层之间有方法的调用,则会发生事务的传播。Spring中事务的传播行为有7种,绝大多数情况都只用第一种。
ServiceA:
ServiceA {
void methodA() {
ServiceB.methodB();
}
}
ServiceB:
ServiceB {
void methodB() {
}
}
假如当前正要运行的事务不在另外一个事务里,那么就起一个新的事务 比方说,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也要回滚
四、Spring中事务的管理:xml配置和注解方式
1,xml配置方式(思想就是AOP)
步骤:配置通知,配置将通知织入目标
<!-- 配置事务通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager" >
<tx:attributes>
<!-- 以方法为单位,指定方法应用什么事务属性
isolation:隔离级别
propagation:传播行为
read-only:是否只读
-->
<tx:method name="save*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" />
<tx:method name="persist*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" />
<tx:method name="update*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" />
<tx:method name="modify*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" />
<tx:method name="delete*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" />
<tx:method name="remove*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" />
<tx:method name="get*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="true" />
<tx:method name="find*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="true" />
<tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" />
</tx:attributes>
</tx:advice>
<!-- 配置织入 -->
<aop:config >
<!-- 配置切点表达式 -->
<aop:pointcut expression="execution(* cn.itcast.service.*ServiceImpl.*(..))" id="txPc"/>
<!-- 配置切面 : 通知+切点
advice-ref:通知的名称
pointcut-ref:切点的名称
-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPc" />
</aop:config>
2,注解方式
步骤:开启注解管理aop事务 ,在对应类或方法上加上注解
@Transactional(isolation=Isolation.REPEATABLE_READ,propagation=Propagation.REQUIRED,readOnly=true)
public class AccountServiceImpl implements AccountService {
private AccountDao ad ;
private TransactionTemplate tt;
@Override
@Transactional(isolation=Isolation.REPEATABLE_READ,propagation=Propagation.REQUIRED,readOnly=false)
public void transfer(final Integer from,final Integer to,final Double money) {
//减钱
ad.decreaseMoney(from, money);
int i = 1/0;
//加钱
ad.increaseMoney(to, money);
}
注:类上加注解,作用在类里面所有方法,也可以在方法上额外加注解来配置该方法的事务