During the interview p7, Ali was rubbed on the ground. Who knows what I've experienced?

Questions Ali p7 was asked during the interview (I only knew the first one at that time):

  1. @What does Conditional do?

  2. @Conditional what is the logical relationship between multiple conditions?

  3. When is conditional judgment performed?

  4. What is the difference between ConfigurationCondition and Condition? When to use ConfigurationCondition?

  5. What is the order in which multiple conditions are executed? Can I configure priority?

  6. Can you introduce some common usage of @ Conditional?

@Conditional annotation

@The Conditional annotation is only available from spring 4.0 and can be used on any type or method. Some Conditional judgments can be configured through the @ Conditional annotation. When all conditions are met, the targets marked by @ Conditional will be processed by the spring container.

For example, you can use @ Conditional to control whether bean s need to be registered and whether the Configuration class marked by @ Configuration needs to be resolved.

The effect is like this code, which is equivalent to adding a conditional judgment in front of the spring container resolution target:

if(@Conditional Do multiple conditions configured in match){
//spring continues to process objects annotated with @ Conditional annotations
}

@Conditional source code:

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

This annotation has only one value parameter, an array of condition type. Condition is an interface that represents a condition judgment. There is an internal method that returns true or false. When all conditions are true, the @ Conditional result is true.

Let's take a look at the Condition interface.

Condition interface

The interface used to represent condition judgment. The source code is as follows:

@FunctionalInterface
public interface Condition {

    /**
     * Judge whether the conditions match
     * context:Conditional judgment context
     */
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);

}

It is a functional interface. There is only one matches method inside to judge whether the condition is true. There are two parameters:

  • Context: conditional context, of the ConditionContext interface type, which can be used to obtain personal information in the container

  • metadata: used to obtain all annotation information on the object marked by @ Conditional

ConditionContext interface

This interface provides some common methods that can be used to obtain various information in the spring container. Take a look at the source code:

public interface ConditionContext {

    /**
     * Return to the bean definition registrar, through which you can obtain various configuration information of the bean definition
     */
    BeanDefinitionRegistry getRegistry();

    /**
     * Returns a bean factory of type ConfigurableListableBeanFactory, which is equivalent to an ioc container object
     */
    @Nullable
    ConfigurableListableBeanFactory getBeanFactory();

    /**
     * Returns the environment configuration information object of the current spring container
     */
    Environment getEnvironment();

    /**
     * Return to resource loader
     */
    ResourceLoader getResourceLoader();

    /**
     * Return class loader
     */
    @Nullable
    ClassLoader getClassLoader();

}

Key question: when is conditional judgment executed?

Spring's processing of configuration classes is mainly divided into two stages:

Configuration class parsing phase

You will get a batch of configuration class information and some bean s that need to be registered

bean registration phase

Register the configuration classes obtained in the configuration class parsing phase and the bean s that need to be registered into the spring container

Take a look at what a configuration class is

Class with any of the following annotations belongs to the configuration class:

  1. There is a @ compound annotation on the class

  2. There is a @ Configuration annotation on the class

  3. There is a @ composentscan annotation on the class

  4. Class has @ Import annotation

  5. Class has @ ImportResource annotation

  6. Method with @ Bean annotation in class

To judge whether a class is a configuration class or not, the following method is used. You can have a look at it if you are interested:

org.springframework.context.annotation.ConfigurationClassUtils#isConfigurationCandidate

The two processes in spring will cycle until the resolution of all configuration classes and the registration of all bean s are completed.

Spring processes configuration classes

Source location:

org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions

The overall process is as follows:

  1. Usually, we will start the spring container by passing in multiple configuration classes through new AnnotationConfigApplicationContext()

  2. spring parses multiple incoming configuration classes

  3. Configuration class parsing stage: this process is the process of processing the annotations in 6 above the configuration class. During this process, many new configuration classes will be found. For example, a batch of new classes imported by @ Import just match the configuration class, and some classes scanned by @ componentscan also happen to be configuration classes; The same process will be performed for these newly generated configuration classes

  4. bean registration phase: after the configuration class is resolved, a batch of configuration classes and beans to be registered will be obtained. At this time, the spring container will register this batch of configuration classes as beans to the spring container, and also register this batch of beans to be registered to the spring container

  5. After the third phase above, many new beans will be registered in the spring container, and there may be many new configuration classes in these new beans

  6. Spring takes all bean s out of the container, traverses them, and filters out a batch of unprocessed new configuration classes, which will continue to be handed over to step 3 for processing

  7. From step 3 to step 6, this process will go through many times until all configuration class parsing and bean registration are completed

We can learn from the above process:

  1. You can add @ Conditional annotation on the configuration class to control whether the configuration class needs to be parsed. If the configuration class is not parsed, the parsing of the above six annotations of the configuration will be skipped

  2. You can add the @ Conditional annotation on the registered bean to control whether the bean needs to be registered in the spring container

  3. If the configuration class will not be registered to the container, all new configuration classes generated by the configuration class resolution and all new bean s generated will not be registered to the container

A configuration class is processed by spring in two stages: configuration class parsing stage and bean registration stage (the configuration class is registered to the spring container as a bean).

If the implementation class of the Condition interface is used as the configuration class in @ Conditional, this Condition will be valid for both phases. At this time, a phase cannot be finely controlled through Condition. If you want to control a phase, for example, you can let it resolve, but you can't let it register, you need to use another interface: ConfigurationCondition

ConfigurationCondition interface

Take a look at the source code of this interface:

public interface ConfigurationCondition extends Condition {

    /**
     * The stage of condition judgment is whether to filter when parsing the configuration class or when creating the bean
     */
    ConfigurationPhase getConfigurationPhase();


    /**
     * Enumeration representing phase: 2 values
     */
    enum ConfigurationPhase {

        /**
         * In the configuration class resolution phase, if the condition is false, the configuration class will not be resolved
         */
        PARSE_CONFIGURATION,

        /**
         * bean In the registration phase, if false, the bean will not be registered
         */
        REGISTER_BEAN
    }

}

The ConfigurationCondition interface has an additional getConfigurationPhase method relative to the Condition interface, which is used to specify the stage of Condition judgment, whether to filter when parsing the configuration class or when creating the bean.

@3 steps for Conditional use

  1. Customize a class, implement the Condition or ConfigurationCondition interface, and implement the matches method

  2. Use the @ Conditional annotation on the target object and specify that the value refers to the custom Condition type

  3. Start the spring container to load resources, and @ Conditional will work

Case 1: prevent processing of configuration class

Use @ Conditional on the configuration class. When one of the conditions specified by the value of this annotation is false, spring will skip processing this configuration class.

Customize a Condition class:

package com.javacode2018.lesson001.demo25.test3;

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

public class MyCondition1 implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return false;
    }
}

We can play inside the matches method at will. Here, in order to demonstrate the effect, we directly return false.

To create a configuration class, use the above condition on the configuration class, which will invalidate the configuration class, as follows:

package com.javacode2018.lesson001.demo25.test3;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

@Conditional(MyCondition1.class) //@1
@Configuration
public class MainConfig3 {
    @Bean
    public String name() { //@1
        return "Passerby a Java";
    }
}

@1: A custom condition class is used

@2: Mark the name method through @ bean. If the configuration class is successfully resolved, the return value of the name method will be registered in the spring container as a bean

Create a test class and start the spring container to load the MainConfig3 configuration class, as follows:

package com.javacode2018.lesson001.demo25;

import com.javacode2018.lesson001.demo25.test3.MainConfig3;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.util.Map;

public class ConditionTest {

    @Test
    public void test3() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig3.class);
        Map<String, String> serviceMap = context.getBeansOfType(String.class);
        serviceMap.forEach((beanName, bean) -> {
            System.out.println(String.format("%s->%s", beanName, bean));
        });
    }
}

In test3, get String type bean s from the container, and run test3 without any output.

We can remove @ Conditional from MainConfig3 and run the output again:

name->Passerby a Java

Case 2: prevent bean registration

