【微服务】spring 条件注解从使用到源码分析详解

目录

一、前言

二、spring 条件注解概述

2.1 条件注解@Conditional介绍

2.2 @Conditional扩展注解

2.2.1 @Conditional扩展注解汇总

三、spring 条件注解案例演示

3.1 @ConditionalOnBean

3.2 @ConditionalOnMissingBean

3.2.1 使用在类上

3.2.2 使用场景补充

3.3 @ConditionalOnClass

3.4 @ConditionalOnExpression

3.5 @ConditionalOnProperty

四、条件注解源码剖析

4.1 @ConditionalOnMissingClass 源码剖析

4.1.1 debug过程分析

4.1.2 源码分析过程小结

五、条件注解的应用场景

5.1 条件注解使用案例

六、写在文末


一、前言

spring框架之所以得到普遍应用,一方面是框架优良的封装性,能够帮助开发人员节省很多底层开发工作,从而提升效率,另一方面框架自身提供了很多扩展点,开发者通过这些扩展点可以对自身业务进行很好的拓展,比如自定义starter,从而与spring框架无缝集成。

随着springboot逐渐成为微服务开发的标配,得到很多开发者的喜爱,springboot相比spring来说,增加了很多对开发友好的功能、配置等,尤其是全注解开发是springboot框架的一大特色,由于注解相当的多,接下来以spring框架中一个容易被大家忽略的扩展点,即条件注解为例进行说明。

二、spring 条件注解概述

2.1 条件注解@Conditional介绍

spring中核心条件注解是@Conditional,它用于判断Bean是否满足特定的条件,如果满足(或不满足)条件,则将标注了该注解的bean注册到IOC容器,否则不注册。因此, @Conditional这个注解的核心就是用来控制 Bean 的创建的。

SpringBoot自动配置功能里面就大量的使用了条件注解,还有很多与springboot集成的第三方组件也有使用。

@Conditional注解和Condition接口搭配一起使用,通过对应Condition接口来告知是否满足匹配条件,注解源码如下:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
	/**
     * 所有用于匹配的Condition接口(实现该接口的类),只有这些类都返回true才认为是满足条件
     */
    Class<? extends Condition>[] value();
}

@Conditional注解可以添加在@Configuration、@Component、@Service等修饰的类上,用于控制对应的Bean是否需要创建,如果添加在@Bean修饰的方法上,则用于控制方法对应的Bean是否需要创建

2.2 @Conditional扩展注解

事实上,在实际开发或看到的框架级源码中,直接使用@Conditional并不多,更多的是使用像@ConditionalOnBean,@ConditionalOnMissingBean,或@ConditionalOnClass等这样的注解,我们也称之为扩展注解,以@ConditionalOnBean为例,不妨看一下该注解源码,可以看到,该注解添加了对@Conditional的引用,侧面说明了属于@Conditional注解的扩展这一说法。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnBeanCondition.class})
public @interface ConditionalOnBean {
    Class<?>[] value() default {};

    String[] type() default {};

    Class<? extends Annotation>[] annotation() default {};

    String[] name() default {};

    SearchStrategy search() default SearchStrategy.ALL;

    Class<?>[] parameterizedContainer() default {};
}

2.2.1 @Conditional扩展注解汇总

下面列举的是@Conditional常用的扩展注解

注解名称使用参考生效位置备注
@ConditionalOnBean@ConditionalOnBean(value=AA.class)beanIOC容器中存在括号中的实例时生效
@ConditionalOnMissingBean@ConditionalOnMissingBean(name = "bean名称")beanIOC容器中不存在括号中的实例时生效
@ConditionalOnClass@ConditionalOnClass(AA.class)class类加载器中存在对应的类生效
@ConditionalOnMissingClass@ConditionalOnMissingClass(AA.class)class类加载器中不存在对应的类生效
@ConditionalOnExpression@ConditionalOnExpression(“’表达式值’”)class 或 bean判断SpEL 表达式成立生效
@ConditionalOnProperty@ConditionalOnProperty(prefix = “spring.redis”, name = “port”, havingValue = “3306”, matchIfMissing = true)class 或 bean应用环境属性满足条件生效
@ConditionalOnResource@ConditionalOnResource(resources= “common.properties”)class 或 bean应用环境存在指定的资源文件生效

三、spring 条件注解案例演示

