In-depth Spring principles-3.Bean post-processor

  • About the author: Hello everyone, I am Tudou Ni who loves cheese. I am a Java contestant in the 24th class of school admissions. Nice to meet you all.
  • Series of columns: Spring source code, JUC source code
  • If you feel that the blogger’s article is not bad, please support the blogger three times in a row.
  • The blogger is working hard to complete the 2023 plan: source code tracing to find out.
  • Contact information: nhs19990716, add me to the group, let’s learn together, make progress together, and fight against the Internet winter together

Article directory

    • The relationship between Bean post-processor and annotations
    • Detailed explanation of the process of Bean post-processor taking effect
    • Relationship between BeanFactory post-processor and annotations
    • Detailed explanation of the process of BeanFactory post-processor taking effect
      • @ComponentScan
      • @Bean
      • @Mapper

The relationship between Bean post-processor and annotations

First, let’s illustrate it with an example without adding additional post-processors:?GenericApplicationContext

GenericApplicationContext context = new GenericApplicationContext();

public class Bean1 {<!-- -->
    private static final Logger log = LoggerFactory.getLogger(Bean1.class);

    private Bean2 bean2;

    @Autowired
    public void setBean2(Bean2 bean2) {<!-- -->
        log.debug("@Autowired takes effect: {}", bean2);
        this.bean2 = bean2;
    }

    @Autowired
    private Bean3 bean3;

    @Resource
    public void setBean3(Bean3 bean3) {<!-- -->
        log.debug("@Resource takes effect: {}", bean3);
        this.bean3 = bean3;
    }

    private String home;

    @Autowired
    public void setHome(@Value("${JAVA_HOME}") String home) {<!-- -->
        log.debug("@Value takes effect: {}", home);
        this.home = home;
    }

    @PostConstruct
    public void init() {<!-- -->
        log.debug("@PostConstruct takes effect");
    }

    @PreDestroy
    public void destroy() {<!-- -->
        log.debug("@PreDestroy takes effect");
    }

