SpringAOP-底层实现源码解析

目录

1. Spring AOP原理流程图

2. 动态代理

3. ProxyFactory

4. Advice的分类

5. Advisor的理解

6. 创建代理对象的方式

ProxyFactoryBean

BeanNameAutoProxyCreator

DefaultAdvisorAutoProxyCreator

7. 对Spring AOP的理解

8. AOP中的概念

9. Advice在Spring AOP中对应API

10. TargetSource的使用

Introduction

LoadTimeWeaver

11. ProxyFactory选择cglib或jdk动态代理原理

代理对象创建过程

JdkDynamicAopProxy

ObjenesisCglibAopProxy

12. 代理对象执行过程

13. 各注解对应的MethodInterceptor

14. AbstractAdvisorAutoProxyCreator

15. @EnableAspectJAutoProxy


1. Spring AOP原理流程图

2. 动态代理

代理模式的解释:为其他对象提供一种代理以控制对这个对象的访问,增强一个类中的某个方法,对程序进行扩展。

比如,现在存在一个UserService类:

public class UserService  {
 	public void test() {
        System.out.println("test...");
    }
}

此时,我们new一个UserService对象,然后执行test()方法,结果是显而易见的。

如果我们现在想在不修改UserService类的源码前提下,给test()增加额外逻辑,那么就可以使用动态代理机制来创建UserService对象了,比如:

UserService target = new UserService();

// 通过cglib技术
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class);

// 定义额外逻辑,也就是代理逻辑
enhancer.setCallbacks(new Callback[]{new MethodInterceptor() {
 @Override
 public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
  System.out.println("before...");
  Object result = methodProxy.invoke(target, objects);
  System.out.println("after...");
  return result;
 }
}});

// 动态代理所创建出来的UserService对象
UserService userService = (UserService) enhancer.create();

// 执行这个userService的test方法时,就会额外会执行一些其他逻辑
userService.test();//test

// 运行结果
before...
test
after...

得到的都是UserService对象,但是执行test()方法时的效果却不一样了,这就是代理所带来的效果。

上面是通过cglib来实现的代理对象的创建,是基于父子类的,被代理类(UserService)是父类,代理类是子类,代理对象就是代理类的实例对象,代理类是由cglib创建的,对于程序员来说不用关心。

除开cglib技术,jdk本身也提供了一种创建代理对象的动态代理机制,但是它只能代理接口,也就是UserService得先有一个接口才能利用jdk动态代理机制来生成一个代理对象,比如:

public interface UserInterface {
     public void test();
}

public class UserService implements UserInterface {
    public void test() {
      System.out.println("test...");
    }
}

利用JDK动态代理来生成一个代理对象:


UserService target = new UserService();

// UserInterface接口的代理对象
Object proxy = Proxy.newProxyInstance(UserService.class.getClassLoader(), new Class[]{UserInterface.class}, new InvocationHandler() {
 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  System.out.println("before...");
  Object result = method.invoke(target, args);
  System.out.println("after...");
  return result;
 }
});

UserInterface userService = (UserInterface) proxy;
userService.test();

// 运行结果
before...
test...
after...

如果把new Class[]{UserInterface.class},替换成new Class[]{UserService.class},允许代码会直接报错:

Exception in thread "main" java.lang.IllegalArgumentException : com.bubble.service.UserService is not an interface
	at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java: 590)
	at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java:557)
	at java.lang.reflect.WeakCache$Factory.get(Weakcache.java: 230)
	at java.lang.reflect.WeakCache.get(WeakCache.java:127)
	at java.lang.reflect.Proxy.getProxyClass0(Proxy.java:419)
	at java.lang.reflect.Proxy.newProxyInstance(Proxy.java:719)
	at com.bubble.Test.main(Test.java:50)

表示一定要是个接口。

由于这个限制,所以产生的代理对象的类型是UserInterface,而不是UserService,这是需要注意的。

3. ProxyFactory

上面介绍了两种动态代理技术,那么在Spring中进行了封装,封装出来的类叫做ProxyFactory,表示是创建代理对象的一个工厂,使用起来会比上面的更加方便,比如:

UserService target = new UserService();

ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addAdvice(new MethodInterceptor() {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("before...");
        Object result = invocation.proceed();
        System.out.println("after...");
        return result;
    }
});

UserInterface userService = (UserInterface) proxyFactory.getProxy();
userService.test();

// 运行结果
before...
test...
after...

通过ProxyFactory,我们可以不再关心到底是用cglib还是jdk动态代理了,ProxyFactory会帮我们去判断,如果UserService实现了接口,那么ProxyFactory底层就会用jdk动态代理,如果没有实现接口,就会用cglib技术,上面的代码,就是由于UserService实现了UserInterface接口,所以最后产生的代理对象是UserInterface类型。

4. Advice的分类

  1. Before Advice:方法之前执行
  2. After returning advice:方法return后执行
  3. After throwing advice:方法抛异常后执行
  4. After (finally) advice:方法执行完finally之后执行,这是最后的,比return更后
  5. Around advice:这是功能最强大的Advice,可以自定义执行顺序
public class BubbleBeforeAdvice implements MethodBeforeAdvice {
	@Override
	public void before(Method method, Object[] args, Object target) throws Throwable {
		System.out.println("方法执行前执行");
	}
}
public class BubbleAfterReturningAdvice implements AfterReturningAdvice {
	@Override
	public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
		System.out.println("方法return后执行");
	}
}
public class BubbleAroundAdvice implements MethodInterceptor {
	@Nullable
	@Override
	public Object invoke(@NotNull MethodInvocation invocation) throws Throwable {
		System.out.println("方法执行Around前");
		Object proceed = invocation.proceed();
		System.out.println("方法执行Around后");
		return proceed;
	}
}
public class BubbleThrowsAdvice implements ThrowsAdvice {
	public void afterThrowing(Method method, Object[] args, Object target, NullPointerException ex) {
		System.out.println("方法抛出异常后执行");
	}
}
UserService target = new UserService();

ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addAdvice(new BubbleBeforeAdvice()); //MethodInteceptor.invoke(invocation) -->before--->invocation.proceed()

