告别硬编码:Spring条件注解优雅应对多类场景

一、背景

在当今的软件开发中,服务接口通常需要对应多个实现类,以满足不同的需求和场景。举例来说,假设我们是一家2B公司,公司的产品具备对象存储服务的能力。然而,在不同的合作机构部署时,发现每家公司底层的对象存储服务都不相同,比如机构A使用阿里云,机构B使用AWS S3等。针对这种情况,公司应用底层需要支持多种云存储平台,如阿里云、AWS S3等。

又由于每种云存储平台都拥有独特的API和特性,因此在设计软件时必须考虑到系统的可扩展性。通常情况下,我们会编写一个对外开放的openAPI接口,而应用底层需要根据不同的需求选择合适的实现类。

在这种情况下,如何避免硬编码并以一种优雅的方式实现上述需求成为了本篇博客要讨论的问题。

以下示例均可在 gitHub#inject-condition 仓库上找到。

二、解决方案

由于应用需要对外提供服务,我们以业内常见的Spring Boot服务应用为前提进行讨论。

在这种情况下,常见的解决方案可分为两类:SPI 和 Spring条件注解。

  1. SPI(Service Provider Interface)
    • SPI 是一种标准的Java扩展机制,允许第三方实现提供服务的接口,并由应用程序在运行时动态加载。
    • 在Spring Boot应用中,我们可以定义一个服务接口,然后多个实现类分别实现这个接口。使用SPI机制,我们可以在配置文件中指定想要使用的实现类。
    • 优点:灵活性高,支持动态加载和配置。
    • 缺点:需要手动管理配置文件,并且在服务实现类数量较多时,容易出现配置混乱的问题。
  2. Spring条件注解
    • Spring提供了一系列的条件注解,如@ConditionalOnProperty@ConditionalOnClass等,用于根据应用程序的配置或环境条件来动态地选择加载或配置Bean。
    • 我们可以使用条件注解来根据应用程序的配置来选择合适的实现类。比如,可以根据配置文件中的属性来决定使用哪个实现类。
    • 优点:无需手动管理配置文件,能够根据配置自动选择合适的实现类。
    • 缺点:相比SPI,条件注解的动态加载能力稍逊,使用上稍显复杂,需要了解和掌握Spring的条件注解机制。

综上所述,针对Spring Boot服务应用中服务接口对应多个实现类的需求,我们可以选择SPI或Spring条件注解作为解决方案。

由于SPI已在另一篇博客中有详细讲解,本文将重点讲解Spring条件注解。更多关于SPI的内容可参考笔者的另一篇博客:Java SPI解读:揭秘服务提供接口的设计与应用

三、示例

3.1、场景模拟

  1. 在应用中新建一个ObjectStorageService存储接口,代码如下:
import java.io.File;public interface ObjectStorageService {/*** 上传文件到对象存储* @param file 文件* @param bucketName 存储桶名称* @param objectKey 对象键(文件名)* @return 文件在对象存储中的URL*/String uploadObject(File file, String bucketName, String objectKey);/*** 从对象存储下载文件* @param bucketName 存储桶名称* @param objectKey 对象键(文件名)* @return 文件*/File downloadObject(String bucketName, String objectKey);
}
  1. 接下来,我们创建了三个通过@Service注入的实现类。首先是默认实现类DefaultObjectStorageServiceImpl,其次是阿里云存储服务的实现类AliyunObjectStorageServiceImpl,最后是S3存储服务的实现类S3ObjectStorageServiceImpl。具体的代码实现:
@Slf4j
@Service
public class DefaultObjectStorageServiceImpl implements ObjectStorageService {@Overridepublic String uploadObject(File file, String bucketName, String objectKey) {// 默认实现上传逻辑return "Default implementation: Upload successful";}@Overridepublic File downloadObject(String bucketName, String objectKey) {// 默认实现下载逻辑return new File("default-file.txt");}
}
@Slf4j
@Service
public class AliyunObjectStorageServiceImpl implements ObjectStorageService {@Overridepublic String uploadObject(File file, String bucketName, String objectKey) {// 阿里云实现上传逻辑return "Aliyun implementation: Upload successful";}@Overridepublic File downloadObject(String bucketName, String objectKey) {// 阿里云实现下载逻辑return new File("aliyun-file.txt");}
}
@Slf4j
@Service
public class S3ObjectStorageServiceImpl implements ObjectStorageService {@Overridepublic String uploadObject(File file, String bucketName, String objectKey) {// S3实现上传逻辑return "S3 implementation: Upload successful";}@Overridepublic File downloadObject(String bucketName, String objectKey) {// S3实现下载逻辑return new File("s3-file.txt");}
}
  1. 最后再创建一个Controller类通过@Autowired注解注入ObjectStorageService,并对外开放接口,代码如下:
@Slf4j
@RestController
public class StorageController {@Autowiredprivate ObjectStorageService objectStorageService;@GetMapping("/example")public void example() {log.info("objectStorageService: {}", objectStorageService);}
}
  1. 此时运行应用报错信息如下:
***************************
APPLICATION FAILED TO START
***************************Description:Field objectStorageService in org.example.inject.web.controller.StorageController required a single bean, but 3 were found:- aliyunObjectStorageServiceImpl: defined in file [D:\IdeaProjects\inject-examples\inject-condition\target\classes\org\example\inject\web\service\impl\AliyunObjectStorageServiceImpl.class]- defaultObjectStorageServiceImpl: defined in file [D:\IdeaProjects\inject-examples\inject-condition\target\classes\org\example\inject\web\service\impl\DefaultObjectStorageServiceImpl.class]- s3ObjectStorageServiceImpl: defined in file [D:\IdeaProjects\inject-examples\inject-condition\target\classes\org\example\inject\web\service\impl\S3ObjectStorageServiceImpl.class]Action:Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed

错误提示StorageController需要一个objectStorageService bean,但是却找到了3个可用的bean:aliyunObjectStorageServiceImpldefaultObjectStorageServiceImpls3ObjectStorageServiceImpl。spring也提示了解决方案:

  1. 在其中一个实现类上添加@Primary注解,指示Spring优先选择这个bean。
  2. 修改StorageController以接受多个objectStorageService,或者使用@Qualifier注解指定要注入的特定bean。

3.2、@Qualifier解决方案

  1. @Autowired是Spring2.5 引入的注解,@Autowired 注解只根据类型进行注入,不会根据名称匹配。当类型无法辨别注入对象时,可以使用 @Qualifier@Primary 注解来修饰,修改后代码如下:
@Slf4j
@RestController
public class StorageController {@Autowired@Qualifier("aliyunObjectStorageServiceImpl")private ObjectStorageService objectStorageService;@GetMapping("/example")public void example() {log.info("objectStorageService: {}", objectStorageService);}
}

@Qualifier注解中的参数是BeanID,即@Service注解所注入的实现类的名称。

  1. 运行应用后一切正常,命令行输入: curl http://localhost:8080/example,日志打印:注入成功
objectStorageService: org.example.inject.condition.service.impl.AliyunObjectStorageServiceImpl@6f2aa58b
  1. 遗憾的是,@Qualifier注解并不支持变量赋值,只能通过硬编码的方式指定具体的实现类。下面是一个错误示例:
@Slf4j
@RestController
public class StorageController {@Value("${storage.provider}")private String storageProvider;@Autowired@Qualifier("${storageProvider}")private ObjectStorageService objectStorageService;@GetMapping("/example")public void example() {log.info("objectStorageService: {}", objectStorageService);}
}

虽然我们希望通过配置变量的方式来指定具体的实现类,但是由于@Qualifier注解的限制,这种方案并不可行,因此不推荐使用。

3.3、@Resource解决方案

  1. 在Spring Boot应用中,除了@Autowired,还可以使用@Resource来进行依赖注入,代码如下:
import javax.annotation.Resource;@Slf4j
@RestController
public class StorageController {//  @Autowired@Resourceprivate ObjectStorageService objectStorageService;@GetMapping("/example")public void example() {log.info("objectStorageService: {}", objectStorageService);}
}
  1. @Resource@Autowired区别在于:

    • @Resource 是 JDK 原生的注解,而 @Autowired 是 Spring 2.5 引入的注解。

    • @Resource 注解有两个属性:nametype。Spring 将 @Resource 注解的 name 属性解析为 bean 的名称,而 type 属性则解析为 bean 的类型。因此,如果使用 name 属性,则采用 byName 的自动注入策略;如果使用 type 属性,则采用 byType 的自动注入策略。如果既不指定 name 也不指定 type 属性,则将通过反射机制使用 byName 自动注入策略。

    • @Autowired 注解只根据类型进行注入,不会根据名称匹配。当类型无法辨别注入对象时,可以使用 @Qualifier@Primary 注解来修饰。

  2. 所以我们可以通过@Resource注解指定name属性从而实现指定实现类注入,代码如下:

@Slf4j
@RestController
public class StorageController {//  @Autowired@Resource(name = "aliyunObjectStorageServiceImpl")private ObjectStorageService objectStorageService;@GetMapping("/example")public void example() {log.info("objectStorageService: {}", objectStorageService);}
}
  1. 运行应用后一切正常,命令行输入: curl http://localhost:8080/example,日志打印:注入成功
objectStorageService: org.example.inject.condition.service.impl.AliyunObjectStorageServiceImpl@6f2aa58b
  1. 遗憾的是,@Resource注解也不支持变量赋值,只能通过硬编码的方式指定具体的实现类,因此不推荐使用。

3.4、@Primary解决方案

  1. @Primary 是一个 Spring 框架中的注解,用于解决多个 Bean 实例同一类型的自动装配问题。当一个接口或者类有多个实现时,Spring 在自动装配时可能会出现歧义,不知道选择哪个 Bean 注入。这时候,可以使用 @Primary 注解来指定首选的 Bean,这样在自动装配时就会选择这个首选的 Bean。

  2. DefaultObjectStorageServiceImpl设置为首选实现类,代码如下:

import org.springframework.context.annotation.Primary;@Slf4j
@Service
@Primary
public class DefaultObjectStorageServiceImpl implements ObjectStorageService {@Overridepublic String uploadObject(File file, String bucketName, String objectKey) {// 默认实现上传逻辑return "Default implementation: Upload successful";}@Overridepublic File downloadObject(String bucketName, String objectKey) {// 默认实现下载逻辑return new File("default-file.txt");}
}
  1. StorageController控制层恢复为最初形态,代码如下:
@Slf4j
@RestController
public class StorageController {@Autowiredprivate ObjectStorageService objectStorageService;@GetMapping("/example")public void example() {log.info("objectStorageService: {}", objectStorageService);}
}
  1. 运行应用,命令行输入: curl http://localhost:8080/example,日志打印:默认实现类注入成功

    objectStorageService: org.example.inject.condition.service.impl.DefaultObjectStorageServiceImpl@633df06
    
  2. 遗憾的是,@Primary注解也是只能通过硬编码的方式指定具体的实现类,因此不推荐使用。

3.5、@Conditional解决方案[推荐]

  1. @Conditional 注解是 Spring 框架提供的一种条件化装配的机制,它可以根据特定的条件来决定是否创建一个 Bean 实例。通过 @Conditional 注解,可以在 Spring 容器启动时根据一些条件来动态地确定是否创建某个 Bean,从而实现更灵活的 Bean 装配。

  2. 在 Spring 中,有一系列内置的条件注解,例如:

    • @ConditionalOnClass:当类路径中存在指定的类时,才创建该 Bean。
    • @ConditionalOnMissingClass:当类路径中不存在指定的类时,才创建该 Bean。
    • @ConditionalOnBean:当容器中存在指定的 Bean 时,才创建该 Bean。
    • @ConditionalOnMissingBean:当容器中不存在指定的 Bean 时,才创建该 Bean。
    • @ConditionalOnProperty:当指定的配置属性满足一定条件时,才创建该 Bean。
    • @ConditionalOnExpression:当指定的 SpEL 表达式为 true 时,才创建该 Bean。
  3. 我们希望达到的效果是通过application.propertiesapplication.yml配置文件的一个配置项就可以指定具体实现类,而非通过硬编码的形式来实现,所以我们将使用@ConditionalOnProperty配置属性条件注解实现。其余注解可参考:官网介绍

