Spring注解演進與自動裝配原理深度解析:從歷史發展到自定義Starter實踐

目錄

Spring注解發展史

Spring 1.X

Spring 2.X

Spring 2.5之前

@Required

@Repository

@Aspect

Spring2.5 之后

Spring 3.x

@ComponentScan

@Import

靜態導入

ImportSelector

ImportBeanDefinitionRegistrar

@EnableXXX

Spring 4.x

Spring 5.x

什么是SPI

自動裝配的流程演示

@EnableAutoConfiguration

那AutoConfigurationImportSelector是什么?

EnableDefineService

MyDefineImportSelector

EnableDemoTest

@EnableAutoConfiguration注解的實現原理

selectImports

getAutoConfigurationEntry

SpringFactoriesLoader

Spring Boot中的條件過濾

自己搓一個Starter來增進對自動裝配的理解

創建一個Maven項目,quick-starter

定義Formate接口

定義相關的配置類

創建spring.factories文件

測試

自定義Starter關聯配置信息


Spring注解發展史

為了更好的理解SpringBoot的內容,我們先梳理Spring注解編程的發展過程,由該過程的演變更理解SpringBoot的由來。

Spring 1.X

2004年3月24日,Spring1.0 正式發布,提供了IoC,AOP及XML配置的方式。

在Spring1.x版本中提供的是純XML配置的方式,也就是在該版本中我們必須要提供xml的配置文件,在該文件中我們通過 <bean> 標簽來配置需要被IoC容器管理的Bean。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><bean class="com.dura.demo01.UserService" />
</beans>public static void main(String[] args) {ApplicationContext ac = new FileSystemXmlApplicationContext("classpath:applicationContext01.xml");System.out.println("ac.getBean(UserService.class) = " + ac.getBean(UserService.class));
}

在Spring1.2版本的時候提供了@Transaction (org.springframework.transaction.annotation ) 注解。簡化了事務的操作。

Spring 2.X

?在2006年10月3日 Spring2.0問世了,在2.x版本中,比較重要的特點是增加了很多注解

Spring 2.5之前

??在2.5版本之前新增的有 @Required @Repository @Aspect,同時也擴展了XML的配置能力,提供了第三方的擴展標簽,比如 <dubbo>

@Required

??如果你在某個java類的某個set方法上使用了該注釋,那么該set方法對應的屬性在xml配置文件中必須被設置,否則就會報錯!!!

如果在xml文件中我們不設置對應的屬性就會給出錯誤的提示。

@Repository

??@Repository 對應數據訪問層Bean.這個注解在Spring2.0版本就提供的。

@Aspect

??@Aspect是AOP相關的一個注解,用來標識配置類。

Spring2.5 之后

??在2007年11月19日,Spring更新到了2.5版本,新增了很多常用注解,大大的簡化配置操作。

注解說明
@Autowired依賴注入
@Qualifier配置@Autowired注解使用
@Component聲明組件
@Service聲明業務層組件
@Controller聲明控制層組件
@RequestMapping聲明請求對應的處理方法

在這些注解的作用下,我們可以不用在xml文件中去注冊沒有bean,這時我們只需要指定掃碼路徑,然后在對應的Bean頭部添加相關的注解即可,這大大的簡化了我們的配置及維護工作。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><context:component-scan base-package="com.dura" />
</beans>

雖然在Spring的2.5版本提供了很多的注解,也大大的簡化了我們的開發,但是任然沒有擺脫XML配置驅動。

Spring 3.x

??在2009年12月16日發布了Spring3.0版本,這是一個注解編程發展的里程碑版本,在該版本中全面擁抱Java5。提供了 @Configuration注解,目的就是去xml化。同時通過 @ImportResource來實現Java配置類和XML配置的混合使用來實現平穩過渡。

