Chapter 02 @Conditional,@Import,@FactoryBean

Section 01 – @Conditional

@Conditional: Optionally inject beans based on conditions

ConditionalBeanConfig added a configuration class ConditionalBeanConfig under config

@Configuration
public class ConditionalBeanConfig {

    @Bean("stark")
    public Person stark(a){
        System.out.println("Stark is instantiated.");
        Person person = new Person();
        person.setName("stark");
        person.setAge(40);
        return person;
    }

    @Bean("peter")
    public Person peter(a){
        System.out.println("Peter is instantiated");
        Person person = new Person();
        person.setName("peter");
        person.setAge(18);
        return person;
    }

    @Bean("thor")
    public Person thor(a){
        System.out.println("Thor is instantiated.");
        Person person = new Person();
        person.setName("thor");
        person.setAge(3000);
        returnperson; }}Copy the code

ConditionalBeanTest added

public class ConditionalBeanTest {

    @Test
    public void testConditionalBean(a){
        ApplicationContext context = new AnnotationConfigApplicationContext(ConditionalBeanConfig.class);
        System.out.println("IoC container initialization completed"); }}Copy the code

Run the test and the console output is as follows

How does the test run instantiate different beans depending on the operating system? That is, how can I optionally inject beans, which requires the @Conditional annotation, and define the selection Condition by implementing the Condition interface

Add two custom Condition classes WinCondition and MacCondition to the config package, which implement the matches method inCondition

public class WinCondition implements Condition {