UserService userService = (UserService) proxyFactory.getProxy();
userService.test(); // invocation.proceed()

5. Advisor的理解

跟Advice类似的还有一个Advisor的概念,一个Advisor是有一个Pointcut和一个Advice组成的,通过Pointcut可以指定要需要被代理的逻辑,比如一个UserService类中有两个方法,按上面的例子,这两个方法都会被代理,被增强,那么我们现在可以通过Advisor,来控制到具体代理哪一个方法,比如:

UserService target = new UserService();

ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addAdvisor(new PointcutAdvisor() {
    @Override
    public Pointcut getPointcut() {
        return new StaticMethodMatcherPointcut() {
            @Override
            public boolean matches(Method method, Class<?> targetClass) {
                return method.getName().equals("test"); //执行test方法才增强
            }
        };
    }

    @Override
    public Advice getAdvice() {
        return new MethodInterceptor() {
            @Override
            public Object invoke(MethodInvocation invocation) throws Throwable {
                System.out.println("before...");
                Object result = invocation.proceed();
                System.out.println("after...");
                return result;
            }
        };
    }

    @Override
    public boolean isPerInstance() {
        return false;
    }
});

UserInterface userService = (UserInterface) proxyFactory.getProxy();
userService.test();

// 运行结果
before...
test...
after...

上面代码表示,产生的代理对象,只有在执行test这个方法时才会被增强,会执行额外的逻辑,而在执行其他方法时是不会增强的。

6. 创建代理对象的方式

上面介绍了Spring中所提供了ProxyFactory、Advisor、Advice、PointCut等技术来实现代理对象的创建,但是我们在使用Spring时,并不会直接这么去使用ProxyFactory,比如,我们希望ProxyFactory所产生的代理对象能直接就是Bean,能直接从Spring容器中得到UserSerivce的代理对象,而这些,Spring都是支持的,只不过,作为开发者肯定得告诉Spring,那些类需要被代理,代理逻辑是什么。

ProxyFactoryBean

@Bean
public ProxyFactoryBean userServiceProxy(){
    UserService userService = new UserService();

    ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
    proxyFactoryBean.setTarget(userService);
    proxyFactoryBean.addAdvice(new MethodInterceptor() {
        @Override
        public Object invoke(MethodInvocation invocation) throws Throwable {
            System.out.println("before...");
            Object result = invocation.proceed();
            System.out.println("after...");
            return result;
        }
    });
    return proxyFactoryBean;
}

通过这种方法来定义一个UserService的Bean,并且是经过了AOP的。但是这种方式只能针对某一个Bean。它是一个FactoryBean,所以利用的就是FactoryBean技术,间接的将UserService的代理对象作为了Bean。

ProxyFactoryBean还有额外的功能,比如可以把某个Advise或Advisor定义成为Bean,然后在ProxyFactoryBean中进行设置

@Bean
public MethodInterceptor bubbleAroundAdvise(){
    return new MethodInterceptor() {
        @Override
        public Object invoke(MethodInvocation invocation) throws Throwable {
            System.out.println("before...");
            Object result = invocation.proceed();
            System.out.println("after...");
            return result;
        }
    };
}

@Bean
public ProxyFactoryBean userService(){
    UserService userService = new UserService();

    ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
    proxyFactoryBean.setTarget(userService);
    proxyFactoryBean.setInterceptorNames("bubbleAroundAdvise");
    return proxyFactoryBean;
}
@Bean
public ProxyFactoryBean userService(){
    UserService userService = new UserService();
    ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
    proxyFactoryBean.addAdvice(new BubbleBeforeAdvice());
    proxyFactoryBean.setTarget(userService);
    return proxyFactoryBean;
}

BeanNameAutoProxyCreator

ProxyFactoryBean得自己指定被代理的对象,那么我们可以通过BeanNameAutoProxyCreator来通过指定某个bean的名字,来对该bean进行代理

@Bean
public BeanNameAutoProxyCreator beanNameAutoProxyCreator() {
     BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator();
     beanNameAutoProxyCreator.setBeanNames("userSe*");
     beanNameAutoProxyCreator.setInterceptorNames("bubbleAroundAdvise");
     beanNameAutoProxyCreator.setProxyTargetClass(true);

    return beanNameAutoProxyCreator;
}

通过BeanNameAutoProxyCreator可以对批量的Bean进行AOP,并且指定了代理逻辑,指定了一个InterceptorName,也就是一个Advise,前提条件是这个Advise也得是一个Bean,这样Spring才能找到的,但是BeanNameAutoProxyCreator的缺点很明显,它只能根据beanName来指定想要代理的Bean。

DefaultAdvisorAutoProxyCreator

/*
* AppConfig.java
*/
@Bean
public DefaultPointcutAdvisor defaultPointcutAdvisor(){
    NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
    pointcut.addMethodName("test");

    DefaultPointcutAdvisor defaultPointcutAdvisor = new DefaultPointcutAdvisor();
    defaultPointcutAdvisor.setPointcut(pointcut);
    defaultPointcutAdvisor.setAdvice(new BubbleAfterReturningAdvice());

    return defaultPointcutAdvisor;
}

@Bean //AppConfig类上加注解@Import(DefaultAdvisorAutoProxyCreator.class)代替 或 @EnableAspectJAutoProxy代替
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {

    DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();

    return defaultAdvisorAutoProxyCreator;
}

