Spring源码学习14

1.AnnotationAwareAspectJAutoProxyCreator.java

  • Ordered:{@code Ordered}是一个可以由<em> 可排序 </ em>的对象实现的接口,例如在{@code Collection}中,方法order()获取对象的优先级较高的值被解释为较低的优先级。 作为结果,具有最低值的对象具有最高优先级
  • ProxyConfig:用于创建代理的配置的便捷超类,确保所有代理创建者都具有一致的属性
  • AopInfrastructureBean: 标记接口,指示一个bean作为Spring的一部分的beanAOP基础设施。 特别是,这意味着任何这样的bean  即使切入点匹配,也不受自动代理的影响
  • ProxyProcessorSupport:为代理处理器提供基本功能的类,特别是ClassLoader管理和{@link #evaluateProxyInterfaces}算法
  • AbstractAutoProxyCreator:它使用AOP代理包装每个符合条件的bean,在调用bean本身之前委托给指定的拦截器,说白了该方法是实现AOP的核心类,暴露了三处实现AOP的地方,下文会提到
  • AbstractAdvisorAutoProxyCreator:基于检测到的每个bean的Advisors;为bean构建AOP代理的通用自动代理创建程序;子类可以通过覆盖{@link #findCandidateAdvisors()}以返回任何对象自定的Advisors的列表,子类还可以覆盖继承的{@link #shouldSkip}方法,以从自动代理中排除某些对象。
  • AspectJAwareAdvisorAutoProxyCreator:当多个advice来自同一个切面暴露AspectJ's调用上下文的并且整理advice优先级的AspectJ的规则  {@link org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator}子类。实现了sortAdvisor方法和shouldSkip,该类已经满足不通过@Aspect实现AOP。
  • AnnotationAwareAspectJAutoProxyCreator: {@link AspectJAwareAdvisorAutoProxyCreator}子类,处理当前应用程序上下文中的所有AspectJ注释的切面,以及Spring Advisors任何AspectJ带注释的类都将被自动识别,如果advice是Spring Aop的proxy-based模型能够处理的  那么advice将会被应用,这包括方法执行连接点;如果<aop:include>元素被应用了,那么只有加了@AspectJ的bean并且名称匹配pattern的name的才会被考虑作为使用Spring 自动代理的切面。

2.我们分析核心类AbstractAutoProxyCreator,它实现了SmartInstantiationAwareBeanPostProcessor接口,所以能够对返回bean实例化前postProcessBeforeInstantiation(文章https://blog.csdn.net/qq_23536449/article/details/95458034提到过!)、解决循环引用getEarlyObject、实例化后postProcessAfterInitialization方法里面做些骚操作;那就是创建代理bean的代理对象!!!

3.我们重点分析AbstractAutoProxyCreator.postProcessBeforeInstantiation,其他两处创建代理对象过程大同小异的感觉!

/**
	 * Create a proxy with the configured interceptors if the bean is
	 * identified as one to proxy by the subclass.
	 *
	 * 如果bean被子类标识为一个代理使用配置的拦截器创建代理
	 * @see #getAdvicesAndAdvisorsForBean
	 */
	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		if (bean != null) {
			Object cacheKey = getCacheKey(bean.getClass(), beanName);
			//如果通过getEarlyObject方法创建了代理则返回
			if (this.earlyProxyReferences.remove(cacheKey) != bean) {
				return wrapIfNecessary(bean, beanName, cacheKey);
			}
		}
		return bean;
	}

/**
	 * Wrap the given bean if necessary, i.e. if it is eligible for being proxied.
	 * @param bean the raw bean instance
	 * @param beanName the name of the bean
	 * @param cacheKey the cache key for metadata access
	 * @return a proxy wrapping the bean, or the raw bean instance as-is
	 */
	protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
		//如果通过targetSource创建了bean
		if (beanName != null && this.targetSourcedBeans.contains(beanName)) {
			return bean;
		}
		//无需增强 下面 advisedBeans 可能此时还没有含有 cacheKey, 所以 get 出 null
		if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
			return bean;
		}
		//bean是为aop的基础设施类比如pointcut、advice、advisor类 || 增强类不需要创建代理
		if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
			this.advisedBeans.put(cacheKey, Boolean.FALSE);
			return bean;
		}

		// Create proxy if we have advice.
		// 查找bean的增强方法
		Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
		if (specificInterceptors != DO_NOT_PROXY) {
			this.advisedBeans.put(cacheKey, Boolean.TRUE);
			//创建代理方法
			Object proxy = createProxy(
					bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
			this.proxyTypes.put(cacheKey, proxy.getClass());
			return proxy;
		}

		this.advisedBeans.put(cacheKey, Boolean.FALSE);
		return bean;
	}

