死磕Spring之AOP篇 - Spring AOP总览

月圆吖 2021-04-16 13:24:13
spring AOP 总览


该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读。

Spring 版本:5.1.14.RELEASE

在开始阅读 Spring AOP 源码之前,需要对 Spring IoC 有一定的了解,可查看我的 《死磕Spring之IoC篇 - 文章导读》 这一系列文章

了解 AOP 相关术语,可先查看 《Spring AOP 常见面试题) 》 这篇文章

该系列其他文章请查看:《死磕 Spring 之 AOP 篇 - 文章导读》

通过上一篇 《初识 JDK、CGLIB 两种动态代理》 文章我们对 Spring AOP 底层的 JDK 动态代理和 CGLIB 动态代理有了一定的了解,也知道如何简单地使用两种动态代理创建代理对象。相信上篇文章可以让你对 Spring AOP 有了一个初步的认识,那么接下来我们准备进入 Spring AOP 源码学习阶段。

在开始 Spring AOP 源码学习前,本文会对 Spring AOP 涉及到的大部分主要的 API 进行介绍,让我们对 Spring AOP 有一个全面的认识,这样在学习 Spring AOP 源码的过程中有一个清晰的思路会更加易于理解,然后在后续的文章从 Spring AOP 的自动代理开始深入学习,直接进入正题吧。

Spring AOP API 总览

如下图所示:

上面这张图片列出了 Spring AOP 涉及到的大部分 API,接下来我们依次简单介绍一下:

  • Joinpoint:连接点,也就是我们需要执行的目标方法

  • Pointcut:切点,提供 ClassFilter 类过滤器和 MethodMatcher 方法匹配器支持对类和方法进行筛选。

  • Advice:通知器,一般都是 MethodInterceptor 方法拦截器,不是的话会通过 AdvisorAdapter 适配器转换成对应的 MethodInterceptor 方法拦截器。

  • Advisor:Advice 容器接口,与 Advice 是一对一的关系;它的子接口 PointcutAdvisor 相当于一种 Pointcut 和 Advice 的容器,将 Pointcut 过滤 Joinpoint 的能力和 Advice 进行整合,这样一来就将两者关联起来了。

  • Advisor 适配器AdvisorAdapter 是 Advisor 的适配器,当筛选出能够应用于方法的所有 Advisor 后,需要获取对应的 Advice;

    • 如果不是 MethodInterceptor 类型,则需要通过 AdvisorAdapter 适配器转换成对应的 MethodInterceptor 方法拦截器。
  • AOP 代理对象:AOP 代理对象,由 JDK 动态代理或者 CGLIB 动态代理创建的代理对象;

    • 选择哪种动态代理是通过 AopProxyFactory 代理工厂根据目标类来决定的。
  • AOP 代理配置AdvisedSupport 配置管理器中保存了对应代理对象的配置信息,例如满足条件的 Advisor 对象、TargetSource 目标类来源;

    • 在创建代理对象的过程,AdvisedSupport 扮演着一个非常重要的角色。
  • AOP 代理对象创建:AOP 代理对象的创建分为手动模式自动模式;不管哪种模式都和 AdvisedSupport 配置管理器有关;

    • 手动模式就是通过 Spring AOP 提供的 API 进行创建;
    • 自动模式则是和 Spring IoC 进行整合,在 Bean 的加载过程中如果需要进行代理,则创建对应的代理对象;
  • AdvisorChainFactoryAdvisor 链路工厂

    • AdvisedSupport 配置管理器中保存了代理对象的所有 Advisor 对象,当拦截某个方法时,需要通过 AdvisorChainFactory 筛选出能够应用于该方法的 Advisor 们;
    • 另外还需要借助 AdvisorAdapter 适配器获取 Advisor 对应的 MethodInterceptor 方法拦截器,将这些 MethodInterceptor 有序地形成一条链路并返回。
  • IntroductionInfo引介接口,支持代理对象实现目标类没有真正实现的额外的接口;

    • 在 Advisor 的子接口 IntroductionAdvisor 中会继承这个 IntroductionInfo 接口,通过 @DeclareParents 注解定义的字段会被解析成 IntroductionAdvisor 对象。
  • AOP 代理目标对象来源目标类来源,和代理对象进行关联,用于获取被代理代理的目标对象;

    • 在代理对象中最终的方法执行都需要先通过 TargetSource 获取对应的目标对象,然后执行目标方法。