  4. 先看下@ConditionalOnProperty注解的几个入参介绍:

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE, ElementType.METHOD})
    @Documented
    @Conditional({OnPropertyCondition.class})
    public @interface ConditionalOnProperty {/*** 配置文件中 key 的前缀,可与 value 或 name 组合使用。*/String prefix() default "";/*** 与 value 作用相同,但不能与 value 同时使用。*/String[] name() default {};/*** 与 value 或 name 组合使用,只有当 value 或 name 对应的值与 havingValue 的值相同时,注入生效。*/String havingValue() default "";/*** 当该属性为 true 时,配置文件中缺少对应的 value 或 name 的属性值,也会注入成功。*/boolean matchIfMissing() default false;
    }
    
  5. 接下来定义配置key,在application.propertiesapplication.yml配置文件新增如下内容:

    storage.provider=aliyun
    
  6. 在各个实现类中新增@ConditionalOnProperty注解,代码如下:

    import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;@Slf4j
    @Service
    //@Primary
    @ConditionalOnProperty(prefix = "storage", name = "provider", havingValue = "default", matchIfMissing = true)
    public class DefaultObjectStorageServiceImpl implements ObjectStorageService {// 省略
    }@Slf4j
    @Service
    @ConditionalOnProperty(prefix = "storage", name = "provider", havingValue = "aliyun")
    public class AliyunObjectStorageServiceImpl implements ObjectStorageService {// 省略
    }@Slf4j
    @Service
    @ConditionalOnProperty(prefix = "storage", name = "provider", havingValue = "s3")
    public class S3ObjectStorageServiceImpl implements ObjectStorageService {// 省略
    }
    
  7. 运行应用,命令行输入: curl http://localhost:8080/example,日志打印:

    objectStorageService: org.example.inject.condition.service.impl.AliyunObjectStorageServiceImpl@3b46e282
    
  8. 如果在 application.propertiesapplication.yml 配置文件中没有配置 storage.provider 属性,则会注入 DefaultObjectStorageServiceImpl 实现类。这是因为 DefaultObjectStorageServiceImpl 实现类的 matchIfMissing = true 属性已经指定了。

  9. 上述注解的实现方式是配置在每个实现类中,这种方式过于分散。为了让开发人员更清晰地了解应用的注入关系,我们应该通过 @Configuration 整合所有实现类的配置。以下是新增的 WebConfiguration 配置类的代码:

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;/*** 自动装配类*/
    @Configuration
    public class WebConfiguration {@Bean@ConditionalOnProperty(prefix = "storage", name = "provider", havingValue = "default", matchIfMissing = true)public ObjectStorageService defaultObjectStorageServiceImpl() {return new DefaultObjectStorageServiceImpl();}@Bean@ConditionalOnProperty(prefix = "storage", name = "provider", havingValue = "aliyun")public ObjectStorageService aliyunObjectStorageServiceImpl() {return new AliyunObjectStorageServiceImpl();}@Bean@ConditionalOnProperty(prefix = "storage", name = "provider", havingValue = "s3")public ObjectStorageService s3ObjectStorageServiceImpl() {return new S3ObjectStorageServiceImpl();}
    }
    

    再将各个实现类中的@Service,@ConditionalOnProperty注解去掉,更改后代码如下:

    import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;@Slf4j
    public class DefaultObjectStorageServiceImpl implements ObjectStorageService {// 省略
    }@Slf4j
    public class AliyunObjectStorageServiceImpl implements ObjectStorageService {// 省略
    }@Slf4j
    public class S3ObjectStorageServiceImpl implements ObjectStorageService {// 省略
    }
    

