一、AOP概念:
AOP是什么?
AOP(Aspect-方面 Orient-朝向 Programming,面向切面编程)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程(OOP)的一种补充和完善。它以通过预编译方式和运行期动态代理方式,实现在不修改源代码的情况下给程序统一添加额外功能的一种技术。
AOP与OOP字面意思相近,但是两者完全是面向不同领域的设计思想。实际项目中我们通常将面向对象理解为一个静态过程(例如一个系统有多少个模块,一个模块有哪些对象,对象有哪些属性),面向切面的运行期代理方式,理解为一个动态过程,可以在对象运行时动态织入一些扩展功能或控制对象执行。
二、应用场景分析:
实际项目中通常会将系统分为两大部分,一部分是核心业务,一部分是非核心业务。在编程实现时我们首先要完成的是核心业务的实现,非核心业务一般是通过特定方式切入到系统中,这种特定方式一般就是借助AOP进行实现。
AOP就是要基于OCP(开闭原则),在不改变原有系统核心业务代码的基础上动态添加一些扩展功能并可以“控制”对象的执行。例如AOP应用于项目中的日志处理,事务处理,权限处理,缓存处理等。如图:
思考:现有一业务,在没有AOP编程时,如何基于OCP原则实现功能扩展?
·举个例子,我们现在写一个业务,随便打印一句话视作为它的核心业务。然后我们再给这个业务做一个功能扩展,给这个业务进行日志打印(例如打印它执行业务前后的时间):
1.这是一个核心业务的实现:
/**
* 这是一个业务的dao/service接口
* @author Administrator
*
*/
interface MailService{
void sendMail(String msg);
}
//实现类
class DeafultMainService implements MailService{
@Override
public void sendMail(String msg) {
System.out.println("send"+msg);
}
}
2.现在假如我需要对上面的业务进行功能扩展,打印一个执行时间日志,如果不使用AOP,我们可能只会这样打印:在实现类中假如打印语句或者一些功能代码
//实现类
class DefaultMailService implements MailService{
@Override
public void sendMail(String msg) {
System.out.println("startTime:"+System.currentTimeMillis());
System.out.println("send"+msg);
System.out.println("endTime:"+System.currentTimeMillis());
}
}
但是,这种方式实际上是违背了开闭原则(对扩展功能开放,对源码修改关闭),因为它修改了源代码,我们不可能每一次对功能进行扩展,都去修改一次源代码,而实际上的项目,几乎都是随着时间推移而可能会需要进行功能的更新或者扩展!
3.所以,如果要基于OCP原则,那么我们可能就需要通过重写一个实现类去实现上面的子类,然后再做修改:下面是第一种基于OCP原则进行功能扩展的方案
/**
* 在不违背OCP原则情况下实现功能扩展的方式1:
* @author Administrator
* 方案一:通过继承方式对目标进行功能扩展
*/
class LogMailServcie extends DefaultMailService{
//继承它的业务后再添加功能
@Override
public void sendMail(String msg) {
System.out.println("startTime:"+System.currentTimeMillis());
super.sendMail(msg);
System.out.println("endTime:"+System.currentTimeMillis());
}
}
/**
* 这个类用于理解OCP开闭原则的原理
* @author Administrator
*
*/
public class TestOCP {
public static void main(String[] args) {
MailService ms = new LogMailServcie();
ms.sendMail("测试OCP");
}
}
没错,这个方式确实基于了OCP原则进行了功能扩展,因为没有修改到它的源码,但是,假如我们遇到了一些final的类,需要对它进行扩展又怎么办呢?
4.方案二:
/**
* 方案二:通过组合方式对目标对象进行功能扩展
*/
class MonitorMailService implements MailService{
//has a (组合)
private MailService mailService;
//通过带参构造器,手动注入值
public MonitorMailService(MailService mailService) {
this.mailService = mailService;
}
@Override
public void sendMail(String msg) {
Long start = System.currentTimeMillis();
System.out.println("开始时间:"+start);
mailService.sendMail(msg);
Long end = System.currentTimeMillis();
System.out.println("结束时间:"+end);
System.out.println("共计用时:"+(end-start));
}
}
/**
* 这个类用于理解OCP开闭原则的原理
* @author Administrator
*
*/
public class TestOCP {
public static void main(String[] args) {
//方案一
MailService ms1 = new LogMailServcie();
ms1.sendMail("测试OCP");
//方案二
MailService ms2 = new MonitorMailService(new DefaultMailService());
ms2.sendMail("测试方案二是否启用!");
}
}
上面的这种方式,可以称之为通过实现了相同的父接口,然后为兄弟类进行功能扩展。
但是这个时候我们又需要知道上面2个方式的一些缺点,就是你必须实现接口、或者继承业务类,这就使得代码的耦合性很高,假如上面的接口或类的方法发生了变化就很麻烦,或者说假如需要实现的扩展功能较多,你就需要创建多个的实现类,代码量也很多。
基于上面的需要以及代码的优化,我们就可以对AOP进行了解了!
三、AOP应用原理分析
Spring AOP底层基于代理机制实现功能扩展:
- 假如目标对象(被代理对象)实现接口,则底层可以采用JDK动态代理机制为目标对象创建代理对象(目标类和代理类会实现共同接口)。
- 假如目标对象(被代理对象)没有实现接口,则底层可以采用CGLIB代理机制为目标对象创建代理对象(默认创建的代理类会继承目标对象类型)。
·说明:Spring Boot2.x中AOP现在默认使用的是CGLIB代理,假如需要使用JDK动态代理可以在配置文件(application.yml/properties)中进行一=以下配置:
spring.aop.proxy-target-class=false
CGLIB属于第三方库,对于该方式的代理实现,其实相对更为灵活,不管你的类中是否实现了接口,它都会创建代理(如果你的业务对象使用了final修饰就只能使用JDK,但实际上很少情况下会使用final)
四、AOP相关术语分析
1.切面(aspect):横切面对象,一般为一个具体类对象(可以借助@Aspect声明)
2.通知(Advice):在切面的某个特定连接点上执行的动作(扩展功能),通知具备有五种类型,分别为around(环绕)、before(前置)、after(后置)、AfterThrowing(异常)、AfterReturning(终止/返回通知)。
3.连接点(joinpoint):程序执行过程中某个特定的点,一般指被拦截到的方法。
4.切入点(pointcut):对多个连接点(joinpoint)一种定义,一般可以理解为多个连接点的集合。
连接点和切入点定义如下:
说明:我们可以简单的将机场的一个安检口理解为连接点,多个安检口为切入点,安检过程看作通知。其实就是根据你使用的通知面对功能的多个对象集合就是切入点,某一个对象方法就是连接点。