/*** @Configuration 標注的Java類 相當于 application.xml 配置文件*/
@Configuration
public class JavaConfig {/*** @Bean 注解 標注的方法就相當于 <bean></bean> 標簽也是 Spring3.0 提供的注解* @return*/@Beanpublic UserService userService(){return new UserService();}
}

在Spring3.1 版之前配置掃描路徑我們還只能在 XML 配置文件中通過 component-scan 標簽來實現,在3.1 版本到來的時候,提供了一個 @ComponentScan注解,該注解的作用是替換掉 component-scan標簽,是注解編程很大的進步,也是Spring實現無配置化的堅實基礎。

@ComponentScan

@ComponentScan的作用是指定掃碼路徑,用來替代在XML中的 <component-scan>標簽,默認的掃碼路徑是當前注解標注的類所在的包及其子包。

@Import

??@Import注解只能用在類上,作用是快速的將實例導入到Spring的IoC容器中,將實例導入到IoC容器中的方式有很多種,比如 @Bean注解,@Import注解可以用于導入第三方包。具體的使用方式有三種。

靜態導入

??靜態導入的方式是直接將我們需要導入到IoC容器中的對象類型直接添加進去即可。這種方式的好處是簡單,直接,但是缺點是如果要導入的比較多,則不太方便,而且也不靈活。

ImportSelector

??@Import注解中我們也可以添加一個實現了 ImportSelector接口的類型,這時不會將該類型導入IoC容器中,而是會調用 ImportSelector接口中定義的 selectImports方法,將該方法的返回的字符串數組的類型添加到容器中。

定義ImportSelector接口的實現,方法返回的是需要添加到IoC容器中的對象對應的類型的全類路徑的字符串數組,我們可以根據不同的業務需求而導入不同的類型,會更加的靈活些。

public class MyImportSelector implements ImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {return new String[]{Logger.class.getName(),Cache.class.getName()};}
}
ImportBeanDefinitionRegistrar

??除了上面所介紹的ImportSelector方式靈活導入以外還提供了 ImportBeanDefinitionRegistrar 接口,也可以實現,相比 ImportSelector 接口的方式,ImportBeanDefinitionRegistrar 的方式是直接在定義的方法中提供了 BeanDefinitionRegistry ,自己在方法中實現注冊。

@EnableXXX

??@Enable模塊驅動,其實是在系統中我們先開發好各個功能獨立的模塊,比如 Web MVC 模塊, AspectJ代理模塊,Caching模塊等。

Spring 4.x

??2013年11月1 日更新的Spring 4.0 ,完全支持Java8.這是一個注解完善的時代,提供的核心注解是@Conditional條件注解。@Conditional 注解的作用是按照一定的條件進行判斷,滿足條件就給容器注冊Bean實例。

??@Conditional的定義為:類和方法中使用

// 該注解可以在 類和方法中使用
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {/*** 注解中添加的類型必須是 實現了 Condition 接口的類型*/Class<? extends Condition>[] value();
}

Condition是個接口,需要實現matches方法,返回true則注入bean,false則不注入。

@Conditional的作用就是給我們提供了對象導入IoC容器的條件機制,這也是SpringBoot中的自動裝配的核心關鍵。當然在4.x還提供一些其他的注解支持,比如 @EventListener,作為ApplicationListener接口編程的第二選擇,@AliasFor解除注解派生的時候沖突限制。@CrossOrigin作為瀏覽器跨域資源的解決方案。

Spring 5.x

??2017年9月28日,Spring來到了5.0版本。5.0同時也是SpringBoot2.0的底層。注解驅動的性能提升方面不是很明顯。在Spring Boot應用場景中,大量使用@ComponentScan掃描,導致Spring模式的注解解析時間耗時增大,因此,5.0時代引入@Indexed,為Spring模式注解添加索引。

