AOP:Aspect oriented programming 面向切面编程,AOP 是 OOP(面向对象编程)的一种延续。
下面我们先看一个 OOP 的例子。
例如:现有三个类,Horse
、Pig
、Dog
,这三个类中都有 eat 和 run 两个方法。
通过 OOP 思想中的继承,我们可以提取出一个 Animal 的父类,然后将 eat 和 run 方法放入父类中,Horse
、Pig
、Dog
通过继承Animal
类即可自动获得 eat()
和 run()
方法。这样将会少些很多重复的代码。
OOP 编程思想可以解决大部分的代码重复问题。但是有一些问题是处理不了的。比如在父类 Animal 中的多个方法的相同位置出现了重复的代码,OOP 就解决不了。
/**
* 动物父类
*/
public class Animal {
/** 身高 */
private String height;
/** 体重 */
private double weight;
public void eat() {
// 性能监控代码
long start = System.currentTimeMillis();
// 业务逻辑代码
System.out.println("I can eat...");
// 性能监控代码
System.out.println("执行时长:" + (System.currentTimeMillis() - start)/1000f + "s");
}
public void run() {
// 性能监控代码
long start = System.currentTimeMillis();
// 业务逻辑代码
System.out.println("I can run...");
// 性能监控代码
System.out.println("执行时长:" + (System.currentTimeMillis() - start)/1000f + "s");
}
}
这部分重复的代码,一般统称为 横切逻辑代码。
横切逻辑代码存在的问题:
-
代码重复问题
-
横切逻辑代码和业务代码混杂在一起,代码臃肿,不变维护
面向对象的局限性
面向对象编程(Object-oriented programming,缩写:OOP)的三大特性:封装、继承、多态,我们早已用得炉火纯青。OOP 的好处已无需赘言,相信大家都有体会。这里咱们来看一下 OOP 的局限性。
当有重复代码出现时,可以就将其封装出来然后复用。我们通过分层、分包、分类来规划不同的逻辑和职责,就像之前讲解的三层架构。但这里的复用的都是核心业务逻辑,并不能复用一些辅助逻辑,比如:日志记录、性能统计、安全校验、事务管理,等等。这些边缘逻辑往往贯穿你整个核心业务,传统 OOP 很难将其封装:
public class UserServiceImpl implements UserService { @Override public void doService() { System.out.println("---安全校验---"); System.out.println("---性能统计 Start---"); System.out.println("---日志打印 Start---"); System.out.println("---事务管理 Start---"); System.out.println("业务逻辑"); System.out.println("---事务管理 End---"); System.out.println("---日志打印 End---"); System.out.println("---性能统计 End---"); } }
为了方便演示,这里只用了打印语句,就算如此这代码看着也很难受,而且这些逻辑是所有业务方法都要加上,想想都恐怖。
OOP 是至上而下的编程方式,犹如一个树状图,A调用B、B调用C,或者A继承B、B继承C。这种方式对于业务逻辑来说是合适的,通过调用或继承以复用。而辅助逻辑就像一把闸刀横向贯穿所有方法,如图2-4所示:
这一条条横线仿佛切开了 OOP 的树状结构,犹如一个大蛋糕被切开多层,每一层都会执行相同的辅助逻辑,所以大家将这些辅助逻辑称为层面或者切面。
代理模式用来增加或增强原有功能再适合不过了,但切面逻辑的难点不是不修改原有业务,而是对所有业务生效。对一个业务类增强就得新建一个代理类,对所有业务增强,每个类都要新建代理类,这无疑是一场灾难。而且这里只是演示了一个日志打印的切面逻辑,如果我再加一个性能统计切面,就得新建一个切面代理类来代理日志打印的代理类,一旦切面多起来这个代理类嵌套就会非常深。
面向切面编程(Aspect-oriented programming,缩写为 AOP)正是为了解决这一问题而诞生的技术。
AOP 另辟蹊径,提出横向抽取机制,将横切逻辑代码和业务逻辑代码分离
代码拆分比较容易,难的是如何在不改变原有业务逻辑的情况下,悄无声息的将横向逻辑代码应用到原有的业务逻辑中,达到和原来一样的效果。
AOP 不是 OOP 的对立面,它是对 OOP 的一种补充。OOP 是纵向的,AOP 是横向的,两者相结合方能构建出良好的程序结构。AOP 技术,让我们能够不修改原有代码,便能让切面逻辑在所有业务逻辑中生效。
我们只需声明一个切面,写上切面逻辑:
@Aspect // 声明一个切面 @Component public class MyAspect { // 原业务方法执行前 @Before("execution(public void com.rudecrab.test.service.*.doService())") public void methodBefore() { System.out.println("===AspectJ 方法执行前==="); } // 原业务方法执行后 @AfterReturning("execution(* com.rudecrab.test.service..doService(..))") public void methodAddAfterReturning() { System.out.println("===AspectJ 方法执行后==="); } }
无论你有一个业务方法,还是一万个业务方法,对我们开发者来说只需编写一次切面逻辑,就能让所有业务方法生效,极大提高了我们的开发效率。
AOP 解决了什么问题
通过上面的分析可以发现,AOP 主要用来解决:在不改变原有业务逻辑的情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复。
AOP的概念:
AOP:Aspect Oriented Programming,面向切面编程。通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了程序的开发效率。
当我们使用动态代理来创建代理对象之后,以后的service如果有业务上的代码就写业务上的代码,如果没有则可能都只有一行代码(即方法)。
作用:在程序运行期间,不修改源码对已有方法进行增强。
优势:减少重复代码、提高开发效率、维护方便(一旦工具类中的方法名发生变化,对于我们来说使用动态代理实现时可能只有一个地方有问题,对于使用每个方法调用时,只要调用了该方法就会有问题)
AOP的实现方式:使用动态代理技术。
Spring的AOP就是通过配置的方式实现动态代理从而创建代理对象,而不是用动态代理去编码的方式。从而再一次精简配置文件中的内容。不用写BeanFactory类。
Spring中的aop术语和细节:
Spring选择AOP的准则:根据是否实现了接口,来决定到底是选择基于接口的动态代理,还是选择基于子类的动态代理。基于接口的动态代理要求被代理对象至少实现一个接口,但是基于子类的动态代理要求被代理类不能是最终类。基于子类的动态代理也可以用于基于接口的动态代理。
Spring的AOP中可以手动控制到底是基于接口还是基于子类。
连接点:业务层接口中的所有方法。连接业务和增强方法的点。我们如何把增强的代码加到业务中来呢?只能通过这些方法,从而形成完整的业务逻辑。
切入点:业务层接口中被增强的方法。以前在创建service代理对象的时候,所有方法都进行了增强,但是,不是业务层的所有方法都被增强。如下图所示:
业务层的test方法就没有被增强。
故所有的切入点都是连接点,但并不是所有的连接点点都是切入点,
Invoke方法中的公用的代码就是通知,方法中的代码是有顺序的,在invoke方法中,明确的调用业务层的方法是rtValue=method.invoke(accountService,args),在其之前的代码是前置通知,在其之后的代码是后置通知,在catch里面的是异常通知,在finally中的是最终通知。四种切入点类型是以切入点方法确定的。整个invoke方法的执行就是环绕通知。环绕通知中有明确的切入点方法调用,环绕通知就是invoke方法中的代码
目标对象Target:指被代理对象。即accountService
织入:把通知(增强)应用到目标对象(被代理对象)来创建代理对象的过程。
Proxy(代理):指代理对象
Aspect(切面):切入点和通知的结合。
学习spring我们要明确的事:
在实际开发中,我们先实现业务需求,抽取公用代码,建立切入点与公共代码的关系。
切面就是在配置文件中声明切入点与通知间的关系。