上面总结了@Conditional条件注解以及相关的扩展注解,为了加深对这些注解的理解,接下来针对各个注解的使用通过代码进行演示,以形成更深刻的印象。

3.1 @ConditionalOnBean

@ConditionalOnBean对应的Condition处理类是OnBeanCondition,即在IOC容器中存在指定的Bean时当前的bean则被创建,源码如下,该注解可用在方法或类上。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnBeanCondition.class})
public @interface ConditionalOnBean {
    Class<?>[] value() default {};

    String[] type() default {};

    Class<? extends Annotation>[] annotation() default {};

    String[] name() default {};

    SearchStrategy search() default SearchStrategy.ALL;

    Class<?>[] parameterizedContainer() default {};
}

创建如下配置类,在当前类中,在depart这个bean的方法上通过ConditionalOnBean注解来控制,即如果IOC容器中存在了role的bean实例,则depart这个bean才会被创建出来;

@ComponentScan("com.congge")
@Configuration
public class ScanConfig {

    @Bean
    public Role role(){
        return new Role("0002","测试角色");
    }

	/**
     * 也可以写为:@ConditionalOnBean(name = "role") , name的值确保是正确的
     * @return
     */
    @Bean
    @ConditionalOnBean(Role.class)
    public Depart depart(){
        return new Depart("0001","架构部");
    }

}

按照上述代码,role这个bean实例会被创建,所以depart的bean也会被创建出来,使用下面的代码进行测试;

public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ScanConfig.class);

        //角色存在
        Role role = (Role)applicationContext.getBean("role");
        System.out.println(role.getRoleName());

        //部门是否存在
        Depart depart = (Depart)applicationContext.getBean("depart");
        System.out.println(depart.getDeptName());
    }

运行上面的代码,可以看到如下效果,说明depart的bean实例也创建了;

如果去掉role的bean实例创建,改造后代码如下

@ComponentScan("com.congge")
@Configuration
public class ScanConfig {

    @Bean
    @ConditionalOnBean(Role.class)
    public Depart depart(){
        return new Depart("0001","架构部");
    }

}

改造测试代码

public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ScanConfig.class);

        //角色存在
        //Role role = (Role)applicationContext.getBean("role");
        //System.out.println("role :" + role);
        //System.out.println(role.getRoleName());

        //部门是否存在
        Depart depart = (Depart)applicationContext.getBean("depart");
        System.out.println("depart :" + depart);

        //System.out.println(depart.getDeptName());
    }

再次运行时直接报错了,即depart这个bean不存在,反向说明@ConditionalOnBean生效了  

3.2 @ConditionalOnMissingBean

@ConditionalOnMissingBean对应的Condition实现类是OnBeanCondition,即IOC容器中不存在指定的Bean则创建当前的bean(或类中的bean),对应源码如下:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnBeanCondition.class})
public @interface ConditionalOnMissingBean {
    Class<?>[] value() default {};

    String[] type() default {};

    Class<?>[] ignored() default {};

    String[] ignoredType() default {};

    Class<? extends Annotation>[] annotation() default {};

    String[] name() default {};

    SearchStrategy search() default SearchStrategy.ALL;

    Class<?>[] parameterizedContainer() default {};
}

紧接上述案例,在创建user的bean的方法上面使用ConditionalOnMissingBean注解,代码如下:

@ComponentScan("com.congge")
@Configuration
public class ScanConfig {

    @Bean
    public Role role(){
        return new Role("0002","测试角色");
    }

    @Bean
    @ConditionalOnMissingBean(name = "depart")
    public User user(){
        return new User("0003","jerry");
    }

}

按照代码猜想,user方法使用当前注解,表示当IOC容器中不存在depart这个bean的时候,user这个bean会被创建出来,使用下面的测试代码

public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ScanConfig.class);

        //角色存在
        Role role = (Role)applicationContext.getBean("role");
        System.out.println(role.getRoleName());

        //用户是否存在
        User user = (User)applicationContext.getBean("user");
        System.out.println(user.getUserName());
    }

运行这段代码,看到如下效果,说明注解生效了

3.2.1 使用在类上

以上演示的是两个注解使用在方法上的效果,从源码上可以发现,也支持在类上面使用,如果作用在类上面时表示整个类中的bean生效;

我们在类上添加这个注解,如果IOC容器中并没有userBean这个实例的情况下,可以创建role和depart的bean实例

@ComponentScan("com.congge")
@Configuration
@ConditionalOnMissingBean(name = "userBean")
public class ScanConfig {

