AOP相关面试题整理

IOC过程:

  1. 加载 xml 配置文件,遍历其中的标签

  2. 遍历标签,通过反射加载 beanClass,Class.forName(className),创建bean,beanClass.newInstance()

  3. 遍历 标签,利用反射将 bean 相关字段访问权限设为可访问,将属性和引用的字段填充进bean

  4. 将 bean 注册到 bean 容器中 beanMap.put(id, bean);

IOC 容器解决循环依赖

缓存 用途
singletonObjects 用于存放完全初始化好的 bean,从该缓存中取出的 bean 可以直接使用
earlySingletonObjects 存放原始的 bean 对象(尚未填充属性),用于解决循环依赖。”早期引用“是指向原始对象的引用。
singletonFactories 存放 bean 工厂对象,用于解决循环依赖

``Spring IOC解决循环依赖是依靠缓存。IOC的加载过程是 创建bean实例,属性实例注入,回调实例实现的接口方法。在创建bean实例,尚未注入属性实例之前,Spring将未填充属性的bean加入到二级缓存中,当bean引用到依赖的实例时先从缓存中获取空壳的bean,也就是“早期引用”。当被引用的bean被实例化后再去一级缓存中读取到可以直接使用的bean。A对象的创建需要引用到B对象,而B对象的创建也需要A对象,而此时当B对象创建的时候直接从缓存里引用A对象(虽然不是完全体A对象,毕竟没有赋值处理),当B对象完成创建以后再被A对象引用进去,则A对象也完成了创建。`

  • AOP过程:

    1. 获取所有的切面类。

    2. 遍历容器中的类。

    3. 使用 AspectJ 表达式筛选通知器

      AOP 中,切点 Pointcut 是用来匹配连接点的,以 AspectJExpressionPointcut 类型的切点为例。该类型切点实现了ClassFilter 和 MethodMatcher 接口,匹配的工作则是由 AspectJ 表达式解析器复杂。除了使用 AspectJ 表达式进行匹配,Spring 还提供了基于正则表达式的切点类,以及更简单的根据方法名进行匹配的切点类。

    4. 尝试进行Aspect的织入 : 首先按照order的值进行升序排序,确保order值小的aspect先织入

    5. Spring 是如何将 AOP 和 IOC 模块整合到一起的

      即通过拓展点 BeanPostProcessor 接口

    6. 最后调用Enhancer.create(targetClass, methodInterceptor),返回的的 bean 是代理对象,而非原始的 bean

AopContext.currentProxy()用于获取当前的代理对象。当 expose-proxy 被配置为 true 时,该代理对象会被放入 ThreadLocal 中

要想启用Spring AOP,配置类加上注解@EnableAspectJAutoProxy,会往spring容器注入一个BeanPostProcessor即AnnotationAwareAspectJAutoProxyCreator
在Bean实例化完成后(即调用构造函数将对象创建出来)会执行AnnotationAwareAspectJAutoProxyCreator#postProcessAfterInitialization(这个方法在父类AbstractAutoProxyCreator中,AnnotationAwareAspectJAutoProxyCreator并没有重写)
当执行完成AbstractAutoProxyCreator#postProcessAfterInitialization这个方法会将原生对象变成代理代理对象,代理对象中写入了横切的逻辑

什么是 AOP

AOP(Aspect-Oriented Programming), 即 面向切面编程 , 它与 OOP( Object-Oriented Programming, 面向对象编程) 相辅相成, 提供了与 OOP 不同的抽象软件结构的视角. 在 OOP 中, 我们以类(class)作为我们的基本单元, 而 AOP 中的基本单元是 Aspect(切面)。AOP要达到的效果是保证开发者在不修改代码的前提下,为系统中某些不同的组件提供一些公共的功能。

SpringAOP优点?

  • 集中处理某一关注点/横切逻辑
  • 可以很方便的添加/删除关注点
  • 侵入性少,增强代码可读性及维护性

SpringAOP实际应用举例?

比如我们用Dubbo对外提供服务,里面有个多个方法,为了不给调用方奇怪的错误,会把每个方法里的异常都catch主,只返回一个result,调用方会根据这个result的success判断此次是否调用成功。如果服务里所有的方法都有这样相同的处理逻辑,那么此时就用上了AOP

比如特殊权限验证,参数修正等操作。

I,记录日志。

II,监控性能。

III,权限控制。

IV,缓存优化。

V,事务管理。

Spring AOP不支持代理类内部方法调用的拦截

在spring的源代码中通过一个增强对象的检查,控制了当前的内部调用是否使用代理来执行

因为目标方法是通过代理类调用的,但是内部调用属于普通调用,所以无法触发拦截,所以需要通过上下文获取代理类

public class BeanA{
    public void method1(){
   	 	method2();
    }
    public void method2(){
        ...
    }
}

public void method1(){
		logger.error("1");
		
		// 如果希望调用的内部方法也被拦截,那么必须用过上下文获取代理对象执行调用,而不能直接内部调用,否则无法拦截
		if(null != AopContext.currentProxy()){
			((NorQuickNewsDAO)AopContext.currentProxy()).method2();
		}else{
			method2();
		}		
	}
	
	public void method2(){
		logger.error("2");
	}
<property name="exposeProxy">
    <value>true</value>
</property>

Aspect(切面)

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

  1. 如何通过 pointcut 和 advice 定位到特定的 joinpoint 上。

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

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

advice(增强)

由 aspect 添加到特定的 join point(即满足 point cut 规则的 join point) 的一段代码. 许多 AOP框架, 包括 Spring AOP, 会将 advice 模拟为一个拦截器(interceptor), 并且在 join point 上维护多个 advice, 进行层层拦截. 例如 HTTP 鉴权的实现, 我们可以为每个使用 RequestMapping 标注的方法织入 advice, 当 HTTP 请求到来时, 首先进入到 advice 代码中, 在这里我们可以分析这个 HTTP 请求是否有相应的权限, 如果有, 则执行 Controller, 如果没有, 则抛出异常. 这里的 advice 就扮演着鉴权拦截器的角色了。

连接点(join point)

程序运行中的一些时间点, 例如一个方法的执行, 或者是一个异常的处理.在 Spring AOP 中, join point 总是方法的执行点, 即只有方法连接点。

切点(point cut)

匹配 join point 的谓词(a predicate that matches join points). Advice 是和特定的 point cut 关联的, 并且在 point cut 相匹配的 join point 中执行.在 Spring 中, 所有的方法都可以认为是 joinpoint, 但是我们并不希望在所有的方法上都添加 Advice, 而 pointcut 的作用就是提供一组规则(使用 AspectJ pointcut expression language 来描述) 来匹配joinpoint, 给满足规则的 joinpoint 添加 Advice。

关于join point 和 point cut 的区别

在 Spring AOP 中, 所有的方法执行都是 join point. 而 point cut 是一个描述信息, 它修饰的是 join point, 通过 point cut, 我们就可以确定哪些 join point 可以被织入 Advice. 因此 join point 和 point cut 本质上就是两个不同纬度上的东西.advice 是在 join point 上执行的, 而 point cut 规定了哪些 join point 可以执行哪些 advice。

introduction

为一个类型添加额外的方法或字段. Spring AOP 允许我们为 目标对象 引入新的接口(和对应的实现). 例如我们可以使用 introduction 来为一个 bean 实现 IsModified 接口, 并以此来简化 caching 的实现。

目标对象(Target)

织入 advice 的目标对象. 目标对象也被称为 advised object.因为 Spring AOP 使用运行时代理的方式来实现 aspect, 因此 adviced object 总是一个代理对象(proxied object)注意, adviced object 指的不是原来的类, 而是织入 advice 后所产生的代理类。

AOP proxy

一个类被 AOP 织入 advice, 就会产生一个结果类, 它是融合了原类和增强逻辑的代理类. 在 Spring AOP 中, 一个 AOP 代理是一个 JDK 动态代理对象或 CGLIB 代理对象。

织入(Weaving)

将 aspect 和其他对象连接起来, 并创建 adviced object 的过程. 根据不同的实现技术, AOP织入有三种方式:

  • 编译器织入, 这要求有特殊的Java编译器。
  • 类装载期织入, 这需要有特殊的类装载器。
  • 动态代理织入, 在运行期为目标类添加增强(Advice)生成子类的方式. Spring 采用动态代理织入, 而AspectJ采用编译器织入和类装载期织入。

advice 的类型

  • before advice, 在 join point 前被执行的 advice. 虽然 before advice 是在 join point 前被执行, 但是它并不能够阻止 join point 的执行, 除非发生了异常(即我们在 before advice 代码中, 不能人为地决定是否继续执行 join point 中的代码)。
  • after return advice, 在一个 join point 正常返回后执行的 advice。
  • after throwing advice, 当一个 join point 抛出异常后执行的 advice。
  • after(final) advice, 无论一个 join point 是正常退出还是发生了异常, 都会被执行的 advice。
  • around advice, 在 join point 前和 joint point 退出后都执行的 advice. 这个是最常用的 advice。

AOP的实现原理

  • JDK动态代理
  • CGLIB动态代理

静态代理和动态代理的区别?

静态代理是程序运行前代理类的字节码文件就已经生成,动态代理是程序运行时利用反射创建生成字节码。

静态代理怎么实现?

AOP相关面试题整理

Proxy和RealSubject实现同一个接口,Proxy再在自己的request()内添加一些自己的逻辑,然后调用RealSubject的request()方法

静态代理的劣势?

根据单一职责原则,一个代理类只能代理一个委托类,同时如果委托类的每个方法都要增强相同的逻辑,如添加日志,那么每个方法都要单独写一下。

5分钟写下JDK动态代理并解释原理?

//以下是伪代码
//target目标类
pubic class InterfaceClass{ void A();} 
pubic class RealObject implements InterfaceClass{void 
    @override
    A(//买车
	);
    }
public class ProxyInvocation implements InvocationHandler{
    private Object target;
    public ProxyInvocation(Object target){
        this.target = target;
    };
    @override
    void invoke((Object proxy, Method method, Object[] args)	{
        //登记
        method.invoke(target,args);
        //提车
    }
}
public class ProxyFactory {
    private Object target;
    public ProxyFactory(Object target){
        this.target = target;
    };
    Object getProxyInstance(){
            return Proxy.newProxyInstance(target.getClass().getClassLoader(), 			target.getClass().getInterfaces(),new ProxyInvocation(target) );
    }
	main(String args[]){
    RealObject realObject = new RealObject();
    InterfaceClass interfaceClass = new ProxyFactory(realObject).getProxyInstance();
        interfaceClass.A();
	}
    
}

谈谈CGLib动态代理?

jdk动态代理的委托类必须是接口的实现类,CGLib则没有这个限制。

jdk动态代理的增强写在InvocationHandler的invoke方法里面,CGLib的增强写在MethodInterceptor的intercept里面。

具体看看用法:

public class MyInterceptor implements MethodInterceptor{
    @override 
    Object intecept(Object obj, Method method, Object[] args, MethodProxy proxy){
        proxy.invokeSuper(obj,args);
    }
}

public class CGlibProxy{
    void main(String[] args){
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperClass(RealObject.class);
        enhancer.setCallback(new MyInterceptor());
        RealObject proxyObject = (RealObject)enhancer.creat();
        proxyObject.request();
    }
}

CGlib动态代理使用上有啥限制吗?

​ cglib实现动态代理,要求类必须不能被fianl修饰。

JDK动态代理的拦截对象是通过反射的机制来调用被拦截方法的,CGlib通过什么机制来提升了方法的调用效率?

FastClass机制,简单来说,就是给每个方法上添加索引,通过索引的形式调用方法比反射调用要快。

Cglib中提供FastClass增强功能,FastClass顾名思义是一个能让被增强类更快调用的Class,主要针对调用方法是变量的场景,用于替代反射调用。

FastClass的实现逻辑,是生成增强类实现invoke方法,invoke方法中,用switch语义将被增强类的所有方法调用枚举出来。用户使用FastClass.invoke方法,传入方法签名和被调用实例,从而达到不使用反射就能实现不确定方法的调用。

SpringAOP的代理是什么?

JDK–>AspectJ–>invokeHandler

CGlib–>AspectJ–>enhancer设置委托类,接口类,回调函数intercepter

默认调用jdk,如果委托类不是接口类型,则会调用cglib

当我们需要强制使用CGLIB来实现AOP的时候,需要配置spring.aop.proxy-target-class=true@EnableAspectJAutoProxy(proxyTargetClass = true)

<aop:aspectj-autoproxy proxy-target-class="true"/>

CGlib实现SpringAOP?

自定义注解

自定义Aspect使用接口

取出所有的切面类,通过类上必须包含的接口和实现类进行过滤

利用AspectJ的表达式识别,再次过滤符合表达式命名规则的切面类

取出所有类,遍历,每一个targetClass和使用AlspectJ过滤后的AspectInfoList作比较,留下符合当前targetClass的AspectInfoList。

新建一个类实现MethodInterceptor接口,重写intercept方法。方法里面内容是依次执行AspectInfoList的advice和targetclass的方法。

使用cglib的Enhancer去设superclasscallback,生成代理类

JDK动态代理实现SpringAOP?

自定义注解

自定义Aspect使用接口

取出所有的切面类,通过类上必须包含的接口和实现类进行过滤

利用AspectJ的表达式识别,再次过滤符合表达式命名规则的切面类

取出所有类,遍历,每一个targetClass和使用AlspectJ过滤后的AspectInfoList作比较,留下符合当前targetClass的AspectInfoList。

新建一个类,实现InvocationHandler接口,重写invoke方法,依次调用AspectInfoList里的方法,方法里面利用反射,method.invoke去调用targetClass所属的方法。

最后利用反射:Proxy.newProxyInstance(targetClass.getClass().getClassLoader(),targetClass.getClass().getInterfaces(), InvocationHandler的实现类);

自研Spring框架之AOP

漫画:AOP 面试造火箭事件始末

Spring AOP 是怎么运行的?彻底搞定这道面试必考题

cglib 动态代理原理分析

上一篇:异常


下一篇:如何实现一个简易版的 Spring - 如何实现 AOP(上)