通过DefaultAdvisorAutoProxyCreator会直接去找所有Advisor类型的Bean,根据Advisor中的PointCut和Advice信息,确定要代理的Bean以及代理逻辑。

但是,我们发现,通过这种方式,我们得依靠某一个类来实现定义我们的Advisor,或者Advise,或者Pointcut,那么这个步骤能不能更加简化一点呢?(能,通过注解!)

我们能只定义一个类,然后通过在类中的方法上通过某些注解,来定义PointCut以及Advice,比如:

@Aspect
@Component
public class BubbleAspect {

 @Before("execution(public void com.bubble.service.UserService.test())")
 public void bubbleBefore(JoinPoint joinPoint) {
  System.out.println("bubbleBefore");
 }

}

通过上面这个类,我们就直接定义好了所要代理的方法(通过一个表达式),以及代理逻辑(被@Before修饰的方法),简单明了,这样对于Spring来说,它要做的就是来解析这些注解了,解析之后得到对应的Pointcut对象、Advice对象,生成Advisor对象,扔进ProxyFactory中,进而产生对应的代理对象,具体怎么解析这些注解就是@EnableAspectJAutoProxy注解所要做的事情了。

7. 对Spring AOP的理解

OOP表示面向对象编程,是一种编程思想,AOP表示面向切面编程,也是一种编程思想,而我们上面所描述的就是Spring为了让程序员更加方便的做到面向切面编程所提供的技术支持,换句话说,就是Spring提供了一套机制,可以让我们更加容易的来进行AOP,所以这套机制我们也可以称之为Spring AOP。

但是值得注意的是,上面所提供的注解的方式来定义Pointcut和Advice,Spring并不是首创,首创是AspectJ,而且也不仅仅只有Spring提供了一套机制来支持AOP,还有比如 JBoss 4.0、aspectwerkz等技术都提供了对于AOP的支持。而刚刚说的注解的方式,Spring是依赖了AspectJ的,或者说,Spring是直接把AspectJ中所定义的那些注解直接拿过来用,自己没有再重复定义了,不过也仅仅只是把注解的定义赋值过来了,每个注解具体底层是怎么解析的,还是Spring自己做的,所以我们在用Spring时,如果你想用@Before、@Around等注解,是需要单独引入aspecj相关jar包的,比如:

compile group: 'org.aspectj', name: 'aspectjrt', version: '1.9.5'
compile group: 'org.aspectj', name: 'aspectjweaver', version: '1.9.5'

值得注意的是:AspectJ是在编译时对字节码进行了修改,是直接在UserService类对应的字节码中进行增强的,也就是可以理解为是在编译时就会去解析@Before这些注解,然后得到代理逻辑,加入到被代理的类中的字节码中去的,所以如果想用AspectJ技术来生成代理对象 ,是需要用单独的AspectJ编译器的。我们在项目中很少这么用,我们仅仅只是用了@Before这些注解,而我们在启动Spring的过程中,Spring会去解析这些注解,然后利用动态代理机制生成代理对象的。

8. AOP中的概念

上面我们已经提到Advisor、Advice、PointCut等概念了,还有一些其他的概念,首先关于AOP中的概念本身是比较难理解的,Spring官网上是这么说的:

Let us begin by defining some central AOP concepts and terminology. These terms are not Spring-specific. Unfortunately, AOP terminology is not particularly intuitive. However, it would be even more confusing if Spring used its own terminology

意思是,AOP中的这些概念不是Spring特有的,不幸的是,AOP中的概念不是特别直观的,但是,如果Spring重新定义自己的那可能会导致更加混乱

  1. Aspect:表示切面,比如被@Aspect注解的类就是切面,可以在切面中去定义Pointcut、Advice等等
  2. Join point:表示连接点,表示一个程序在执行过程中的一个点,比如一个方法的执行,比如一个异常的处理,在Spring AOP中,一个连接点通常表示一个方法的执行。
  3. Advice:表示通知,表示在一个特定连接点上所采取的动作。Advice分为不同的类型,后面详细讨论,在很多AOP框架中,包括Spring,会用Interceptor拦截器来实现Advice,并且在连接点周围维护一个Interceptor链
  4. Pointcut:表示切点,用来匹配一个或多个连接点,Advice与切点表达式是关联在一起的,Advice将会执行在和切点表达式所匹配的连接点上
  5. Introduction:可以使用@DeclareParents来给所匹配的类添加一个接口,并指定一个默认实现
  6. Target object:目标对象,被代理对象
  7. AOP proxy:表示代理工厂,用来创建代理对象的,在Spring Framework中,要么是JDK动态代理,要么是CGLIB代理
  8. Weaving:表示织入,表示创建代理对象的动作,这个动作可以发生在编译时期(比如Aspejctj),或者运行时,比如Spring AOP

9. Advice在Spring AOP中对应API

上面说到的Aspject中的注解,其中有五个是用来定义Advice的,表示代理逻辑,以及执行时机:

  1. @Before
  2. @AfterReturning
  3. @AfterThrowing
  4. @After
  5. @Around

我们前面也提到过,Spring自己也提供了类似的执行实际的实现类:

  1. 接口MethodBeforeAdvice,继承了接口BeforeAdvice
  2. 接口AfterReturningAdvice
  3. 接口ThrowsAdvice
  4. 接口AfterAdvice
  5. 接口MethodInterceptor

Spring会把五个注解解析为对应的Advice类:

  1. @Before:AspectJMethodBeforeAdvice,实际上就是一个MethodBeforeAdvice
  2. @AfterReturning:AspectJAfterReturningAdvice,实际上就是一个AfterReturningAdvice
  3. @AfterThrowing:AspectJAfterThrowingAdvice,实际上就是一个MethodInterceptor
  4. @After:AspectJAfterAdvice,实际上就是一个MethodInterceptor
  5. @Around:AspectJAroundAdvice,实际上就是一个MethodInterceptor