    运行应用,命令行输入: curl http://localhost:8080/example,日志打印:

    objectStorageService: org.example.inject.condition.service.impl.AliyunObjectStorageServiceImpl@3b46e282
    
  10. 通过 @ConditionalOnProperty 注解和 WebConfiguration 统一装配类,我们基本实现了可配置化注入实现类的方案,初步实现了我们的目标。

3.6、自定义@Conditional解决方案[强烈推荐]

在上面的示例中,我们是通过在配置文件中定义属性来决定实现类,这需要在配置文件中定义一份属性,并在各个 @ConditionalOnProperty 注解中配置 prefixname 属性。以前面的示例为例,就需要进行4次配置。然而,这种方式容易出错,特别是当服务有多个接口需要配置多个实现类时,需要配置更多的属性,增加了配置的复杂性和出错的可能性,如下图所示:

在这里插入图片描述

根据上图中的三个接口,需要配置三个配置项以及7次 @ConditionalOnProperty 注解;因此,我们需要采用一种简化的方式来减少配置,只需要在配置文件中配置一次即可,而无需更改@ConditionalOnProperty 注解。

  1. 要满足上述需求,首先需要重点关注配置文件中的属性。以上面的对象存储的情景举例,一个重要的配置项是storage.provider=aliyun。为了更通用地解决所有接口的配置需求,建议统一将配置项命名为接口的全限定名。这种做法不仅能够确保配置项的唯一性,同时也让人一目了然,清晰明了。以上面对象存储场景为例,修改后的配置如下所示:

    org.example.inject.condition.service.ObjectStorageService=aliyun
    
  2. 其次希望简化@ConditionalOnProperty注解的编写,不再需要指定prefix = "storage", name = "provider"等属性。而是根据注解所在位置自动分析当前返回值类的全限定名称,然后直接从配置文件中读取相应的配置项。示例如下:

    @Bean
    @ConditionalOnProperty(name = ObjectStorageService.class, matchIfMissing = true)
    public ObjectStorageService defaultObjectStorageServiceImpl() {return new DefaultObjectStorageServiceImpl();
    }@Bean
    @ConditionalOnProperty(name = ObjectStorageService.class, havingValue = "aliyun")
    public ObjectStorageService aliyunObjectStorageServiceImpl() {return new AliyunObjectStorageServiceImpl();
    }@Bean
    @ConditionalOnProperty(name = ObjectStorageService.class, havingValue = "s3")
    public ObjectStorageService s3ObjectStorageServiceImpl() {return new S3ObjectStorageServiceImpl();
    }
    

    可以观察到,除了需要配置havingValue属性外,其他配置项无需手动设置,使得配置变得十分简洁。

  3. 注意,目前Spring并未提供类似的能力来实现我们需要的条件判断,因此我们需要自定义条件注解。幸运的是,Spring 提供了条件接口,让我们可以自行创建自定义的条件类来实现所需的条件判断逻辑。首先,我们创建一个自定义条件类,它继承Condition接口,并编写自定义的条件判断逻辑。代码如下:

    import org.springframework.context.annotation.Condition;
    import org.springframework.context.annotation.ConditionContext;
    import org.springframework.core.type.AnnotatedTypeMetadata;
    import org.springframework.util.StringUtils;/*** 自定义的条件判断类,用于根据指定类名的配置值判断是否应用某个配置。*/
    public class ConditionalOnClassNameCustom implements Condition {/*** 判断是否满足条件。** @param context  条件上下文* @param metadata 注解元数据* @return 如果满足条件,则返回true;否则返回false*/@Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {// 获取ConditionalOnClassName注解的属性值Class<?>[] annotationValues = (Class<?>[]) metadata.getAnnotationAttributes(ConditionalOnClassName.class.getName()).get("name");String annotationClassName = annotationValues[0].getName(); // 获取类的全限定名String havingValue = (String) metadata.getAnnotationAttributes(ConditionalOnClassName.class.getName()).get("havingValue");boolean matchIfMissing = (boolean) metadata.getAnnotationAttributes(ConditionalOnClassName.class.getName()).get("matchIfMissing");// 获取配置项对应的配置值String propertyValue = context.getEnvironment().getProperty(annotationClassName);// 检查配置值是否符合预期if (StringUtils.hasText(propertyValue)) {return havingValue.equals(propertyValue);} else {return matchIfMissing;}}
    }
    