Joinpoint

类图

  • ConstructorInvocation,是基于构造器的一个调用器,在 Spring AOP 中仅支持方法级别的代理,所以这个接口并没有被使用

  • ReflectiveMethodInvocation,Joinpoint 的底层实现类,一个基于反射的方法调用器,本文不进行分析,在后续的文章进行讲解

  • CglibMethodInvocation,和上者实现类差不多,新增了一个 CGLIB 中的 MethodProxy 方法代理对象,本文不进行分析,在后续的文章进行讲解

org.aopalliance.intercept.Joinpoint,连接点,也就是我们需要执行的目标方法,是由 AOP 联盟提供的一个接口,如下:

public interface Joinpoint {
/**
* 执行方法调用器中的下一个拦截器,执行完所有的拦截器则执行目标方法
*/
Object proceed() throws Throwable;
/**
* 返回目标对象
*/
Object getThis();
/**
* 返回目标方法
*/
AccessibleObject getStaticPart();
}

org.aopalliance.intercept.Invocation,调用器,是由 AOP 联盟提供的一个接口,如下:

public interface Invocation extends Joinpoint {
/**
* 获取参数数组
*/
Object[] getArguments();
}

MethodInvocation

org.aopalliance.intercept.MethodInvocation,方法调用器,是由 AOP 联盟提供的一个接口,如下:

public interface MethodInvocation extends Invocation {
/**
* 获取目标方法
*/
Method getMethod();
}

ProxyMethodInvocation

org.springframework.aop.ProxyMethodInvocation,MethodInvocation 方法调用器的扩展,允许访问代理对象,如下:

public interface ProxyMethodInvocation extends MethodInvocation {
/**
* 返回代理对象
*/
Object getProxy();
/**
* 克隆方法
*/
MethodInvocation invocableClone();
/**
* 克隆方法,使用参数
*/
MethodInvocation invocableClone(Object... arguments);
/**
* 设置参数数组
*/
void setArguments(Object... arguments);
/**
* 添加自定义属性
*/
void setUserAttribute(String key, @Nullable Object value);
/**
* 获取自定义属性
*/
@Nullable
Object getUserAttribute(String key);
}

Pointcut

类图

  • AspectJExpressionPointcut,基于 AspectJ 处理表达式的能力实现的一个 Pointcut,本文不进行分析,在后续的文章进行讲解

上述类图仅列出了部分 Pointcut 相关的接口

org.springframework.aop.Pointcut,切点,提供 ClassFilter 类过滤器和 MethodMatcher 方法匹配器支持对类和方法进行筛选,如下:

public interface Pointcut {
/**
* 返回类过滤器
*/
ClassFilter getClassFilter();
/**
* 返回方法匹配器
*/
MethodMatcher getMethodMatcher();
/**
* 总是匹配通过的 Pointcut 实例对象
*/
Pointcut TRUE = TruePointcut.INSTANCE;
}

org.springframework.aop.support.ExpressionPointcut,支持表达式匹配的 Pointcut,如下:

public interface ExpressionPointcut extends Pointcut {
/**
* 返回表达式
*/
@Nullable
String getExpression();
}

StaticMethodMatcherPointcut

org.springframework.aop.support.StaticMethodMatcherPointcut,抽象类,本身是一个方法匹配器的 Pointcut,如下:

public abstract class StaticMethodMatcherPointcut extends StaticMethodMatcher implements Pointcut {
/** 默认情况类过滤器总是匹配通过 */
private ClassFilter classFilter = ClassFilter.TRUE;
/**
* 设置类过滤器
*/
public void setClassFilter(ClassFilter classFilter) {
this.classFilter = classFilter;
}
/**
* 获取类过滤器
*/
@Override
public ClassFilter getClassFilter() {
return this.classFilter;
}
/**
* 返回方法匹配器,就是当前对象,因为当前对象继承的 StaticMethodMatcher 就是一个 MethodMatcher
*/
@Override
public final MethodMatcher getMethodMatcher() {
return this;
}
}