10. TargetSource的使用

在日常的AOP中,被代理对象就是Bean对象,是由BeanFactory给我们创建出来的,但是Spring AOP中提供了TargetSource机制,可以让我们用来自定义逻辑来创建被代理对象

比如@Lazy注解,当加在属性上时,会产生一个代理对象赋值给这个属性,产生代理对象的代码为:

/**
 * 构建一个延迟解析的代理对象,用于实现对依赖的懒加载解析。
 * @param descriptor 依赖描述符,描述需要解析的依赖的信息
 * @param beanName 如果适用,表示当前 Bean 的名称
 * @return 延迟解析的代理对象
 */
protected Object buildLazyResolutionProxy(final DependencyDescriptor descriptor, final @Nullable String beanName) {
    BeanFactory beanFactory = getBeanFactory();// 获取当前 BeanFactory
    Assert.state(beanFactory instanceof DefaultListableBeanFactory,
            "BeanFactory needs to be a DefaultListableBeanFactory");
    final DefaultListableBeanFactory dlbf = (DefaultListableBeanFactory) beanFactory;

    TargetSource ts = new TargetSource() {// 创建一个 TargetSource 实例,用于延迟解析依赖
        @Override
        public Class<?> getTargetClass() {
            return descriptor.getDependencyType();
        }
        @Override
        public boolean isStatic() {
            return false;
        }
        @Override
        public Object getTarget() {
            Set<String> autowiredBeanNames = (beanName != null ? new LinkedHashSet<>(1) : null);
            // 使用 BeanFactory 解析依赖对象
            Object target = dlbf.doResolveDependency(descriptor, beanName, autowiredBeanNames, null);
            if (target == null) {
                // 如果依赖对象为空,根据类型返回一个空的集合或映射
                Class<?> type = getTargetClass();
                if (Map.class == type) {
                    return Collections.emptyMap();
                }
                else if (List.class == type) {
                    return Collections.emptyList();
                }
                else if (Set.class == type || Collection.class == type) {
                    return Collections.emptySet();
                }// 抛出异常,表示延迟注入点的可选依赖不存在
                throw new NoSuchBeanDefinitionException(descriptor.getResolvableType(),
                        "Optional dependency not present for lazy injection point");
            }// 如果存在依赖对象,并且有关联的 BeanName,将这些 BeanName 注册为当前 Bean 的依赖
            if (autowiredBeanNames != null) {
                for (String autowiredBeanName : autowiredBeanNames) {
                    if (dlbf.containsBean(autowiredBeanName)) {
                        dlbf.registerDependentBean(autowiredBeanName, beanName);
                    }
                }
            }
            return target;
        }
        @Override
        public void releaseTarget(Object target) {// 这里无需实现,因为是懒加载的代理对象
        }
    };

    ProxyFactory pf = new ProxyFactory();// 使用 ProxyFactory 创建代理对象并返回
    pf.setTargetSource(ts);
    Class<?> dependencyType = descriptor.getDependencyType();
    if (dependencyType.isInterface()) {
        pf.addInterface(dependencyType);
    }
    return pf.getProxy(dlbf.getBeanClassLoader());
}

这段代码就利用了ProxyFactory来生成代理对象,以及使用了TargetSource,以达到代理对象在执行某个方法时,调用TargetSource的getTarget()方法实时得到一个被代理对象

Introduction

Spring AOP之Introduction(@DeclareParents)简介 - 门罗的魔术师 - 博客园 (cnblogs.com)

package com.bubble.aspect;
import com.bubble.service.UserImplement;
import com.bubble.service.UserInterface;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class BubbleAspect {

	@DeclareParents(value = "com.bubble.service.UserService", defaultImpl = UserImplement.class)
	private UserInterface userInterface;
}
package com.bubble.service;

public interface UserInterface {
	public void testXxx();
}
package com.bubble.service;

public class UserImplement implements UserInterface{
	@Override
	public void testXxx() {
		System.out.println("testXxx");
	}
}
import com.bubble.service.*;
import org.springframework.context.annotation.*;
@ComponentScan("com.bubble")
@EnableAspectJAutoProxy
public class AppConfig {
}
package com.bubble;
import com.bubble.service.*;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Test {

	public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
		UserInterface userService = (UserInterface) applicationContext.getBean("userService");
		userService.testXxx();//运行结果-> testXxx
    }
}

LoadTimeWeaver

Spring之LoadTimeWeaver——一个需求引发的思考---转 - 一天不进步,就是退步 - 博客园 (cnblogs.com)

11. ProxyFactory选择cglib或jdk动态代理原理

ProxyFactory在生成代理对象之前需要决定到底是使用JDK动态代理还是CGLIB技术:

@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    // 如果ProxyFactory的isOptimize为true,Spring认为cglib比jdk动态代理要快
    // 或者isProxyTargetClass为true,
    // 或者被代理对象没有实现接口,
    // 或者只实现了SpringProxy这个接口
    // 那么则利用Cglib进行动态代理,但如果被代理类是接口,或者被代理类已经是进行过JDK动态代理而生成的代理类了则只能进行JDK动态代理

    // 其他情况都会进行JDK动态代理,比如被代理类实现了除SpringProxy接口之外的其他接口

    // 是不是在GraalVM虚拟机上运行
    if (!NativeDetector.inNativeImage() &&
            // config就是ProxyFactory对象
            // optimize为true,或proxyTargetClass为true,或用户没有给ProxyFactory对象添加interface
            (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.");
        }
        if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
            return new JdkDynamicAopProxy(config);
        }
        return new ObjenesisCglibAopProxy(config);
    }
    else {
        return new JdkDynamicAopProxy(config);
    }
}

代理对象创建过程

