搞定 spring aop 面试题,这一篇就够了

微信扫一扫,分享到朋友圈

搞定 spring aop 面试题,这一篇就够了

常见问题

  • 什么是AOP

  • Spring AOP and AspectJ AOP 有什么区别?AOP 有哪些实现方式?

  • JDK动态代理和CGLIB动态代理的区别

  • 解释一下Spring AOP里面的几个名词

  • 解释下Spring在运行时通知对象

  • Spring支持什么级别的连接点

  • Spring通知有哪些类型?

  • 什么是切面 Aspect?

  • 有几种不同类型的自动代理?

字节码和机器码

  1. 机器码就是计算机可以直接执行,并且执行速度最快的代码,是电脑的CPU可直接解读的数据(计算机只认识0和1)。

  2. 字节码(byte code)是一种包含执行程序、由一序列 OP代码(操作码)/数据对 组成的二进制文件。
    字节码是一种中间码,它比机器码更抽象,需要直译器转译后才能成为机器码的中间代码。

java程序-编译

源文件由编译器编译成字节码(ByteCode)

一个常见的java源文件

package com.bugcodes.aop.step1;


/**
* @author bugcoder
* @date 2020/7/10
*/
public class Hello {


public void sayHello() {
System.out.println("Hello, AspectJ!");
}


public static void main(String[] args) {
Hello hello = new Hello();
hello.sayHello();
}
}

一个class文件主要包含常量池和方法字节码

public class com.bugcodes.aop.step1.Hello
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #8.#18 // java/lang/Object."<init>":()V
#2 = Fieldref #19.#20 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #21 // Hello, AspectJ!
#4 = Methodref #22.#23 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #24 // com/bugcodes/aop/step1/Hello
#6 = Methodref #5.#18 // com/bugcodes/aop/step1/Hello."<init>":()V
#7 = Methodref #5.#25 // com/bugcodes/aop/step1/Hello.sayHello:()V
#8 = Class #26 // java/lang/Object
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = Utf8 Code
#12 = Utf8 LineNumberTable
#13 = Utf8 sayHello
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 SourceFile
#17 = Utf8 Hello.java
#18 = NameAndType #9:#10 // "<init>":()V
#19 = Class #27 // java/lang/System
#20 = NameAndType #28:#29 // out:Ljava/io/PrintStream;
#21 = Utf8 Hello, AspectJ!
#22 = Class #30 // java/io/PrintStream
#23 = NameAndType #31:#32 // println:(Ljava/lang/String;)V
#24 = Utf8 com/bugcodes/aop/step1/Hello
#25 = NameAndType #13:#10 // sayHello:()V
#26 = Utf8 java/lang/Object
#27 = Utf8 java/lang/System
#28 = Utf8 out
#29 = Utf8 Ljava/io/PrintStream;
#30 = Utf8 java/io/PrintStream
#31 = Utf8 println
#32 = Utf8 (Ljava/lang/String;)V
{
public com.bugcodes.aop.step1.Hello();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 7: 0


public void sayHello();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String Hello, AspectJ!
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 10: 0
line 11: 8


public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: new #5 // class com/bugcodes/aop/step1/Hello
3: dup
4: invokespecial #6 // Method "<init>":()V
7: astore_1
8: aload_1
9: invokevirtual #7 // Method sayHello:()V
12: return
LineNumberTable:
line 14: 0
line 15: 8
line 16: 12
}

一个class字节码文件的数据结构如下

java程序-运行

字节码由java虚拟机解释运行

从字节码到jvm虚拟机的运行

1. java反射

常见用例

Class<?> class1 = null;
class1 = Class.forName("com.bugcodes.aop.step0.Student");
Method method = class1.getMethod("pre");
method.invoke(class1.newInstance());

method.invoke()作为反射的核心方法,其流程图如下

  • MethodAccessor就是上面提到的所有同名method共享的一个实例,由ReflectionFactory创建。一个是Java实现的。Java实现的版本在初始化时需要较多时间,但长久来说性能较好; 另一个是 native code 实现的。native 版本正好相反,启动时相对较快,但运行时间长了之后速度就比不过 Java 版了。这是 HotSpot 的优化方式带来的性能特性,同时也是许多虚拟机的共同点:跨越 native 边界会对优化有阻碍作用,它就像个黑箱一样让虚拟机难以分析也将其内联,于是运行时间长了之后反而是托管版本的代码更快些。

  • 为了权衡两个版本的性能,Sun 的 JDK 使用了

    inflation
    的技巧:让 Java 方法在被反射调用时,开头若干次使用 native 版,等反射调用次数超过阈值(

    15
    次)时则生成一个专用的 MethodAccessor 实现类,生成其中的 invoke() 方法的字节码,以后对该 Java 方法的反射调用就会使用 Java 版。

