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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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;企业级场景市…