4.shouldskip()方法

@Override
	protected boolean shouldSkip(Class<?> beanClass, String beanName) {
		// TODO: Consider optimization by caching the list of the aspect names
		//通过缓存方面名称列表来考虑优化
		List<Advisor> candidateAdvisors = findCandidateAdvisors();
		for (Advisor advisor : candidateAdvisors) {
			//该类型advisor都是通过aop:advice配置而来的
			//他的aspectName就是定义的切面增强的bean
			//所以对于实现切面增强的bean不用创建代理
			if (advisor instanceof AspectJPointcutAdvisor) {
				if (((AbstractAspectJAdvice) advisor.getAdvice()).getAspectName().equals(beanName)) {
					return true;
				}
			}
		}
		return super.shouldSkip(beanClass, beanName);
	}

使用xml方式比如:

<bean id="aspectBean" class="org.springframework.study.day11.AspectBean"/>
    <bean id="explictAspect" class="org.springframework.study.day11.ExplictAspect"/>

    <aop:config>
        <!--切入点-->
        <aop:pointcut expression="execution(* *.aspectTest(..))" id="p1"/>
        <!-- 配置aop操作中的切面 -->
        <aop:aspect ref="explictAspect" order="0">
            <aop:around method="aroundAdvice" pointcut-ref="p1"/>
            <aop:before method="beforeAdvice" pointcut-ref="p1"/>
            <aop:after method="afterAdvice" pointcut-ref="p1"/>
        </aop:aspect>
    </aop:config>

ExplictAspect.java

public class ExplictAspect {

    public void beforeAdvice(){
        System.out.println("before advice");
    }

    public void afterAdvice(){
        System.out.println("after advice");
    }

    public Object aroundAdvice(ProceedingJoinPoint p) throws Throwable {
        System.out.println("arround-before");
        Object o = null;
        o = p.proceed();
        System.out.println("arround-after");
        return o;
    }
}

对于ExplictAspect.java他是个增强bean,该bean不应该创建代理,shouldSkip方法就是做这个判断。

5.getAdvicesAndAdvisorsForBean;该方法查找bean的所有增强,被委托给了AbstractAdvisorAutoProxyyCreator实现

@Override
	protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, TargetSource targetSource) {
		List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
		if (advisors.isEmpty()) {
			return DO_NOT_PROXY;
		}
		return advisors.toArray();
	}

/**
	 * 为了自动代理该类,查找所有符合条件的advisors
	 * @param beanClass the clazz to find advisors for
	 * @param beanName the name of the currently proxied bean
	 * @return the empty List, not {@code null},
	 * if there are no pointcuts or interceptors
	 * @see #findCandidateAdvisors
	 * @see #sortAdvisors
	 * @see #extendAdvisors
	 */
	protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
		//获取所有的advisors
		List<Advisor> candidateAdvisors = findCandidateAdvisors();
		//寻找该类匹配的advisor
		List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
		//扩展advisors,实际上添加了一个DefaultPointcutAdvisor
		extendAdvisors(eligibleAdvisors);
		if (!eligibleAdvisors.isEmpty()) {
			eligibleAdvisors = sortAdvisors(eligibleAdvisors);
		}
		return eligibleAdvisors;
	}

6.我们关注下findCadidateAdvisors()方法,由于我们分析的是AnnotationAwareAspectJAutoProxyCreator所以我们进入AnnotationAwareAspectJAutoProxyCreator.findCandidateAdvisors()

@Override
	protected List<Advisor> findCandidateAdvisors() {
		// Add all the Spring advisors found according to superclass rules.
		// 加载所有的Spring advisors 使用超类的方法
		List<Advisor> advisors = super.findCandidateAdvisors();
		// Build Advisors for all AspectJ aspects in the bean factory.
		// 从@Aspect标记的bean中提取所有Advisors切面
		advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
		return advisors;
	}