aop与springaop

  1. aop(Aspect Oriented Programming): 面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术

  2. springaop是这种技术的实现

java aop的实现方式

1、静态AOP:在编译期,切面直接以字节 码的形式编译到目标字节 码文件中。

AspectJ属于静态AOP,是在编译时进行增强,会在编译的时候将AOP逻辑织入到代码中,需要专有的编译器和织入器。

  • 优点:被织入的类性能不受影响。

  • 缺点:不够灵活

2、动态AOP(JDK动态代理):在运行期,目标类加载后,为接口动态生成代理类,将切面植入到代理类中。

Java从1.3引入动态代理。实现原理是为被代理的业务接口生成代理类,将AOP逻辑写入到代理类中,在运行时动态织入AOP,使用反射执行织入的逻辑。
主要实现方式依赖java.lang.reflect包下的InvocationHandler和Proxy类。

    • 优点:Java标准库原生支持,使用简单,无需引用额外的包。相对于静态AOP更灵活。

    • 缺点:带代理的类必须是接口,灵活性受到一些限制;使用反射会影响一些性能。

3、动态代码字节生成:在运行期,目标类加载后,动态构建字节码文件生成目标类的子类,将切面逻辑加入到子类中。

CGLib是动态代码字节生成的实现,它封装字节码生成工具Asm,原理是在运行期间目标字节码加载后,生成目标类的子类,将切面逻辑加入到子类中,所以使用Cglib实现AOP不需要基于接口。

  • 优点:没有接口也可以织入,灵活性高。

  • 缺点:扩展类的实例方法为final时,则无法进行织入

4、自定义类加载器:在运行前,目标加载前,将切面逻辑加到目标字节码中。

可以考虑javassist来实现。Javassist 是一个编辑字节码的框架,可以让你很简单地操作字节码。它可以在运行期定义或修改Class。使用Javassist实现AOP的原理是在字节码加载前直接修改需要切入的方法。

  • 优点:可以对绝大部分类织入。

  • 缺点:如果用到了其他类加载器,则这些类将不被织入

aspectj和springaop的区别

  1. AspectJ是一个比较牛逼的AOP框架,他可以对类的成员变量,方法进行拦截。由于 AspectJ 是 Java 语言语法和语义的扩展,所以它提供了自己的一套处理方面的关键字。除了包含字段和方法之外,AspectJ 的方面声明还包含切入点和通知成员。

  2. Spring使用了和aspectj一样的注解,并使用Aspectj来做切入点解析和匹配(AspectJ  5让第三方使用AspectJ的切入点解析和匹配引擎的工具API)。但是spring AOP运行时仍旧是纯的spring  AOP,并不依赖于Aspectj的编译器或者织入器

2. 静态代理

1. AspectJ
的安装和使用

1.去官网下载aspectj的jar包,关注下自己使用的jdk的版本
https://www.eclipse.org/aspectj/downloads.php
执行命令:java -jar aspectj-1.8.14.jar
2.安装完成以后需要添加环境变量
vim ./.bash_profile


export PATH=${JAVA_HOME}/bin:$PATH:/Users/zbj/aspectj1.8/bin
export CLASSPATH=${CLASSPATH}:/Users/zbj/aspectj1.8/lib/aspectjrt.jar


使用source ./.bash_profile使刚刚修改的文件生效


public static void main(String[] args) {
Hello hello = new Hello();
sayHello_aroundBody1$advice(hello, TxAspect.aspectOf(), (AroundClosure)null);
}


public void ajc$around$com_bugcodes_aop_step1_TxAspect$1$f54fe983(AroundClosure ajc$aroundClosure) {
System.out.println("开始事务...");
ajc$around$com_bugcodes_aop_step1_TxAspect$1$f54fe983proceed(ajc$aroundClosure);
System.out.println("事务结束...");
}


public static TxAspect aspectOf() {
if (ajc$perSingletonInstance == null) {
throw new NoAspectBoundException("com_bugcodes_aop_step1_TxAspect", ajc$initFailureCause);
} else {
return ajc$perSingletonInstance;
}
}

2. jdk静态代理

/**
* JDK静态代理-代理类
*
* @author bugcoder
* @date 2020/7/12
*/
public class JdkProxyService implements JdkService{


/**
* 接口
*/
private JdkService jdkService;


/**
* 构造器注入
* @param jdkService
*/
public JdkProxyService(JdkService jdkService) {
this.jdkService = jdkService;
}


/**
* 代理方法,对普通方法进行了增强
*/
@Override
public void hello() {
before();
jdkService.hello();
after();
}


/**
* 在委托方法执行之前执行
*/
private void before(){
System.out.println("我是代理,在委托方法执行之前执行");
}


/**
* 在委托方法执行之后执行
*/
private void after(){
System.out.println("我是代理,在委托方法执行之后执行");
}
}

