AOP - 面向切面编程(Aspect Oriented Programming)
Spring早期版本的核心功能:管理对象生命周期与对象分配。
- 即Bean本身的管理创建,以及它整个生命周期里跟其他对象相互之间引用装配
为了更好的实现管理和装配,一个自然而然的想法就是,加一个中间层代理(字节码增强)来实现所有对象的托管。
- 为了更好的实现Bean的管理装配,特别是需要对他做一些增强。所以在可以不改变原来这些Bean本身的定义功能的前提下,进一步把需要增强的的部分、需要额外处理的部分。通过这个中间层来解决。做增强的这个中间层这个面就是所谓的切面。
补充:
- 计算机领域有一句至理名言:计算机领域的任何问题都可以通过增加一个中间层来解决。
IoC - 控制反转(Inversion Of Control)
也成为DI(Dependency Injection)依赖注入。对象装配思路的改进。
从对象A直接引用和操作对象B,变成对象A里只需要依赖一个接口IB,系统启动和装配阶段,把IB接口的实例对象注入到对象A,这样A就不需要依赖一个IB接口的具体实现,也就是类B。
从而可以实现在不修改代码的情况,修改配置文件,即可以运行时替换成注入IB接口另一实现类C的一个对象实例。
补充:
- 为什么叫DI:
- 正常情况下写一个接口,在一个包或者某一个类里需要用到它具体的一个实现类,写在另外一个包里。
- 在常规编程中,我们需要在定义这个接口使用的地方,直接new出来这个接口的实现类,这样的话,上层就需要依赖下层的某个东西。
- 实际上我们更期望这个灵活的编程模型能够做到:在上层使用的时候定义一个接口,我们现在不管他实现类是什么,在运行期只要有一个实现类塞进来,就可以装配进去,放在这里使用。
- 相当于装修房子:先在墙上留出来一个位置,准备放一幅画。只要把这个位置大小留好了,那幅画最终入住的时候放进来,是油画、水彩、素描都可以。只要我们留出来这个位置是合适的。那么在前期装修房子的时候,先就不用特别关注于这一小块,到底以后用什么类型的画。
- 这么做还有一个好处:就是当装修完房子以后,发现我现在放的这幅油画我不喜欢了,我就随时可以使用一个水彩画替换掉这个油画。
- 也就是说当我们实现依赖注入(控制反转)的方式装配对象,那么在运行期可以不去改我们的代码,改一下装配、配置这块的一些配置(配置文件、注解等),就可以把我们在运行期实际上这一块这个位置上使用的东西直接给替换掉。
循环依赖
- 场景一:比如说一个对象(A),里面需要用某个接口,这个接口它实际的实现类需要用到(B)的实例,(B)里面某个属性需要用一个接口,那个接口他的具体实现类是(A)。
解决:
Spring Bean:类A和类B,他们俩都各自先独立被创建出来的。再在后面的过程中,对它内部的这些属性进行装配。这个时候这两个对象的引用本身是有了,所以就可以彼此的给对方赋值了。也别是显式的拿到一个Bean,再把他通过代码显式的方式注入到其他的一些使用的对象里面去的时候,这个问题就更不是问题了。
AOP:假如说在里面做了AOP,这时候拿到的是一个代理类,或者增强的一个运行期生成的子类,中间做一层代理,那么更可以解决所谓的循环依赖的问题。
什么类型的循环依赖Spring无法处理?除了Spring,循环依赖还有哪些类似场景?
- 场景二:A对象在构造函数里需要传进来:一个B对象才能初始化(或者一个接口,接口实例是B,才能初始化),反过来B也需要再new出来它的实例,构造函数里面,参数要传一个A才能初始化。这个时候Spring就没办法解决了。常规的编写Java代码也解决不了。
- 这两个东西相当于死锁了,每个对象自己创建一个实例的时候,都需要另外的那个对象,另一个对象也没有,有需要这个对象。这样来回调就会导致堆栈溢出了。
- 所以常规说,属性之间的这种循环依赖,Spring是可以解决的。构造器里产生的循环依赖Spring是解决不了的。
- 循环依赖本质原因是会导致死锁(频繁调用或来来回回的赋值都会导致死锁),所以要尽量避免。
AOP代理方式
在Spring里可以把一个类型注册成Spring里的一个Bean,这时候Spring就会帮我们把这个Bean初始化,变成一个可用的对象。加入我们需要在上面做一些增强,就是我们所谓的AOP。这时候我们就需要在中间加一层代理类或者增强类。
- JdkProxy:假如说这个对象所在的类上面有接口(基于接口来做的),Spring会默认使用JdkProxy(JDK的动态代理),来生成一个代理,在代理里进一步的把所有对这个类做的增强操作,放到代理执行的代码里面。然后先做了增强的操作,再去调用原本的类的他的方法。
- proxyTargetClass:如果要代理的类有接口但想强制不用默认JDK的动态代理,也是用字节码增强的技术,就可以开启proxyTargetClass选项。同CGlib。
- CGlib:假如说要增强或代理的这个类没有接口,只有一个类的定义,Spring会默认使用CGlib,对他做字节码增强。相当于硬生生的给他生成一个子类。在这个子类里,当我们调用原先这个类的某个方法时,先做增强操作,再去调原本类的方法,最后再把结果返回回来。
总之,Spring AOP面向切面的增强功能,都是作用在方法上的!
小插曲:两种常用的注入方法:
- @Autowired(required = true):默认是按类型注入。required=true表示启动的时候就找该对象(就是把他先配置好)还是当我们去调用该对象(调用某个属性或者某个方法)时,再看他要不要装配(懒加载)。
- @Resource(name = "student"):默认按名字注入
配置AOP(常用两种方式):
- 代码+XML:around、before、after
- 全注解:
同时配置文件开启自动代理:
AOP在工作中的作用:
- 在调用service具体一些业务方法的时候,想在前面打一些日志。
- 通过前后两次取时间戳来减一下,来统计所有业务方法执行的时间。
- 在调用某一类业务方法时,判断用户有没有权限。
- 在一系列业务方法前后加上事务的控制。
- 比如startTransaction、commitTransaction(模拟事务控制)。
等等当我们需要对已有的这些业务,写好的一些方法进行一些额外的处理、控制的时候,我们就需要AOP了。
AOP可以再不改变原有代码的基础上,给我们做这些基于方法的额外的增强。
类似CGlib做字节码增强的工具有哪些?
怎么理解字节码增强操作?
- 在面向对象里,先定义一个对象,然后显式调用对象的方法,或者基于接口调用具体某个对象的方法,这时所有操作都是显式的。写代码的时候就必须知道这个方法有,而且能够调用这个方法,调的到他。
- 反射:很多时候我们要操作一批对象,这批对象给过来的时候,我们都不知道他具体的类型(比如传参都是Object),但是我们约定好了,不管是什么类型,他都有一个ding()方法,这个方法参数是空的,这个时候就可以通过反射的方式,用这些对象.getClass(),.getMethod("ding")获取到具体的ding方法。然后再通过反射:method.invoke()来执行ding方法,这样一来反射相当于在某种程度上破坏了单纯的面向对象这个良好的封装。
// 创建对象
Object instance=clazz.getDeclaredConstructor().newInstance();
// 执行方法
Method method=clazz.getMethod(methodName);
method.invoke(instance);
- 面向对象在我们看来,就想医生给病人做检查的时候,通过外部来看一下(起色,体温等)。反射相当于拍CT、拍X光片,胃镜等深入到内部去吧内部的一些信息通过反射的东西窥探出来。
- 不管是面向对象本身的显式调用还是反射这种隐式调用的方式,都是在对象现有的结构上做的,但是字节码操作不同。他是在运行期,通过在内存里拼一个新的类型,这种类型需要的字节码就相当于动态的创建出来了一个新的类。
- 还是拿看病举例:字节码技术相当于创造了新的基因,创造了新的物种。凭空创造出了一个新的个体出来了。
字节码增强新工具:ByteBuddy
提供了更友好的API,比较常用,做好了很方便的函数,方便使用。(机器语言->汇编语言->高级语言),比上面那些机器语言更高的汇编语言,写起来更方便。
Instrumentation
- 既然字节码技术能够创建出来一些新的类型,继承原有老的类型。即在运行期动态创建一些子类型。那有没有一种技术可以直接修改jar包里的一些字节码?--->
- Jdk的新技术:Instrumentation。作用在JVM加载一个jar包,把jar包完整的加载到内存之前的一个预处理的步骤上,通过Java Agent技术,就可以再中间拦一层,跟AOP类似,但他是AOP在整个JVM上。就可以把Jar包在往JVM加载的时候中间在agent层做一次预处理,就可以把原先Jar包里的一些字节码直接给替换掉。
- 这个技术就相当于在字节码技术创建新物种的程度上更深入了一层,可以直接替换现有的一些生物个体他们内部的基因片段。
- 常见的应用场景:
- APM
- 应用性能监控、全链路监控
相关文章
暂无评论...