super.findCandidateAdvisors()方法很简单,就是读取使用xml配置的增强和切点,advisor

/**
	 * Find all eligible Advisor beans in the current bean factory,
	 * ignoring FactoryBeans and excluding beans that are currently in creation.
	 *
	 * 找到当前bean工厂中所有符合条件的Advisor bean,
	 * 忽略FactoryBeans并排除当前正在创建的bean。
	 * @return the list of {@link org.springframework.aop.Advisor} beans
	 * @see #isEligibleBean
	 */
	public List<Advisor> findAdvisorBeans() {
		// 如果cachedAdvisorBeanNames尚未确定,则定advisor的beanName
		String[] 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!
			//不要在这里初始化FactoryBeans:我们需要保留所有常规bean
			//未初始化让自动代理创建者用于他们!
			advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
					this.beanFactory, Advisor.class, true, false);
			this.cachedAdvisorBeanNames = advisorNames;
		}
		if (advisorNames.length == 0) {
			return new ArrayList<Advisor>();
		}

		List<Advisor> advisors = new ArrayList<Advisor>();
		for (String name : advisorNames) {
			if (isEligibleBean(name)) {
				if (this.beanFactory.isCurrentlyInCreation(name)) {
					if (logger.isDebugEnabled()) {
						logger.debug("Skipping currently created advisor '" + name + "'");
					}
				}
				else {
					try {
						//创建advisor并添加到advisors集合
						advisors.add(this.beanFactory.getBean(name, Advisor.class));
					}
					catch (BeanCreationException ex) {
						Throwable rootCause = ex.getMostSpecificCause();
						if (rootCause instanceof BeanCurrentlyInCreationException) {
							BeanCreationException bce = (BeanCreationException) rootCause;
							if (this.beanFactory.isCurrentlyInCreation(bce.getBeanName())) {
								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;
	}

advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());

@Aspect解析的Advisor UML类图:

  • InstantiationModelAwarePointcutAdvisor:由Spring AOP Advisors实现的接口包含可能具有延迟初始化策略的AspectJ切面。 例如perThis 、perTarget模型 将会意味着懒惰的初始化advice;大致意思为@Aspect支持不同的切面类型比如perThis、perTarget,而这种模型需要对Advice进行懒加载,故接口提供了isLazy()方法
  • AspectJPrecedenceInformation:接口由可提供信息的类型实现 需要按照AspectJ的优先规则对advise/advisor进行排序
  • InstantiationModelAwarePointcutAdvisorImpl:AspectJPointcutAdvisor的内部实现。注意每个目标方法即添加@Aspect注解的类里面的切面方法将有一个此advisor的实例

代码分析:

/**
	 *
	 * 在当前工厂查找带有AspectJ注解的切面beans,
	 * 并返回代表他们的Spring AOP Advisors列表
	 *
	 * <p>为每个AspectJ切面的advice创建一个advisor
	 * @return the list of {@link org.springframework.aop.Advisor} beans
	 * @see #isEligibleBean
	 */
	public List<Advisor> buildAspectJAdvisors() {
		List<String> aspectNames = this.aspectBeanNames;
		//尚未被缓存
		if (aspectNames == null) {
			synchronized (this) {
				aspectNames = this.aspectBeanNames;
				if (aspectNames == null) {
					//创建一个LinkList保存AspectJ注解里面的切面
					List<Advisor> advisors = new LinkedList<Advisor>();
					aspectNames = new LinkedList<String>();
					//查找所有的beanName
					String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
							this.beanFactory, Object.class, true, false);
					//循环所有的beanName
					for (String beanName : beanNames) {
						//判断beanName是否符合调价
						//如果配置文件配置了<aop:include>
						//那么只有符合表达式条件的切面类的增强才会被提取,谢谢
						if (!isEligibleBean(beanName)) {
							continue;
						}
						// We must be careful not to instantiate beans eagerly as in this case they
						// would be cached by the Spring container but would not have been weaved.
						// 我们必须小心的不要急切的实例化beans
						// 因为在这种情况下,它们将被Spring容器缓存但不会被编织。
						Class<?> beanType = this.beanFactory.getType(beanName);
						//获取bean对应的类型为null进入下次循环
						if (beanType == null) {
							continue;
						}
						//判断有无AspectJ注解
						if (this.advisorFactory.isAspect(beanType)) {
							//添加切面beanNames
							aspectNames.add(beanName);
							AspectMetadata amd = new AspectMetadata(beanType, beanName);
							/**
							 * 切面实例化模型简介
							 *
							 * singleton: 即切面只会有一个实例;
							 * perthis  : 每个切入点表达式匹配的连接点对应的AOP对象都会创建一个新切面实例;
							 *            使用@Aspect("perthis(切入点表达式)")指定切入点表达式;
							 *            例如: @Aspect("perthis(this(com.lyc.cn.v2.day04.aspectj.Dog))")
							 * pertarget: 每个切入点表达式匹配的连接点对应的目标对象都会创建一个新的切面实例;
							 *            使用@Aspect("pertarget(切入点表达式)")指定切入点表达式;
							 *            例如:
							 *
							 * 默认是singleton实例化模型,Schema风格只支持singleton实例化模型,而@AspectJ风格支持这三种实例化模型。
							 */
							if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
								//解析AspectJ中标记为增强的方法
								MetadataAwareAspectInstanceFactory factory =
										new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
								List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
								//如果切面(带有@Aspect注解)bean式单例模式,则将advisors放到缓存,下次直接取得
								if (this.beanFactory.isSingleton(beanName)) {
									this.advisorsCache.put(beanName, classAdvisors);
								}
								//否则将用于创建advisor的factory放到缓存,下次获取advisors使用
								else {
									this.aspectFactoryCache.put(beanName, factory);
								}
								advisors.addAll(classAdvisors);
							}
							else {
								// Per target or per this.
								// 切面bean是单例的,但是切面实例化模型却不是单例的,所以抛出异常
								if (this.beanFactory.isSingleton(beanName)) {
									throw new IllegalArgumentException("Bean with name '" + beanName +
											"' is a singleton, but aspect instantiation model is not singleton");
								}
								//创建一个beanFactory用于创建advisor并加入到缓存
								MetadataAwareAspectInstanceFactory factory =
										new PrototypeAspectInstanceFactory(this.beanFactory, beanName);
								this.aspectFactoryCache.put(beanName, factory);
								advisors.addAll(this.advisorFactory.getAdvisors(factory));
							}
						}
					}
					this.aspectBeanNames = aspectNames;
					return advisors;
				}
			}
		}
		//没有bean有@Aspect注解
		if (aspectNames.isEmpty()) {
			return Collections.emptyList();
		}
		//从缓存中获取advisors
		List<Advisor> advisors = new LinkedList<Advisor>();
		for (String aspectName : aspectNames) {
			List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName);
			if (cachedAdvisors != null) {
				advisors.addAll(cachedAdvisors);
			}
			else {
				MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName);
				advisors.addAll(this.advisorFactory.getAdvisors(factory));
			}
		}
		return advisors;
	}

