一、AOP的相关概念[理解]
什么是AOP: 全称是Aspect Oriented Programming即:面向切面编程。
简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。
AOP的作用及优势:
作用: 在程序运行期间,不修改源码对已有方法进行增强
优势:减少重复代码 、提高开发效率 、维护方便
AOP的实现方式:
使用动态代理技术:
1、基于接口的JDK官方的动态代理
2、基于子类的第三方的cglib的动态代理
二、Spring中的AOP[掌握]
1、 Spring中AOP的细节
AOP相关术语:
Joinpoint(连接点):
所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。
Pointcut(切入点):
所谓切入点是指我们要对哪些Joinpoint进行拦截的定义。
Advice(通知/增强):
所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。
通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
Introduction(引介):-了解
引介是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field。
Target(目标对象):
代理的目标对象。
Weaving(织入):
是指把增强应用到目标对象来创建新的代理对象的过程。
spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
Proxy(代理):
一个类被AOP织入增强后,就产生一个结果代理类。
Aspect(切面):
是切入点和通知(引介)的结合。
学习spring中的AOP要明确的事
a、开发阶段(我们做的)
编写核心业务代码(开发主线):大部分程序员来做,要求熟悉业务需求。
把公用代码抽取出来,制作成通知。(开发阶段最后再做):AOP编程人员来做。
在配置文件中,声明切入点与通知间的关系,即切面。:AOP编程人员来做。
b、运行阶段(Spring框架完成的)
Spring框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
2、 基于XML的AOP配置
第一步:创建maven工程并导入依赖
pom.xml:
<!--统一版本管理-->
<properties>
<spring.version>5.0.6.RELEASE</spring.version>
<junit.version>4.12</junit.version>
</properties>
<dependencies>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!--springIOC相关-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!--spring AOP相关-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<!--jdk编译版本插件-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
第二步:创建接口和实现类
接口:
public interface IAccountService {
/**
* 模拟保存
*/
void saveAccount();
/**
* 模拟更新
* @param i
*/
void updateAccount(int i);
/**
* 模拟删除
* @return
*/
int deleteAccount();
}
实现类:
public class AccountServiceImpl implements IAccountService {
public void saveAccount() {
System.out.println("保存账户");
}
public void updateAccount(int i) {
System.out.println("更新账户");
}
public int deleteAccount() {
System.out.println("删除账户");
return 0;
}
}
第三步:编写切面类
切面类也可以称之为通知类:
public class Logger {
/**
* 用于打印日志 计划让其在切入点方法执行之前执行
*/
public void printLog() {
System.out.println("Logger类中的printLog方法开始记录日志了。。。");
}
}
第四步:创建spring的配置文件并导入约束
<?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"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--装配AccountServiceImpl到容器中-->
<bean id="accountService" class="cn.yirujiwang.service.impl.AccountServiceImpl"></bean>
<!--装配Logger到容器中-->
<bean id="logger" class="cn.yirujiwang.utils.Logger"></bean>
<!-- 配置AOP -->
<aop:config>
<!-- 配置切面
id:切面的唯一标识
ref:引用通知类
-->
<aop:aspect id="logAdvice" ref="logger">
<!-- 配置前置通知
method:配置通知方法(即具体进行增强的方法)
pointcut:配置AspectJ表达式,即将通知增强到哪个方法
execution:使用AspectJ的切入点表达式
execution(修饰符 返回值类型 包名.类名.方法名(参数列表))
-->
<aop:before method="printLog" pointcut="execution(public void cn.yirujiwang.service.impl.AccountServiceImpl.saveAccount())" />
</aop:aspect>
</aop:config>
</beans>
第五步:测试
public class IAccountServiceTest {
private IAccountService accountService;
@Before
public void before(){
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
accountService = (IAccountService) ac.getBean("accountService");
}
@Test
public void saveAccount() {
accountService.saveAccount();
}
@Test
public void updateAccount() {
accountService.updateAccount(1);
}
@Test
public void deleteAccount() {
int i = accountService.deleteAccount();
}
}
AspectJ表达式优化
如果要配置在对某个实现类的方法中设置多个不同类型的前置通知的增强:
public class Logger {
/**
* 用于打印日志 计划让其在切入点方法执行之前执行
*/
public void printLog() {
System.out.println("Logger类中的printLog方法开始记录日志了。。。");
}
public void printLog2() {
System.out.println("Logger类中的printLog2方法开始记录日志了。。。");
}
public void printLog3() {
System.out.println("Logger类中的printLog3方法开始记录日志了。。。");
}
}
那么就需要编写多次<aop:before>标签以及配置多次pointcut属性,比如:
<aop:config>
<aop:aspect id="logAdvice" ref="logger">
<aop:before method="printLog" pointcut="execution(public void cn.yirujiwang.service.impl.AccountServiceImpl.saveAccount())"/>
<aop:before method="printLog1" pointcut="execution(public void cn.yirujiwang.service.impl.AccountServiceImpl.saveAccount())"/>
<aop:before method="printLog2" pointcut="execution(public void cn.yirujiwang.service.impl.AccountServiceImpl.saveAccount())"/>
</aop:aspect>
</aop:config>
为了使用相同的表达式方便,因此可以定义一个通用的表达式来进行引用。
<!-- 配置aop -->
<aop:config>
<!-- 配置切入点表达式方便被引用 -->
<aop:pointcut expression=" execution(public void cn.yirujiwang.service.impl.AccountServiceImpl.saveAccount())" id="pt1"/>
<aop:aspect id="logAdvice" ref="logger">
<aop:before method="printLog" pointcut-ref="pt1"/>
<aop:before method="printLog2" pointcut-ref="pt1"/>
<aop:before method="printLog3" pointcut-ref="pt1"/>
</aop:aspect>
</aop:config>
AspectJ切入点表达式说明
execution:匹配方法的执行(常用)
execution(表达式)
表达式语法:execution([修饰符] 返回值类型 包名.类名.方法名(参数))
写法说明:
全匹配方式:
public void cn.itcast.service.impl.AccountServiceImpl.saveAccount()
访问修饰符可以省略
void cn.itcast.service.impl.AccountServiceImpl.saveAccount()
返回值可以使用*号,表示任意返回值
* cn.itcast.service.impl.AccountServiceImpl.saveAccount()
包名可以使用*号,表示任意包,但是有几级包,需要写几个*
* *.*.*.*.AccountServiceImpl.saveAccount()
使用..来表示当前包,及其子包
* cn..AccountServiceImpl.saveAccount()
类名可以使用*号,表示任意类
* cn..*.saveAccount()
方法名可以使用*号,表示任意方法
* cn..*.*()
参数列表可以使用*,表示参数可以是任意数据类型,但是必须有参数
* cn..*.*(*)
参数列表可以使用..表示有无参数均可,有参数可以是任意类型
* cn..*.*(..)
全通配方式:
* *..*.*(..)
注:
通常情况下,我们都是对业务层的方法进行增强,所以切入点表达式都是切到业务层实现类。
execution(* cn.itcast.service.impl.*.*(..))
3、 常用标签
(1)<aop:config>
作用:用于声明开始aop的配置
配置:<aop:config></aop:config>
(2) <aop:aspect>
作用: 用于配置切面。
属性:
id:给切面提供一个唯一标识。
ref:引用配置好的通知类bean的id。
配置:<aop:aspect id="logAdvice" ref="logger">
(3) <aop:pointcut>
作用:用于配置切入点表达式
属性:
expression:用于定义切入点表达式。
id:用于给切入点表达式提供一个唯一标识。
配置:<aop:pointcut expression="execution(* cn.itcast.service.impl.*.*(..))" id="pt1"/>
(4) <aop:before>
作用:用于配置前置通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
配置:<aop:before method="beforePrintLog" pointcut-ref="pt1"/>
(5) <aop:after-returning>
作用:用于配置后置通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
配置:<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"/>
(6) <aop:after-throwing>
作用: 用于配置异常通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
配置:<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"/>
(7) <aop:after>
作用: 用于配置最终通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
配置:<aop:after method="afterPrintLog" pointcut-ref="pt1"/>
(8) <aop:around>
作用:用于配置环绕通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
配置:<aop:around method="transactionAround" pointcut-ref="pt1"/>
说明:
它是spring框架为我们提供的一种可以在代码中手动控制增强代码什么时候执行的方式。
注意:
通常情况下,环绕通知都是独立使用的
各种通知使用案例:
添加各种通知:在通知类Logger中添加各种不同类型的通知:前置通知、后置通知、异常通知、最终通知、环绕通知。
package cn.yirujiwang.utils;
public class Logger {
//前置通知
public void beforePrintLog(){
System.out.println("前置通知执行了");
}
//后置通知
public void afterReturningPrintLog(){
System.out.println("后置通知执行了");
}
//异常通知
public void afterThrowingPrintLog(){
System.out.println("异常通知执行了");
}
//最终通知
public void afterPrintLog(){
System.out.println("最终通知执行了");
}
}
前置通知
步骤一:在xml中配置前置通知
<!--前置通知:目标方法执行之前执行-->
<aop:before method="beforePrintLog" pointcut-ref="pt1"></aop:before>
步骤二:测试前置通知
后置通知
步骤一:配置后置通知
<!--后置通知:目标方法执行之后执行-->
<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>
步骤二:测试后置通知
异常通知
步骤一:添加异常
@Override
public void saveAccount() {
System.out.println("保存账户");
int x = 1 / 0;
}
步骤二:配置异常通知
<!--异常通知:目标方法发生异常时执行-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>
步骤三:测试异常通知
最终通知
步骤一:配置最终通知
<!--最终通知:不管有没有异常,最终都会执行-->
<aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>
步骤二:测试最终通知
环绕通知
步骤一:编写环绕通知
/**
* 环绕通知
*
* spring框架为我们提供了一个接口,该接口可以作为环绕通知的方法参数来使用
* ProceedingJoinPoint。当环绕通知执行时,spring框架会为我们注入该接口的实现类。
* 它有一个方法proceed(),就相当于invoke,执行目标方法
*
* spring的环绕通知:
* 它是spring为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
*/
public Object around(ProceedingJoinPoint pjp){
Object obj = null;
try {
System.out.println("前置通知");
obj = pjp.proceed();
System.out.println("后置通知");
} catch (Throwable e) {
System.out.println("异常通知");
e.printStackTrace();
}finally{
System.out.println("最终通知");
}
return obj;
}
步骤二:配置环绕通知
<!--环绕通知:手动在代码中配置各类通知的执行时机-->
<aop:around method="around" pointcut-ref="pt1"></aop:around>
步骤三:测试环绕通知
4、 基于注解的AOP配置
第一步:工程搭建
复制之前的工程并且重命名。然后修改pom.xml中的artifactId为当前工程名称,修改artifactId,删除其他文件,只留下src和pom.xml,然后通过idea打开工具即可。
第二步:配置文件
<?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"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--开启注解扫描-->
<context:component-scan base-package="cn.yirujiwang"></context:component-scan>
<!--开启注解AOP-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
第三步:注解装配service实现类
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
@Override
public void saveAccount() {
System.out.println("保存账户");
int x = 1 / 0;
}
@Override
public void updateAccount(int i) {
System.out.println("更新账户");
}
@Override
public int deleteAccount() {
System.out.println("删除账户");
return 0;
}
}
第四步:通知类上使用注解配置
@Component("logger")
public class Logger { }
第五步:在通知类上使用@Aspect注解声明为切面
作用:把当前类声明为切面类。
@Component("logger")
@Aspect//表示当前类是一个切面类(也可以称为通知类)
public class Logger { }
第六步:使用注解配置通知类型
/**
* 前置通知
* @Before
*/
@Before("pt1()")
public void beforePrintLog(){
System.out.println("前置通知执行了");
}
/**
* 后置通知
* @AfterReturning
*/
@AfterReturning("pt1()")
public void afterReturningPrintLog(){
System.out.println("后置通知执行了");
}
/**
* 异常通知
* @AfterThrowing
*/
@AfterThrowing("pt1()")
public void afterThrowingPrintLog(){
System.out.println("异常通知执行了");
}
/**
* 最终通知
* @After
*/
@After("pt1()")
public void afterPrintLog(){
System.out.println("最终通知执行了");
}
第七步:执行测试
public class IAccountServiceTest {
private IAccountService accountService;
@Before
public void before(){
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
accountService = (IAccountService) ac.getBean("accountService");
}
@Test
public void saveAccount() {
accountService.saveAccount();
}
@Test
public void updateAccount() {
accountService.updateAccount(1);
}
@Test
public void deleteAccount() {
int i = accountService.deleteAccount();
}
}
环绕通知注解配置
/**
* 环绕通知的运行过程:
* 1、需要手动配置目标方法的执行
* 2、在目标方法执行的前后,可以自行配置各类通知
*
*ProceedingJoinPoint:可以通过该接口来执行目标方法。
* spring在监听环绕通知即将执行的时候,会将ProceedingJoinPoint的实现类通过参数注入进去,通过该实现类来执行目标方法
* 执行proceed()方法,其实就相当于在动态代理中的invoke方法。
* @param pjp
* @Around
* @return
*/
@Around("execution(* cn.yirujiwang.service.impl.*.*(..))")
public Object aroundPrintLog(ProceedingJoinPoint pjp) {
Object proceed = null;
try {
System.out.println("前置通知");
proceed = pjp.proceed();//明确的方法调用
System.out.println("后置通知");
} catch (Throwable e) {
System.out.println("异常通知");
e.printStackTrace();
}finally {
System.out.println("最终通知");
}
return proceed;
}
切入点表达式注解
/**
* 指定切入点表达式
*/
@Pointcut("execution(* com.yirujiwang.service.impl.*.*(..))")
public void pt1(){}
引用方式:
/**
* 环绕通知
* @param pjp
* @return
*/
@Around("pt1()")//注意:千万别忘了写括号
public Object around(ProceedingJoinPoint pjp) {
}
5、 纯注解的配置方式
第一步:工程搭建
复制之前的工程,重命名,然后修改pom.xml中的artifactId为当前工程名称,修改artifactId,删除其他文件,只留下src和pom.xml,然后通过idea打开工具即可。
第二步:创建配置类
@Configuration//声明这是个配置类
@ComponentScan("cn.yirujiwang")//配置包扫描
@EnableAspectJAutoProxy//开启AOP的注解扫描
public class SpringConfiguration {
}
第三步:测试
public class IAccountServiceTest {
private IAccountService accountService;
@Before
public void before(){
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
accountService = (IAccountService) ac.getBean("accountService");
}
@Test
public void saveAccount() {
accountService.saveAccount();
}
@Test
public void updateAccount() {
accountService.updateAccount(1);
}
@Test
public void deleteAccount() {
int i = accountService.deleteAccount();
}
}
三、Spring中的事务控制
1、Spring事务控制我们要明确的
第一:JavaEE体系进行分层开发,事务处理位于业务层,Spring提供了分层设计业务层的事务处理解决方案。
第二:spring框架为我们提供了一组事务控制的接口。这组接口是在spring-tx-4.3.13.RELEASE.jar中。
第三:spring的事务控制都是基于AOP的,它既可以使用编程的方式实现,也可以使用配置的方式实现。我们学习的重点是使用配置的方式实现。
2、Spring中事务控制的API介绍
(1)PlatformTransactionManager
-
PlatformTransactionManager :平台事务管理器,是Spring真正管理事务的对象,是一个接口,常用实现类有如下两个:
-
DataSourceTransactionManager :针对JDBC和mybatis事务管理
-
HibernateTransactionManager :针对Hibernate事务管理
-
涉及到的两个接口:
-
TransactionDefinition :事务定义的对象
-
事务隔离级别
-
事务传播行为
-
事务是否只读
-
事务超时信息
-
-
TransactionStatus :事务状态信息的对象
-
是否有保存点
-
是否已经完成
-
等等…..
-
Spring框架进行事务的管理,首先使用TransactionDefinition对事务进行定义。通过PlatformTransactionManager根据TransactionDefinition的定义信息进行事务的管理。在事务管理过程中产生一系列的状态:保存到TransactionStatus中。
(2)TransactionDefinition
它是事务的定义信息对象,里面有如下方法:
事务的隔离级别
事务的传播行为
事务传播行为PROPAGATION的取值:
REQUIRED 支持当前事务,如果不存在,就新建一个(默认的传播行为)
* 删除客户 删除订单, 处于同一个事务,如果 删除订单失败,删除客户也要回滚
SUPPORTS 支持当前事务,如果不存在,就不使用事务
MANDATORY 支持当前事务,如果不存在,抛出异常
REQUIRES_NEW 如果有事务存在,挂起当前事务,创建一个新的事务
* 生成订单, 发送通知邮件, 通知邮件会创建一个新的事务,如果邮件失败, 不影响订单生成
NOT_SUPPORTED 以非事务方式运行,如果有事务存在,挂起当前事务
NEVER 以非事务方式运行,如果有事务存在,抛出异常
NESTED 如果当前事务存在,则嵌套事务执行
* 依赖于 JDBC3.0 提供 SavePoint 技术
* 删除客户 删除订单, 在删除客户后, 设置SavePoint, 执行删除订单,删除订单和删除客户在同一个事务 ,删除部分订单失败, 事务回滚 SavePoint , 由用户控制是事务提交 还是 回滚
三个代表:
REQUIRED 一个事务, 要么都成功,要么都失败
REQUIRES_NEW 两个不同事务,彼此之间没有关系 一个事务失败了 不影响另一个事务
NESTED 一个事务, 在A事务 调用 B过程中, B失败了, 回滚事务到 之前SavePoint , 用户可以选择提交事务或者回滚事务
超时时间:默认值是-1,没有超时限制。如果有,以秒为单位进行设置。
是否是只读事务:建议查询时设置为只读
3、 基于XML的声明式事务控制(配置方式)重点
需求:编写service层和dao层,在service使用转账方法进行资金的转出和转入,dao层使用jdbcTemplate进行数据库的操作。
第一步:创建maven工程并导入依赖
创建工程,导入依赖:
<!--统一版本管理-->
<properties>
<spring.version>5.0.6.RELEASE</spring.version>
<junit.version>4.12</junit.version>
<mysql.version>5.1.32</mysql.version>
</properties>
<dependencies>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!--springIOC相关-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!--spring jdbc相关-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<!--aop相关-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<!--spring test-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
第二步:创建spring的配置文件并导入约束
<?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"
xmlns:tx="http://www.springframework.org/schema/tx"
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/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
</beans>
第三步:实体类
第四步:编写业务层接口和实现类
账户的业务层接口:
public interface IAccountService {
void transfer(String sourceName,String targetName,Float money);
}
账户的业务层实现类:
public class AccountServiceImpl implements IAccountService {
//注入AccountDaoImpl
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void transfer(String sourceName, String targetName, Float money) {
//通过账户名称查询账户
Account source = accountDao.queryAccountByName(sourceName);
Account target = accountDao.queryAccountByName(targetName);
//修改金额
source.setMoney(source.getMoney()-money);
target.setMoney(target.getMoney()+money);
//执行修改
accountDao.updateAccount(source);
accountDao.updateAccount(target);
}
}
第五步:编写Dao接口和实现类
持久层接口:
public interface IAccountDao {
Account queryAccountByName(String name);
void updateAccount(Account account);
}
持久层实现类:
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {
@Override
public Account queryAccountByName(String name) {
return (Account) getJdbcTemplate().queryForObject("select * from account where name = ?",new BeanPropertyRowMapper<Account>(Account.class),name);
}
@Override
public void updateAccount(Account account) {
getJdbcTemplate().update("update account set money = ? where id = ?",account.getMoney(),account.getId());
}
}
第六步:配置文件
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring
jdbc.username=root
jdbc.password=root
applicationContext.xml:
<!--加载外部资源文件-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<!--装配AccountServiceImpl-->
<bean id="accountService" class="cn.yirujiwang.service.impl.AccountServiceImpl">
<!--注入AccountDaoImpl-->
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--装配AccountDaoImpl-->
<bean id="accountDao" class="cn.yirujiwang.dao.impl.AccountDaoImpl">
<!--将DataSource注入到JdbcDaoSupport-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--装配数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driverClass}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
第七步:测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class IAccountServiceTest {
@Autowired
private IAccountService accountService;
@Test
public void transfer() {
accountService.transfer("aaa","bbb",100f);
}
}
问题:由于没有使用事务,因此如果在转账过程中如果出现异常就可能导致转账出现错误。
因此我们发现如果没有事务的增删改就很容易造成数据的错误,所有必须得使用事务。
第一步:装配事务管理器
<!-- 配置一个事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入DataSource -->
<property name="dataSource" ref="dataSource"></property>
</bean>
第二步:应用事务管理
注意:需要配置tx和aop的名称空间和约束
配置事务管理器需要tx的名称空间和约束
注意:事务管理器的id最好固定为transactionManager,因为transaction-manager="transactionManager"是默认值,如果名称相同,就可以不配置该属性,否则就需要指定id了
<!--加载外部资源文件-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<!--装配AccountServiceImpl-->
<bean id="accountService" class="cn.yirujiwang.service.impl.AccountServiceImpl">
<!--注入AccountDaoImpl-->
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--装配AccountDaoImpl-->
<bean id="accountDao" class="cn.yirujiwang.dao.impl.AccountDaoImpl">
<!--将DataSource注入到JdbcDaoSupport-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--装配数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driverClass}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--装配事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务策略-->
<tx:advice id="txAdvice">
<!-- 配置事务的属性-->
<tx:attributes>
<!--
指定对哪些方法使用事务
name:方法名称,使用通配符* 代表对所有方法使用事务
isolation:配置事务的隔离级别,默认使用当前数据库默认的隔离级别
read-only:是否只读,一般对增删改方法使用false,表示读写,对查询方法使用true,表示只读即可,默认是读写。
propagation:指定事务的传播行为,默认REQUIRED,增删改的时候使用,SUPPORTS:用于查询
no-rollback-for:指定对哪种异常不回滚
rollback-for:指定对哪种异常进行回滚
timeout:设置事务的超时时间,单位是秒,默认是-1 :永不超时。
-->
<tx:method name="*"/>
<!-- 表示只对query开头的方法使用只读事务和SUPPORTS的传播行为
相似度越高,匹配度越高。因此query*代表了所有以query开头的方法都会使用它的事务配置。
-->
<!--<tx:method name="query*" read-only="true" propagation="SUPPORTS"/>-->
</tx:attributes>
</tx:advice>
<!--配置AOP-->
<aop:config>
<!--配置切入点表达式-->
<aop:pointcut id="pt1" expression="execution(* cn.yirujiwang.service.impl.*.*(..))"></aop:pointcut>
<!--配置事务管理器应用到切入点-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>
第三步:测试
4、 基于注解的配置方式
第一步:创建maven工程并导入依赖
第二步:spring的配置文件
<!--开启注解扫描-->
<context:component-scan base-package="cn.yirujiwang"></context:component-scan>
<!--开启注解事务管理器-->
<tx:annotation-driven></tx:annotation-driven>
<!--加载外部资源文件-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<!--装配JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--装配数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driverClass}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--装配事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
第三步:业务层实现类使用注解让spring管理
@Service
@Transactional
public class AccountServiceImpl implements IAccountService {
//注入AccountDaoImpl
@Autowired
private IAccountDao accountDao;
@Override
public void transfer(String sourceName, String targetName, Float money) {
//通过账户名称查询账户
Account source = accountDao.queryAccountByName(sourceName);
Account target = accountDao.queryAccountByName(targetName);
//修改金额
source.setMoney(source.getMoney()-money);
target.setMoney(target.getMoney()+money);
//执行修改
accountDao.updateAccount(source);
int x = 1 / 0;
accountDao.updateAccount(target);
}
}
第四步:Dao实现类使用注解让spring管理
@Repository
public class AccountDaoImpl implements IAccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public Account queryAccountByName(String name) {
return jdbcTemplate.queryForObject("select * from account where name = ?",new BeanPropertyRowMapper<Account>(Account.class),name);
}
@Override
public void updateAccount(Account account) {
jdbcTemplate.update("update account set money = ? where id = ?",account.getMoney(),account.getId());
}
}
5、配置类的方式
第一步:创建maven工程并导入依赖
第二步:创建配置类
SpringConfiguration:
@Configuration
@ComponentScan("cn.yirujiwang")
@EnableTransactionManagement//声明使用注解方式的事务管理器
@Import(value = {JdbcConfig.class,TxConfig.class})
public class SpringConfiguration {
}
JdbcConfig:
@PropertySource({"classpath:jdbc.properties"})
public class JdbcConfig {
@Value("${jdbc.driverClassName}")
private String driverClassName;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
@Bean
public DataSource getDataSource(){
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}
TxConfig:
public class TxConfig {
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}
第三步:执行测试
四、Spring的监听器
我们学习java编程,最终肯定使用来编写web应用的,既然是web应用,那么最后一定是发布后有客户端发送url,服务器端接收url然后执行相应的crud的操作。现在,我们模拟web应用,客户端发送url,服务器端接收url并做相应的业务处理。
第一步:创建maven工程导入依赖
注意:创建工程时选择的打包方式为war
添加依赖:
<!--统一版本管理-->
<properties>
<spring.version>5.0.6.RELEASE</spring.version>
<junit.version>4.12</junit.version>
<servlet.version>3.1.0</servlet.version>
</properties>
<dependencies>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!--springIOC相关-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${servlet.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<configuration>
<port>8080</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
第二步:设置web结构
第三步:创建service层
Service接口:
public interface IHelloService {
void sayHello();
}
Service实现类:
public class HelloServiceImpl implements IHelloService {
private IHelloDao helloDao;
public void setHelloDao(IHelloDao helloDao) {
this.helloDao = helloDao;
}
@Override
public void sayHello() {
System.out.println("业务层的sayHello");
helloDao.sayHello();
}
}
第四步:创建dao层
Dao层接口:
public interface IHelloDao {
void sayHello();
}
Dao层实现类:
public class HelloDaoImpl implements IHelloDao {
public void sayHello() {
System.out.println("持久层的sayHello");
}
}
第五步:创建servlet
public class HelloServlet extends javax.servlet.http.HttpServlet {
protected void doPost(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, IOException {
doGet(request,response);
}
protected void doGet(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, IOException {
//初始化容器
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
IHelloService helloService = (IHelloService) ac.getBean("helloServiceImpl");
helloService.sayHello();
}
}
第六步:设置web.xml
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>cn.itcast.web.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
第七步:创建spring配置文件
<!--装配HelloServiceImpl-->
<bean id="helloService" class="cn.itcast.service.impl.HelloServiceImpl">
<!--注入AccountDaoImpl-->
<property name="helloDao" ref="helloDao"></property>
</bean>
<!--装配HelloDaoImpl-->
<bean id="helloDao" class="cn.itcast.dao.impl.HelloDaoImpl"></bean>
第八步:启动tomcat发布工程后测试
问题:
麻烦
每一个servlet都要手动初始化spring容器,然后从容器中获取service层实现类,如果有很多个servlet的话就要初始化多次spring容器。
浪费资源
每次初始化容器的时候,都创建了新的容器对象,消耗了资源,降低了性能。
效率低
Spring容器只有在客户端发送请求,请求到达服务端后才初始化spring容器,效率不高。
解决思路:保证容器只有一个。并且在应用加载的时候启动,应用卸载的时候销毁。
解决方案:让spring容器在应用加载的时候创建一次即可。spring提供了一个监听器ContextLoaderListener,该监听器会帮助我们初始化一个全局唯一的spring容器。
Spring提供了一个叫ContextLoaderListener的监听器,它位于spring-web-5.0.6.RELEASE.jar中。
需要导入依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
我们只需要在web.xml中配置这个监听器,那么当tomcat启动的时候,就会有这个监听器来帮助我们初始化spring容器了
配置监听器
<!--指定applicationContext.xml的位置-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!--配置spring监听器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>cn.itcast.web.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
修改servlet
在spring-web依赖中提供了一个工具类帮助我们获取spring容器:
WebApplicationContext ac = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
附:Spring—AOP练习源码
https://download.csdn.net/download/zhuyi2576947717/10685527