  4. 借助这个条件判断逻辑,我们接下来设计一个全新的条件配置注解:ConditionalOnClassName,它将使用前述的ConditionalOnClassNameCustom实现类。具体代码如下:

    import org.springframework.context.annotation.Conditional;import java.lang.annotation.*;/*** 定义一个自定义条件注解,用于根据指定类名的配置值判断是否应用某个配置。*/
    @Target({ ElementType.TYPE, ElementType.METHOD }) // 注解可以应用于类和方法
    @Retention(RetentionPolicy.RUNTIME) // 注解会在运行时保留
    @Documented // 注解会被包含在javadoc中
    @Conditional(ConditionalOnClassNameCustom.class) // 该注解条件受到 ConditionalOnClassNameCustom 类的限制
    public @interface ConditionalOnClassName {Class<?>[] value() default {}; // 作为 value 属性的别名,用于更简洁地指定需要检查的类Class<?>[] name(); // 需要检查的类的全限定名数组String havingValue() default "default"; // 期望的配置值,默认为 "default"boolean matchIfMissing() default false; // 如果配置值缺失是否匹配,默认为 false
    }
    
  5. 完成了上述准备工作后,接下来是验证新创建的注解。我们需要修改WebConfiguration配置类。代码如下:

    /*** 自动装配类*/
    @Configuration
    public class WebConfiguration {@Bean@ConditionalOnClassName(name = ObjectStorageService.class, matchIfMissing = true)public ObjectStorageService defaultObjectStorageServiceImpl() {return new DefaultObjectStorageServiceImpl();}@Bean@ConditionalOnClassName(name = ObjectStorageService.class, havingValue = "aliyun")public ObjectStorageService aliyunObjectStorageServiceImpl() {return new AliyunObjectStorageServiceImpl();}@Bean@ConditionalOnClassName(name = ObjectStorageService.class, havingValue = "s3")public ObjectStorageService s3ObjectStorageServiceImpl() {return new S3ObjectStorageServiceImpl();}
    }
    
  6. 接下来定义配置key,在application.propertiesapplication.yml配置文件新增如下内容:

    org.example.inject.condition.service.ObjectStorageService=aliyun
    
  7. 运行应用,命令行输入: curl http://localhost:8080/example,日志打印:

    objectStorageService: org.example.inject.condition.service.impl.AliyunObjectStorageServiceImpl@4cf4e0a
    

在这个示例中,我们利用自定义条件注解简化了@ConditionalOnProperty注解的配置,同时统一了配置文件属性命名,实现了一次配置多处使用。这种优化提高了配置的简洁性和可维护性,同时减少了配置的复杂度和错误可能性。

四、总结

本文通过自定义条件注解,简化了@ConditionalOnProperty注解的配置,同时统一了配置文件属性命名。这一优化方案提高了系统的可维护性和稳定性。以往的配置模式需要在不同的类或方法上重复配置属性的前缀和名称,容易出错且繁琐。通过优化后的方案,只需在配置文件中一次性配置,即可在多处重复使用,简化了配置过程。这种优化提高了开发效率,降低了配置错误的风险,尤其适用于大型项目。

总的来说,通过自定义条件注解来简化配置,统一配置文件属性命名,是一种非常实用的优化方案。它不仅提高了系统的可维护性和稳定性,还能够提升开发效率,减少配置错误的可能性,是服务开发中值得推广的实践之一。

五、相关资料