貌似很长啊:我们分解下方法

  • a.获取beanFactory中的所有bean
  • b.过滤出带有@Apsect注解的bean,并保存到aspectNames中
  • c.构造AspectMetadata
  • d.判断切面的实例化模型:比如singleton,perthis,pertarget然后实例化advisor,我们关注下:切面为单例模型的情况下解析@Aspect标注的bean的advisor的创建;看不懂也罢。
  • @Override
    	public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {
    		//获取切面类
    		Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
    		//获取切面名称
    		String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();
    		//验证切面类
    		validate(aspectClass);
    
    		// We need to wrap the MetadataAwareAspectInstanceFactory with a decorator
    		// so that it will only instantiate once.
    		//我们需要一个装饰器包装MetadataAwareAspectInstanceFactory,这样它只会实例化一次
    		MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory =
    				new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);
    		//提取增强
    		List<Advisor> advisors = new ArrayList<Advisor>();
    		//
    		for (Method method : getAdvisorMethods(aspectClass)) {
    			Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName);
    			if (advisor != null) {
    				advisors.add(advisor);
    			}
    		}
    
    		// If it's a per target aspect, emit the dummy instantiating aspect.
    		// 如果aspect是pertarget类型,则
    		if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
    			//创建SyntheticInstantiationAdvisor类型的advisor
    			Advisor instantiationAdvisor = new SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory);
    			//添加到advisors的第一位
    			advisors.add(0, instantiationAdvisor);
    		}
    
    		// Find introduction fields.
    		// 循环得到aspectClass的declaredFields
    		for (Field field : aspectClass.getDeclaredFields()) {
    			//这个就比较简单了实例化一个DeclareParentsAdvisor
    			Advisor advisor = getDeclareParentsAdvisor(field);
    			if (advisor != null) {
    				advisors.add(advisor);
    			}
    		}
    
    		return advisors;
    	}
    @Override
    	public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory,
    			int declarationOrderInAspect, String aspectName) {
    		//又是一次验证
    		validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());
    		//提取切入点
    		AspectJExpressionPointcut expressionPointcut = getPointcut(
    				candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());
    		if (expressionPointcut == null) {
    			return null;
    		}
    		//创建切入点增强
    		return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,
    
  • e.判断有无@Aspect注解的bean
  • f.从缓存拿到所有advisors

