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

news2024/11/15 20:38:27

一、背景

在当今的软件开发中,服务接口通常需要对应多个实现类,以满足不同的需求和场景。举例来说,假设我们是一家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 {
    @Override
    public String uploadObject(File file, String bucketName, String objectKey) {
        // 默认实现上传逻辑
        return "Default implementation: Upload successful";
    }

    @Override
    public File downloadObject(String bucketName, String objectKey) {
        // 默认实现下载逻辑
        return new File("default-file.txt");
    }
}
@Slf4j
@Service
public class AliyunObjectStorageServiceImpl implements ObjectStorageService {
    @Override
    public String uploadObject(File file, String bucketName, String objectKey) {
        // 阿里云实现上传逻辑
        return "Aliyun implementation: Upload successful";
    }

    @Override
    public File downloadObject(String bucketName, String objectKey) {
        // 阿里云实现下载逻辑
        return new File("aliyun-file.txt");
    }
}
@Slf4j
@Service
public class S3ObjectStorageServiceImpl implements ObjectStorageService {
    @Override
    public String uploadObject(File file, String bucketName, String objectKey) {
        // S3实现上传逻辑
        return "S3 implementation: Upload successful";
    }

    @Override
    public File downloadObject(String bucketName, String objectKey) {
        // S3实现下载逻辑
        return new File("s3-file.txt");
    }
}
  1. 最后再创建一个Controller类通过@Autowired注解注入ObjectStorageService,并对外开放接口,代码如下:
@Slf4j
@RestController
public class StorageController {
    @Autowired
    private 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
    @Resource
    private 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 {

    @Override
    public String uploadObject(File file, String bucketName, String objectKey) {
        // 默认实现上传逻辑
        return "Default implementation: Upload successful";
    }

    @Override
    public File downloadObject(String bucketName, String objectKey) {
        // 默认实现下载逻辑
        return new File("default-file.txt");
    }
}
  1. StorageController控制层恢复为最初形态,代码如下:
@Slf4j
@RestController
public class StorageController {