    @Bean
    public Role role(){
        return new Role("0002","测试角色");
    }

    /**
     * 也可以写为:@ConditionalOnBean(name = "role")
     * @return
     */
    @Bean
    public Depart depart(){
        return new Depart("0001","架构部");
    }
    
}

运行如下的测试代码

 public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ScanConfig.class);

        //用户是否存在
        //User user = (User)applicationContext.getBean("userBean");
        //System.out.println(user.getUserName());

        //角色存在
        Role role = (Role)applicationContext.getBean("role");
        System.out.println(role.getRoleName());

        //角色存在
        Depart depart = (Depart)applicationContext.getBean("depart");
        System.out.println(depart.getDeptName());
    }

通过控制台输出日志也印证了上面的猜想,可以正确获取role或depart对应的bean

3.2.2 使用场景补充

对于该注解的使用场景,做如下补充:

  • 项目中,只允许某个bean注入一次的时候使用,即保证在ioc中这个bean的实例只有一个1;
  • 一般来说,对于自定义配置类,可以加@ConditionalOnMissingBean注解,以避免多个配置同时注入的风险;

加了这个注解后,当你注册两个相同类型的bean时,会抛出异常,它会保证同一个类型的bean只有一个

如下,同时创建两个User的bean实例,user1和user2;

	@Bean
    @ConditionalOnMissingBean
    public User user1(){
        return new User("user1","james");
    }

    @Bean
    @ConditionalOnMissingBean
    public User user2(){
        return new User("user2","zhaoyun");
    }

通过运行下面的测试代码发现,在这种情况下,最终只创建了user1这个bean

public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ScanConfig.class);
        User user1 = (User)applicationContext.getBean("user1");
        System.out.println(user1.getUserName());

        User user2 = (User)applicationContext.getBean("user2");
        System.out.println(user2.getUserName());
    }

运行效果如下:

3.3 @ConditionalOnClass

@ConditionalOnClass对应的Condition处理类是OnClassCondition,即如果当前类路径下面有指定的类时则生效。对应的源码如下:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnClassCondition.class})
public @interface ConditionalOnClass {
    Class<?>[] value() default {};

    String[] name() default {};
}

在当前工程中存在User这样一个类的情况下,通过这个注解,可以预想role这个bean可以被创建出来

@ComponentScan("com.congge")
@Configuration
//@ConditionalOnMissingBean(name = "userBean")
public class ScanConfig {

    /**
     * 也可以写成: com.congge.entity.User
     * @return
     */
    @Bean
    @ConditionalOnClass(User.class)
    public Role role(){
        return new Role("0002","测试角色");
    }

}

运行测试代码,可以看到role对应的bean被创建出来了;

假如将User类删除或注释掉,使用下面代码,再次测试

    /**
     * 也可以写成: com.congge.entity.User
     * @return
     */
    @Bean
    //@ConditionalOnClass(User.class)
    @ConditionalOnClass(name ="com.congge.entity.User")
    public Role role(){
        return new Role("0002","测试角色");
    }

运行可以看到下面的效果

 @ConditionalOnMissingClass 可以类比进行本地验证

3.4 @ConditionalOnExpression

@ConditionalOnExpression对应的Condition处理类是OnExpressionCondition,即只有当SpEL表达式满足条件的时候则生效,对应的源码如下

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional({OnExpressionCondition.class})
public @interface ConditionalOnExpression {
    String value() default "true";
}

在本地配置文件 application.properties中增加如下配置:

server.port=8087

#条件表达式需用到的配置
hxstrive.type=server
hxstrive.user.enable=true
hxstrive.order.size=10

在配置类上添加@ConditionalOnExpression注解,代码如下,当作用在类上面的时候,表示当表达式中的条件得到满足时,类中的bean将会被创建;

@Configuration
@ConditionalOnExpression("'${hxstrive.type}'.equals('server') && (${hxstrive.user.enable})")
public class ScanConfig {

    @Bean
    public Role role(){
        return new Role("0002","测试角色");
    }
}

关于SPEL表达式的写法,网上有非常详细的资料可供参考,这里就不再过多展示了

再在启动类中获取一下role的bean,如果能够获取到,说明表达式生效了

@SpringBootApplication
@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
public class AnnoApp {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(AnnoApp.class, args);
        Role role = (Role) context.getBean("role");
        System.out.println(role.getRoleName());
    }
}