7.findAdvisorsThatCanApply;这个方法根据beanClass过滤出适合自己的advisor,

/**
	 * 确定适用于给定class的{@code candidateAdvisors}的子列表advisors
	 * @param candidateAdvisors the Advisors to evaluate
	 * @param clazz the target class
	 * @return sublist of Advisors that can apply to an object of the given class
	 * (may be the incoming List as-is)
	 */
	public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
		if (candidateAdvisors.isEmpty()) {
			return candidateAdvisors;
		}
		List<Advisor> eligibleAdvisors = new LinkedList<Advisor>();
		for (Advisor candidate : candidateAdvisors) {
			//判断class是否符合IntroductionAdvisor的增强
			if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
				eligibleAdvisors.add(candidate);
			}
		}
		//是否包含intructions,就是那个给代理类动态添加方法的
		boolean hasIntroductions = !eligibleAdvisors.isEmpty();
		for (Advisor candidate : candidateAdvisors) {
			if (candidate instanceof IntroductionAdvisor) {
				// already processed
				continue;
			}
			if (canApply(candidate, clazz, hasIntroductions)) {
				eligibleAdvisors.add(candidate);
			}
		}
		return eligibleAdvisors;
	}
    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);
		}
		else {
			// It doesn't have a pointcut so we assume it applies.
			return true;
		}
	}
public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
		//class不匹配直接退出
		Assert.notNull(pc, "Pointcut must not be null");
		if (!pc.getClassFilter().matches(targetClass)) {
			return false;
		}
		//获取methodMatcher
		MethodMatcher methodMatcher = pc.getMethodMatcher();
		//为true直接返回true
		if (methodMatcher == MethodMatcher.TRUE) {
			// No need to iterate the methods if we're matching any method anyway...
			return true;
		}
		//向上造型为IntroductionAwareMethodMatcher
		IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
		if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
			introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
		}
		//获取targetClass的所有classes包括父类
		Set<Class<?>> classes = new LinkedHashSet<Class<?>>(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
		classes.add(targetClass);
		for (Class<?> clazz : classes) {
			Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
			for (Method method : methods) {
				//自身类或者有其中一个父类方法匹配成功了就可以
				if ((introductionAwareMethodMatcher != null &&
						introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions)) ||
						methodMatcher.matches(method, targetClass)) {
					return true;
				}
			}
		}

		return false;
	}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对于学习Spring源码的网站,可以参考以下链接: 1. Spring官方网站:https://spring.io/ 在官方网站上,你可以找到关于Spring的所有文档、教程和源码下载链接。它是学习Spring的首要资源。 2. GitHub上的Spring源码仓库:https://github.com/spring-projects/spring-framework 这是Spring源码的官方GitHub仓库,你可以在这里找到最新的Spring源码,并参与到开发讨论中。 3. CSDN博客:https://blog.csdn.net/navyfrost/article/details/102919323 这是一篇关于Spring源码学习的博客文章,作者分享了学习Spring源码的心得和方法,并提供了一些学习资源和案例。 希望以上资源可以帮助你开始学习Spring源码。祝你学习顺利!<span class="em">1</span><span class="em">2</span><span class="em">3</span><span class="em">4</span> #### 引用[.reference_title] - *1* [Spring源码学习加注释,方便学习.zip](https://download.csdn.net/download/weixin_47367099/85350853)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* *4* [Spring源码学习系列——源码下载和环境](https://blog.csdn.net/shangguoli/article/details/124710529)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值