    @Override
    public String toString() {<!-- -->
        return "Bean1{" +
               "bean2=" + bean2 +
               ", bean3=" + bean3 +
               ", home='" + home + ''' +
               '}';
    }
}

public class Bean2 {<!-- -->
}

public class Bean3 {<!-- -->
}

// Register three beans using original methods
context.registerBean("bean1", Bean1.class);
context.registerBean("bean2", Bean2.class);
context.registerBean("bean3", Bean3.class);

context.refresh(); // Execute beanFactory post-processor, add bean post-processor, initialize all singletons

After running, it was found that bean1 bean2 bean3 was not registered successfully!

The reason is that @Autowired and @value in bean1 do not take effect, so enable it next

//Autowired candidate parser
        context.getDefaultListableBeanFactory().setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
        context.registerBean(AutowiredAnnotationBeanPostProcessor.class); // @Autowired @Value

At this point @Autowired and @Value in the bean are injected

[DEBUG] 21:07:55.642 [main] com.itheima.a04.Bean1 - @Value takes effect: C:\Program Files\Java\jdk1.8.0_231
[DEBUG] 21:07:55.647 [main] com.itheima.a04.Bean1 - @Autowired takes effect: com.itheima.a04.Bean2@120f102b

And if you want @Resource @PostConstruct @PreDestroy to also be injected, you need to start this

context.registerBean(CommonAnnotationBeanPostProcessor.class); // @Resource @PostConstruct @PreDestroy

The effect is as follows:

[DEBUG] 21:12:03.082 [main] com.itheima.a04.Bean1 - @Resource Effective: com.itheima.a04.Bean3@37858383
[DEBUG] 21:12:03.093 [main] com.itheima.a04.Bean1 - @Autowired Effective: com.itheima.a04.Bean2@59af0466
[DEBUG] 21:12:03.102 [main] com.itheima.a04.Bean1 - @Value takes effect: C:\Program Files\Java\jdk1.8.0_231
[DEBUG] 21:12:03.102 [main] com.itheima.a04.Bean1 - @PostConstruct takes effect
[DEBUG] 21:12:03.186 [main] com.itheima.a04.Bean1 - @PreDestroy takes effect

From the above, we can actually see that the parsing of annotations such as @Autowired belongs to the extended functions of the bean life cycle stage (dependency injection, initialization), and these extended functions are completed by the bean’s post-processor.

And if you carefully observe the output results, you can find that @Resource is used first and then @Autowired. This is related to the execution order of its source code, and its @Resource logic is judged before @Autowired.

The above is to load some ordinary classes, but it is actually not possible to load configuration classes.

@ConfigurationProperties(prefix = "java")
public class Bean4 {<!-- -->

    private String home;

    private String version;

    public String getHome() {<!-- -->
        return home;
    }

    public void setHome(String home) {<!-- -->
        this.home = home;
    }

    public String getVersion() {<!-- -->
        return version;
    }

    public void setVersion(String version) {<!-- -->
        this.version = version;
    }

    @Override
    public String toString() {<!-- -->
        return "Bean4{" +
               "home='" + home + ''' +
               ", version='" + version + ''' +
               '}';
    }
}

context.registerBean("bean4", Bean4.class);
System.out.println(context.getBean(Bean4.class));

It was found that the corresponding home and version were not successfully loaded in its configuration class.

Bean4{home='null', version='null'}

In fact, you still need to add a new post-processor here:

ConfigurationPropertiesBindingPostProcessor.register(context.getDefaultListableBeanFactory());

In fact, the essence here is to explain the relationship between common annotations and post-processors. The corresponding post-processors need to be added to make these annotations effective.

Detailed explanation of the process of Bean post-processor taking effect

Let’s take the above code as an example:

DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        beanFactory.registerSingleton("bean2", new Bean2()); // Creation process, dependency injection, initialization
        beanFactory.registerSingleton("bean3", new Bean3());
        beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver()); // @Value

In fact, what just made @Autowired and @Value work is AutowiredAnnotationBeanPostProcessor.class

Then let’s go inside and take a look at the specific implementation process.

// Find which properties and methods are @Autowired, this is called InjectionMetadata
AutowiredAnnotationBeanPostProcessor processor = new AutowiredAnnotationBeanPostProcessor();
        processor.setBeanFactory(beanFactory);

        // If you create it yourself, dependency injection, etc. will not take effect.
        Bean1 bean1 = new Bean1();
        System.out.println(bean1);
        processor.postProcessProperties(null, bean1, "bean1"); // Perform dependency injection @Autowired @Value
        System.out.println(bean1);

Output:

Bean1{bean2=null, bean3=null, home='null'}
[DEBUG] 21:32:35.864 [main] com.itheima.a04.Bean1 - @Value takes effect: ${JAVA_HOME}
[DEBUG] 21:32:35.873 [main] com.itheima.a04.Bean1 - @Autowired Effective: com.itheima.a04.Bean2@4562e04d
Bean1{bean2=com.itheima.a04.Bean2@4562e04d, bean3=com.itheima.a04.Bean3@2a65fe7c, home='${JAVA_HOME}'}

In fact, it can be found that when processor.postProcessProperties(null, bean1, “bean1”);, bean1 has actually been injected

Then go to the postProcessProperties method to view

public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {<!-- -->
        InjectionMetadata metadata = this.findAutowiringMetadata(beanName, bean.getClass(), pvs);

        try {<!-- -->
            metadata.inject(bean, beanName, pvs);
            return pvs;
        } catch (BeanCreationException var6) {<!-- -->
            throw var6;
        } catch (Throwable var7) {<!-- -->
            throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", var7);
        }
    }

As you can see, findAutowiringMetadata is actually those attributes. Those method parameters are annotated with Autowired. After they are found, they are encapsulated into InjectionMetadata and then Injected.

Enter findAutowiringMetadata to view the following findings:

private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {<!-- -->
        String cacheKey = StringUtils.hasLength(beanName) ? beanName : clazz.getName();
        InjectionMetadata metadata = (InjectionMetadata)this.injectionMetadataCache.get(cacheKey);
        if (InjectionMetadata.needsRefresh(metadata, clazz)) {<!-- -->
            synchronized(this.injectionMetadataCache) {<!-- -->
                metadata = (InjectionMetadata)this.injectionMetadataCache.get(cacheKey);
                if (InjectionMetadata.needsRefresh(metadata, clazz)) {<!-- -->
                    if (metadata != null) {<!-- -->
                        metadata.clear(pvs);
                    }

                    metadata = this.buildAutowiringMetadata(clazz);
                    this.injectionMetadataCache.put(cacheKey, metadata);
                }
            }
        }

        return metadata;
    }

It is private, so we first obtain this method through reflection:

//Get the private method
        Method findAutowiringMetadata = AutowiredAnnotationBeanPostProcessor.class.getDeclaredMethod("findAutowiringMetadata", String.class, Class.class, PropertyValues.class);
        findAutowiringMetadata.setAccessible(true);
// This line of code actually executes findAutowiringMetadata, analyzes the bean1 class, which one has autowire modification, and collects it with the parser
        InjectionMetadata metadata = (InjectionMetadata) findAutowiringMetadata.invoke(processor, "bean1", Bean1.class, null);//Get the @Value @Autowired member variables and method parameter information added to Bean1
// Since some methods do not override toString, you need to add breakpoints to test
        System.out.println(metadata);

The breakpoints are as shown in the figure:

At this point, we have actually found all the @Autowired

At this time, InjectionMetadata is called to perform dependency injection, and the value is searched by type during injection.

metadata.inject(bean1, "bean1", null);
System.out.println(bean1);

And what did inject do? First, obtain the member variables or methods through reflection

Field bean3 = Bean1.class.getDeclaredField("bean3");
DependencyDescriptor dd1 = new DependencyDescriptor(bean3, false);
// Get the type based on the information of member variables
Object o = beanFactory.doResolveDependency(dd1, null, null, null);
System.out.println(o);

This code is actually the essence of inject. First, the Field object of the member variable named “bean3” in the Bean1 class is obtained through the reflection API, and then Spring internally encapsulates a DependencyDescriptor object to describe bean3. Dependencies of member variables, finally, the doResolveDependency() method will be called to resolve the dependencies of bean3, which means Spring will try to find and inject the beans required by the member variable named “bean3″, and then print the return value, which is mainly ” bean3″ dependency, that is, the required Bean instance.

The method of @Autowired is also similar:

Method setBean2 = Bean1.class.getDeclaredMethod("setBean2", Bean2.class);
DependencyDescriptor dd2 =
                new DependencyDescriptor(new MethodParameter(setBean2, 0), true);
Object o1 = beanFactory.doResolveDependency(dd2, null, null, null);
System.out.println(o1);

Method setHome = Bean1.class.getDeclaredMethod("setHome", String.class);
DependencyDescriptor dd3 = new DependencyDescriptor(new MethodParameter(setHome, 0), true);
Object o2 = beanFactory.doResolveDependency(dd3, null, null, null);
System.out.println(o2);

Relationship between BeanFactory post-processor and annotations

Let’s explain it with an example.

GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("config", Config.class);

@Configuration
@ComponentScan("com.itheima.a05.component")
public class Config {<!-- -->
    @Bean
    public Bean1 bean1() {<!-- -->
        return new Bean1();
    }

    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {<!-- -->
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        return sqlSessionFactoryBean;
    }

    @Bean(initMethod = "init")
    public DruidDataSource dataSource() {<!-- -->
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl("jdbc:mysql://localhost:3306/test");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        return dataSource;
    }
}

public class Bean1 {<!-- -->

    private static final Logger log = LoggerFactory.getLogger(Bean1.class);

    public Bean1() {<!-- -->
        log.debug("I am managed by Spring");
    }
}

context.refresh();

for (String name : context.getBeanDefinitionNames()) {<!-- -->
   System.out.println(name);
}

In fact, the output will only print out config, and nothing else. Combining the relationship between the Bean post-processor and annotations, we can easily think that there must be some missing post-processor, so other output cannot be output.

context.registerBean(ConfigurationClassPostProcessor.class); // @ComponentScan @Bean @Import @ImportResource

We found that the output was consistent with what we expected:

[DEBUG] 22:07:04.915 [main] com.itheima.a05.component.Bean2 - I am managed by Spring
[DEBUG] 22:07:04.919 [main] com.itheima.a05.component.Bean3 - I am managed by Spring
[DEBUG] 22:07:04.928 [main] com.itheima.a05.Bean1 - I am managed by Spring
[INFO] 22:07:05.039 [main] c.a.druid.pool.DruidDataSource - {dataSource-1} inited
config
org.springframework.context.annotation.ConfigurationClassPostProcessor
bean2
bean3
bean1
sqlSessionFactoryBean
dataSource
[INFO] 22:07:05.107 [main] c.a.druid.pool.DruidDataSource - {dataSource-1} closing ...
[INFO] 22:07:05.108 [main] c.a.druid.pool.DruidDataSource - {dataSource-1} closed

The above actually configures our beanFactory. The configurations in the config are all registered as beans. Anyone who has used Mybatis knows that in the end we use the Mapper interface. If we configure the Mapper interface, will it be registered? Woolen cloth?

@Mapper
public interface Mapper1 {<!-- -->
}

@Mapper
public interface Mapper2 {<!-- -->
}

The test found that it did not, so another post-processor needs to be configured.

context.registerBean(MapperScannerConfigurer.class, bd -> {<!-- --> // @MapperScanner automatic configuration indirectly uses this
            bd.getPropertyValues().add("basePackage", "com.itheima.a05.mapper");
        });

At this time, you can see in the results that mapper1 and mapper2 have been registered.

From the above, we can see that the parsing of @ComponentScan, @Bean, @Mapper and other annotations belongs to the extended functions of the core container (i.e. BeanFactory). These extended functions are completed by different BeanFactory post-processors. In fact, they mainly supplement some bean definitions. .

Detailed explanation of the process of BeanFactory post-processor taking effect

@ComponentScan

public class ComponentScanPostProcessor implements BeanDefinitionRegistryPostProcessor {<!-- -->
    @Override // context.refresh This method is called before all bean definitions have been loaded and the beans are instantiated. It allows you to perform post-processing operations on the bean factory before instantiation
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {<!-- -->

    }
    // Simulation analysis
    @Override // This method is called before all bean definitions are loaded but not instantiated. It allows you to modify, add or delete registered bean definitions.
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException {<!-- -->
        try {<!-- -->
            //Scan the ComponentScan annotation in the Config class
            ComponentScan componentScan = AnnotationUtils.findAnnotation(Config.class, ComponentScan.class);
            if (componentScan != null) {<!-- -->
                // Convenience package structure
                for (String p : componentScan.basePackages()) {<!-- -->
                    System.out.println(p);
                    // com.itheima.a05.component -> classpath*:com/itheima/a05/component/**/*.class
                    String path = "classpath*:" + p.replace(".", "/") + "/**/*.class";
                    System.out.println(path);
                    //Read the original information of the class
                    CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
                    // Get all qualified resource files based on the specified path
                    Resource[] resources = new PathMatchingResourcePatternResolver().getResources(path);
                    // Strategy class used to generate bean names
                    AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
                    for (Resource resource : resources) {<!-- -->
                        // System.out.println(resource);
                        //Read each class
                        MetadataReader reader = factory.getMetadataReader(resource);
                        // System.out.println("Class name:" + reader.getClassMetadata().getClassName());
                        // Get the annotation information on the class
                        AnnotationMetadata annotationMetadata = reader.getAnnotationMetadata();
                        // System.out.println("Whether @Component is added:" + annotationMetadata.hasAnnotation(Component.class.getName()));
                        // System.out.println("Whether @Component is derived:" + annotationMetadata.hasMetaAnnotation(Component.class.getName()));
                        if (annotationMetadata.hasAnnotation(Component.class.getName())
                            || annotationMetadata.hasMetaAnnotation(Component.class.getName())) {<!-- -->

                            /**
                             Creates a generic bean definition instance based on the provided class name (reader.getClassMetadata().getClassName()).
                             Specifically, it creates a RootBeanDefinition instance for a given class name, which contains metadata information about the class.
                             */

                            AbstractBeanDefinition bd = BeanDefinitionBuilder
                                    .genericBeanDefinition(reader.getClassMetadata().getClassName())
                                    .getBeanDefinition();

                            /**
                             Then, use the generateBeanName() method to generate a unique bean name. This method will be defined based on the given Bean
                             and Bean factory rules to generate an appropriate name. The generated name is usually based on the class name, with ordinal numbers added to ensure uniqueness in case of name conflicts.
                             */

                            String name = generator.generateBeanName(bd, beanFactory);

                            /**
                             Finally, register the generated Bean definition into the Bean factory through the beanFactory.registerBeanDefinition() method.
                             Enables the Bean to be managed and used by the Spring container.
                             */

                            beanFactory.registerBeanDefinition(name, bd);
                        }
                    }
                }
            }
        } catch (IOException e) {<!-- -->
            e.printStackTrace();
        }
    }
}

context.registerBean(ComponentScanPostProcessor.class); // Parse @ComponentScan

In fact, the essence is to read the package structure in the @ComponentScan annotation, and then traverse all the resources in each package path. If the corresponding resource has an @Component or a derived class, just inject it into it.

Output:

com.itheima.a05.component
classpath*:com/itheima/a05/component/**/*.class
[DEBUG] 22:18:26.118 [main] com.itheima.a05.component.Bean2 - I am managed by Spring
[DEBUG] 22:18:26.121 [main] com.itheima.a05.component.Bean3 - I am managed by Spring
config
com.itheima.a05.ComponentScanPostProcessor
bean2
bean3

@Bean

The above output does not actually inject the three beans in the config, so the bean injection needs to be improved:

public class AtBeanPostProcessor implements BeanDefinitionRegistryPostProcessor {<!-- -->
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {<!-- -->

    }

    /**

     Optimization points: Path and configuration classes are written to death
     Theoretically, first find all configuration classes, and then operate on the configuration classes

     */

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException {<!-- -->
        try {<!-- -->
            CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
            MetadataReader reader = factory.getMetadataReader(new ClassPathResource("com/itheima/a05/Config.class"));
            Set<MethodMetadata> methods = reader.getAnnotationMetadata().getAnnotatedMethods(Bean.class.getName());
            for (MethodMetadata method : methods) {<!-- -->
                System.out.println(method);

                //The analysis is similar
                String initMethod = method.getAnnotationAttributes(Bean.class.getName()).get("initMethod").toString();

                BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
                // Define the factory method in config
                builder.setFactoryMethodOnBean(method.getMethodName(), "config");
                // Automatic assembly, because sqlSessionFactoryBean has parameters, dataSource needs to be assembled.
                //Constructor method parameters, factory method parameters, automatic assembly, select assembly mode AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR
                builder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);

                if (initMethod.length() > 0) {<!-- -->

                    /**
                     The function of builder.setInitMethodName(initMethod) method is to set the initialization method name to BeanDefinitionBuilder.
                     Let the Spring container automatically call this specified initialization method after creating the bean instance.

                     Specifically, when using BeanDefinitionBuilder to build a BeanDefinition object, you can use this method to set the created bean
                     The name of the initialization method that needs to be executed so that this method is automatically called when creating the bean instance.
                     */

                    builder.setInitMethodName(initMethod);
                }

                AbstractBeanDefinition bd = builder.getBeanDefinition();
                beanFactory.registerBeanDefinition(method.getMethodName(), bd);
            }
        } catch (IOException e) {<!-- -->
            e.printStackTrace();
        }
    }
}

context.registerBean(AtBeanPostProcessor.class); // Parse @Bean

The essential parsing is actually similar. The difference is that there is no traversal of the package path level. You only need to traverse the methods in the specified config.class.

At this time, you can actually print the output and see that the beans inside have also been injected.

@Mapper

The mapper interface is actually not the same as the first two. For example, register it in BeanFactory:

 @Bean
    public MapperFactoryBean<Mapper1> mapper1(SqlSessionFactory sqlSessionFactory) {<!-- -->
        MapperFactoryBean<Mapper1> factory = new MapperFactoryBean<>(Mapper1.class);
        factory.setSqlSessionFactory(sqlSessionFactory);
        return factory;
    }

    @Bean
    public MapperFactoryBean<Mapper2> mapper2(SqlSessionFactory sqlSessionFactory) {<!-- -->
        MapperFactoryBean<Mapper2> factory = new MapperFactoryBean<>(Mapper2.class);
        factory.setSqlSessionFactory(sqlSessionFactory);
        return factory;
    }

In this way, mapper can actually be injected, but if all the interfaces under the mapper package are manually injected in this way, wouldn’t it be very troublesome, and it does not conform to the idea of automatic injection, right? Then it can be like this:

public class MapperPostProcessor implements BeanDefinitionRegistryPostProcessor {<!-- -->

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException {<!-- -->
        try {<!-- -->
            PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
            Resource[] resources = resolver.getResources("classpath:com/itheima/a05/mapper/**/*.class");
            AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
            CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
            for (Resource resource : resources) {<!-- -->
                MetadataReader reader = factory.getMetadataReader(resource);
                ClassMetadata classMetadata = reader.getClassMetadata();
                // Determine whether it is an interface or an implementation class
                if (classMetadata.isInterface()) {<!-- -->

                    /**
                     @Bean
                     public MapperFactoryBean<Mapper2> mapper2(SqlSessionFactory sqlSessionFactory) {
                        MapperFactoryBean<Mapper2> factory = new MapperFactoryBean<>(Mapper2.class);
                        factory.setSqlSessionFactory(sqlSessionFactory);
                        return factory;
                     }
                     */


                    AbstractBeanDefinition bd = BeanDefinitionBuilder.genericBeanDefinition(MapperFactoryBean.class)
                            .addConstructorArgValue(classMetadata.getClassName())
                            .setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE)
                            .getBeanDefinition();

                    AbstractBeanDefinition bd2 = BeanDefinitionBuilder.genericBeanDefinition(classMetadata.getClassName()).getBeanDefinition();

                    /**
                     If bd is used directly to generate the name, both mappers are called MapperFactoryBean because of MapperFactoryBean.class
                     So refer to the spring source code and use classMetadata.getClassName() as the name to generate the bean.
                     */

                    String name = generator.generateBeanName(bd2, beanFactory);
                    beanFactory.registerBeanDefinition(name, bd);
                }
            }
        } catch (IOException e) {<!-- -->
            e.printStackTrace();
        }

    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {<!-- -->

    }
}

In fact, they are essentially the same as the first two, but there is a pitfall here, which is the naming. If you use bd directly to generate the name, since MapperFactoryBean.class is unique, both mappers are called MapperFactoryBean, so here , refer to the Spring source code, and use the second AbstractBeanDefinition to generate a bean with classMetadata.getClassName() as the name. This way there won’t be any duplication of names.