Spring源码解析(七):bean后置处理器AutowiredAnnotationBeanPostProcessor
Spring源码系列文章
Spring源码解析(一):环境搭建
Spring源码解析(二):bean容器的创建、默认后置处理器、扫描包路径bean
Spring源码解析(三):bean容器的刷新
Spring源码解析(四):单例bean的创建流程
Spring源码解析(五):循环依赖
Spring源码解析(六):bean定义后置处理器ConfigurationClassPostProcessor
Spring源码解析(七):bean后置处理器AutowiredAnnotationBeanPostProcessor
目录
- 一、AutowiredAnnotationBeanPostProcessor简介
- 二、determineCandidateConstructors(筛选候选构造函数)
- 三、postProcessMergedBeanDefinition(查询@Autowired @Value属性)
- 1、 findAutowiringMetadata
- 2、checkConfigMembers
- 四、postProcessProperties(属性填充)
- 1、inject
- 2、字段的属性注入
- 3、方法的属性注入
- 4、查询匹配的bean对象
- 4.1、搜索类型匹配的bean的Map
- 4.2、出现多个bean,如何筛选最后的bean
- 5、冷知识:使用@Value进行依赖注入
一、AutowiredAnnotationBeanPostProcessor简介
类图如下:
SmartInstantiationAwareBeanPostProcessor#determineCandidateConstructors
- 过滤出可以作为构造注入的构造函数列表
MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition
- 查找bean的@Autowired @Value属性方法并缓存起来
InstantiationAwareBeanPostProcessor#postProcessProperties
- @Autowired @Resource注解属性填充
AutowiredAnnotationBeanPostProcessor构造函数
- autowiredAnnotationTypes 集合中保存了该类会处理的注解
- autowiredAnnotationTypes 中添加了三个注解
@Autowired
、@Value
、和通过反射得到的javax.inject.Inject
private final Set<Class<? extends Annotation>> autowiredAnnotationTypes = new LinkedHashSet<>(4);...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.}
}
二、determineCandidateConstructors(筛选候选构造函数)
- 触发时机:
bean实例化
时候,获取bean的构造函数 - 其作用是从注入bean的所有构造函数中过滤出可以作为构造注入的构造函数列表
@Override
@Nullable
public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, final String beanName)throws BeanCreationException {// 在这里首先处理了@Lookup注解// 判断是否已经解析过 。lookupMethodsChecked 作为一个缓存集合,保存已经处理过的beanif (!this.lookupMethodsChecked.contains(beanName)) {if (AnnotationUtils.isCandidateClass(beanClass, Lookup.class)) {try {Class<?> targetClass = beanClass;do {// 遍历bean中的每一个方法ReflectionUtils.doWithLocalMethods(targetClass, method -> {// 判断 方法是否被 @Lookup 修饰Lookup lookup = method.getAnnotation(Lookup.class);if (lookup != null) {Assert.state(this.beanFactory != null, "No BeanFactory available");// 如果被@Lookup 修饰,则封装后保存到RootBeanDefinition 的methodOverrides 属性中,在 SimpleInstantiationStrategy#instantiate(RootBeanDefinition, String, BeanFactory) 进行了cglib的动态代理。LookupOverride override = new LookupOverride(method, lookup.value());try {RootBeanDefinition mbd = (RootBeanDefinition)this.beanFactory.getMergedBeanDefinition(beanName);mbd.getMethodOverrides().addOverride(override);}catch (NoSuchBeanDefinitionException ex) {throw new BeanCreationException(beanName,"Cannot apply @Lookup to beans without corresponding bean definition");}}});targetClass = targetClass.getSuperclass();}while (targetClass != null && targetClass != Object.class);}catch (IllegalStateException ex) {throw new BeanCreationException(beanName, "Lookup method resolution failed", ex);}}// 将已经解析好的beanName 添加到缓存中this.lookupMethodsChecked.add(beanName);}// 这里开始处理构造函数// 获取bean的所有候选构造函数Constructor<?>[] candidateConstructors = this.candidateConstructorsCache.get(beanClass);if (candidateConstructors == null) {synchronized (this.candidateConstructorsCache) {candidateConstructors = this.candidateConstructorsCache.get(beanClass);// 如果候选构造构造函数为空if (candidateConstructors == null) {Constructor<?>[] rawCandidates;try {// 获取所有访问权限的构造函数rawCandidates = beanClass.getDeclaredConstructors();}catch (Throwable ex) {throw new BeanCreationException(beanName,"Resolution of declared constructors on bean Class [" + beanClass.getName() +"] from ClassLoader [" + beanClass.getClassLoader() + "] failed", ex);}// 根据所有构造函数数量创建候选集合List<Constructor<?>> candidates = new ArrayList<>(rawCandidates.length);// @Autowired(required = true) 的构造函数,有且只能有一个Constructor<?> requiredConstructor = null;// 默认的无参构造函数Constructor<?> defaultConstructor = null;// 针对 Kotlin 语言的构造函数,不太明白,一般为nullConstructor<?> primaryConstructor = BeanUtils.findPrimaryConstructor(beanClass);int nonSyntheticConstructors = 0;for (Constructor<?> candidate : rawCandidates) {// 构造函数是否是非合成// 一般我们自己创建的都是非合成的// Java在编译过程中可能会出现一些合成的构造函数if (!candidate.isSynthetic()) {nonSyntheticConstructors++;}else if (primaryConstructor != null) {continue;}// 遍历autowiredAnnotationTypes集合// 判断当前构造函数是否被autowiredAnnotationTypes集合中的注解修饰,若未被修饰,则返回null// autowiredAnnotationTypes 集合中的注解在一开始就说了是 @Autowired、@Value 和 @Inject 三个。 MergedAnnotation<?> ann = findAutowiredAnnotation(candidate);if (ann == null) {// 如果未被修饰,这里判断是否是 Cglib 的代理类,如果是则获取原始类,否则直接返回beanClassClass<?> userClass = ClassUtils.getUserClass(beanClass);// 如果这里不相等,肯定是通过 cglib的代理类,这里的userClass 就是原始类// 再次判断构造函数是否包含指定注解if (userClass != beanClass) {try {Constructor<?> superCtor =userClass.getDeclaredConstructor(candidate.getParameterTypes());ann = findAutowiredAnnotation(superCtor);}catch (NoSuchMethodException ex) {// Simply proceed, no equivalent superclass constructor found...}}}if (ann != null) {// 如果已经找到了必须装配的构造函数(requiredConstructor != null)// 那么当前这个就是多余的,则抛出异常if (requiredConstructor != null) {throw new BeanCreationException(beanName,"Invalid autowire-marked constructor: " + candidate +". Found constructor with 'required' Autowired annotation already: " +requiredConstructor);}// 确定是否是必须的,@Autowired 和 @Inject 默认为true// @Autowired 可以通过 required 修改boolean required = determineRequiredStatus(ann);if (required) {// 如果当前构造函数为必须注入,但是候选列表不为空,则说明已经有构造函数适配,则抛出异常// 就是只要有required = true的构造函数就不允许存在其他可注入的构造函数if (!candidates.isEmpty()) {throw new BeanCreationException(beanName,"Invalid autowire-marked constructors: " + candidates +". Found constructor with 'required' Autowired annotation: " +candidate);}// 到这一步,说明当前构造函数是必须的,且目前没有其他构造函数候选// 直接将当前构造函数作为必须构造函数requiredConstructor = candidate;}// 添加到候选列表candidates.add(candidate);}// 如果 构造函数参数数量为0,则是默认构造函数,使用默认构造函数else if (candidate.getParameterCount() == 0) {defaultConstructor = candidate;}}// 如果候选构造函数不为空if (!candidates.isEmpty()) {// 将默认构造函数添加到可选构造函数列表中,作为回退if (requiredConstructor == null) {if (defaultConstructor != null) {candidates.add(defaultConstructor);}else if (candidates.size() == 1 && logger.isInfoEnabled()) {logger.info("Inconsistent constructor declaration on bean with name '" + beanName +"': single autowire-marked constructor flagged as optional - " +"this constructor is effectively required since there is no " +"default constructor to fall back to: " + candidates.get(0));}}candidateConstructors = candidates.toArray(new Constructor<?>[0]);}// 如果 当前bean只有一个有参构造函数,那么将此构造函数作为候选列表返回// (这就代表,如果bean中只有一个有参构造函数并不需要使用特殊注解,也会作为构造函数进行注入)else if (rawCandidates.length == 1 && rawCandidates[0].getParameterCount() > 0) {candidateConstructors = new Constructor<?>[] {rawCandidates[0]};}//下面这一段判断不是太理解else if (nonSyntheticConstructors == 2 && primaryConstructor != null &&defaultConstructor != null && !primaryConstructor.equals(defaultConstructor)) {candidateConstructors = new Constructor<?>[] {primaryConstructor, defaultConstructor};}else if (nonSyntheticConstructors == 1 && primaryConstructor != null) {candidateConstructors = new Constructor<?>[] {primaryConstructor};}else {candidateConstructors = new Constructor<?>[0];}this.candidateConstructorsCache.put(beanClass, candidateConstructors);}}}return (candidateConstructors.length > 0 ? candidateConstructors : null);
}
总结
- 解析@Lookup 注解的方法,保存到 RootBeanDefinition 中
- 从缓存中获取筛选好的构造函数列表,若有直接返回,没有则进行下一步
- 通过反射获取bean 的所有构造函数,并进行构造函数遍历
- 筛选每个构造函数是否被 @Autowired @Inject注解修饰
- 当前构造函数没有被修饰,则判断当前bean是否Cglib动态代理类
- 如果是,则获取原始类的构造函数
- 再判断 构造函数是否被 @Autowired、@Inject 注解修饰
- 如果筛选出候选构造函数
- 如果有一个必须注入的构造函数(
@Autowired(required =true
)或者 @Inject )- 则不允许有其他候选构造函数出现
- 有且只能筛选出一个必须注入的构造函数
- 如果不存在必须注入的构造含函数 (
@Autowired(required =false
) 或者 @Inject)- 则允许多个候选注入构造函数出现(@Autowired(required = false) 修饰的构造函数)
- 并且将这个几个候选构造函数返回
- 如果bean有且
只有一个
构造函数- 即使没有被注解修饰,也会调用该构造函数作为bean创建的构造函使用
- 如果有一个必须注入的构造函数(
三、postProcessMergedBeanDefinition(查询@Autowired @Value属性)
- 实例化之后会调用所有MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition方法
- 找到所有的注入点,其实就是被@Autowired注解修饰的方法以及字段,同时静态的方法以及字段也会被排除
@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);metadata.checkConfigMembers(beanDefinition);
}
1、 findAutowiringMetadata
- 解析@Autowired @Value注解的信息,生成元数据
private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, PropertyValues pvs) {// 1.设置cacheKey的值(beanName 或者 className)String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());// 2.检查beanName对应的InjectionMetadata是否已经存在于缓存中InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);// 3.检查InjectionMetadata是否需要刷新(为空或者class变了)if (InjectionMetadata.needsRefresh(metadata, clazz)) {synchronized (this.injectionMetadataCache) {// 4.加锁后,再次从缓存中获取beanName对应的InjectionMetadatametadata = this.injectionMetadataCache.get(cacheKey);// 5.加锁后,再次检查InjectionMetadata是否需要刷新if (InjectionMetadata.needsRefresh(metadata, clazz)) {if (metadata != null) {// 6.如果需要刷新,并且metadata不为空,则先移除metadata.clear(pvs);}try {// 7.解析@Autowired注解的信息,生成元数据(包含clazz和clazz里解析到的注入的元素,// 这里的元素包括AutowiredFieldElement和AutowiredMethodElement)metadata = buildAutowiringMetadata(clazz);// 8.将解析的元数据放到injectionMetadataCache缓存,以备复用,每一个类只解析一次this.injectionMetadataCache.put(cacheKey, metadata);} catch (NoClassDefFoundError err) {throw new IllegalStateException("Failed to introspect bean class [" + clazz.getName() +"] for autowiring metadata: could not find class that it depends on", err);}}}}return metadata;
}
buildAutowiringMetadata
private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {// 1.用于存放所有解析到的注入的元素的变量LinkedList<InjectionMetadata.InjectedElement> elements = new LinkedList<InjectionMetadata.InjectedElement>();Class<?> targetClass = clazz;// 2.循环遍历do {// 2.1 定义存放当前循环的Class注入的元素(有序)final LinkedList<InjectionMetadata.InjectedElement> currElements =new LinkedList<InjectionMetadata.InjectedElement>();// 2.2 如果targetClass的属性上有@Autowired @Value注解,则用工具类获取注解信息ReflectionUtils.doWithLocalFields(targetClass, new ReflectionUtils.FieldCallback() {@Overridepublic void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {// 2.2.1 获取field上的@Autowired注解信息AnnotationAttributes ann = findAutowiredAnnotation(field);if (ann != null) {// 2.2.2 校验field是否被static修饰// 如果是则直接返回,因为@Autowired注解不支持static修饰的fieldif (Modifier.isStatic(field.getModifiers())) {if (logger.isWarnEnabled()) {logger.warn("Autowired annotation is not supported on static fields: " + field);}return;}// 2.2.3 获取@Autowired注解的required的属性值//(required:值为true时,如果没有找到bean时,自动装配应该失败;false则不会)boolean required = determineRequiredStatus(ann);// 2.2.4 将field、required封装成AutowiredFieldElement,添加到currElementscurrElements.add(new AutowiredFieldElement(field, required));}}});// 2.3 如果targetClass的方法上有@Autowired注解,则用工具类获取注解信息ReflectionUtils.doWithLocalMethods(targetClass, new ReflectionUtils.MethodCallback() {@Overridepublic void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {// 2.3.1 找到桥接方法Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);// 2.3.2 判断方法的可见性,如果不可见则直接返回if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {return;}// 2.3.3 获取method上的@Autowired注解信息AnnotationAttributes ann = findAutowiredAnnotation(bridgedMethod);if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {// 2.3.4 校验method是否被static修饰,如果是则直接返回// 因为@Autowired注解不支持static修饰的methodif (Modifier.isStatic(method.getModifiers())) {if (logger.isWarnEnabled()) {logger.warn("Autowired annotation is not supported on static methods: " + method);}return;}// 2.3.5 @Autowired注解标识在方法上的目的就是将容器内的Bean注入到方法的参数中,没有参数就违背了初衷if (method.getParameterTypes().length == 0) {if (logger.isWarnEnabled()) {logger.warn("Autowired annotation should only be used on methods with parameters: " +method);}}// 2.3.6 获取@Autowired注解的required的属性值boolean required = determineRequiredStatus(ann);// 2.3.7 获取method的属性描述器PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);// 2.3.8 将method、required、pd封装成AutowiredMethodElement,添加到currElementscurrElements.add(new AutowiredMethodElement(method, required, pd));}}});// 2.4 将本次循环获取到的注解信息添加到elementselements.addAll(0, currElements);// 2.5 在解析完targetClass之后,递归解析父类// 将所有的@Autowired的属性和方法收集起来,且类的层级越高其属性会被越优先注入targetClass = targetClass.getSuperclass();}// 2.6 递归解析targetClass父类(直至父类为Object结束)while (targetClass != null && targetClass != Object.class); // 2.7 将clazz和解析到的注入的元素封装成InjectionMetadatareturn new InjectionMetadata(clazz, elements);
}
总结
- 遍历当前bean中的所有
属性
和方法
,过滤静态
属性和方法 - 属性:将field、required封装成
AutowiredFieldElement
- 方法:将method、required、pd(获取method的属性描述器)封装成
AutowiredMethodElement
- 将解析的元数据放到
injectionMetadataCache
缓存,以后统一处理
2、checkConfigMembers
- 将所有需要注入的属性和方法添加到集合中,后面会使用
- Member是Field和method的父类
public void checkConfigMembers(RootBeanDefinition beanDefinition) {Set<InjectedElement> checkedElements = new LinkedHashSet<InjectedElement>(this.injectedElements.size());// 1.遍历检查所有要注入的元素for (InjectedElement element : this.injectedElements) {Member member = element.getMember();// 2.如果beanDefinition的externallyManagedConfigMembers属性不包含该memberif (!beanDefinition.isExternallyManagedConfigMember(member)) {// 3.将该member添加到beanDefinition的externallyManagedConfigMembers属性beanDefinition.registerExternallyManagedConfigMember(member);// 4.并将element添加到checkedElementscheckedElements.add(element);}}// 5.赋值给checkedElements(检查过的元素)this.checkedElements = checkedElements;
}
四、postProcessProperties(属性填充)
- 在postProcessProperties 方法中完成了Bean 中
@Autowired
、@Inject
、@Value
注解的属性填充 - 上一步postProcessMergedBeanDefinition已经筛选出需要注入的属性放入injectionMetadataCache中
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {// 筛选出需要注入的属性类型InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);try {// 进行属性注入// 存在两种InjectionMetadata// 1.AutowiredFieldElement// 2.AutowiredMethodElement// 分别对应字段的属性注入以及方法的属性注入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;
}
1、inject
- inject 的实现很简单,遍历所有元素,调用元素的 inject 方法
- 属性调用的是AutowiredFieldElement.inject
- 方法调用的是 AutowiredMethodElement.inject
public void inject(Object target, String beanName, PropertyValues pvs) throws Throwable {// 如果checkedElements存在,则使用checkedElements,否则使用injectedElementsCollection<InjectedElement> elementsToIterate =(this.checkedElements != null ? this.checkedElements : this.injectedElements);if (!elementsToIterate.isEmpty()) {boolean debug = logger.isDebugEnabled();for (InjectedElement element : elementsToIterate) {if (debug) {logger.debug("Processing injected element of bean '" + beanName + "': " + element);}// 解析@Autowired注解生成的元数据类:AutowiredFieldElement、AutowiredMethodElement,// 这两个类继承InjectionMetadata.InjectedElement,各自重写了inject方法。element.inject(target, beanName, pvs);}}
}
- 在
findAutowiringMetadata
中添加的注入元素的顺序先添加属性
元素,再添加方法
元素 - 那么在 InjectionMetadata#inject 的遍历中也是先遍历属性元素,再遍历方法元素
方法注入的优先级要高于属性注入
,因为方法注入在属性注入后,会将属性注入的结果覆盖掉
2、字段的属性注入
- 获取属性field
beanFactory.resolveDependency
找到当前字段所匹配的Bean对象- 将找的的Bean对象封装成
ShortcutDependencyDescriptor
对象作为缓存- 如果当前Bean是
原型
Bean,那么下次再来创建该Bean时 - 就可以直接拿缓存的结果对象,不需要再次进行查找
- 如果当前Bean是
- 利用
反射
将结果对象赋值给字段
@Override
protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {// 1.拿到该元数据的属性值Field field = (Field) this.member;Object value;// 2.如果缓存中已经存在,则直接从缓存中解析属性(原型Bean)if (this.cached) {// 对于原型Bean,第一次创建的时候,也找注入点,然后进行注入,此时cached为false,注入完了之后cached为true// 第二次创建的时候,先找注入点(此时会拿到缓存好的注入点),也就是AutowiredFieldElement对象,此时cache为true,也就进到此处了// 注入点内并没有缓存被注入的具体Bean对象,而是beanName,这样就能保证注入到不同的原型Bean对象value = resolvedCachedArgument(beanName, this.cachedFieldValue);} else {// 3.把field和required属性,包装成desc描述类DependencyDescriptor desc = new DependencyDescriptor(field, this.required);desc.setContainingClass(bean.getClass());Set<String> autowiredBeanNames = new LinkedHashSet<String>(1);TypeConverter typeConverter = beanFactory.getTypeConverter();try {// 4.核心逻辑: 进行依赖查找,找到当前字段所匹配的Bean对象value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);} catch (BeansException ex) {throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);}synchronized (this) {if (!this.cached) {// 5.value不为空或者required为trueif (value != null || this.required) {// 6.如果属性依赖注入的bean不止一个(Array,Collection,Map),缓存cachedFieldValue放的是DependencyDescriptorthis.cachedFieldValue = desc;// 7.注册依赖关系到缓存(beanName 依赖 autowiredBeanNames)registerDependentBeans(beanName, autowiredBeanNames);// 8.如果属性依赖注入的bean只有一个(正常都是一个)if (autowiredBeanNames.size() == 1) {String autowiredBeanName = autowiredBeanNames.iterator().next();if (beanFactory.containsBean(autowiredBeanName)) {// @Autowired标识属性类型和Bean的类型要匹配// 因此Array,Collection,Map类型的属性不支持缓存属性Bean名称// 9.检查autowiredBeanName对应的bean的类型是否为field的类型if (beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {// 10.将该属性解析到的bean的信息封装成ShortcutDependencyDescriptor,// 以便之后可以通过getBean方法来快速拿到bean实例this.cachedFieldValue = new ShortcutDependencyDescriptor(desc, autowiredBeanName, field.getType());}}}} else {this.cachedFieldValue = null;}// 11.缓存标识设为truethis.cached = true;}}}if (value != null) {// 12.设置字段访问性ReflectionUtils.makeAccessible(field);// 13.通过反射为属性赋值,将解析出来的bean实例赋值给fieldfield.set(bean, value);}
}
3、方法的属性注入
- 逻辑几乎与字段注入方式一样
// 代码看着很长,实际上逻辑跟字段注入基本一样
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {// 判断XML中是否配置了这个属性,如果配置了直接跳过// 换而言之,XML配置的属性优先级高于@Autowired注解if (checkPropertySkipping(pvs)) {return;}Method method = (Method) this.member;Object[] arguments;if (this.cached) {arguments = resolveCachedArguments(beanName);} else {// 通过方法参数类型构造依赖描述符// 逻辑基本一样的,最终也是调用beanFactory.resolveDependency方法Class<?>[] paramTypes = method.getParameterTypes();arguments = new Object[paramTypes.length];DependencyDescriptor[] descriptors = new DependencyDescriptor[paramTypes.length];Set<String> autowiredBeans = new LinkedHashSet<>(paramTypes.length);Assert.state(beanFactory != null, "No BeanFactory available");TypeConverter typeConverter = beanFactory.getTypeConverter();// 遍历方法的每个参数for (int i = 0; i < arguments.length; i++) {MethodParameter methodParam = new MethodParameter(method, i);DependencyDescriptor currDesc = new DependencyDescriptor(methodParam, this.required);currDesc.setContainingClass(bean.getClass());descriptors[i] = currDesc;try {// 还是要调用这个方法Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeans, typeConverter);if (arg == null && !this.required) {arguments = null;break;}arguments[i] = arg;} catch (BeansException ex) {throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(methodParam), ex);}}synchronized (this) {if (!this.cached) {if (arguments != null) {Object[] cachedMethodArguments = new Object[paramTypes.length];System.arraycopy(descriptors, 0, cachedMethodArguments, 0, arguments.length); // 注册bean之间的依赖关系registerDependentBeans(beanName, autowiredBeans);// 跟字段注入差不多,存在@Value注解,不进行缓存if (autowiredBeans.size() == paramTypes.length) {Iterator<String> it = autowiredBeans.iterator();for (int i = 0; i < paramTypes.length; i++) {String autowiredBeanName = it.next();if (beanFactory.containsBean(autowiredBeanName) &&beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) {cachedMethodArguments[i] = new ShortcutDependencyDescriptor(descriptors[i], autowiredBeanName, paramTypes[i]);}}}this.cachedMethodArguments = cachedMethodArguments;} else {this.cachedMethodArguments = null;}this.cached = true;}}}if (arguments != null) {try {// 反射调用方法// 像我们的setter方法就是在这里调用的ReflectionUtils.makeAccessible(method);method.invoke(bean, arguments);} catch (InvocationTargetException ex) {throw ex.getTargetException();}}
}
4、查询匹配的bean对象
@Override
@Nullable
public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {// descriptor代表当前需要注入的那个字段,或者方法的参数,也就是注入点// ParameterNameDiscovery用于解析方法参数名称descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());// 所需要的类型是Optionalif (Optional.class == descriptor.getDependencyType()) {return createOptionalDependency(descriptor, requestingBeanName);}// 所需要的的类型是ObjectFactory,或ObjectProviderelse if (ObjectFactory.class == descriptor.getDependencyType() ||ObjectProvider.class == descriptor.getDependencyType()) {return new DependencyObjectProvider(descriptor, requestingBeanName);} else if (javaxInjectProviderClass == descriptor.getDependencyType()) {return new Jsr330ProviderFactory().createDependencyProvider(descriptor, requestingBeanName);} else {// 在属性或set方法上使用了@Lazy注解,那么则构造一个代理对象并返回,真正使用该代理对象时才进行类型筛选BeanObject result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(descriptor, requestingBeanName);if (result == null) {// ★ 核心步骤// descriptor表示某个属性或某个set方法// requestingBeanName表示正在进行依赖注入的Bean名称result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);}return result;}
}
DefaultListableBeanFactory#doResolveDependency 处理属性依赖关系的核心方法
@Nullable
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {// 相当于打个点,记录下当前的步骤位置 返回值为当前的InjectionPoint InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);try {// 简单的说就是去Bean工厂的缓存里去看看,有没有名称为此的Bean,有就直接返回,没必要继续往下走了// 原型模式这里才有值Object shortcut = descriptor.resolveShortcut(this);if (shortcut != null) {return shortcut;}// 此处为:class com.fsx.bean.GenericBeanClass<?> type = descriptor.getDependencyType();//处理@Value注解 获取@Value中的value属性Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);// 若存在value值,那就去解析它// 也就是使用StringValueResolver处理器去处理一些表达式~~if (value != null) {if (value instanceof String) {String strVal = resolveEmbeddedValue((String) value);BeanDefinition bd = (beanName != null && containsBean(beanName) ? getMergedBeanDefinition(beanName) : null);value = evaluateBeanDefinitionString(strVal, bd);}//如果需要会进行类型转换后返回结果TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());return (descriptor.getField() != null ?converter.convertIfNecessary(value, type, descriptor.getField()) :converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));}// 如果descriptor所对应的类型是数组、Map这些// 就将descriptor对应的类型所匹配的所有bean方法,不用进一步做筛选了Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);if (multipleBeans != null) {return multipleBeans;}// 获取所有【类型】匹配的Beans,形成一个Map(此处用Map装,是因为可能不止一个符合条件)// 该方法就特别重要了,对泛型类型的匹配、对@Qualifierd的解析都在这里面,下面详情分解Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);// 若没有符合条件的Bean。。。if (matchingBeans.isEmpty()) {// 并且是必须的,那就抛出没有找到合适的Bean的异常吧// 我们非常熟悉的异常信息:expected at least 1 bean which qualifies as autowire candidate...if (isRequired(descriptor)) {raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);}return null;}String autowiredBeanName;Object instanceCandidate;//如果类型匹配的bean不止一个,Spring需要进行筛选,筛选失败的话继续抛出异常// 如果只找到一个该类型的,就不用进这里面来帮忙筛选了~~~~~~~~~if (matchingBeans.size() > 1) {// 该方法作用:从给定的beans里面筛选出一个符合条件的bean// Spring在查找依赖的时候遵循先类型再名称的原则(没有@Qualifier注解情况下)autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);// 无法推断出具体的名称if (autowiredBeanName == null) {// 如果依赖是必须的,直接抛出异常// 如果依赖不是必须的,但是这个依赖类型不是集合或者数组,那么也抛出异常if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {return descriptor.resolveNotUnique(type, matchingBeans);}// Spring4.3之后才有:表示如果是required=false,或者就是List Map类型之类的,即使没有找到Bean,也让它不抱错,因为最多注入的是空集合嘛// 依赖不是必须的,但是依赖类型是集合或者数组,那么返回一个nullelse {return null;}}instanceCandidate = matchingBeans.get(autowiredBeanName);}else {// 仅仅只匹配上一个,走这里 很简单 直接拿出来即可// 注意这里直接拿出来的技巧:不用遍历,直接用iterator.next()即可Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();autowiredBeanName = entry.getKey();instanceCandidate = entry.getValue();}// 把找到的autowiredBeanName 放进去if (autowiredBeanNames != null) {autowiredBeanNames.add(autowiredBeanName);}// 底层就是调用了beanFactory.getBean(beanName); // 确保该实例肯定已经被实例化了的if (instanceCandidate instanceof Class) {instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);}Object result = instanceCandidate;if (result instanceof NullBean) {if (isRequired(descriptor)) {raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);}result = null;}// 再一次校验,type和result的type类型是否吻合if (!ClassUtils.isAssignableValue(type, result)) {throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass());}return result;}// 最终把节点归还回来finally {ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);}
}
总结:
- 注入原型对象会从缓存中获取(其实是getBean创建新对象)
- 处理
@Value
注解,获取@Value中的value属性 - 对map、collection、数组类型的依赖进行处理
- 根据指定类型可能会找到多个bean
- 如果一个都没找到
required=true
(即依赖是必须的),抛出异常required=false
(即依赖不是必须的),返回null
- 如果通过类型找到多个
- 优先选择
@Primary
注解bean - 再选择
@Priority
注解优先级最高的(值最小
) - 最后根据
名称匹配
,还匹配不上则抛异常
- 优先选择
- 如果只找到一个
- 就直接使用该bean
- 如果一个都没找到
4.1、搜索类型匹配的bean的Map
- 将获取类型匹配的Bean工作交给BeanFactoryUtils.beanNamesForTypeIncludingAncestors
- 该方法除了当前beanFactory还会递归对父parentFactory进行查找
- 如果注入类型是特殊类型或其子类(ApplicationContext、BeanFactory等等),会将特殊类型的实例添加到结果
- 对结果进行筛选
- BeanDefinition的
autowireCandidate
属性,表示是否允许该bena注入到其他bean中,默认为true 泛型
类型的匹配,如果存在的话Qualifier注解
。如果存在Qualifier注解的话,会直接比对Qualifier注解中指定的beanName(Spring处理自己定义的Qualifier注解,还支持javax.inject.Qualifier注解)
- BeanDefinition的
- 如果筛选后,结果为空,Spring会放宽筛选条件,再筛选一次
protected Map<String, Object> findAutowireCandidates(@Nullable String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {// 1.从BeanFactory中找出和requiredType所匹配的beanName,仅仅是beanName// 这些bean不一定经过了实例化,只有到最终确定某个Bean了// 如果这个Bean还没有实例化才会真正进行实例化String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this, requiredType, true, descriptor.isEager());//记录所有匹配的bean, key-beanName, value-bean实例Map<String, Object> result = new LinkedHashMap<>(candidateNames.length);// 2.注入类型是特殊类型或其子类// 比如你要注入ApplicationContext、BeanFactory等等for (Class<?> autowiringType : this.resolvableDependencies.keySet()) {if (autowiringType.isAssignableFrom(requiredType)) {Object autowiringValue = this.resolvableDependencies.get(autowiringType);autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType);if (requiredType.isInstance(autowiringValue)) {result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue);break;}}}// 3.candidateNames可能会有多个,这里就要开始过滤了,比如@Qualifier、泛型等等for (String candidate : candidateNames) {// 不是自引用 && 符合注入条件// 不是自引用,什么是自引用?// 1.候选的Bean的名称跟需要进行注入的Bean名称相同,意味着,自己注入自己// 2.或者候选的Bean对应的factoryBean的名称跟需要注入的Bean名称相同,// 也就是说A依赖了B但是B的创建又需要依赖A// 符合注入条件// 检查泛型和@Qualifierif (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, descriptor)) {addCandidateEntry(result, candidate, descriptor, requiredType);}}// 4.结果集为空 && 注入属性是非数组、容器类型 那么Spring就会放宽注入条件,然后继续寻找// 什么叫放宽:比如泛型不要求精确匹配了、比如自引用的注入等等if (result.isEmpty() && !indicatesMultipleBeans(requiredType)) {// 是泛型,就需要获取真实的类型,然后进行匹配DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch();for (String candidate : candidateNames) {if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, fallbackDescriptor)) {addCandidateEntry(result, candidate, descriptor, requiredType);}}// 5. 如果result为空, 则表明依赖的就是自己,则将自己添加到result中if (result.isEmpty()) {for (String candidate : candidateNames) {if (isSelfReference(beanName, candidate) &&(!(descriptor instanceof MultiElementDescriptor) || !beanName.equals(candidate)) &&isAutowireCandidate(candidate, fallbackDescriptor)) {addCandidateEntry(result, candidate, descriptor, requiredType);}}}}return result;
}
4.2、出现多个bean,如何筛选最后的bean
- 从多个Bean中,筛选出一个符合条件的Bean
@Nullable
protected String determineAutowireCandidate(Map<String, Object> candidates, DependencyDescriptor descriptor) {Class<?> requiredType = descriptor.getDependencyType();// 看看传入的Bean中有没有标注了@Primary注解的String primaryCandidate = determinePrimaryCandidate(candidates, requiredType);// 如果找到了 就直接返回// 由此可见,@Primary的优先级还是非常的高的if (primaryCandidate != null) {return primaryCandidate;}//找到一个标注了javax.annotation.Priority注解的。(备注:优先级的值不能有相同的,否则报错)String priorityCandidate = determineHighestPriorityCandidate(candidates, requiredType);if (priorityCandidate != null) { return priorityCandidate;}// 这里是最终的处理(相信绝大部分情况下,都会走这里~~~~~~~~~~~~~~~~~~~~)// 此处就能看出resolvableDependencies它的效能了,他会把解析过的依赖们缓存起来,不用再重复解析了for (Map.Entry<String, Object> entry : candidates.entrySet()) {String candidateName = entry.getKey();Object beanInstance = entry.getValue();// 到这一步就比较简单了,matchesBeanName匹配上Map的key就行。// 需要注意的是,bean可能存在很多别名,所以只要有一个别名相同,就认为是能够匹配上的 具体参考AbstractBeanFactory#getAliases方法//descriptor.getDependencyName() 这个特别需要注意的是:如果是字段,这里调用的this.field.getName() 直接用的是字段的名称// 因此此处我们看到的情况是,我们采用@Autowired虽然匹配到两个类型的Bean了,即使我们没有使用@Qualifier注解,也会根据字段名找到一个合适的(若没找到,就抱错了)if ((beanInstance != null && this.resolvableDependencies.containsValue(beanInstance)) ||matchesBeanName(candidateName, descriptor.getDependencyName())) {return candidateName;}}return null;
}
筛选@Primary注解的Bean
@Primary
只能标注一个
在同类型的Bean上- 多个会抛出异常
@Nullable
protected String determinePrimaryCandidate(Map<String, Object> candidates, Class<?> requiredType) {String primaryBeanName = null;for (Map.Entry<String, Object> entry : candidates.entrySet()) {String candidateBeanName = entry.getKey();Object beanInstance = entry.getValue();// isPrimary就是去看看容器里(包含父容器)对应的Bean定义信息是否有@Primary标注if (isPrimary(candidateBeanName, beanInstance)) {if (primaryBeanName != null) {boolean candidateLocal = containsBeanDefinition(candidateBeanName);boolean primaryLocal = containsBeanDefinition(primaryBeanName);// 这个相当于如果已经找到了一个@Primary的,然后又找到了一个 那就抛出异常// @Primary只能标注到一个同类型的Bean上if (candidateLocal && primaryLocal) {throw new NoUniqueBeanDefinitionException(requiredType, candidates.size(),"more than one 'primary' bean found among candidates: " + candidates.keySet());}else if (candidateLocal) {primaryBeanName = candidateBeanName;}}// 把找出来的标注了@Primary的Bean的名称返回出去else {primaryBeanName = candidateBeanName;}}}return primaryBeanName;
}
筛选@Priority注解优先级最高的Bean
@Priority
虽然可以标注多个,但是里面的优先级值,不能出现相同的- @Priority是JSR 250标准,值越
小
优先级越高 - 如果优先级的值相等,是不允许的,会抛出异常
protected String determineHighestPriorityCandidate(Map<String, Object> candidates, Class<?> requiredType) {String highestPriorityBeanName = null;Integer highestPriority = null;for (Map.Entry<String, Object> entry : candida tes.entrySet()) {String candidateBeanName = entry.getKey();Object beanInstance = entry.getValue();if (beanInstance != null) {//AnnotationAwareOrderComparator#getPriority// 这里就是为了兼容JDK6提供的javax.annotation.Priority这个注解,然后做一个优先级排序// 注意注意注意:这里并不是@Order,和它木有任何关系~~~// 它有的作用像Spring提供的@Primary注解Integer candidatePriority = getPriority(beanInstance);// 大部分情况下,我们这里都是null,但是需要注意的是,@Primary只能标注一个,这个虽然可以标注多个,但是里面的优先级值,不能出现相同的(强烈建议不要使用~~~~而使用@Primary)if (candidatePriority != null) {if (highestPriorityBeanName != null) {// 如果优先级的值相等,是不允许的,这里需要引起注意,个人建议一般还是使用@Primary吧if (candidatePriority.equals(highestPriority)) {throw new NoUniqueBeanDefinitionException(requiredType, candidates.size(),"Multiple beans found with the same priority ('" + highestPriority +"') among candidates: " + candidates.keySet());}else if (candidatePriority < highestPriority) {highestPriorityBeanName = candidateBeanName;highestPriority = candidatePriority;}}else {highestPriorityBeanName = candidateBeanName;highestPriority = candidatePriority;}}}}return highestPriorityBeanName;
}
5、冷知识:使用@Value进行依赖注入
- AutowiredAnnotationBeanPostProcessor不仅处理@Autowired也处理@Value
@Configuration
public class Config {@Beanpublic Person person() {return new Person();}// 这样就能够实现依赖注入了@Value("#{person}")private Person person;
}
注意
- 只能是
#{person}
而不能是${person}
- person表示beanName,因此请保证此Bean
必须存在
- 比如若写成这样@Value(“#{person2}”)就报错:
Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1008E: Property or field 'person2' cannot be found on object of type 'org.springframework.beans.factory.config.BeanExpressionContext' - maybe not public or not valid?at org.springframework.expression.spel.ast.PropertyOrFieldReference.readProperty(PropertyOrFieldReference.java:217)at org.springframework.expression.spel.ast.PropertyOrFieldReference.getValueInternal(PropertyOrFieldReference.java:104)
@Value(#{})与@Value(${})的区别
@Value("#{}")
: 表示SpEl表达式通常用来获取bean的属性
,或者调用bean的某个方法
。当然还有可以表示常量
@Value(${})
:获取配置文件
中的属性值- 它俩可以结合使用:比如:
@Value("#{'${spring.redis.cluster.nodes}'.split(',')}")
是一个结合使用的案例~ 这样就可以把如下配置解析成List了
spring.redis.cluster.nodes=10.102.144.94:7535,10.102.144.94:7536,10.102.144.95:7535,10.102.144.95:7536,10.102.148.153:7535,10.102.148.153:7536
相关文章:

Spring源码解析(七):bean后置处理器AutowiredAnnotationBeanPostProcessor
Spring源码系列文章 Spring源码解析(一):环境搭建 Spring源码解析(二):bean容器的创建、默认后置处理器、扫描包路径bean Spring源码解析(三):bean容器的刷新 Spring源码解析(四):单例bean的创建流程 Spring源码解析(五)&…...

【C#学习笔记】引用类型(1)
文章目录 引用类型class匿名类 记录引用相等和值相等record声明 接口delegate 委托合并委托/多路广播委托 引用类型 引用类型的变量存储对其数据(对象)的引用,而值类型的变量直接包含其数据。 对于引用类型,两种变量可引用同一对…...

STM32CubeMX+VSCODE+EIDE+RT-THREAD 工程创建
Eide环境搭建暂且不表,后续补充。主要记录下Vscode环境下 创建Rt-thread工程的过程。分别介绍STM32CubeMX添加rtt支持包的方式和手动添加rtt kernel方式。STM32CubeMX生成工程的时候有"坑",防止下次忘记,方便渡一下有缘人ÿ…...

java中javamail发送带附件的邮件实现方法
java中javamail发送带附件的邮件实现方法 本文实例讲述了java中javamail发送带附件的邮件实现方法。分享给大家供大家参考。具体分析如下: JavaMail,顾名思义,提供给开发者处理电子邮件相关的编程接口。它是Sun发布的用来处理email的API。它…...

Stable Diffusion高阶技能(2)-稳定扩散百态:解密AI绘画工具「SD WebUI」的提示词高级使用策略
简介 在我们的生活中,艺术元素可谓无处不在,而处于中心地位的绘画,无疑是携带着强烈的艺术魅力。现如今随着AI技术的日新月异,AI绘画对我们的生活世界的改造影响越来越深远。那么,如何让我们在AI绘画工具中更好的指导AI完成我们心中的作品呢? 这需要我们玩转这个工具的…...

【果树农药喷洒机器人】Part2:机器人变量喷药系统硬件选型
本专栏介绍:付费专栏,持续更新机器人实战项目,欢迎各位订阅关注。 关注我,带你了解更多关于机器人、嵌入式、人工智能等方面的优质文章! 文章目录 一、引言二、变量喷药系统总体要求2.1系统功能要求2.2系统技术要求三、机器人关键硬件选型3.1深度相机概述与选型3.2单片机选…...

解决vite+vue3项目npm装包失败
报错如下: Failed to remove some directories [ npm WARN cleanup [ npm WARN cleanup D:\\V3Work\\v3project\\node_modules\\vue, npm WARN cleanup [Error: EPERM: operation not permitted, rmdir D:\V3Work\v3project\node_modules\vue\reactivity\…...

Rust之错误处理
在Rust中,将错误分为两种,可恢复错误和不可恢复错误。所谓可恢复错误就是指类似于文件未找到这类错误,一般需要将它们报告给用户并再次尝试进行操作,而不可恢复错误往往就是Bug,需要停止程序的运行。 1、不可恢复错误…...

docker compose快速编排
Docker-compose概述 Docker-Compose项目是Docker官方的开源项目,负责实现对Docker容集群的快速编排 Docker-Compose将所管理的容器分为三层,分别是工程(project),服务(service)以及容器&#x…...

java.io.File类的使用
文章目录 概述构造器常用方法1、获取文件和目录基本信息2、列出目录的下一级3.File类的重命名功能4、判断功能的方法5、创建、删除功能 练习 概述 File类及本章下的各种流,都定义在java.io包下。一个File对象代表硬盘或网络中可能存在的一个文件或者文件目录&#…...

TypeScript技能总结(三)
typescript是js的超集,目前很多前端框架都开始使用它来作为项目的维护管理的工具,还在不断地更新,添加新功能中,我们学习它,才能更好的在的项目中运用它,发挥它的最大功效 //泛型 > 参数和返回值类型相…...
python绿色版运行程序,python 绿色版免安装
大家好,小编来为大家解答以下问题,python绿色版运行程序,python 绿色版免安装,今天让我们一起来看看吧! 软件简介 Python3.7.0 是一种被广大从业者广泛使用的通用型设计语言。该软件提供了丰富全面的模块,并…...

Python 向Excel写数据
1.项目终端导入 xlwt 库 pip install xlwt2.导入依赖包 import xlwt3.创建Excel表格类型文件 调用xlwt模块中的Workbook方法来创建一个excel表格类型文件,其中的第一个参数是设置数据的编码格式,这里是’utf-8’的形式,style_compression设…...

MySQL(1)
MySQL创建数据库和创建数据表 创建数据库 1. 连接 MySQL mysql -u root -p 2. 查看当前的数据库 show databases; 3. 创建数据库 create database 数据库名; 创建数据库 4. 创建数据库时设置字符编码 create database 数据库名 character set utf8; 5. 查看和显示…...

Android10 Recovery系列(二)增加OTG升级功能
一 、背景 起因是遇到了客户有这个需求,本着了解的原则,去看了一下之前Android版本的代码,想看看之前有没有现成的实现,移植过来。结果很不幸,没有找到。于是自己开始了功能实现的过程。下面分享一下该功能的实现 二 、准备工作 首先简单了解一下Recovery 模块的系统升…...

el-popover使用自定义图标
使用el-popover实现鼠标点击或浮动到自定义图标上弹出表格弹窗,官方文档上使用的是按钮el-button,如果想换成图标或其他的组件的话直接把el-button替换掉即可。注意替换之后的组件一定要加slot“reference”,不然组件是显示不出来的。 代码如…...

KCOM4串口转键鼠控制线测试说明
1.KOCM4介绍 KCOM4是一款最新开发的串口转键盘鼠标控制线,采用32位内核,最大60Mhz的工作频率,完美适用于游戏挂机等应用场景(如果是用在工作电脑控制或展厅电脑控制推荐CH9329双头线)。KCOM4支持普通键盘、相对鼠标、…...

2023华数杯数学建模C题完整5问代码思路分析
目前已经写出2023华数杯C题母亲身心健康对婴儿成长的影响全部5问的完整代码和42页论文(正文30页,论文部分摘要如下: 本文共解决了五个问题,涉及婴儿行为特征、睡眠质量与母亲的身体指标和心理指标的关系,以及如何优化…...

02_kafka_基本概念_基础架构
文章目录 常见的消息队列工作模式基本概念kafka 特性Kafka 基本架构topic 分区的 目的/ 好处 日志存储形式消费者,消费方式 逻辑消费组 高性能写入: 顺序写 mmap读取:零拷贝DMA 使用场景 常见的消息队列工作模式 至多一次:消息被…...

HTTP 常用状态码 301 302 304 403
HTTP 常用状态码 301 302 304 403 301 永久重定向,浏览器会把重定向后的地址缓存起来,将来用户再次访问原始地址时,直接引导用户访问新地址 302 临时重定向,浏览器会引导用户进入新地址,但不会缓存原始地址,…...

分布式 - 服务器Nginx:一小时入门系列之静态网页配置
文章目录 1. 静态文件配置2. nginx listen 命令解析3. nginx server_name 命令解析4. nginx server 端口重复5. nginx location 命令 1. 静态文件配置 在 /home 文件下配置一个静态的AdminLTE后台管理系统: [rootnginx-dev conf.d]# cd /home [rootnginx-dev home…...

kubernetes网络之网络策略-----Network Policies - Example
创建一个Deployment并配置Service 创建一个 nginx Deployment 用于演示 Kubernetes 的 NetworkPolicy: kubectl create deployment nginx --imagenginx 输出结果 deployment.apps/nginx created通过Service暴露该Deployment kubectl expose deployment nginx --po…...

【GDI/GDI+】如何抓取屏幕保存到bitmap文件?
问题 如何抓取屏幕保存到bitmap文件? 方法 GDI 方法 1、抓取。 HBITMAP CRectChartUI::GetBitmap(HDC hDC) {HDC hMemDC;int x, y;int nWidth, nHeight;HBITMAP hBitmap, hOldBitmap;hMemDC CreateCompatibleDC(hDC);nWidth GetDeviceCaps(hDC, HORZRES);nHei…...

HDFS介绍
目录 编辑 一、HDFS基础 1.1 概述 1.2 HDFS的设计目标 1.2.1 硬件故障 1.2.2 流式数据访问 1.2.3 超大数据集 1.2.4 简单的一致性模型 1.2.5 移动计算而不是移动数据 1.2.6 跨异构硬件和软件平台的可移植性 1.3 基础概念 1.3.1 块(Block) 1.3.2 复制…...

每日一题——两数之和
题目 给出一个整型数组 numbers 和一个目标值 target,请在数组中找出两个加起来等于目标值的数的下标,返回的下标按升序排列。 (注:返回的数组下标从1开始算起,保证target一定可以由数组里面2个数字相加得到࿰…...

Maven: ‘mvn‘ is not recognized as an internal or external command
下载并配置好Maven之后,CMD测试安装是否成功:mvn -v 提示: mvn is not recognized as an internal or external command, operable program or batch file. 检查环境变量: MAVEN_HOME: %MAVEN_HOME%\bin: 看上去没问题&#x…...

CubeSLAM: Monocular 3D Object SLAM——论文简述
一、简介 提出一种在动态和静态环境中同时进行3D目标检测和定位建图的方法,并且能够互相提升准确度。具体地,对于3D目标,其位置、方向和尺寸通过slam进行了优化;而3D目标作为slam中的路标,可以提供额外的语义和几何约…...

【雕爷学编程】MicroPython动手做(30)——物联网之Blynk 2
知识点:什么是掌控板? 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片,支持WiFi和蓝牙双模通信,可作为物联网节点,实现物联网应用。同时掌控板上集成了OLED…...

linux scp 拷贝文件到目标linux系统
scp -P 8866 -r jsonrpc/ root192.168.6.66:/folder_path...

Oracle-expdp报错ORA-39077、06502(Bug-16928674)
问题: 用户在使用expdp进程导出时,出现队列报错ORA-39077、ORA-06502 ORA-31626: job does not exist ORA-31638: cannot attach to job SYS_EXPORT_SCHEMA_01 for user SYS ORA-06512: at "SYS.DBMS_SYS_ERROR", line 95 ORA-06512: at "SYS.KUPV$…...