目录
3 AOP面向切面编程
3.1 动态代理
3.1.1 代理模式:
-
为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
-
假设你有a类,想要调用c类的方法,但是c类禁止a类调用它的方法,因此可以在a和c中间创建一个b代理,让b来访问c。此时,可以通过a访问b、b再访问c的过程,达到a类调用c方法的效果。
-
使用代理模式的作用:
-
功能增强:在你原有的功能上,增加了新的额外的功能
-
控制访问:代理类不让你直接访问目标
-
减少代码的重复
-
专注于业务逻辑代码
-
解耦合
-
-
实现代理的方式:
-
静态代理:
代理类是自己手工实现的,自己创建一个java类,表示代理类。同时你所要代理的目标类是确定的。
特点:实现简单、容易理解
功能:目标方法调用、功能增强
缺陷:当目标类增多,代理类也会成倍增加,代理类数量过多;接口中增加或修改功能,需要修改大量的目标类和代理类。
-
动态代理:
在程序执行过程中,使用jdk的反射机制,创建代理对象,并动态的指定要代理的目标类。
特点:
在静态代理中目标类很多时,可以使用动态代理,避免静态代理的缺点。
动态代理中目标类即使很多,代理类数量却可以很少,当你修改接口中的方式时,不会影响代理类。
-
-
动态代理的实现分类:
-
jdk动态代理:使用java反射包中的类和接口实现动态代理的功能。必须实现接口。
-
cglib动态代理:是第三方的工具库,创建代理对象。
cglib的原理是继承,cglib通过继承目标类,创建它的子类,在子类中重写父类中的同名方法,实现功能的修改。因为cglib是继承,重写方法,所以要求目标类不能是final的,方法也不能是final的。
-
3.1.2 jdk动态代理:
-
反射:method类表示方法。通过Method可以执行某个方法。
-
实现:
反射包java.lang.reflect,里面有三个类:InvocationHandler,Method,Proxy
-
InvocationHandler接口(调用处理器):就一个方法invoke()
invoke:表示代理对象要执行的功能代码。你的代理类要完成的功能就写在其中。
怎么使用这个接口:创建类实现接口InvocationHandler;重写invoke方法,把原来静态代理中代理类要完成的功能,写在invoke中。
-
Method类(方法):目标类中的方法。通过Method可以执行目标类方法。
-
Proxy类:核心对象,作用是创建代理对象。
方法:静态方法 newProxyInstance(),创建代理对象。它有三个参数
-
ClassLoader loader类加载器,通过反射a.class,getClassLoader()获得
-
Class<?>[] interfaces:接口,目标对象实现的接口,也用反射获得
-
InvocationHandler h:自己写的,代理类要实现的代码
返回值就是一个代理对象。
-
-
3.2 AOP是什么东西
3.2.1 概述
AOP(Aspect Orient Programming),面向切面编程。面向切面编程是从动态角度考虑程序运行过程。
AOP底层,就是采用动态代理模式实现的,采用了两种代理:JDK动态代理和CGLIB动态代理。
AOP就是动态代理的规范化,把动态代理的实现步骤、方法都定义好了,让开发人员可以使用统一的方式。
3.2.2 如何理解面向切面编程
-
需要分析项目功能时,找出切面
-
合理安排切面的执行时间(在目标方法前,还是目标方法后)
-
合理的安排切面执行的位置,在哪个类,哪个方法增加功能
关键术语:
-
Aspect:切面,给你的目标类增加非业务的功能,就是切面。 特点:一般都是非业务方法,独立使用。 常见切面功能:日志,事务,统计信息,参数检查,权限验证。
-
Advice 通知,通知表示切面功能执行的时间
-
JoinPoint 连接点,连接业务方法和切面的位置。就是某类中的业务方法
-
PointCut 切入点,指多个连接点方法的集合。多个方法
一个切面的三个关键要素:
-
切面的功能代码,切面干什么
-
切面的执行位置,使用PointCut表示切面执行的位置
-
切面的执行时间,使用Advice表示时间,在目标方法之前,还是目标方法之后
3.3 AOP的实现
aop是一个规范,时动态的一个规范化,一个标准
aop的技术实现框架:
-
spring:spring在内部实现了aop主要在事务处理时使用,我们项目开发中很少使用,因为比较笨重
-
aspectJ:是eclipse的一个开源框架。spring中集成了aspectJ框架。
aspectJ框架实现aop的两种方式:
-
使用xml的配置文件:配置全局事务
-
使用注解,我们在项目中要做aop功能,一般都使用注解。aspectJ有5种注解。
-
3.3.1 学习aspectJ框架
-
切面的执行时间,这个执行时间在规范中叫做Advice(通知,增强) 在aspect框架中注解表示:
-
@Before
-
@AfterReturning
-
@Around
-
@AfterThrowing
-
@After
-
-
切面执行的位置,使用的是切入点表达式。
其中可以使用以下字符:
3.3.2 aspectJ使用步骤
-
新建maven项目:quick_start
-
加入依赖:
-
spring依赖
-
aspectJ依赖
-
junit依赖
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.5.RELEASE</version> </dependency> </dependencies>
-
-
创建目标类
package com.pjh.ba01; public interface SomeService { void doSome(String name, Integer age); } //上为接口,下为实现类 package com.pjh.ba01; public class SomeServiceImpl implements SomeService{ @Override public void doSome(String name, Integer age) { //给目标类增加一个执行时间的功能 System.out.println("==== 目标方法doSome() ===="); } }
-
创建切面类:普通类
package com.pjh.ba01; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import java.util.Date; /** * @Aspect 是aspectJ框架中的注解。 * 作用:表示当前类是切面类 * 切面类:用来给业务方法增加功能的类,在这个类中有切面的功能代码 * 位置:在类定义的上面 */ @Aspect public class MyAspect { /** * 定义方法:方式是实现切面功能的 * 方法的定义要求: * 1. 公共方法public * 2. 方法没有返回值 * 3. 方法名称自定义 * 4. 方法可以有参数,也可以没有,如果有参数,参数是有规范的 * * @Before 前置通知注解 * 属性:value,是切入点表达式,表示切面的功能执行的位置。 * 位置:在方法上面 * 特点: * 1. 在目标方法之前先执行 * 2. 不会改变目标方法的执行结果 * 3. 不会影响目标方法的执行 */ @Before(value = "execution(void com.pjh.ba01.SomeServiceImpl.doSome(String,Integer))") public void myBefore() { System.out.println("==== 切面功能:输出执行时间 ===="); System.out.println(new Date()); } }
-
声明spring的配置文件:声明对象,将对象交给容器管理
-
声明目标对象
-
声明切面类对象
-
声明aspectJ框架中的自动代理生成器标签。
自动代理生成器:用来完成代理对象的自动创建功能的。
<?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.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 将对象交由spring容器统一管理 --> <!-- 声明目标对象 --> <bean id="someService" class="com.pjh.ba01.SomeServiceImpl"/> <!-- 声明切面对象 --> <bean id="myAspect" class="com.pjh.ba01.MyAspect"/> <!-- 声明自动代理生成器 使用aspectJ框架内部功能,创建目标对象的代理对象。 创建代理对象是在内存中实现的,修改目标对象的内存中的结构,创建为代理对象, 所以目标对象就是修改后的代理对象 aspectj-autoproxy:会把spring容器中的所有的目标对象,一次性都生成代理对象 --> <aop:aspectj-autoproxy/> </beans>
-
-
创建测试类,从spring容器中获取目标对象(实际是代理对象)
执行代理方法,实现aop的功能增强。
@Test public void Test01() { String config = "applicationContext.xml"; ApplicationContext context = new ClassPathXmlApplicationContext(config); //com.sun.proxy.$Proxy: jdk动态代理 SomeService proxy = (SomeService) context.getBean("someService"); proxy.doSome("lisi", 20); } /** * 输出结果如下: * * ==== 切面功能:输出执行时间 ==== * Sun Oct 03 16:41:02 CST 2021 * ==== 目标方法doSome() ==== */
3.3.3 JoinPoint参数
即通知方法的参数中可选的一项。
其表示一个要加入切面功能的业务方法。
作用是:可以在通知方法中获取方法执行时的信息,例如方法名称,方法的实参。
如果你的切面功能中需要用到方法的信息,就加入JoinPoint。
这个JoinPoint参数的值,是由框架赋予的,帮你叙事第一个位置的参数。
JoinPoint的几个方法:
-
getSignature():获得方法的签名(定义)
-
getArgs(): 获得方法的实参,其返回一个obj数组表示方法的所有实参。
3.3.4 五种注解
-
@Before:前置通知
定义要求:
-
公共方法
-
方法没有返回值
-
方法名称自定义
-
方法可以有参数,可以没有,如果有参数请见3.3.3
属性:value,是切入点表达式,表示切面的功能执行的位置。 位置:在方法上面 特点:
-
在目标方法之前先执行
-
不会改变目标方法的执行结果
-
不会影响目标方法的执行
-
-
@AfterReturning:后置通知
定义要求:
-
公共方法
-
方法没有返回值
-
方法名称自定义
-
方法有参数,推荐Object,参数名自定义
属性:
-
value 切入点表达式
-
returning 自定义变量,表示目标方法的返回值;自定义变量名必须和通知方法的形参名一样
位置:在方法定义之上
特点:
-
在目标方法之后执行
-
能够获取目标方法的返回值,可以根据这个返回值做不同的处理功能
-
可以修改这个返回值
-
-
@Around:环绕通知
定义格式:
-
public
-
必须有一个返回值,推荐Object
-
方法名称自定义
-
方法有固定参数 ProceedingJoinPoint
属性:value 切入点表达式
位置:在方法定义上面
特点:
-
它是功能最强的通知
-
它能在目标方法前后都能增强功能
-
控制目标方法是否被调用执行
-
修改原来的目标方法的执行结果,影响最后的调用结果
相当于jdk的动态代理,InvocationHandler
参数:ProceedingJoinPoint,继承于joinPoint,作用等同于method
作用:执行目标方法
返回值:就是目标方法的执行结果,可以被修改
-
-
@AfterThrowing:异常通知
定义格式:
-
public
-
没有返回值
-
方法名称自定义
-
方法有一个Exception参数,如果还有就是JoinPoint
属性:
-
value 切入点表达式
-
throwing 自定义变量,表示目标方法抛出的异常对象。变量名必须和方法的参数名一样
特点:
-
在目标方法抛出异常时执行
-
可以做异常的监控程序,监控目标方法执行时是不是有异常
-
-
@After:最终通知
定义格式:
-
public
-
没有返回值
-
方法名称自定义
-
方法没有参数,如果有就是JoinPoint
属性:value 切入点表达式
位置:在方法上面
特点:
-
总是会执行
-
在目标方法之后执行
-
-
@Pointcut:定义切入点
五种注解有六个不是常识吗(X)
作用:用来定义和管理切入点,如果你的项目中有多个切入点表达式时重复的,可以复用的,可以使用@Pointcut。
属性:value 切入点表达式
位置:在自定义方法的上面
特点:当使用@Pointcut定义在一个方法的上面,此时这个方法的名称就是切入表达式的别名,其他通知中,value属性就可以使用这个方法名称,代替切入点表达式了,记得方法后面要加括号。
3.3.5 如果目标类不是接口的实现类(没有接口)
aspectJ会使用cglib,采用继承的方式实现动态代理。
当然有接口也可以使用cglib方式,只要目标类不是final。
方法是:
在spring的配置文件中,将<aop:aspectj-autoproxy/>改为<aop:aspectj-autoproxy proxy-target-class="true"/>
4 Spring集成MyBatis
使用IoC技术,它能把mybatis和spring集成在一起,像一个框架一样。
因为IoC能创建对象,可以把mybatis框架中的对象交给spring统一管理,开发人员从spring中获取对象。
开发人员就不用同时面对两个或者多个框架了。
4.1 回顾一下MyBatis
使用步骤:
-
定义dao接口,StudentDao
-
定义mapper文件,StudentDao.xml
-
定义mabatis主配置文件,mybatis.xml
-
创建dao的代理对象,StudentDao dao = SqlSession.getMapper(StudentDao.class);
-
查询数据库,List<Student> students = dao.selectStudents();
要使用dao对象,需要使用SqlSession的getMapper方法,因此我们需要:
-
获取SqlSession对象,需要使用SqlSessionFactory的openSession对象
-
创建SqlSessionFactory对象。通过读取mybatis的主配置文件,能创建SqlSessionFactory对象
需要SqlSessionFactory对象,使用Factory能获取SqlSession,有了SqlSession就能有dao,目的就是获取dao对象。Factory的创建需要读取主配置文件:
<!-- 数据库信息 -->
<environment id="test2">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.pwd}"/>
</dataSource>
</environment>
<!-- mapper文件位置 -->
<mappers>
<mapper resource="com/pjh/dao/StudentDao.xml"/>
</mappers>
由于mybatis自带的连接池不够强力,所以我们使用其他强力的连接池代替它默认的,把这个连接池也交给spring创建。
4.2 我们需要spring做什么
通过4.1的说明,我们需要让spring创建一下对象
-
独立的连接池类对象,使用阿里的druid连接池
-
SqlSessionFactory对象
-
创建出dao对象
我们需要学习的就是创建这三个对象的语法,使用xml的bean标签。
4.3 整合创建项目
步骤:
-
新建maven项目
-
加入maven依赖
-
spring依赖
-
mybatis依赖
-
mysql驱动
-
spring事务依赖
-
mybatis和spring集成依赖:mybatis官方提供,用来在spring项目中创建mybatis的SqlSessionFactory,dao对象的。
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.2.5.RELEASE</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.19</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.1</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.1</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.12</version> </dependency> </dependencies>
-
-
创建实体类
-
创建dao接口和mapper文件
-
创建mybatis主配置文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- 提示日志 --> <settings> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings> <!-- 别名 --> <typeAliases> <!-- name:实体类所在包名 --> <package name="com.pjh.domain"/> </typeAliases> <!-- sql mapper的位置 --> <mappers> <package name="com.pjh.dao"/> </mappers> </configuration>
-
创建Service接口和实现类,属性是dao
-
创建spring的配置文件:声明mybatis的对象交给spring创建
-
数据源DataSource
-
SqlSessionFactory
-
Dao对象
-
声明自定义的service
<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!-- 读取配置文件 --> <context:property-placeholder location="classpath:jdbc.properties"/> <!-- 声明数据源DataSource对象,作用是连接数据库 --> <bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <!-- set注入给DruidDataSource提供连接数据库的信息 --> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> <property name="maxActive" value="${jdbc.maxActive}"/> <!-- druid还有更多细分的设置项,但一般来说,设置这四项已经足够了,详情请查询druid的github wiki --> </bean> <!-- 声明mybatis提供的SqlSessionFactoryBean类,这个类内部创建SqlSessionFactory --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- set注入给sqlSessionFactory提供数据源和mybatis主配置文件位置的信息 --> <property name="dataSource" ref="myDataSource"/> <property name="configLocation" value="classpath:mybatis.xml"/> </bean> <!-- 声明Dao对象,使用SqlSession的getMapper --> <!-- MapperScannerConfigurer:在内部调用getMapper()生成每个dao接口的代理对象 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!-- 指定SqlSessionFactory对象的id --> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> <!-- 指定包名,包名是dao接口所在的包名 --> <!-- MapperScannerConfigurer会扫描这个包中的所有接口,把每个接口都执行一次getMapper方法,得到每个接口的dao对象 --> <!-- 创建好的dao对象放到spring的容器中,dao对象的默认名称为接口名首字母小写 --> <property name="basePackage" value="com.pjh.dao"/> </bean> <!-- 声明Service对象 --> <bean id="studentService" class="com.pjh.service.impl.StudentServiceImpl"> <property name="studentDao" ref="studentDao"/> </bean> </beans>
-
-
创建测试类,获取sevice对象,通过service调用dao完成数据库的访问
4.4 小贴士
mybatis和spring整合使用时,事务自动提交,不需要再手动commit
5 Spring的事务处理
5.1 事务
什么是事务:
事务是指一组sql语句的集合,集合中有多条sql语句,可能是insert,update,select,delete,我们希望这些多个sql语句都能成功或者失败,这些sql语句的执行是一致的,作为一个整体执行。
什么时候使用事务:
当操作涉及多个表,或者是多个sql语句的insert,update,delete。需要保证这些语句都是成功才能完成我的功能,或者都失败,保证操作时符合要求的;在java代码中写程序,控制事务,应该放在service类的业务方法上,因为业务方法会调用多个dao方法,执行多个语句
jdbc和mybatis访问数据库怎样处理事务:
-
jdbc:Connection.commit(); Connection.rollback();
-
mybatis:SqlSession.commit(); SqlSession.rollback();
-
缺陷:
-
不同数据库访问技术,处理事务的对象,方法不同,需要了解不同数据库访问技术使用事务的原理
-
掌握多种数据库中事务的处理逻辑。什么时候提交,什么时候回滚
-
处理事务的多种方法
-
-
总结:多种数据库访问技术,有不同的事务处理的机制,对象,方法
怎么解决不足:
spring提供了一种处理事务的统一模型,能使用统一步骤、方法完成多种不同数据库访问技术的事务处理。
5.2 Spring提供处理事务的统一模型
声明式事务:把事务相关的资源和内容都提供给spring,spring就能处理事务提交和回滚了,几乎不用代码。
spring处理事务的模型,使用的步骤都是固定的,把事务使用的信息提供给spring就可以了
-
事务内部提交,回滚事务,使用的事务管理器对象,代替你完成commit和rollback
事务管理器:是一个接口和他的众多实现类。
接口:PlatformTransactionManager,定义了事务的重要方法commit、rollback
实现类:spring把每一种数据库访问技术对应的事务处理类都创建好了。
怎么使用:你需要告诉spring,你使用的哪种数据库的访问技术,在spring的配置文件种使用<bean>声明对应的实现类就可以了,例如mybaits就声明DataSourceTransactionManager。
-
你的事务方法需要什么样的事务,说明需要事务的类型
说明方法需要的事务三个方面:
-
事务的隔离级别:4个值
DEFAULT:MySql默认REPEATABLE_READ
READ_UNCOMMITTED:读未提交。未解决并发问题
READ_COMMITTED:读已提交,解决脏读,存在不可重复读与幻读
REPEATABLE_READ:可重复读,解决脏读、不可重复读,存在幻读
SERIALIZABLE:串行化。不存在并发问题
-
事务超时时间:表示一个方法最长的执行时间,如果方法执行时超过了时间,事务就回滚。
单位是秒,整数值,默认是-1(不设置超时时间)
-
事的传播行为:控制事务方式是不是有事务,是怎样的事务。
7种传播行为,表示你的业务方法调用时,事务在方法之间时如何使用的。此处重点掌握前三个:
PROPAGATION_REQUIRED
指定的方法必须需要一个事务。如果当前存在事务,方法会自动加入到事务中(白嫖);如果不存在事务,方法会自己开启一个新的事务并加入其中(没白嫖到被迫用自己的,屑)。事务的启动和加入由spring控制。
PROPAGATION_REQUIRES_NEW
指定的方法必须需要一个事务,且必定创建一个新事务。如果当前已经存在事务,它会将旧的事务挂起,优先执行新的事务。
PROPAGATION_SUPPORTS
指定的方法非必须需要事务。如果当前存在事务,方法会自动加入到事务中;如果不存在事务,方法会以非事务方式执行。
PROPAGATION_MANDATORY
PROPAGATION_NESTED
PROPAGATION_NEVER
PROPAGATION_NOT_SUPPORTED
-
-
事务提交和回滚的时机:
-
当你的业务方法执行成功,且没有异常抛出,当方法执行完毕,spring在方法执行后提交事务。
-
当你的业务方法抛出运行时异常或ERROR,spring执行回滚,调用事务管理器的rollback
-
当你的业务方法抛出非运行时异常,主要是受查异常时,提交事务
受查异常:写代码时必须处理的异常。例如IOException,SQLException
-
5.3 spring框架中提供的两种事务处理方案
5.3.1 适合中小项目使用的注解方案
spring框架自己用aop实现给业务方法增加事务的功能,使用@Transactional注解增加事务。
@Transactional注解:
是spring框架自己的注解,放在public方法的上面,表示当前方法具有事务。可以给注解的属性赋值,表示具体的隔离级别,传播行为,异常信息等。
使用注解的步骤:
-
需要声明事务管理对象:<bean id="xx" class="DataSourceTransactionManager">
-
开启事务注解驱动,告诉spring框架,我要使用注解的方式管理事务
spring使用aop机制,创建@Transactional所在类的代理对象,给方法加入事务功能
spring给业务方法加入事务:
在你的业务方法执行前,先开启事务,在业务方法结束后提交或者回滚事务吗,使用aop的环绕通知
<!-- 使用spring的事务处理 --> <!-- 1. 声明事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 指定数据源 --> <property name="dataSource" ref="myDataSource"/> </bean> <!-- 2. 开启事务注解驱动,告诉spring使用注解管理事务,创建代理对象 --> <!-- transaction-manager:事务管理器对象id --> <tx:annotation-driven transaction-manager="transactionManager"/>
-
在方法上方加入@Transactional注解
/** * rollback:表示发生指定的异常一定回滚 * 处理逻辑: * 1. spring框架会首先检查方法抛出的异常是不是在rollbackfor的属性值中,如果在,不管是什么类型的* 异常,一定回滚 * 2. 如果抛出异常不在rollbackfor列表中,spring会判断异常是不是RuntimeException,如果是一定 * 回滚。 */ @Transactional( //传播方式 propagation = Propagation.REQUIRED, //隔离级别 isolation = Isolation.DEFAULT, //是否仅读 readOnly = false, //指定异常回滚 rollbackFor = { NullPointerException.class, NotEnoughException.class } ) //或者直接使用@Transactional,使用注解的默认值 @Override public void buy(Integer gid, Integer nums) { Sale sale = new Sale(); sale.setGid(gid); sale.setNums(nums); saleDao.insertSale(sale); Goods goods = goodsDao.selectGoods(gid); if(goods == null) { throw new NullPointerException("编号 " + gid + "商品不存在"); } else if(goods.getAmount() < nums) { throw new NotEnoughException("编号 " + gid + "商品不足"); } Goods buyGoods = new Goods(); buyGoods.setId(gid); buyGoods.setAmount(nums); goodsDao.updateGoods(buyGoods); }
5.3.2 适合大型项目使用aspectJ框架功能
在spring配置文件中声明类,方法需要的事务。这种方式业务方法和事务完全分离。
实现步骤:都是在xml配置文件中实现
-
加入aspectJ框架的依赖
-
声明事务管理器对象
-
声明方法需要的事务类型(配置方法的事务属性:隔离级别、传播行为、超时)
-
配置aop:指定哪些类需要创建代理
<!-- 使用spring的事务处理 -->
<!-- 1. 声明事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 指定数据源 -->
<property name="dataSource" ref="myDataSource"/>
</bean>
<!-- 2. 声明业务方法的事务属性 -->
<!-- id:自定义名称,表示标签之间的配置内容的 -->
<!-- transaction-manager:事务管理器对象id -->
<tx:advice id="myAdvice" transaction-manager="transactionManager">
<!-- attributes:配置事务属性 -->
<tx:attributes>
<!-- method:给具体的方法配置事务属性,method可以有多个,分别给不同的方法设置事务属性 -->
<!--
name:方法名称
1. 完整方法名称,不带一包和类名
2. 方法可以使用通配符,*表示任意字符
-->
<tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT" rollback-for="java.lang.NullPointerException,com.pjh.exception.NotEnoughException"/>
</tx:attributes>
</tx:advice>
<!-- 3. 配置aop -->
<aop:config>
<!--
配置切入点表达式:表达哪些包中的类要使用事务
id:切入点表达式的名称,唯一值
expression:切入点表达式,指定哪些类要使用事务,aspectJ会创建代理对象
-->
<aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/>
<!--
配置增强器
关联advice和pointcut
advice-ref:通知
pointcut-ref:切入点表达式的id
-->
<aop:advisor advice-ref="myAdvice" pointcut-ref="servicePt"/>
</aop:config>
总结
spring的学习就暂时到此为止了,那是真的难顶,下一站springMVC
6 web项目中怎么使用容器对象
web项目在服务器上运行,tomcat一启动,项目一直运行,如果每次都要创建一次容器对象,是十分消耗资源的行为,因此spring容器对象存在一个即可。
我们将创建好的容器对象放到全局作用域ServletContext中。
实现:
使用监听器,当全局作用域对象被创建时,创建容器,存到ServletContext中
监听器的作用:
-
创建容器对象
-
将容器对象存到ServletContext
监听器可以自己创建,也可以使用框架提供好的ContextLoaderListener
使用步骤:
-
在web.xml中注册监听器
<!-- 注册监听器 --> <!-- 监听器被创建对象后,会读取WEB-INF中的applicationContext.xml文件 --> <!-- 可以使用contest-param修改文件位置 --> <context-param> <!-- 需要配置的监听器对象 --> <param-name>contextConfigLocation</param-name> <!-- 指定文件路径 --> <param-value>classpath:spring.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
-
在servlet中获取容器对象
WebApplicationContext context = null; //自己通过key找到全局作用域中的webApplicationContext //String key = WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE; //context = (WebApplicationContext) request.getServletContext().getAttribute(key); //通过spring提供的工具类 context = WebApplicationContextUtils.getWebApplicationContext(getServletContext());