1.依赖注入到底有几种?两种、四种、五种?

两种:

手动:set(byType、byName)、构造器

自动:xml中:set、构造器 autowired注解中:set、属性、构造器

重点不在于到底有几种,而在于是否真的理解了依赖注入。

2.@Autowired注解方式自动注入源码过程

具体分析属性填充populateBean方法:只看关键部分,注入的部分

  // 是否在BeanDefinition中设置了属性值PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);int resolvedAutowireMode = mbd.getResolvedAutowireMode();if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {// by_name是根据根据属性名字找bean// by_type是根据属性所对应的set方法的参数类型找bean// 找到bean之后都要调用set方法进行注入MutablePropertyValues newPvs = new MutablePropertyValues(pvs);// Add property values based on autowire by name if applicable.if (resolvedAutowireMode == AUTOWIRE_BY_NAME) {autowireByName(beanName, mbd, bw, newPvs);}// Add property values based on autowire by type if applicable.if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {autowireByType(beanName, mbd, bw, newPvs);}pvs = newPvs;// 注意,执行完这里的代码之后,这是把属性以及找到的值存在了pvs里面,并没有完成反射赋值}// 执行完了Spring的自动注入之后,就开始解析@Autowired,这里叫做实例化回调boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);PropertyDescriptor[] filteredPds = null;if (hasInstAwareBpps) {if (pvs == null) {pvs = mbd.getPropertyValues();}for (BeanPostProcessor bp : getBeanPostProcessors()) {if (bp instanceof InstantiationAwareBeanPostProcessor) {InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;// 调用BeanPostProcessor分别解析@Autowired、@Resource、@Value,得到属性值PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);if (pvsToUse == null) {if (filteredPds == null) {filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);}pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);if (pvsToUse == null) {return;}}pvs = pvsToUse;}}}
}

xml方式的byName和byType

简单说一下xml的byType和byName

Spring在通过byName的自动填充属性时流程是:

  1. 找到所有set方法所对应的XXX部分的名字
  2. 根据XXX部分的名字去获取bean

Spring在通过byType的自动填充属性时流程是:

  1. 找到所有set方法所对应的XXX部分的名字
  2. 根据XXX部分的名字重新再获取得到PropertyDescriptor
  3. 获取到set方法中的唯一参数的类型,并且根据该类型去容器中获取bean
  4. 如果找到多个,会报错

然后进autowireByType方法看一下源码:resolveDependency 是一个核心方法,稍后在@Autowired处分析。beanDefinition方式解析不做过多分析

