当前位置: 首页 > news >正文

SpringBoot的自动装配进阶

目录

前言

自动装配重点项

@SpringBootApplication 注解

理解 @EnableAutoConfiguration

理解 EnableAutoConfigurationImportSelector

@Import 注解

EnableAutoConfigurationImportSelector 实现类

@Conditional 条件注解

@Conditional 的衍生注解

SpringBoot Starter 实践

小结

引申


前言

        SpringBoot 最核心的功能就是自动装配,Starter 作为 SpringBoot 的核心功能之一,基于自动配置代码提供了自动配置模块及依赖的能力,让软件集成变得简单、易用。使用 SpringBoot 时,我们只需引人对应的 Starter,SpringBoot 启动时便会自动加载相关依赖,集成相关功能,这便是 SpringBoot 的自动装配功能。
        简单概括其自动配置的原理:由@SpringBootAppliction组合注解中的@EnableAutoConfiguration注解开启自动配置,加载 spring.factories 文件中注册的各种 默认定义的XxxAutoConfiguration 配置类,并且该类可指定@Conditional 条件注解,当其 @Conditional 条件注解生效时,实例化该配置类中定义的 Bean,并注入 Spring 上下文。

自动装配重点项

SpringBoot 自动装配过程涉及以下主要内容:
        @EnableAutoConfiguration: 扫描类路径下的META-INF/spring.factories文件,并加载其中中注册的 AutoConfiguration 配置类,开启自动装配;
        spring.factories:配置文件,位于 jar 包的 META-INF 目录下,按照指定格式注册了                       各类AutoConfiguration 配置类。AutoConfiguration 类:自动配置类,SpringBoot 的大量以 xxxAutoConfiguration 命名的自动配置类,定义了三方组件集成 Spring 所需初始化的 Bean 和条件;
        @Conditional 条件注解及其行生注解:使用在 AutoConfiguration 类上,设置了配置类的实例化条件;
        Starter:三方组件的依赖及配置,包括 SpringBoot 预置组件和自定义组件,往往会包含
spring.factories 文件、AutoConfiguration 类和其他配置类,其功能间的作用关系如下图:

@SpringBootApplication 注解

        SpringBoot 项目创建完成会默认生成一个xxxApplication的带有main方法的入口类,通过该类的 main 方法即可启动 SpringBoot 项目,通过调用 SpringApplication 的静态方法 run 作为 SpringBoot 项目的入口,示例代码如下:

@SpringBootApplication
public class LearnSpringBootApplication {
    public static void main(String[] args) {
        SpringApplication.run(LearnSpringBootApplication.class, args);
    }
}

        在 SpringBoot 入口类上,唯一的注解就是 @SpringBootApplication。它是 SpringBoot 项目的核心注解,用于开启自动配置,更准确地说,是组合注解。如包含了@EnableAutoConfiguration开启自动装配功能。

@SpringBootApplication的源码如下:

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
    
    // 排除指定的自动配置类
	@AliasFor(annotation = EnableAutoConfiguration.class)
	Class<?>[] exclude() default {};
    
    // 通过类名的方式排除自动配置类
    @AliasFor(annotation = EnableAutoConfiguration.class)
	String[] excludeName() default {};
    
    // 指定扫描的包名,激活包下 @Component 等注解组件的初始化
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
	String[] scanBasePackages() default {};

    // 扫描指定类所在包下的所有组件
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
	Class<?>[] scanBasePackageClasses() default {};
    
    @AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
	Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
    
    // 指定是否代理 @Bean 方法以强制执行 bean 的生命周期行为
    @AliasFor(annotation = Configuration.class)
	boolean proxyBeanMethods() default true;
}

@SpringBootApplication注解中组合了@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan,其中@SpringBootConfiguration就是@Configuration。在实践过程中也可以使用这3个注解来替代@SpringBootApplication