    /** * Filter criteria, return true or false *@paramContext Determines the context in which the condition can be used@paramMetadata annotation information *@return* /
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // Check whether it is Windows
        // Get the beanFactory being used by the IoC container
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        Environment environment = context.getEnvironment();
        String osName = environment.getProperty("os.name");
        System.out.println(osName);
        // If the condition is met, return true
        if (osName.contains("Windows")) {return true;
        }
        return false; }}Copy the code
public class MacCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); Environment environment = context.getEnvironment(); String osName = environment.getProperty("os.name"); System.out.println(osName); if (osName.contains("Mac")){ return true; } return false; }}Copy the code

Tips FactoryBean is different from BeanFactory. BeanFactory: Used to get instantiated beans from the container. ApplicationContext indirectly inherits the BeanFactory

To register into the container in the ConditionalBeanConfig Bean increased conditions @ Conditional comments, said, according to the conditions of selective injection by whatever system into stark, without any conditions, Peter injection will determine the operating system before, Thor is injected only if the operating system is Win and Thor is injected only if the operating system is Mac

@Configuration public class ConditionalBeanConfig { @Bean("stark") public Person stark(){ System.out.println(" Stark is instantiated "); Person person = new Person(); person.setName("stark"); person.setAge(40); return person; } @conditional (wincondition.class) @bean (" Peter ") public Person Peter (){system.out.println (" Peter instantiated ");} @conditional (wincondition.class) @bean (" Peter ") public Person Peter (){system.out.println (" Peter instantiated "); Person person = new Person(); person.setName("peter"); person.setAge(18); return person; } @conditional (macconditional.class) @bean ("thor") public Person thor(){system.out.println (" Thor instantiated ");} @conditional (maccondition.class) @bean ("thor") public Person thor(){system.out.println (" Thor instantiated "); Person person = new Person(); person.setName("thor"); person.setAge(3000); return person; }}Copy the code

Perform the test and check the console print. The conditional injection is successful and only Stark and Thor are injected

Section 02 – @Import

@Import

  • Manually add components to the IoC container
  • Use ImportSelector to customize the return component, which is the Bean to inject into the container
  • Use custom components ImportBeanDefinitionRegistrar return, return the component is to injection container Bean

Several ways to register beans into an IoC container in a configuration class

  1. The method returns the type of the Bean and the method name defaults to the Bean ID. The Bean ID can also be changed by @bean (“Bean ID”), usually used to import third-party components
  2. Registered in IoC containers with @ComponentScan + @Component annotations (including @Controller, @Service, @Repository), usually used to import Controllers, The Controller and Service classes in the Service package are commonly used in SSM frameworks to replace the @ComponentScan annotation with a configuration file
  3. @ Import can quickly into the container, the component registration using the @ Import after Import, the Bean’s default ID for the full path name of a class, can also custom implementations ImportSelector, ImportBeanDefinitionRegistrar will Bean manually register into the container, All registered Bean can use BeanDefinitionRegistry interface, the realization of his class DefaultListableBeanFactory implements registerBeanDefinition () method, the beans into a Map

Add the entity class Role, User to the Entity package, using Lombok’s @data annotation to automatically generate getter/setter/toString methods

@Data
public class User {

    private String username;
    private String password;
}
Copy the code
@Data
public class Role {

    private Long id;
    private Long roleId;
    private String roleName;
    private String roleDesc;

}
Copy the code

Add ImportBeanConfig to the config package. @import is used in config classes where value is an array that can add bytecode to multiple classes

@Configuration
@Import(value = {Role.class, User.class})
public class ImportBeanConfig {

    @Bean("stark")
    public Person stark(a){
        System.out.println("Stark is instantiated.");
        Person person = new Person();
        person.setName("stark");
        person.setAge(40);
        returnperson; }}Copy the code

Add the test class ImportBeanTest to get the IDS of all beans in the container and perform tests to view beans in the container

public class ImportBeanTest { @Test public void getBeanByImport(){ ApplicationContext context = new AnnotationConfigApplicationContext(ImportBeanConfig.class); System.out.println("IoC container initialization complete "); String[] beanDefinitionNames = context.getBeanDefinitionNames(); for (String beanDefinitionName : beanDefinitionNames) { System.out.println(beanDefinitionName); }}}Copy the code

The console printed successfully, and User and Role were successfully injected into the IoC container

ImportSelectors return an array of the full pathnames of the components to be registered with the IoC container. You need to customize the implementation so you implement selectImports directly in the ImportSelector class without changing anything

public class CustImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return null; }}Copy the code

Add CustImportSelector. Class to the @import annotation in ImportBeanConfig

@Configuration
@Import(value = {Role.class, User.class, CustImportSelector.class})
public class ImportBeanConfig {

    @Bean("stark")
    public Person stark(a){
        System.out.println("Stark is instantiated.");
        Person person = new Person();
        person.setName("stark");
        person.setAge(40);
        returnperson; }}Copy the code

Execute ImportBeanTest to see the console print

Plus: Debug Error cause

To set breakpointsStart Debug, then step into

Step into twice in a row. In the figure below, classNames is the array of classNames to return. This is empty because null is returned in custom classes

Step Into twice in a row, and you get to the error,

It can be seen that the root cause of the error is the return of null in the custom class. Therefore, the return of importSelectors cannot be used correctly for the null ImportSelector interface

Modify the importSelectors method in CustImportSelector

public class CustImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"com.citi.entity.Product"."com.citi.entity.Category"}; }}Copy the code

When executing ImportBeanTest again, Stark is registered into the container with the @bean annotation, Role and User are injected into the container with @import, and Product and Category are registered with the custom class ImportSelect interface

Using a custom class implements ImportBeanDefinitionRegistrar interface into the new one Order entity Bean classes

@Data
public class Order {
    private Integer id;

    private String orderNo;

    private Integer userId;

    private Integer totalPrice;

}
Copy the code

Custom class implements ImportBeanDefinitionRegistrar, registerBeanDefinition () method needs to pass in a BeanDefination, BeanDefination is an interface, RootBeanDefinition indirectly inherits BeanDefinition, so you can instantiate a RootBeanDefinition into the registerBeanDefinition() method

public class CustImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
        boolean containProduct = registry.containsBeanDefinition("com.citi.entity.Product");
        boolean containCategory = registry.containsBeanDefinition("com.citi.entity.Category");

        // If both are true, Order is injected
        if (containCategory && containProduct){
            BeanDefinition orderDefinition = new RootBeanDefinition(Order.class);
            registry.registerBeanDefinition("order",orderDefinition); }}}Copy the code

Source DefaultListableBeanFactory realized registerBeanDefinition, will Bean put into a beanDefinitionMapBeanDefinitionMap is a map-type data structure

Use custom CustImportBeanDefinitionRegistrar classes in ImportBeanConfig @ Import annotation on the class to add a custom value in the array

@Configuration
@Import(value = {Role.class, User.class, CustImportSelector.class,CustImportBeanDefinitionRegistrar.class})
public class ImportBeanConfig {

    @Bean("stark")
    public Person stark(a){
        System.out.println("Stark is instantiated.");
        Person person = new Person();
        person.setName("stark");
        person.setAge(40);
        returnperson; }}Copy the code

Execute the test that order exists when the instantiated objects of Product and Category exist

Remove the custom ImportSelect class from the value array in the @import annotation on the ImportBeanConfig class, that is, no longer register products and categories in the container

@Configuration
@Import(value = {Role.class, User.class,CustImportBeanDefinitionRegistrar.class})
public class ImportBeanConfig {

    @Bean("stark")
    public Person stark(a){
        System.out.println("Stark is instantiated.");
        Person person = new Person();
        person.setName("stark");
        person.setAge(40);
        returnperson; }}Copy the code

At this point, the test is performed to see if there is an order instantiation object in the container

Can be seen that the container does not exist in the Product Category and instantiate objects, also there is no Order to instantiate objects, explain CustImportBeanDefinitionRegistrar conditions of success

Section 03 – FactoryBean

The FactoryBean interface can also implement registering beans to the container, but to implement the interface’s methods,Person is the Bean to import

public class CustFactoryBean implements FactoryBean<Person> {

    @Override
    public Person getObject(a) throws Exception {
        Person person = new Person();
        person.setName("peter");
        person.setAge(18);
        return person;
    }

    @Override
    publicClass<? > getObjectType() {return Person.class;
    }

    @Override
    public boolean isSingleton(a) {
        return true; }}Copy the code

There are two ways to use CustFactoryBean, either in the vlaue array of the @import annotation, or by adding methods to the configuration class that return CustFactoryBean, Adding @beans to the method is to inject the custom CustFactoryBean into the container, get the CustFactoryBean from the container, and call the getObejct() method to get the Person Bean

@configuration public class ImportBeanConfig {@bean ("stark") public Person Stark (){system.out.println ("stark instantiated "); Person person = new Person(); person.setName("stark"); person.setAge(40); return person; } @Bean("custFactoryBean") public CustFactoryBean custFactoryBean(){ return new CustFactoryBean(); }}Copy the code

or

@Configuration @Import(value = {CustFactoryBean.class}) public class ImportBeanConfig { @Bean("stark") public Person Stark (){system.out.println (" Stark is instantiated "); Person person = new Person(); person.setName("stark"); person.setAge(40); return person; }}Copy the code

To create a FactoryBeanTest, first get CustFactoryBean and then call the getObject method of the class to get the Person Bean

public class FactoryBeanTest {

    @Test
    public void getBeanByImport(a){
        ApplicationContext context = new AnnotationConfigApplicationContext(ImportBeanConfig.class);
        System.out.println("IoC container initialization completed");
        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
        }
        CustFactoryBean custFactoryBean = (CustFactoryBean) context.getBean(CustFactoryBean.class);
        Person person = null;
        try {
            person = (Person) custFactoryBean.getObject();
        } catch(Exception e){ e.printStackTrace(); } System.out.println(person); }}Copy the code

The console successfully prints CustFactoryBean and the Person instantiation object Peter

Modify the isSingleton method of CustFactoryBean from singleton to multi-instance

@Override
public boolean isSingleton(a) {
    return false;
}
Copy the code

Add a test method to FactoryBeanTest that takes two Person instantiations to see if they are the same object

@Test public void getMultiInstanceByCustFactoryBean(){ ApplicationContext context = new AnnotationConfigApplicationContext(ImportBeanConfig.class); System.out.println("IoC container initialization complete "); String[] beanDefinitionNames = context.getBeanDefinitionNames(); for (String beanDefinitionName : beanDefinitionNames) { System.out.println(beanDefinitionName); } CustFactoryBean custFactoryBean = (CustFactoryBean) context.getBean(CustFactoryBean.class); Person person = null; Person person1 = null; try { person = (Person) custFactoryBean.getObject(); person1 = (Person) custFactoryBean.getObject(); } catch (Exception e){ e.printStackTrace(); } System.out.println(person); System.out.println(person == person1); }Copy the code

The console prints false, two Person objects do not want to wait, indicating multiple cases