0. IOC思想(DI)
1. 关键注解
@Repository
public class DeptDaoImpl1 implements DeptDao{}
@Repository
@Primary
public class DeptDaoImpl2 implements DeptDao{}
@Service
public class DeptServiceImpl implements DeptService{
@Autowired
@Qulifier("deptDaoImpl2")
private DeptDao deptDao;
}
2. 创建对象
自定义对象:@Repository @Service @Controller @Component
第三方对象:@Bean @Configuration
3. 对象作用域@Scope
单例:只有一个, 默认容器启动,对象创建(@Lazy延迟到第一次获取)
多例:每次获取,创建一个新的
4. 单元测试
@RunWith(运行器)
@ContextConfiguration(classes=)
日志功能
需求:在业务层类中的方法中打印日志,记录
方法执行前后
以及方法发生异常
的时间点
基本方式实现
导入初始工程
添加日志功能
修改DeptServiceImpl,添加日志功能
测试
分析代码问题
目前代码存在两个问题
- 代码耦合性高:业务代码和日志代码耦合在了一起
- 代码复用性低:日志代码在每个方法都要书写一遍
jdk动态代理
复制工程
准备目标类
目标类指的是要被代理的类
准备增强类
增强类指的是要给被代理类添加的功能
创建代理对象
在测试类中,使用JDK技术创建代理对象,然后调用方法
cglib动态代理
CGLIB(Code Generation )动态代理利用ASM字节码操作框架在运行时生成代理类,它通过继承的方式代理目标对象,因此可以代理没有实现接口的类。
复制工程
删除接口
删除DeptService接口,并删除实现类中对接口的实现
创建代理对象
package com.itheima.test;
import com.itheima.config.SpringConfig;
import com.itheima.log.Logger;
import com.itheima.service.impl.DeptServiceImpl;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.InvocationHandler;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.lang.reflect.Method;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class DeptServiceTest {
@Autowired
private DeptServiceImpl deptServiceImpl;
@Autowired
private Logger logger;
@Test
public void test1() {
//1. 准备目标对象(deptService就是)
//2. 编写增强逻辑
//注意: 这个InvocationHandler是org.springframework.cglib.proxy.InvocationHandler提供的
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object res = null;
logger.m1();
try {
res = method.invoke(deptServiceImpl,args);
}catch (Exception e){
logger.m3();
}
logger.m2();
return res;
}
};
//3. 创建代理对象
DeptServiceImpl proxyInstance = (DeptServiceImpl) Enhancer.create(DeptServiceImpl.class, invocationHandler);
//4. 调用代理对象的方法
proxyInstance.findAll();
}
}
小结
首先明确在创建代理实现类时,jdk的速度要高于cglib,所以选择的时候:
当被代理类有接口的时候,使用jdk动态代理;当被代理类没有接口的时候,使用cglib动态代理
不同点:
总结
当核心业务(保存)和增强业务(日志)同时出现时,我们可以在开发时对他们分别开发,运行时再组装在一起(使用动态代理的方式)。
这样做的好处是:
- 逻辑清晰:开发核心业务的时候,不必关注增强业务的代码
- 代码复用性高:增强代码不用重复书写
这就是一种 AOP ( 面向切面编程 ) 的思想,它的目的就是在不修改源代码的基础上,对原有功能进行增强。
我的总结: 开发阶段分别开发,运行阶段组装运行
AOP
AOP介绍
AOP( 面向切面编程 )是一种思想,它的目的就是在不修改源代码的基础上,对原有功能进行增强。
SpringAOP是对AOP思想的一种实现,Spring底层同时支持jdk和cglib动态代理。
Spring会根据被代理的类是否有接口自动选择代理方式:
- 如果有接口,就采用jdk动态代理
- 如果没接口,就采用cglib的方式
在AOP中有一些核心概念,需要大家了解
* 目标对象(Target)
被代理的对象
* 连接点(JoinPoint)
目标对象中得所有方法
* 切入点(PointCut)
目标对象中得要进行功能增强那部分方法
* 增强 (Advice 通知)
一个具体增强功能(增强对象 增强方法)
* 切面 (Aspect)
切面是一种描述,描述的是: 哪个增强方法 加入到了 哪个切点 的 什么位置
增强方法和切点方法的执行顺序
入门案例
使用SpringAop完成在业务层类中的方法上打印日志
创建模块,导入依赖
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.26</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.6</version>
</dependency>
<!--切点表达式解析坐标-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
<!--测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.6</version>
</dependency>
</dependencies>
创建实体类
创建业务层接口和实现类
创建日志类
配置切面
我们需要把切面相关的内容配置在这种增强类中
创建配置类
单元测试
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfig.class)
通知类型
四大通知
四大通知描述的就是增强方法在切点方法的什么位置上执行
- 前置通知(before):增强方法在切点运行之前执行 - 返回后通知(after-returning):增强方法在某切点正常完成后执行的通知,不包括抛出异常的情况 - 异常后通知(after-throwing):增强方法在某切点抛出异常退出时执行的通知 - 后置通知(after):增强方法在某切点退出的时候执行的通知(不论是正常返回还是异常退出)
try{ 前置通知(before) //切点执行位置 返回后通知(after-returning) }catch(Execption e){ 异常后通知(after-throwing) }finally{ 后置通知(after) }
① 添加通知方法
② 测试
环绕通知
它是一种特殊的通知,他允许以编码的形式实现四大通知
① 添加通知方法
② 测试
切点表达式
切点表达式用于挑选切点
execution
execution() :指定一组规则来匹配某些类中的方法,匹配中的就是切点
* 代表一个或多个位置 .. 代表0个或多个位置
@annotation
@annotation:指定一个注解,凡是标有此注解的方法都是切点
① 自定义注解
② 在需要作为切点的方法上添加注解
③ 设置切点表达式
④ 测试
记录日志详情
事务管理
事务回顾
事务是一组操作的集合,它是一个不可分割的工作单位,这些操作 要么同时成功,要么同时失败。
开启事务(一组操作开始前,开启事务):start transaction / begin ; 提交事务(这组操作全部成功后,提交事务):commit ; 回滚事务(中间任何一个操作出现异常,回滚事务):rollback ;
转账案例
添加账户表
-- 创建账户表,并且添加两条测试数据
create table account (
id int primary key auto_increment,
name varchar(32),
balance float
);
insert into account (name, balance) VALUES ('A', 1000), ('B', 1000);
导入工程
添加持久层代码
在Mapper接口中添加加钱和减钱的方法
添加业务层代码
在Service接口和实现类中添加转账方法
测试
在测试类中添加转账方法
事务管理
注解:@Transactional
位置:业务(service)层的方法上、类上、接口上
作用:将当前方法交给spring进行事务管理,方法执行前,开启事务;成功执行完毕,提交事务;出现异常,回滚事务
配置事务管理器
在配置类中配置事务管理器,并开启声明式事务
添加事务注解
在转账方法上添加@Transactional注解,注意方法中要模拟异常
测试
在测试方法中对转账方法进行测试
事务属性
rollbackFor
默认情况下,只有出现 RuntimeException 才回滚异常,rollbackFor属性用于控制让非运行时异常也回滚。
propagation
propagation称为事务传播行为,表示当一个事务方法被另一个事务方法调用时,应该如何进行事务控制
Spring支持通过配置的形式来实现7种事务传播行为,我们需要掌握其中的前两种
属性值 | 含义 |
---|---|
REQUIRED | 【默认值】需要事务,有则加入,无则创建新事务 |
REQUIRES_NEW | 需要新事务,无论有无,总是创建新事务 |
SUPPORTS | 支持事务,有则加入,无则在无事务状态中运行 |
NOT_SUPPORTED | 不支持事务,在无事务状态下运行,如果当前存在已有事务,则挂起当前事务 |
MANDATORY | 必须有事务,否则抛异常 |
NEVER | 必须没事务,否则抛异常 |
NESTED | 如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务, 则开启一个新的事务。 内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。 而内层事务操作失败并不会引起外层事务的回滚。 |
建新事务 |
| REQUIRES_NEW | 需要新事务,无论有无,总是创建新事务 |
| SUPPORTS | 支持事务,有则加入,无则在无事务状态中运行 |
| NOT_SUPPORTED | 不支持事务,在无事务状态下运行,如果当前存在已有事务,则挂起当前事务 |
| MANDATORY | 必须有事务,否则抛异常 |
| NEVER | 必须没事务,否则抛异常 |
| NESTED | 如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务, 则开启一个新的事务。
内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。
而内层事务操作失败并不会引起外层事务的回滚。 |