运行上述启动类,通过控制台可以发现,role这个bean的实例创建出来了

3.5 @ConditionalOnProperty

@ConditionalOnProperty对应的Condition实现类OnPropertyCondition,只有当对应的配置属性和给定条件的值相等的时候则生效,源码如下,

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional({OnPropertyCondition.class})
public @interface ConditionalOnProperty {
    String[] value() default {};

    String prefix() default "";

    String[] name() default {};

    String havingValue() default "";

    boolean matchIfMissing() default false;
}

该注解通过其三个属性prefix,name以及havingValue来实现的,其中prefix表示配置文件里节点前缀,name用来从application.properties中读取某个属性值,havingValue表示目标值,具体来说:

  • 如果该值为空,则返回false;
  • 如果值不为空,则将该值与havingValue指定的值进行比较,如果一样则返回true,否则返回false;
  • 返回值为false,则该configuration不生效;为true则生效;

在如下代码中,userBean的创建依赖于类上的注解@ConditionalOnProperty以及其中的属性值是否满足要求

@Configuration
@ConditionalOnProperty(prefix="spring.user",name = "username", havingValue = "jerry")
public class BeanConfig {

    @Bean("userBean")
    public User userBean(){
        return new User("0003","mike");
    }
}

在application.properties配置文件中存在如下配置

spring.user.username=jerry

基于上面的配置条件下,运行下面的启动类,可以看到可以获取到userBean的实例

public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(AnnoApp.class, args);
        User user = (User) context.getBean("userBean");
        System.out.println(user.getUserName());
    }

四、条件注解源码剖析

以@ConditionalOnMissingClass为例,分析下该注解的源码执行过程,有如下代码,在ScanConfig这个类上使用了该注解,表示如果注解中的User类存在的情况下,role这个bean将会被创建;

@Configuration
@ComponentScan("com.congge")
@ConditionalOnMissingClass(value = "com.congge.entity.User")
public class ScanConfig {

    @Bean
    public Role role(){
        return new Role("0002","测试角色");
    }

}

在当前的工程中,我们先将User类删除或注释掉,使用如下的代码进行测试,效果是role的bean被创建出来了,这也正好与@ConditionalOnMissingClass这个注解要实现的功能内涵相符;

4.1 @ConditionalOnMissingClass 源码剖析

springboot从启动到如何进行扫描,注解解析,bean解析以及后置处理器等等流程这里不继续展开,这是一个相对复杂的过程,我们直接从该注解源码入手来看,搞清楚其底层实现原理。 

@ConditionalOnMissingClass 源码如下,该注解可以作用在类上,也可以作用在某个方法即bean的创建上;  

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnClassCondition.class})
public @interface ConditionalOnMissingClass {
    String[] value() default {};
}

实现条件选择的重要入口OnClassCondition.class,从这里点击进去,可以看到,该类继承了FilteringSpringBootCondition这个类,一般如果有继承关系,还需要从父类寻找线索;

从FilteringSpringBootCondition继续深入,看到了SpringBootCondition这个类,

继续深入到SpringBootCondition这个类中,才找到了最终的答案,即要完成条件的判断,最终需要调用matches这个方法,这个方法的接口定义就是在Condition接口中定义的;

接下去只需要把matches这个方法的来龙去脉搞清楚即可,关于这个方法,我们通过代码注释加以理解

//针对所有条件注解进行判断
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        //条件注解是写在哪个类或方法上呢?通过这个方法进行获取
        String classOrMethodName = getClassOrMethodName(metadata);

        try {
        	//拿到条件注解的判断结果
            ConditionOutcome outcome = this.getMatchOutcome(context, metadata);
            
            //记录当前条件判断的结果到日志中
            this.logOutcome(classOrMethodName, outcome);
            
            //将判断的结果记录到ConditionEvaluationReport中去
            this.recordEvaluation(context, classOrMethodName, outcome);
            
            //获取判断的结果
            return outcome.isMatch();
        } catch (NoClassDefFoundError var5) {
            throw new IllegalStateException("Could not evaluate condition on " + classOrMethodName + " due to " + var5.getMessage() + " not found. Make sure your own configuration does not rely on that class. This can also happen if you are @ComponentScanning a springframework package (e.g. if you put a @ComponentScan in the default package by mistake)", var5);
        } catch (RuntimeException var6) {
            throw new IllegalStateException("Error processing condition on " + this.getName(metadata), var6);
        }
    }

