文章目录
- Q1、什么是AOP?能做什么?
- Q2、解释下Spring AOP中常见的概念名词
- Q3、Spring AOP的通知有哪些类型?
- Q4、Spring AOP和AspectJ AOP有什么区别?
- Q5、JDK动态代理和CGLIB动态代理的区别是什么?
- Q6、JavaConfig方式如何启用AOP?如何强制使用cglib?
- Q7、介绍下AOP有几种实现方式?
- Q8、什么情况下AOP会失效?怎么解决?
- Q9、Spring的AOP是在哪里创建的动态代理?
- Q10、描述Spring AOP完整的实现流程?
Q1、什么是AOP?能做什么?
答案:
AOP(Aspect Oriented Programming),即面向切面编程,用于将那些与业务无关
,但却对多个对象产生影响的公共行为和逻辑
(如日志采集),抽取并封装形成切面(Aspect)后对已有方法或对象做一个增强,减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理等。
AOP与OOP?
AOP、OOP在字面上虽然非常类似,但却是面向不同领域的两种设计思想,OOP面向对象编程,针对的是业务处理过程的实体及其属性和行为进行抽象封装
,以获得更加清浙高效的逻单元划分。而AOP作为面向对象的一种补充,则是针对业务处理过程中的切面进行提取,已达到业务代码和公共行为代码之间低耦合性的隔离效果,这两种设计思想在目标上有着本质的差异。
Q2、解释下Spring AOP中常见的概念名词
答案:
- 切面:Aspect,在Spring AOP中就是切面类,切面类中管理者切点和通知
- 连接点:Join Point,在Spring AOP中就是
被增强的业务方法
,广义的来说,是程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等 - 通知:Advice,就是需要增强到业务方法中的
公共代码
,有五种类型:前置通知、后置通知、异常通知、环绕通知、返回通知 - 切点:PoinCut,即
要增强哪些方法,哪些不需要增强
,结合切点表达式来实现匹配 - 目标对象:Target Object,被增强的那个对象
- 织入:Weaving,在Spring AOP中就是为目标对象创建动态代理的过程。在Aspectj中比较繁琐,Spring不涉及。
Q3、Spring AOP的通知有哪些类型?
答案:
在AOP术语中,在的某个特定的连接点上执行的动作。Spring切面可以应用5种类型的通知:
- 前置通知 (Before):在目标方法被调用之前调用通知功能
- 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么
- 返回通知 (After-returning ) :在目标方法成功执行之后调用通知
- 异常通知 (After-throwing):在目标方法抛出异常后调用通知
- 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为
关于执行顺序:
Q4、Spring AOP和AspectJ AOP有什么区别?
先说下二者的关系:当在Spring中要使用@Aspect、@Before等这些注解时,就需要添加AspectJ相关依赖:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
Spring Aop需要 AspectJ 框架的支持
,但只用到的AspectJ的切点解析和匹配(@Aspect、@Before..等这些注解都是由AspectJ
发明的),底层的实现则没有用AspectJ的静态织入。
答案:
- AOP实现的关键在于代理模式,AOP代理主要分为静态代理和动态代理。静态代理的代表为AspectJ,动态代理则以Spring AOP为代表
- Spring AOP基于动态代理实现(JDK提供的动态代理和CGLIB动态代理)
- AspectJ是静态代理的增强,就是AOP框架会在编译阶段生成AOP代理类,
在编译阶段就将增强代码织入到Java字节码(.class)中
,因此也叫编译时增强
PS:关于织入时机,可了解下:
- AspcetJ是AOP的
完全解决方案
,能做很多Spring AOP不能做的事,Spring AOP则只专注于
解决企业级开发中常见的AOP需求 - AspectJ在实际代码运行前就完成了织入,而Spring AOP是Spring容器启动时才生成需要的代理对象,在启动上相比AspectJ会有一点性能损失
Q5、JDK动态代理和CGLIB动态代理的区别是什么?
答案:
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理:
JDK动态代理:
- JDK动态代理只提供接口的代理,不支持类的代理
- JDK会在运行时为目标类生成一个动态代理类
$proxy*.class
- 该代理类是实现了目标类接口, 并且代理类会实现接口所有的方法,并加增强代码
- 调用时 通过代理类先去调用处理类进行增强,再通过反射的方式进行调用目标方法,从而实现AOP
CGLIB动态代理:
如果代理类没有实现 接口,那么Spring AOP会选择使用CGLIB来动态代理目标:
- CGLIB的底层是通过ASM在运行时动态的生成目标类的一个子类(会生成多个,还有其他相关类,主要是为增强调用时效率)
- 并且会重写父类所有的方法增强代码
- 调用时先通过代理类进行增强,再直接调用父类对应的方法进行调用原目标方法,从而实现AOP
需要注意的是:
- CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为fnal,那么它是无法使用CGLIB做动态代理的
- CGLIB 除了生成目标子类代理类,还有一个FasiClass(路由类),可以(但不是必须)让本类方法调用进行增强,而不会像Jdk代理那样本类方法调用增强会失效
最后,关于JDK动态代理和CGLIB动态代理的性能问题:
- jdk动态代理生成类速度快,调用慢,cglib生成类速度慢,但后续调用快
- 在老版本中,CGLIB速度优于JDK动态代理
- 在新版本(JDK7、JDK8…),JDK动态代理性能要比CGLIB好20%左右,总之就是看版本
Q6、JavaConfig方式如何启用AOP?如何强制使用cglib?
答案:
- 首先需要添加Spring AOP和AspectJ的依赖
- 其次加
@EnableAspectJAutoProxy
注解
@EnableAspectJAutoProxy注解有两个属性:
- proxyTargetClass = true ,即强制使用cglib动态代理
- exposeProxy = true, 即在线程中暴露代理对象
PS:所谓在线程中暴露代理对象,就是为true时,可以拿到当前的代理对象,这样就可以防止本类方法不会增强的问题。
//拿到当前的代理对象,记得再转型为本类
AopContext.currentProxy()
//用法:本类中的方法调用其他方法
( (本类)AopContext.currentProxy() ).本类中的其他方法();
Q7、介绍下AOP有几种实现方式?
答案:
第一种
:Spring1.2,基于接口
来实现,也是现在注解背后的逻辑
第二种
:Spring2.0的schema-based配置
,使用xml以及<aop>
标签
第三种
:Spring2.0的@AspectJ配置,基于注解实现第四种
:以上三种是Spring AOP的实现,也可以直接用AspectJ框架,这时和Spring没有关系了,静态织入+AspectJ单独编译
Q8、什么情况下AOP会失效?怎么解决?
答案:
内部调用不会触发AOP
,即同一个类里的方法A内部调用方法B,则执行方法A时,中间的方法B不会被增强。
public class AopTest{
public void add(){
}
public void mod(){
this.add(); //这里不是增强的add方法
}
}
解决思路是必须走代理,用代理对象去调用
:
想拿到动态代理对象,有两种方式:
方式一:
在这个类里,注入对象自身
public class AopTest{
@Autowired
AopTest aopTest;
public void add(){
}
public void mod(){
aopTest.add(); //此时是增强的add方法
}
}
方式二:
设置暴露当前代理对象到本地线程,这样AopContext.currentProxy()就可以拿到正在调用的代理对象
@EnableAspectJAutoProxy(exposeProxy = true)
//....
public class AopTest{
public void add(){
}
public void mod(){
((AopTest) AopContext.currentProxy()).add();
//这个封装的静态方法currentProxy,底层在操作ThreadLocal
}
}
最后补充下其余失效场景:(多为使用不当)
- 方法是private修饰,需要改为public
- 目标类没有配置为Bean,Spring是为Bean去创建动态代理
Q9、Spring的AOP是在哪里创建的动态代理?
答案:
- 正常的Bean在Bean的生命周期
初始化之后
,通过Bean的后置处理器来创建aop(BeanPostProcessor.postProcessAfterInitialization)
- 还有一种特殊的情况,即在Bean的生命周期的
属性注入阶段,当出现循环依赖
,也会为循环依赖的Bean创建aop(MergedBeanDefinitionPostProcessor.postProcessMergedBeanDefinition)
Q10、描述Spring AOP完整的实现流程?
答案:
以JavaConfig为例(xml实现AOP的方式跳过):@EnableAspectJAutoProxy会通过@Import注册一个Bean的后置处理器来处理AOP,然后:
解析切面
:在创建Bean之前,调用Bean的后置处理器去解析切面,一个通知解析成一个advisor对象,这个对象中包含着通知和被增强的pointcut
创建动态代理
:正常的Bean初始化后调用BeanPostProcessor拿到之前缓存的advisor,再通过advisor里的pointcut来判断当前Bean是否被切点表达式匹配,如果匹配,就会为Bean创建动态代理
调用
:通过之前创建的动态代理,调用方法执行增强,通过调用链设计模式依次调用各个类型的通知方法