JdkDynamicAopProxy

  1. 在构造JdkDynamicAopProxy对象时,会先拿到被代理对象自己所实现的接口,并且额外的增加SpringProxy、Advised、DecoratingProxy三个接口,组合成一个Class[],并赋值给proxiedInterfaces属性
  2. 并且检查这些接口中是否定义了equals()、hashcode()方法
  3. 执行Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this),得到代理对象,JdkDynamicAopProxy作为InvocationHandler,代理对象在执行某个方法时,会进入到JdkDynamicAopProxy的invoke()方法中
/**
 * 创建一个 JDK 动态代理对象,并返回。
 * @param classLoader 用于加载代理类的类加载器
 * @return 创建的 JDK 动态代理对象
 */
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
    if (logger.isTraceEnabled()) {
        logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
    }
    // 使用 Proxy.newProxyInstance() 创建 JDK 动态代理对象,实现的接口为 this.proxiedInterfaces,
    // 代理对象的方法调用会由 this(实现了 InvocationHandler 接口)处理
    // this实现了InvocationHandler
    return Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this);
}
/**
 * Implementation of {@code InvocationHandler.invoke}.
 * <p>Callers will see exactly the exception thrown by the target,
 * unless a hook method throws an exception.
 * 实现了 {@code InvocationHandler.invoke} 方法的逻辑。
 * <p>调用者将会看到目标抛出的异常,除非 hook 方法抛出异常。
 */
@Override
@Nullable
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    Object oldProxy = null;
    boolean setProxyContext = false;

    // 拿到被代理对象
    TargetSource targetSource = this.advised.targetSource;
    Object target = null;

    try {
        // 如果接口中没有定义equals()方法,那么则直接调用,不走代理
        // equals 方法不被代理,因为 Proxy 的 equals 逻辑默认是比较代理对象是否是同一个对象
        if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
            // The target does not implement the equals(Object) method itself.
            // 目标本身不实现 equals(Object) 方法。
            return equals(args[0]);
        }
        // 如果接口中没有定义hashCode()方法,直接调用,不走代理
        else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
            // The target does not implement the hashCode() method itself.
            return hashCode();
        }
        // 如果方法来自 DecoratingProxy 接口,获取被代理的实际类
        else if (method.getDeclaringClass() == DecoratingProxy.class) {
            // There is only getDecoratedClass() declared -> dispatch to proxy config.
            // 得到代理对象的类型,而不是所实现的接口
            return AopProxyUtils.ultimateTargetClass(this.advised);
        }
        // 如果方法来自 Advised 接口,直接调用代理对象的相关方法,不走代理逻辑
        else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
                method.getDeclaringClass().isAssignableFrom(Advised.class)) {
            // Service invocations on ProxyConfig with the proxy config...
            // 也是直接调用Advised接口中的方法,不走代理逻辑
            // 其实就是利用代理对象获取ProxyFactory中的信息
            return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
        }

        Object retVal;

        // 如果ProxyFactory的exposeProxy为true,则将代理对象设置到currentProxy这个ThreadLocal中去
        if (this.advised.exposeProxy) {
            // Make invocation available if necessary.// 如有必要,使调用可用。
            oldProxy = AopContext.setCurrentProxy(proxy);
            setProxyContext = true;
        }

        // Get as late as possible to minimize the time we "own" the target,
        // in case it comes from a pool.
        // 尽可能晚地到达,以尽量减少我们“拥有”目标的时间,// 如果它来自池。
        // 被代理对象和代理类
        target = targetSource.getTarget();
        Class<?> targetClass = (target != null ? target.getClass() : null);

        // Get the interception chain for this method.// 获取该方法的拦截器链
        // 代理对象在执行某个方法时,根据方法筛选出匹配的Advisor,并适配成Interceptor
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

        // Check whether we have any advice. If we don't, we can fallback on direct
        // reflective invocation of the target, and avoid creating a MethodInvocation.
        // 检查是否有拦截器。如果没有,直接调用目标对象的方法,避免创建 MethodInvocation。
        if (chain.isEmpty()) {
            // We can skip creating a MethodInvocation: just invoke the target directly
            // Note that the final invoker must be an InvokerInterceptor so we know it does
            // nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
            // 我们可以跳过创建方法调用:直接调用目标
            // 请注意,最终调用者必须是 InvokerInterceptor,因此我们知道它确实如此
            // 只是对目标进行反射操作,没有热交换或花哨的代理。

            // 如果没有Advice,则直接调用对应方法
            Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
            retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
        }
        else {
            // We need to create a method invocation...// 需要创建一个方法调用
            MethodInvocation invocation =
                    new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
            // Proceed to the joinpoint through the interceptor chain.// 通过拦截器链执行方法调用
            retVal = invocation.proceed();
        }

        // Massage return value if necessary.// 如果需要,调整返回值
        Class<?> returnType = method.getReturnType();
        if (retVal != null && retVal == target &&
                returnType != Object.class && returnType.isInstance(proxy) &&
                !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
            // Special case: it returned "this" and the return type of the method
            // is type-compatible. Note that we can't help if the target sets
            // a reference to itself in another returned object.
            // 特殊情况:如果返回的是目标对象本身,并且方法返回类型是兼容的,则返回代理对象
            retVal = proxy;
        }
        else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
            throw new AopInvocationException(
                    "Null return value from advice does not match primitive return type for: " + method);
        }
        return retVal;
    }
    finally {
        if (target != null && !targetSource.isStatic()) {
            // Must have come from TargetSource.// 来自 TargetSource。释放目标对象
            targetSource.releaseTarget(target);
        }
        if (setProxyContext) {
            // Restore old proxy.// 恢复旧的代理对象
            AopContext.setCurrentProxy(oldProxy);
        }
    }
}