我们自定义的 Pointcut 通常可以继承 StaticMethodMatcherPointcut 来实现,重写 MethodMatcher matches(Method method, Class<?> targetClass, Object... args) 方法即可,也可以再实现 ClassFilter,实现 matches(Class clazz) 方法

ClassFilter

org.springframework.aop.ClassFilter,函数式接口,类过滤器,用于判断这个 Class 是否满足 Pointcut 切点,如下:

@FunctionalInterface
public interface ClassFilter {
/**
* 匹配这个 Class 类
*/
boolean matches(Class<?> clazz);
/**
* 总是匹配通过的类过滤器
*/
ClassFilter TRUE = TrueClassFilter.INSTANCE;
}

MethodMatcher

org.springframework.aop.MethodMatcher,方法匹配器,用于判断这个 Method 是否满足 Pointcut 切点,如下:

public interface MethodMatcher {
/**
* 匹配这个方法
*/
boolean matches(Method method, Class<?> targetClass);
/**
* 当前方法匹配器是否为运行状态
*/
boolean isRuntime();
/**
* 匹配这个方法,并校验参数
*/
boolean matches(Method method, Class<?> targetClass, Object... args);
/**
* 总是匹配通过的方法匹配器
*/
MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
}

Advice

类图

  • Advice,标记接口
  • BeforeAdvice,标记接口,后置通知
  • AfterAdvice,标记接口,前置通知
  • Interceptor,标记接口,拦截器
  • AbstractAspectJAdvice,包装了一个 Aspect 切面或者 AspectJ 注解标注的方法,Around、Before、After、AfterReturning、AfterThrowing 几种类型的 Advice 最终都会转换成 AbstractAspectJAdvice 对应的子类并实现 MethodInterceptor 方法拦截器接口,本文不进行分析,在后续的文章进行讲解

上述类图仅列出了部分 Advice 相关的接口

MethodInterceptor

org.aopalliance.intercept.MethodInterceptor,方法拦截器,函数式接口,如下:

@FunctionalInterface
public interface MethodInterceptor extends Interceptor {
/**
* 执行 MethodInvocation 调用器
*/
Object invoke(MethodInvocation invocation) throws Throwable;
}

Advisor

类图

上面只列出了 Advisor 的部分实现类,在 Spring AOP 中还有非常多的实现类,在后续的文章进行分析

  • AbstractBeanFactoryPointcutAdvisor,关联 BeanFactory 的 PointcutAdvisor 抽象类,支持从 IoC 容器中获取 Advice 对象,我们可以通过继承这个类来自定义一个 PointcutAdvisor 对象

org.springframework.aop.Advisor,Advice 容器接口,与 Advice 是一对一的关系,如下:

public interface Advisor {
/**
* 空的 Advice
*/
Advice EMPTY_ADVICE = new Advice() {};
/**
* 获取 Advice
*/
Advice getAdvice();
/**
* 返回这个 Advice 是否有一个指定的目标实例对象,默认都是 true
*/
boolean isPerInstance();
}

PointcutAdvisor

org.springframework.aop.PointcutAdvisor,继承 Advisor 接口,Pointcut 和 Advice 的容器,将 Pointcut 过滤 Joinpoint 的能力和 Advice 进行整合,这样一来就将两者关联起来了,如下:

public interface PointcutAdvisor extends Advisor {
/**
* 获取 Pointcut 切点
*/
Pointcut getPointcut();
}

IntroductionAdvisor

org.springframework.aop.IntroductionAdvisor,继承 Advisor 和 IntroductionInfo 接口,支持代理对象实现目标类没有真正实现的额外的接口;通过 @DeclareParents 注解定义的字段会被解析成 IntroductionAdvisor 对象,如下:

public interface IntroductionAdvisor extends Advisor, IntroductionInfo {
/**
* 获取类加载器,因为没有 Pointcut 切点,则提供一个类过滤器
*/
ClassFilter getClassFilter();
/**
* 校验是否实现 IntroductionInfo 中的接口
*/
void validateInterfaces() throws IllegalArgumentException;
}
public interface IntroductionInfo {
/**
* 返回额外的接口
*/
Class<?>[] getInterfaces();
}

AdvisorAdapter

类图

org.springframework.aop.framework.adapter.AdvisorAdapter,Advisor 的适配器,当筛选出能够应用于方法的所有 Advisor 后,需要获取对应的 Advice;如果不是 MethodInterceptor 类型,则需要通过 AdvisorAdapter 适配器转换成对应的 MethodInterceptor 方法拦截器,如下:

public interface AdvisorAdapter {
/**
* 是否支持该类型的 Advice 适配
*/
boolean supportsAdvice(Advice advice);
/**
* 获取 Advisor 的 Advice,并包装成对应的 MethodInterceptor 方法拦截器
*/
MethodInterceptor getInterceptor(Advisor advisor);
}

org.springframework.aop.framework.adapter.MethodBeforeAdviceAdapter,前置 Advice 的适配器,如下:

class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {
@Override
public boolean supportsAdvice(Advice advice) {
return (advice instanceof MethodBeforeAdvice);
}
@Override
public MethodInterceptor getInterceptor(Advisor advisor) {
MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
return new MethodBeforeAdviceInterceptor(advice);
}
}

可以看到这个 MethodBeforeAdviceAdapter 适配器只能在 Spring 内部使用,先判断 Advice 是否为 MethodBeforeAdvice 类型,如果是的话,则可以将 Advice 包装成 MethodBeforeAdviceInterceptor 对象,其他的适配器类似。

当然,AdvisorAdapter 适配器不是直接被使用,而是通过 DefaultAdvisorAdapterRegistry 适配器注册中心使用的,在后续文章进行分析

AOP 代理对象

类图

org.springframework.aop.framework.AopProxy,AOP 代理接口,用于创建代理对象,如下:

public interface AopProxy {
/**
* 创建一个代理对象
*/
Object getProxy();
/**
* 创建一个代理对象,传入指定的 ClassLoader 类加载器
*/
Object getProxy(@Nullable ClassLoader classLoader);
}

在 Spring AOP 中有 JdkDynamicAopProxy 和 CglibAopProxy 两种实现类,分别代表 JDK 动态代理和 CGLIB 动态代理。选择哪种动态代理是通过 DefaultAopProxyFactory 代理工厂根据目标类来决定的,这些内容在后续文章进行分析

AOP 代理配置

类图

org.springframework.aop.framework.ProxyConfig,代理配置类,如下:

/**
* @see AdvisedSupport
*/
public class ProxyConfig implements Serializable {
/** use serialVersionUID from Spring 1.2 for interoperability. */
private static final long serialVersionUID = -8409359707199703185L;
/** 是否使用类代理 */
private boolean proxyTargetClass = false;
private boolean optimize = false;
boolean opaque = false;
/** 是否暴露代理对象 */
boolean exposeProxy = false;
/** 配置是否被冻结 */
private boolean frozen = false;
}

这个类仅提供一些顶层的配置信息,更多和创建代理对象相关的配置信息都在子类 AdvisedSupport 中,这些内容在后续文章进行分析

AOP 代理对象创建

类图

AOP 代理对象的创建分为手动模式自动模式;不管哪种模式都和 AdvisedSupport 配置管理器有关;

  • 手动模式就是通过 Spring AOP 提供的 API 进行创建,例如 ProxyFactory 代理工厂;
  • 自动模式则是和 Spring IoC 进行整合,在 Bean 的加载过程中如果需要进行代理,则创建对应的代理对象;可以看到 AbstractAutoProxyCreator 实现了 BeanPostProcessor 相关接口,在加载 Bean 的过程中,初始化后会调用 BeanPostProcessor#postProcessAfterInitialization 的初始化后置处理方法,在 AbstractAutoProxyCreator 中则可以创建对应的代理对象(如果有必要的话)