Create a configuration class as follows:

package com.javacode2018.lesson001.demo25.test4;

import com.javacode2018.lesson001.demo25.test3.MyCondition1;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MainConfig4 {
    @Conditional(MyCondition1.class) //@1
    @Bean
    public String name() {
        return "Passerby a Java";
    }

    @Bean
    public String address() {
        return "Shanghai";
    }
}

The above two methods use the @ bean annotation to define two beans, and the name method uses the @ Conditional annotation. This condition will be judged before the name bean is registered in the container. When the condition is true, the name bean will be registered in the container.

Add a new test case in ConditionTest to load the above configuration class and obtain all bean outputs of String type from the container. The code is as follows:

@Test
public void test4() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig4.class);
    Map<String, String> serviceMap = context.getBeansOfType(String.class);
    serviceMap.forEach((beanName, bean) -> {
        System.out.println(String.format("%s->%s", beanName, bean));
    });
}

Run output:

address->Shanghai

You can see that only one address is registered in the container, while the bean name is not registered.

Case 3: register only when the bean does not exist

demand

IService interface has two implementation classes Service1 and Service1. These two classes will be placed in two configuration classes and registered to the container through @ bean. At this time, we want to add a restriction that only one bean of IService type is allowed to be registered to the container.

You can add conditional restrictions on the two methods marked with @ bean. Only when there is no bean of IService type in the container can the bean defined by this method be registered in the container. Let's see the code implementation below.

code implementation

Condition judgment class: OnMissingBeanCondition

package com.javacode2018.lesson001.demo25.test1;

import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.ConfigurationCondition;
import org.springframework.core.type.AnnotatedTypeMetadata;

import java.util.Map;

public class OnMissingBeanCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        //Get bean factory
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        //Get IService type bean from container
        Map<String, IService> serviceMap = beanFactory.getBeansOfType(IService.class);
        //Determine whether the serviceMap is empty
        return serviceMap.isEmpty();
    }

}

In the above matches method, we will check whether there is a bean of IService type in the container. If it does not exist, it will return true

IService interface

package com.javacode2018.lesson001.demo25.test1;

public interface IService {
}

Interface has 2 implementation classes

Service1

package com.javacode2018.lesson001.demo25.test1;

public class Service1 implements IService {
}

Service2

package com.javacode2018.lesson001.demo25.test1;

public class Service2 implements IService {
}

A configuration class is responsible for registering Service1 to the container

package com.javacode2018.lesson001.demo25.test1;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

@Configuration
public class BeanConfig1 {
    @Conditional(OnMissingBeanCondition.class) //@1
    @Bean
    public IService service1() {
        return new Service1();
    }
}

@1: Conditional judgment was used before the method

Another configuration class is responsible for registering Service2 to the container

package com.javacode2018.lesson001.demo25.test1;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

@Configuration
public class BeanConfig2 {
    @Conditional(OnMissingBeanCondition.class)//@1
    @Bean
    public IService service2() {
        return new Service2();
    }
}

@1: Conditional judgment was used before the method

To a general configuration class, import the other two configuration classes

package com.javacode2018.lesson001.demo25.test1;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@Import({BeanConfig1.class,BeanConfig2.class}) //@1
public class MainConfig1 {
}

@1: Import the other 2 configuration classes through @ import

Let's have a test case

ConditionTest adds a new method, which obtains the bean of IService type from the container, and then outputs:

@Test
public void test1() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig1.class);
    Map<String, IService> serviceMap = context.getBeansOfType(IService.class);
    serviceMap.forEach((beanName, bean) -> {
        System.out.println(String.format("%s->%s", beanName, bean));
    });
}

Run output:

service1->com.javacode2018.lesson001.demo25.test1.Service1@2cd76f31

You can see that there is only one bean of type IService in the container.

You can remove @ Conditional from the two methods marked with @ Bean, and then run it to output:

service1->com.javacode2018.lesson001.demo25.test1.Service1@49438269
service2->com.javacode2018.lesson001.demo25.test1.Service2@ba2f4ec