3. 动态代理

1. jdk动态代理

2. cglib动态代理

4. spring aop

pointcut

我们如何来识别这些表达式:

  • 正则表达式

  • Aspectj切点表达式解析器

package com.bugcodes.aop.step3.pointcut;




import org.aspectj.weaver.tools.PointcutExpression;
import org.aspectj.weaver.tools.PointcutParser;
import org.aspectj.weaver.tools.ShadowMatch;


import java.lang.reflect.Method;


/**
* Aspectj切点表达式解析匹配,需要引入AspectJ的jar
*
* @author zbj
* @date 2020/7/15
*/
public class AspectJPointCut implements PointCut {


/**
* Aspectj切点解析器
*/
private static PointcutParser pointcutParser = PointcutParser.getPointcutParserSupportingAllPrimitivesAndUsingContextClassloaderForResolution();


/**
* 表达式
*/
private String expression;


/**
* pointcut表达式对象
*/
private PointcutExpression pointcutExpression;


public AspectJPointCut(String expression) {
this.expression = expression;
pointcutExpression = pointcutParser.parsePointcutExpression(expression);
}


/**
* 匹配类
*
* @param targetClass
* @return
*/
@Override
public boolean matchClass(Class<?> targetClass) {
return pointcutExpression.couldMatchJoinPointsInType(targetClass);
}


/**
* 匹配方法
*
* @param method
* @param targetClass
* @return
*/
@Override
public boolean matchMethod(Method method, Class<?> targetClass) {
ShadowMatch shadowMatch = pointcutExpression.matchesMethodExecution(method);
return shadowMatch.alwaysMatches();
}


public String getExpression() {
return expression;
}
}

advice

Spring AOP 提供了5种类型的通知

  • 前置通知(Before):在目标方法被调用之前调用通知功能。

  • 后置通知(After):在目标方法完成之后调用通知,无论该方法是否发生异常。

  • 后置返回通知(After-returning):在目标方法成功执行之后调用通知。

  • 后置异常通知(After-throwing):在目标方法抛出异常后调用通知。

  • 环绕通知(Around):在被通知的方法调用之前和调用之后执行自定义的行为。

可选择在方法前、后、异常时进行功能增强

weaving

  • 创建bean实例的时候,在bean初始化完成后,再对其进行增强

  • 对bean类及其方法挨个匹配用户指定的切面,如果有切面匹配就是要增强的

  • 通过代理的方式来织入

  • AdvisorAutoProxyCreator是AOP增强处理的具体实现

  • 判断bean实例是否要增强

/**
* 在此判断bean是否需要进行切面增强
* @param bean
* @param beanName
* @return
*/
private List<Advisor> getMatchedAdvisors(Object bean, String beanName) {
if (CollectionUtils.isEmpty(advisors)) {
return null;
}
// 得到类、类的所有方法
Class<?> beanClass = bean.getClass();
List<Method> allMethods = this.getAllMethodForClass(beanClass);
// 存放匹配的Advisor的list
List<Advisor> matchAdvisors = new ArrayList<>();
// 遍历Advisor来找匹配的
for (Advisor ad : this.advisors) {
if (ad instanceof PointCutAdvisor) {
if (isPointcutMatchBean((PointCutAdvisor) ad, beanClass, allMethods)) {
matchAdvisors.add(ad);
}
}
}
return matchAdvisors;
}





  • 在IOC容器(bean工厂)接口里面注册AOP织入(注册AOP增强处理的观察者实现)的方法

    增强逻辑代码应该写在JDK动态代理的invoke方法和cglib动态代理的intercept方法里面

  • 增强的具体实现

        //1.获取要对当前方法进行增强的advice
List<Object> shouldApplyAdvices = getShouldApplyAdvices(target.getClass(), method, advisors, beanFactory);
//2.如果有增强的advice,就责任链式增强执行,如果没有直接调用
if (CollectionUtils.isEmpty(shouldApplyAdvices)){
return method.invoke(target, args);
}else {
// 有Advice就责任链式执行增强
AopAdviceChainInvocation chain = new AopAdviceChainInvocation(proxy, target, method, args, shouldApplyAdvices);
return chain.invoke();
}

5. spring aop的使用场景

spring aop的使用场景

  • 事务处理

  • 日志记录

  • 性能统计

  • 安全控制

  • 异常处理

6. 设计模式

  • 代理模式

  • 观察者模式

  • 责任链模式

什么是AOP

AOP(Aspect-Oriented Programming),一般称为面向切面编程,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理等。

Spring AOP and AspectJ AOP 有什么区别?AOP 有哪些实现方式?

Spring AOP属于运行时增强,而AspectJ是编译时增强。

