死磕 Spring 之 AOP 篇 - Spring AOP 常见面试题,java 高级开发工程师简历
熔断,如:Netflix Hystrix
限流和降级:如:Alibaba Sentinel
认证和授权,如:Spring Security
监控,如:JMX
性能场景
缓存,如 Spring Cache
超时控制
可以说在我们的日常开发环境中都是离不开 AOP 的。
简述 AOP 中几个比较重要的概念
在 AOP 中有以下几个概念:
AspectJ:切面,只是一个概念,没有具体的接口或类与之对应,是 Join point,Advice 和 Pointcut 的一个统称。
Join point:连接点,指程序执行过程中的一个点,例如方法调用、异常处理等。在 Spring AOP 中,仅支持方法级别的连接点。
Advice:通知,即我们定义的一个切面中的横切逻辑,有“around”,“before”和“after”三种类型。在很多的 AOP 实现框架中,Advice 通常作为一个拦截器,也可以包含许多个拦截器作为一条链路围绕着 Join point 进行处理。
Pointcut:切点,用于匹配连接点,一个 AspectJ 中包含哪些 Join point 需要由 Pointcut 进行筛选。
Introduction:引介,让一个切面可以声明被通知的对象实现任何他们没有真正实现的额外的接口。例如可以让一个代理对象代理两个目标类。
Weaving:织入,在有了连接点、切点、通知以及切面,如何将它们应用到程序中呢?没错,就是织入,在切点的引导下,将通知逻辑插入到目标方法上,使得我们的通知逻辑在方法调用时得以执行。
AOP proxy:AOP 代理,指在 AOP 实现框架中实现切面协议的对象。在 Spring AOP 中有两种代理,分别是 JDK 动态代理和 CGLIB 动态代理。
Target object:目标对象,就是被代理的对象。
你知道哪几种 AOP 框架?
主流 AOP 框架:
AspectJ:完整的 AOP 实现框架
Spring AOP:非完整的 AOP 实现框架
Spring AOP 是基于 JDK 动态代理和 Cglib 提升实现的,两种代理方式都属于运行时的一个方式,所以它没有编译时的一个处理,那么因此 Spring 是通过 Java 代码实现的。AspectJ 自己有一个编译器,在编译时期可以修改 .class 文件,在运行时也会进行处理。
Spring AOP 有别于其他大多数 AOP 实现框架,目的不是提供最完整的 AOP 实现(尽管 Spring AOP 相当强大);相反,其目的是在 AOP 实现和 Spring IoC 之间提供紧密的集成,以提供企业级核心特性。
Spring AOP 从未打算与 AspectJ 竞争以提供全面的 AOP 解决方案,我们认为 Spring AOP 等基于代理实现的框架和 AspectJ 等成熟的框架都是有价值的,并且它们是互补的,而不是竞争关系。Spring 将 Spring AOP 和 IoC 与 AspectJ 无缝集成,以实现 AOP 的所有功能都可以在一个 Spring 应用中。这种集成不会影响 Spring AOP API 或 AOP Alliance API,保持向后兼容。
什么是 AOP 代理?
代理模式是一种结构性设计模式,通过代理类为其他对象提供一种代理以控制对这个对象的访问。AOP 代理是 AOP 框架中 AOP 的实现,主要分为静态代理和动态代理,如下:
静态代理:代理类需要实现被代理类所实现的接口,同时持有被代理类的引用,新增处理逻辑,进行拦截处理,不过方法还是由被代理类的引用所执行。静态代理通常需要由开发人员在编译阶段就定义好,不易于维护。
常用 OOP 继承和组合相结合
AspectJ,在编辑阶段会织入 Java 字节码,且在运行期间会进行增强。
动态代理:不会修改字节码,而是在 JVM 内存中根据目标对象新生成一个 Class 对象,这个对象包含了被代理对象的全部方法,并且在其中进行了增强。
JDK 动态代理
字节码提升,例如 CGLIB
讲讲 JDK 动态代理?
基于接口代理,通过反射机制生成一个实现代理接口的类,在调用具体方法时会调用 InvocationHandler 来处理。
需要借助 JDK 的?java.lang.reflect.Proxy
?来创建代理对象,调用?`Proxy.newProxyInsta
nce(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)`?方法创建一个代理对象,方法的三个入参分别是:
ClassLoader loader
:用于加载代理对象的 Class 类加载器Class<?>[] interfaces
:代理对象需要实现的接口InvocationHandler h
:代理对象的处理器
新生成的代理对象的 Class 对象会继承?Proxy
,且实现所有的入参?interfaces
?中的接口,在实现的方法中实际是调用入参?InvocationHandler
?的?invoke(..)
?方法。
为什么 JDK 动态代理只能基于接口代理,不能基于类代理?
因为 JDK 动态代理生成的代理对象需要继承?
Proxy
?这个类,在 Java 中类只能是单继承关系,无法再继承一个代理类,所以只能基于接口代理。
为什么 InvocationHandler 不直接声明到这个代理对象里面,而是放入继承的?
Proxy
?父类中?
我觉得代理类既然是 JDK 动态生成的,那么 JDK 就需要识别出哪些类是生成的代理类,哪些是非代理类,或者说 JDK 需要对代理类做统一的处理,这时如果没有一个统一的类 Proxy 来进行引用根本无法处理。这只是笔者的想法,具体为什么这么做不知道有小伙伴知道不 ~
讲讲 CGLIB 动态代理?
JDK 动态代理的目标对象必须是一个接口,在我们日常生活中,无法避免开发人员不写接口直接写类,或者根本不需要接口,直接用类进行表达。这个时候我们就需要通过一些字节码提升的手段,来帮助做这个事情,在运行时,非编译时,来创建一个新的 Class 对象,这种方式称之为字节码提升。在 Spring 内部有两个字节码提升的框架,ASM(过于底层,直接操作字节码)和 CGLIB(相对于前者更加简便)。
CGLIB 动态代理则是基于类代理(字节码提升),通过 ASM(Java 字节码的操作和分析框架)将被代理类的 class 文件加载进来,修改其字节码生成一个子类。
需要借助于 CGLIB 的?org.springframework.cglib.proxy.Enhancer
?类来创建代理对象,设置以下几个属性:
Class<?> superClass
:被代理的类Callback callback
:回调接口
新生成的代理对象的 Class 对象会继承?superClass
?被代理的类,在重写的方法中会调用?callback
?回调接口(方法拦截器)进行处理。
如果你想设置一个 Callback[] 数组去处理不同的方法,那么需要设置一个 CallbackFilter 筛选器,用于选择具体的方法使用数组中的哪个 Callback 去处理
JDK 动态代理和 CGLIB 动态代理有什么不同?
两者都是在 JVM 运行时期新创建一个 Class 对象,实例化一个代理对象,对目标类(或接口)进行代理。JDK 动态代理只能基于接口进行代理,生成的代理类实现了这些接口;而 CGLIB 动态代理则是基于类进行代理的,生成的代理类继承目标类,但是不能代理被 final 修饰的类,也不能重写 final 或者 private 修饰的方法。
CGLIB 动态代理比 JDK 动态代理复杂许多,性能也相对比较差。
Spring AOP 和 AspectJ 有什么关联?
Spring AOP 和 AspectJ 都是 AOP 的实现框架,AspectJ 是 AOP 的完整实现,Spring AOP 则是部分实现。AspectJ 有一个很好的编程模型,包含了注解的方式,也包含了特殊语法。Spring 认为 AspectJ 的实现在 AOP 体系里面是完整的,不需要在做自己的一些实现。
Spring AOP 整合 AspectJ 注解与 Spring IoC 容器,比 AspectJ 的使用更加简单,也支持 API 和 XML 的方式进行使用。不过 Spring AOP 仅支持方法级别的 Pointcut 拦截。
为什么 Spring AOP 底层没有使用 AspectJ 动态代理创建代理对象?
我觉得是因为 AspectJ 的特殊语法对于 Spring 或者 Java 开发人员来说不是很友好,使用起来可能有点困难。Spring 也选择整合 AspectJ 的注解,使用起来非常方便。
Spring AOP 中有哪些 Advice 类型?
Around Advice,围绕型通知器,需要主动去触发目标方法的执行,这样可以在触发的前后进行相关相关逻辑处理
Before Advice,前置通知器,在目标方法执行前会被调用
After Advice,后置通知器
AfterReturning,在目标方法执行后被调用(方法执行过程中出现异常不会被调用)
After,在目标方法执行后被调用(执行过程出现异常也会被调用)
AfterThrowing,执行过程中抛出异常后会被调用(如果异常类型匹配)
执行顺序(Spring 5.2.7 之前的版本):Around “前处理” > Before > 方法执行 > Around “后处理” > After > AfterReturning|AfterThrowing
执行顺序(Spring 5.2.7 开始):Around “前处理” > Before > 方法执行 > AfterReturning|AfterThrowing > After > Around “后处理”
如下(在后续文章会进行分析,Spring 5.1.14):
评论