一、Condition注解

1.情景一:判断指定文件是否存在

需求:在Spring的IoC容器中有一个User的bean,要求:
导入Jedis坐标后,加载该Bean,没有导入,则不加载。

实现:

项目结构:

springBoot项目可以使用的应用服务器 springboot 内置服务器_User

(1)创建User类:

package mainDir.domain;


public class User {
    private String name;
    private int age;
    
    //getter & setter & toString
    ......
}

(2)创建bean定义的源UserConfig:

package mainDir.config;

import mainDir.condition.ClassCondition;
import mainDir.domain.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

//@Configuration类允许通过调用同一类中的其他@Bean方法来定义bean之间的依赖关系
@Configuration
public class UserConfig {
    
    //@Bean注解用于告诉该方法,产生一个Bean对象,然后这个Bean对象交给Spring管理。 
    //产生这个Bean对象的方法Spring只会调用一次,随后这个Spring将会将这个Bean对象
    //放在自己的IOC容器中
    @Bean
    @Conditional(ClassCondition.class)
    public User user(){
        //instantiate, configure and return bean
        //通过这个方法生成一个名为user的Bean
        return new User();
    }
}

接下来我们来关注@Conditional注解的使用:

首先,在这个注解中,我们要传入一个静态类ClassCondition(这个类名可以自定义),在这个类中编写判断user()方法是否执行的条件

(3)编写ClassCondition类进行测试:

package mainDir.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class ClassCondition implements Condition {
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        return false;
    }
}

启动类:(测试是否获取到bean)

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        //返回Spring的IoC容器
        ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
        //获取bean -- user
        User user = context.getBean("user", User.class);
        System.out.println(user);
    }
}

注意你引入的Condition接口是否正确:org.springframework.context.annotation.Condition。
在上面的代码中,如果直接返回false,那么这个方法将不会被调用。同理,你在进行测试的时候如果执行context.getBean("user")时,这个语句将会报错:No bean named 'user' available

在进行判断前,我们在路径中加入Jedis依赖:

<dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

接下来修改判断条件文件ClassCondition:

public class ClassCondition implements Condition {
    
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        boolean flag = true;
        try {
            //思路:判断redis.clents.jedis.Jedis.class文件是否存在
            Class<?> cls = Class.forName("redis.clients.jedis.Jedis");
        }catch (ClassNotFoundException e){
            flag = false;
        }
        return flag;
    }
}

此时,你可以修改这个文件,观察@Conditional注解下的bean能否成功创建。

2.情景二:进行动态判断

需求:现在需要在书写UserConfig.class时再向condition文件中传入要进行判断的参数(而非直接写在定义好的ClassCondition类中)。

(1)创建自己的注解@ConditionOnClass:

package com.test.condition;

import org.springframework.context.annotation.Conditional;

import java.lang.annotation.*;

//在自定义注解上添加三个原注解
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ClassCondition.class)
public @interface ConditionOnClass {
    String[] value();
}

注意这个类上有四个注解,前三个注解可以在Conditional类的定义中找到。
第四个注解@Conditional(ClassCondtion.class)则指向你之前定义的条件类。这个注解中写入你进行判断的方法。

(2)ClassCondition类:

package com.test.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;

import java.util.Map;

public class ClassCondition implements Condition {
    /***
     * matches方法的两个参数能够帮助我们动态地获取需要进行判断的信息
     * @param context 上下文对象。用于获取环境、IoC容器、ClassLoader对象等
     * @param metadata 注解元对象。可以用于获取注解定义的属性值
     * @return
     */
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        //获取环境信息(注意导入的是springframework包下的类)
        //Environment environment = context.getEnvironment();
        //还可以通过环境获取配置类
        //environment.getProperty();
        
        //通过注解属性value指定的坐标后创建bean
        
        //获取注解属性值value
        Map<String, Object> map = metadata.getAnnotationAttributes(ConditionOnClass.class.getName());
        //获取"value"对应的值(即在@ConditionOnClass()自定义注解中传入的值)
        String value[] = (String[]) map.get("value");
        
        boolean flag = true;
        try {
            //遍历value数组的值,即各级路径,若存在一个路径对应不上,则说明文件不存在
            for (String className : value){
                Class<?> cls = Class.forName(className);
            }
        }catch (ClassNotFoundException e){
            flag = false;
        }
        return flag;
    }
}

在这个类中,写入你要进行判断的条件。与情景一不同的是,你将获取到由自定义注解ConditionOnClass传入的参数String value[]。可以在metadata属性中使用getAnnotationAttributes方法来进行获取。

(3)接下来,就可以使用启动类进行测试,和情景一相同。
 

小结

自定义条件:自定义类实现Condition接口,重写matches方法,在matches方法中进行逻辑判断,返回boolean值。matches方法有两个参数:

context:上下文对象,可以获取属性值,获取类加载器,获取BeanFactory等。
metaData:元数组对象,用于获取注解属性

判断条件:初始化bean时,使用@Condition(条件类.class)注解

SpringBoot提供的常用条件注解

@ConditionalOnProperty:当配置文件中(application.yml)指定的属性和对应的值存在,才会创建bean。 

@ConditionalOnClass:判断环境中是否有对应字节码文件才初始化bean。

@ConditionalOnMissingBean:判断环境中有没有对应bean才初始化bean。

二、切换内置服务器

在依赖文件的web包下,可以找到一个叫做embedded的文件夹,里面有包含Tomcat在内的4种内置服务器:

springBoot项目可以使用的应用服务器 springboot 内置服务器_后端_02

接下来,我们找到文件夹中的配置类EmbeddedWebServerFactoryCustomizerAutoConfiguration:

springBoot项目可以使用的应用服务器 springboot 内置服务器_spring boot_03

 在这个类中,我们可以看到刚刚学到的@Condition注解。@ConditionalOnClass判断环境中是否有相应字节码文件,才初始化bean。接下来,我们看spring-boot-starter-web中的依赖:

springBoot项目可以使用的应用服务器 springboot 内置服务器_spring_04

现在我们来排除tomcat的依赖: 点击tomcat,右键Exclude,在pom.xml文件中观察变化:

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                    <groupId>org.springframework.boot</groupId>
                </exclusion>
            </exclusions>
        </dependency>

可以发现,现在tomcat的依赖已经被排除。接下来引入对jetty的依赖:

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jetty</artifactId>
        </dependency>

现在重启服务器,观察控制台可以发现,当前启动的服务器为jetty:

springBoot项目可以使用的应用服务器 springboot 内置服务器_java_05