1.前言 ​

Spring注解开发中,我们只需求要类上加上@Configuration注解,然后在类中的方法上面加上@Bean注解即可完成Spring Bean组件的注册。相较于之前的xml配置文件定义注册组件简化了非常多。那么Spring底层是如何处理@Configuration注解来完成Spring组件的注册,下面通过源码一步一步进行分析。

2.准备工作

Spring版本:2.2.13.RELEASE

源码中涉及的类:

  • ConfigurationClassPostProcessor
  • ConfigurationClassParser
  • ConfigurationClass
  • BeanMethod
  • ConfigurationClassBeanDefinitionReader
  • ConfigurationClassEnhancer
  • ConfigurationClassUtils

说明:文中统一将被@Configuration注解标注的类称为配置类

3.涉及相关类说明

ConfigurationClassPostProcessor

  配置类的后知处理器,其中实现了BeanDefinitionRegistryPostProcessor接口,可以拿到BeanDefinitionRegistry对象手动注册组件,也是整个解析流程的入口

ConfigurationClassParser

  配置类解析器,解析配置类,封装成一个个的ConfigurationClass对象

ConfigurationClass

  配置类经解析后的实体对象

BeanMethod

  被@Bean注解标注的方法解析后的实体对象

ConfigurationClassBeanDefinitionReader

  用于读取注册配置类中定义的组件

ConfigurationClassEnhancer

  对配置类进行代理增强的角色类

ConfigurationClassUtils

  配置类解析相关的工具类

4.源码流程分析

ConfigurationClassPostProcessor作为入口类,类的层次结构入上图,我们只需要关心BeanDefinitionRegistryPostProcessor和BeanFactoryPostProcessor两个接口的回调方法BeanFactoryPostProcessor和postProcessBeanFactory。

两个方法的具体实现如下,可以看到两个方法的内部都调用了processConfigBeanDefinitions方法,下面针对此方法进行分析

 1 public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
2 int registryId = System.identityHashCode(registry);
3 if (this.registriesPostProcessed.contains(registryId)) {
4 throw new IllegalStateException(
5 "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
6 }
7 if (this.factoriesPostProcessed.contains(registryId)) {
8 throw new IllegalStateException(
9 "postProcessBeanFactory already called on this post-processor against " + registry);
10 }
11 this.registriesPostProcessed.add(registryId);
12
13 processConfigBeanDefinitions(registry);
14 }
15
16 public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
17 int factoryId = System.identityHashCode(beanFactory);
18 if (this.factoriesPostProcessed.contains(factoryId)) {
19 throw new IllegalStateException(
20 "postProcessBeanFactory already called on this post-processor against " + beanFactory);
21 }
22 this.factoriesPostProcessed.add(factoryId);
23 if (!this.registriesPostProcessed.contains(factoryId)) {
24 // BeanDefinitionRegistryPostProcessor hook apparently not supported...
25 // Simply call processConfigurationClasses lazily at this point then.
26 processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
27 }
28
29 enhanceConfigurationClasses(beanFactory);
30 beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
31 }
1.processConfigBeanDefinitions方法分析如下(删除了部分不相关代码)
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
// 待处理配置类集合
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
// 获取所有组件名称
String[] candidateNames = registry.getBeanDefinitionNames(); for (String beanName : candidateNames) {
// 获取组件定义信息
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
// 判断组件是否已经被作为一个配置类处理过
if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
if (logger.isDebugEnabled()) {
logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
}
}
// 判断组件是否为候选的配置类,如果是则加入待处理集合。checkConfigurationClassCandidate下面具体分析
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
} // 如果待处理配置类为空,直接返回
if (configCandidates.isEmpty()) {
return;
} // 对所有配置类进行排序
configCandidates.sort((bd1, bd2) -> {
int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
return Integer.compare(i1, i2);
});
// 初始化配置类解析器
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry); Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);do {
// 解析每一个配置类
parser.parse(candidates);
// 验证每一个配置类
parser.validate();
// 获取解析后的所有配置类实体ConfigurationClass
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
// 初始化配置类中定义的组件读取者
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}

