目录
1. IOC的设计初衷
在没有IOC之前,我们项目中调用某个方法都是先 new 一个方法的类对象,然后通过该对象调用该方法,但这种 new 对象的方式存在一个很大的缺点——项目代码之间耦合度太高了,如果后期需求变动需要对项目功能做修改,由于项目内部使用的 new 的方式导致耦合度较高,后期代码很有可能会对之前的代码产生侵入,就违背了 Java 面向对象中对扩展开放对修改封闭的原则。
为了降低项目内部new对象导致模块之间耦合度较高的问题,就有了IOC容器这一解决方案(这是主要原因,不是唯一原因)。
2. IOC的核心设计理念
IOC是 "Inversion of Control" 的简写,翻译过来就是控制反转。
IOC(控制反转)就是依赖倒置的一种设计思想,IOC正是通过DI(依赖注入)实现了控制反转,是一个面向对象的重要变成法则,设计模式采用的是工厂模式。
在 Spring 项目中,程序猿可以将对象的实例化,初始化,对象之间的依赖关系,对象的控制交由 IOC 容器来做,IOC容器会完成 创建对象,管理对象,注入对象,控制对象等一系列操作。创建出来的一个个对象其实本质上和我们自己 new 出来的对象没有本质区别,但是Spring 为了做区分,将这些对象换了个名字称之为 Bean 。
当在程序中想要调用某个类的方法的时候,就不需要像以前一样自己去 new 对象,而是将这个类的对象创建到管理交给 IOC容器,在需要的时候直接从IOC容器中的对象取出来用就可以了,这个过程中对象的创建和控制从程序员转化成IOC容器,实现了控制反转。降低了项目代码内部的耦合度,实现了高内聚低耦合的目的。同时,Spring 容器将所有的对象都集中在一种,非常方便配置和管理,同时还提高了程序的性能,减轻了JVM的压力。
关于 Spring 的 Bean 对象,有想要深入了解源码的同学可以看我的另一篇文章,简述了生命周期的全过程。
3. AOP概述
AOP全称"Aspect Oriented Programming" 翻译过来为面向切面编程,是对OOP(面向对象编程)的一种补充和完善。它是通过预编译方式和运行期动态代理的方式实现,在不修改源代码的情况下添加新的功能,给程序动态统一添加额外功能的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间耦合度降低,提高程序的可重用性,同时提高开发效率。
举个例子,我们要对以前的一个项目添加一些功能,该如何做?同学们想想能修改源代码吗?肯定不能的吧,否则就违背了Java对扩展开放对修改封闭这一原则。再比如说,我要在用户做一些事情之前先做用户校验确保安全,该怎么办,重构项目代码代价太大,这个时候我们就需要用到AOP,它可以做到不修改源代码的同时添加新的功能,就好比是在程序内部方法与方法之间切开一个口子,将要添加的方法放到切开的口子里。
假设转账业务中A方法执行完毕执行B方法,现在使用AOP将A和B切开,将C方法加到AB中间,那么程序在执行完A方法之后,就会先去执行切入进去的C方法,再执行B方法。
4. AOP相关术语
4.1 横切关注点
我们来看下面,add,sub,mul,div都是一个个方法,在程序中自上而下独立执行,程序执行的时候会调用很多的业务方法。以下面四个方法为例,后期我现在希望在四个方法中都加入某个新的功能。
比如执行完数据库操作之后添加一个日志方法记录一下,记录插入操作,我们要解决的问题就是添加一个新的通用日志功能,
又比如我要在所有业务之前都执行用户验证,添加一个用户校验的方法,我们要解决的问题是用户校验,要添加用户校验这一业务功能,但并没有去做这件事,只是强调这件事的目的,这就叫 "横切关注点" ,业务中有多少个希望附加的功能,就有多少个横切关注点。
4.2 通知(增强)
每个"横切关注点上要做的事情就是添加一个方法去完成我们要达到的目的",添加的这个方法就叫通知方法,也叫增强方法,所以用一句总结,"横切关注点是要添加的业务功能,通知是实现业务功能的方法"。方法中分为以下几种通知。
前置通知 @Before:在被代理的目标方法前执行;
返回通知 @AfterReturning:在被代理的目标方法成功结束后执行;
异常通知 @AfterThrowing:在被代理的目标方法异常结束后执行;
后置通知 @After:在被代理的目标方法最终结束后执行;
环绕通知 @Around:使用 try...catch...finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置;
4.3 切面
封装上述通知方法的类,也可以叫做切面类。
4.4 目标
被代理的目标对象。
4.5 代理
向目标对象应用通知之后产生的代理对象。
4.6 连接点
我们原本的业务方法是自上而下执行的,业务中可能会一步步调用不同的方法,而我们的增强方法就是要添加到原本业务各个方法中的某一个位置,放置增强方法的位置叫连接点。也可以说,就是 Spring 允许你使用通知的地方就叫连接点。
4.7 切入点
连接点位置要放置的方法就是切入点,这一点非常容易和通知搞混,通知方法是实现我们业务功能的整体方法,这个整体方法中又分为我们刚才说的众多通知,每一个通知也可以是一个小的方法,这些小的方法就是切入点。
5. Spring 基于注解的AOP
Spring 中实现AOP有两种方式,一种是基于注解的AOP,另一种就是基于 xml 配置文件的AOP,实际使用中基于直接的AOP要比 xml 的更加简单,所以这里重点来说说基于注解的AOP实现。
此外,SpringAOP有动态代理和静态代理两种,动态代理又分为JDK动态代理和 cglib 动态代理,静态代理是AspectJ(但最终效果是动态的)。
5.1 JDK动态代理
JDK动态代理主要应用于目标方法有接口的动态代理,是JDK原生的实现方式,它底层实际上是生成了接口实现类的代理对象;cglib 动态代理也可以适用于有接口的目标方法。
5.2 cglib 动态代理
cglib 动态代理则适用于目标方法没有接口的动态代理,它底层是通过继承目标类创建目标类对象实现动态代理;
5.3 AspectJ 代理
AspectJ 是AOP思想的一种实现,它本身其实也是一个框架,不算是Spring的一部分,weaver是织入器,它将代理逻辑"织入"被代理的目标类编译得到的字节码文件对象,所以最终效果是动态的。而 Spring 框架则是借用了里面的一些注解,实现了AOP的功能。
Spring 中有很多关于AOP的常用注解
@Before(value = "execution(方法路径(方法参数))"):标注该方法为前置通知方法;
@AfterReturning(value = "execution(方法路径(方法参数))"):标注该方法为返回通知方法;
@AfterThrowing(value = "execution(方法路径(方法参数))"):标注该方法为异常通知方法;
@After(value = "execution(方法路径(方法参数))"):标注该方法为后置通知方法;
@Around(value = "execution(方法路径(方法参数))"):标注该方法为环绕方法;
@Aspect:标注该类是一个切面类;
@Pointcut("execution(方法路径(方法参数))"):标注一个切入点,类似于MyBatis框架中的SQL片段,可以重复利用,不需要再众多方法上都写一边目标方法的路径;