??當我們在項目中使用了 @Indexed之后,編譯打包的時候會在項目中自動生成 META-INT/spring.components文件。當Spring應用上下文執行 ComponentScan掃描時,META-INT/spring.components將會被 CandidateComponentsIndexLoader 讀取并加載,轉換為 CandidateComponentsIndex對象,這樣的話 @ComponentScan不在掃描指定的package,而是讀取 CandidateComponentsIndex對象,從而達到提升性能的目的。

<dependency><groupId>org.springframework</groupId><artifactId>spring-context-indexer</artifactId>
</dependency>

什么是SPI

在SpringBoot的自動裝配中其實有使用到SPI機制

SPI ,全稱為 Service Provider Interface,是一種服務發現機制。它通過在ClassPath路徑下的META-INF/services文件夾查找文件,自動加載文件里所定義的類。這一機制為很多框架擴展提供了可能,比如在Dubbo、JDBC中都使用到了SPI機制。我們先通過一個很簡單的例子來看下它是怎么用的。

流程:A項目中僅聲明個接口;在拓展的實現,導入A項目的依賴,創建接口的實現類。然后在resources目錄下創建 META-INF/services 目錄,然后在目錄中創建一個文件,名稱必須是定義的接口的全類路徑名稱。然后在文件中寫上接口的實現類的全類路徑名稱。然后A項目、B項目均可以用于C項目。

ServiceLoader:

 // 配置文件的路徑private static final String PREFIX = "META-INF/services/";// 加載的服務  類或者接口private final Class<S> service;// 類加載器private final ClassLoader loader;// 訪問權限的上下文對象private final AccessControlContext acc;// 保存已經加載的服務類private LinkedHashMap<String,S> providers = new LinkedHashMap<>();// 內部類,真正加載服務類private LazyIterator lookupIterator;

load方法創建了一些屬性,重要的是實例化了內部類,LazyIterator。

public final class ServiceLoader<S> implements Iterable<S>private ServiceLoader(Class<S> svc, ClassLoader cl) {//要加載的接口service = Objects.requireNonNull(svc, "Service interface cannot be null");//類加載器loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;//訪問控制器acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;reload();}public void reload() {//先清空providers.clear();//實例化內部類 LazyIterator lookupIterator = new LazyIterator(service, loader);}
}

查找實現類和創建實現類的過程,都在LazyIterator完成。當我們調用iterator.hasNext和iterator.next方法的時候,實際上調用的都是LazyIterator的相應方法。

private class LazyIterator implements Iterator<S>{Class<S> service;ClassLoader loader;Enumeration<URL> configs = null;Iterator<String> pending = null;String nextName = null; private boolean hasNextService() {//第二次調用的時候,已經解析完成了,直接返回if (nextName != null) {return true;}if (configs == null) {//META-INF/services/ 加上接口的全限定類名,就是文件服務類的文件//META-INF/services/com.viewscenes.netsupervisor.spi.SPIServiceString fullName = PREFIX + service.getName();//將文件路徑轉成URL對象configs = loader.getResources(fullName);}while ((pending == null) || !pending.hasNext()) {//解析URL文件對象,讀取內容,最后返回pending = parse(service, configs.nextElement());}//拿到第一個實現類的類名nextName = pending.next();return true;}
}

創建實例對象,當然,調用next方法的時候,實際調用到的是,lookupIterator.nextService。它通過反射的方式,創建實現類的實例并返回。

private class LazyIterator implements Iterator<S>{private S nextService() {//全限定類名String cn = nextName;nextName = null;//創建類的Class對象Class<?> c = Class.forName(cn, false, loader);//通過newInstance實例化S p = service.cast(c.newInstance());//放入集合,返回實例providers.put(cn, p);return p; }
}

在前面的分析中,Spring Framework一直在致力于解決一個問題,就是如何讓bean的管理變得更簡單,如何讓開發者盡可能的少關注一些基礎化的bean的配置,從而實現自動裝配。所以,所謂的自動裝配,實際上就是如何自動將bean裝載到Ioc容器中來。

實際上在spring 3.x版本中,Enable模塊驅動注解的出現,已經有了一定的自動裝配的雛形,而真正能夠實現這一機制,還是在spirng 4.x版本中,conditional條件注解的出現。我們就來分析SpringBoot 自動裝配到底怎么個事兒?

自動裝配的流程演示

 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency> spring:redis:host: 127.0.0.1 port: 6379@Autowired
private RedisTemplate<String,String>redisTemplate;

按照上面的順序添加starter,然后添加配置,使用RedisTemplate就可以使用了?

為什么RedisTemplate可以被直接注入?而他又是什么時候加入到IOC容器的呢?

這就是自動裝配-->能夠使得ClassPath下依賴的包相關的Bean被自動裝配到Spring IoC容器中。

@EnableAutoConfiguration

EnableAutoConfiguration的主要作用其實就是幫助Spring Boot應用把所有符合條件的@Configuration配置都加載到當前SpringBoot創建并使用的IoC容器中。

再回到EnableAutoConfiguration這個注解中,我們發現它的import是這樣

@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration { ----}

從EnableAutoConfiguration上面的import注解來看,這里面并不是引入另外一個Configuration。而是一個ImportSelector。這個是什么東西呢?

那AutoConfigurationImportSelector是什么?

Enable注解不僅僅可以實現多個Configuration的整合,還可以實現一些復雜的場景,比如可以根據上下文來激活不同類型的bean,@Import注解可以配置三種不同的class