// 读取配置类中所有被@Bean注解标注的方法,并将每个方法封装成BeanDefinition注册到容器中
this.reader.loadBeanDefinitions(configClasses);
}
while (!candidates.isEmpty());
}
2.ConfigurationClassUtils.checkConfigurationClassCandidate方法分析
public static boolean checkConfigurationClassCandidate(
BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) { // 获取组件的全限定类名
String className = beanDef.getBeanClassName();
if (className == null || beanDef.getFactoryMethodName() != null) {
return false;
} // 下面这一大段代码都是在获取组件类的元数据信息AnnotationMetadata,这里不详细介绍
AnnotationMetadata metadata;
if (beanDef instanceof AnnotatedBeanDefinition &&
className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) {
// Can reuse the pre-parsed metadata from the given BeanDefinition...
metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata();
}
else if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {
// Check already loaded Class if present...
// since we possibly can't even load the class file for this Class.
Class<?> beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();
if (BeanFactoryPostProcessor.class.isAssignableFrom(beanClass) ||
BeanPostProcessor.class.isAssignableFrom(beanClass) ||
AopInfrastructureBean.class.isAssignableFrom(beanClass) ||
EventListenerFactory.class.isAssignableFrom(beanClass)) {
return false;
}
metadata = AnnotationMetadata.introspect(beanClass);
}
else {
try {
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className);
metadata = metadataReader.getAnnotationMetadata();
}
catch (IOException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Could not find class file for introspecting configuration annotations: " +
className, ex);
}
return false;
}
} // 获取组件类的@Configuration注解属性,如果组件类上不存在@Configuration注解,则返回空
Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
// 如果存在,并且proxyBeanMethods属性的值为true,则标注当前配置类为full,即需要代理增强,为一个代理类
if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
}
// 如果存在,并且isConfigurationCandidate返回true,则标注当前配置类为lite,即不需要代理增强,为一个普通类
else if (config != null || isConfigurationCandidate(metadata)) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
}
// 组件类不存在@Configuration注解,直接返回false
else {
return false;
} // 获取配置类的排序顺序,设置到组件定义属性中
Integer order = getOrder(metadata);
if (order != null) {
beanDef.setAttribute(ORDER_ATTRIBUTE, order);
} return true;
}

