JdbcTemplate类 | 说明 |
---|---|
public JdbcTemplate(DataSource dataSource) | 创建一个模板对象,注入数据源 |
public int update(String sql, Object…args) | 增删改操作 |
查询1条记录 | |
List |
查询多条记录 |
BeanPropertyRowMapper类的构造方法 | 说明 |
---|---|
public BeanPropertyRowMapper(Class |
用于行映射的实现类 |
使用注解改造CRUD工程
@Repository:配置在DAO层
@Service:配置在业务层
@Autowired:注入对象
基于纯注解改造CRUD工程
注解 | 作用 |
---|---|
@Configuration | 表示这是一个配置类 |
@ComponentScan | 扫描基包 |
@Import | 导入其它的配置类 |
@PropertySource | 将属性文件加载到容器中 |
@Value | 注入属性值 |
@Bean | 放在方法: 1. 把方法的返回值放在容器中 2. 可以指定对象的id,如果没有指定id就是方法名字 3. 如果方法有参数,从容器中按类型匹配的方式注入 |
注解名 | 属性作用 |
---|---|
@RunWith(SpringJUnit4ClassRunner.class) @ExtendWith (JUnit5) |
作用:指定第三方运行器 在JUnit中这个注解变了 |
@ContextConfiguration | locations: 默认指定XML的配置文件 |
classes: 配置类的类型 |
<dependency>
<groupId>org.junit.jupitergroupId>
<artifactId>junit-jupiterartifactId>
<version>5.5.2version>
<scope>testscope>
dependency>
package com.itheima.test;
import com.itheima.entity.Account;
import com.itheima.service.AccountService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import java.util.List;
//指定运行器
@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:applicationContext.xml") //指定配置文件
public class TestAccount2 {
//获取业务层对象
@Autowired
AccountService accountService;
//查询所有记录
@Test
public void testFindAll() {
List<Account> accountList = accountService.findAll();
accountList.forEach(System.out::println);
}
}
@BeforeAll 修饰静态方法,在类加载的时候执行1次
@BeforeEach 在每个测试方法运行前运行
@Test 用在测试类上
@AfterEach 在每个测试方法后运行
@AfterAll 修饰静态方法,在所有测试结束以后,只执行1次
比如银行系统会有一个取款流程,传统程序的流程是这样的:
另外系统还会有一个查询余额流程
把这两个流程放到一起:
两者有一个相同的验证流程:
AOP(Aspect Oriented Programming),即面向切面编程。 (OOP:Object 面向对象编程)
有了AOP,你写代码时不需要把这个验证用户步骤写进去,即完全不考虑验证用户。只写取款和显示余额的业务代码。而在另一个地方,写好验证用户的代码。这个验证用户的代码就是切面代码,以后在执行取款和显示余额的时候,利用代理模式。将验证用户的功能在执行取款和显示余额前调用。
代码在Spring容器中执行的时候,通过配置告诉Spring你要把这段代码加到哪几个地方,Spring就会在执行正常业务流程的时候帮你把验证代码和取款代码织入到一起。
AOP真正目的是:你写代码的时候,只需考虑主流程,而不用考虑那些不重要的,但又必须要写的其它相同的代码,这些其它的相同代码所在的类就是切面类。
AOP的本质是在一系列纵向的控制流程中,把那些相同的子流程(如:验证用户)提取成一个横向的面。将分散在主流程中相同的代码提取出来,然后在程序编译或运行时,将这些提取出来的切面代码应用到需要执行的地方。
如:"取款,查询,转账"前都要进行验证用户,则验证用户就可以做成切面类。在执行"取款,查询,转账"的操作时,由Spring容器将验证用户的代码织入到它们的前面,从而达到验证用户的目的。而验证用户的代码只需要编写一次,我们也可以将编程的精力放在"取款,查询,转账"的主要业务上。
如:通过切面可以分别在类1和类2方法中加入了事务,日志,权限控制等功能。
JoinPoint(连接点):在程序执行过程中的某个阶段点,连接点就是指主业务方法的调用,它是客观存在的。
Pointcut(切入点):切入点指的是类或者方法名,满足某一规则的类或方法都是切入点,通过切入点表达式来制定规则。
Advice(通知):切入点处所要执行的程序代码,即切面类中要执行的公共方法。通知的类型有: 前置通知,后置通知,异常通知,最终通知,环绕通知。
Target(目标对象):被代理的对象。比如动态代理案例中的明星,房东。
Weaving(织入):织入指的是把新增的功能用于目标对象,创建代理对象的过程。
Proxy(代理):一个类被AOP织入增强后产生的结果类,即代理类。比如动态代理案例中的经纪人或中介
Aspect(切面):切面指的是切入点(规则)和通知(织入方法)的类 = 切入点+通知
1. 在不修改原来代码的前提下对功能进行增强
2. 程序员可以将精力放在主业务代码上
3. 减少冗余的通用代码
使用XML配置AOP完成案例
当向数据库中保存账户的时候,使用日志记录下这次保存操作
导入spring-context
导入开源的面向切面编程的组件: aspectjweaver
导入JUnit5
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.itheimagroupId>
<artifactId>day47_01_AOPartifactId>
<version>1.0-SNAPSHOTversion>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.2.0.RELEASEversion>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.8.13version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>5.2.0.RELEASEversion>
<scope>testscope>
dependency>
<dependency>
<groupId>org.junit.jupitergroupId>
<artifactId>junit-jupiterartifactId>
<version>5.5.2version>
<scope>testscope>
dependency>
dependencies>
project>
package com.itheima.service;
/**
* 账户业务接口
*/
public interface AccountService {
/**
* 保存账户
*/
void save();
}
实现业务接口类,输出"保存账户"
package com.itheima.service.impl;
import com.itheima.service.AccountService;
public class AccountServiceImpl implements AccountService {
/**
* 保存账户
*/
@Override
public void save() {
System.out.println("保存账户");
}
}
package com.itheima.utils;
import java.sql.Timestamp;
/**
* 记录日志功能的类:切面类 = 切入点(规则)+通知(方法)
*/
public class LogAspect {
/**
* 记录日志
*/
public void printLog() {
System.out.println(new Timestamp(System.currentTimeMillis()) + " 记录日志");
}
}
注:在导入aop的命名空间,idea可以自动导入
<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">
<bean class="com.itheima.service.impl.AccountServiceImpl" id="accountService"/>
<bean class="com.itheima.utils.LogAspect" id="logAspect"/>
<aop:config>
<aop:pointcut id="pt" expression="execution(public void com.itheima.service.impl.AccountServiceImpl.save())"/>
<aop:aspect ref="logAspect">
<aop:before method="printLog" pointcut-ref="pt"/>
aop:aspect>
aop:config>
beans>
package com.itheima.test;
import com.itheima.service.AccountService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
/**
* 测试类
*/
@ExtendWith(SpringExtension.class) //指定第三方运行类
@ContextConfiguration("classpath:applicationContext.xml")
public class TestAccount {
@Autowired
private AccountService accountService;
@Test
public void testSave() {
System.out.println("业务对象类型:" + accountService.getClass());
accountService.save();
}
}
业务对象类型:class com.sun.proxy.$Proxy17
2020-12-31 09:22:10.164 记录日志
保存账户
AOP的配置,本质上就是使用代理模式实现功能增强。不需要自己编写代理模式,而通过配置就可以实现。
掌握切入点表达式语法
切入点表达式的作用:一组规则,指定哪些类和方法要被切入
切入点函数 | 作用 |
---|---|
execution | 细粒度函数,精确到方法 |
within | 粗粒度,只能精确到类 |
bean | 粗粒度,精确到类,从容器中通过id获取对象 |
() 没有参数
(*) 1个或多个参数
(..) 0个或多个参数
类全名的包通配符写法:
.. 表示当前包和子包
execution(public void com.itheima.service.impl.AccountServiceImpl.save())
最精确的写法
execution(* com.itheima.service..*.*(String))
service的包和子包下面所有的类和方法,方法参数是String类型
execution(* *(..))
匹配所有的类和方法
execution(* save(..)) || execution(* update(..))
匹配方法名是save或update的方法
也可以使用&&符号,虽然语法是正确的,但没有意义
!execution(* save(..))
除了方法名是save的所有方法
within(com.itheima..*)
匹配包和子包中所有的类
bean(accountService)
从容器中获取一个id为accountService的类中所有方法
bean(*Service)
从容器中获取所有Service的方法
修改AccountService接口,创建方法int update(String name),并且实现方法。
AccountService接口
package com.itheima.service;
/**
* 账户业务类
*/
/**
* 账户业务接口
*/
public interface AccountService {
/**
* 保存账户
*/
void save();
/**
* 更新账户
* @param name
* @return
*/
int update(String name);
}
AccountServiceImpl实现类
package com.itheima.service.impl;
import com.itheima.service.AccountService;
public class AccountServiceImpl implements AccountService {
/**
* 保存账户
*/
@Override
public void save() {
System.out.println("保存账户");
}
/**
* 更新账户
* @param name
* @return
*/
@Override
public int update(String name) {
System.out.println("更新了" + name + "的账户");
return 1;
}
}
LogAspect类,输出:“现在时间是:xxx” (没变)
修改测试类,同时调用save()和update()方法
package com.itheima.test;
import com.itheima.service.AccountService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
/**
* 测试类
*/
@ExtendWith(SpringExtension.class) //指定第三方运行类
@ContextConfiguration("classpath:applicationContext.xml")
public class TestAccount {
@Autowired
private AccountService accountService;
@Test
public void testSave() {
System.out.println("业务对象类型:" + accountService.getClass());
accountService.save();
accountService.update("小乔");
}
}
分别修改applicationContext.xml中切入点表达式,执行不同的效果
切入点表达式
execution(* com.itheima.service.impl.*.*(..))
execution(* com.itheima.service..*.*(String))
execution(* update*(..))
execution(* save(..)) || execution(* update(..))
!execution(* save(..))
within(com.itheima..*)
bean(accountService)
applicationContext.xml代码
<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">
<bean class="com.itheima.service.impl.AccountServiceImpl" id="accountService"/>
<bean class="com.itheima.utils.LogAspect" id="logAspect"/>
<aop:config>
<aop:pointcut id="pt" expression="bean(accountService) "/>
<aop:aspect ref="logAspect">
<aop:before method="printLog" pointcut-ref="pt"/>
aop:aspect>
aop:config>
beans>
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
访问修饰符 返回类型 包名.类名.方法名(参数类型) 抛出异常类型
必须的参数是哪三个?
返回类型
方法名
参数类型
以下切点函数的作用:
指示符 | 作用 |
---|---|
execution | 细粒度,精确到方法 |
within | 粗粒度,精确到类 |
bean | 粗粒度,精确到类(从容器中获取) |
通过案例学习前四种通知的使用
修改LogAspect中的方法
public void before()
public void afterReturning()
public void afterThrowing()
public void after()
修改AccountService的int update(String name)方法
如果账户名为NewBoy,则抛出运行时异常:“余额不足”
修改applicationContext.xml
注:如果after标签放在afterReturning标签之前,则after会先执行
package com.itheima.utils;
import java.sql.Timestamp;
/**
* 记录日志功能的类:切面类 = 切入点(规则)+通知(方法)
*/
public class LogAspect {
public void before() {
System.out.println("---前置通知---");
}
public void afterReturning() {
System.out.println("---后置通知---");
}
public void afterThrowing() {
System.out.println("---异常通知---");
}
public void after() {
System.out.println("---最终通知---");
}
}
<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">
<bean class="com.itheima.service.impl.AccountServiceImpl" id="accountService"/>
<bean class="com.itheima.utils.LogAspect" id="logAspect"/>
<aop:config>
<aop:pointcut id="pt" expression="execution(* update(..))"/>
<aop:aspect ref="logAspect">
<aop:before method="before" pointcut-ref="pt"/>
<aop:after-returning method="afterReturning" pointcut-ref="pt"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="pt"/>
<aop:after method="after" pointcut-ref="pt"/>
aop:aspect>
aop:config>
beans>
package com.itheima.service.impl;
import com.itheima.service.AccountService;
public class AccountServiceImpl implements AccountService {
/**
* 保存账户
*/
@Override
public void save() {
System.out.println("保存账户");
}
/**
* 更新账户
* @param name
* @return
*/
@Override
public int update(String name) {
System.out.println("更新了" + name + "的账户");
if ("newboy".equals(name)) {
throw new RuntimeException("这是个穷人");
}
return 1;
}
}
package com.itheima.test;
import com.itheima.service.AccountService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
/**
* 测试类
*/
@ExtendWith(SpringExtension.class) //指定第三方运行类
@ContextConfiguration("classpath:applicationContext.xml")
public class TestAccount {
@Autowired
private AccountService accountService;
@Test
public void testSave() {
accountService.update("孙悟空");
}
}
标签 | 通知类型 | 属性 |
---|---|---|
前置通知 | method: 指定切面中方法名字 pointcut-ref: 指定切入点表达式的id |
|
后置通知 | ||
异常通知 | ||
最终通知 | ||
环绕通知 |
Spring框架提供了ProceedingJoinPoint接口,作为环绕通知的参数。在环绕通知执行的时候,Spring框架会提供接口的对象,我们直接使用即可。
ProceedingJoinPoint接口中方法 | 功能 |
---|---|
Object[] getArgs() | 获取目标方法的参数 |
proceed(Object[] args) | 调用目标方法,如果没有执行这句话,目标方法不会执行 |
proceed() | 调用目标方法,使用它原来的参数 |
getSignature() | 获取目标方法其它的信息,如:类名,方法名等 |
删除update方法
修改接口AccountService中的save方法
package com.itheima.service;
/**
* 账户业务类
*/
/**
* 账户业务接口
*/
public interface AccountService {
/**
* 保存账户
*/
int save(int id, String name, double money);
}
修改实现类AccountServiceImpl
package com.itheima.service.impl;
import com.itheima.service.AccountService;
public class AccountServiceImpl implements AccountService {
/**
* 保存账户
*/
@Override
public void save() {
System.out.println("保存账户");
}
/**
* 更新账户
* @param name
* @return
*/
@Override
public int update(String name) {
System.out.println("更新了" + name + "的账户");
if ("newboy".equals(name)) {
throw new RuntimeException("这是个穷人");
}
return 1;
}
}
在通知类中编写环绕通知方法:
Object around(ProceedingJoinPoint point) 在方法中:
环绕通知方法中输出
package com.itheima.utils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import java.util.Arrays;
/**
* 记录日志功能的类:切面类 = 切入点(规则)+通知(方法)
*/
public class LogAspect {
/**
* 环绕通知方法
* @param joinPoint 接口,由Spring注入对象
* @return 方法的返回值
*/
public Object around(ProceedingJoinPoint joinPoint) {
//获取目标方法签名对象
Signature signature = joinPoint.getSignature();
System.out.println("目标方法名字:" + signature.getName());
//获取参数的数组
Object[] args = joinPoint.getArgs();
System.out.println("目标方法参数:" + Arrays.toString(args));
System.out.println("目标对象实现接口的全名:" + signature.getDeclaringTypeName());
Object result = null;
//修改目标方法参数
args[0] = "白骨精";
try {
System.out.println("前置通知");
//如果修改了参数,要使用带参数的方法。(如果不执行这句话,目标方法不会执行)
result = joinPoint.proceed(args); //直接调用目标方法
System.out.println("后置通知");
} catch (Throwable throwable) {
System.out.println("异常通知");
} finally {
System.out.println("最终通知");
}
return 99;
}
}
修改方法输入参数和返回值
applicationContext.xml 配置环绕通知的配置
<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">
<bean class="com.itheima.service.impl.AccountServiceImpl" id="accountService"/>
<bean class="com.itheima.utils.LogAspect" id="logAspect"/>
<aop:config>
<aop:pointcut id="pt" expression="execution(* update(..))"/>
<aop:aspect ref="logAspect">
<aop:around method="around" pointcut-ref="pt"/>
aop:aspect>
aop:config>
beans>
package com.itheima.test;
import com.itheima.service.AccountService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
/**
* 测试类
*/
@ExtendWith(SpringExtension.class) //指定第三方运行类
@ContextConfiguration("classpath:applicationContext.xml")
public class TestAccount {
@Autowired
private AccountService accountService;
@Test
public void testSave() {
int row = accountService.update("孙悟空");
System.out.println("返回值:" + row);
}
}
环绕通知和其他通知的区别是什么?
代替其它四个通知
可以修改方法的参数
可以修改方法的返回值
可以拦截目标方法不执行
使用注解的方式实现AOP的编程
基于XML声明式的AOP需要在配置文件中配置不少信息。为了解决这个问题,AspectJ框架为AOP的实现提供了一套注解,用以取代applicationContext.xml文件中配置代码。
注解 | 说明 |
---|---|
@Aspect | 用在类上,表示这是一个切面类。 这个切面类要放到IoC容器中,所以类上还要加@Component注解 |
@Before | 用在方法上,表示这是一个前置通知 value:指定切入点表达式 |
@AfterReturning | 用在方法上,表示这是一个后置通知 |
@AfterThrowing | 用在方法上,表示这是一个异常通知 |
@After | 用在方法上,表示这是一个最终通知。注:最终通知在后置通知之前执行 |
@Around | 用在方法上,表示这是一个环绕通知 |
@Pointcut | 用在方法上,用来定义切入点表达式 方法名:随意起 返回值:void 方法体:为空 |
作用:开启Spring中自动代理,注解的方式来使用AOP
作用 | |
---|---|
proxy-target-class | true 使用CGLIB进行代理 |
false 默认使用JDK代理,如果有接口才起作用 |
AccountServiceImpl,使用@Service注解的方式,指定名字为accountService
package com.itheima.service.impl;
import com.itheima.service.AccountService;
import org.springframework.stereotype.Service;
@Service
public class AccountServiceImpl implements AccountService {
/**
* 保存账户
*/
@Override
public void save() {
System.out.println("保存账户");
}
/**
* 更新账户
* @param name
* @return
*/
@Override
public int update(String name) {
System.out.println("更新了" + name + "的账户");
if ("newboy".equals(name)) {
throw new RuntimeException("这是个穷人");
}
return 1;
}
}
applicationContext.xml 配置XML文件
<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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.itheima"/>
<aop:aspectj-autoproxy/>
beans>
使用@Pointcut注解优化切点表达式
修改切面类,使用注解
切面类使用@Aspect和@Component
编写前置通知方法,使用注解:@Before
编写后置通知,使用注解:@AfterReturning
编写异常通知,@AfterThrowing
编写最终通知,使用注解:@After
注:最终通知会在后置通知前面执行,但如果出现异常则先执行异常通知
package com.itheima.utils;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* 记录日志功能的类:切面类 = 切入点(规则)+通知(方法)
*/
@Component
@Aspect //这是一个切面类
public class LogAspect {
//定义切入点函数
@Pointcut("execution(* com.itheima.service..*(..))")
public void pt() {
}
@Before("pt()") //写方法的名字
public void before() {
System.out.println("---前置通知---");
}
@AfterReturning("pt()")
public void afterReturning() {
System.out.println("---后置通知---");
}
@AfterThrowing("pt()")
public void afterThrowing() {
System.out.println("---异常通知---");
}
@After("pt()")
public void after() {
System.out.println("---最终通知---");
}
}
package com.itheima.test;
import com.itheima.service.AccountService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
/**
* 测试类
*/
@ExtendWith(SpringExtension.class) //指定第三方运行类
@ContextConfiguration("classpath:applicationContext.xml")
public class TestAccount {
@Autowired
private AccountService accountService;
@Test
public void testSave() {
accountService.save();
}
@Test
public void testUpdate() {
accountService.update("张三");
}
}
正常执行输出后置通知
---前置通知---
更新了张三的账户
---最终通知---
---后置通知---
出现异常输出异常信息
---前置通知---
更新了newboy的账户
---异常通知---
---后置通知---
相关的注解:
注解 | 说明 |
---|---|
@Component | 将对象放在容器中 |
@Aspect | 放在切面类上,表示这是一个切面类 |
@Pointcut | 放在方法上,表示这是一个切入点表达式 |
注:最终通知会在后置通知前面执行
使用AOP注解的方式实现环绕通知
调用目标方法,并且输出方法的参数
将
正常执行效果
目标方法名字:update
目标方法参数:[张三]
目标对象实现接口的全名:com.itheima.service.impl.AccountServiceImpl
前置通知
更新了白骨精的账户
后置通知
最终通知
class com.itheima.service.impl.AccountServiceImpl$$EnhancerBySpringCGLIB$$c29d8113
与上面是相同的
/**
* 环绕通知方法
* @param joinPoint 接口,由Spring注入对象
* @return 方法的返回值
*/
@Around("pt()")
public Object around(ProceedingJoinPoint joinPoint) {
//获取目标方法签名对象
Signature signature = joinPoint.getSignature();
System.out.println("目标方法名字:" + signature.getName());
//获取参数的数组
Object[] args = joinPoint.getArgs();
System.out.println("目标方法参数:" + Arrays.toString(args));
System.out.println("目标对象实现接口的全名:" + signature.getDeclaringTypeName());
Object result = null;
//修改目标方法参数
args[0] = "白骨精";
try {
System.out.println("前置通知");
//如果修改了参数,要使用带参数的方法。(如果不执行这句话,目标方法不会执行)
result = joinPoint.proceed(args); //直接调用目标方法
System.out.println("后置通知");
} catch (Throwable throwable) {
System.out.println("异常通知");
} finally {
System.out.println("最终通知");
}
return 99;
}
说说以下注解的作用:
注解 | 说明 |
---|---|
@Around | 用在方法上,表示这是环绕通知 |
事务以后我们主要用在业务层
业务层中一个方法会多次调用DAO层中增删改,所有的方法必须全部执行成功,如果有一个方法执行失败,就要进行回滚。要么全部成功,要么全部失败。
事务特性 | 说明 |
---|---|
原子性(Automicity) | 每个事务是一个最小的执行单元,它做为一个整体运行,不可再拆分 |
一致性(Consistency) | 事务执行前和执行后对数据库的状态影响是一致的 如:转账前和转账后的总金额是一致的 |
隔离性(Isolation) | 事务与事务之间不能相互影响,它们之间是相互隔离的 |
持久性(Durability) | 事务提交后对数据库的影响是永久的,关机以后也是存在的 |
什么是声明式事务
事务是切面,主业务类还是以前的方法
编程式事务管理:
通过编写代码实现的事务管理,包括定义事务的开始,正常执行后事务提交和异常时的事务回滚。
声明式事务管理:
通过AOP技术实现事务管理,主要思想是将事务管理作为一个"切面"代码单独编写,然后通过AOP技术将事务管理的"切面"代码织入到业务目标类中。
优点在于开发者无须通过编程的方式来管理事务,只需在配置文件中进行相关的事务规则声明,就可以将事务规则应用到业务逻辑中。
将下面的这些事务处理代码通过配置的方式来实现
这些接口和方法了解就行,编程的时候不会用到
功能:事务的顶层接口,定义了:提交事务、获取事务、回滚事务的方法。
该接口提供了我们操作事务的常用方法,方法不需要我们主动去调用,由Spring容器去调用。
org.springframework.jdbc.datasource.DataSourceTransactionManager作用:支持使用spring jdbc 或者 Mybatis框架的事务管理器。
功能:定义了事务的隔离级别、传播行为、超时时间等。
事务指定一个隔离级别,该隔离级别定义一个事务必须与由其他事务进行的资源或数据更改相隔离的程度。隔离级别从允许的并发副作用(例如,脏读 或 幻读)的角度进行描述。
什么叫事务传播行为?即然是传播,那么至少有两个东西,才可以发生传播。单体不存在传播这个行为。
事务传播行为(propagation behavior)指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。
例如:methodA()事务方法调用methodB()事务方法时,methodB()是继续在调用者methodA()的事务中运行呢,还是为自己开启一个新事务运行,这就是由methodB()的事务传播行为决定的。
如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
methodB();
// do something
}
@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
// do something
}
psvm(){
methodA()
}
如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
methodB();
// do something
}
// 事务属性为SUPPORTS
@Transactional(propagation = Propagation.SUPPORTS)
public void methodB() {
// do something
}
提供了获取事务状态的方法,事务是否是新的,事务是否完成,事务是否是只读等
快速找一个类:ctrl+shift+alt+n
显示类的继承结构:ctrl+h
显示类的成员:ctrl+f12
说说每个类的功能:
使用声明式事务实现账户转账的功能:环境搭建
用户A要给用户B转账,使用Spring IoC+JdbcTemplate实现
转账需要执行几条SQL语句?
-- Jack给Rose转账
-- Jack扣钱
UPDATE account SET money = money - 200 WHERE `name`='Jack';
-- Rose加钱
UPDATE account SET money = money + 200 WHERE `name`='Rose';
pom.xml文件中导入jar包
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.2.0.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-txartifactId>
<version>5.2.0.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jdbcartifactId>
<version>5.2.0.RELEASEversion>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.8.7version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.10version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.30version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>5.2.0.RELEASEversion>
dependency>
dependencies>
创建表
drop table if exists `account`;
CREATE TABLE `account` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(10) DEFAULT NULL,
`money` double DEFAULT NULL,
PRIMARY KEY (`id`)
);
insert into account values(null, 'Jack', 1000);
insert into account values(null, 'Rose', 1000);
select * from account;
实体类Account,属性类型可以使用包装类
package com.itheima.entity;
public class Account {
private Integer id;
private String name;
private Double money;
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
}
复制类库下的druid.properties配置文件到resources目录下
jdbc.url=jdbc:mysql://localhost:3306/day47?characterEncoding=utf8
jdbc.username=root
jdbc.password=root
jdbc.driverClassName=com.mysql.jdbc.Driver
applicationContext.xml
<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:component-scan base-package="com.itheima"/>
<context:property-placeholder location="classpath:druid.properties"/>
<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="url" value="${jdbc.url}"/>
<property name="driverClassName" value="${jdbc.driverClassName}"/>
bean>
<bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
<property name="dataSource" ref="dataSource"/>
bean>
beans>
SQL语句:分别正常执行测试和出现异常执行,并且还原数据库的数据
-- 更新账户的余额
update account set money=money + ? where name=?
-- 还原金额
update account set money=1000;
select * from account;
AccountDao接口和实现类
package com.itheima.dao;
public interface AccountDao {
/**
* 更新账户余额的方法
*/
void updateAccount(String name, double money);
}
实现类
package com.itheima.dao.impl;
import com.itheima.dao.AccountDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class AccountDaoImpl implements AccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 更新账户余额的方法
* @param name
* @param money
*/
@Override
public void updateAccount(String name, double money) {
jdbcTemplate.update("UPDATE account SET money=money + ? WHERE `name`=?", money, name);
}
}
AccountService接口和实现类
package com.itheima.service;
public interface AccountService {
/**
* 实现转账的功能
* @param fromUser 转出账户
* @param toUser 转入账户
* @param money 金额
*/
void transfer(String fromUser, String toUser, double money);
}
实现类
package com.itheima.service.impl;
import com.itheima.dao.AccountDao;
import com.itheima.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
/**
* 实现转账的功能
* @param fromUser 转出账户
* @param toUser 转入账户
* @param money 金额
*/
@Override
public void transfer(String fromUser, String toUser, double money) {
//扣钱
accountDao.updateAccount(fromUser, -money);
//加钱
accountDao.updateAccount(toUser, money);
System.out.println("转账成功");
}
}
测试类
package com.itheima.test;
import com.itheima.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestAccount {
@Autowired
private AccountService accountService;
@Test
public void testTransfer() {
accountService.transfer("Jack","Rose",200);
}
}
如果没有事务控制,转账业务出现异常会怎么样?
钱被扣了,收款人没有收到钱,所以要使用事务
声明式事务的配置
使用之前要需要导入tx命名空间,注:不要选错了,事务的空间,而不是缓存
功能:事务通知配置的父元素
属性 | 说明 |
---|---|
id | 事务通知配置的标识 |
transaction-manager | 从容器中获取事务管理器对象,前提:在IoC中要存在一个事务管理器对象 |
功能:tx:method标签的父标签,指定不同方法事务的属性
<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:tx="http://www.springframework.org/schema/tx" 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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.itheima"/>
<context:property-placeholder location="classpath:druid.properties"/>
<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="url" value="${jdbc.url}"/>
<property name="driverClassName" value="${jdbc.driverClassName}"/>
bean>
<bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
<property name="dataSource" ref="dataSource"/>
bean>
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="dataSource"/>
bean>
<tx:advice id="interceptor" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="transfer" read-only="false" propagation="REQUIRED" isolation="DEFAULT"/>
<tx:method name="find*" read-only="true" propagation="SUPPORTS"/>
tx:attributes>
tx:advice>
<aop:config>
<aop:pointcut id="pt" expression="execution(* com.itheima.service..*.*(..))"/>
<aop:advisor advice-ref="interceptor" pointcut-ref="pt"/>
aop:config>
beans>
package com.itheima.service.impl;
import com.itheima.dao.AccountDao;
import com.itheima.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
/**
* 实现转账的功能
* @param fromUser 转出账户
* @param toUser 转入账户
* @param money 金额
*/
@Override
public void transfer(String fromUser, String toUser, double money) {
//扣钱
accountDao.updateAccount(fromUser, -money);
//模拟出现异常
//System.out.println(1 / 0);
//加钱
accountDao.updateAccount(toUser, money);
System.out.println("转账成功");
}
}
声明式事务本质上就是AOP应用,将事务代码定义成切面,我们只需要在配置文件中配置这个切面
以及方法使用事务的规则
注解实现声明式事务的API
声明式事务的不足:
在XML中需要比较多的配置,如何简化呢?使用注解
需要去掉XML中的配置
有关事务通知的配置
AOP的配置
在配置文件中:配置事务管理器,注入数据源。(已经做了)
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="dataSource"/>
bean>
在配置文件中配置注解式事务驱动:
<tx:annotation-driven transaction-manager="transactionManager"/>
在业务代码中类或方法上使用注解:@Transactional
package com.itheima.service.impl;
import com.itheima.dao.AccountDao;
import com.itheima.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
/**
* 实现转账的功能
* @param fromUser 转出账户
* @param toUser 转入账户
* @param money 金额
*/
@Override
@Transactional(noRollbackFor = ArithmeticException.class, propagation = Propagation.REQUIRED)
public void transfer(String fromUser, String toUser, double money) {
//扣钱
accountDao.updateAccount(fromUser, -money);
//模拟出现异常
//System.out.println(1 / 0);
//加钱
accountDao.updateAccount(toUser, money);
System.out.println("转账成功");
}
}
## @Transactional
#### 作用
放在类或方法上,让这个方法使用事务
#### 注解的应用范围
1. 类上面:表示这个类中所有的方法都使用事务
2. 方法上面:表示这个方法使用事务
3. 接口:表示只要实现这个接口的所有子类中所有方法都使用事务
4. 接口中方法:表示实现这个接口的子类中这个方法使用事务
#### 注解的参数和作用
| 参数名称 | 描述 |
| ------------- | ------------------------------------------------ |
| propagation | 传播行为 |
| isolation | 隔离级别 |
| readOnly | 是否只读 |
| timeout | 超时时间,默认是-1,表示不超时 |
| rollbackFor | 哪些异常会进行回滚,默认只对非运行时异常进行回滚 |
| noRollbackFor | 哪些异常不进行回滚 |
### 代码
```java
package com.itheima.service.impl;
import com.itheima.dao.AccountDao;
import com.itheima.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
/**
* 实现转账的功能
* @param fromUser 转出账户
* @param toUser 转入账户
* @param money 金额
*/
@Override
@Transactional(noRollbackFor = ArithmeticException.class, propagation = Propagation.REQUIRED)
public void transfer(String fromUser, String toUser, double money) {
//扣钱
accountDao.updateAccount(fromUser, -money);
//模拟出现异常
//System.out.println(1 / 0);
//加钱
accountDao.updateAccount(toUser, money);
System.out.println("转账成功");
}
}
类,方法,接口,接口中方法
配置事务管理器,注入数据源
开启注解事务驱动
在方法或类上使用@Transactional
所有配置文件都要删除,使用相应的注解代替,applicationContext.xml配置如下:
注解扫描基包
加载外部的配置文件druid.properties
创建数据源DruidDataSource
创建JdbcTemplate
创建事务管理器DataSourceTransactionManager
开启事务管理器的注解
功能:开启Spring声明式事务管理的注解支持
相当于:
通过构造方法创建事务管理器,参数:传入数据源
public DataSourceTransactionManager(DataSource dataSource)
编写JdbcConfig,封装数据库连接相关配置
说说下面注解的功能:
注解 | 功能 |
---|---|
@PropertySource | 加载属性文件 |
@Value | 注入属性值 |
@Bean | 把方法的返回值放到容器中 |
@Import | 导入另一个配置文件 |
@EnableTransactionManagement | 开启事务注解的支持 |
理解AOP相关概念和术语
理解AspectJ表达式语言
切点函数 | 作用 |
---|---|
execution | 细粒度,精确到方法 |
within | 精确到类 |
bean | 精确到类,从容器中获取 |
方法:
() 没有参数
(*) 1个或多个参数
(..) 0个或多个参数
类全名的包
.. 表示当前包和子包
掌握AOP的注解实现
注解 | 说明 |
---|---|
@Aspect | 放在类上表示这是一个切面类 |
@Before | 放在方法上:前置通知 |
@AfterReturning | 后置通知 |
@AfterThrowing | 异常通知 |
@After | 最终通知 |
@Around | 环绕通知 |
@Pointcut | 定义切入点表达式 |
能够理解事务的传播行为
1. 配置事务管理器
2. 配置注解的驱动` `
3. 在类或方法上使用@Transactional
Memorial Day is 513 days |