理解 @EnableAutoConfiguration

        上面说到@EnableAutoConfiguration用来开启自动装配功能,接下来重点看下该注解。@EnableAutoConfiguration的主要功能是在启动 Spring 应用程序上下文时进行自动配置,它会尝试配置项目可能需要的 Bean。自动配置通常是基于项目 classpath 中引入的类和已定义的 Bean 来实现的。在此过程中,被自动配置的组件来自项目自身和项目依赖的 jar 包中。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    // 排除指定的自动配置类
	Class<?>[] exclude() default {};

    // 通过类名的方式排除自动配置类
	String[] excludeName() default {};

}

        上文提到,在 SpringBoot 启动应用上下文的时候,@EnableAutoConfiguration会尝试配置项目可能需要的 Bean,但在实践中如果不需要自定配置相关的 Bean,则可以通过exclude/excludeName让自动化配置类失效,比如不希望数据源(DataSource)自动配置:

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class LearnSpringBootApplication {
}

        另外,注意注解类上的@AutoConfigurationPackage,通过阅读其 JavaDoc 可知:当basePackages或者basePackageClasses没有指定的话, 被@EnableAutoConfiguration注解的配置类所在的包就是扫描的基础包。

这也是为什么@SpringBootApplication的主配置类要放在顶级 package 下的原因。

理解 EnableAutoConfigurationImportSelector

        @EnableAutoConfiguration的自动配置功能是通过@Import注解导入的ImportSelector来完成的。 @Import(EnableAutoConfigurationImportSelector.class)@EnableAutoConfiguration注解的组成部分,也是自动配置功能的核心实现部分。下面讲解@Import的基本使用方法和ImportSelector的实现类EnableAutoConfigurationImportSelector

@Import 注解

@Import注解,提供了导入配置类的功能。SpringBoot 的源代码中,有大量的EnableXXX类都使用了该注解,了解@Import有助于我们理解 SpringBoot 的自动装配,@Import有以下三个用途:

  1. 通过@Import引入@Configuration注解的类;
  2. 导入实现了ImportSelectorImportBeanDefinitionRegistrar的类;
  3. 通过@Import导入普通的POJO

EnableAutoConfigurationImportSelector 实现类

        @Import的许多功能都需要借助接口ImportSelector来实现,ImportSelector决定可引人哪些 @Configuration的注解类,ImportSelector接口源码如下:

public interface ImportSelector {
	String[] selectImports(AnnotationMetadata importingClassMetadata);
}

        ImportSelector接口只提供了一个参数为AnnotationMetadata的方法,返回的结果为一个字符串数组。其中参数AnnotationMetadata内包含了被@Import注解的类的注解信息。在selectImports方法内可根据具体实现决定返回哪些配置类的全限定名,将结果以字符串数组的形式返回。
        如果实现了接口ImportSelector的类的同时又实现了以下4个Aware接口,那么 Spring 保证在调用ImportSelector之前会先调用Aware接口的方法。这4个接口为:EnvironmentAwareBeanFactoryAwareBeanClassLoaderAwareResourceLoaderAware

在EnableAutoConfigurationImportSelector的源代码中就实现了这4个接口:

public class EnableAutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
 
   ....代码块.....
}

        EnableAutoConfigurationImportSelector实现了ImportSelector的子接口 DeferredlmportSelector

DeferredlmportSelector接口与ImportSelector的区别是,前者会在所有的 @Configuration 类加载完成之后再加载返回的配置类,而ImportSelector 是在加载完 @Configuration 类之前先去加载返回的配置类。

其加载流程如图所示: 

EnableAutoConfigurationImportSelector实现的selectImports方法如下:

 getCandidateConfigurations 方法用于获取META-INF/spring.factories的配置类信息:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
        return configurations;
    }

其中关键点在于getCandidateConfigurations方法中使用SpringFactoriesLoader的loadFactoryNames方法加载META-INF/spring.factories文件中配置的EnableAutoConfiguration类型的自动配置类: 

public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();

        try {
            Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
            ArrayList result = new ArrayList();

            while(urls.hasMoreElements()) {
                URL url = (URL)urls.nextElement();
                Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
                String factoryClassNames = properties.getProperty(factoryClassName);
                result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
            }

            return result;
        } catch (IOException var8) {
            throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8);
        }
    }

看下某些自定义在META-INF/spring.factories文件的部分打算自动装配的Configure内容:

SpringFactoriesLoader 加载了这些xxxAutoConfiguration配置类,通过上面提到的加载、过滤、实例化的过程后,就自动开启某一些具体的功能。

@Conditional 条件注解

        前面我们看了EnableAutoConfigurationImportSelector是如何进行自动配置类的读取和筛选的,打开每一个自动配置类,我们都会看到@Conditional或其行生的条件注解,其作用是限定自动配置类的生效条件。

        下面讲一下@Conditional注解原理,@Conditional可根据是否满足指定的条件来决定是否进行 Bean 的实例化及装配(比如,设定当类路径下包含某个类的时候才会对注解的类进行实例化操作),就是根据一些特定条件来控制 Bean 实例化的行为,@Conditional代码如下:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Conditional {
    Class<? extends Condition>[] value();
}