    @Autowired
    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.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
         */
        @Override
        public 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.coloradmin.cn/o/1685479.html

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

相关文章

每天五分钟深度学习框架PyTorch:创建具有特殊值的tensor张量

本文重点 tensor张量是一个多维数组,本节课程我们将学习一些pytorch中已经封装好的方法,使用这些方法我们可以快速创建出具有特殊意义的tensor张量。 创建一个值为空的张量 import torch import numpy as np a=torch.empty(1) print(a) print(a.dim()) print(s.shape) 如图…

python3如何查看是32位还是64位

在安装一些python的软件包时&#xff0c;经常安装错误&#xff0c;可能是跟python的位数有关系。 下面告诉大家如何查看python的位数。 第一种方法&#xff1a;通过在cmd中输入“python”即可。 第二种方法&#xff1a;通过platform包查看&#xff0c;首先导入platform包&…

Nginx企业级负载均衡:技术详解系列(11)—— 实战一机多站部署技巧

你好&#xff0c;我是赵兴晨&#xff0c;97年文科程序员。 工作中你是否遇到过这种情况&#xff1a;公司业务拓展&#xff0c;新增一个域名&#xff0c;但服务器资源有限&#xff0c;只能跟原有的网站共用同一台Nginx服务器。 也就是说两个网站的域名都指向同一台Nginx服务器…

金融信贷风控基础知识

一、所谓风控(What && Why) 所谓风控&#xff0c;可以拆解从2个方面看&#xff0c;即 风险和控制 风险(what) 风险 这里狭隘的特指互联网产品中存在的风险点&#xff0c;例如 账户风险 垃圾注册账号账号被泄露盗用 交易支付风险 刷单&#xff1a;为提升卖家店铺人气…

小程序-滚动触底-页面列表数据无限加载

// index/index.vue <template> <!-- 自定义导航栏 --> <CustomNavbar /> <scroll-view scrolltolower"onScrolltolower" scroll-y class"scroll-view"> <!-- 猜你喜欢 --> <Guess ref"guessRef" /> </s…

利用Python队列生产者消费者模式构建高效爬虫

目录 一、引言 二、生产者消费者模式概述 三、Python中的队列实现 四、生产者消费者模式在爬虫中的应用 五、实例分析 生产者类&#xff08;Producer&#xff09; 消费者类&#xff08;Consumer&#xff09; 主程序 六、总结 一、引言 随着互联网的发展&#xff0c;信…

利用Anaconda+Pycharm配置PyTorch完整过程

说在前面&#xff1a;这篇是记录贴&#xff0c;因为被配置环境折磨了大半天&#xff0c;所以记录下来下次方便配置&#xff0c;有点像流水账&#xff0c;有不懂的地方可以评论问。 参考文章&#xff1a; https://blog.csdn.net/m0_48609250/article/details/129402319 环境&…

Android:使用Kotlin搭建MVC架构模式

一、简介Android MVC架构模式 M 层 model &#xff0c;负责处理数据&#xff0c;例如网络请求、数据变化 V 层 对应的是布局 C 层 Controller&#xff0c; 对应的是Activity&#xff0c;处理业务逻辑&#xff0c;包含V层的事情&#xff0c;还会做其他的事情&#xff0c;导致 ac…

ChineseOcr Lite Ncnn:高效轻量级中文OCR工具

目录结构 前言opencv编译编译命令编译结果 ncnn设置OcrLiteNcnn编译OcrLiteNcnn1.8.0源码下载OcrLiteNcnn1.8.0源码编译 OCR图片文本识别测试编译文件测试命令编译文件测试输出 模型下载相关链接 前言 ChineseOcr Lite Ncnn&#xff0c;超轻量级中文OCR PC Demo&#xff0c;支…

AI率怎么降低?有哪些论文降重降AI率的工具和方法?

关于aigc降重怎么降重&#xff1f;论文降重有哪些方法&#xff1f;有没有好用的降重软件&#xff1f;网上很多大神都有回答&#xff0c;但是最近还是会有很多学弟学妹会问这些问题&#xff01; 有没有发现论文降重像玄学一样复杂&#xff1f;最近刚完成一篇论文&#xff0c;使…

Python数据可视化(五)

实现GUI效果 借助 matplotlib&#xff0c;除可以绘制动画内容外&#xff0c;还可以实现用户图形界面的效果&#xff0c;也就是 GUI 效果。 GUI是用户使用界面的英文单词首字母的缩写。接下来&#xff0c;我们就以模块widgets中的类RadioButtons、 Cursor 和 CheckButtons 的使用…

说说什么是AOP,以及AOP的具体实现场景(外卖中应用)

推荐B站&#xff1a;【Spring AOP】实际开发中到底有什么用&#xff1f;_哔哩哔哩_bilibili 一、AOP的原理 AOP即Aspect Oriented Program&#xff0c;面向切面编程&#xff0c;是面向对象编程(OOP)的一种增强模式&#xff0c;可以将项目中与业务无关的&#xff0c;却为业务模…

Spark-广播变量详解

Spark概述 Spark-RDD概述 1.为什么会需要广播变量&#xff1f; 广播变量是为了在分布式计算环境中有效地向集群中的所有节点广播大型只读数据集而设计的。 在分布式环境中&#xff0c;通常会遇到需要在所有节点上使用相同的数据集的情况&#xff0c;但是将这些数据集复制到每个…

以及Spring中为什么会出现IOC容器?@Autowired和@Resource注解?

以及Spring中为什么会出现IOC容器&#xff1f;Autowired和Resource注解&#xff1f; IOC容器发展史 没有IOC容器之前 首先说一下在Spring之前&#xff0c;我们的程序里面是没有IOC容器的&#xff0c;这个时候我们如果想要得到一个事先已经定义的对象该怎么得到呢&#xff1f;…

数据结构(树)

1.树的概念和结构 树&#xff0c;顾名思义&#xff0c;它看起来像一棵树&#xff0c;是由n个结点组成的非线性的数据结构。 下面就是一颗树&#xff1a; 树的一些基本概念&#xff1a; 结点的度&#xff1a;一个结点含有的子树的个数称为该结点的度&#xff1b; 如上图&#…

Python | Leetcode Python题解之第107题二叉树的层序遍历II

题目&#xff1a; 题解&#xff1a; class Solution:def levelOrderBottom(self, root: TreeNode) -> List[List[int]]:levelOrder list()if not root:return levelOrderq collections.deque([root])while q:level list()size len(q)for _ in range(size):node q.popl…

夏天晚上热,早上凉怎么办?

温差太大容易引起感冒 1.定个大概3点的闹钟&#xff0c;起来盖被子。有些土豪可以开空调&#xff0c;我这个咸鱼没有空调。 2.空调调到合适的温度&#xff0c;比如20几度。

JAVA基础Day 1面向对象

目录 包调用包 对象和类多态继承重写与重载 抽象接口接口的声明接口的实现 包 package bao;class FreshJuice{enum FreshJuiceSize{small,medium,lager}FreshJuiceSize size; } public class aa {public static void main(String[] args) {System.out.println("hello&quo…

电子招投标系统源码实现与立项流程:基于Spring Boot、Mybatis、Redis和Layui的企业电子招采平台

随着企业的快速发展&#xff0c;招采管理逐渐成为企业运营中的重要环节。为了满足公司对内部招采管理提升的要求&#xff0c;建立一个公平、公开、公正的采购环境至关重要。在这个背景下&#xff0c;我们开发了一款电子招标采购软件&#xff0c;以最大限度地控制采购成本&#…

大数据量MySQL的分页查询优化

目录 造数据查看耗时优化方案总结 造数据 我用MySQL存储过程生成了100多万条数据&#xff0c;存储过程如下。 DELIMITER $$ USE test$$ DROP PROCEDURE IF EXISTS proc_user$$CREATE PROCEDURE proc_user() BEGINDECLARE i INT DEFAULT 1;WHILE i < 1000000 DOINSERT INT…