目录
- AOP:面向切面编程
- 概念
- 为什么需要 AOP
- AOP 实现
- AOP 术语
- 设计原理
- IOC:控制反转
- 概念
- 目的
- IoC和DI
- IOC的初始化过程
- 使用IOC的好处
AOP:面向切面编程
概念
AOP (Aspect Orient Programming),直译过来就是 面向切面编程。AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面。
为什么需要 AOP
想象下面的场景,开发中在多个模块间有某段重复的代码,我们通常是怎么处理的?显然,没有人会靠“复制粘贴”吧。在传统的面向过程编程中,我们也会将这段代码,抽象成一个方法,然后在需要的地方分别调用这个方法,这样当这段代码需要修改时,我们只需要改变这个方法就可以了。然而需求总是变化的,有一天,新增了一个需求,需要再多出做修改,我们需要再抽象出一个方法,然后再在需要的地方分别调用这个方法,又或者我们不需要这个方法了,我们还是得删除掉每一处调用该方法的地方。实际上涉及到多个地方具有相同的修改的问题我们都可以通过 AOP 来解决。
AOP 实现
AOP 要达到的效果是,保证开发者不修改源代码的前提下,去为系统中的业务组件添加某种通用功能。AOP 的本质是由 AOP 框架修改业务组件的多个方法的源代码,看到这其实应该明白了,AOP 其实就是前面一篇文章讲的代理模式的典型应用。 按照 AOP 框架修改源代码的时机,可以将其分为两类:
静态 AOP 实现, AOP 框架在编译阶段对程序源代码进行修改,生成了静态的 AOP 代理类(生成的 *.class 文件已经被改掉了,需要使用特定的编译器),比如 AspectJ。 动态 AOP 实现, AOP 框架在运行阶段对动态生成代理对象(在内存中以 JDK 动态代理,或 CGlib 动态地生成 AOP 代理类),如 SpringAOP。
AOP 术语
- 通知(Advice): AOP 框架中的增强处理。通知描述了切面何时执行以及如何执行增强处理。 前置通知:@Before 在目标业务方法执行之前执行 后置通知:@After 在目标业务方法执行之后执行 返回通知:@AfterReturning 在目标业务方法返回结果之后执行 异常通知:@AfterThrowing 在目标业务方法抛出异常之后 环绕通知:@Around 功能强大,可代替以上四种通知,还可以控制目标业务方法是否执行以及何时执行
- 连接点(join point): 连接点表示应用执行过程中能够插入切面的一个点,这个点可以是方法的调用、异常的抛出。在 Spring AOP 中,连接点总是方法的调用。
- 切点(PointCut): 可以插入增强处理的连接点。
- 切面(Aspect): 切面是通知和切点的结合。
- 引入(Introduction):引入允许我们向现有的类添加新的方法或者属性。
- 织入(Weaving): 将增强处理添加到目标对象中,并创建一个被增强的对象,这个过程就是织入。
设计原理
aop设计原理:主要的分代理的创建和代理的调用两部分,
1)代理的创建
创建代理工厂:拦截器数组,目标对象接口数组,目标对象。
创建代理工厂时,默认会在拦截器数组尾部再增加一个默认拦截器 —— 用于最终的调用目标方法。
当调用 getProxy 方法的时候,会根据接口数量大余 0 条件返回一个代理对象(JDK or Cglib)。
注意:创建代理对象时,同时会创建一个外层拦截器,这个拦截器就是 Spring 内核的拦截器,用于控制整个 AOP 的流程。
2)代理的调用
当对代理对象进行调用时,就会触发外层拦截器。
外层拦截器根据代理配置信息,创建内层拦截器链。创建的过程中,会根据表达式判断当前拦截是否匹配这个拦截器。而这个拦截器链设计模式就是职责链模式。
当整个链条执行到最后时,就会触发创建代理时那个尾部的默认拦截器,从而调用目标方法,最后返回。
aop 面向切面编程,一个程序中跨越多个点的功能被称为横切关注点。
AOP即面向切面编程将安全,事务等于程序逻辑相对独立的功能抽取出来,利用spring的配置文件将这些功能插进去,实现了按照方面编程,提高了复用性。 常见的功能例子:日志记录、声明性事务、安全性,和缓存等等。
与业务无关,却为业务模块所
共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
AOP的技术主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码
①JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。
②如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
AOP相关面试问题:
- 关注点和横切关注的区别是什么? 关注点是应用中一个模块的行为,比如库存管理、航运管理、用户管理等。 横切关注点:贯穿整个应用程序的关注点。像事务管理、权限、日志、安全等。
- 什么是连接点? 被拦截到的点,因为 Spring 只支持方法类型的连接点,所以在 Spring 中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器。
- Spring 的通知是什么?有哪几种类型? 1)before:前置通知,在一个方法执行前被调用。 2)after: 在方法执行之后调用的通知,无论方法执行是否成功。 3)after-returning: 仅当方法成功完成后执行的通知。 4)after-throwing: 在方法抛出异常退出时执行的通知。 5)around: 在方法执行之前和之后调用的通知。
- 什么是切点? 切入点是一个或一组连接点,通知将在这些位置执行。可以通过表达式或匹配的方式指明切入点。
- 什么是目标对象? 被一个或者多个切面所通知的对象。它通常是一个代理对象。也指被通知(advised)对象。
- 什么是代理? 代理是通知目标对象后创建的对象。从客户端的角度看,代理对象和目标对象是一样的。
- 什么是织入?什么是织入应用的不同点? 织入是将切面和到其他应用类型或对象连接或创建一个被通知对象的过程。织入可以在编译时,加载时,或运行时完成。
AOP代理注解失效的场景以及解决方案
1、Controller直接调用Service B方法:Controller > Service A
在Service A 上加@Transactional的时候可以正常实现AOP功能。
2、Controller调用Service A方法,A再调用B方法:
Controller > Service A > Service B 在Service B上加@Transactional的时候不能实现AOP功能,因为在Service A方法中调用Service B方法想当于使用this.B(),this代表的是Service类本身,并不是真实的代理Service对象,所以这种不能实现代理功能。 所以,如果不是直接调用的方式,是不能实现代理功能的,非常需要注意。 但确实有这种不是直接调用的试,也需要实现代理功能,怎么做呢?很简单,只需要暴露当前代理对象给当前线程就行了
3、注解修饰在非public的方法上
4、方法中捕获了异常,但是没有抛出新的异常出方法外
解决办法:
1、AB没有依赖,直接调用B方法 2、Spring 解决通过AopContext.currentProxy()获取当前代理对象。 修改XML 新增如下语句;先开启cglib代理,开启 exposeProxy = true,暴露代理对象 SpringBoot解决,通过ApplicationContext获取当前代理对象
IOC:控制反转
概念
Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。利用工厂模式把对象交给容器管理,在配置文件中配置对应的bean以及对应的属性,让spring容器生成类的实例对象以及管理对象。容器启动时,进行初始化,当调用时,把初始化的bean分配给调用的类,(setter方法注入)
目的
(1)脱开、降低类之间的耦合; (2)倡导面向接口编程、实施依赖倒换原则; (3)提高系统可插入、可测试、可修改等特性。
IoC和DI
DI—Dependency Injection,即“依赖注入”:组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么” ● 谁依赖于谁:当然是应用程序依赖于IoC容器; ●为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源; ●谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象; ●注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。
IOC的初始化过程
- 定位并加载配置文件
- 解析配置文件中的bean节点,一个bean节点对应一个BeanDefinition对象(这个对象会保存我们在Bean节点内配置的所有内容,比如id,全限定类名,依赖值等等)
- 根据上一步的BeanDefinition集合生成(BeanDefinition对象内包含生成这个对象所需要的所有参数)所有非懒加载的单例对象,其余的会在使用的时候再实例化对应的对象。
- 依赖注入
- 后置处理
IOC容器的本质就是初始化BeanFactory和ApplicationContext
须知:依赖注入的三种方式: (1)接口注入 (2)Construct注入 (3)Setter注入
使用IOC的好处
1、IoC生成对象的方式转为外置方式,也就是把对象生成放在配置文件里进行定义,这样,当我们更换一个实现子类将会变得很简单,只要修改配置文件就可以了,完全具有热插拨的特性。 2、可维护性比较好,非常便于进行单元测试,便于调试程序和诊断故障。