public static boolean isConfigurationCandidate(AnnotationMetadata metadata) {
// 如果配置类为一个接口,则不处理
if (metadata.isInterface()) {
return false;
} /*
* 判断配置类是否存在(@Component,ComponentScan,@Import,ImportResource)注解
* 如果存在上述任意一个注解,则返回true
*/
for (String indicator : candidateIndicators) {
if (metadata.isAnnotated(indicator)) {
return true;
}
} // 如果上述四个注解都不存在,则判断配置类中是否存在被@Bean标注的方法
return hasBeanMethods(metadata);
}
3.ConfigurationClassParser.parse方法分析
public void parse(Set<BeanDefinitionHolder> configCandidates) {
for (BeanDefinitionHolder holder : configCandidates) {
// 循环每一个配置类的定义信息,然后调用parse方法,下面三个parse重载方法逻辑一样
BeanDefinition bd = holder.getBeanDefinition();
try {
if (bd instanceof AnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
}
else {
parse(bd.getBeanClassName(), holder.getBeanName());
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
}
} this.deferredImportSelectorHandler.process();
} protected final void parse(@Nullable String className, String beanName) throws IOException {
Assert.notNull(className, "No bean class name for configuration class bean definition");
MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className);
// 将配置类封装成ConfigurationClass后调用processConfigurationClass
processConfigurationClass(new ConfigurationClass(reader, beanName), DEFAULT_EXCLUSION_FILTER);
} protected final void parse(Class<?> clazz, String beanName) throws IOException {
// 将配置类封装成ConfigurationClass后调用processConfigurationClass
processConfigurationClass(new ConfigurationClass(clazz, beanName), DEFAULT_EXCLUSION_FILTER);
} protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
// 将配置类封装成ConfigurationClass后调用processConfigurationClass
processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
}
4.processConfigurationClass方法分析
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
// 判断配置类是否有@Condition注解,如果有的话根据条件判断是否成立
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
// 递归处理配置类和其所有父类
SourceClass sourceClass = asSourceClass(configClass, filter);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
}
while (sourceClass != null); }
5.doProcessConfigurationClass方法分析
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
throws IOException { // 判断配置类上的注解层次中是否包含@Component注解,如果包含则递归获取内部类进行处理
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
// 递归处理所有的内部类
processMemberClasses(configClass, sourceClass, filter);
} // 处理配置类上注解层次中的所有@PropertySource注解
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
if (this.environment instanceof ConfigurableEnvironment) {
processPropertySource(propertySource);
}
else {
logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
"]. Reason: Environment must implement ConfigurableEnvironment");
}
} // 处理配置类上注解层次中的所有@ComponentScan注解
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// Check the set of scanned definitions for any further config classes and parse recursively if needed
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (bdCand == null) {
bdCand = holder.getBeanDefinition();
}
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}
} // 处理配置类上注解层次中的所有@Import注解
processImports(configClass, sourceClass, getImports(sourceClass), filter, true); // 处理配置类上注解层次中的所有@ImportResource注解
AnnotationAttributes importResource =
AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
if (importResource != null) {
String[] resources = importResource.getStringArray("locations");
Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
for (String resource : resources) {
String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
configClass.addImportedResource(resolvedResource, readerClass);
}
} // 处理配置类中的所有标注了@Bean注解的方法,并封装成BeanMethod添加到配置类的实体ConfigurationClass中
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
} // Process default methods on interfaces
processInterfaces(configClass, sourceClass); // 如果有父类,则返回父类
if (sourceClass.getMetadata().hasSuperClass()) {
String superclass = sourceClass.getMetadata().getSuperClassName();
if (superclass != null && !superclass.startsWith("java") &&
!this.knownSuperclasses.containsKey(superclass)) {
this.knownSuperclasses.put(superclass, configClass);
// Superclass found, return its annotation metadata and recurse
return sourceClass.getSuperClass();
}
} // 没有父类,返回null
return null;
}
6.ConfigurationClassBeanDefinitionReader.loadBeanDefinitions方法分析
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
// 可跟踪记录的条件评估者,也就是解析@Conditon注解的作用,并且可以记录满足@Condition和不满足@Condition的原因
TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
for (ConfigurationClass configClass : configurationModel) {
// 从配置类中加载组件定义信息
loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
}
} private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) { // 处理配置类上的@Condition注解
if (trackedConditionEvaluator.shouldSkip(configClass)) {
String beanName = configClass.getBeanName();
if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
this.registry.removeBeanDefinition(beanName);
}
this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
return;
} // 判断当前配置类是否是通过@Import注解导入的
if (configClass.isImported()) {
registerBeanDefinitionForImportedConfigurationClass(configClass);
}
// 获取配置类中的所有@Bean注解标注的方法
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
// 从@Bean标注的方法加载组件定义信息
loadBeanDefinitionsForBeanMethod(beanMethod);
} loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
7.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForBeanMethod方法分析
private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
// 获取配置类信息
ConfigurationClass configClass = beanMethod.getConfigurationClass();
MethodMetadata metadata = beanMethod.getMetadata();
// 获取方法名
String methodName = metadata.getMethodName(); // 处理方法上的@Conditional注解
if (this.conditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) {
configClass.skippedBeanMethods.add(methodName);
return;
}
if (configClass.skippedBeanMethods.contains(methodName)) {
return;
} // 获取方法上@Bean注解的属性信息
AnnotationAttributes bean = AnnotationConfigUtils.attributesFor(metadata, Bean.class);
Assert.state(bean != null, "No @Bean annotation attributes"); // 处理@Bean注解的name属性,如果此属性为空,则组件的名称为方法名
List<String> names = new ArrayList<>(Arrays.asList(bean.getStringArray("name")));
String beanName = (!names.isEmpty() ? names.remove(0) : methodName); // 将name属性的值注册为此组件的别名
for (String alias : names) {
this.registry.registerAlias(beanName, alias);
}
     // 将@Bean方法封装成BeanDefinition
ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata, beanName);
beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource())); // 如果@Bean方法为静态方法
if (metadata.isStatic()) {
// static @Bean method
if (configClass.getMetadata() instanceof StandardAnnotationMetadata) {
beanDef.setBeanClass(((StandardAnnotationMetadata) configClass.getMetadata()).getIntrospectedClass());
}
else {
beanDef.setBeanClassName(configClass.getMetadata().getClassName());
}
beanDef.setUniqueFactoryMethodName(methodName);
}
// 如果@Bean方法为实例方法
else {
beanDef.setFactoryBeanName(configClass.getBeanName());
beanDef.setUniqueFactoryMethodName(methodName);
} beanDef.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
beanDef.setAttribute(org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor.
SKIP_REQUIRED_CHECK_ATTRIBUTE, Boolean.TRUE); /*
处理@Bean方法上的公共注解(@Lazy,@Primary,@DependsOn,@Role,@Description)
这里稍微解释一下这几个注解
@Lazy:组件是否为懒加载,组件的创建可以在项目启动Spring容器刷新的时候创建,也可以在第一次获取组件的时候(BeanFactory.getBean)创建。后者则为懒加载
@Primary:如果一个接口在容器中有多个实现类的时候,在使用@Autowired依赖注入的时候优先使用被@Primary注解标注的实现类
@DependsOn:创建此组件时依赖的其他组件,在创建此组件时,先创建完成依赖的其他组件
@Role:组件的角色,Spring框架底层使用。开发者一般不用
@Description:可以为组件定义添加描述,相当于一个描述信息,仅此而已
*/
AnnotationConfigUtils.processCommonDefinitionAnnotations(beanDef, metadata); // 下面一段都是处理@Bean注解的各个属性
Autowire autowire = bean.getEnum("autowire");
if (autowire.isAutowire()) {
beanDef.setAutowireMode(autowire.value());
} boolean autowireCandidate = bean.getBoolean("autowireCandidate");
if (!autowireCandidate) {
beanDef.setAutowireCandidate(false);
} String initMethodName = bean.getString("initMethod");
if (StringUtils.hasText(initMethodName)) {
beanDef.setInitMethodName(initMethodName);
} String destroyMethodName = bean.getString("destroyMethod");
beanDef.setDestroyMethodName(destroyMethodName);
     // 将组件定义注册到容器中