  • Java SPI解读:揭秘服务提供接口的设计与应用
  • Spring条件注解官网介绍
  • 产品SDK化转型:标准化与机构个性化定制解决方案

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/14310.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RedisTemplateAPI:List

文章目录 ⛄介绍⛄List的常见命令有⛄RedisTemplate API❄️❄️添加缓存❄️❄️将List放入缓存❄️❄️设置过期时间(单独设置)❄️❄️获取List缓存全部内容&#xff08;起始索引&#xff0c;结束索引&#xff09;❄️❄️从左或从右弹出一个元素❄️❄️根据索引查询元素❄…

探索 Rust 语言的精髓:深入 Rust 标准库

探索 Rust 语言的精髓&#xff1a;深入 Rust 标准库 Rust&#xff0c;这门现代编程语言以其内存安全、并发性和性能优势而闻名。它不仅在系统编程领域展现出强大的能力&#xff0c;也越来越多地被应用于WebAssembly、嵌入式系统、分布式服务等众多领域。Rust 的成功&#xff0…

Day25:Leetcode:669. 修剪二叉搜索树 + 108.将有序数组转换为二叉搜索树 + 538.把二叉搜索树转换为累加树

LeetCode&#xff1a;669. 修剪二叉搜索树 问题描述 解决方案&#xff1a; 1.思路 2.代码实现 class Solution {public TreeNode trimBST(TreeNode root, int low, int high) {if (root null) {return null;}if (root.val < low) {return trimBST(root.right, low, hi…

Nginx文件解析漏洞复现:CVE-2013-4547

漏洞原理 CVE-2013-4547漏洞是由于非法字符空格和截止符导致Nginx在解析URL时的有限状态机混乱&#xff0c;导致攻击者可以通过一个非编码空格绕过后缀名限制。假设服务器中存在文件1. jpg&#xff0c;则可以通过改包访问让服务器认为访问的为PHP文件。 漏洞复现 开启靶场 …

联盟 | 歌者 AIPPT X HelpLook携手,开启企业高效办公新时代

面对日益增长的工作负荷和追求效率优化的压力&#xff0c;企业知识的积累与传播显得愈发重要。如何系统化地沉淀员工与企业的知识精华&#xff1f;如何快速分享内外部知识&#xff1f;更重要的是&#xff0c;如何在获取这些知识后&#xff0c;迅速将其转化为精美的PPT&#xff…

人类交互3 皮肤感觉与运动系统

皮肤感觉概述 皮肤是人体最大的器官之一&#xff0c;具有多种感觉功能&#xff0c;包括&#xff1a; 触觉&#xff1a;通过触觉&#xff0c;我们能感知物体的形状、质地&#xff0c;帮助我们与外界环境进行互动和感知周围物体的特征。 热觉&#xff1a;热觉使我们能感知周围环…

哪款电脑桌面日历记事本软件好用?推荐优秀的电脑日历记事本

对于众多上班族而言&#xff0c;每天在电脑前忙碌已成为生活常态。若想提升工作效率&#xff0c;简化繁琐的工作流程&#xff0c;选择一款出色的电脑桌面日历与记事本软件就显得至关重要。 然而&#xff0c;在Windows操作系统上设定提醒显得相当繁琐&#xff0c;而系统自带的记…

Discourse 中可能使用的 HMAC 算法 Java 实现

在 DiscourseConnect 中&#xff0c;对数据的签名使用的是 HMAC 算法。 实际使用的算法为 HmacSHA256。 Java 生成签名的方法很简单。 String hmac new HmacUtils(HmacAlgorithms.HMAC_SHA_256, "55619458534897682511405307018226").hmacHex(ssoPayload);HmacUti…

工程项目核算报价-项目CPQ报价系统控成本高效完成工程项目报价

首先了解一下CPQ报价如何解决工程项目报价难的? 目前市场上的工程项目报价方案制作效率低&#xff0c;易出错&#xff0c;反复修改&#xff0c;成本核算的过程不够严谨&#xff0c;凭以经验和数据大差不差的估算当下项目&#xff0c;报价过程中会忽略侧面因素&#xff0c;导致…

Elasticsearch 分析器的高级用法二(停用词,拼音搜索)

Elasticsearch 分析器的高级用法二&#xff08;停用词&#xff0c;拼音搜索&#xff09; 停用词简介停用词分词过滤器自定义停用词分词过滤器内置分析器的停用词过滤器注意&#xff0c;有一个细节 拼音搜索安装使用相关配置 停用词 简介 停用词是指&#xff0c;在被分词后的词…

uwsgi状态监控

使用 uWSGI 内置的状态服务器 uWSGI 提供了一个内置的状态服务器&#xff0c;你可以通过配置 uWSGI 来启用它&#xff0c;并使用 Web 浏览器或者通过 HTTP 请求来查看 uWSGI 的状态信息。 启用状态服务器 在 uWSGI 的配置文件中添加以下配置&#xff1a; [uwsgi] ... sta…

TTS相关

文章目录 VALL-E-X简介code vist论文解读代码解读模块loss代码 valle名词解释 VALL-E-X 简介 微软VALL-E-X&#xff1a;夸克在用 可以预训练模型 端到端 code code&#xff1a;https://github.com/Plachtaa/VALL-E-X/tree/master 报错1: File "/mnt/TTS/VALL-E-X/tes…

pip如何快速install packet

1、在后面加-i https://mirrors.aliyun.com//pypi//simple或https://pypi.tuna.tsinghua.edu.cn/simple pip install numpy -i https://mirrors.aliyun.com//pypi//simplepip install numpy1.21.0 -i https://pypi.tuna.tsinghua.edu.cn/simple2、需要注意的是&#xff0c;如果…

Langchain:数据连接封装、缓存封装和LCEL学习和探索

&#x1f335; 目录 &#x1f335; &#x1f60b; 数据连接封装 &#x1f354; 文档加载器&#xff1a;Document Loaders 文档处理器&#xff1a;TextSplitter 向量数据库与向量检索 总结 &#x1f349; 缓存封装&#xff1a;Memory &#x1f3d6;️ 对话上下文&#xf…

上位机图像处理和嵌入式模块部署(mcu之芯片选择)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 目前市面上的mcu很多&#xff0c;有国产的&#xff0c;有进口的&#xff0c;总之种类很多。以stm32为例&#xff0c;这里面又包括了stm32f1、stm32…

热爱无解 少年万丈光芒!首席艺人【彭禹锦】登陆第八季完美童模全球赛

2024年7月&#xff0c;一档由IPA模特委员会创办于2017年的王牌少儿模特大赛即将拉开全球总决赛的帷幕!作为家喻户晓的国民赛事——完美童模曾6季荣获CCTV央视新闻报道&#xff0c;以创意引领、美学引领、和兼具文化底蕴的赛事特色&#xff0c;收获了全球百万亲子家庭的喜爱。20…

深度学习之基于Pytorch+Flask Web框架预测手写数字

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景与意义 随着人工智能和深度学习的快速发展&#xff0c;手写数字识别已成为一个重要的应用领域。该项目…

Python 实现批量文件重命名工具

在现代软件开发中&#xff0c;图形用户界面 (GUI) 工具的创建是一个常见需求。对于那些需要频繁处理文件的任务&#xff0c;拥有一个简便的 GUI 工具尤为重要。在这篇博客中&#xff0c;我们将介绍如何使用 wxPython 创建一个简单的批量文件重命名工具。该工具可以选择一个文件…

Web开发——HTMLCSS

1、概述 Web开发分前端开发和后端开发&#xff0c;前端开发负责展示数据&#xff0c;后端开发负责处理数据。 HTML&CSS是浏览器数据展示相关的内容。 1&#xff09;网页的组成部分 文字、图片、音频、视频、超链接、表格等等 2&#xff09;网页背后的本质 程序员写的前端…

重大活动网络安全保障建设及运营指南

在当今高度数字化的社会中&#xff0c;各类重大活动如会议、展览、赛事及庆典等正面临着日益复杂和严峻的网络安全威胁。这些威胁不限于网络入侵或数据泄露&#xff0c;更涉及到对基础设施、关键信息系统和公众舆论的复杂攻击&#xff0c;需要国际社会的密切合作和长期关注。因…