Java 岗大厂面试百日冲刺 - 日积月累,每日三题【Day16
车票
本栏目 Java 开发岗高频面试题主要出自以下各技术栈:Java基础知识
、集合容器
、并发编程
、JVM
、Spring全家桶
、MyBatis等ORMapping框架
、MySQL数据库
、Redis缓存
、RabbitMQ消息队列
、Linux操作技巧
等。
=================================================================================
AOP
(Aspect Oriented Programming),面向切面思想,是 Spring 的三大核心思想之一(两外两个:IOC-控制反转、DI-依赖注入)。AOP 主要应用于处理一些具有横切性质的系统级服务
,如日志收集
、事务管理
、安全检查(权限校验)
、缓存
、对象池管理
等。
那么 AOP 是干啥的?在我们的程序中,经常存在一些系统性的需求,比如权限校验、记录日志等,这些代码会散落穿插在各个业务逻辑中,非常冗余且不利于维护。例如下面这个示意图:
有多少业务操作,就要写多少重复的校验和日志记录代码,这显然是无法接受的。当然,用面向对象的思想,我们可以把这些重复的代码抽离出来,写成公共方法
,就是下面这样:
这样,代码冗余和可维护性的问题得到了解决,但每个业务方法中依然要依次手动调用这些公共方法,也是略显繁琐。有没有更好的方式呢?有的,那就是AOP
,AOP 将权限校验、日志记录等非业务代码完全提取出来,与业务代码分离,并寻找节点切入业务代码中:
使用 AOP 的好处主要是降低模块的耦合度
、使系统容易扩展
、提高代码复用性
。
简单地去理解,其实 AOP 要做三类事:
在哪里切入,也就是权限校验等非业务操作在哪些业务代码中执行。
在什么时候切入,是业务代码执行前还是执行后。
切入后做什么事,比如做权限校验、日志记录等。
因此,AOP 的体系可以梳理为下图:
AOP 的一些概念:
切入点(Pointcut)
:决定处理如权限校验、日志记录等在何处切入业务代码中(即织入切面)。切点分为 execution 方式和 annotation 方式。前者可以用路径表达式指定哪些类织入切面,后者可以指定被哪些注解修饰的代码织入切面。通知(Advice)
:我们也叫它处理(即“切面”对于某个“连接点”所产生的动作
),包括处理时机和处理内容。处理内容就是要做什么事,比如校验权限和记录日志。处理时机就是在什么时机执行处理内容,分为前置处理(即业务代码执行前)、后置处理(业务代码执行后)等。切面(Aspect)
:即 Pointcut 和 Advice。连接点(Joint point)
:是程序执行的一个点。例如,一个方法的执行或者一个异常的处理。在 Spring AOP 中,一个连接点总是代表一个方法执行。织入(Weaving)
:就是通过动态代理,在目标对象方法中执行处理内容的过程。目标对象(Target Object)
:被一个或者多个切面所通知的对象。AOP代理(AOP Proxy)
在 Spring AOP 中有两种代理方式,JDK动态代理和CGLIB代理
。
在 AOP 术语中,切面的工作被称为通知
,实际上是程序执行时要通过SpringAOP框架触发的代码段
。
Spring 切面可以应用 5 种类型的通知:
前置通知(Before):在目标方法被调用之前调用通知功能;
后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
返回通知(After-returning ):在目标方法成功执行之后调用通知;
异常通知(After-throwing):在目标方法抛出异常后调用通知;
环绕通知(Around):通知包裹了被通知的方法,
在被通知的方法调用之前和调用之后执行自定义的行为
。
追问 2:在同一个切面(Aspect)中,不同 Advice 的执行顺序
无
异常情况下:
/不同 Advice 的执行顺序****/
around before advice
before advice
target method (执行代码段)
around after advice
after advice
/前五个都一样/
afterReturning
/*************************************************/
有
异常情况下:
/不同 Advice 的执行顺序****/
around before advice
before advice
target method (执行代码段)
around after advice
after advice
/前五个都一样/
afterThrowing:异常发生
java.lang.RuntimeException: 异常发生
/*************************************************/
被群友一致认为的济南泉城广场????。。。坐标:深圳
作者:對你何止一句钟意
(航拍)
面试题 2:AspectJ AOP 和 Spring AOP 有什么区别?
===================================================================================================
AOP 实现的关键在于代理模式
,AOP 代理主要分为静态代理
和动态代理
。
静态代理的代表为 AspectJ;
动态代理则以 Spring AOP 为代表;
Spring AOP 和 AspectJ 有不同的目标。
Spring AOP 旨在通过 Spring IoC 提供一个简单的 AOP 实现,以解决编码人员面临的最常出现的问题。这并不是完整的 AOP 解决方案,
它只能用于Spring容器管理的beans
。另一方面,AspectJ 是最原始的 AOP 实现技术,提供了玩这个的 AOP 解决方案。AspectJ 更为健壮,相对于 Spring AOP 也显得更为复杂。值得注意的是,AspectJ 能够被应用于所有的领域对象。
从原理上看:
Spring AOP
基于动态代理来实现,默认如果使用接口的,用 JDK 提供的动态代理实现,如果是方法则使用 CGLIB 实现
Spring AOP 需要依赖 IOC
容器来管理,并且只能作用于 Spring 容器,使用纯 Java 代码实现
在性能上,由于 Spring AOP 是基于动态代理来实现的,在容器启动时需要生成代理实例,在方法调用上也会增加栈的深度,使得 Spring AOP 的性能不如 AspectJ 的那么好
AspectJ
AspectJ 属于静态代理(织入),通过修改代码来实现,有如下几个织入的时机:
编译期织入
(Compile-time weaving): 如类 A 使用 AspectJ 添加了一个属性,类 B 引用了它,这个场景就需要编译期的时候就进行织入,否则没法编译类 B。编译后织入
(Post-compile weaving): 也就是已经生成了 .class 文件,或已经打成 jar 包了,这种情况我们需要增强处理的话,就要用到编译后织入。类加载后织入
(Load-time weaving): 指的是在加载类的时候进行织入,要实现这个时期的织入,有几种常见的方法。1、自定义类加载器来干这个,这个应该是最容易想到的办法,在被织入类加载到 JVM 前去对它进行加载,这样就可以在加载的时候定义行为了。2、在 JVM 启动的时候指定 AspectJ 提供的 agent:-javaagent:xxx/xxx/aspectjweaver.jar。
AspectJ 可以做 Spring AOP 干不了的事情,它是 AOP 编程的完全解决方案,Spring AOP 则致力于解决企业级开发中最普遍的 AOP(方法织入)。而不是成为像 AspectJ 一样的 AOP 方案。
因为 AspectJ 在实际运行之前就完成了织入,所以说它生成的类是没有额外运行时开销的
| 指标项 | Spring AOP | AspectJ |
| --- | --- | --- |
| 使用语言 | 在纯 Java 中实现 | 使用 Java 编程语言的扩展实现 |
| 是否需要编译 | 不需要单独的编译过程 | 除非设置 LTW,否则需要 AspectJ 编译器 (ajc) |
| 织入方式 | 只能使用运行时织入 | 运行时织入不可用。支持编译时、编译后和加载时织入 |
| 织入能力 | 功能不强-仅支持方法级编织 | 更强大 – 可以编织字段、方法、构造函数、静态初始值设定项、最终类/方法等……。 |
| 适用范围 | 只能在由 Spring 容器管理的 bean 上实现 | 可以在所有域对象上实现 |
| 切入点要求 | 仅支持方法执行切入点 | 支持所有切入点 |
| 代理局限 | 代理是由目标对象创建的, 并且切面应用在这些代理上 | 在执行应用程序之前 (在运行时) 前, 各方面直接在代码中进行织入 |
| 性能 | 比 AspectJ 慢很多 | 更好的性能 |
| 复杂度 | 易于学习和应用 | 相对于 Spring AOP 来说更复杂 |
追问 1:了解 JDK 动态代理和 CGLIB 动态代理的原理么?他俩有哪些区别?
Spring AOP 中的动态代理主要有两种方式,JDK动态代理
和CGLIB动态代理
:
JDK动态代理
:是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用 InvokeHandler 来处理,他有一个限制,就是它只能为接口创建代理实例,那么对于没有通过接口定义业务方法的类
,就要用CGLIB动态代理
了。
CGLIB(Code Generation Library)动态代理
:是一个基于 ASM 的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理,在子类中采用方法拦截的技术拦截所有父类方法的调用并顺势织入横切逻辑。
JDK动态代理具体实现原理:
通过实现 InvocationHandler 接口创建自己的调用处理器;
通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理;
通过反射机制获取动态代理类的构造函数,其唯一参数类型就是调用处理器接口类型;
通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数参入;
JDK 动态代理是面向接口的代理模式,如果被代理目标没有接口那么 Spring 也无能为力,Spring 通过 Java 的反射机制生产被代理接口的新的匿名实现类,重写了其中 AOP 的增强方法。
CGLib动态代理:
利用 ASM 开源包,对代理对象类的 class 文件加载进来,通过修改其字节码生成子类来处理。
如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理实现 AOP;
如果目标对象实现了接口,可以强制使用 CGLIB 实现 AOP;
如果目标对象
没有实现了接口
,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
;
3、两者对比:
JDK 动态代理是
面向接口的
。CGLib 动态代理是通过字节码底层继承要代理类来实现(
对指定的类生成一个子类,覆盖其中的方法
),因此如果被代理类被 final 关键字所修饰,会失败。
评论