Spring Aop(初探)
什么是 AOP
AOP(Aspect-OrientedProgramming,面向切面编程),可以说是 OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP 引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP 则显得无能为力。也就是说,OOP 允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在 OOP 设计中,它导致了大量代码的重复,而不利于各个模块的重用。
而 AOP 技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP 代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹。
AOP 的基本概念
(1)Aspect(切面):通常是一个类,里面可以定义切入点和通知
(2)JointPoint(连接点):程序执行过程中明确的点,一般是方法的调用
(3)Advice(通知):AOP 在特定的切入点上执行的增强处理,有 before,after,afterReturning,afterThrowing,around
(4)Pointcut(切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式
(5)AOP 代理:AOP 框架创建的对象,代理就是目标对象的加强。Spring 中的 AOP 代理可以使 JDK 动态代理,也可以是 CGLIB 代理,前者基于接口,后者基于子类
通知方法:
前置通知:在我们执行目标方法之前运行(@Before)
后置通知:在我们目标方法运行结束之后 ,不管有没有异常**(@After)**
返回通知:在我们的目标方法正常返回值后运行**(@AfterReturning)**
异常通知:在我们的目标方法出现异常后运行**(@AfterThrowing)**
环绕通知:动态代理, 需要手动执行 joinPoint.procced()(其实就是执行我们的目标方法执行之前相当于前置通知, 执行之后就相当于我们后置通知(@Around)
Spring AOP
Spring 中的 AOP 代理还是离不开 Spring 的 IOC 容器,代理的生成,管理及其依赖关系都是由 IOC 容器负责,Spring 默认使用 JDK 动态代理,在需要代理类而不是代理接口的时候,Spring 会自动切换为使用 CGLIB 代理,不过现在的项目都是面向接口编程,所以 JDK 动态代理相对来说用的还是多一些。
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 Proxy
Spring AOP 默认使用标准的 JDK 动态代理(dynamic proxy)技术来实现 AOP 代理, 通过它, 我们可以为任意的接口实现代理. 如果需要为一个类实现代理, 那么可以使用 CGLIB 代理.
当一个业务逻辑对象没有实现接口时, 那么 Spring AOP 就默认使用 CGLIB 来作为 AOP 代理了. 即如果我们需要为一个方法织入 advice, 但是这个方法不是一个接口所提供的方法, 则此时 Spring AOP 会使用 CGLIB 来实现动态代理. 鉴于此, Spring AOP 建议基于接口编程, 对接口进行 AOP 而不是类.
#彻底理解 aspect, join point, point cut, advice
看完了上面的理论部分知识, 我相信还是会有不少朋友感觉到 AOP 的概念还是很模糊, 对 AOP 中的各种概念理解的还不是很透彻. 其实这很正常, 因为 AOP 中的概念是在是太多了, 我当时也是花了老大劲才梳理清楚的. 下面我以一个简单的例子来比喻一下 AOP 中 aspect, jointpoint, pointcut 与 advice 之间的关系.
让我们来假设一下, 从前有一个叫爪哇的小县城, 在一个月黑风高的晚上, 这个县城中发生了命案. 作案的凶手十分狡猾, 现场没有留下什么有价值的线索. 不过万幸的是, 刚从隔壁回来的老王恰好在这时候无意中发现了凶手行凶的过程, 但是由于天色已晚, 加上凶手蒙着面, 老王并没有看清凶手的面目, 只知道凶手是个男性, 身高约七尺五寸. 爪哇县的县令根据老王的描述, 对守门的士兵下命令说: 凡是发现有身高七尺五寸的男性, 都要抓过来审问. 士兵当然不敢违背县令的命令, 只好把进出城的所有符合条件的人都抓了起来.
来让我们看一下上面的一个小故事和 AOP 到底有什么对应关系. 首先我们知道, 在 Spring AOP 中 join point 指代的是所有方法的执行点, 而 point cut 是一个描述信息, 它修饰的是 join point, 通过 point cut, 我们就可以确定哪些 join point 可以被织入 Advice. 对应到我们在上面举的例子, 我们可以做一个简单的类比, join point 就相当于 爪哇的小县城里的百姓, point cut 就相当于 老王所做的指控, 即凶手是个男性, 身高约七尺五寸, 而 advice 则是施加在符合老王所描述的嫌疑人的动作: 抓过来审问. 为什么可以这样类比呢?
join point --> 爪哇的小县城里的百姓: 因为根据定义, join point 是所有可能被织入 advice 的候选的点, 在 Spring AOP 中, 则可以认为所有方法执行点都是 join point. 而在我们上面的例子中, 命案发生在小县城中, 按理说在此县城中的所有人都有可能是嫌疑人.
point cut --> 男性, 身高约七尺五寸: 我们知道, 所有的方法(joint point) 都可以织入 advice, 但是我们并不希望在所有方法上都织入 advice, 而 pointcut 的作用就是提供一组规则来匹配 joinpoint, 给满足规则的 joinpoint 添加 advice. 同理, 对于县令来说, 他再昏庸, 也知道不能把县城中的所有百姓都抓起来审问, 而是根据
凶手是个男性, 身高约七尺五寸
, 把符合条件的人抓起来. 在这里凶手是个男性, 身高约七尺五寸
就是一个修饰谓语, 它限定了凶手的范围, 满足此修饰规则的百姓都是嫌疑人, 都需要抓起来审问.advice --> 抓过来审问, advice 是一个动作, 即一段 Java 代码, 这段 Java 代码是作用于 point cut 所限定的那些 join point 上的. 同理, 对比到我们的例子中,
抓过来审问
这个动作就是对作用于那些满足男性, 身高约七尺五寸
的爪哇的小县城里的百姓
.aspect: aspect 是 point cut 与 advice 的组合, 因此在这里我们就可以类比: "根据老王的线索, 凡是发现有身高七尺五寸的男性, 都要抓过来审问" 这一整个动作可以被认为是一个 aspect.
或则我们也可以从语法的角度来简单类比一下. 我们在学英语时, 经常会接触什么 定语
, 被动句
之类的概念, 那么可以做一个不严谨的类比, 即 joinpoint
可以认为是一个 宾语
, 而 pointcut
则可以类比为修饰 joinpoint
的定语, 那么整个 aspect
就可以描述为: 满足 pointcut 规则的 joinpoint 会被添加相应的 advice 操作.
@AspectJ 支持
@AspectJ
是一种使用 Java 注解来实现 AOP 的编码风格。
@AspectJ 风格的 AOP 是 AspectJ Project 在 AspectJ 5 中引入的, 并且 Spring 也支持 @AspectJ 的 AOP 风格.
使能 @AspectJ 支持
@AspectJ 可以以 XML 的方式或以注解的方式来使能, 并且不论以哪种方式使能@ASpectJ, 我们都必须保证 aspectjweaver.jar 在 classpath 中.
使用 Java Configuration 方式使能@AspectJ
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
使用 XML 方式使能@AspectJ
<aop:aspectj-autoproxy/>
定义 aspect(切面)
当使用注解 @Aspect 标注一个 Bean 后, 那么 Spring 框架会自动收集这些 Bean, 并添加到 Spring AOP 中, 例如:
@Component
@Aspect
public class MyTest {
}
注意, 仅仅使用@Aspect 注解, 并不能将一个 Java 对象转换为 Bean, 因此我们还需要使用类似 @Component 之类的注解.` `注意, 如果一个 类被@Aspect 标注, 则这个类就不能是其他 aspect 的 **advised object** 了, 因为使用 @Aspect 后, 这个类就会被排除在 auto-proxying 机制之外.
声明 pointcut
一个 pointcut 的声明由两部分组成:
一个方法签名, 包括方法名和相关参数
一个 pointcut 表达式, 用来指定哪些方法执行是我们感兴趣的(即因此可以织入 advice).
在@AspectJ 风格的 AOP 中, 我们使用一个方法来描述 pointcut, 即:
@Pointcut("execution(* com.xys.service.UserService.*(..))") // 切点表达式
private void dataAccessOperation() {} // 切点前面
这个方法必须无返回值.
这个方法本身就是 pointcut signature, pointcut 表达式使用@Pointcut 注解指定.
上面我们简单地定义了一个 pointcut, 这个 pointcut 所描述的是: 匹配所有在包 com.xys.service.UserService 下的所有方法的执行.
切点标志符(designator)
AspectJ5 的切点表达式由标志符(designator)和操作参数组成. 如 "execution(* greetTo(..))" 的切点表达式, execution 就是 标志符, 而圆括号里的 *greetTo(..) 就是操作参数
execution
匹配 join point 的执行, 例如 "execution(* hello(..))" 表示匹配所有目标类中的 hello() 方法. 这个是最基本的 pointcut 标志符.
within
匹配特定包下的所有 join point, 例如 within(com.xys.*)
表示 com.xys 包中的所有连接点, 即包中的所有类的所有方法. 而within(com.xys.service.*Service)
表示在 com.xys.service 包中所有以 Service 结尾的类的所有的连接点.
this 与 target
this 的作用是匹配一个 bean, 这个 bean(Spring AOP proxy) 是一个给定类型的实例(instance of). 而 target 匹配的是一个目标对象(target object, 即需要织入 advice 的原始的类), 此对象是一个给定类型的实例(instance of).
bean
匹配 bean 名字为指定值的 bean 下的所有方法, 例如:
bean(*Service) // 匹配名字后缀为 Service 的 bean 下的所有方法
bean(myService) // 匹配名字为 myService 的 bean 下的所有方法
args
匹配参数满足要求的的方法. 例如:
@Pointcut("within(com.xys.demo2.*)")
public void pointcut2() {
}
@Before(value = "pointcut2() && args(name)")
public void doSomething(String name) {
logger.info("---page: {}---", name);
}
@Service
public class NormalService {
private Logger logger = LoggerFactory.getLogger(getClass());
public void someMethod() {
logger.info("---NormalService: someMethod invoked---");
}
public String test(String name) {
logger.info("---NormalService: test invoked---");
return "服务一切正常";
}
}
当 NormalService.test 执行时, 则 advice doSomething
就会执行, test 方法的参数 name 就会传递到 doSomething
中.
常用例子:
// 匹配只有一个参数 name 的方法
@Before(value = "aspectMethod() && args(name)")
public void doSomething(String name) {
}
// 匹配第一个参数为 name 的方法
@Before(value = "aspectMethod() && args(name, ..)")
public void doSomething(String name) {
}
// 匹配第二个参数为 name 的方法
Before(value = "aspectMethod() && args(*, name, ..)")
public void doSomething(String name) {
}
@annotation
匹配由指定注解所标注的方法, 例如:
@Pointcut("@annotation(com.xys.demo1.AuthChecker)")
public void pointcut() {
}
则匹配由注解 AuthChecker
所标注的方法.
常见的切点表达式
匹配方法签名
// 匹配指定包中的所有的方法
execution(* com.xys.service.*(..))
// 匹配当前包中的指定类的所有方法
execution(* UserService.*(..))
// 匹配指定包中的所有 public 方法
execution(public * com.xys.service.*(..))
// 匹配指定包中的所有 public 方法, 并且返回值是 int 类型的方法
execution(public int com.xys.service.*(..))
// 匹配指定包中的所有 public 方法, 并且第一个参数是 String, 返回值是 int 类型的方法
execution(public int com.xys.service.*(String name, ..))
匹配类型签名
// 匹配指定包中的所有的方法, 但不包括子包
within(com.xys.service.*)
// 匹配指定包中的所有的方法, 包括子包
within(com.xys.service..*)
// 匹配当前包中的指定类中的方法
within(UserService)
// 匹配一个接口的所有实现类中的实现的方法
within(UserDao+)
匹配 Bean 名字
// 匹配以指定名字结尾的 Bean 中的所有方法
bean(*Service)
切点表达式组合
// 匹配以 Service 或 ServiceImpl 结尾的 bean
bean(*Service || *ServiceImpl)
// 匹配名字以 Service 结尾, 并且在包 com.xys.service 中的 bean
bean(*Service) && within(com.xys.service.*)
声明 advice
advice 是和一个 pointcut 表达式关联在一起的, 并且会在匹配的 join point 的方法执行的前/后/周围 运行. pointcut 表达式可以是简单的一个 pointcut 名字的引用, 或者是完整的 pointcut 表达式
. 下面我们以几个简单的 advice 为例子, 来看一下一个 advice 是如何声明的.
Before advice
/**
* @author xiongyongshun
* @version 1.0
* @created 16/9/9 13:13
*/
@Component
@Aspect
public class BeforeAspectTest {
// 定义一个 Pointcut, 使用 切点表达式函数 来描述对哪些 Join point 使用 advise.
@Pointcut("execution(* com.xys.service.UserService.*(..))")
public void dataAccessOperation() {
}
}
@Component
@Aspect
public class AdviseDefine {
// 定义 advise
@Before("com.xys.aspect.PointcutDefine.dataAccessOperation()")
public void doBeforeAccessCheck(JoinPoint joinPoint) {
System.out.println("*****Before advise, method: " + joinPoint.getSignature().toShortString() + " *****");
}
}
这里, @Before 引用了一个 pointcut, 即 "com.xys.aspect.PointcutDefine.dataAccessOperation()" 是一个 pointcut 的名字. 如果我们在 advice 在内置 pointcut, 则可以:
@Component
@Aspect
public class AdviseDefine {
// 将 pointcut 和 advice 同时定义
@Before("within(com.xys.service..*)")
public void doAccessCheck(JoinPoint joinPoint) {
System.out.println("*****doAccessCheck, Before advise, method: " + joinPoint.getSignature().toShortString() + " *****");
}
}
around advice
around advice 比较特别, 它可以在一个方法的之前之前和之后添加不同的操作, 并且甚至可以决定何时, 如何, 是否调用匹配到的方法.
@Component
@Aspect
public class AdviseDefine {
// 定义 advise
@Around("com.xys.aspect.PointcutDefine.dataAccessOperation()")
public Object doAroundAccessCheck(ProceedingJoinPoint pjp) throws Throwable {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 开始
Object retVal = pjp.proceed();
stopWatch.stop();
// 结束
System.out.println("invoke method: " + pjp.getSignature().getName() + ", elapsed time: " + stopWatch.getTotalTimeMillis());
return retVal;
}
}
around advice 和前面的 before advice 差不多, 只是我们把注解 @Before 改为了 @Around 了.
基于注解的 AOP 配置方式
切面类:
package com.enjoy.cap10.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
//日志切面类
@Aspect //什么这个类是一个切面类
public class LogAspects {
//@Pointcut声明切入点
@Pointcut("execution(public int com.enjoy.cap10.aop.Calculator.*(..))")
public void pointCut(){};
//@before代表在目标方法执行前切入, 并指定在哪个方法前切入
@Before("pointCut()")
public void logStart(){
System.out.println("除法运行....参数列表是:{}");
}
@After("pointCut()")
public void logEnd(){
System.out.println("除法结束......");
}
@AfterReturning("pointCut()")
public void logReturn(){
System.out.println("除法正常返回......运行结果是:{}");
}
@AfterThrowing("pointCut()")
public void logException(){
System.out.println("运行异常......异常信息是:{}");
}
@Around("pointCut()")
public Object Around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
System.out.println("@Arount:执行目标方法之前...");
Object obj = proceedingJoinPoint.proceed();//相当于开始调div地
System.out.println("@Arount:执行目标方法之后...");
return obj;
}
}
目标方法:
package com.enjoy.cap10.aop;
public class Calculator {
//业务逻辑方法
public int div(int i, int j){
System.out.println("--------");
return i/j;
}
}
配置类:
package com.enjoy.cap10.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import com.enjoy.cap10.aop.Calculator;
import com.enjoy.cap10.aop.LogAspects;
@Configuration
@EnableAspectJAutoProxy
public class Cap10MainConfig {
@Bean
public Calculator calculator(){
return new Calculator();
}
@Bean
public LogAspects logAspects(){
return new LogAspects();
}
}
测试类:
public class Cap10Test {
@Test
public void test01(){
AnnotationConfigApplicationContext app = new AnnotationConfigApplicationContext(Cap10MainConfig.class);
Calculator c = app.getBean(Calculator.class);
int result = c.div(4, 3);
System.out.println(result);
app.close();
}
}
结果:
@Arount:执行目标方法之前...
除法运行....参数列表是:{}
--------
@Arount:执行目标方法之后...
除法结束......
除法正常返回......运行结果是:{}
1
AOP 源码赏析
在这个注解比较流行的年代里,当我们想要使用 spring 的某些功能时只需要加上一行代码就可以了,比如:
@EnableAspectJAutoProxy 开启 AOP
@EnableTransactionManagement 开启 spring 事务管理,
@EnableCaching 开启 spring 缓存
@EnableWebMvc 开启 webMvc
对于我们使用者而言十分简单便利,然而,其背后所做的事,却远远比一个注解复杂的多了,本篇只是简略的介绍一下@EnableAspectJAutoProxy 背后所发生的那些事,了解其工作原理,才能更好的运用,并从中领略大师的智慧.
废话不多说,先来看一下源码:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
/**
* Indicate whether subclass-based (CGLIB) proxies are to be created as opposed
* to standard Java interface-based proxies. The default is {@code false}.
*/
boolean proxyTargetClass() default false;
/**
* Indicate that the proxy should be exposed by the AOP framework as a {@code ThreadLocal}
* for retrieval via the {@link org.springframework.aop.framework.AopContext} class.
* Off by default, i.e. no guarantees that {@code AopContext} access will work.
* @since 4.3.1
*/
boolean exposeProxy() default false;
}
英文注解已经很详细了,这里简单介绍一下两个参数,一个是控制 aop 的具体实现方式,为 true 的话使用 cglib,为 false 的话使用 java 的 Proxy,默认为 false,第二个参数控制代理的暴露方式,解决内部调用不能使用代理的场景,默认为 false.
这里核心是@Import(AspectJAutoProxyRegistrar.class);在 AspectJAutoProxyRegistrar 里,核心的地方是
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
一个 AOP 的工具类,这个工具类的主要作用是把 AnnotationAwareAspectJAutoProxyCreator 这个类定义为 BeanDefinition 放到 spring 容器中,这是通过实现 ImportBeanDefinitionRegistrar 接口来装载的,具体装载过程不是本篇的重点,这里就不赘述,我们重点看 AnnotationAwareAspectJAutoProxyCreator 这个类.
首先看看这个类图:
从类图是可以大致了解 AnnotationAwareAspectJAutoProxyCreator 这个类的功能.它实现了一系列 Aware 的接口,在 Bean 装载的时候获取 BeanFactory(Bean 容器),Bean 的 ClassLoader,还实现了 order 接口,继承了 PorxyConfig,ProxyConfig 中主要封装了代理的通用处理逻辑,比如设置目标类,设置使用 cglib 还是 java proxy 等一些基础配置.
而能够让这个类参与到 bean 初始化功能,并为 bean 添加代理功能的还是因为它实现了 BeanPostProcessor 这个接口.这个接口的 postProcessAfterInitialization 方法会在 bean 初始化结束后(赋值完成)被调用。
这里先看一下最顶部的抽象类:AbstractAutoProxyCreator,这个抽象类主要抽象了实现代理的逻辑:
@Override public Object postProcessBeforeInitialization(Object bean, String beanName) { return bean; } // 主要看这个方法,在bean初始化之后对生产出的bean进行包装 @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean != null) { Object cacheKey = getCacheKey(bean.getClass(), beanName); if (!this.earlyProxyReferences.contains(cacheKey)) { return wrapIfNecessary(bean, beanName, cacheKey); } } return bean; } // wrapIfNecessary protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { if (beanName != null && this.targetSourcedBeans.contains(beanName)) { return bean; } if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) { return bean; } if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) { this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; } // Create proxy if we have advice. // 意思就是如果该类有advice则创建proxy, Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null); if (specificInterceptors != DO_NOT_PROXY) { this.advisedBeans.put(cacheKey, Boolean.TRUE); // 1.通过方法名也能简单猜测到,这个方法就是把bean包装为proxy的主要方法, Object proxy = createProxy( bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean)); this.proxyTypes.put(cacheKey, proxy.getClass()); // 2.返回该proxy代替原来的bean return proxy; } this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; }
总结:
1)将 AnnotationAwareAspectJAutoProxyCreator 注册到 Spring 容器中
2)AnnotationAwareAspectJAutoProxyCreator 类的 postProcessAfterInitialization()方法将所有有 advice 的 bean 重新包装成 proxy
创建 proxy 过程分析
通过之前的代码结构分析,我们知道,所有的 bean 在返回给用户使用之前都需要经过 AnnotationAwareAspectJAutoProxyCreator 类的 postProcessAfterInitialization()方法,而该方法的主要作用也就是将所有拥有 advice 的 bean 重新包装为 proxy,那么我们接下来直接分析这个包装为 proxy 的方法即可,看一下 bean 如何被包装为 proxy,proxy 在被调用方法时,是具体如何执行的
以下是 AbstractAutoProxyCreator.wrapIfNecessary(Object bean, String beanName, Object cacheKey)中的 createProxy()代码片段分析
protected Object createProxy(
Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) {
if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
}
// 1.创建proxyFactory,proxy的生产主要就是在proxyFactory做的
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this);
if (!proxyFactory.isProxyTargetClass()) {
if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
}
else {
evaluateProxyInterfaces(beanClass, proxyFactory);
}
}
// 2.将当前bean适合的advice,重新封装下,封装为Advisor类,然后添加到ProxyFactory中
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
for (Advisor advisor : advisors) {
proxyFactory.addAdvisor(advisor);
}
proxyFactory.setTargetSource(targetSource);
customizeProxyFactory(proxyFactory);
proxyFactory.setFrozen(this.freezeProxy);
if (advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
}
// 3.调用getProxy获取bean对应的proxy
return proxyFactory.getProxy(getProxyClassLoader());
}
TargetSource 中存放被代理的对象,这段代码主要是为了构建 ProxyFactory,将配置信息(是否使用 java proxy,是否 threadlocal 等),目标类,切面,传入 ProxyFactory 中
创建何种类型的 Proxy?JDKProxy 还是 CGLIBProxy?
// getProxy()方法
public Object getProxy(ClassLoader classLoader) {
return createAopProxy().getProxy(classLoader);
}
// createAopProxy()方法就是决定究竟创建何种类型的proxy
protected final synchronized AopProxy createAopProxy() {
if (!this.active) {
activate();
}
// 关键方法createAopProxy()
return getAopProxyFactory().createAopProxy(this);
}
// createAopProxy()
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
// 1.config.isOptimize()是否使用优化的代理策略,目前使用与CGLIB
// config.isProxyTargetClass() 是否目标类本身被代理而不是目标类的接口
// hasNoUserSuppliedProxyInterfaces()是否存在代理接口
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
// 2.如果目标类是接口或者是代理类,则直接使用JDKproxy
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
// 3.其他情况则使用CGLIBproxy
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
getProxy()方法
由上面可知,通过 createAopProxy()方法来确定具体使用何种类型的 Proxy,针对于该示例,我们具体使用的为 JdkDynamicAopProxy,下面来看下 JdkDynamicAopProxy.getProxy()方法
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable// JdkDynamicAopProxy类结构,由此可知,其实现了InvocationHandler,则必定有invoke方法,来被调用,也就是用户调用bean相关方法时,此invoke()被真正调用
// getProxy()
public Object getProxy(ClassLoader classLoader) {
if (logger.isDebugEnabled()) {
logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
}
Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
// JDK proxy 动态代理的标准用法
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
invoke()方法
以上的代码模式可以很明确的看出来,使用了 JDK 动态代理模式,真正的方法执行在 invoke()方法里,下面我们来看下该方法,来看下 bean 方法如何被代理执行的
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MethodInvocation invocation;
Object oldProxy = null;
boolean setProxyContext = false;
TargetSource targetSource = this.advised.targetSource;
Class<?> targetClass = null;
Object target = null;
try {
// 1.以下的几个判断,主要是为了判断method是否为equals、hashCode等Object的方法
if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
// The target does not implement the equals(Object) method itself.
return equals(args[0]);
}
else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
// The target does not implement the hashCode() method itself.
return hashCode();
}
else if (method.getDeclaringClass() == DecoratingProxy.class) {
// There is only getDecoratedClass() declared -> dispatch to proxy config.
return AopProxyUtils.ultimateTargetClass(this.advised);
}
else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
method.getDeclaringClass().isAssignableFrom(Advised.class)) {
// Service invocations on ProxyConfig with the proxy config...
return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
}
Object retVal;
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
// May be null. Get as late as possible to minimize the time we "own" the target,
// in case it comes from a pool.
target = targetSource.getTarget();
if (target != null) {
targetClass = target.getClass();
}
// 2.获取当前bean被拦截方法链表
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
// 3.如果为空,则直接调用target的method
if (chain.isEmpty()) {
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}
// 4.不为空,则逐一调用chain中的每一个拦截方法的proceed
else {
// We need to create a method invocation...
invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// Proceed to the joinpoint through the interceptor chain.
retVal = invocation.proceed();
}
...
return retVal;
}
...
}
拦截方法真正被执行调用 invocation.proceed()
public Object proceed() throws Throwable {
// We start with an index of -1 and increment early.
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
}
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
// Evaluate dynamic method matcher here: static part will already have
// been evaluated and found to match.
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
return dm.interceptor.invoke(this);
}
else {
// Dynamic matching failed.
// Skip this interceptor and invoke the next in the chain.
return proceed();
}
}
else {
// It's an interceptor, so we just invoke it: The pointcut will have
// been evaluated statically before this object was constructed.
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}
总结 4:依次遍历拦截器链的每个元素,然后调用其实现,将真正调用工作委托给各个增强器
总结:
纵观以上过程可知:实际就是为 bean 创建一个 proxy,JDKproxy 或者 CGLIBproxy,然后在调用 bean 的方法时,会通过 proxy 来调用 bean 方法
重点过程可分为:
1)将 AnnotationAwareAspectJAutoProxyCreator 注册到 Spring 容器中
2)AnnotationAwareAspectJAutoProxyCreator 类的 postProcessAfterInitialization()方法将所有有 advice 的 bean 重新包装成 proxy
3)调用 bean 方法时通过 proxy 来调用,proxy 依次调用增强器的相关方法,来实现方法切入
- 感谢你赐予我前进的力量