4.1.1 debug过程分析

从上面的matches方法入手,通过debug来到下面的流程

找到具体的条件注解对应的实现

进入到OnClassCondition类中找到getMatchOutcome方法,源码如下,可结合注释进行理解

public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
        ClassLoader classLoader = context.getClassLoader();
        ConditionMessage matchMessage = ConditionMessage.empty();
        //拿到ConditionalOnClass 注解中的value值,也就是需要判断这个类是否存在
        List<String> onClasses = this.getCandidates(metadata, ConditionalOnClass.class);
        List onMissingClasses;
        if (onClasses != null) {
        	//过滤onClasses中不存在的类
            onMissingClasses = this.filter(onClasses, ClassNameFilter.MISSING, classLoader);
            //如果上一步过滤出来了不存在的类,说明不匹配
            if (!onMissingClasses.isEmpty()) {
                return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class, new Object[0]).didNotFind("required class", "required classes").items(Style.QUOTE, onMissingClasses));
            }
			//否则就是匹配到了
            matchMessage = matchMessage.andCondition(ConditionalOnClass.class, new Object[0]).found("required class", "required classes").items(Style.QUOTE, this.filter(onClasses, ClassNameFilter.PRESENT, classLoader));
        }
        
		//拿到ConditionalOnMissingClass 注解中的value值,也就是需要判断这个类是否存在
        onMissingClasses = this.getCandidates(metadata, ConditionalOnMissingClass.class);
        if (onMissingClasses != null) {
        	//进一步检查这个ConditionalOnMissingClass中的class类是否存在
            List<String> present = this.filter(onMissingClasses, ClassNameFilter.PRESENT, classLoader);
            //如果过滤出来有不符合条件的,说明不匹配
            if (!present.isEmpty()) {
                return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingClass.class, new Object[0]).found("unwanted class", "unwanted classes").items(Style.QUOTE, present));
            }

            matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class, new Object[0]).didNotFind("unwanted class", "unwanted classes").items(Style.QUOTE, this.filter(onMissingClasses, ClassNameFilter.MISSING, classLoader));
        }

        return ConditionOutcome.match(matchMessage);
    }

通过断点调试,会进入到解析ConditionalOnMissingClass注解的方法,然后解析到了User这个类

继续跟进,最终会调用到 ConditionOutcome.match 这个方法,由于上述的判断通过了,这里直接返回了使用ConditionOutcome封装的对象,其中match为true;

返回到调用的地方,当上述的判断通过之后,SpringBootCondition中的matches方法返回true

到这里,相信对spring注册bean过程稍有了解的同学不难猜到,接下来将会执行对ScanConfig类的解析,并对该类中需要创建bean的方法进行创建的过程,这也不难理解,在端点的调用链路上即可窥探一二

从上面的端点执行链路不难看出,在执行doRegisterBean的过程中,调用了一个shouldSkip的方法,就是在这个方法中完成了对上述ConditionalOnMissingClass注解的解析过程,如果matches方法返回true之后,我们继续前进一步,接下去就是一个注册bean的过程;

解析并注册bean,即对ScanConfig类解析和创建BeanDefinition等一些列步骤;

4.1.2 源码分析过程小结

上述以@ConditionalOnMissingClass注解为例分析了源码执行的过程,其他的几个注解可以类似的进行研究,总结来说,条件注解的解析发生在bean注册之前,作为是否创建bean的判断条件,只要记住这一点就可以了。

五、条件注解的应用场景

 在springboot框架中,关于条件注解有非常多的应用,有心的同学可以在很多与springboot框架集成的外部组件的源码中看到,比如ConditionalOnMissingClass注解用于日志整合中,作为兜底类,比如日志配置中,如果某个项目中没有特殊的配置,那么就用兜底的配置,否则就用兜底的配置。这就是没有指定的实现,就会有默认的配置实现。

5.1 条件注解使用案例

下面以@ConditionalOnProperty这个注解为例配合一个定时任务调度场景说明,我们的需求是:

通过一个外部配置文件来控制定时任务是否执行;

能够实现上述需求的方式有很多,这里选择使用@ConditionalOnProperty条件表达式注解来实现,具体来说,在执行定时任务的类上面(或方法)添加该注解,通过注解中引入配置参数的值来控制即可。代码如下:

@Component
@EnableScheduling
public class UserTask {