  1. 第一種就是前面演示過的,基于普通bean或者帶有@Configuration的bean進行諸如

  2. 實現ImportSelector接口進行動態注入

  3. 實現ImportBeanDefinitionRegistrar接口進行動態注入

    CacheService
    public class CacheService {
    }
    LoggerService
    public class LoggerService {
    }

    EnableDefineService

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented 
    @Inherited ?//允許被繼承
    @Import({MyDefineImportSelector.class})
    public @interface EnableDefineService {String[] packages() default "";
    }

    MyDefineImportSelector

    public class MyDefineImportSelector implements ImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) {//獲得指定注解的詳細信息。我們可以根據注解中配置的屬性來返回不同的class,//從而可以達到動態開啟不同功能的目的annotationMetadata.getAllAnnotationAttributes(EnableDefineService.class.getName(),true).forEach((k,v) -> {log.info(annotationMetadata.getClassName());log.info("k:{},v:{}",k,String.valueOf(v));});return new String[]{CacheService.class.getName()};}
    }

    EnableDemoTest

    @SpringBootApplication
    @EnableDefineService(name = "dura",value = "dura")
    public class EnableDemoTest {public static void main(String[] args) {ConfigurableApplicationContext ca=SpringApplication.run(EnableDemoTest.class,args);System.out.println(ca.getBean(CacheService.class));System.out.println(ca.getBean(LoggerService.class));}
    }

    了解了Selector的基本原理之后,后續再去分析AutoConfigurationImportSelector的原理就很簡單了,它本質上也是對于bean的動態加載。

    @EnableAutoConfiguration注解的實現原理

    了解了ImportSelector和ImportBeanDefinitionRegistrar后,對于EnableAutoConfiguration的理解就容易一些了

    它會通過import導入第三方提供的bean的配置類:AutoConfigurationImportSelector

    @Import(AutoConfigurationImportSelector.class)

    從名字來看,可以猜到它是基于ImportSelector來實現基于動態bean的加載功能。

    我們知道SpringBoot @Enable*注解的工作原理ImportSelector接口 的selectImports 方法返回的數組(類的全類名)都會被納入到Spring容器中。

