spring第二篇之AOP及其事物的管理

本文深入探讨SpringAOP的动态代理机制,包括JDK动态代理和cglib动态代理,以及如何利用AOP进行事务控制。同时,详细解析Spring事务管理的概念、特性、并发访问问题及解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这片文章主要讲述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);
	}

注:类上加注解,作用在类里面所有方法,也可以在方法上额外加注解来配置该方法的事务
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值