    //每5秒钟执行一次
    @Scheduled(cron="0/5 * * * * ?")
    @ConditionalOnProperty(prefix="user.task",name = "enable", havingValue = "true")
    public void cron(){
        System.out.println("执行定时任务");
    }

}

第一次,在配置参数为false的情况下,启动程序,等待5秒之后并没有执行

将参数开启为true,再次启动程序,等待5秒之后,,定时任务就执行了

六、写在文末

spring条件注解是spring框架注解体系中非常重要的组成成员,也是spring为开发者提供的扩展点之一,合理使用条件注解,可以给应用开发带来意想不到的效果,希望对看到本文的你提供一个思路,本文到此技术,感谢观看。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何使用 Docker Compose 运行 OSS Wordle 克隆

了解如何使用 Docker Compose 在五分钟内运行您自己的流行 Wordle 克隆实例。您将如何部署 Wordle&#xff1f; Wordle在 2021 年底发布后席卷了互联网。对于许多人来说&#xff0c;这仍然是一种早晨的仪式&#xff0c;与一杯咖啡和一天的开始完美搭配。作为一名 DevOps 工程师…

开源TTS+gtx1080+cuda11.7+conda+python3.9吊打百度TTS

一、简介 开源项目&#xff0c;文本提示的生成音频模型 https://github.com/suno-ai/bark Bark是由Suno创建的基于变换器的文本到音频模型。Bark可以生成极为逼真的多语种演讲以及其他音频 - 包括音乐、背景噪音和简单的声音效果。该模型还可以产生非言语沟通&#xff0c;如…

Linux存储学习笔记

相关文章 Linux 存储系列&#xff5c;请描述一下文件的 io 栈&#xff1f; - tcpisopen的文章 - 知乎 https://zhuanlan.zhihu.com/p/478443978 深入学习 Linux 操作系统的存储 IO 堆栈 - KaiwuDB的文章 - 知乎 https://zhuanlan.zhihu.com/p/636720297 linux存储栈概览 - st…

ssm+vue游戏攻略网站源码和论文

ssmvue游戏攻略网站源码和论文052 开发工具&#xff1a;idea 数据库mysql5.7 数据库链接工具&#xff1a;navcat,小海豚等 技术&#xff1a;ssm 一、主要内容和基本要求 游戏攻略网站分为管理员与用户两种角色。 管理员的功能包括登录&#xff0c;用户管理&#xff0c;游…

Centos7 安装Docker 详细多图版

配置要求 Docker CE&#xff08;社区免费版&#xff09; 支持 64 位版本 CentOS 7&#xff0c;并且要求内核版本不低于 3.10&#xff0c; CentOS 7 满足最低内核的要求&#xff0c;所以我们在CentOS 7安装Docker。 一、Centos安装Docker 1.1 卸载&#xff08;可选&#xff0…

Datawhale AI夏令营 - 用户新增预测挑战赛 | 学习笔记

数据分析与可视化 为了拟合出更好的结果就要了解训练数据之间的相互关系&#xff0c;进行数据分析是必不可少的一步 导入必要的库 # 导入库 import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns pandas库是一个强大的分析结构化…

研发管理工具大揭秘!6款利器助你高效研发

"研发管理工具有哪些&#xff1f;6款研发管理利器分析Zoho Projects、Trello、Asana、Monday.com、Smartsheet、Jira。" 在如今的科技发展日新月异的时代&#xff0c;研发管理工具的重要性日益凸显。研发管理工具有助于提高研发效率&#xff0c;降低成本&#xff0c;…

无涯教程-PHP - preg_grep()函数

preg_grep() - 语法 array preg_grep ( string $pattern, array $input [, int $flags] ); 返回由与给定模式匹配的输入数组元素组成的数组。 如果将flag设置为PREG_GREP_INVERT&#xff0c;则此函数返回输入数组中与给定模式不匹配的元素。 preg_grep() - 返回值 返回使用…

Docker创建 LNMP 服务+Wordpress 网站平台

文章目录 Docker创建 LNMP 服务Wordpress 网站平台一.环境及准备工作1.项目环境2.服务器环境3.任务需求 二.Linux 系统基础镜像三.docker构建Nginx1.建立工作目录上传安装包2.编写 Dockerfile 脚本3.准备 nginx.conf 配置文件4.生成镜像5.创建自定义网络6.启动镜像容器7.验证 n…