    那么可以猜想到這里的實現原理也應該是一樣的,定位到AutoConfigurationImportSelector這個類中的selectImports方法

selectImports
public String[] selectImports(AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return NO_IMPORTS;}
// 從配置文件(spring-autoconfigure-metadata.properties)中加載 AutoConfigurationMetadataAutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
// 獲取所有候選配置類EnableAutoConfigurationAutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
getAutoConfigurationEntry
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return EMPTY_ENTRY;}
//獲取元注解中的屬性AnnotationAttributes attributes = getAttributes(annotationMetadata);
//使用SpringFactoriesLoader 加載classpath路徑下META-INF\spring.factories中,
//key= org.springframework.boot.autoconfigure.EnableAutoConfiguration對應的valueList<String> configurations = getCandidateConfigurations(annotationMetadata,attributes);
//去重configurations = removeDuplicates(configurations);
//應用exclusion屬性Set<String> exclusions = getExclusions(annotationMetadata, attributes);checkExcludedClasses(configurations, exclusions);configurations.removeAll(exclusions);
//過濾,檢查候選配置類上的注解@ConditionalOnClass,如果要求的類不存在,則這個候選類會被過濾不被加載configurations = filter(configurations, autoConfigurationMetadata);//廣播事件
fireAutoConfigurationImportEvents(configurations, exclusions);return new AutoConfigurationEntry(configurations, exclusions);
}

本質上來說,其實EnableAutoConfiguration會幫助SpringBoot應用把所有符合@Configuration配置都加載到當前SpringBoot創建的IoC容器,而這里面借助了Spring框架提供的一個工具類SpringFactoriesLoader的支持,以及用到了Spring提供的條件注解@Conditional,選擇性的針對需要加載的Bean進行條件過濾。

SpringFactoriesLoader

然后,我們先樹立下SpringFactoriesLoader這個由Spring所提供的工具類的用途。

它其實和Java中的SPI機制原理是類似的。只不過是它比SPI更好的一點在于一次性不會加載所有的類,而是根據Key進行加載。

首先,SpringFactoriesLoader的作用是從classpath/META-INF/spring.factories文件中,根據key來加載對應的類到spring IoC容器中。

整體流程如圖

Spring Boot中的條件過濾

在分析AutoConfigurationImportSelector的源碼時,會先掃描spring-autoconfiguration-metadata.properties文件,最后在掃描spring.factories對應的類時,會結合前面的元數據進行過濾,為什么要過濾呢? 原因是很多的@Configuration其實是依托于其他的框架來加載的,如果當前的classpath環境下沒有相關聯的依賴,則意味著這些類沒必要進行加載,所以,通過這種條件過濾可以有效的減少@configuration類的數量從而降低SpringBoot的啟動時間。

自己搓一個Starter來增進對自動裝配的理解

  1. 創建一個Maven項目,quick-starter

定義相關依賴

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><version>2.1.6.RELEASE</version>
</dependency>
<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.56</version><!-- 可選 --><optional>true</optional>
</dependency>
  1. 定義Formate接口

public interface FormatProcessor {/*** 定義一個格式化的方法* @param obj* @param <T>* @return*/<T> String formate(T obj);
}
public class JsonFormatProcessor implements FormatProcessor {@Overridepublic <T> String formate(T obj) {return "JsonFormatProcessor:" + JSON.toJSONString(obj);}
}
public class StringFormatProcessor implements FormatProcessor {@Overridepublic <T> String formate(T obj) {return "StringFormatProcessor:" + obj.toString();}
}

  1. 定義相關的配置類

@Configuration
public class FormatAutoConfiguration {@ConditionalOnMissingClass("com.alibaba.fastjson.JSON")@Bean@Primary // 優先加載public FormatProcessor stringFormatProcessor(){return new StringFormatProcessor();}@ConditionalOnClass(name="com.alibaba.fastjson.JSON")@Beanpublic FormatProcessor jsonFormatProcessor(){return new JsonFormatProcessor();}
}

定義一個模板工具類

public class HelloFormatTemplate {private FormatProcessor formatProcessor;public HelloFormatTemplate(FormatProcessor processor){this.formatProcessor = processor;}public <T> String doFormat(T obj){StringBuilder builder = new StringBuilder();builder.append("Execute format : ").append("<br>");builder.append("Object format result:" ).append(formatProcessor.formate(obj));return builder.toString();}
}