上面这些 AOP 自动代理类在后续文章会进行分析

AOP 代理目标对象来源

类图

org.springframework.aop.TargetSource目标类来源,和代理对象进行关联,用于获取被代理代理的目标对象,如下:

public interface TargetSource extends TargetClassAware {
/**
* 获取目标对象的 Class 对象
*/
@Override
@Nullable
Class<?> getTargetClass();
/**
* 是否是静态的,返回 `fasle` 表示每次获取目标对象都需要进行创建,那么每次创建都需要释放,例如原型模式的 Bean
*/
boolean isStatic();
/**
* 获取目标对象
*/
@Nullable
Object getTarget() throws Exception;
/**
* 释放目标对象
*/
void releaseTarget(Object target) throws Exception;
}

SingletonTargetSource

org.springframework.aop.target.SingletonTargetSource,单例 Bean 的目标对象来源,如下:

public class SingletonTargetSource implements TargetSource, Serializable {
/** use serialVersionUID from Spring 1.2 for interoperability. */
private static final long serialVersionUID = 9031246629662423738L;
/** 保存目标对象 */
private final Object target;
/**
* 创建实例对象的时候就是设置目标对象
*/
public SingletonTargetSource(Object target) {
Assert.notNull(target, "Target object must not be null");
this.target = target;
}
@Override
public Class<?> getTargetClass() {
return this.target.getClass();
}
@Override
public Object getTarget() {
return this.target;
}
@Override
public void releaseTarget(Object target) {
// nothing to do
}
@Override
public boolean isStatic() {
return true;
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof SingletonTargetSource)) {
return false;
}
SingletonTargetSource otherTargetSource = (SingletonTargetSource) other;
return this.target.equals(otherTargetSource.target);
}
@Override
public int hashCode() {
return this.target.hashCode();
}
@Override
public String toString() {
return "SingletonTargetSource for target object [" + ObjectUtils.identityToString(this.target) + "]";
}
}

单例 Bean 对应的 TargetSource 都是 SingletonTargetSource,里面保存了这个单例 Bean。

总结

本文开头的图片展示了 Spring AOP 涉及到的大部分主要的 API,围绕着这张图片对这些 API 进行简单的讲述,对 Spring AOP 可以有一个整体的认识。不过 Spring AOP 还有一个部分,如何开启整个 AOP 模块在本文没有体现出来,简单提一下,通过 @EnableAspectJAutoProxy 注解或者 <aop:aspectj-autoproxy /> XML 配置可以激活 AOP 模块,底层会注册一个 AbstractAutoProxyCreator 类型的 Bean 完成 AOP 自动代理,具体过程在后面的文章进行分析

  • 在 Spring AOP 中有着 Joinpoint 连接点,Pointcut 切点和 Advice 通知,通过 Advisor 接口保存一个 Advice,其子接口 PointcutAdvisor 又关联一个 Pointcut,将 Pointcut 过滤 Joinpoint 的能力和 Advice 进行整合,这样一来就将两者关联起来了。

  • Spring AOP 传递 Advice 的过程通常是通过 Advisor 实现的,而使用 Advice 的过程都是通过 MethodInterceptor 执行,如果不是该类型则通过 AdvisorAdapter 适配器将其包装成对应的 MethodInterceptor 方法拦截器。

  • Sping AOP 底层有 JDK 动态代理和 CGLIB 动态代理两种方式,分别对应 JdkDynamicAopProxyCglibAopProxy,选择哪种代理方式需要通过 DefaultAopProxyFactory 代理工厂根据目标类来决定。

  • 在 Spring AOP 中一个 AdvisedSupport 配置管理器,里面保存对应代理对象的配置信息,例如满足条件的 Advisor 对象、TargetSource 目标类来源,AdvisedSupport 在 Spring AOP 中扮演一个重要的角色。

  • Spring AOP 中的自动代理主要由 AbstractAutoProxyCreator 这个 Bean 进行创建,因为它实现了几种 BeanPostProcessor,例如在 Bean 加载过程中,初始化后会调用 AbstractAutoProxyCreator 的方法进行处理,返回一个代理对象(如果有必要的话)。

