在之前的文章中我已经详细的说明了关于Spring的aop的使用,大家有兴趣的可以翻一下之前的文章【spring】Spring的aop的使用详解
今天,我就带着大家一起来走进aop源码,看看其内部具体是如何实现的。
UTF-8
UTF-8
1.8
1.8
1.8
5.1.6.RELEASE
4.12
1.7.35
1.8.9
1.2.27
org.springframework
spring-context
${spring.version}
org.springframework
spring-test
${spring.version}
junit
junit
${junit.version}
test
org.slf4j
slf4j-log4j12
${slf4j.version}
org.aspectj
aspectjweaver
${aspectjweaver.version}
com.alibaba
fastjson
${json.version}
package com.sxx.controller.service;
public interface UserService {
void work();
}
package com.sxx.controller.service.impl;
import com.sxx.controller.service.UserService;
public class UserServiceImpl implements UserService {
@Override
public void work() {
System.out.println("UserServiceImpl执行了");
}
}
package com.sxx.controller.aop;
import org.aspectj.lang.JoinPoint;
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.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Component
@Aspect
public class UserServiceAspect {
@Pointcut(value = "execution(* com.sxx.controller.service.impl.*.* (..))")
public void pointCut() {
System.out.println("@进入切点...");
}
//环绕通知(连接到切入点开始执行,下一步进入前置通知,在下一步才是执行操作方法)
@Around(value = "pointCut()")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("@Around进入环绕通知...");
long startTime = System.currentTimeMillis();
// 调用了下一个拦截器,递归调用ReflectiveMethodInvocation#proceed
joinPoint.proceed(); // ===> debug into,环绕round的下一个链
long endTime = System.currentTimeMillis();
System.out.printf("@Around方法执行耗时>>>>>: %s%n", endTime - startTime);
}
//前置通知(进入环绕后执行,下一步执行方法)
@Before(value = "pointCut()")
public void before(JoinPoint joinPoint) {
System.out.println("@Before进入前置通知:" + Arrays.toString(joinPoint.getArgs()));
}
//异常通知(出错时执行)
@AfterThrowing(value = "pointCut()", throwing = "ex")
public void afterThrowing(JoinPoint joinPoint, Throwable ex) {
System.out.println("@AfterThrowing进入异常通知" + Arrays.toString(joinPoint.getArgs()));
}
//后置通知(返回之前执行)
@After(value = "pointCut()")
public void after() {
System.out.println("@After进入后置通知...");
}
//最终通知(正常返回通知,最后执行,出现异常)
@AfterReturning(value = "pointCut()")
public void afterReturning() {
System.out.println("@AfterReturning进入最终通知...End!");
}
}
package com.sxx.controller;
import com.sxx.controller.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class UserController {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:/application.xml");
UserService userService = (UserService) applicationContext.getBean("userService");
System.out.println(userService.getClass().getName());
userService.work();
}
}
com.sun.proxy.$Proxy16
@Around进入环绕通知...
@Before进入前置通知:[]
UserServiceImpl执行了
@Around方法执行耗时>>>>>: 0
@After进入后置通知...
@AfterReturning进入最终通知...End!
通过上面的执行结果我们可以看出,在打印userService.getClass().getName()打印出来的是com.sun.proxy.$Proxy16,我们知道proxy是代理类,我们在getBean的时候获取的应该是UserServiceImpl对象,怎么会被代理了呢?Spring又是在哪一步对UserServiceImpl做了代理动作呢?或许我们可以从源码中了解一二。
先放张aop的流程图:
我们知道Spring在初始化Bean的时候有自己的一套流程,aop在代理bean这一过程肯定是发生在Spring初始化,那么具体是在初始化哪一步呢?如果大家对Spring Bean的生命周期还不太清楚,可以看下我之前的文章:
【Spring】Spring中bean的生命周期
我们知道,如果想要代理一个事物,首先这个事物应该是已经存在(我们应当是可以进行描述),只有这样我们才能知道被替代的事物有哪些功能和属性等信息,然后才能创建一个新的代理去代替他。所以,代理类的创建肯定是在被代理类的bean已经创建好的阶段发生的,而Spring创建好bean后最后一步调用的是BeanPostProcessor,因此我们可以猜测,Spring创建代理类的是在BeanPostProcessor中。
创建代理的入口是在initializeBean中,具体的可以查看org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean(),源码如下:
protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction
通过源码我们可以知道,在initializeBean主要做了三件事:
我们重点关注bean的后置处理器,这个是Spring aop的入口。
@Override
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
throws BeansException {
Object result = existingBean;
//aop
//此处getBeanPostProcessors()有8个内置后置处理器;
// 生成代理会调用里面的 AnnotationAwareAspectJAutoProxyCreator
for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
//Aop调用AnnotationAwareAspectJAutoProxyCreator#postProcessAfterInitialization
// 所以,最好在这个for里,下面这行打断点,加断点表达式:
// aop入口
AnnotationAwareAspectJAutoProxyCreator
Object current = beanProcessor.postProcessAfterInitialization(result, beanName);
if (current == null) {
return result;
}
result = current;
}
return result;
}
注意:只有需要创建代理类的bean才能进入到AbstractAutoProxyCreator#postProcessAfterInitialization方法中,因此需要debug很多次才能进入。
//如果当前的bean适合被代理,则需要包装指定的bean
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) throws BeansException {
if (bean != null) {
// 根据给定的bean的class和name构建一个key
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (!this.earlyProxyReferences.contains(cacheKey)) {
// 如果当前的bean适合被代理,则需要包装指定的bean
// aop:注意看这个bean,这还是UserServiceImpl本尊,那么进去以后,情况变了~
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
可以看到postProcessAfterInitialization方法首先会给当前需要包装的bean构造一个key,然后判断当前的bean是否已经创建好对应的代理类了,如果没有就调用wrapIfNecessary方法去创建,那么wrapIfNecessary方法里面又做了哪些东西呢?
//目标:代理对象的创建就藏在这货里面!!!
//1、判断当前bean是否已经生成过代理对象
//2、拿到切面类中的所有增强方法(拦截器:环绕、前置、后置等)
//3、生成代理对象
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
//
// 判断是否为空 否在TargetSource缓存中存在
if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
return bean;
}
// advisedBeans缓存了不需要代理的bean(为false的),如果缓存中存在,则可以直接返回
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
//Infrastructure基础设施
// 判断bean是否为Spring自带bean,如果是,不用进行代理的
// shouldSkip()则用于判断当前bean是否应该被略过
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
// 对当前bean进行缓存
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
//AOP: 【关键点 1 】
// 找到哪些aspect的execution能匹配上当前bean
// 匹配上的话列出前后和置换的方法(拦截器:环绕、前置、后置等),并且!!排好顺序 ===>
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null); // ===> debug进
//debug后,仔细看一下上面这个数组内的值,其实就是UserServiceImpl里的匹配上的方法信息!
//如果有匹配上的方法,就生成代理对象【关键点!】
if (specificInterceptors != DO_NOT_PROXY) {
// 允许创建代理对象的,设置为false
this.advisedBeans.put(cacheKey, Boolean.TRUE);
// 【关键节点 2 】
// 开始创建AOP代理!!!!!!!!!!
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean)); // ===>
// 缓存生成的代理bean的类型,并且返回生成的代理bean
this.proxyTypes.put(cacheKey, proxy.getClass());
//此处返回的代理和在Main函数中返回的是一样的
//说明此处代理成功创建,debug看一下proxy对象信息,变了~真身被隐藏
return proxy;
}
//如果拿到的增强方法为空,缓存起来(使用false标记不需要代理)
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
从wrapIfNecessary源码我们知道,它是Spring创建代理对象的核心,里面除了对创建代理对象时做的一些检查和校验外,最主要的功能有三个:
在这里我们要重点关注两个方面,一是Spring是如何找到bean对应的环绕方法,二是Spring创建aop代理对象的过程。
从刚才的源码中我们可以看出,Spring找到bean对应的环绕方法主要过程是在getAdvicesAndAdvisorsForBean这个方法中的,我们可以继续进去看看这个方法具体做了哪些东西。
@Override
@Nullable
protected Object[] getAdvicesAndAdvisorsForBean(Class> beanClass, String beanName, @Nullable TargetSource targetSource) {
//AOP: 遍历所有注解了 @Aspectj 的类里的方法,将满足当前bean拦截条件的,封装成Advisor
List advisors = findEligibleAdvisors(beanClass, beanName); // ===>
if (advisors.isEmpty()) {
return DO_NOT_PROXY;
}
return advisors.toArray(); // 最终以数组形式返回
}
从上面可以看出getAdvicesAndAdvisorsForBean里面几乎没有做其他操作,只是调用了findEligibleAdvisors方法,看来真正实现这个功能是它调用的findEligibleAdvisors方法,这个方法具体做了哪些东西,我们继续点进去看。
protected List findEligibleAdvisors(Class> beanClass, String beanName) {
// 这是所有的切面里的方法(before,around,after……)!
List candidateAdvisors = findCandidateAdvisors();
// ===> 这是Pointcut匹配符合当前bean的
List eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName); // ===> 进去!
if (!eligibleAdvisors.isEmpty()) {
// 排序,将来按这个顺序执行责任链!!!
eligibleAdvisors = sortAdvisors(eligibleAdvisors);
} // 将来执行责任链的时候,就是按照这个顺序,一环环推进的
return eligibleAdvisors;
}
从findEligibleAdvisors方法中我们大致可以推测这个方法中完成的以下几件事:
那么这些功能又是怎么实现的呢?接下来我们一个个跟踪源码解析这些方法。
protected List findCandidateAdvisors() {
Assert.state(this.advisorRetrievalHelper != null, "No BeanFactoryAdvisorRetrievalHelper available");
return this.advisorRetrievalHelper.findAdvisorBeans();
}
findCandidateAdvisors本身没有做什么,内部主要是调用advisorRetrievalHelper.findAdvisorBeans()方法,具体内容我们跟进去看下
public List findAdvisorBeans() {
// Determine list of advisor bean names, if not cached already.
String[] advisorNames = null;
synchronized (this) {
advisorNames = this.cachedAdvisorBeanNames;
if (advisorNames == null) {
// Do not initialize FactoryBeans here: We need to leave all regular beans
// uninitialized to let the auto-proxy creator apply to them!
advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this.beanFactory, Advisor.class, true, false);
this.cachedAdvisorBeanNames = advisorNames;
}
}
if (advisorNames.length == 0) {
return new LinkedList<>();
}
List advisors = new LinkedList<>();
for (String name : advisorNames) {
if (isEligibleBean(name)) {
if (this.beanFactory.isCurrentlyInCreation(name)) {
if (logger.isDebugEnabled()) {
logger.debug("Skipping currently created advisor '" + name + "'");
}
}
else {
try {
advisors.add(this.beanFactory.getBean(name, Advisor.class));
}
catch (BeanCreationException ex) {
Throwable rootCause = ex.getMostSpecificCause();
if (rootCause instanceof BeanCurrentlyInCreationException) {
BeanCreationException bce = (BeanCreationException) rootCause;
String bceBeanName = bce.getBeanName();
if (bceBeanName != null && this.beanFactory.isCurrentlyInCreation(bceBeanName)) {
if (logger.isDebugEnabled()) {
logger.debug("Skipping advisor '" + name +
"' with dependency on currently created bean: " + ex.getMessage());
}
// Ignore: indicates a reference back to the bean we're trying to advise.
// We want to find advisors other than the currently created bean itself.
continue;
}
}
throw ex;
}
}
}
}
return advisors;
}
在这个方法里只做了一件事,就是把BeanFactoryUtils.beanNamesForTypeIncludingAncestors拿到所有有切面方法bean的name集合,然后通过this.beanFactory.getBean拿到当前所有bean的通知。
protected List findAdvisorsThatCanApply(
List candidateAdvisors, Class> beanClass, String beanName) {
ProxyCreationContext.setCurrentProxiedBeanName(beanName);
try {
return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass); // ===>
}
finally {
ProxyCreationContext.setCurrentProxiedBeanName(null);
}
}
findAdvisorsThatCanApply主要作用是根据当前beanName去获取对应的通知,从源码中可以看出除了将当前beanName放到和清除ThreadLocal的动作外还有一个方法AopUtils.findAdvisorsThatCanApply,我们根据方法名字即可猜测挑选Bean的通知方法应该是在这里完成的,我们可以进去看下其源码。
public static List findAdvisorsThatCanApply(List candidateAdvisors, Class> clazz) {
if (candidateAdvisors.isEmpty()) {
return candidateAdvisors;
}
List eligibleAdvisors = new LinkedList<>();
for (Advisor candidate : candidateAdvisors) {
if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
eligibleAdvisors.add(candidate);
}
}
boolean hasIntroductions = !eligibleAdvisors.isEmpty();
for (Advisor candidate : candidateAdvisors) {
if (candidate instanceof IntroductionAdvisor) {
// already processed
continue;
}
if (canApply(candidate, clazz, hasIntroductions)) { // ===> aop 会进canApply 判断 切面表达式是否符合当前bean
eligibleAdvisors.add(candidate); // 如果满足条件,就添加到可用数组里去,最终返回去
}
}
return eligibleAdvisors;
}
ps:关于IntroductionAdvisor和PointcutAdvisor大家可以自行百度下,本章就不再讲解。
public static boolean canApply(Advisor advisor, Class> targetClass, boolean hasIntroductions) {
if (advisor instanceof IntroductionAdvisor) {
return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
}
else if (advisor instanceof PointcutAdvisor) {
PointcutAdvisor pca = (PointcutAdvisor) advisor;
return canApply(pca.getPointcut(), targetClass, hasIntroductions); // ===> aop 进!
}
else {
// It doesn't have a pointcut so we assume it applies.
return true;
}
}
aop的通知属于PointcutAdvisor,因此最终走的是canApply方法,那么这个方法具体怎么做的呢?
public static boolean canApply(Pointcut pc, Class> targetClass, boolean hasIntroductions) {
Assert.notNull(pc, "Pointcut must not be null");
if (!pc.getClassFilter().matches(targetClass)) {
return false;
}
// 留心这个 MethodMatcher 里的:pointcutExpression , 发现了什么???
MethodMatcher methodMatcher = pc.getMethodMatcher();
if (methodMatcher == MethodMatcher.TRUE) {
// No need to iterate the methods if we're matching any method anyway...
return true;
}
IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
}
Set> classes = new LinkedHashSet<>(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
classes.add(targetClass); // targetClass 就是我们的 SlaveImpl 这个bean
for (Class> clazz : classes) {
Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz); // 遍历它所有的方法,跟aop切面表达式匹配!
for (Method method : methods) { // 注意这个method,是不是我们的work方法???
if ((introductionAwareMethodMatcher != null &&
introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions)) ||
methodMatcher.matches(method, targetClass)) { // 方法名是否匹配,就在这里!
return true; // 表达式 匹配这个方法,就返回true
}
}
}
return false;
}
至此,Spring根据beanName去匹配对应的通知方法的过程就结束了,下面就剩最重要一步,即Spring为aop创建代理对象的过程。
从之前的源码可知,aop创建代理类的过程注释藏在AbstractAutoProxyCreator#createProxy,我们可以dubug进去追一下这个方法的源码。
protected Object createProxy(Class> beanClass, @Nullable String beanName,
@Nullable Object[] specificInterceptors, TargetSource targetSource) {
//为true,DefaultListableBeanFactory
if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
//标记一下,当前的bean具备创建代理的资格
//其实就是在BD设置了属性setAttribute("originalTargetClass",bean的class)
AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
}
//创建一个默认的代理工厂DefaultAopProxyFactory,注意:父类无参构造器
ProxyFactory proxyFactory = new ProxyFactory();
//proxyFactory通过复制配置进行初始化
//this为AbstractAutoProxyCreator对象,说明AbstractAutoProxyCreator继承参数实际类型
proxyFactory.copyFrom(this); // 设置了一堆属性,不管他
/**
* true
* 目标对象没有接口(只有实现类) – 使用CGLIB代理机制
* false
* 目标对象实现了接口 – 使用JDK代理机制(代理所有实现了的接口)
*/
//默认值为false,为true,使用CGLIB,
if (!proxyFactory.isProxyTargetClass()) {
//来判断@EnableAspectJAutoProxy注解或者XML的proxyTargetClass参数(true或者false)
//也就是:用户有没有明确指定什么方式生成代理
if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
} else {
//评估接口的合理性,一些内部回调接口,比如InitializingBean等不要,我们自己的加入proxyFactory
evaluateProxyInterfaces(beanClass, proxyFactory); // ===>
}
}
// 把前面我们过滤出来的那些切面方法,转成Advisor数组,specificInterceptors还有印象吧??
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
//加入到代理工厂
proxyFactory.addAdvisors(advisors);
//设置要代理的类(目标类)
proxyFactory.setTargetSource(targetSource);
//子类实现,定制代理
customizeProxyFactory(proxyFactory);
//是否还允许修改通知,缺省值为false
proxyFactory.setFrozen(this.freezeProxy);
//设置预过滤
if (advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
}
//【关键点!】proxyFactory携带了aop里配置的各种切面方法等等,由它来创建代理
return proxyFactory.getProxy(getProxyClassLoader()); // ===> aop藏在这里!代理类诞生的地方
}
可以看到,在这个方法里面,Spring主要做了以下几件事情:
在createProxy方法中,其他方法都是在进行创建代理对象的必要性校验和属性赋值,最重要的方法还是proxyFactory.getProxy(getProxyClassLoader())方法,接下来我们进入这个方法看看它具体做了些什么。
//通过类加载期获取代理【重点方法!】
public Object getProxy(@Nullable ClassLoader classLoader) {
//分别进入createAopProxy (获取aopProxy对象)和getProxy(代理实例的方法)
return createAopProxy().getProxy(classLoader); // ===> 先进 createAopProxy 方法, 再进 getProxy 方法
}
在getProxy方法中它实际调用了createAopProxy和getProxy两个方法,这两个方法至关重要,我们一个拆分来看,先来看看createAopProxy里做了什么。
protected final synchronized AopProxy createAopProxy() {
if (!this.active) {
activate();
}
//进入createAopProxy,查看返回jdk还是cglib
return getAopProxyFactory().createAopProxy(this); // ===> 进!
}
//走jdk还是走Cglib?【关键方法】
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
// isOptimize: 是否对代理进行优化
// isProxyTargetClass: 值为true,使用CGLIB代理,默认false
/**
* hasNoUserSuppliedProxyInterfaces:
* 1:接口是不是满足,如果长度为0;也就是接口为空,返回false
* 2:如果接口类型不是SpringProxy类型的,也返回flase
*/
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.");
}
// 如果目标对象实现了接口,默认情况下会采用JDK动态代理,
// 但也可以通过配置(proxy-target-class=true)强制使用CGLIB。
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
//jdk
return new JdkDynamicAopProxy(config);
}
//cglib
return new ObjenesisCglibAopProxy(config);
} else {
//jdk代理 , 走这里!
// 注意,config里面藏着了我们上面筛出来的那些 advisor(即切面方法,并且它们已经排好了顺序),
return new JdkDynamicAopProxy(config); // 【找到了!】 走的jdk动态代理,
// 这货其实就是动态代理里的那个InvocationHandler,往回走
}
}
可以看到,在createAopProxy最终走到了createAopProxy(AdvisedSupport config)方法中,而这个方法则是根据被代理类是否实现接口以及是否设置了proxy-target-class属性去选择是使用jdk动态代理还是走Cglib动态代理。
接下来我们再回到getProxy去看getProxy(classLoader)方法,这个方法有两个实现JdkDynamicAopProxy、CglibAopProxy,具体调用哪个方法需要看createAopProxy具体返回的信息来决定。
我们以jdk动态代理为例来看下源码:
//获取最终的代理对象(由JDK生成;运行时织入)
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
if (logger.isDebugEnabled()) {
logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
}
//获取代理对象需要实现的接口(业务接口和内置接口),不管他
Class>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
//判断接口中是否重写了equals和hashCode方法,也不用搭理他
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
/**
* 【关键点】 jdk动态代理的诞生!Proxy是jdk的动态代理生成工具类
* 第一个参数是类加载器(目标类)
* 第二个参数是目标类实现的接口(含系统接口)(数组)
* 第三个是InvocationHandler,也就是这个JdkDynamicAopProxy自己,它实现了handler接口
* 而前面的步骤,把筛出来的 一堆排好序的 advisor ,给了这货
* 未来真正调用代理方法的时候,这货的invoke被触发,开始使用这些advisor当责任链列表,一环环被调用……
* 而这,就是aop的真相~ 其实很简单!
*/
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this); // 真身被隐藏,proxy被创建!
}
至此,Spring创建aop代理对象的过程就算是跟完了,但是,我们在实际调用的时候代理对象是怎么执行的以及jdk动态代理还是走Cglib动态代理具体使用上有什么不一样的,我将放在后面的课程讲解。