网络安全(大厂)面试题

以下为网络安全各个方向涉及的面试题&#xff0c;星数越多代表问题出现的几率越大&#xff0c;祝各位都能找到满意的工作。 注&#xff1a;本套面试题&#xff0c;已整理成pdf文档&#xff0c;但内容还在持续更新中&#xff0c;因为无论如何都不可能覆盖所有的面试问题&#xf…

海思Hi3861L开发一-环境搭建

一、简介 之前的文章中有详细介绍了HarmonyOS的Hi3861开发,但是该开发是基于HarmonyOS来的。实际在项目开发中,可能不会用到HarmonyOS,用的还是原生的Hi3861。那这次就重新学习Hi3861L。 二、环境搭建 环境:Ubuntu18.04.5 关于Ubuntu的环境搭建,还是参考之前的文章,附上…

mysql------做主从复制,读写分离

1.为什么要做主从复制&#xff08;主从复制的作用&#xff09; 做数据的热备&#xff0c;作为后备数据库&#xff0c;主数据库服务器故障后&#xff0c;可切换到从数据库继续工作&#xff0c;避免数据丢失。 架构的扩展。业务量越来越大,I/O访问频率过高&#xff0c;单机无法满…

Java 内置注解

一、内置注解 Java内置注解 也称 Java标准注解&#xff0c;是Java JDK 中自带的注解。Java 中有许多标准注解&#xff0c;以下是一些常见的标准注解&#xff1a; 1. Override&#xff1a;用于表示一个方法是重写父类中的方法。 2. Deprecated&#xff1a;用于标记已经过时的方法…

WinPlan经营大脑垂直大模型,一站式解决企业经营管理难题

WinPlan经营大脑是杭州数利得科技有限公司打造的一款SAAS产品,为市场现存的企业经营管理难题,提供一站式解决方案。助力企业经营管理转型,帮助企业快速实现“经营规划管理&数据分析”线上化、可视化、数字化。 WinPlan决策系统 算力 阿里云 腾讯云 AWS亚马逊 框架 业务数…

Python入门【原生字符串、边界字符、search函数、re模块中其他常用的函数 、贪婪模式和非贪婪模式、择一匹配(|)的使用、分组】(三十)

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱敲代码的小王&#xff0c;CSDN博客博主,Python小白 &#x1f4d5;系列专栏&#xff1a;python入门到实战、Python爬虫开发、Python办公自动化、Python数据分析、Python前后端开发 &#x1f4e7;如果文章知识点有错误…

MySQL 数据备份和数据恢复

目录 一、数据备份 1、概述 2、MySQLdump命令备份 1&#xff09;备份单个数据库中的所有表 2) 备份数据中某个或多个表 3) 备份所有数据库 4&#xff09;备份多个库 5) 只备份一个表或多个表结构 二、数据恢复 三、数据备份与恢复应用 一、数据备份 1、概述 数据备…

An easy problem

一、题目 we define f(A) 1, f(a) -1, f(B) 2, f(b) -2, … f(Z) 26, f(z) -26; Give you a letter x and a number y , you should output the result of yf(x). Input On the first line, contains a number T.then T lines follow, each line is a case.each case …

一、数据库基础

数据库 一、数据库基础 1、一些概念 数据库&#xff1a;数据库&#xff08;DataBase &#xff0c;简称DB&#xff09;&#xff0c;就是信息的集合。数据库是由数据库管理系统管理的数据的集合&#xff1b;数据库管理系统&#xff1a;简称DBMS 。是一种操纵和管理数据库的大型…

Vue 项目布署后,刷新页面(或跳转页面)出现 404 解决办法

Vue 项目布署后&#xff0c;刷新页面&#xff08;或跳转页面&#xff09;出现 404 问题背景为什么会出现404解决办法&#xff08;两种&#xff09;方法一&#xff1a;改变服务器配置方法二&#xff1a;改变路由模式 单页应用(SPA)概念 问题背景 今天重新部署一个vue项目的时候…

【NX】NX二次开发BlockUI集列表的详细使用步骤

最近使用NX二次开发&#xff0c;需要用到集列表&#xff0c;也就是SetList这个控件&#xff0c;然而网上相关的资料和范例实在是太少&#xff0c;有幸找到《NX二次开发-BlockUI集列表的使用技巧》和《UG&#xff08;NX&#xff09;二次开发 BlockUI 集列表使用方法》&#xff0…