TypeConverter converter = getCustomTypeConverter();
if (converter == null) {converter = bw;
}Set<String> autowiredBeanNames = new LinkedHashSet<>(4);// 找到有对应set方法的属性
String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);
for (String propertyName : propertyNames) {try {PropertyDescriptor pd = bw.getPropertyDescriptor(propertyName);// Don't try autowiring by type for type Object: never makes sense,// even if it technically is a unsatisfied, non-simple property.if (Object.class != pd.getPropertyType()) {// set方法中的参数信息MethodParameter methodParam = BeanUtils.getWriteMethodParameter(pd);// Do not allow eager init for type matching in case of a prioritized post-processor.// 当前Bean是否实现了PriorityOrderedboolean eager = !(bw.getWrappedInstance() instanceof PriorityOrdered);DependencyDescriptor desc = new AutowireByTypeDependencyDescriptor(methodParam, eager);// 根据类型找bean,这就是byTypeObject autowiredArgument = resolveDependency(desc, beanName, autowiredBeanNames, converter);if (autowiredArgument != null) {pvs.add(propertyName, autowiredArgument);}for (String autowiredBeanName : autowiredBeanNames) {registerDependentBean(autowiredBeanName, beanName);if (logger.isTraceEnabled()) {logger.trace("Autowiring by type from bean name '" + beanName + "' via property '" +propertyName + "' to bean named '" + autowiredBeanName + "'");}}autowiredBeanNames.clear();}}catch (BeansException ex) {throw new UnsatisfiedDependencyException(mbd.getResourceDescription(), beanName, propertyName, ex);}
}

接下来分析@Autowired方式注入,重新回到填充属性方法populateBean中,看这一段BeanPostProcessor的postProcessProperties方法调用。

for (BeanPostProcessor bp : getBeanPostProcessors()) {if (bp instanceof InstantiationAwareBeanPostProcessor) {InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;//用于进行@Autowired和@Value、@Resource的注入PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);if (pvsToUse == null) {if (filteredPds == null) {filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);}pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);if (pvsToUse == null) {return;}}pvs = pvsToUse;}
}

直至循环到AutowiredAnnotationBeanPostProcessor的postProcessProperties方法,

public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {//获取注入的元信息InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);try {metadata.inject(bean, beanName, pvs);}catch (BeanCreationException ex) {throw ex;}catch (Throwable ex) {throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);}return pvs;
}

然后深入到findAutowiringMetadata()->buildAutowiringMetadata()方法中,看这一段源码:

// 遍历属性,看是否有@Autowired,@Value,@Inject注解
ReflectionUtils.doWithLocalFields(targetClass, field -> {// 如果存在@Autowired,@Value,@Inject注解其中一个MergedAnnotation<?> ann = findAutowiredAnnotation(field);if (ann != null) {// 如果字段是static的,则直接进行返回,不进行注入if (Modifier.isStatic(field.getModifiers())) {if (logger.isInfoEnabled()) {logger.info("Autowired annotation is not supported on static fields: " + field);}return;}//required属性注入boolean required = determineRequiredStatus(ann);// 生成一个注入点AutowiredFieldElement,这个在属性填充前(MergedBeanDefinitionPostProcessor中)//,此时只是告诉spring要注入这些,还没有真正注入。//真正注入还是在属性填充内完成currElements.add(new AutowiredFieldElement(field, required));}
});

@Autowired为什么不支持静态的 ?

静态变量、类变量不是对象的属性,而是一个类的属性,所以静态方法是属于类(class)的,普通方法才是属于实体对象(也就是New出来的对象)的,spring注入是在容器中实例化对象,所以不能使用静态方法。而使用静态变量、类变量扩大了静态方法的使用范围。静态方法在spring是不推荐使用的,依赖注入的主要目的,是让容器去产生一个对象的实例,然后在整个生命周期中使用他们,同时也让testing工作更加容易。一旦你使用静态方法,就不再需要去产生这个类的实例,这会让testing变得更加困难,同时你也不能为一个给定的类,依靠注入方式去产生多个具有不同的依赖环境的实例,这种static field是隐含共享的,并且是一种global全局状态,spring同样不推荐这样去做。

然后进行进入findAutowiredAnnotation方法,该方法是为了获取是否有这些注解

private MergedAnnotation<?> findAutowiredAnnotation(AccessibleObject ao) {// 查看当前字段上是否存在@Autowired,@Value,@Inject注解,存在其中一个则返回,表示可以注入MergedAnnotations annotations = MergedAnnotations.from(ao);// autowiredAnnotationTypes是一个LinkedHashSet,所以会按顺序去判断当前字段中是否有Autowired注解,如果有则返回// 如果没有Autowired注解,那么则判断是否有Value注解,再判断是否有Inject注解for (Class<? extends Annotation> type : this.autowiredAnnotationTypes) {MergedAnnotation<?> annotation = annotations.get(type);if (annotation.isPresent()) {return annotation;}}return null;
}

为什么会只查看@Autowired,@Value,@Inject注解?

因为autowiredAnnotationTypes里只添加了这三个,为什么会先判断@Autowired,是因为是LinkedHashSet,按添加顺序去判断,看一下源码添加逻辑

public AutowiredAnnotationBeanPostProcessor() {this.autowiredAnnotationTypes.add(Autowired.class);this.autowiredAnnotationTypes.add(Value.class);try {//如果有这个类就添加,没有则跳过this.autowiredAnnotationTypes.add((Class<? extends Annotation>)ClassUtils.forName("javax.inject.Inject", AutowiredAnnotationBeanPostProcessor.class.getClassLoader()));logger.trace("JSR-330 'javax.inject.Inject' annotation found and supported for autowiring");}catch (ClassNotFoundException ex) {// JSR-330 API not available - simply skip.}
}

我们返回到buildAutowiringMetadata()方法中:刚才是遍历了所有属性,接下来是遍历所有方法,本质上还是通过方法获取到了属性。

// 遍历方法,看是否有@Autowired,@Value,@Inject注解
ReflectionUtils.doWithLocalMethods(targetClass, method -> {Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {return;}MergedAnnotation<?> ann = findAutowiredAnnotation(bridgedMethod);if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {// 静态方法不能用来注入属性if (Modifier.isStatic(method.getModifiers())) {if (logger.isInfoEnabled()) {logger.info("Autowired annotation is not supported on static methods: " + method);}return;}// 方法参数值为0,不能用来注入属性if (method.getParameterCount() == 0) {if (logger.isInfoEnabled()) {logger.info("Autowired annotation should only be used on methods with parameters: " +method);}}// 看是否有@required注解boolean required = determineRequiredStatus(ann);// 根据方法找出对应的属性PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);currElements.add(new AutowiredMethodElement(method, required, pd));}
});

findAutowiringMetadata()方法内的内容执行完毕,获取到注入点的元信息metadata,然后接下来将注入点注入进去。

public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {//获取到注解的元信息metadataInjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);try {//将注入点进行实际注入metadata.inject(bean, beanName, pvs);}catch (BeanCreationException ex) {throw ex;}catch (Throwable ex) {throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);}return pvs;
}

进入到inject方法中,这里是循环将InjectedElement进行注入。

if (!elementsToIterate.isEmpty()) {for (InjectedElement element : elementsToIterate) {if (logger.isTraceEnabled()) {logger.trace("Processing injected element of bean '" + beanName + "': " + element);}element.inject(target, beanName, pvs);}
}

再向里面走,进入inject方法,这时使用的是子类重写的inject方法,而不是自己的inject方法。

protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {Field field = (Field) this.member;Object value;if (this.cached) {// 当前注入点已经注入过了,有缓存了,则利用cachedFieldValue去找对应的beanvalue = resolvedCachedArgument(beanName, this.cachedFieldValue);}else {//  Spring在真正查找属性对应的对象之前, 会先将该属性的描述封装成一个DependencyDescriptor(依赖描述)对象,// 里面保存了Filed、是否强制需要即required, 以及属性所在的类(即Field所在的类Class对象)DependencyDescriptor desc = new DependencyDescriptor(field, this.required);desc.setContainingClass(bean.getClass());Set<String> autowiredBeanNames = new LinkedHashSet<>(1);Assert.state(beanFactory != null, "No BeanFactory available");TypeConverter typeConverter = beanFactory.getTypeConverter();try {// 根据field去寻找合适的beanvalue = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);}catch (BeansException ex) {throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);}synchronized (this) {if (!this.cached) {if (value != null || this.required) {this.cachedFieldValue = desc;// 注册当前bean依赖了哪些其他的bean的nameregisterDependentBeans(beanName, autowiredBeanNames);if (autowiredBeanNames.size() == 1) {String autowiredBeanName = autowiredBeanNames.iterator().next();if (beanFactory.containsBean(autowiredBeanName) &&beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {// 对得到的对象进行缓存this.cachedFieldValue = new ShortcutDependencyDescriptor(desc, autowiredBeanName, field.getType());}}}else {this.cachedFieldValue = null;}this.cached = true;}}}// 反射设值,直到这里才真真正正的将注入点要注入的内容注入进来,完成赋值if (value != null) {ReflectionUtils.makeAccessible(field);field.set(bean, value);}
}

如何根据field去寻找合适的bean,深入到的方法resolveDependency()里。该方法里前半部分主要为了判断依赖描述的类型,直接看后半段。

如Optional方法用get得到。ObjectFactory可用于注入每次都去getObject,如原型模式可以是不同对象。

else {// 在使用@Autowired注解时,也可以使用@Lazy注解,到时候注入的会是一个代理对象Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(descriptor, requestingBeanName);if (result == null) {// 通过解析descriptor找到bean对象result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);}return result;
}

具体解析还是在doResolveDependency()方法中,进去查看,这个方法内容比较多,我们来慢慢分析

一开始进行了缓存判断和@Value注解的解析

//依赖描述信息的缓存判断
Object shortcut = descriptor.resolveShortcut(this);
if (shortcut != null) {return shortcut;
}
//获取依赖描述的类
Class<?> type = descriptor.getDependencyType();
//获取该注入对象上的@Value注解,这也可以知道@Autowired和@Value是可以同时使用的
Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);

获取到@Value后进行判断是否存在,先分析存在的情况

if (value != null) {if (value instanceof String) {// 先进行占位符的填充,解析"$"符号,如@Value("${aa}")String strVal = resolveEmbeddedValue((String) value);BeanDefinition bd = (beanName != null && containsBean(beanName) ?getMergedBeanDefinition(beanName) : null);// 解析Spring EL表达式,解析"#"符号(可以进行运算,可以写某个bean的名字)value = evaluateBeanDefinitionString(strVal, bd);}

再来分析不存在的情况,先判断了依赖描述的类型

// 要注入的依赖的类型是不是一个Map、Array、Collection,不是的话就继续走。
Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
if (multipleBeans != null) {return multipleBeans;
}

当不是以上三种类型的情况下,到下面调用了这个方法findAutowireCandidates(寻找注入的候选者),在resolveMultipleBeans方法中其实也是这个核心方法。

返回的map中的value是对象,可能是bean对象,也可能是class对象,因为该方法不负责实例化。

Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);

接下来分析一下findAutowireCandidates这个方法。

//去当前beanfactory以及祖先beanfactory中找类型为requiredType的bean的名字,将找到的name作为候选者记录下来
String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this, requiredType, true, descriptor.isEager());
Map<String, Object> result = new LinkedHashMap<>(candidateNames.length);
//把resolvableDependencies中保存的对象作为当前属性的一个候选者? ------这段代码不是太理解
for (Map.Entry<Class<?>, Object> classObjectEntry : this.resolvableDependencies.entrySet()) {Class<?> autowiringType = classObjectEntry.getKey();if (autowiringType.isAssignableFrom(requiredType)) {Object autowiringValue = classObjectEntry.getValue();autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType);if (requiredType.isInstance(autowiringValue)) {result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue);break;}}
}
//对候选者进行过滤,
//两个判断,第一个判断是否是自己的引用
//第二个判断为是否能被注入,里面包含了对@Qualifier注解的判断,如果有@Qualifier,则确认@Qualifier的name与descriptor的是否一致
//如:@Autowired
//@Qualifier("order")
//public Order order22;
//就会注入一个beanName为order,调用为order22的bean。
//只使用@Autowired的话会寻找到type为order的bean,如果只有一个bean,那么正确返回,如果有多个则抛出异常
for (String candidate : candidateNames) {if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, descriptor)) {addCandidateEntry(result, candidate, descriptor, requiredType);}
}
//如果已经有候选者则直接返回,没有则进行下面流程
if (result.isEmpty()) {boolean multiple = indicatesMultipleBeans(requiredType);//如果第一次传递找不到任何内容,请考虑回退匹配。。。DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch();for (String candidate : candidateNames) {if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, fallbackDescriptor) &&(!multiple || getAutowireCandidateResolver().hasQualifier(descriptor))) {addCandidateEntry(result, candidate, descriptor, requiredType);}}if (result.isEmpty() && !multiple) {// 如果还是没有,则放宽要求,允许自己注入自己,但不是同一个beanfor (String candidate : candidateNames) {if (isSelfReference(beanName, candidate) &&(!(descriptor instanceof MultiElementDescriptor) || !beanName.equals(candidate)) &&isAutowireCandidate(candidate, fallbackDescriptor)) {addCandidateEntry(result, candidate, descriptor, requiredType);}}}
}
return result;