ObjenesisCglibAopProxy

  1. 创建Enhancer对象
  2. 设置Enhancer的superClass为通过ProxyFactory.setTarget()所设置的对象的类
  3. 设置Enhancer的interfaces为通过ProxyFactory.addInterface()所添加的接口,以及SpringProxy、Advised、DecoratingProxy接口
  4. 设置Enhancer的Callbacks为DynamicAdvisedInterceptor
  5. 最后创建一个代理对象,代理对象在执行某个方法时,会进入到DynamicAdvisedInterceptor的intercept()方法中
/**
 * 创建 CGLIB 代理对象。
 *
 * @param classLoader 类加载器,用于加载生成的代理类
 * @return 代理对象实例
 * @throws AopConfigException 如果生成代理过程中出现异常
 */
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
    if (logger.isTraceEnabled()) {
        logger.trace("Creating CGLIB proxy: " + this.advised.getTargetSource());
    }

    try {
        // 被代理的类
        Class<?> rootClass = this.advised.getTargetClass();
        Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");

        Class<?> proxySuperClass = rootClass;
        // 如果被代理类本身就已经是Cglib所生成的代理类了
        if (rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {
            // 获取真正的被代理类
            proxySuperClass = rootClass.getSuperclass();
            // 获取被代理类所实现的接口
            Class<?>[] additionalInterfaces = rootClass.getInterfaces();
            for (Class<?> additionalInterface : additionalInterfaces) {
                this.advised.addInterface(additionalInterface);
            }
        }

        // Validate the class, writing log messages as necessary.
        // 验证类,如果需要的话,记录日志信息
        validateClassIfNecessary(proxySuperClass, classLoader);

        // Configure CGLIB Enhancer...// 配置 CGLIB Enhancer...
        Enhancer enhancer = createEnhancer();
        if (classLoader != null) {
            enhancer.setClassLoader(classLoader);
            if (classLoader instanceof SmartClassLoader &&
                    ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
                enhancer.setUseCache(false);
            }
        }

        // 被代理类,代理类的父类
        enhancer.setSuperclass(proxySuperClass);
        // 代理类额外要实现的接口
        enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
        enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
        enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));

        // 获取和被代理类所匹配的Advisor
        Callback[] callbacks = getCallbacks(rootClass);
        Class<?>[] types = new Class<?>[callbacks.length];
        for (int x = 0; x < types.length; x++) {
            types[x] = callbacks[x].getClass();
        }
        // fixedInterceptorMap only populated at this point, after getCallbacks call above
        // 在 getCallbacks 调用后才填充 fixedInterceptorMap
        enhancer.setCallbackFilter(new ProxyCallbackFilter(
                this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
        enhancer.setCallbackTypes(types);

        // Generate the proxy class and create a proxy instance.
        // 生成代理类并创建代理对象实例
        return createProxyClassAndInstance(enhancer, callbacks);
    }
    catch (CodeGenerationException | IllegalArgumentException ex) {
        throw new AopConfigException("Could not generate CGLIB subclass of " + this.advised.getTargetClass() +
                ": Common causes of this problem include using a final class or a non-visible class",
                ex);
    }
    catch (Throwable ex) {
        // TargetSource.getTarget() failed
        throw new AopConfigException("Unexpected AOP exception", ex);
    }
}
/**
 * Determine the complete set of interfaces to proxy for the given AOP configuration.
 * <p>This will always add the {@link Advised} interface unless the AdvisedSupport's
 * {@link AdvisedSupport#setOpaque "opaque"} flag is on. Always adds the
 * {@link org.springframework.aop.SpringProxy} marker interface.
 * @param advised the proxy config
 * @param decoratingProxy whether to expose the {@link DecoratingProxy} interface
 * @return the complete set of interfaces to proxy
 * @since 4.3
 * @see SpringProxy
 * @see Advised
 * @see DecoratingProxy
 *
 * 确定要为给定的 AOP 配置进行代理的完整接口集合。
 * <p>除非 AdvisedSupport 的 "opaque" 标志打开,否则这将始终添加 Advised 接口。始终添加
 * SpringProxy 标记接口。
 *  *
 * @param advised         代理配置
 * @param decoratingProxy 是否暴露 DecoratingProxy 接口
 * @return 要代理的完整接口集合
 */
static Class<?>[] completeProxiedInterfaces(AdvisedSupport advised, boolean decoratingProxy) {
    // 被代理对象自己所实现的接口
    Class<?>[] specifiedInterfaces = advised.getProxiedInterfaces();

    // 如果被代理对象没有实现接口,则判断被代理类是不是接口,或者被代理类是不是已经经过JDK动态代理之后的类从而获取想对应的接口
    if (specifiedInterfaces.length == 0) {
        // No user-specified interfaces: check whether target class is an interface.
        // 没有用户指定的接口:检查目标类是否为接口。
        Class<?> targetClass = advised.getTargetClass();
        if (targetClass != null) {
            if (targetClass.isInterface()) {
                advised.setInterfaces(targetClass);
            }
            else if (Proxy.isProxyClass(targetClass)) {
                advised.setInterfaces(targetClass.getInterfaces());
            }
            specifiedInterfaces = advised.getProxiedInterfaces();
        }
    }

    // 添加三个Spring内置接口:SpringProxy、Advised、DecoratingProxy
    List<Class<?>> proxiedInterfaces = new ArrayList<>(specifiedInterfaces.length + 3);
    for (Class<?> ifc : specifiedInterfaces) {
        // Only non-sealed interfaces are actually eligible for JDK proxying (on JDK 17)
        // 只有非密封的接口实际上才有资格进行 JDK 代理(在 JDK 17 上)
        if (isSealedMethod == null || Boolean.FALSE.equals(ReflectionUtils.invokeMethod(isSealedMethod, ifc))) {
            proxiedInterfaces.add(ifc);
        }
    }
    if (!advised.isInterfaceProxied(SpringProxy.class)) {
        proxiedInterfaces.add(SpringProxy.class);
    }
    if (!advised.isOpaque() && !advised.isInterfaceProxied(Advised.class)) {
        proxiedInterfaces.add(Advised.class);
    }
    if (decoratingProxy && !advised.isInterfaceProxied(DecoratingProxy.class)) {
        proxiedInterfaces.add(DecoratingProxy.class);
    }
    return ClassUtils.toClassArray(proxiedInterfaces);
}
/**
 * 获取用于创建代理的拦截器回调数组。
 *
 * @param rootClass 根类,被代理的目标类
 * @return 代理的拦截器回调数组
 * @throws Exception 异常
 */