public interface Condition {
    boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2);
}

@Conditional 注解唯一的元素属性是接口 Condition 的数组,只有在数组中指定的所有Conditionmatehes方法都返回true的情况下,被注解的类才会被加载: 

@FunctionalInterface
public interface Condition {
    // 决定条件是否匹配
	boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

        matches方法的第一个参数为ConditionContext,可通过该接口提供的方法来获得 Spring 应用的上下文信息;matches方法的第二个参数为AnnotatedTypeMetadata,该接口提供了访问特定类或方法的注解功能,可以用来检查带有@Bean注解的方法上是否还有其他注解。

@Conditional 的衍生注解

        SpringBoot 提供了很多基于@Conditional注解的衍生注解,它们适用不同的场景并提供了不同的功能,这些注解均位于spring-boot-autoconfigure项目的org.springframework.boot.autoconfigure.condition包下,比如:

  • @ConditionalOnBean:在容器中有指定 Bean 的条件下
  • @ConditionalOnClass: 在 classpath 类路径下有指定类的条件下
  • @ConditionalOnMissingBean: 当容器里没有指定 Bean 的条件时
  • @ConditionalOnMissingClass:当类路径下没有指定类的条件时
  • @ConditionalOnProperty:在指定的属性有指定值的条件
  • @ConditionalOnWebApplication:在项目是一个 Web 项目的条件下

        阅读以上这些注解的源码可以看到,它们都组合了@Conditional注解,通过不同的Condition实现类实现不同的功能,而且用到的实现类大部分都继承自实现了Condition接口的SpringBootCondition抽象类,其继承关系如下:

ConditionalOnClass为例,看下具体代码:

@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
    ...
}

@Order(Ordered.HIGHEST_PRECEDENCE)
class OnClassCondition extends FilteringSpringBootCondition {
    ...
}
public abstract class SpringBootCondition implements Condition {

	@Override
	public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		String classOrMethodName = getClassOrMethodName(metadata);
		try {
            // 调用新定义的抽象方法 getMatchOutcome 并交由子类来实现,
            // 在 matches 方法中根据子类 getMatchOutcome 返回的结果判断是否匹配
			ConditionOutcome outcome = getMatchOutcome(context, metadata);
			logOutcome(classOrMethodName, outcome);
			recordEvaluation(context, classOrMethodName, outcome);
			return outcome.isMatch();
		}
		catch (NoClassDefFoundError ex) {}
	}
    // 在抽象类 SpringBootCondition 中实现了 matches 方法,是通过调用新定义的抽象方法 getMatchOutcome 并交由子类来实现
    // 在 matches 方法中根据子类返回的结果判断是否匹配
    public abstract ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata);

}

SpringBoot Starter 实践

        上文已经讲解了 SpringBoot 的核心运作原理,SpringBoot 使用上述自动配置原理实现了许多的starter,可以在项目中快速集成。日常工作中我们可以借鉴 SpringBoot 的 starter 的创建机制来创建自己的starter,这一节我们自己实现一个简单的starter项目,可以快速集成到其他 SpringBoot 项目中。
        我们设计一个能判断当前环境的starter项目:引入该项目后,会根据环境在 Spring 上下文中自动注入一个对应当前环境的实现IProfileService接口的类Bean,该BeangetProfile方法能够返回当前的环境名,我们需要根据不同环境注入不同的Bean

// 不同环境有不同的实现类:DevService、ProdService
public interface IProfileService {
    String getProfile();
}

        首先创建一个Maven项目,命名为spring-boot-starter-profilespring-boot-starter项目依赖spring-boot-autoconfigure,引入其Maven坐标:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-autoconfigure</artifactId>
  <version>2.0.1.RELEASE</version>
</dependency>

创建com.spring.learn.IProfileService接口和对应devprod环境的两个实现类:

public interface IProfileService {
    String getProfile();
}
// dev 环境下的实现类
public class DevService implements IProfileService{
    @Override
    public String getProfile() {
        return "dev";
    }
}
// prod 环境下的实现类
public class ProdService implements IProfileService {
    @Override
    public String getProfile() {
        return "prod";
    }
}