At this time, there are no restrictions. Both services will be registered to the container.

Case 4: select configuration class according to environment

Usually, when we do projects, there are development environment, test environment and online environment. Some information in each environment is different, such as the configuration information of the database. Let's simulate using different configuration classes to register different bean s in different environments.

Customize a conditional annotation

package com.javacode2018.lesson001.demo25.test2;

import org.springframework.context.annotation.Conditional;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Conditional(EnvCondition.class) //@1
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnvConditional {
    //Environment (test environment, development environment, production environment)
    enum Env { //@2
        TEST, DEV, PROD
    }

    //environment
    Env value() default Env.DEV; //@3
}

@1: Note that this annotation is special. The @ Conditional annotation is used above this annotation, and a custom condition class is used here: EnvCondition

@2: Enumeration, which represents the environment and defines three environments

@3: This parameter is used to specify the environment

The above annotation will be used for configuration classes in different environments later

Here are three configuration classes

Let the three configuration classes take effect in different environments. The above customized @ EnvConditional annotation will be used on these configuration classes for conditional qualification.

In each configuration class, a bean named name is defined through @ bean. The bean will be output to determine which configuration class is effective.

Let's look at the code of three configuration classes

Test environment configuration class

package com.javacode2018.lesson001.demo25.test2;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnvConditional(EnvConditional.Env.TEST)//@1
public class TestBeanConfig {
    @Bean
    public String name() {
        return "I'm the test environment!";
    }
}

@1 specified test environment

Development environment configuration class

package com.javacode2018.lesson001.demo25.test2;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnvConditional(EnvConditional.Env.DEV) //@1
public class DevBeanConfig {
    @Bean
    public String name() {
        return "I am the development environment!";
    }
}

@1: Specified development environment

Production environment configuration class

package com.javacode2018.lesson001.demo25.test2;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnvConditional(EnvConditional.Env.PROD) //@1
public class ProdBeanConfig {
    @Bean
    public String name() {
        return "I am the production environment!";
    }
}

@1: Specified production environment

Let's take a look at the condition class: EnvCondition

The condition class will parse the @ EnvConditional annotation on the configuration class to get the environment information.

Then compare with the current environment and decide whether to return true or false, as follows:

package com.javacode2018.lesson001.demo25.test2;

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

public class EnvCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        //Current environment
        EnvConditional.Env curEnv = EnvConditional.Env.DEV; //@1
        //Gets the corresponding environment in the EnvCondition annotation on the class using the condition
        EnvConditional.Env env = (EnvConditional.Env) metadata.getAllAnnotationAttributes(EnvConditional.class.getName()).get("value").get(0);
        return env.equals(curEnv);
    }

}

@1: This is used to specify the current environment. It is assumed that the current development environment is used. We can play this at will in the future. For example, put these into the configuration file for the convenience of demonstration.

Let's have a test case

@Test
public void test2() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig2.class);
    System.out.println(context.getBean("name"));
}

Run output

I am the development environment!

You can see that the development environment is in effect.

Modify the EnvCondition code and switch to the production environment:

EnvConditional.Env curEnv = EnvConditional.Env.PROD;

Run the test2 method again and output:

I am the production environment!

The production environment configuration class is effective.

Case 5: Condition specifying priority

Multiple conditions are executed sequentially

@When value in constraint specifies multiple constraints, they will be executed in sequence by default. Let's see the effect through the code.

Three conditions are defined in the following code. The current class name will be output in the matches method of each Condition, and then these three conditions will be used simultaneously on the configuration class:

package com.javacode2018.lesson001.demo25.test5;

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

class Condition1 implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        System.out.println(this.getClass().getName());
        return true;
    }
}

class Condition2 implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        System.out.println(this.getClass().getName());
        return true;
    }
}

class Condition3 implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        System.out.println(this.getClass().getName());
        return true;
    }
}

@Configuration
@Conditional({Condition1.class, Condition2.class, Condition3.class})
public class MainConfig5 {
}

Let's have a test case

@Test
public void test5() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig5.class);
}

Run output:

com.javacode2018.lesson001.demo25.test5.Condition1
com.javacode2018.lesson001.demo25.test5.Condition2
com.javacode2018.lesson001.demo25.test5.Condition3
com.javacode2018.lesson001.demo25.test5.Condition1
com.javacode2018.lesson001.demo25.test5.Condition2
com.javacode2018.lesson001.demo25.test5.Condition3
com.javacode2018.lesson001.demo25.test5.Condition1
com.javacode2018.lesson001.demo25.test5.Condition2
com.javacode2018.lesson001.demo25.test5.Condition3

There are multiple lines of output above because spring performs conditional judgment in several places during the process of parsing the entire configuration class.

We only focus on the first three lines. We can see that the order of the output attributes and the value value in @ Conditional is the same.

Specify the order of conditions

Custom conditions can implement the PriorityOrdered interface, inherit the Ordered interface, or use the @ Order annotation to specify the priority of these conditions.

Sorting rules: first sort by PriorityOrdered, and then sort by the value of order; That is: PriorityOrdered asc,order value asc

The following can be specified order Value of
 Interface: org.springframework.core.Ordered,There's one getOrder Method to return int Value of type
 Interface: org.springframework.core.PriorityOrdered,Inherited Ordered Interface, so there are getOrder method
 Notes: org.springframework.core.annotation.Order,There's one int Type value Parameter assignment Order Size of

Look at the case code:

package com.javacode2018.lesson001.demo25.test6;


import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.annotation.Order;
import org.springframework.core.type.AnnotatedTypeMetadata;

@Order(1) //@1
class Condition1 implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        System.out.println(this.getClass().getName());
        return true;
    }
}

class Condition2 implements Condition, Ordered { //@2
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        System.out.println(this.getClass().getName());
        return true;
    }

    @Override
    public int getOrder() { //@3
        return 0;
    }
}

class Condition3 implements Condition, PriorityOrdered { //@4
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        System.out.println(this.getClass().getName());
        return true;
    }

    @Override
    public int getOrder() {
        return 1000;
    }
}

@Configuration
@Conditional({Condition1.class, Condition2.class, Condition3.class})//@5
public class MainConfig6 {
}

@1: Condition1 specifies the Order through @ Order, and the value is 1

@2: Condition2 specifies the order by implementing the Ordered interface, and the @ 3: getOrder method returns 1

@4: Condition3 implements the PriorityOrdered interface. To implement this interface, you need to rewrite the getOrder method and return 1000

@5: Condtion order is 1, 2 and 3

According to the sorting rules, PriorityOrdered will be ranked first, and then in ascending order according to order. The final order can be:

Condtion3->Condtion2->Condtion1

Let's take a test case to see if the effect is as follows:

@Test
public void test6() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig6.class);
}

Run test6, and some outputs are as follows:

com.javacode2018.lesson001.demo25.test6.Condition3
com.javacode2018.lesson001.demo25.test6.Condition2
com.javacode2018.lesson001.demo25.test6.Condition1

The results are consistent with our analysis.

Case 6: configuration condition usage

ConfigurationCondition is rarely used and will not be introduced in many places. The Condition interface can basically meet 99% of the requirements, but ConfigurationCondition is widely used in spring boot.

ConfigurationCondition is difficult to understand through explanation. Take a case to experience:

A common class: Service

package com.javacode2018.lesson001.demo25.test7;

public class Service {
}

Come to a configuration class and register the above Service through the configuration class

package com.javacode2018.lesson001.demo25.test7;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class BeanConfig1 {
    @Bean
    public Service service() {
        return new Service();
    }
}

Another configuration class: BeanConfig2

package com.javacode2018.lesson001.demo25.test7;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class BeanConfig2 {
    @Bean
    public String name() {
        return "Passerby a Java";
    }
}

To a general configuration class

package com.javacode2018.lesson001.demo25.test7;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@Import({BeanConfig1.class, BeanConfig2.class})
public class MainConfig7 {
}

The above introduces two other configuration classes through @ Import

Use a test case to load the MainConfig7 configuration class