整合到SpringBoot的Java配置類

@Configuration
@Import(FormatAutoConfiguration.class)
public class HelloAutoConfiguration {@Beanpublic HelloFormatTemplate helloFormatTemplate(FormatProcessor formatProcessor){return new HelloFormatTemplate(formatProcessor);}
}
  1. 創建spring.factories文件

在resources下創建META-INF目錄,再在其下創建spring.factories文件

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.dura.autoconfiguration.HelloAutoConfiguration

install 打包,然后就可以在SpringBoot項目中依賴該項目來操作了。

  1. 測試

<dependency><groupId>org.example</groupId><artifactId>format-spring-boot-starter</artifactId><version>1.0-SNAPSHOT</version>
</dependency>@RestController
public class UserController {@Autowiredprivate HelloFormatTemplate helloFormatTemplate;@GetMapping("/format")public String format(){User user = new User();user.setName("BoBo");user.setAge(18);return helloFormatTemplate.doFormat(user);}
}

}

  1. 自定義Starter關聯配置信息

有些情況下我們可以需要用戶在使用的時候動態的傳遞相關的配置信息,比如Redis的Ip,端口等等,這些信息顯然是不能直接寫到代碼中的,這時我們就可以通過SpringBoot的配置類來實現。

 
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><version>2.2.6.RELEASE</version><optional>true</optional>
</dependency>

 
@ConfigurationProperties(prefix = HelloProperties.HELLO_FORMAT_PREFIX)
public class HelloProperties {public static final String HELLO_FORMAT_PREFIX="mashibing.hello.format";private String name;private Integer age;private Map<String,Object> info;public Map<String, Object> getInfo() {return info;}public void setInfo(Map<String, Object> info) {this.info = info;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}
}

然后再Java配置類中關聯

@Configuration
@Import(FormatAutoConfiguration.class)
@EnableConfigurationProperties(HelloProperties.class)
public class HelloAutoConfiguration {@Beanpublic HelloFormatTemplate helloFormatTemplate(HelloProperties helloProperties,FormatProcessor formatProcessor){return new HelloFormatTemplate(helloProperties,formatProcessor);}
}

調整模板方法

public class HelloFormatTemplate {private FormatProcessor formatProcessor;private HelloProperties helloProperties;public HelloFormatTemplate(HelloProperties helloProperties,FormatProcessor processor){this.helloProperties = helloProperties;this.formatProcessor = processor;}public <T> String doFormat(T obj){StringBuilder builder = new StringBuilder();builder.append("Execute format : ").append("<br>");builder.append("HelloProperties:").append(formatProcessor.formate(helloProperties.getInfo())).append("<br>");builder.append("Object format result:" ).append(formatProcessor.formate(obj));return builder.toString();}
}

增加提示在這個工程的META-INF/下創建一個additional-spring-configuration-metadata.json,這個是設置屬性的提示類型

