Spring源码解析——IOC属性填充

正文

doCreateBean() 主要用于完成 bean 的创建和初始化工作,我们可以将其分为四个过程:

最全面的Java面试网站

  • createBeanInstance() 实例化 bean
  • populateBean() 属性填充
  • 循环依赖的处理
  • initializeBean() 初始化 bean

第一个过程实例化 bean在前面一篇博客中已经分析完了,这篇博客开始分析 属性填充,也就是 populateBean()

protected void populateBean(String beanName, RootBeanDefinition mbd, BeanWrapper bw) {  PropertyValues pvs = mbd.getPropertyValues();  if (bw == null) {  if (!pvs.isEmpty()) {  throw new BeanCreationException(  mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance");  }  else {  // Skip property population phase for null instance.  return;  }  }  // Give any InstantiationAwareBeanPostProcessors the opportunity to modify the  // state of the bean before properties are set. This can be used, for example,  // to support styles of field injection.  boolean continueWithPropertyPopulation = true;  if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {  for (BeanPostProcessor bp : getBeanPostProcessors()) {  if (bp instanceof InstantiationAwareBeanPostProcessor) {  InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;  //返回值为是否继续填充bean  if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {  continueWithPropertyPopulation = false;  break;  }  }  }  }  //如果后处理器发出停止填充命令则终止后续的执行  if (!continueWithPropertyPopulation) {  return;  }  if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME ||  mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {  MutablePropertyValues newPvs = new MutablePropertyValues(pvs);  // Add property values based on autowire by name if applicable.  //根据名称自动注入  if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME) {  autowireByName(beanName, mbd, bw, newPvs);  }  // Add property values based on autowire by type if applicable.  //根据类型自动注入  if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {  autowireByType(beanName, mbd, bw, newPvs);  }  pvs = newPvs;  }  //后处理器已经初始化  boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();  //需要依赖检查  boolean needsDepCheck = (mbd.getDependencyCheck() != RootBeanDefinition.DEPENDENCY_CHECK_NONE);  if (hasInstAwareBpps || needsDepCheck) {  PropertyDescriptor[] filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);  if (hasInstAwareBpps) {  for (BeanPostProcessor bp : getBeanPostProcessors()) {  if (bp instanceof InstantiationAwareBeanPostProcessor) {  InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;  //对所有需要依赖检查的属性进行后处理  pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);  if (pvs == null) {  return;  }  }  }  }  if (needsDepCheck) {  //依赖检查,对应depends-on属性,3.0已经弃用此属性  checkDependencies(beanName, mbd, filteredPds, pvs);  }  }  //将属性应用到bean中  //将所有ProtertyValues中的属性填充至BeanWrapper中。  applyPropertyValues(beanName, mbd, bw, pvs);  
} 

我们来分析下populateBean的流程:

(1)首先进行属性是否为空的判断

(2)通过调用InstantiationAwareBeanPostProcessor的postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)方法来控制程序是否继续进行属性填充

(3)根据注入类型(byName/byType)提取依赖的bean,并统一存入PropertyValues中

(4)应用InstantiationAwareBeanPostProcessor的postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName)方法,对属性获取完毕填充前的再次处理,典型的应用是RequiredAnnotationBeanPostProcesser类中对属性的验证

(5)将所有的PropertyValues中的属性填充至BeanWrapper中

上面步骤中有几个地方是我们比较感兴趣的,它们分别是依赖注入(autowireByName/autowireByType)以及属性填充,接下来进一步分析这几个功能的实现细节

分享一份大彬精心整理的大厂面试手册,包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等高频面试题,非常实用,有小伙伴靠着这份手册拿过字节offer~

需要的小伙伴可以自行下载

http://mp.weixin.qq.com/s?__biz=Mzg2OTY1NzY0MQ==&mid=2247485445&idx=1&sn=1c6e224b9bb3da457f5ee03894493dbc&chksm=ce98f543f9ef7c55325e3bf336607a370935a6c78dbb68cf86e59f5d68f4c51d175365a189f8#rd

自动注入

Spring 会根据注入类型( byName / byType )的不同,调用不同的方法(autowireByName() / autowireByType())来注入属性值。

autowireByName()

protected void autowireByName(String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) {// 获取 Bean 对象中非简单属性String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);for (String propertyName : propertyNames) {// 如果容器中包含指定名称的 bean,则将该 bean 注入到 bean中if (containsBean(propertyName)) {// 递归初始化相关 beanObject bean = getBean(propertyName);// 为指定名称的属性赋予属性值  pvs.add(propertyName, bean);// 属性依赖注入registerDependentBean(propertyName, beanName);if (logger.isDebugEnabled()) {logger.debug("Added autowiring by name from bean name '" + beanName +"' via property '" + propertyName + "' to bean named '" + propertyName + "'");}}else {if (logger.isTraceEnabled()) {logger.trace("Not autowiring property '" + propertyName + "' of bean '" + beanName +"' by name: no matching bean found");}}}
}

该方法逻辑很简单,获取该 bean 的非简单属性,什么叫做非简单属性呢?就是类型为对象类型的属性,但是这里并不是将所有的对象类型都都会找到,比如 8 个原始类型,String 类型 ,Number类型、Date类型、URL类型、URI类型等都会被忽略,如下:

protected String[] unsatisfiedNonSimpleProperties(AbstractBeanDefinition mbd, BeanWrapper bw) {Set<String> result = new TreeSet<>();PropertyValues pvs = mbd.getPropertyValues();PropertyDescriptor[] pds = bw.getPropertyDescriptors();for (PropertyDescriptor pd : pds) {if (pd.getWriteMethod() != null && !isExcludedFromDependencyCheck(pd) && !pvs.contains(pd.getName()) &&!BeanUtils.isSimpleProperty(pd.getPropertyType())) {result.add(pd.getName());}}return StringUtils.toStringArray(result);
}

这里获取的就是需要依赖注入的属性。

autowireByName()函数的功能就是根据传入的参数中的pvs中找出已经加载的bean,并递归实例化,然后加入到pvs中

autowireByType

autowireByType与autowireByName对于我们理解与使用来说复杂程度相似,但是实现功能的复杂度却不一样,我们看下方法代码:

protected void autowireByType(  String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) {  TypeConverter converter = getCustomTypeConverter();  if (converter == null) {  converter = bw;  }  Set<String> autowiredBeanNames = new LinkedHashSet<String>(4);  //寻找bw中需要依赖注入的属性  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.equals(pd.getPropertyType())) {  //探测指定属性的set方法  MethodParameter methodParam = BeanUtils.getWriteMethodParameter(pd);  // Do not allow eager init for type matching in case of a prioritized post-processor.  boolean eager = !PriorityOrdered.class.isAssignableFrom(bw.getWrappedClass());  DependencyDescriptor desc = new AutowireByTypeDependencyDescriptor(methodParam, eager);  //解析指定beanName的属性所匹配的值,并把解析到的属性名称存储在autowiredBeanNames中,  Object autowiredArgument = resolveDependency(desc, beanName, autowiredBeanNames, converter);  if (autowiredArgument != null) {  pvs.add(propertyName, autowiredArgument);  }  for (String autowiredBeanName : autowiredBeanNames) {  //注册依赖  registerDependentBean(autowiredBeanName, beanName);  if (logger.isDebugEnabled()) {  logger.debug("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);  }  }  
}

根据名称第一步与根据属性第一步都是寻找bw中需要依赖注入的属性,然后遍历这些属性并寻找类型匹配的bean,其中最复杂就是寻找类型匹配的bean。spring中提供了对集合的类型注入支持,如使用如下注解方式:

@Autowired
private List<Test> tests;

这种方式spring会把所有与Test匹配的类型找出来并注入到tests属性中,正是由于这一因素,所以在autowireByType函数,新建了局部遍历autowireBeanNames,用于存储所有依赖的bean,如果只是对非集合类的属性注入来说,此属性并无用处。

对于寻找类型匹配的逻辑实现是封装在了resolveDependency函数中,其实现如下:

public Object resolveDependency(DependencyDescriptor descriptor, String beanName, Set<String> autowiredBeanNames, TypeConverter typeConverter) throws BeansException {descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());  if (descriptor.getDependencyType().equals(ObjectFactory.class)) {  //ObjectFactory类注入的特殊处理  return new DependencyObjectFactory(descriptor, beanName);  }  else if (descriptor.getDependencyType().equals(javaxInjectProviderClass)) {  //javaxInjectProviderClass类注入的特殊处理  return new DependencyProviderFactory().createDependencyProvider(descriptor, beanName);  }  else {  //通用处理逻辑  return doResolveDependency(descriptor, descriptor.getDependencyType(), beanName, autowiredBeanNames, typeConverter);  }  
}  protected Object doResolveDependency(DependencyDescriptor descriptor, Class<?> type, String beanName,  Set<String> autowiredBeanNames, TypeConverter typeConverter) throws BeansException {  /* * 用于支持Spring中新增的注解@Value */  Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);  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()));  }  //如果解析器没有成功解析,则需要考虑各种情况  //属性是数组类型  if (type.isArray()) {  Class<?> componentType = type.getComponentType();  //根据属性类型找到beanFactory中所有类型的匹配bean,  //返回值的构成为:key=匹配的beanName,value=beanName对应的实例化后的bean(通过getBean(beanName)返回)  Map<String, Object> matchingBeans = findAutowireCandidates(beanName, componentType, descriptor);  if (matchingBeans.isEmpty()) {  //如果autowire的require属性为true而找到的匹配项却为空则只能抛出异常  if (descriptor.isRequired()) {  raiseNoSuchBeanDefinitionException(componentType, "array of " + componentType.getName(), descriptor);  }  return null;  }  if (autowiredBeanNames != null) {  autowiredBeanNames.addAll(matchingBeans.keySet());  }  TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());  //通过转换器将bean的值转换为对应的type类型  return converter.convertIfNecessary(matchingBeans.values(), type);  }  //属性是Collection类型  else if (Collection.class.isAssignableFrom(type) && type.isInterface()) {  Class<?> elementType = descriptor.getCollectionType();  if (elementType == null) {  if (descriptor.isRequired()) {  throw new FatalBeanException("No element type declared for collection [" + type.getName() + "]");  }  return null;  }  Map<String, Object> matchingBeans = findAutowireCandidates(beanName, elementType, descriptor);  if (matchingBeans.isEmpty()) {  if (descriptor.isRequired()) {  raiseNoSuchBeanDefinitionException(elementType, "collection of " + elementType.getName(), descriptor);  }  return null;  }  if (autowiredBeanNames != null) {  autowiredBeanNames.addAll(matchingBeans.keySet());  }  TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());  return converter.convertIfNecessary(matchingBeans.values(), type);  }  //属性是Map类型  else if (Map.class.isAssignableFrom(type) && type.isInterface()) {  Class<?> keyType = descriptor.getMapKeyType();  if (keyType == null || !String.class.isAssignableFrom(keyType)) {  if (descriptor.isRequired()) {  throw new FatalBeanException("Key type [" + keyType + "] of map [" + type.getName() +  "] must be assignable to [java.lang.String]");  }  return null;  }  Class<?> valueType = descriptor.getMapValueType();  if (valueType == null) {  if (descriptor.isRequired()) {  throw new FatalBeanException("No value type declared for map [" + type.getName() + "]");  }  return null;  }  Map<String, Object> matchingBeans = findAutowireCandidates(beanName, valueType, descriptor);  if (matchingBeans.isEmpty()) {  if (descriptor.isRequired()) {  raiseNoSuchBeanDefinitionException(valueType, "map with value type " + valueType.getName(), descriptor);  }  return null;  }  if (autowiredBeanNames != null) {  autowiredBeanNames.addAll(matchingBeans.keySet());  }  return matchingBeans;  }  else {  Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);  if (matchingBeans.isEmpty()) {  if (descriptor.isRequired()) {  raiseNoSuchBeanDefinitionException(type, "", descriptor);  }  return null;  }  if (matchingBeans.size() > 1) {  String primaryBeanName = determinePrimaryCandidate(matchingBeans, descriptor);  if (primaryBeanName == null) {  throw new NoUniqueBeanDefinitionException(type, matchingBeans.keySet());  }  if (autowiredBeanNames != null) {  autowiredBeanNames.add(primaryBeanName);  }  return matchingBeans.get(primaryBeanName);  }  // We have exactly one match.  Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();  if (autowiredBeanNames != null) {  autowiredBeanNames.add(entry.getKey());  }  //已经确定只有一个匹配项  return entry.getValue();  }  
}

