Notes-SpringBoot注解原理,自动装配原理-《Java笔记》

admin 2025-10-19 04:49:38 编程 来源:ZONE.CI 全球网 0 阅读模式

    Java SpringBoot首先看SpringBoot的主配置类:

    1. @SpringBootApplication
    2. public class StartEurekaApplication
    3. {
    4. public static void main(String[] args)
    5. {
    6. SpringApplication.run(StartEurekaApplication.class, args);
    7. }
    8. }

    点进@SpringBootApplication来看,发现@SpringBootApplication是一个组合注解。

    1. @Target(ElementType.TYPE)
    2. @Retention(RetentionPolicy.RUNTIME)
    3. @Documented
    4. @Inherited
    5. @SpringBootConfiguration
    6. @EnableAutoConfiguration
    7. @ComponentScan(excludeFilters = {
    8. @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    9. @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
    10. public @interface SpringBootApplication {
    11. }

    先来看 @SpringBootConfiguration

    1. @Target({ElementType.TYPE})
    2. @Retention(RetentionPolicy.RUNTIME)
    3. @Documented
    4. @Configuration
    5. public @interface SpringBootConfiguration {
    6. }

    可以看到这个注解除了元注解以外,就只有一个@Configuration,那也就是说这个注解相当于@Configuration,所以这两个注解作用是一样的,它让开发者能够去注册一些额外的Bean,并且导入一些额外的配置。那@Configuration还有一个作用就是把该类变成一个配置类,不需要额外的XML进行配置。所以@SpringBootConfiguration就相当于@Configuration。进入@Configuration,发现@Configuration核心是@Component,说明Spring的配置类也是Spring的一个组件。

    1. @Target({ElementType.TYPE})
    2. @Retention(RetentionPolicy.RUNTIME)
    3. @Documented
    4. @Component
    5. public @interface Configuration {
    6. @AliasFor(
    7. annotation = Component.class
    8. )
    9. String value() default "";
    10. }

    继续来看下一个@EnableAutoConfiguration,这个注解是开启自动配置的功能。

    1. @Target({ElementType.TYPE})
    2. @Retention(RetentionPolicy.RUNTIME)
    3. @Documented
    4. @Inherited
    5. @AutoConfigurationPackage
    6. @Import({AutoConfigurationImportSelector.class})
    7. public @interface EnableAutoConfiguration {
    8. String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    9. Class<?>[] exclude() default {};
    10. String[] excludeName() default {};
    11. }

    可以看到它是由 @AutoConfigurationPackage@Import(EnableAutoConfigurationImportSelector.class)这两个而组成的,先说@AutoConfigurationPackage,他是说:让包中的类以及子包中的类能够被自动扫描到Spring容器中。

    1. @Target({ElementType.TYPE})
    2. @Retention(RetentionPolicy.RUNTIME)
    3. @Documented
    4. @Inherited
    5. @Import({Registrar.class})
    6. public @interface AutoConfigurationPackage {
    7. }

    使用@Import来给Spring容器中导入一个组件 ,这里导入的是Registrar.class。来看下这个Registrar:

    1. static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    2. Registrar() {
    3. }
    4. public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    5. AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());
    6. }
    7. public Set<Object> determineImports(AnnotationMetadata metadata) {
    8. return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata));
    9. }
    10. }

    就是通过以上这个方法获取扫描的包路径,可以debug查看具体的值:Spring Boot 注解原理,自动装配原理 - 图1那metadata是什么呢,可以看到是标注在@SpringBootApplication注解上的DemosbApplication,也就是主配置类Application:其实就是将主配置类(即@SpringBootApplication标注的类)的所在包及子包里面所有组件扫描加载到Spring容器。因此要把DemoApplication放在项目的最高级中(最外层目录)。看看注解@Import(AutoConfigurationImportSelector.class)@Import注解就是给Spring容器中导入一些组件,这里传入了一个组件的选择器:AutoConfigurationImportSelector。可以从图中看出AutoConfigurationImportSelector%20继承了%20DeferredImportSelector%20继承了%20ImportSelector,ImportSelector有一个方法为:selectImports。将所有需要导入的组件以全类名的方式返回,这些组件就会被添加到容器中。

    public%20String[]%20selectImports(AnnotationMetadata%20annotationMetadata)%20{%20%20%20%20if%20(!this.isEnabled(annotationMetadata))%20{%20%20%20%20%20%20%20%20return%20NO_IMPORTS;%20%20%20%20}%20else%20{%20%20%20%20%20%20%20%20AutoConfigurationMetadata%20autoConfigurationMetadata%20=%20AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);%20%20%20%20%20%20%20%20AutoConfigurationImportSelector.AutoConfigurationEntry%20autoConfigurationEntry%20=%20%20%20%20%20%20%20%20%20this.getAutoConfigurationEntry(autoConfigurationMetadata,%20annotationMetadata);%20%20%20%20%20%20%20%20return%20StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());%20%20%20%20}}

    会给容器中导入非常多的自动配置类(xxxAutoConfiguration);就是给容器中导入这个场景需要的所有组件,并配置好这些组件。有了自动配置类,免去了手动编写配置注入功能组件等的工作。那是如何获取到这些配置类的呢,看看下面这个方法:

    1. protected AutoConfigurationImportSelector.AutoConfigurationEntry
    2. getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
    3. if (!this.isEnabled(annotationMetadata)) {
    4. return EMPTY_ENTRY;
    5. } else {
    6. AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
    7. List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
    8. configurations = this.removeDuplicates(configurations);
    9. Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
    10. this.checkExcludedClasses(configurations, exclusions);
    11. configurations.removeAll(exclusions);
    12. configurations = this.filter(configurations, autoConfigurationMetadata);
    13. this.fireAutoConfigurationImportEvents(configurations, exclusions);
    14. return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
    15. }
    16. }

    可以看到getCandidateConfigurations()这个方法,他的作用就是引入系统已经加载好的一些类,到底是那些类呢:

    1. protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    2. List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
    3. Assert.notEmpty(configurations,
    4. "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
    5. return configurations;
    6. }
    1. public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
    2. String factoryClassName = factoryClass.getName();
    3. return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    4. }

    会从META-INF/spring.factories中获取资源,然后通过Properties加载资源:

    1. private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    2. MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
    3. if (result != null) {
    4. return result;
    5. } else {
    6. try {
    7. Enumeration<URL> urls = classLoader !=
    8. null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
    9. LinkedMultiValueMap result = new LinkedMultiValueMap();
    10. while(urls.hasMoreElements()) {
    11. URL url = (URL)urls.nextElement();
    12. UrlResource resource = new UrlResource(url);
    13. Properties properties = PropertiesLoaderUtils.loadProperties(resource);
    14. Iterator var6 = properties.entrySet().iterator();
    15. while(var6.hasNext()) {
    16. Map.Entry<?, ?> entry = (Map.Entry)var6.next();
    17. String factoryClassName = ((String)entry.getKey()).trim();
    18. String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
    19. int var10 = var9.length;
    20. for(int var11 = 0; var11 < var10; ++var11) {
    21. String factoryName = var9[var11];
    22. result.add(factoryClassName, factoryName.trim());
    23. }
    24. }
    25. }
    26. cache.put(classLoader, result);
    27. return result;
    28. } catch (IOException var13) {
    29. throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
    30. }
    31. }
    32. }

    可以知道SpringBoot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值,将这些值作为自动配置类导入到容器中,自动配置类就生效,进行自动配置工作。以前需要自己配置的东西,自动配置类都完成了。如下图可以发现Spring常见的一些类已经自动导入。接下来看@ComponentScan注解,@ComponentScan(excludeFilters%20=%20{%20@Filter(type%20=%20FilterType.CUSTOM,%20classes%20=%20TypeExcludeFilter.class),%20@Filter(type%20=%20FilterType.CUSTOM,%20classes%20=%20AutoConfigurationExcludeFilter.class)%20}),这个注解就是扫描包,然后放入spring容器。

    @ComponentScan(excludeFilters%20=%20{%20%20%20%20@Filter(type%20=%20FilterType.CUSTOM,classes%20=%20{TypeExcludeFilter.class}),%20%20%20%20%20@Filter(type%20=%20FilterType.CUSTOM,classes%20=%20{AutoConfigurationExcludeFilter.class})})public%20@interface%20SpringBootApplication%20{}

    总结下@SpringbootApplication:就是说,他已经把很多东西准备好,具体是否使用取决于程序或者说配置。接下来继续看run方法:

    public%20static%20void%20main(String[]%20args)%20{%20%20%20%20SpringApplication.run(Application.class,%20args);}

    来看下在执行run方法到底有没有用到哪些自动配置的东西,点进run:

    public%20ConfigurableApplicationContext%20run(String...%20args)%20{%20%20%20%20//计时器%20%20%20%20StopWatch%20stopWatch%20=%20new%20StopWatch();%20%20%20%20stopWatch.start();%20%20%20%20ConfigurableApplicationContext%20context%20=%20null;%20%20%20%20Collection<SpringBootExceptionReporter>%20exceptionReporters%20=%20new%20ArrayList();%20%20%20%20this.configureHeadlessProperty();%20%20%20%20//监听器%20%20%20%20SpringApplicationRunListeners%20listeners%20=%20this.getRunListeners(args);%20%20%20%20listeners.starting();%20%20%20%20Collection%20exceptionReporters;%20%20%20%20try%20{%20%20%20%20%20%20%20%20ApplicationArguments%20applicationArguments%20=%20new%20DefaultApplicationArguments(args);%20%20%20%20%20%20%20%20ConfigurableEnvironment%20environment%20=%20this.prepareEnvironment(listeners,%20applicationArguments);%20%20%20%20%20%20%20%20this.configureIgnoreBeanInfo(environment);%20%20%20%20%20%20%20%20Banner%20printedBanner%20=%20this.printBanner(environment);%20%20%20%20%20%20%20%20//准备上下文%20%20%20%20%20%20%20%20context%20=%20this.createApplicationContext();%20%20%20%20%20%20%20%20exceptionReporters%20=%20this.getSpringFactoriesInstances(SpringBootExceptionReporter.class,%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20new%20Class[]{ConfigurableApplicationContext.class},%20context);%20%20%20%20%20%20%20%20//预刷新context%20%20%20%20%20%20%20%20this.prepareContext(context,%20environment,%20listeners,%20applicationArguments,%20printedBanner);%20%20%20%20%20%20%20%20//刷新context%20%20%20%20%20%20%20%20this.refreshContext(context);%20%20%20%20%20%20%20%20//刷新之后的context%20%20%20%20%20%20%20%20this.afterRefresh(context,%20applicationArguments);%20%20%20%20%20%20%20%20stopWatch.stop();%20%20%20%20%20%20%20%20if%20(this.logStartupInfo)%20{%20%20%20%20%20%20%20%20%20%20%20%20(new%20StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(),%20stopWatch);%20%20%20%20%20%20%20%20}%20%20%20%20%20%20%20%20listeners.started(context);%20%20%20%20%20%20%20%20this.callRunners(context,%20applicationArguments);%20%20%20%20}%20catch%20(Throwable%20var10)%20{%20%20%20%20%20%20%20%20this.handleRunFailure(context,%20var10,%20exceptionReporters,%20listeners);%20%20%20%20%20%20%20%20throw%20new%20IllegalStateException(var10);%20%20%20%20}%20%20%20%20try%20{%20%20%20%20%20%20%20%20listeners.running(context);%20%20%20%20%20%20%20%20return%20context;%20%20%20%20}%20catch%20(Throwable%20var9)%20{%20%20%20%20%20%20%20%20this.handleRunFailure(context,%20var9,%20exceptionReporters,%20(SpringApplicationRunListeners)null);%20%20%20%20%20%20%20%20throw%20new%20IllegalStateException(var9);%20%20%20%20}}

    那关注的就是%20refreshContext(context);%20刷新context,点进来看。

    private%20void%20refreshContext(ConfigurableApplicationContext%20context)%20{%20%20%20refresh(context);%20%20%20if%20(this.registerShutdownHook)%20{%20%20%20%20%20%20try%20{%20%20%20%20%20%20%20%20%20context.registerShutdownHook();%20%20%20%20%20%20}%20%20%20%20%20%20catch%20(AccessControlException%20ex)%20{%20%20%20%20%20%20%20%20%20//%20Not%20allowed%20in%20some%20environments.%20%20%20%20%20%20}%20%20%20}}

    继续点进refresh(context);

    protected%20void%20refresh(ApplicationContext%20applicationContext)%20{%20%20%20Assert.isInstanceOf(AbstractApplicationContext.class,%20applicationContext);%20%20%20((AbstractApplicationContext)%20applicationContext).refresh();}

    他会调用%20((AbstractApplicationContext)%20applicationContext).refresh();方法,点进来看:

    public%20void%20refresh()%20throws%20BeansException,%20IllegalStateException%20{%20%20%20synchronized%20(this.startupShutdownMonitor)%20{%20%20%20%20%20%20//%20Prepare%20this%20context%20for%20refreshing.%20%20%20%20%20%20prepareRefresh();%20%20%20%20%20%20//%20Tell%20the%20subclass%20to%20refresh%20the%20internal%20bean%20factory.%20%20%20%20%20%20ConfigurableListableBeanFactory%20beanFactory%20=%20obtainFreshBeanFactory();%20%20%20%20%20%20//%20Prepare%20the%20bean%20factory%20for%20use%20in%20this%20context.%20%20%20%20%20%20prepareBeanFactory(beanFactory);%20%20%20%20%20%20try%20{%20%20%20%20%20%20%20%20%20//%20Allows%20post-processing%20of%20the%20bean%20factory%20in%20context%20subclasses.%20%20%20%20%20%20%20%20%20postProcessBeanFactory(beanFactory);%20%20%20%20%20%20%20%20%20//%20Invoke%20factory%20processors%20registered%20as%20beans%20in%20the%20context.%20%20%20%20%20%20%20%20%20invokeBeanFactoryPostProcessors(beanFactory);%20%20%20%20%20%20%20%20%20//%20Register%20bean%20processors%20that%20intercept%20bean%20creation.%20%20%20%20%20%20%20%20%20registerBeanPostProcessors(beanFactory);%20%20%20%20%20%20%20%20%20//%20Initialize%20message%20source%20for%20this%20context.%20%20%20%20%20%20%20%20%20initMessageSource();%20%20%20%20%20%20%20%20%20//%20Initialize%20event%20multicaster%20for%20this%20context.%20%20%20%20%20%20%20%20%20initApplicationEventMulticaster();%20%20%20%20%20%20%20%20%20//%20Initialize%20other%20special%20beans%20in%20specific%20context%20subclasses.%20%20%20%20%20%20%20%20%20onRefresh();%20%20%20%20%20%20%20%20%20//%20Check%20for%20listener%20beans%20and%20register%20them.%20%20%20%20%20%20%20%20%20registerListeners();%20%20%20%20%20%20%20%20%20//%20Instantiate%20all%20remaining%20(non-lazy-init)%20singletons.%20%20%20%20%20%20%20%20%20finishBeanFactoryInitialization(beanFactory);%20%20%20%20%20%20%20%20%20//%20Last%20step:%20publish%20corresponding%20event.%20%20%20%20%20%20%20%20%20finishRefresh();%20%20%20%20%20%20}catch%20(BeansException%20ex)%20{%20%20%20%20%20%20%20%20%20if%20(logger.isWarnEnabled())%20{%20%20%20%20%20%20%20%20%20%20%20%20logger.warn("Exception%20encountered%20during%20context%20initialization%20-%20"%20+%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20"cancelling%20refresh%20attempt:%20"%20+%20ex);%20%20%20%20%20%20%20%20%20}%20%20%20%20%20%20%20%20%20//%20Destroy%20already%20created%20singletons%20to%20avoid%20dangling%20resources.%20%20%20%20%20%20%20%20%20destroyBeans();%20%20%20%20%20%20%20%20%20//%20Reset%20'active'%20flag.%20%20%20%20%20%20%20%20%20cancelRefresh(ex);%20%20%20%20%20%20%20%20%20//%20Propagate%20exception%20to%20caller.%20%20%20%20%20%20%20%20%20throw%20ex;%20%20%20%20%20%20}finally%20{%20%20%20%20%20%20%20%20%20//%20Reset%20common%20introspection%20caches%20in%20Spring's%20core,%20since%20we%20%20%20%20%20%20%20%20%20//%20might%20not%20ever%20need%20metadata%20for%20singleton%20beans%20anymore...%20%20%20%20%20%20%20%20%20resetCommonCaches();%20%20%20%20%20%20}%20%20%20}}

    由此可知,就是一个Spring的bean的加载过程。继续来看一个方法叫做%20onRefresh()

    protected%20void%20onRefresh()%20throws%20BeansException%20{%20%20%20//%20For%20subclasses:%20do%20nothing%20by%20default.}

    他在这里并没有直接实现,找他的具体实现:比如Tomcat跟web有关,可以看到有个ServletWebServerApplicationContext:

    1. @Override
    2. protected void onRefresh() {
    3. super.onRefresh();
    4. try {
    5. createWebServer();
    6. }
    7. catch (Throwable ex) {
    8. throw new ApplicationContextException("Unable to start web server", ex);
    9. }
    10. }

    可以看到有一个createWebServer();方法他是创建web容器的,而Tomcat不就是web容器,那是如何创建的呢,继续看:

    1. private void createWebServer() {
    2. WebServer webServer = this.webServer;
    3. ServletContext servletContext = getServletContext();
    4. if (webServer == null && servletContext == null) {
    5. ServletWebServerFactory factory = getWebServerFactory();
    6. this.webServer = factory.getWebServer(getSelfInitializer());
    7. }
    8. else if (servletContext != null) {
    9. try {
    10. getSelfInitializer().onStartup(servletContext);
    11. }
    12. catch (ServletException ex) {
    13. throw new ApplicationContextException("Cannot initialize servlet context",
    14. ex);
    15. }
    16. }
    17. initPropertySources();
    18. }

    factory.getWebServer(getSelfInitializer());他是通过工厂的方式创建的。

    1. public interface ServletWebServerFactory {
    2. WebServer getWebServer(ServletContextInitializer... initializers);
    3. }

    可以看到 它是一个接口,为什么会是接口。因为不止是Tomcat一种web容器。Spring Boot 注解原理,自动装配原理 - 图7看到还有Jetty,来看TomcatServletWebServerFactory:

    1. @Override
    2. public WebServer getWebServer(ServletContextInitializer... initializers) {
    3. Tomcat tomcat = new Tomcat();
    4. File baseDir = (this.baseDirectory != null) ? this.baseDirectory
    5. : createTempDir("tomcat");
    6. tomcat.setBaseDir(baseDir.getAbsolutePath());
    7. Connector connector = new Connector(this.protocol);
    8. tomcat.getService().addConnector(connector);
    9. customizeConnector(connector);
    10. tomcat.setConnector(connector);
    11. tomcat.getHost().setAutoDeploy(false);
    12. configureEngine(tomcat.getEngine());
    13. for (Connector additionalConnector : this.additionalTomcatConnectors) {
    14. tomcat.getService().addConnector(additionalConnector);
    15. }
    16. prepareContext(tomcat.getHost(), initializers);
    17. return getTomcatWebServer(tomcat);
    18. }

    那这块代码,就是要寻找的内置Tomcat,在这个过程当中,可以看到创建Tomcat的一个流程。如果不明白的话, 在用另一种方式来理解下,大家要应该都知道stater,举点例子。

    1. <dependency>
    2. <groupId>org.springframework.boot</groupId>
    3. <artifactId>spring-boot-starter-data-redis</artifactId>
    4. </dependency>
    5. <dependency>
    6. <groupId>org.springframework.boot</groupId>
    7. <artifactId>spring-boot-starter-freemarker</artifactId>
    8. </dependency>

    首先自定义一个stater。

    1. <parent>
    2. <groupId>org.springframework.boot</groupId>
    3. <artifactId>spring-boot-starter-parent</artifactId>
    4. <version>2.1.4.RELEASE</version>
    5. <relativePath/>
    6. </parent>
    7. <groupId>com.zgw</groupId>
    8. <artifactId>gw-spring-boot-starter</artifactId>
    9. <version>1.0-SNAPSHOT</version>
    10. <dependencies>
    11. <dependency>
    12. <groupId>org.springframework.boot</groupId>
    13. <artifactId>spring-boot-autoconfigure</artifactId>
    14. </dependency>
    15. </dependencies>

    先来看maven配置写入版本号,如果自定义一个stater的话必须依赖spring-boot-autoconfigure这个包,先看下项目目录。Spring Boot 注解原理,自动装配原理 - 图8

    1. public class GwServiceImpl implements GwService{
    2. @Autowired
    3. GwProperties properties;
    4. @Override
    5. public void Hello()
    6. {
    7. String name=properties.getName();
    8. System.out.println(name+"说:你们好啊");
    9. }
    10. }

    做的就是通过配置文件来定制name这个是具体实现。

    1. @Component
    2. @ConfigurationProperties(prefix = "spring.gwname")
    3. public class GwProperties {
    4. String name="zgw";
    5. public String getName() {
    6. return name;
    7. }
    8. public void setName(String name) {
    9. this.name = name;
    10. }
    11. }

    这个类可以通过@ConfigurationProperties读取配置文件。

    1. @Configuration
    2. @ConditionalOnClass(GwService.class) //扫描类
    3. @EnableConfigurationProperties(GwProperties.class) //让配置类生效
    4. public class GwAutoConfiguration {
    5. /**
    6. * 功能描述 托管给spring
    7. * @author zgw
    8. * @return
    9. */
    10. @Bean
    11. @ConditionalOnMissingBean
    12. public GwService gwService()
    13. {
    14. return new GwServiceImpl();
    15. }
    16. }

    这个为配置类,为什么这么写因为,spring-boot的stater都是这么写的,可以参照他仿写stater,以达到自动配置的目的,然后在通过spring.factories也来进行配置。

    1. org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.gw.GwAutoConfiguration

    然后这样一个简单的stater就完成了,然后可以进行maven的打包,在其他项目引入就可以使用。

    以太坊cppgolang区别 编程

    以太坊cppgolang区别

    以太坊是一种去中心化的开源平台,它采用智能合约技术,旨在构建和运行不受干扰的分布式应用程序。作为目前最受欢迎的区块链平台之一,以太坊提供了多种编程语言的支持,其
    progolang 编程

    progolang

    Go语言(Golang)是由Google开发的一门静态类型编程语言。作为一名专业的Golang开发者,我深知这门语言的优势和特点。在本文中,我将介绍Golang
    golangn个发送者 编程

    golangn个发送者

    Golang是一种开源的编程语言,由Google团队开发,旨在提高程序的并发性和简化软件开发过程。在Go语言中,有时需要向多个接收者发送信息。本文将介绍如何在G
    golang技能图谱 编程

    golang技能图谱

    从互联网行业的快速发展到人工智能技术的日益成熟,各种编程语言也应运而生。而在这众多的编程语言中,Golang(即Go)作为一门强大且高效的开发语言备受关注。Go
    评论:0   参与:  4