{"properties": [{"name": "dura.hello.format.name","type": "java.lang.String","description": "賬號信息","defaultValue": "root"},{"name": "dura.hello.format.age","type": "java.lang.Integer","description": "年齡","defaultValue": 18}]
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/95073.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/95073.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/95073.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

第三屆機械工程與先進制造智能化技術研討會(MEAMIT2025)

【重要信息】 大會官網&#xff1a;https://www.yanfajia.com/action/p/BYE27DYDhttps://www.yanfajia.com/action/p/BYE27DYD 會議地點&#xff1a;哈爾濱理工大學 論文檢索&#xff1a;EI Compendex、Scopus 還有部份版面&#xff0c;欲投稿從速&#xff0c;即將提交出版…

筆記本電腦頻繁出現 vcomp140.dll丟失怎么辦?結合移動設備特性,提供適配性強的修復方案

對于需要用電腦處理工作的人來說&#xff0c;“vcomp140.dll 丟失” 導致程序頻繁閃退&#xff0c;無疑會嚴重影響工作效率。嘗試重啟電腦、重新安裝軟件后&#xff0c;問題依然存在&#xff0c;這時候該怎么辦&#xff1f;別著急&#xff0c;vcomp140.dll 丟失看似棘手&#x…

微動開關-電競鼠標核心!5000萬次壽命微動開關評測

一、主流電競微動開關技術對比?光磁微動技術?采用非接觸式光學觸發原理理論壽命突破5000萬次觸發響應速度0.2ms??傳統機械微動?歐姆龍D2FC-F-7N系列5000萬次標稱壽命機械結構簡單可靠??創新結構微動?雙飛燕漆藍熒光微動特殊涂層提升耐久性手感反饋獨特?二、5000萬次壽…

Go語言與Docker 開發的核心應用領域

1. 容器化應用構建與部署??輕量化鏡像構建Go 語言編譯生成靜態二進制文件&#xff0c;結合多階段構建的 Dockerfile&#xff0c;可大幅縮小鏡像體積&#xff08;例如使用 scratch 或 alpine 基礎鏡像&#xff09;&#xff0c;提升部署效率?。示例 Dockerfile 片段&#xff1…

【ESP32-IDF】網絡連接開發2:Wi?Fi 智能配網(SmartConfig)

系列文章目錄 持續更新… 文章目錄系列文章目錄前言一、Wi?Fi 智能配網概述1.SmartConfig 簡介2.SmartConfig 工作原理3.SmartConfig 協議類型二、Wi?Fi 智能配網類型定義及相關API三、Wi?Fi 智能配網示例程序總結前言 在物聯網設備開發過程中&#xff0c;如果將 Wi-Fi 的…

CVPR深度學習研究指南:特征提取模塊仍是論文創新難點

關注gongzhonghao【CVPR頂會精選】在深度學習賽道里&#xff0c;別只盯著堆模型卷參數了。最近不少高分工作都在打“可解釋”這張牌&#xff0c;把原本難以理解的黑箱模型用輕量方法剖開&#xff0c;既能增強學術價值&#xff0c;還能拓展落地場景。更妙的是&#xff0c;這類研…

redis----list詳解

列表&#xff08;List&#xff09;相當于數組或者順序表一、通用命令LPUSH key value1 [value2 ...]在列表 key 的左側&#xff08;頭部&#xff09;插入一個或多個值。示例&#xff1a;LPUSH fruits apple banana → 列表變為 [banana, apple]LPUSHX 只有列表已存在時才會執行…

【python】相機輸出圖片時保留時間戳數據

有時候需要參考時間戳&#xff0c;寫個筆記記錄下 但是輸出時間可能不穩&#xff0c;有待進一步優化 import cv2 import time import os# 創建一個保存圖像的文件夾 output_folder "camera_images" if not os.path.exists(output_folder):os.makedirs(output_folder…

(Nginx)基于Nginx+PHP 驅動 Web 應用(上):配置文件與虛擬主機篇

1.應用場景 主要用于學習基于 Nginx PHP 驅動 Web 應用&#xff08;上&#xff09;&#xff1a; 配置文件與虛擬主機篇&#xff0c;學習弄清楚Nginx的常規操作&#xff0c;之前困惑的地方。 本文主要介紹了基于NginxPHP驅動Web應用的配置方法&#xff0c;重點講解了Nginx配置…

【golang長途旅行第34站】網絡編程

網絡編程 基本介紹核心主題&#xff1a;?? Golang面向大規模后端服務程序的設計目標中&#xff0c;網絡通信是必不可少且至關重要的部分。?兩種網絡編程方式&#xff1a;???TCP Socket編程? ?性質&#xff1a;網絡編程的主流 ?底層協議&#xff1a;基于TCP/IP協議 ?舉…

Hadoop(六)

目錄&#xff1a;1.Hadoop概述2.為什么需要分布式存儲3.分布式的基礎架構分析4.HDFS的基礎架構1.Hadoop概述2.為什么需要分布式存儲3.分布式的基礎架構分析4.HDFS的基礎架構

Oracle 12g安裝

1. 下載地址 官方網站 一般這種導向的進入的都是oracle的官方網站(先登錄&#xff0c;如果沒有就創建賬號)&#xff0c;并沒有真實的12g供你下載。需要你轉入Oracle的云中下載&#xff1a;https://edelivery.oracle.com/osdc/faces/SoftwareDelivery 。我選擇的是12.1.0.2.0下…

ros2--service/服務--接口

獲取service名稱const char *get_service_name() const;std::string client_name client_->get_service_name();RCLCPP_INFO(this->get_logger(), "Client name: %s", client_name.c_str());

安卓開發---SimpleAdapter

概念&#xff1a;SimpleAdapter 是 Android 中比 ArrayAdapter 更強大的適配器&#xff0c;用于將復雜的數據綁定到復雜的布局&#xff0c;支持將 Map 中的數據映射到布局中的多個 View。方法簽名&#xff1a;public SimpleAdapter( Context context, //上下文 List<? exte…

軟考-系統架構設計師 辦公自動化系統(OAS)詳細講解

個人博客&#xff1a;blogs.wurp.top 一、OAS的核心概念與演進 1. 什么是OAS&#xff1f; OAS是一個綜合性的信息系統&#xff0c;它利用計算機技術、通信技術、系統科學和行為科學&#xff0c;為組織的日常辦公事務、信息管理和協同工作提供支持。其本質是將傳統辦公流程電…

leetcode 155 官方golang標準答案錯誤

真是誤人子弟&#xff0c;leetcode155題官網的golang答案是錯誤的。push方法的append操作&#xff0c;必然不能保證是o(1)的時間復雜度。就這還是官網的標準答案&#xff0c;就這水平&#xff0c;&#x1f604;leetcode誤人子弟不是第一次了。光會刷算法&#xff0c;可惜水平還…

開源 python 應用 開發(十三)AI應用--百度智能云TTS語音合成

最近有個項目需要做視覺自動化處理的工具&#xff0c;最后選用的軟件為python&#xff0c;剛好這個機會進行系統學習。短時間學習&#xff0c;需要快速開發&#xff0c;所以記錄要點步驟&#xff0c;防止忘記。 鏈接&#xff1a; 開源 python 應用 開發&#xff08;一&#xf…

大白話說 AI 編程 Trae,小白進!

大家好&#xff0c;我是櫻木。 一些小白用戶&#xff0c;打開字節出的 AI 編程工具 Trae 時&#xff0c;可能覺得還是有點生疏&#xff0c;但是作為程序員&#xff0c;看到這樣的界面分布&#xff0c;已經是在熟悉不過了&#xff0c;甚至心中竊喜&#xff0c;長得和 IDEA 等開…

主流國產數據庫:文檔完備性

官方文檔通常是用戶獲取數據庫產品相關信息最權威的渠道&#xff0c;文檔的完備性&#xff08;準確、全面、易用&#xff09;直接影響著開發者的學習成本、項目實施的效率以及后期的運維便利性。 例如&#xff0c;Oracle 數據庫的官方文檔被廣泛認為是行業的黃金標準&#xff…

現今流行的操作系統及其應用場景

2025 年主流操作系統及其應用場景&#xff0c;結合技術趨勢與行業實踐&#xff0c;涵蓋從個人設備到關鍵基礎設施的全場景覆蓋&#xff1a;一、桌面與生產力領域1. Windows 11/12&#xff08;微軟&#xff09;市場地位&#xff1a;全球桌面市場占比 71%&#xff0c;企業級場景市…