private Callback[] getCallbacks(Class<?> rootClass) throws Exception {
    // Parameters used for optimization choices...// 用于优化选择的参数...
    boolean exposeProxy = this.advised.isExposeProxy();
    boolean isFrozen = this.advised.isFrozen();
    boolean isStatic = this.advised.getTargetSource().isStatic();

    // Choose an "aop" interceptor (used for AOP calls).// 选择“aop”拦截器(用于AOP调用)。
    Callback aopInterceptor = new DynamicAdvisedInterceptor(this.advised);

    // Choose a "straight to target" interceptor. (used for calls that are
    // unadvised but can return this). May be required to expose the proxy.
    // 选择“直接到目标”拦截器。(用于未经通知的调用,但可能返回this)。可能需要暴露代理。
    Callback targetInterceptor;
    if (exposeProxy) {
        targetInterceptor = (isStatic ?
                new StaticUnadvisedExposedInterceptor(this.advised.getTargetSource().getTarget()) :
                new DynamicUnadvisedExposedInterceptor(this.advised.getTargetSource()));
    }
    else {
        targetInterceptor = (isStatic ?
                new StaticUnadvisedInterceptor(this.advised.getTargetSource().getTarget()) :
                new DynamicUnadvisedInterceptor(this.advised.getTargetSource()));
    }

    // Choose a "direct to target" dispatcher (used for
    // unadvised calls to static targets that cannot return this).
    // 选择“直接到目标”分派程序(用于无法返回this的静态目标的未经通知的调用)。
    Callback targetDispatcher = (isStatic ?
            new StaticDispatcher(this.advised.getTargetSource().getTarget()) : new SerializableNoOp());
    // 主要拦截器回调数组
    Callback[] mainCallbacks = new Callback[] {
            aopInterceptor,  // for normal advice,用于常规的 Advice,执行Interceptor链
            // 调用目标而不考虑 Advice,如果进行了优化,则返回 this,将代理对象设置到 ThreadLocal 中,AopContext.setCurrentProxy(proxy)
            targetInterceptor,  // invoke target without considering advice, if optimized 将代理对象设置到ThreadLocal中,AopContext.setCurrentProxy(proxy)
            new SerializableNoOp(),  // no override for methods mapped to this// 对于映射到此的方法,不进行覆盖
            targetDispatcher, this.advisedDispatcher,
            new EqualsInterceptor(this.advised),
            new HashCodeInterceptor(this.advised)
    };

    Callback[] callbacks;

    // If the target is a static one and the advice chain is frozen,
    // then we can make some optimizations by sending the AOP calls
    // direct to the target using the fixed chain for that method.
    // 如果目标是静态的,并且 Advice 链被冻结,
    // 那么我们可以通过使用方法的固定链将 AOP 调用直接发送到目标,以便进行一些优化。
    if (isStatic && isFrozen) {
        Method[] methods = rootClass.getMethods();
        Callback[] fixedCallbacks = new Callback[methods.length];
        this.fixedInterceptorMap = CollectionUtils.newHashMap(methods.length);

        // TODO: small memory optimization here (can skip creation for methods with no advice)
        // TODO:此处进行小内存优化(可以跳过没有建议的方法的创建)
        for (int x = 0; x < methods.length; x++) {
            Method method = methods[x];
            List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, rootClass);
            fixedCallbacks[x] = new FixedChainStaticTargetInterceptor(
                    chain, this.advised.getTargetSource().getTarget(), this.advised.getTargetClass());
            this.fixedInterceptorMap.put(method, x);
        }

        // Now copy both the callbacks from mainCallbacks
        // and fixedCallbacks into the callbacks array.
        // mainCallbacks 和 fixedCallbacks 中的回调复制到 callbacks 数组中。
        callbacks = new Callback[mainCallbacks.length + fixedCallbacks.length];
        System.arraycopy(mainCallbacks, 0, callbacks, 0, mainCallbacks.length);
        System.arraycopy(fixedCallbacks, 0, callbacks, mainCallbacks.length, fixedCallbacks.length);
        this.fixedInterceptorOffset = mainCallbacks.length;
    }
    else {
        callbacks = mainCallbacks;
    }
    return callbacks;
}
DynamicAdvisedInterceptor的intercept()方法
/**
 * CGLIB 的拦截方法,用于拦截被代理对象的方法调用。
 *
 * @param proxy       代理对象
 * @param method      被调用的方法
 * @param args        方法的参数
 * @param methodProxy CGLIB 生成的 MethodProxy 实例,用于调用目标方法
 * @return 方法调用的返回值
 * @throws Throwable 异常
 */
