Sping源码(七)—context: component-scan标签如何扫描、加载Bean

2024-04-26 02:12

本文主要是介绍Sping源码(七)—context: component-scan标签如何扫描、加载Bean,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

序言

简单回顾一下。上一篇文章介绍了从xml文件context component-scan标签的加载流程到ConfigurationClassPostProcessor的创建流程。
本篇会深入了解context component-scan标签底层做了些什么。

component-scan

早期使用Spring进行开发时,很多时候都是注解 + 标签的形式来进行类的配置。而在之前文章中有介绍过xml在加载解析时,会对 各种标签进行解析。那注解修饰的类是什么时候被Spring识别的呢?

就是在component-scan标签解析时,获取对应base-package所对应的包下所有符合条件的类,从而进行处理

流程图

在这里插入图片描述

parse - 主流程

接下来从parse() 主流程开始,看看每一步都有哪些细节。

@Override@Nullablepublic BeanDefinition parse(Element element, ParserContext parserContext) {//获取base-package属性值String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);//解析占位符basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);//解析base-package属性值进行拆分,返回数组String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);// Actually scan for bean definitions and register them.// 创建和配置ClassPathBeanDefinitionScanner对象ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);//执行扫描,返回bean定义集合并注册到BeanFactorySet<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);//注册组件(包括注册一些内部的注解后置处理器(ConfigurationClassPostProcessor.class)等 触发注册事件registerComponents(parserContext.getReaderContext(), beanDefinitions, element);return null;}

configureScanner解析标签属性

方法主要是看context:component-scan标签中是否包含scope-resolver、resource-pattern、use-default-filters等属性值,并创建scanner对象进行封装。
在这里插入图片描述

protected ClassPathBeanDefinitionScanner configureScanner(ParserContext parserContext, Element element) {//去除部分无用代码....boolean useDefaultFilters = true;//解析use-default-filters属性值,默认为trueif (element.hasAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE)) {useDefaultFilters = Boolean.parseBoolean(element.getAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE));}// Delegate bean definition registration to scanner class.//创建ClassPathBeanDefinitionScanner对象,将bean定义注册委托给scannerClassPathBeanDefinitionScanner scanner = createScanner(parserContext.getReaderContext(), useDefaultFilters);scanner.setBeanDefinitionDefaults(parserContext.getDelegate().getBeanDefinitionDefaults());scanner.setAutowireCandidatePatterns(parserContext.getDelegate().getAutowireCandidatePatterns());//解析name-generator属性值if (element.hasAttribute(RESOURCE_PATTERN_ATTRIBUTE)) {scanner.setResourcePattern(element.getAttribute(RESOURCE_PATTERN_ATTRIBUTE));}//如果包含name-generator属性值,则按照Spring规则生成beanName.parseBeanNameGenerator(element, scanner);//解析scope-resolver属性值parseScope(element, scanner);}//解析include-filter和exclude-filter子标签属性。parseTypeFilters(element, scanner, parserContext);return scanner;}

parseTypeFilters解析子标签

值得注意的和扩展的是parseTypeFilters方法。方法中会对 context:exclude-filter 子标签和 context:exclude-filter 子标签进行处理。

type类型:
assignable-指定扫描某个接口派生出来的类annotation-指定扫描使用某个注解的类aspectj-指定扫描AspectJ表达式相匹配的类custom-指定扫描自定义的实现了org.springframework.core.type.filter.TypeFilter接口的类 regex-指定扫描符合正则表达式的类

context:exclude-filter
标签的作用是:在base-package扫描时,让指定的类不被Spring管理。
例子中代表标记了Controller注解的类不被Spring识别管理

<!--举个栗子 -->
<context:component-scan base-package="com.example"><context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

context:include-filter
标签的作用是:在base-package扫描时,额外扫描指定的类被Spring管理
例子中User类虽然没有注解修饰,但也会被加载。

<!--举个栗子 -->
<context:component-scan base-package="com.example"><context:include-filter type="assignable" expression="org.springframework.User"/>
</context:component-scan>

额外加载User类。

package org.springframework;public class User {}

方法会遍历context:componet-scan标签下的include和exclude子标签,并加到scanner的属性中。

protected void parseTypeFilters(Element element, ClassPathBeanDefinitionScanner scanner, ParserContext parserContext) {// Parse exclude and include filter elements.ClassLoader classLoader = scanner.getResourceLoader().getClassLoader();NodeList nodeList = element.getChildNodes();for (int i = 0; i < nodeList.getLength(); i++) {Node node = nodeList.item(i);if (node.getNodeType() == Node.ELEMENT_NODE) {String localName = parserContext.getDelegate().getLocalName(node);if (INCLUDE_FILTER_ELEMENT.equals(localName)) {TypeFilter typeFilter = createTypeFilter((Element) node, classLoader, parserContext);scanner.addIncludeFilter(typeFilter);}else if (EXCLUDE_FILTER_ELEMENT.equals(localName)) {TypeFilter typeFilter = createTypeFilter((Element) node, classLoader, parserContext);scanner.addExcludeFilter(typeFilter);}		}}}

其中,Scanner对象在创建时,构造方法中会对IncludeFilter进行初始化赋值操作。
划重点!!!!!!!!!!!!后面includeFilters有用到。在这也可以看出,为什么只会识别@Component注解和@Configuration

	protected ClassPathBeanDefinitionScanner createScanner(XmlReaderContext readerContext, boolean useDefaultFilters) {return new ClassPathBeanDefinitionScanner(readerContext.getRegistry(), useDefaultFilters,readerContext.getEnvironment(), readerContext.getResourceLoader());}public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,Environment environment, @Nullable ResourceLoader resourceLoader) {//删除无用代码if (useDefaultFilters) {registerDefaultFilters();}}protected void registerDefaultFilters() {//删掉无用代码this.includeFilters.add(new AnnotationTypeFilter(Component.class));}

@Configuration
Configaration注解上也被@Component修饰,所以也可以被识别到。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
}

doScan-扫描package下所有class文件

方法主要是获取到base-package包下所有类,并遍历看是否符合条件让Spring进行管理。其中findCandidateComponents会对类进行筛选。

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();//遍历basePackagefor (String basePackage : basePackages) {//获取basePackage下所有符合要求的BeanSet<BeanDefinition> candidates = findCandidateComponents(basePackage);for (BeanDefinition candidate : candidates) {//解析@Scope注解ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);candidate.setScope(scopeMetadata.getScopeName());//用生成器生成beanNameString beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);if (candidate instanceof AbstractBeanDefinition) {postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);}if (candidate instanceof AnnotatedBeanDefinition) {// 处理定义在目标类上的通用注解,包括@Lazy,@Primary,@DependsOn,@Role,@DescriptionAnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);}//再次检查beanName是否注册过,如果注册过,检查是否兼容if (checkCandidate(beanName, candidate)) {//将beanName和beanDefinition封装到BeanDefinitionHolderBeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);definitionHolder =AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);beanDefinitions.add(definitionHolder);//注册BeanDefinitionregisterBeanDefinition(definitionHolder, this.registry);}}}return beanDefinitions;}

findCandidateComponents - 筛选符合条件的类

代码会走scanCandidateComponents方法逻辑。

public Set<BeanDefinition> findCandidateComponents(String basePackage) {if (this.componentsIndex != null && indexSupportsIncludeFilters()) {return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);}else {return scanCandidateComponents(basePackage);}}

scanCandidateComponents
获取package下所有class文件,并转换成Resource -> MetadataReader读取数据进行判断。
如果满足isCandidateComponent方法的逻辑,则创建ScannedGenericBeanDefinition对象封装Bean信息。

private Set<BeanDefinition> scanCandidateComponents(String basePackage) {//删除无用代码。。。。Set<BeanDefinition> candidates = new LinkedHashSet<>();try {//packageSearchPath: "classpath*:com/example/*/**.class"String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +resolveBasePackage(basePackage) + '/' + this.resourcePattern;Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);for (Resource resource : resources) {if (resource.isReadable()) {try {MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);//判断该类是否允许被Spring识别if (isCandidateComponent(metadataReader)) {//创建BeanDefinition封装Bean信息ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);sbd.setSource(resource);if (isCandidateComponent(sbd)) {candidates.add(sbd);}}}}}}return candidates;}

isCandidateComponent
根据配置 context:include-filter 和 context:exclude-filter 规则进行过滤,如果都没有配置,则此时 includeFilters 属性中有默认值 @Component ,所以此处只会保留包含 @Component注解的类

protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {for (TypeFilter tf : this.excludeFilters) {if (tf.match(metadataReader, getMetadataReaderFactory())) {return false;}}for (TypeFilter tf : this.includeFilters) {if (tf.match(metadataReader, getMetadataReaderFactory())) {return isConditionMatch(metadataReader);}}return false;}

isConditionMatch
创建ConditionEvaluator对象,并在shouldSkip()方法中判断类是否含有 @Conditional注解,是否符合@Conditional中的类加载条件

private boolean isConditionMatch(MetadataReader metadataReader) {if (this.conditionEvaluator == null) {this.conditionEvaluator =new ConditionEvaluator(getRegistry(), this.environment, this.resourcePatternResolver);}return !this.conditionEvaluator.shouldSkip(metadataReader.getAnnotationMetadata());}

如果类中没有@Conditional注解,则直接返回,否则获取Conditional注解中value属性值并进行加载。递归调用,看是否符合家在条件。

public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {//如果metadata为空或者没有@Conditional注解,直接返回falseif (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {return false;}//第一次进来时phase为null,所以一定会走下面方法//判断是否是@Configuration注解,如果是,则进入shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION)if (phase == null) {if (metadata instanceof AnnotationMetadata &&//判断是否是抽象类 return false//判断是否被Component、ComponentScan、Import、ImportResource注解修饰 return true//判断是否被Bean修饰 return trueConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {//递归调用shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION)return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);}return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);}List<Condition> conditions = new ArrayList<>();//获取@Conditional注解的value属性for (String[] conditionClasses : getConditionClasses(metadata)) {for (String conditionClass : conditionClasses) {//创建value属性所对应的Condition类Condition condition = getCondition(conditionClass, this.context.getClassLoader());conditions.add(condition);}}//对conditions进行排序AnnotationAwareOrderComparator.sort(conditions);for (Condition condition : conditions) {ConfigurationPhase requiredPhase = null;if (condition instanceof ConfigurationCondition) {requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();}//此逻辑为:1.requiredPhase不是ConfigurationCondition的实例//2.phase==requiredPhase,从上述的递归可知:phase可为ConfigurationPhase.PARSE_CONFIGURATION或者ConfigurationPhase.REGISTER_BEAN//3.condition.matches(this.context, metadata)返回false//如果1、2或者1、3成立,则在此函数的上层将阻断bean注入Spring容器if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {return true;}}return false;}

registerComponents 注册组件 (ConfigurationClassPostProcessor.class等)

将doScan过滤出来的beanDefinition添加到compositeDef的nestedComponents属性中。
获取annotation-config属性值(默认为true),并调用registerAnnotationConfigProcessors方法进行注册。

protected void registerComponents(XmlReaderContext readerContext, Set<BeanDefinitionHolder> beanDefinitions, Element element) {Object source = readerContext.extractSource(element);//根据tagName(此处为context:component-scan)和source创建CompositeComponentDefinition对象CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), source);// 将扫描到的所有beanDefinition添加到compositeDef的nestedComponents属性中for (BeanDefinitionHolder beanDefHolder : beanDefinitions) {compositeDef.addNestedComponent(new BeanComponentDefinition(beanDefHolder));}// Register annotation config processors, if necessary.boolean annotationConfig = true;if (element.hasAttribute(ANNOTATION_CONFIG_ATTRIBUTE)) {//获取component-scan标签的annotation-config属性值annotationConfig = Boolean.parseBoolean(element.getAttribute(ANNOTATION_CONFIG_ATTRIBUTE));}//annotationConfig默认为true.if (annotationConfig) {//注册注解配置处理器Set<BeanDefinitionHolder> processorDefinitions =AnnotationConfigUtils.registerAnnotationConfigProcessors(readerContext.getRegistry(), source);for (BeanDefinitionHolder processorDefinition : processorDefinitions) {// 将注册的注解后置处理器的BeanDefinition添加到compositeDef的nestedComponents属性中compositeDef.addNestedComponent(new BeanComponentDefinition(processorDefinition));}}readerContext.fireComponentRegistered(compositeDef);}

registerAnnotationConfigProcessors

此时,又回到了我们上一篇文章所讲的 ConfigurationClassPostProcessor类的由来。就是在此处进行的加载。

public static final String CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME ="org.springframework.context.annotation.internalConfigurationAnnotationProcessor";

判断当前BeanFacroty中是否包含internalConfigurationAnnotationProcessor,如果不包含,则创建ConfigurationClassPostProcessor.class

public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(BeanDefinitionRegistry registry, @Nullable Object source) {//获取beanFactoryDefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);if (beanFactory != null) {if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) {//设置依赖比较器beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);}if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) {//设置自动装配解析器beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());}}//创建BeanDefinitionHolder集合Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);// 注册内部管理的用于处理@configuration注解的后置处理器的beanif (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);def.setSource(source);beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));}// 注册内部管理的用于处理@Autowired,@Value,@Inject以及@Lookup注解的后置处理器beanif (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);def.setSource(source);beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));}// Check for JSR-250 support, and if present add the CommonAnnotationBeanPostProcessor.// 注册内部管理的用于处理JSR-250注解,例如@Resource,@PostConstruct,@PreDestroy的后置处理器beanif (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);def.setSource(source);beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));}// Check for JPA support, and if present add the PersistenceAnnotationBeanPostProcessor.if (jpaPresent && !registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) {RootBeanDefinition def = new RootBeanDefinition();try {def.setBeanClass(ClassUtils.forName(PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME,AnnotationConfigUtils.class.getClassLoader()));}catch (ClassNotFoundException ex) {throw new IllegalStateException("Cannot load optional framework class: " + PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, ex);}def.setSource(source);beanDefs.add(registerPostProcessor(registry, def, PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME));}// 注册内部管理的用于处理@EventListener注解的后置处理器的beanif (!registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) {RootBeanDefinition def = new RootBeanDefinition(EventListenerMethodProcessor.class);def.setSource(source);beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME));}// 注册内部管理用于生产ApplicationListener对象的EventListenerFactory对象if (!registry.containsBeanDefinition(EVENT_LISTENER_FACTORY_BEAN_NAME)) {RootBeanDefinition def = new RootBeanDefinition(DefaultEventListenerFactory.class);def.setSource(source);beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_FACTORY_BEAN_NAME));}return beanDefs;}

下一篇文章详细看看 ConfigurationClassPostProcessor 类的 postProcessBeanDefinitionRegistry()方法做了什么。

这篇关于Sping源码(七)—context: component-scan标签如何扫描、加载Bean的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/936438

相关文章

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

Flutter 进阶:绘制加载动画

绘制加载动画:由小圆组成的大圆 1. 定义 LoadingScreen 类2. 实现 _LoadingScreenState 类3. 定义 LoadingPainter 类4. 总结 实现加载动画 我们需要定义两个类:LoadingScreen 和 LoadingPainter。LoadingScreen 负责控制动画的状态,而 LoadingPainter 则负责绘制动画。

EMLOG程序单页友链和标签增加美化

单页友联效果图: 标签页面效果图: 源码介绍 EMLOG单页友情链接和TAG标签,友链单页文件代码main{width: 58%;是设置宽度 自己把设置成与您的网站宽度一样,如果自适应就填写100%,TAG文件不用修改 安装方法:把Links.php和tag.php上传到网站根目录即可,访问 域名/Links.php、域名/tag.php 所有模板适用,代码就不粘贴出来,已经打

Spring 源码解读:自定义实现Bean定义的注册与解析

引言 在Spring框架中,Bean的注册与解析是整个依赖注入流程的核心步骤。通过Bean定义,Spring容器知道如何创建、配置和管理每个Bean实例。本篇文章将通过实现一个简化版的Bean定义注册与解析机制,帮助你理解Spring框架背后的设计逻辑。我们还将对比Spring中的BeanDefinition和BeanDefinitionRegistry,以全面掌握Bean注册和解析的核心原理。

音视频入门基础:WAV专题(10)——FFmpeg源码中计算WAV音频文件每个packet的pts、dts的实现

一、引言 从文章《音视频入门基础:WAV专题(6)——通过FFprobe显示WAV音频文件每个数据包的信息》中我们可以知道,通过FFprobe命令可以打印WAV音频文件每个packet(也称为数据包或多媒体包)的信息,这些信息包含该packet的pts、dts: 打印出来的“pts”实际是AVPacket结构体中的成员变量pts,是以AVStream->time_base为单位的显

kubelet组件的启动流程源码分析

概述 摘要: 本文将总结kubelet的作用以及原理,在有一定基础认识的前提下,通过阅读kubelet源码,对kubelet组件的启动流程进行分析。 正文 kubelet的作用 这里对kubelet的作用做一个简单总结。 节点管理 节点的注册 节点状态更新 容器管理(pod生命周期管理) 监听apiserver的容器事件 容器的创建、删除(CRI) 容器的网络的创建与删除