然后返回了候选者列表,如果不存在就去判断required是否为true,为true则报错,为false直接返回null

Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
if (matchingBeans.isEmpty()) {if (isRequired(descriptor)) {raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);}return null;
}

接着开始判断候选者的个数,如果只有一个,则返回,多个进行进一步的处理。

先来分析简单的,一个的情况,会执行下面代码

else {// 只有一个Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();autowiredBeanName = entry.getKey();instanceCandidate = entry.getValue();
}

接下来就是将获得的autowiredBeanName和instanceCandidate进行一些判断,不一一列举了。找个典型的:

//来判断获取的这个对象是bean还是class对象,如果是class对象则调用getBean方法
if (instanceCandidate instanceof Class) {instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
}

然后回到候选者数量判断那里,如果是多个的情况:会调用determineAutowireCandidate方法

if (matchingBeans.size() > 1) {autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);

进入determineAutowireCandidate方法,里面进行了@Primary和@Priority注解的判断,可以得知顺序为先判断@Primary,再判断@Priority

String primaryCandidate = determinePrimaryCandidate(candidates, requiredType);
if (primaryCandidate != null) {return primaryCandidate;
}
String priorityCandidate = determineHighestPriorityCandidate(candidates, requiredType);
if (priorityCandidate != null) {return priorityCandidate;
}

