问题:Spring AOP代理中的运行时期,是在初始化时期织入仍是获取对象时期织入?java
织入就是代理的过程,指目标对象进行封装转换成代理,实现了代理,就能够运用各类代理的场景模式。spring
简单点来定义就是切面,是一种编程范式。与OOP对比,它是面向切面,为什么须要切面,在开发中,咱们的系统从上到下定义的模块中的过程当中会产生一些横切性的问题,这些横切性的问题和咱们的主业务逻辑关系不大,假如不进行AOP,会散落在代码的各个地方,形成难以维护。AOP的编程思想就是把业务逻辑和横切的问题进行分离,从而达到解耦的目的,使代码的重用性、侵入性低、开发效率高。sql
在这里问题中,也有一个相似的一对IOC和DI(dependency injection)的关系,AOP能够理解是一种编程目标,Spring AOP就是这个实现这个目标的一种手段。同理IOC也是一种编程目标,DI就是它的一个手段。数据库
在Spring官网能够看到,AOP的实现提供了两种支持分别为@AspectJ、Schema-based AOP。其实在Spring2.5版本时,Spring本身实现了一套AOP开发的规范和语言,可是这一套规范比较复杂,可读性差。以后,Spring借用了AspectJ编程风格,才有了@AspectJ的方式支持,那么何为编程风格。编程
SpringAOP和AspectJ的详细对比,在以后的章节会在进行更加详细的说明,将会在他们的背景、织入方法、性能作介绍。安全
阅读官网,是咱们学习一个新知识的最好途径,这个就是Spring AOP的核心概念点,跟进它们的重要性,我作了从新的排序,以便好理解,这些会为咱们后续的源码分析起到做用。bash
Aspect:切面;使用@Aspect注解的Java类来实现,集合了全部的切点,作为切点的一个载体,作一个比喻就像是咱们的一个数据库。 Tips:这个要实现的话,必定要交给Spirng IOC去管理,也就是须要加入@Component。app
Pointcut:切点;表示为全部Join point的集合,就像是数据库中一个表。源码分析
Join point:链接点;俗称为目标对象,具体来讲就是servlet中的method,就像是数据库表中的记录。性能
Advice:通知;这个就是before、after、After throwing、After (finally)。
Weaving:把代理逻辑加入到目标对象上的过程叫作织入。
target:目标对象、原始对象。
aop Proxy:代理对象 包含了原始对象的代码和增长后的代码的那个对象。
Tips 这个应用点,有不少的知识点可让咱们去挖掘,好比Pointcut中execution、within的区别,我相信你去针对性搜索或者官网都未必能有好的解释,稍后会再专门挑一个文章作重点的使用介绍;
为了回答咱们的一开始的问题,前面的几个章节咱们作了一些简单的概念介绍作为铺垫,那么接下来咱们回归正题,正面去切入问题。以码说话,咱们以最简洁的思路把AOP实现,咱们先上代码。
项目结构介绍
项目目录结构,比较简单,5个主要的文件;
package com.will.config;
@Configuration
@ComponentScan("com.will")
@EnableAspectJAutoProxy(proxyTargetClass = false)
public class AppConfig {
}
复制代码
WilAspect.java ;按照官网首推的方式(@AspectJ support),实现AOP代理。
package com.will.config;
/**
* 定义一个切面的载体
*/
@Aspect
@Component
public class WilAspect {
/**
* 定义一个切点
*/
@Pointcut("execution(* com.will.dao.*.*(..))")
public void pointCutExecution(){
}
/**
* 定义一个Advice为Before,并指定对应的切点
* @param joinPoint
*/
@Before("pointCutExecution()")
public void before(JoinPoint joinPoint){
System.out.println("proxy-before");
}
}
复制代码
Dao.java
package com.will.dao;
public interface Dao {
public void query();
}
复制代码
UserDao.java
package com.will.dao;
import org.springframework.stereotype.Component;
@Component
public class UserDao implements Dao {
public void query() {
System.out.println("query user");
}
}
复制代码
Test.java
package com.will.test;
import com.will.config.AppConfig;
import com.will.dao.Dao;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Test {
public static void main(String[] args) {
/**
* new一个注册配置类,启动IOC容器,初始化时期;
*/
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
/**
* 获取Dao对象,获取对象时期,并进行query打印
*/
Dao dao = annotationConfigApplicationContext.getBean(Dao.class);
dao.query();
annotationConfigApplicationContext.start();
}
}
复制代码
好了,这样咱们总体的AOP代理就已经完成。
到底是哪一个时期进行对象织入的,好比Test类中,到底是第一行仍是第二行进行织入的,咱们只能经过源码进行分析,假如是你,你会进行如何的分析源码解读。
Spring的代码很是优秀,同时也很是复杂,那是一个大项目,里面进行了不少的代码封装,那么的代码你三天三夜也读不完,甚至于你都不清楚哪一行的该留意的,哪一行是起到关键性做用的,这里教几个小技巧。
debug模式StepInfo(F5)
后,进入 AbstractApplicationContext.getBean
方法,这个是Spring应用上下文中最重要的一个类,这个抽象类中提供了几乎ApplicationContext
的全部操做。这里第一个语句返回void,咱们能够直接忽略,看下面的关键性代码。
继续debug后,会进入到 DefaultListableBeanFactory
类中,看以下代码
return new NamedBeanHolder<>(beanName, getBean
(beanName, requiredType, args));
复制代码
在该语句中,这个能够理解为 DefaultListableBeanFactory
容器,帮咱们获取相应的Bean。
进入到AbstractBeanFactory
类的doGetBean
方法以后,咱们运行完。
Object sharedInstance = getSingleton(beanName);
复制代码
语句以后,看到sharedInstance
对象打印出&Proxyxxx ,说明在getSingleton
方法的时候就已经获取到了对象,因此须要跟踪进入到 getSingleton
方法中,继续探究。
不方便不方便咱们进行问题追踪到这个步骤以后,我须要引入IDEA的条件断点,不方便咱们进行问题追踪由于Spring会初始化不少的Bean,咱们再ObjectsharedInstance=getSingleton(beanName);
加入条件断点语句。
继续debug进入到DefaultSingletonBeanRegistry
的getSingleton
方法。 咱们观察下执行完ObjectsingletonObject=this.singletonObjects.get(beanName);
以后的singletonObject
已经变成为&ProxyUserDao,这个时候Spring最关键的一行代码出现了,请注意这个this.singletonObjects
。
this.singletonObjects
就是至关IOC容器,反之IOC容器就是一个线程安全的线程安全的HashMap,里面存放着咱们须要Bean。
咱们来看下singletonObjects
存放着的数据,里面就有咱们的UserDao
类。
这就说明,咱们的初始化的时期进行织入的,上图也有整个Debug模式的调用链。
经过上一个环节已经得知是在第一行进行初始化的,可是它在初始化的时候是何时完成织入的,抱着求知的心态咱们继续求证。
仍是那个问题,那么多的代码,个人切入点在哪里?
既然singletonObjects
是容器,存放咱们的Bean,那么找到关键性代码在哪里进行存放(put方法)就能够了。因而咱们经过搜索定位到了。
咱们经过debug模式的条件断点和debug调用链模式,就能够进行探索。
这个时候借助上图中的调用链,咱们把思路放到放到IDEA帮我定位到的两个方法代码上。
DefaultSingletonBeanRegistry.getSingleton
咱们一步步断点,得知,当运行完singletonObject=singletonFactory.getObject();
以后,singletonObject
已经得到了代理。
至此咱们知道,代理对象的获取关键在于singletonFactory
对象,因而又定位到了AbstractBeanFactorydoGetBean
方法,发现singletonFactory
参数是由createBean
方法创造的。这个就是Spring中IOC容器最核心的地方了,这个代码的模式也值得咱们去学习。
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
});
复制代码
这个第二个参数是用到了jdk8中的lambda,这一段的含义是就是为了传参,重点看下 createBean(beanName,mbd,args);
代码。随着断点,咱们进入到这个类方法里面。
AbstractAutowireCapableBeanFactory.createBean
中的;
ObjectbeanInstance=doCreateBean(beanName,mbdToUse,args)
方法;
doCreateBean
方法中,作了简化。
Initialize the bean instance.
Object exposedObject = bean;
try {
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
...
return exposedObject;
复制代码
当运行完 exposedObject=initializeBean(beanName,exposedObject,mbd);
以后,咱们看到exposedObject
已是一个代理对象,并执行返回。这一行代码就是取判断对象要不要执行代理,要的话就去初始化代理对象,不须要直接返回。后面的initializeBean
方法是涉及代理对象生成的逻辑(JDK、Cglib),后续会有一个专门的章节进行详细介绍。
经过源码分析,咱们得知,Spring AOP的代理对象的织入时期是在运行Spring初始化的时候就已经完成的织入,而且也分析了Spring是如何完成的织入。