this.registry.registerBeanDefinition(beanName, beanDefToRegister);
}

Spring源码之@Configuration注解解析的更多相关文章

  1. Spring源码之@Configuration原理

    总结 @Configuration注解的Bean,在BeanDefinition加载注册到IOC容器之后,进行postProcessBeanFactory处理时会进行CGLIB动态代理 将@Prope ...

  2. Spring源码 05 IOC 注解方式

    参考源 https://www.bilibili.com/video/BV1tR4y1F75R?spm_id_from=333.337.search-card.all.click https://ww ...

  3. Spring源码:IOC原理解析(一)

    版权声明:本文为博主原创文章,转载请注明出处,欢迎交流学习! IOC(Inversion of Control),即控制反转,意思是将对象的创建和依赖关系交给第三方容器处理,我们要用的时候告诉容器我们 ...

  4. Spring源码:IOC原理解析(二)

    版权声明:本文为博主原创文章,转载请注明出处,欢迎交流学习! 接着上一章节的内容,我们来分析当new一个FileSystemXmlApplicationContext对象的时候,spring到底做了那 ...

  5. Spring如何解析XML文件——Spring源码之XML初解析

    首先,在我的这篇博客中已经说到容器是怎么初步实现的,并且要使用XmlBeanDefinitionReader对象对Xml文件进行解析,那么Xml文件是如何进行解析的,将在这片博客中进行一些陈述. 数据 ...

  6. spring源码-增强容器xml解析-3.1

    一.ApplicationContext的xml解析工作是通过ClassPathXmlApplicationContext来实现的,其实看过ClassPathXmlApplicationContext ...

  7. 【spring源码分析】BeanDefinitionRegistryPostProcessor解析

    一.自定义BeanDefinitionRegistryPostProcessor BeanDefinitionRegistryPostProcessor继承自BeanFactoryPostProces ...

  8. Spring源码阅读-IoC容器解析

    目录 Spring IoC容器 ApplicationContext设计解析 BeanFactory ListableBeanFactory HierarchicalBeanFactory Messa ...

  9. Spring源码追踪2——xml解析入口

    解析xml节点入口 org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.doRegisterBeanDe ...