主要就是通过Type从BeanFactory中找到对应的benaName,然后通过getBean获取实例

protected Map<String, Object> findAutowireCandidates(@Nullable String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {//在BeanFactory找到所有Type类型的beanNameString[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this, requiredType, true, descriptor.isEager());Map<String, Object> result = new LinkedHashMap<>(candidateNames.length);//遍历所有的beanName,通过getBean获取for (String candidate : candidateNames) {if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, descriptor)) {//addCandidateEntry(result, candidate, descriptor, requiredType);}}return result;
}private void addCandidateEntry(Map<String, Object> candidates, String candidateName,DependencyDescriptor descriptor, Class<?> requiredType) {Object beanInstance = descriptor.resolveCandidate(candidateName, requiredType, this);if (!(beanInstance instanceof NullBean)) {candidates.put(candidateName, beanInstance);}
}public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory)throws BeansException {//通过类型找到beanName,然后再找到其实例return beanFactory.getBean(beanName);
}

applyPropertyValues

程序运行到这里,已经完成了对所有注入属性的获取,但是获取的属性是以PropertyValues形式存在的,还并没有应用到已经实例化的bean中,这一工作是在applyPropertyValues中。继续跟踪到方法体中:

protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) {  if (pvs == null || pvs.isEmpty()) {  return;  }  MutablePropertyValues mpvs = null;  List<PropertyValue> original;  if (System.getSecurityManager() != null) {  if (bw instanceof BeanWrapperImpl) {  ((BeanWrapperImpl) bw).setSecurityContext(getAccessControlContext());  }  }  if (pvs instanceof MutablePropertyValues) {  mpvs = (MutablePropertyValues) pvs;  //如果mpvs中的值已经被转换为对应的类型那么可以直接设置到beanwapper中  if (mpvs.isConverted()) {  // Shortcut: use the pre-converted values as-is.  try {  bw.setPropertyValues(mpvs);  return;  }  catch (BeansException ex) {  throw new BeanCreationException(  mbd.getResourceDescription(), beanName, "Error setting property values", ex);  }  }  original = mpvs.getPropertyValueList();  }  else {  //如果pvs并不是使用MutablePropertyValues封装的类型,那么直接使用原始的属性获取方法  original = Arrays.asList(pvs.getPropertyValues());  }  TypeConverter converter = getCustomTypeConverter();  if (converter == null) {  converter = bw;  }  //获取对应的解析器  BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this, beanName, mbd, converter);  // Create a deep copy, resolving any references for values.  List<PropertyValue> deepCopy = new ArrayList<PropertyValue>(original.size());  boolean resolveNecessary = false;  //遍历属性,将属性转换为对应类的对应属性的类型  for (PropertyValue pv : original) {  if (pv.isConverted()) {  deepCopy.add(pv);  }  else {  String propertyName = pv.getName();  Object originalValue = pv.getValue();  Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);  Object convertedValue = resolvedValue;  boolean convertible = bw.isWritableProperty(propertyName) &&  !PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName);  if (convertible) {  convertedValue = convertForProperty(resolvedValue, propertyName, bw, converter);  }  // Possibly store converted value in merged bean definition,  // in order to avoid re-conversion for every created bean instance.  if (resolvedValue == originalValue) {  if (convertible) {  pv.setConvertedValue(convertedValue);  }  deepCopy.add(pv);  }  else if (convertible && originalValue instanceof TypedStringValue &&  !((TypedStringValue) originalValue).isDynamic() &&  !(convertedValue instanceof Collection || ObjectUtils.isArray(convertedValue))) {  pv.setConvertedValue(convertedValue);  deepCopy.add(pv);  }  else {  resolveNecessary = true;  deepCopy.add(new PropertyValue(pv, convertedValue));  }  }  }  if (mpvs != null && !resolveNecessary) {  mpvs.setConverted();  }  // Set our (possibly massaged) deep copy.  try {  bw.setPropertyValues(new MutablePropertyValues(deepCopy));  }  catch (BeansException ex) {  throw new BeanCreationException(  mbd.getResourceDescription(), beanName, "Error setting property values", ex);  }  
}

我们来看看具体的属性赋值过程

public class MyTestBean {private String name ;public MyTestBean(String name) {this.name = name;}public MyTestBean() {}public String getName() {return name;}public void setName(String name) {this.name = name;}
}<bean id="myTestBean"  class="dabin.spring01.MyTestBean"><property name="name" value="dabin"></property>
</bean>

如上 bw.setPropertyValues 最终都会走到如下方法

@Override
public void setValue(final @Nullable Object value) throws Exception {//获取writeMethod,也就是我们MyTestBean的setName方法final Method writeMethod = (this.pd instanceof GenericTypeAwarePropertyDescriptor ?((GenericTypeAwarePropertyDescriptor) this.pd).getWriteMethodForActualAccess() :this.pd.getWriteMethod());if (System.getSecurityManager() != null) {AccessController.doPrivileged((PrivilegedAction<Object>) () -> {ReflectionUtils.makeAccessible(writeMethod);return null;});try {AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () ->writeMethod.invoke(getWrappedInstance(), value), acc);}catch (PrivilegedActionException ex) {throw ex.getException();}}else {ReflectionUtils.makeAccessible(writeMethod);//通过反射调用方法进行赋值writeMethod.invoke(getWrappedInstance(), value);}
}

就是利用反射进行调用对象的set方法赋值

至此,doCreateBean() 第二个过程:属性填充 已经分析完成了,下篇分析第三个过程:循环依赖的处理,其实循环依赖并不仅仅只是在 doCreateBean() 中处理,其实在整个加载 bean 的过程中都有涉及,所以下篇内容并不仅仅只局限于 doCreateBean()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/151505.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

复旦大学EMBA:揭秘科创企业,领略未来战略!

智能制造&#xff0c;国之重器。作为制造强国建设的主攻方向&#xff0c;智能制造的发展水平关系到我国未来制造业在全球的地位与影响力。发展智能制造&#xff0c;是加快建设现代化产业体系的重要手段&#xff0c;提升供给体系适配性的有力抓手&#xff0c;也是建设数字中国的…

【C++设计模式之状态模式:行为型】分析及示例

简介 状态模式&#xff08;State Pattern&#xff09;是一种行为型设计模式&#xff0c;它允许对象在内部状态改变时改变其行为&#xff0c;看起来就像是改变了其类。状态模式将对象的状态封装成不同的类&#xff0c;并使得对象在不同状态下有不同的行为。 描述 状态模式通过…

Android用户登录与数据存储:从权限请求到内外部存储的完整实践【完整实践步骤、外部存储、内部存储】

步骤 1: 登录页面布局 在 MainActivity 中实现用户登录功能&#xff0c;首先创建一个布局文件 activity_main.xml 包含用户名和密码的输入字段以及登录按钮。 <!-- activity_main.xml --> <LinearLayoutxmlns:android"http://schemas.android.com/apk/res/andr…

Qt之实现圆形进度条

在Qt自带的控件中&#xff0c;只有垂直进度条、水平进度条两种。 在平时做页面开发时&#xff0c;有些时候会用到圆形进度条&#xff0c;比如说&#xff1a;下载某个文件的下载进度。 展示效果&#xff0c;如下图所示&#xff1a; 实现这个功能主要由以下几个重点&#xff1a…

记录vue开发实例

封装的表格组件 <template><div><div style"width: 100%" v-if"showList"><el-table v-loading.lock"loading" :data"dataList":header-cell-style"{background: #F2FCFE,fontSize: 14px,color: #50606D}&…

因为在此系统上禁止运行脚本

问题&#xff1a; 解决办法&#xff1a; vue项目搭建中"因为在此系统上禁止运行脚本"报错&#xff0c;解决方法 - 你的剧本 - 博客园 (cnblogs.com)

详解链表oJ<反转链表,链表的中间节点及链表的回文>

hello&#xff0c;大家好&#xff0c;这里是Dark FlameMaster,今天和大家分享的是有关数据结构链表的几道题目&#xff0c;链表的中间节点&#xff0c;反转链表及判断链表是否为回文结构&#xff0c;放在一起讲解会印象更加深刻。 文章目录 一&#xff0c;链表的中间节点二&…

从0到1基于ChatGLM-6B使用LoRA进行参数高效微调

从0到1基于ChatGLM-6B使用LoRA进行参数高效微调 吃果冻不吐果冻皮 ​ 关注他 cliniNLPer 等 189 人赞同了该文章 ​ 目录 收起 ChatGLM-6B简介 具备的一些能力 局限性 LoRA 技术原理 环境搭建 数据集准备 数据预处理 参数高效微调 单卡模式模型训练 数据并行模式模型训练 模型推…

自动驾驶学习笔记(二)——Apollo入门

#Apollo开发者# 学习课程的传送门如下&#xff0c;当您也准备学习自动驾驶时&#xff0c;可以和我一同前往&#xff1a; 《自动驾驶新人之旅》免费课程—> 传送门 《2023星火培训【感知专项营】》免费课程—>传送门 文章目录 前言 Ubuntu Linux文件系统 Linux指令…

水波纹文字效果动画

效果展示 CSS 知识点 text-shadow 属性绘制立体文字clip-path 属性来绘制水波纹 工具网站 CSS clip-path maker 效果编辑器 页面整体结构实现 使用多个 H2 标签来实现水波纹的效果实现&#xff0c;然后使用clip-path结合动画属性一起来进行波浪的起伏动画实现。 <div …

5分钟理解什么是卷积的特征提取

大家好啊&#xff0c;我是董董灿。 卷积算法之所以重要&#xff0c;关键在于其提取特征的能力。 5分钟入门卷积算法中提到&#xff0c;卷积模仿的就是人眼识图的过程&#xff0c;以“感受野”的视角去扫描图片&#xff0c;从而获取不同区域的图片信息。 在这一过程中&#x…

新风机注意事项有哪些?

选择和使用新风机时&#xff0c;有几个关键注意事项需要牢记&#xff1a; 安装位置&#xff1a;新风机的安装位置很重要。通常情况下&#xff0c;应将其安装在室外以避免室内产生噪音和减少室内的体积占据。确保选择合适的安装位置&#xff0c;以便新风机能够顺利引入新鲜空气。…

《从零开始学ARM》勘误

1. 50页 2 51页 3 236页 14.2.3 mkU-Boot 修改为&#xff1a; mkuboot 4 56页 修改为&#xff1a; 位[31&#xff1a;24]为条件标志位域&#xff0c;用f表示&#xff1b; 位[23&#xff1a;16]为状态位域&#xff0c;用s表示&#xff1b; 位[15&#xff1a;8]为扩展位域&…

【Unity C#_菜单Window开发系列_Inspector Component UnityEditor开发】

GUI系列操作 1.枚举菜单实现文件1&#xff1a;Assets/MyScript/Test1.cs代码如下&#xff1a; 文件2&#xff1a;Assets/MyScript/Editor/Test1Editor.cs代码如下&#xff1a; 测试一下新建一个场景&#xff0c;新建一个Empty 节点&#xff0c;用来测试枚举组件将文件1&#xf…

Nginx详细学习记录

1. Nginx概述 Nginx是一个轻量级的高性能HTTP反向代理服务器&#xff0c;同时它也是一个通用类型的代理服务器&#xff0c;支持绝大部分协议&#xff0c;如TCP、UDP、SMTP、HTTPS等。 1.1 Nginx基础架构 Nginx默认采用多进程工作方式&#xff0c;Nginx启动后&#xff0c;会运行…

ArcMap:第二届全国大学生GIS技能大赛(广西师范学院)详解-上午题

目录 01 题目 1.1 第一小题 1.2 第二小题 1.3 第三小题 1.4 数据展示 02 思路和实操 2.1 第一问思路 2.2 第一问操作过程 2.2.1 地理配准 2.2.2 镶嵌 2.2.2.1 第一种镶嵌方法 2.2.2.2 第二种镶嵌方法 2.2.3 裁剪 2.2.4 DEM信息提取 2.2.5 分类 2.3 第二问思路 …

网络安全:个人信息保护,企业信息安全,国家网络安全的重要性

在当前的数字化时代&#xff0c;无论是个人&#xff0c;企业&#xff0c;还是国家&#xff0c;都会面临严重的网络安全威胁。网络安全不仅涉及我们的日常生活&#xff0c;也涉及到社会的稳定和国家的安全。这就需要我们高度重视网络安全&#xff0c;强化个人信息保护&#xff0…

数据库配置mysql5.7

1 创建数据库 """ 1.管理员连接数据库 mysql -uroot -proot2.创建数据库 create database hello default charsetutf8;3.查看用户 select user,host,password from mysql.user;# 5.7往后的版本 select user,host,authentication_string from mysql.user; "…

机器学习:决策树

决策树 决策树是一种基于树形结构的模型&#xff0c;决策树从根节点开始&#xff0c;一步步走到叶子节点&#xff08;决策&#xff09;&#xff0c;所有的数据最终都会落到叶子节点&#xff0c;既可以做分类也可以做回归。 特征选择 根节点的选择该用哪一个特征呢&#xff…