文章目录
AOP( 面向切面编程)
1. AOP介绍
1.1 简介
AOP( 面向切面编程 )是一种思想,它的目的就是在不修改源代码的基础上,对原有功能进行增强。
编码时分开编写,运行时植入(weaving)切面有关的代码,这就叫做面向切面编程AOP
SpringAOP是对AOP思想的一种实现,Spring底层同时支持jdk和cglib动态代理。
Spring会根据被代理的类是否有接口自动选择代理方式:
- 如果有接口,就采用jdk动态代理
- 如果没接口,就采用cglib的方式
1.2 核心概念
* 目标对象(Target)
被代理的对象
* 连接点(JoinPoint)
目标对象中得所有方法
* 切入点(PointCut)
目标对象中得要进行功能增强那部分方法
* 增强 (Advice 通知)
一个具体增强功能(增强对象 增强方法)
* 切面 (Aspect)
切面是一种描述,描述的是: 哪个增强方法 加入到了 哪个切点 的 什么位置
增强方法和切点方法的执行顺序
2. 入门案例
2.1 创建模块,导入依赖
<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>
2.2 创建实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Dept {
private Integer id;
private String name;
}
2.3 创建业务层接口和实现类
接口
public interface DeptService {
//列表查询
List<Dept> findAll();
//保存
void save(Dept dept);
//主键查询
Dept findById(Integer id);
}
实现类
@Service
public class DeptServiceImpl implements DeptService {
@Override
public List<Dept> findAll() {
System.out.println("findAll");
return new ArrayList<>();
}
@Override
public void save(Dept dept) {
System.out.println("save");
}
@Override
public Dept findById(Integer id) {
System.out.println("findById");
return new Dept(1,"开发部");
}
}
2.4 创建日志类
@Component
public class Logger {
public void m1(){
System.out.println("即将进入方法");
}
}
2.5 配置切面
@Aspect //切面注解,需要标注在增强类上
@Component
public class Logger {
//切点表达式:作用就是定位出切点(findAll)
@Pointcut("execution(java.util.List com.itheima.service.DeptService.findAll())")
public void pt(){}
@Before("pt()")//指定被标注的方法要在切点之前执行
public void m1(){
System.out.println("即将进入方法");
}
}
2.6 创建配置类
@EnableAspectJAutoProxy //激活切面自动代理
@ComponentScan("com.itheima")
public class SpringConfig {
}
2.7 单元测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class DeptServiceTest {
//从容器中获取的已经是代理对象
@Autowired
private DeptService deptService;
@Test
public void test1(){
deptService.findAll();
}
}
测试结果:
即将进入方法
findAll
3. 通知类型
3.1 四大通知
四大通知描述的就是增强方法在切点方法的什么位置上执行
- 前置通知(before):增强方法在切点运行之前执行
- 返回后通知(after-returning):增强方法在某切点正常完成后执行的通知,不包括抛出异常的情况
- 异常后通知(after-throwing):增强方法在某切点抛出异常退出时执行的通知
- 后置通知(after):增强方法在某切点退出的时候执行的通知(不论是正常返回还是异常退出)
通知执行的位置
try{
前置通知(before)
//切点执行位置
返回后通知(after-returning)
}catch(Execption e){
异常后通知(after-throwing)
}finally{
后置通知(after)
}
3.1.1 添加通知
@Aspect //切面注解,需要标注在增强类上
@Component
public class Logger {
//切点表达式:作用就是定位出切点(findAll)
@Pointcut("execution(java.util.List com.itheima.service.DeptService.findAll())")
public void pt(){}
@Before("pt()")//指定被标注的方法要在切点之前执行
public void m1(){
System.out.println("即将进入方法");
}
@AfterReturning("pt()")//指定被标注的方法正常结束后通知
public void m2(){
System.out.println("方法正常结束");
}
@AfterThrowing("pt()")//指定被标注的方法出现异常后通知
public void m3(){
System.out.println("方法出现异常");
}
@After("pt()") //指定被标注的方法退出后通知(无论是正常结束还是异常)
public void m4(){
System.out.println("方法运行到最后");
}
}
3.1.2 测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class DeptServiceTest {
//从容器中获取的已经是代理对象
@Autowired
private DeptService deptService;
@Test
public void test1(){
deptService.findAll();
}
}
测试结果
即将进入方法
findAll
方法正常结束
方法运行到最后
3.2 环绕通知
它是一种特殊的通知,他允许以编码的形式实现四大通知
@Around环绕通知需要自己调用ProceedingJoinPoint.proceed()来让原始方法执行,其他通知不需要考虑目标方法执行.
@Around环绕通知方法的返回值,必须指定为Object,来接收原始方法的返回结果.
当有多个切面的切入点都匹配到了目标方法,目标方法运行时,多个通知方法都会被执行。
//切点表达式:作用就是定位出切点(findAll)
@Pointcut("execution(java.util.List com.itheima.service.DeptService.findAll())")
public void pt(){}
//环绕通知
@Around("pt()")
public Object m5 (ProceedingJoinPoint pjp){
Object obj = null ;
try {
System.out.println("方法执行前");
obj = pjp.proceed();//代表切点执行
System.out.println("方法正常结束");
} catch (Exception e) {
System.out.println("方法出现异常");
}finally {
System.out.println("方法运行到最后");
return obj;
}
}
测试结果
方法执行前
findAll
方法正常结束
方法运行到最后
3.3 通知顺序:
3.3.1 默认顺序
不同切面类中,默认按照切面类的类名字母排序:
目标方法前的通知方法:字母排名靠前的先执行
目标方法后的通知方法:字母排名靠前的后执行
//案例
//这是L方法
@Aspect //切面注解,需要标注在增强类上
@Component
public class Logger {
//方式2:@annotation(自定义注解类型) 使用一个注解来挑选出切点
@Pointcut("@annotation(com.itheima.anno.LogAnno)")
public void pt(){}
@Before("pt()")//指定被标注的方法要在切点之前执行
public void m1(){
System.out.println("即将进入L方法");
}
@After("pt()") //指定被标注的方法退出后通知(无论是正常结束还是异常)
public void m4(){
System.out.println("方法L运行到最后");
}
}
//这是M方法
@Aspect
@Component
public class MM {
@Pointcut("@annotation(com.itheima.anno.LogAnno)")
public void pt(){}
@Before("pt()")//指定被标注的方法要在切点之前执行
public void m1(){
System.out.println("即将进入M方法");
}
@After("pt()") //指定被标注的方法退出后通知(无论是正常结束还是异常)
public void m4(){
System.out.println("方法M运行到最后");
}
}
//测试
@Test
public void test1(){
deptService.findAll();
}
测试结果:
即将进入L方法
即将进入M方法
findAll
方法M运行到最后
方法L运行到最后
3.3.2 用@Order(数字)加在切面类上来控制顺序
目标方法前的通知方法:数字小的先执行
目标方法后的通知方法:数字小的后执行
//案例
//这是L方法
@Aspect //切面注解,需要标注在增强类上
@Component
@Order(1)
public class Logger {
//方式2:@annotation(自定义注解类型) 使用一个注解来挑选出切点
@Pointcut("@annotation(com.itheima.anno.LogAnno)")
public void pt(){}
@Before("pt()")//指定被标注的方法要在切点之前执行
public void m1(){
System.out.println("即将进入L方法");
}
@After("pt()") //指定被标注的方法退出后通知(无论是正常结束还是异常)
public void m4(){
System.out.println("方法L运行到最后");
}
}
//这是M方法
@Aspect
@Component
@Order(0)
public class MM {
@Pointcut("@annotation(com.itheima.anno.LogAnno)")
public void pt(){}
@Before("pt()")//指定被标注的方法要在切点之前执行
public void m1(){
System.out.println("即将进入M方法");
}
@After("pt()") //指定被标注的方法退出后通知(无论是正常结束还是异常)
public void m4(){
System.out.println("方法M运行到最后");
}
}
//测试
@Test
public void test1(){
deptService.findAll();
}
测试结果:
即将进入M方法
即将进入L方法
findAll
方法L运行到最后
方法M运行到最后
4. 切点表达式
4.1 execution
execution() :指定一组规则来匹配某些类中的方法,匹配中的就是切点
execution(修饰符? 返回值 包名.类名.方法名(参数) throws 异常?)
* 代表一个或多个位置
… 代表0个或多个位置
?表示可以省略
@Aspect //切面注解,需要标注在增强类上
@Component
public class Logger {
//切点表达式:作用就是定位出切点
// * 表示一个或多个位置
// .. 表示0个或多个位置
//com.itheima.service.impl[包名].*[实现类].*[方法](..)[方法参数]
@Pointcut("execution(* com.itheima.service.impl.*.*(..))")
public void pt(){}
//环绕通知
@Around("pt()")
public Object m5 (ProceedingJoinPoint pjp){
Object obj = null ;
try {
System.out.println("方法执行前");
obj = pjp.proceed();
System.out.println("方法正常结束");
} catch (Exception e) {
System.out.println("方法出现异常");
}finally {
System.out.println("方法运行到最后");
return obj;
}
}
}
测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class DeptServiceTest {
//从容器中获取的已经是代理对象
@Autowired
private DeptService deptService;
@Test
public void test1(){
deptService.findAll();
System.out.println("============");
deptService.save(new Dept());
System.out.println("============");
deptService.findById(1);
}
}
测试结果:
方法执行前
findAll
方法正常结束
方法运行到最后
============
方法执行前
save
方法正常结束
方法运行到最后
============
方法执行前
findById
方法正常结束
方法运行到最后
4.2 @annotation
@annotation:指定一个注解,凡是标有此注解的方法都是切点
4.2.1 自定义注解
//自定义注解
public @interface LogAnno {
}
4.2.2 在作为切点的方法上添加注解
@Service
public class DeptServiceImpl implements DeptService {
@Override
@LogAnno
public List<Dept> findAll() {
System.out.println("findAll");
return new ArrayList<>();
}
@Override
@LogAnno
public void save(Dept dept) {
System.out.println("save");
}
@Override
@LogAnno
public Dept findById(Integer id) {
System.out.println("findById");
return new Dept(1,"开发部");
}
}
4.2.3 设置切点表达式
在增强类上设置
@Aspect //切面注解,需要标注在增强类上
@Component
public class Logger {
//方式2:@annotation(自定义注解类型) 使用一个注解来挑选出切点
@Pointcut("@annotation(com.itheima.anno.LogAnno)")
public void pt(){}
//环绕通知
@Around("pt()")
public Object m5 (ProceedingJoinPoint pjp){
Object obj = null ;
try {
System.out.println("方法执行前");
obj = pjp.proceed();
System.out.println("方法正常结束");
} catch (Exception e) {
System.out.println("方法出现异常");
}finally {
System.out.println("方法运行到最后");
return obj;
}
}
}
4.2.4 测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class DeptServiceTest {
//从容器中获取的已经是代理对象
@Autowired
private DeptService deptService;
@Test
public void test1(){
deptService.findAll();
System.out.println("============");
deptService.save(new Dept());
System.out.println("============");
deptService.findById(1);
}
}
测试结果
findAll
方法正常结束
方法运行到最后
============
方法执行前
save
方法正常结束
方法运行到最后
============
方法执行前
findById
方法正常结束
方法运行到最后
5. 日志记录详情
日志:类名,方法名,参数,返回结果,报错,耗时时间
//环绕通知
@Around("pt()")
public Object m5 (ProceedingJoinPoint pjp) throws Throwable{
Object obj = null ;
StringBuilder sb = new StringBuilder();
//获取类名
sb.append("类名"+ pjp.getTarget().getClass().getName());
//获取方法名
MethodSignature methodSignature =(MethodSignature) pjp.getSignature();
sb.append("方法名"+methodSignature.getMethod().getName());
//获取方法参数
sb.append("参数"+ Arrays.toString(pjp.getArgs()));
long begin = System.currentTimeMillis();//开始时间
try {
obj = pjp.proceed();
//获取返回结果[] 为无返回值
sb.append("返回值"+obj);
} catch (Exception e) {
//返回错误信息
sb.append("错误信息"+e.getMessage());
}finally {
long end = System.currentTimeMillis();//结束时间
sb.append("耗时时间"+(end-begin));//耗时
//输出日志信息
System.out.println("日志结果"+sb.toString());
}
return obj;
}
}
测试类
@Test
public void test1(){
deptService.findAll();
System.out.println("============");
deptService.save(new Dept());
System.out.println("============");
deptService.findById(1);
}
测试结果
findAll
日志结果类名com.itheima.service.impl.DeptServiceImpl方法名findAll参数[]返回值[]耗时时间1
============
save
日志结果类名com.itheima.service.impl.DeptServiceImpl方法名save参数[Dept(id=null, name=null)]返回值null耗时时间0
============
findById
日志结果类名com.itheima.service.impl.DeptServiceImpl方法名findById参数[1]返回值Dept(id=1, name=开发部)耗时时间1