@Test
public void test7() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig7.class);
    context.getBeansOfType(String.class).forEach((beanName, bean) -> {
        System.out.println(String.format("%s->%s", beanName, bean));
    });
}

Above, get the String type bean from the container and output it.

Run output

name->Passerby a Java

Now we have a need

BeanConfig2 takes effect only when there is a bean of Service type in the container.

It's very simple. Just add a Condition. Internally judge whether there are Service type bean s in the container. Continue

A custom Condition

package com.javacode2018.lesson001.demo25.test7;


import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class MyCondition1 implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        //Get spring container
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        //Determine whether there is a bean of Service type in the container
        boolean existsService = !beanFactory.getBeansOfType(Service.class).isEmpty();
        return existsService;
    }
}

The above code is very simple to determine whether there are IService type bean s in the container.

Use Condition judgment on BeanConfig2

@Configuration
@Conditional(MyCondition1.class)
public class BeanConfig2 {
    @Bean
    public String name() {
        return "Passerby a Java";
    }
}

Run test7 output again

No output

Why?

As we said earlier in the article, the processing of configuration class will go through two stages: configuration class resolution stage and Bean registration stage. The conditions of Condition interface type will be valid for both stages. In the resolution stage, there is no Service Bean in the container, and the beans defined through @ Bean annotation in the configuration class will be registered in the spring container in the Bean registration stage, Therefore, BeanConfig2 cannot see the Service Bean in the container during the parsing phase, so it is rejected.

At this point, we need to use the ConfigurationCondition to make the condition judgment take effect in the bean registration stage.

Customize a ConfigurationCondition class

package com.javacode2018.lesson001.demo25.test7;

import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.ConfigurationCondition;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class MyConfigurationCondition1 implements ConfigurationCondition {
    @Override
    public ConfigurationPhase getConfigurationPhase() {
        return ConfigurationPhase.REGISTER_BEAN; //@1
    }

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        //Get spring container
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        //Determine whether there is a bean of Service type in the container
        boolean existsService = !beanFactory.getBeansOfType(Service.class).isEmpty();
        return existsService;
    }
}

@1: The specified condition is valid only in the bean registration phase

The contents of the matches method are copied directly, and the judgment rules remain unchanged.

Modify the class content of BeanConfig2

take
@Conditional(MyCondition1.class)
Replace with
@Conditional(MyConfigurationCondition1.class)

Run test7 output again

name->Passerby a Java

At this point, the bean name is output.

You can try to remove the @ Bean from the service method in BeanConfig1. At this time, the service will not be registered in the container. Run test7 again and you will find that there is no output. At this time, BeanConfig2 will fail.

The ConfigurationCondition interface is usually used to determine whether the bean exists or not. The phase is REGISTER_BEAN, which ensures that the conditional judgment is performed during the bean registration phase.

Familiar with springboot, there are many @ Conditionxxx annotations. You can take a look at these annotations. Many of them implement the ConfigurationCondition interface.

Spring source code

@The Conditional annotation is handled by the following class

org.springframework.context.annotation.ConfigurationClassPostProcessor

It's this class again. I've said it many times. It's a very important class. Let's go down and talk about the source code of this class more, so that we can understand it more smoothly.

Case source code

https://gitee.com/javacode2018/spring-series

All the case codes of passerby a java will be put on this in the future. Let's watch it and continue to pay attention to the dynamics.

summary

  1. @The Conditional annotation can be marked on the objects that spring needs to process (configuration class and @ Bean method), which is equivalent to adding a Conditional judgment. Through the judgment result, spring will feel whether to continue to process the objects marked by this annotation

  2. Spring handles configuration classes in roughly two processes: parsing configuration classes and registering bean s. In both processes, @ Conditional can be used to control whether spring needs to handle this process

  3. Condition is valid for both processes by default

  4. The configuration condition is controlled in more detail, and can be controlled to the specific stage of use condition judgment

Tags: Java Spring Back-end

Posted on Tue, 16 Nov 2021 07:26:35 -0500 by MHardeman25