至此,resolveDependency方法的核心内容分析完毕,整个依赖注入的过程也分析完了,稍微总结一下。

@Autowired注入过程总结

依赖注入的流程:在bean实例化后进行属性填充,忽略xml配置的方式,只说@Autowired的过程。

1.通过InstantiationAwareBeanPostProcessor#postProcessProperties切入,进行@Autowired和@Value、@Resource的注入点的注入。

2.判断是否存在@Autowired,@Value,@Inject注解是否存在任意一个,存在就保存为注入点,以便之后的注入。

3.将注入点进行注入。

3.1 发现有@Value注解,则进行#和$的表达式的解析,返回相应bean。

3.2 进行注入,发现没有@Value注解,则寻找候选者。

4.寻找到所有符合的候选者,然后进行过滤

4.1如果有@Qualifier注解,则进行判断@Qualifier上的name与bean中的name是否一致,符合的通过筛选

5.筛选结束,进行候选个数判断

5.1.如果只有一个,则返回对应的bean

5.2.如果还有多个,则进一步的筛选

6.进行@Primary和@Priority注解的筛选

6.1.如果有一个@Primary注解,则返回对应的bean,多个报错。

6.2.如果没有@Primary注解,则进行@Priority注解的筛选,数字越小优先级越高,返回优先级最高的bean