接下来,创建自动配置类 ProfileAutoConfiguration,如何判断环境?我们用上文提到的@ConditionalOnProperty 代替,通过在 properties(或 yml)配置文件中配置profile.service.enabled配置项来模拟不同环境:

@Configuration
@ConditionalOnMissingBean(IProfileService.class) // 容器中不存在 IProfileService 类型的 Bean 时才生效
public class ProfileAutoConfiguration {

    @Bean
    // profile.service.enabled=dev 时,注入 DevService
    @ConditionalOnProperty(prefix = "profile.service", value = "enabled", havingValue = "dev")
    public DevService devService() {
        return new DevService();
    }

    @Bean
    // profile.service.enabled=prod 时,注入 ProdService
    @ConditionalOnProperty(prefix = "profile.service", value = "enabled", havingValue = "prod")
    public ProdService prodService() {
        return new ProdService();
    }
}

resources目录下创建META-INF/spring.factories文件,并填入以下内容:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.spring.learn.ProfileAutoConfiguration

最后,通过mvn clean install将该包安装到本地仓库,方便其他项目引用。
任意打开一个 SpringBoot 项目,引入我们的自定义starter坐标:

<dependency>
  <groupId>org.spring.learn</groupId>
  <artifactId>spring-boot-starter-profile</artifactId>
  <version>1.0-SNAPSHOT</version>
</dependency>

application.properties配置文件中指定下环境配置,模拟不同环境:

profile.service.enabled=prod

        通过SpringApplicationContext获取IProfileService类型Bean,打印getProfile方法获取的环境名称:

public static void main(String[] args) {
    ConfigurableApplicationContext context = SpringApplication.run(LearnSpringBootApplication.class, args);
    IProfileService bean = context.getBean(IProfileService.class);
    System.out.println("get profile from IProfileService: " + bean.getProfile());
}

小结

        本文围绕 SpringBoot 的核心功能展开,从总体上了解 SpringBoot 自动配置的原理以及自动配置核心组件的运作过程,重点学习自动配置原理、@EnableAutoConfiguration、 @lmport、 ImportSelector、@Conditional 以及自定义 starter 示例解析等内容。掌握了这些基础的组建内容及其功能,在集成其他三方类库的自动配置时,才能够更加清晰地了解它们都运用了自动配置的哪些功能。

引申

        有没有考虑过利用以上的内容原理,实现类似的@SpringBootApplication注解的能力呢?这样就可以不单单的可以实现利用SpringBoot的XxxAutoConfiguration自动装配的能力,而是可拓展更强大的能力,比如注册自定义的bean。相关内容参考:Spring Cloud提供的某些注入启动注解如@EnableEurekaClient、@EnableDiscoveryClient、@EnableEurekaServer等类似逻辑。

如下自定义组合注解@EnableCustomerClient实现注册自定义bean:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import({CustomerClientRegistrar.class, CustomerProcessorConfiguration.class}) //定义自定义Registrar 实现ImportBeanDefinitionRegistrar 实现BeanDefinition能力、ProcessorConfiguration配置类实现自定义的某些Bean的创建能力
public @interface EnableCustomerClient {//作为组合启动注解 用于定义在启动类上

    String[] value() default {};

    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};

}


public class CustomerClientRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {


 @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { //重新注册bean定义 importingClassMetadata包含EnableCustomerClient注解的属性内容
       //自定义注册逻辑 ,如scan扫包 加载具备某些自定义注解的类对象、过滤等
...
//如最后利用BeanDefinitionReaderUtils.registerBeanDefinition方法实现注册bean定义
    ScopeMetadataResolver scopeMetadataResolver = new AnnotationScopeMetadataResolver();
        AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotationMetadata);
        ScopeMetadata scopeMetadata = scopeMetadataResolver.resolveScopeMetadata(abd);
        abd.setScope(scopeMetadata.getScopeName());
        AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
        BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
        BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);
    }
}


@Import({XxxProperties.class})
@Configuration
public class CustomerProcessorConfiguration {

    @Bean
    public ProcessorInitializer processorInitializer(
            ApplicationContext applicationContext,
            XxxProperties properties) { //注册一个程序初始化启动器
        ProcessorInitializer processorInitializer = new ProcessorInitializer(
                applicationContext,
                properties
        );
        processorInitializer.initAndStart(); //进行整个初始化行为的初始化和开始方法
        return processorInitializer;
    }
}


@Slf4j
public class ProcessorInitializer {
    private final ApplicationContext applicationContext;
    private final XxxProperties properties;