在后续的文章我们主要围绕自动代理进行分析,对于 Bean 的加载过程不熟悉的小伙伴可查看我前面的《死磕Spring之IoC篇 - 文章导读》文章,或者直接查看《死磕Spring之IoC篇 - Bean 的创建过程》 这篇文章

版权声明
本文为[月圆吖]所创,转载请带上原文链接,感谢
https://www.cnblogs.com/lifullmoon/p/14654845.html

  1. 【计算机网络 12(1),尚学堂马士兵Java视频教程
  2. 【程序猿历程,史上最全的Java面试题集锦在这里
  3. 【程序猿历程(1),Javaweb视频教程百度云
  4. Notes on MySQL 45 lectures (1-7)
  5. [computer network 12 (1), Shang Xuetang Ma soldier java video tutorial
  6. The most complete collection of Java interview questions in history is here
  7. [process of program ape (1), JavaWeb video tutorial, baidu cloud
  8. Notes on MySQL 45 lectures (1-7)
  9. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  10. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  11. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  12. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  13. 【递归,Java传智播客笔记
  14. [recursion, Java intelligence podcast notes
  15. [adhere to painting for 386 days] the beginning of spring of 24 solar terms
  16. K8S系列第八篇(Service、EndPoints以及高可用kubeadm部署)
  17. K8s Series Part 8 (service, endpoints and high availability kubeadm deployment)
  18. 【重识 HTML (3),350道Java面试真题分享
  19. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  20. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  21. [re recognize HTML (3) and share 350 real Java interview questions
  22. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  23. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  24. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  25. RPC 1: how to develop RPC framework from scratch
  26. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  27. RPC 1: how to develop RPC framework from scratch
  28. 一次性捋清楚吧,对乱糟糟的,Spring事务扩展机制
  29. 一文彻底弄懂如何选择抽象类还是接口,连续四年百度Java岗必问面试题
  30. Redis常用命令
  31. 一双拖鞋引发的血案,狂神说Java系列笔记
  32. 一、mysql基础安装
  33. 一位程序员的独白:尽管我一生坎坷,Java框架面试基础
  34. Clear it all at once. For the messy, spring transaction extension mechanism
  35. A thorough understanding of how to choose abstract classes or interfaces, baidu Java post must ask interview questions for four consecutive years
  36. Redis common commands
  37. A pair of slippers triggered the murder, crazy God said java series notes
  38. 1、 MySQL basic installation
  39. Monologue of a programmer: despite my ups and downs in my life, Java framework is the foundation of interview
  40. 【大厂面试】三面三问Spring循环依赖,请一定要把这篇看完(建议收藏)
  41. 一线互联网企业中,springboot入门项目
  42. 一篇文带你入门SSM框架Spring开发,帮你快速拿Offer
  43. 【面试资料】Java全集、微服务、大数据、数据结构与算法、机器学习知识最全总结,283页pdf
  44. 【leetcode刷题】24.数组中重复的数字——Java版
  45. 【leetcode刷题】23.对称二叉树——Java版
  46. 【leetcode刷题】22.二叉树的中序遍历——Java版
  47. 【leetcode刷题】21.三数之和——Java版
  48. 【leetcode刷题】20.最长回文子串——Java版
  49. 【leetcode刷题】19.回文链表——Java版
  50. 【leetcode刷题】18.反转链表——Java版
  51. 【leetcode刷题】17.相交链表——Java&python版
  52. 【leetcode刷题】16.环形链表——Java版
  53. 【leetcode刷题】15.汉明距离——Java版
  54. 【leetcode刷题】14.找到所有数组中消失的数字——Java版
  55. 【leetcode刷题】13.比特位计数——Java版
  56. oracle控制用户权限命令
  57. 三年Java开发,继阿里,鲁班二期Java架构师
  58. Oracle必须要启动的服务
  59. 万字长文!深入剖析HashMap,Java基础笔试题大全带答案
  60. 一问Kafka就心慌?我却凭着这份,图灵学院vip课程百度云