Spring AOP基于代理(Proxying),而AspectJ基于字节码操作(Bytecode Manipulation)

AOP实现的关键在于 代理模式,AOP代理主要分为静态代理和动态代理。静态代理的代表为AspectJ;动态代理则以Spring AOP为代表。

(1)AspectJ是静态代理的增强,所谓静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。

(2)Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。

Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理:

  • JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。

  • 如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。

静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。

InvocationHandler 的 invoke(Object proxy,Method method,Object[] args):proxy是最终生成的代理实例; method 是被代理目标实例的某个具体方法; args 是被代理目标实例某个方法的具体入参, 在方法反射调用时使用。

解释一下Spring AOP里面的几个名词

(1)切面(Aspect):切面是通知和切点的结合。通知和切点共同定义了切面的全部内容。 在Spring AOP中,切面可以使用通用类(基于模式的风格) 或者在普通类中以 @AspectJ 注解来实现。

(2)连接点(Join point):指方法,在Spring AOP中,一个连接点 总是 代表一个方法的执行。 应用可能有数以千计的时机应用通知。这些时机被称为连接点。连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。

(3)通知(Advice):在AOP术语中,切面的工作被称为通知。

(4)切入点(Pointcut):切点的定义会匹配通知所要织入的一个或多个连接点。我们通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。

(5)引入(Introduction):引入允许我们向现有类添加新方法或属性。

(6)目标对象(Target Object): 被一个或者多个切面(aspect)所通知(advise)的对象。它通常是一个代理对象。也有人把它叫做 被通知(adviced) 对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个 被代理(proxied) 对象。

(7)织入(Weaving):织入是把切面应用到目标对象并创建新的代理对象的过程。在目标对象的生命周期里有多少个点可以进行织入:

  • 编译期:切面在目标类编译时被织入。AspectJ的织入编译器是以这种方式织入切面的。

  • 类加载期:切面在目标类加载到JVM时被织入。需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5的加载时织入就支持以这种方式织入切面。

  • 运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。SpringAOP就是以这种方式织入切面。

Spring在运行时通知对象

通过在代理类中包裹切面,Spring在运行期把切面织入到Spring管理的bean中。代理封装了目标类,并拦截被通知方法的调用,再把调用转发给真正的目标bean。当代理拦截到方法调用时,在调用目标bean方法之前,会执行切面逻辑。

直到应用需要被代理的bean时,Spring才创建代理对象。如果使用的是ApplicationContext的话,在ApplicationContext从BeanFactory中加载所有bean的时候,Spring才会创建被代理的对象。因为Spring运行时才创建代理对象,所以我们不需要特殊的编译器来织入SpringAOP的切面。

Spring只支持方法级别的连接点

因为Spring基于动态代理,所以Spring只支持方法连接点。Spring缺少对字段连接点的支持,而且它不支持构造器连接点。方法之外的连接点拦截功能,我们可以利用Aspect来补充。

Spring通知有哪些类型?

在AOP术语中,切面的工作被称为通知,实际上是程序执行时要通过SpringAOP框架触发的代码段。

Spring切面可以应用5种类型的通知:

  1. 前置通知(Before):在目标方法被调用之前调用通知功能;

  2. 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;

  3. 返回通知(After-returning ):在目标方法成功执行之后调用通知;

  4. 异常通知(After-throwing):在目标方法抛出异常后调用通知;

  5. 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。

同一个aspect,不同advice的执行顺序: ①没有异常情况下的执行顺序: around before advice before advice target method 执行 around after advice after advice afterReturning ②有异常情况下的执行顺序: around before advice before advice target method 执行 around after advice after advice afterThrowing:异常发生 java.lang.RuntimeException: 异常发生

什么是切面 Aspect?

aspect 由 pointcount 和 advice 组成,切面是通知和切点的结合。 它既包含了横切逻辑的定义, 也包括了连接点的定义. Spring AOP 就是负责实施切面的框架, 它将切面所定义的横切逻辑编织到切面所指定的连接点中. AOP 的工作重心在于如何将增强编织目标对象的连接点上, 这里包含两个工作:

  • 如何通过 pointcut 和 advice 定位到特定的 joinpoint 上

  • 如何在 advice 中编写切面代码.

可以简单地认为, 使用 @Aspect 注解的类就是切面.

有几种不同类型的自动代理?

BeanNameAutoProxyCreator

DefaultAdvisorAutoProxyCreator

微信扫一扫,分享到朋友圈

搞定 spring aop 面试题,这一篇就够了

全能旗舰+全品类潮品 11.11焕新来华为商城抢福利

上一篇

精心为你准备的最全的 20 道 MySQL 面试题

下一篇

你也可能喜欢

搞定 spring aop 面试题,这一篇就够了

长按储存图像,分享给朋友