随机推荐

  1. Apache Shiro反序列化远程代码执行复现

    最近也是看shiro漏洞比较多,所以自己也在本地复现了一下,拿出来与大家一起分享 0x00 关于Apache Shiro Apache shiro是一个Java安全框架,提供了认证.授权.加密和会话管 ...

  2. 一文看懂B端产品和C端产品

    大纲 什么是B端产品 什么是C端产品 为什么会产生B端产品和C端产品 怎么判断一个产品是B端还是C端 B端产品和C端产品存在哪些差异 C端产品经理如何向B端产品经理转型 写在最后   什么是B, Bu ...

  3. ArcGIS把导入的shp按渔网区块分割成更小的文件

    前言 前端地图的开发需要导入城市的3D建筑白模,如果直接导入整个城市的json,文件大小高达76M,浏览器会直接崩溃,所以需要用ArcGIS分割成更小的文件后再给前端导入展示. ArcGIS版本:10 ...

  4. 学习javaScript必知必会(3)~数组(数组创建,for...in遍历,辅助函数,高级函数filter、map、reduce)

    一.数组: 1.js是弱语言,js中的数组定义时:不用指定数据类型.不用功指定数组长度:数组可以存储任何数据类型的数据 2.数组定义的[ ] 的实质: [] = new Array(); {} = n ...

  5. Linux命令(2)--cp拷贝、mv剪切、head、tail追踪、tar归档

    文章目录 一.知识回顾 ls cd 二.Linux基本操作(二) 1.cp 拷贝 2.mv 移动(剪切) 3.head 头部 4.tail 追踪(尾部) 5.tar 归档 查看 压缩 解压 总结 一. ...

  6. java单例模式(饿汉式和懒汉式)

    1 /* 2 * 设计模式:对问题行之有效的解决方式.其实它是一种思想. 3 * 4 * 1,单例设计模式 5 * 解决的问题:就是可以保证一个类在内容中的对象唯一性. 6 * 7 * 必须对于多个程 ...

  7. 集合框架-ArrayList练习(去除ArrayList集合中的重复元素)

    1 package cn.itcast.p3.arraylist.test; 2 3 import java.util.ArrayList; 4 import java.util.Iterator; ...

  8. 微信小程序入门教程之三:脚本编程

    这个系列教程的前两篇,介绍了小程序的项目结构和页面样式. 今天,接着往下讲,教大家为小程序加入 JavaScript 脚本,做出动态效果,以及如何跟用户互动.学会了脚本,就能做出复杂的页面了. 本篇的 ...

  9. Ali云组件概念

  10. 如何加载本地下载下来的BERT模型,pytorch踩坑!!

    近期做实验频繁用到BERT,所以想着下载下来使用,结果各种问题,网上一搜也是简单一句:xxx.from_pretrained("改为自己的路径") 我只想说,大坑!!! 废话不多说 ...