@Override
@Nullable
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    Object oldProxy = null;
    boolean setProxyContext = false;
    Object target = null;
    TargetSource targetSource = this.advised.getTargetSource();
    try {
        if (this.advised.exposeProxy) {
            // Make invocation available if necessary.
            // 如果允许暴露代理,则将当前代理对象设置到 AopContext 中
            oldProxy = AopContext.setCurrentProxy(proxy);
            setProxyContext = true;
        }
        // Get as late as possible to minimize the time we "own" the target, in case it comes from a pool...
        // 尽量晚获取目标对象,以减少我们“拥有”目标对象的时间,以防它来自池...
        target = targetSource.getTarget();
        Class<?> targetClass = (target != null ? target.getClass() : null);
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
        Object retVal;
        // Check whether we only have one InvokerInterceptor: that is,
        // no real advice, but just reflective invocation of the target.
        // 检查是否只有一个 InvokerInterceptor:即,没有实际的通知,而只是目标的反射调用。
        if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
            // We can skip creating a MethodInvocation: just invoke the target directly.
            // Note that the final invoker must be an InvokerInterceptor, so we know
            // it does nothing but a reflective operation on the target, and no hot
            // swapping or fancy proxying.
            // 可以跳过创建 MethodInvocation:直接调用目标方法。
            // 注意,最终的调用者必须是 InvokerInterceptor,所以我们知道它只是对目标的反射操作,没有热交换或复杂的代理。
            Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
            retVal = methodProxy.invoke(target, argsToUse);
        }
        else {
            // We need to create a method invocation... // 需要创建一个方法调用...
            retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
        }
        retVal = processReturnType(proxy, target, method, retVal);// 处理方法调用的返回值
        return retVal;
    }
    finally {
        if (target != null && !targetSource.isStatic()) {
            targetSource.releaseTarget(target);// 如果目标对象不是静态的,则释放目标对象
        }
        if (setProxyContext) {
            // Restore old proxy.// 恢复之前的代理对象
            AopContext.setCurrentProxy(oldProxy);
        }
    }
}

12. 代理对象执行过程

  1. 在使用ProxyFactory创建代理对象之前,需要往ProxyFactory先添加Advisor
  2. 代理对象在执行某个方法时,会把ProxyFactory中的Advisor拿出来和当前正在执行的方法进行匹配筛选
  3. 把和方法所匹配的Advisor适配成MethodInterceptor
  4. 把和当前方法匹配的MethodInterceptor链,以及被代理对象、代理对象、代理类、当前Method对象、方法参数封装为MethodInvocation对象
  5. 调用MethodInvocation的proceed()方法,开始执行各个MethodInterceptor以及被代理对象的对应方法
  6. 按顺序调用每个MethodInterceptor的invoke()方法,并且会把MethodInvocation对象传入invoke()方法
  7. 直到执行完最后一个MethodInterceptor了,就会调用invokeJoinpoint()方法,从而执行被代理对象的当前方法

13. 各注解对应的MethodInterceptor

  • @Before对应的是AspectJMethodBeforeAdvice,在进行动态代理时会把AspectJMethodBeforeAdvice转成MethodBeforeAdviceInterceptor
    • 先执行advice对应的方法
    • 再执行MethodInvocation的proceed(),会执行下一个Interceptor,如果没有下一个Interceptor了,会执行target对应的方法

  • @After对应的是AspectJAfterAdvice,直接实现了MethodInterceptor
    • 先执行MethodInvocation的proceed(),会执行下一个Interceptor,如果没有下一个Interceptor了,会执行target对应的方法
    • 再执行advice对应的方法

  • @Around对应的是AspectJAroundAdvice,直接实现了MethodInterceptor
    • 直接执行advice对应的方法,由@Around自己决定要不要继续往后面调用

  • @AfterThrowing对应的是AspectJAfterThrowingAdvice,直接实现了MethodInterceptor
    • 先执行MethodInvocation的proceed(),会执行下一个Interceptor,如果没有下一个Interceptor了,会执行target对应的方法
    • 如果上面抛了Throwable,那么则会执行advice对应的方法

  • @AfterReturning对应的是AspectJAfterReturningAdvice,在进行动态代理时会把AspectJAfterReturningAdvice转成AfterReturningAdviceInterceptor
    • 先执行MethodInvocation的proceed(),会执行下一个Interceptor,如果没有下一个Interceptor了,会执行target对应的方法
    • 执行上面的方法后得到最终的方法的返回值
    • 再执行Advice对应的方法

14. AbstractAdvisorAutoProxyCreator

DefaultAdvisorAutoProxyCreator的父类是AbstractAdvisorAutoProxyCreator。

AbstractAdvisorAutoProxyCreator非常强大以及重要,只要Spring容器中存在这个类型的Bean,就相当于开启了AOP,AbstractAdvisorAutoProxyCreator实际上就是一个BeanPostProcessor,所以在创建某个Bean时,就会进入到它对应的生命周期方法中,比如:在某个Bean初始化之后,会调用wrapIfNecessary()方法进行AOP,底层逻辑是,AbstractAdvisorAutoProxyCreator会找到所有的Advisor,然后判断当前这个Bean是否存在某个Advisor与之匹配(根据Pointcut),如果匹配就表示当前这个Bean有对应的切面逻辑,需要进行AOP,需要产生一个代理对象。

15. @EnableAspectJAutoProxy

这个注解主要就是往Spring容器中添加了一个AnnotationAwareAspectJAutoProxyCreator类型的Bean。

AspectJAwareAdvisorAutoProxyCreator继承了AbstractAdvisorAutoProxyCreator,重写了findCandidateAdvisors()方法,AbstractAdvisorAutoProxyCreator只能找到所有Advisor类型的Bean对象,但是AspectJAwareAdvisorAutoProxyCreator除开可以找到所有Advisor类型的Bean对象,还能把@Aspect注解所标注的Bean中的@Before等注解及方法进行解析,并生成对应的Advisor对象。

所以可以理解@EnableAspectJAutoProxy,其实就是像Spring容器中添加了一个AbstractAdvisorAutoProxyCreator类型的Bean,从而开启了AOP,并且还会解析@Before等注解生成Advisor。

  • 43
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值