    public ProcessorInitializer(ApplicationContext applicationContext,  XxxProperties properties) {
        Assert.notNull(applicationContext, "applicationContext must not be null");
        Assert.notNull(properties, "properties must not be null");
        this.applicationContext = applicationContext;
        this.properties = properties;
    }

    public void initAndStart() {//初始化和启动方法的入口
        this.init();
        this.start();
    }

    private void init() {
    ...//自定义业务逻辑 初始化逻辑
    }
    private void start() {
    ...//自定义业务逻辑 启动逻辑
    }
}

        参考代码: ImportBeanDefinitionRegistrar实现动态创建自定义bean,自己写的代码封装成BeanDefinition对象示例:ImportBeanDefinitionRegistrar - 愤怒的苹果ext - 博客园、ImportBeanDefinitionRegistrar接口 - 溪水静幽 - 博客园

相关文章:

  • 高通平台Android 蓝牙调配置手试和册-- OPP File Transmission Failure
  • STC15单片机内部RAM讲解
  • zemax---Ray Aberration(光线光扇图)
  • Polygon zkEVM Arithmetic状态机
  • 汽车毫米波雷达测试与测量解决方案
  • 网络编程--sockaddr 与 sockaddr_in
  • HashMap底层分析
  • 《工程伦理与学术道德》之《导论》
  • VGLUT 1抗体丨SYSY VGLUT 1抗体化学性质和文献参考
  • 598. 范围求和 II (脑筋急转弯)
  • 【云存储】大容量网盘的介绍与选择
  • openEuler-22.03系统安装openGauss3.0.0 企业版过程中遇到的坑
  • Vue组件、slot介绍
  • 网络编程之POP3协议邮箱收信
  • Markdown 数学公式详解
  • 实现windows 窗体的自己画,网上摘抄的,学习了
  • Angular数据绑定机制
  • cookie和session
  • download使用浅析
  • es6--symbol
  • hadoop入门学习教程--DKHadoop完整安装步骤
  • Linux编程学习笔记 | Linux多线程学习[2] - 线程的同步
  • mysql外键的使用
  • QQ浏览器x5内核的兼容性问题
  • Storybook 5.0正式发布:有史以来变化最大的版本\n
  • 半理解系列--Promise的进化史
  • 机器人定位导航技术 激光SLAM与视觉SLAM谁更胜一筹?
  • 排序(1):冒泡排序
  • 前端面试之闭包
  • 使用 Node.js 的 nodemailer 模块发送邮件(支持 QQ、163 等、支持附件)
  • 世界上最简单的无等待算法(getAndIncrement)
  • 一起参Ember.js讨论、问答社区。
  • ​​​​​​​​​​​​​​汽车网络信息安全分析方法论
  • ​DB-Engines 12月数据库排名: PostgreSQL有望获得「2020年度数据库」荣誉?
  • ​草莓熊python turtle绘图代码(玫瑰花版)附源代码
  • ###STL(标准模板库)
  • (1)(1.13) SiK无线电高级配置(五)
  • (env: Windows,mp,1.06.2308310; lib: 3.2.4) uniapp微信小程序
  • (HAL)STM32F103C6T8——软件模拟I2C驱动0.96寸OLED屏幕
  • (Java岗)秋招打卡!一本学历拿下美团、阿里、快手、米哈游offer
  • (读书笔记)Javascript高级程序设计---ECMAScript基础
  • (翻译)Entity Framework技巧系列之七 - Tip 26 – 28
  • (附源码)ssm基于微信小程序的疫苗管理系统 毕业设计 092354
  • (附源码)ssm失物招领系统 毕业设计 182317
  • (七)c52学习之旅-中断
  • (四)Linux Shell编程——输入输出重定向
  • (算法)求1到1亿间的质数或素数
  • (一)硬件制作--从零开始自制linux掌上电脑(F1C200S) <嵌入式项目>
  • (转)EXC_BREAKPOINT僵尸错误
  • (转)从零实现3D图像引擎:(8)参数化直线与3D平面函数库
  • (转)平衡树
  • .Net 6.0 处理跨域的方式
  • .NET 8 中引入新的 IHostedLifecycleService 接口 实现定时任务
  • .net core 微服务_.NET Core 3.0中用 Code-First 方式创建 gRPC 服务与客户端
  • .NET 线程 Thread 进程 Process、线程池 pool、Invoke、begininvoke、异步回调