7.通过反射就bean的值设置进来,完成注入。

(每次没找到bean,就会判断一下required)

题外话:如果是@Resource方式注入(j2ee的注解),有几种方式(针对同种type有多个的情况):

1.如果不写name,但存在对应的bean,则直接返回。

2.如果不写name,还不存在bean,则报错。

3.如果写了name,但不存在对应的bean,则会执行@Autowired的逻辑。

4.如果写了name,并存在对应的bean,则会直接通过byName去找。

spring源码解析(二) @Autowired自动注入过程相关推荐

  1. Spring源码解析二之创建Bean(实例化)

    上一节我们分析到了createBean,而真正创建Bean的过程在doCreateBean过程,我们可以发现Spring的编码风格,do才是真正的过程,不带do的通常是在做在准备过程,并且我们跳过了一 ...

  2. Spring @Resource 源码解析 – 为什么是ByName注入

    前言 上篇博客[@Autowired 源码为什么是ByType注入]跟着源码详细的说明了@Autowired在Spring源码里面是如何设计为byType注入的.本篇博客的主要内容就是源码追踪探究@R ...

  3. Spring源码解析【完整版】--【bilibili地址:https://www.bilibili.com/video/BV1oW41167AV】

    [本文为bilibili视频雷丰阳的Spring源码解析的完整版总结文章,其中文章前面大部分为他人博文的搬运,后面补充了其未总结的部分] 一.Java的注解 1. 注解的概念 注释:用文字描述程序,给 ...

  4. SpringBoot源码分析(二)之自动装配demo

    SpringBoot源码分析(二)之自动装配demo 文章目录 SpringBoot源码分析(二)之自动装配demo 前言 一.创建RedissonTemplate的Maven服务 二.创建测试服务 ...

  5. Spring 源码解析 - Bean创建过程 以及 解决循环依赖

    一.Spring Bean创建过程以及循环依赖 上篇文章对 Spring Bean资源的加载注册过程进行了源码梳理和解析,我们可以得到结论,资源文件中的 bean 定义信息,被组装成了 BeanDef ...

  6. Spring源码解析-bean实例化

    Spring源码解析-bean实例化 ​ 本文介绍Spring创建 bean 过程中的第一个步骤:实例化 bean. 1. Bean实例化源码 ​ 虽然实例化Bean有多种方式(包括静态工厂和工厂实例 ...

  7. 源码解析:Spring源码解析笔记(五)接口设计总览

    本文由colodoo(纸伞)整理 QQ 425343603 Java学习交流群(717726984) Spring解析笔记 启动过程部分已经完成,对启动过程源码有兴趣的朋友可以作为参考文章. 源码解析 ...

  8. Spring源码解析之-- 事务TransactionInterceptor 分析(开启事务)

    目录 一.介绍 二.TransactionInterceptor 分析 2. 流程 2.1 invoke 2.1.1 TransactionAspectSupport#invokeWithinTran ...

  9. Spring源码解析 - AbstractBeanFactory 实现接口与父类分析

    2019独角兽企业重金招聘Python工程师标准>>> 我们先来看类图吧: 除了BeanFactory这一支的接口,AbstractBeanFactory主要实现了AliasRegi ...

  10. Spring源码解析:自定义标签的解析过程

    2019独角兽企业重金招聘Python工程师标准>>> spring version : 4.3.x Spring 中的标签分为默认标签和自定义标签两类,上一篇我们探究了默认标签的解 ...

最新文章

  1. 在Linux 上安装WAS7.0
  2. linux中date函数的使用方法,linux date -d 的一些使用方法
  3. Windows via C/C++ 学习(15)线程调度、线程优先级和亲缘性
  4. 重磅:.NET 6 发布首个预览版
  5. Nagle算法延时确认
  6. packer build 报错 无任何输出 一直报“skipping line: 1 skipping line: 2 skipping line: 3.....”
  7. vue中选项和url根据彼此的改变实现高亮显示
  8. 一个简单的Webservice的demo,简单模拟服务
  9. sql游标 while_用SQL Server中的排名函数替换SQL While循环和游标,以提高查询性能
  10. 一则 gfs2 集群文件系统无法挂载的解决案例
  11. 简单的达梦数据库使用DISTINCT去重
  12. Word自动生成的目录超出页边距
  13. k8s集群Canal的网络控制
  14. 百行代码手撸扫雷(下)c/c++
  15. VR全景航拍补天教程
  16. 用python计算圆柱体的体积和表面积_Java圆柱体表面积和体积计算代码实例
  17. Seata异常:endpoint format should like ip:port
  18. 手机配件实体店好做不_震惊!手机实体店,你不得不防的套路!
  19. c语言:求二元一次方程根的所有情况
  20. cron每半个小时执行一次_如何用crontab设定程序每半个小时执行一次

热门文章

  1. 推进生态社会化分工 与伙伴共担未来 数商云受邀出席京东科技合作伙伴论坛
  2. <Verilog实现除法器> Verilog实现不恢复余数(non-restoring)除法器
  3. Photoshop Elements 2018 For Dummies 免积分下载
  4. BGP基础配置(3)——解决数据黑洞
  5. 追逐自己的梦想----------辅助制作第十四课:给出自动打怪和技能打怪的源码
  6. hackthissite(Basic missions level1-10)攻略
  7. linux查询当前主机的IP地址,根据ip地址查询其他主机名(Linux)
  8. 企业上云一张网,华为将在分析师大会上亮出哪些大招?
  9. 电商收付通,公众号H5合单支付同步存储openId
  10. js设置字体大小自适应屏幕分辨率