目录
6.4.2 @before的前置通知,方法有JoinPoint参数(掌握)
6.4.3 @AfterReturning后置通知,注解有returning属性(掌握)
6.4.4 @Around环绕通知,增强方法有ProceedingJoinpoint(掌握)
6.4.5 @AfterThrowing异常通知,注解中有throwing属性(了解)
前言
声明:本专栏文章均为观看动力节点王鹤老师三大框架的视频所撰写的笔记,笔者实力有限,内容如有错误欢迎各位小伙伴在评论区指出。
视频链接:SSM-Spring
大家好,我是尼根。Spring中两大核心,一是IOC控制反转,一是AOP面向切面。控制反转负责对象的创建,面向切面负责非业务逻辑的实现和组织,上一篇中详述了控制反转的两种实现方式,本篇中将会具体讲解AOP的概念、原理和使用。
1、动态代理
了解动态代理之前我们需要知道什么是代理模式?
代理模式就是用一个类去代替另一个类中的方法,简单实现的话,只需要在代替类中写一个和被代替类中的方法一样的方法,然后在代替类的内部去调用被代替类的方法即可。有了上面的代替类的包装,我们去使用代替类的方法就相当于调用了被代替类中的方法。
🍎举个例子
客户想去买苹果,既可以去果农手里买也可以去水果卖场买。这个案例中,果农和水果卖场都能出售苹果的,而且注意到水果卖场想要出售苹果是需要先去找果农进货的,这里的进货就是典型的代理。
但是我们知道,水果卖场作为代理只会代理水果的生意,而其他类型的就不可以了,例如4s店代理汽车、淘宝代理店铺等等。现实生活中,这些都存在,但是如果哪一天这个水果卖场做大了,他想要伸手到其他类型的生意上时,他就再需要找到各个汽车厂商去洽谈代理,此时他不光可以代理水果还可以代理汽车。但是如果用代码来实现的话就会发现,我们的水果卖场代理类中,要添加很多的同名方法去包装各个被代理果农和汽车厂商的出售方法,如果代理的体量越来越大,可想而知代理类中的同名方法会非常多,非常臃肿,给后续的使用带来很大的不变。
所以,按部就班的每增加一个代理对象甚至增加其他类型的代理对象就自己手动的实现代理类中同名方法,当被代理对象的数量较少或者类型单一的时候还是可以的,一旦被代理对象数量激增,就会使得代理对象不堪重负,代码后续的维护异常艰难。于是,人们想到了反射机制,利用运行时反射创建被代理对象以及他们的类型也就是接口就可以解决掉上面那种按部就班的缺点。正因如此,我们把按部就班的这种代理方式就称为静态代理,把利用运行时反射机制的代理方式称为动态代理。
上面的描述略显浅显,也不够生动,想要更详细了解动态代理的可以转到这篇文章:《代理模式》
2、不使用AOP的开发方式(理解)
假设甲方公司有同一种类型的业务Service,该业务的完成主要需要的是两个任务doSome()、doOther。如果,乙方公司同时进来两个这种类型的业务,需要两个人完成,与此同时,公司为了考察两个人的工作效率还要求完成业务的同时记录各自的完成时间,也就是各自的任务中还需要执行一个doLog()。
先定义好接口与一个实现类,该实现类中除了要实现接口中的方法外,还要再写两个非业务方法。非业务方法也称为交叉业务逻辑:
➢ doTransaction():用于事务处理
➢ doLog():用于日志处理
然后,再使接口方法调用它们。接口方法也称为主业务逻辑。
Service接口:
public interface SomeService {
void doSome();
void doOther();
}
Service实现类:第一个人
/**
* 第一个人
* */
public class ServiceImpl1 implements Service {
@Override
public void doSome() {
doLog();
System.out.println("第一个人完成了doSome");
doTransaction();
}
@Override
public void doOther() {
doLog();
System.out.println("第一个人完成了doOther");
doTransaction();
}
public void doLog(){
System.out.println("执行日志处理");
}
public void doTransaction(){
System.out.println("执行事务处理");
}
}
Service实现类:第二个人
/**
* 第二个人
* */
public class ServiceImpl2 implements Service {
@Override
public void doSome() {
doLog();
System.out.println("第二个人完成了doSome");
doTransaction();
}
@Override
public void doOther() {
doLog();
System.out.println("第二个人完成了doOther");
doTransaction();
}
public void doLog(){
System.out.println("执行日志处理");
}
public void doTransaction(){
System.out.println("执行事务处理");
}
}
观察上述两个实现类的代码可以发现,对于非主业务的代码二者的内容是完全一样的,对此,我们可以创建一个工具类保存非主业务代码,这样实现类中直接调用即可,这样就不会出现大量重复冗余的代码。
非主业务工具类:
public class ServiceTools {
public static void doLog(){
System.out.println("执行日志处理");
}
public static void doTransaction(){
//方法的最后,提交事务
System.out.println("执行事务处理");
}
}
实现类调用工具类:
import com.bjnode.service.util;
/**
* 第二个人
* */
public class ServiceImpl2 implements Service {
ServiceTools tools = new ServiceTools();
@Override
public void doSome() {
tools.doLog();
System.out.println("第二个人完成了doSome");
tools.doTransaction();
}
@Override
public void doOther() {
tools.doLog();
System.out.println("第二个人完成了doOther");
tools.doTransaction();
}
}
有了工具类,可以看见业务实现类的冗余代码消失了,但是在实现类的主业务逻辑的代码中仍有冗余,许多重复的非业务逻辑代码重复出现,这会使得主业务的逻辑处理过程变得不清晰,显然这种情况也不是我们愿意看到的。
不同类中重复方法可以利用工具类进行抽离封装,那不同方法中相同的执行语句怎么抽离封装呢?
答案就是动态代理,从类的层面上去看的话,动态代理做的就是工具类的事,只不过二者的处理的层次不一样。具体怎么实现动态代理,请参考这篇文章:《