preface


Spring plays a pivotal role in the vast Java architecture, bringing great convenience and surprise to every developer. We all know that Spring is a factory for creating and managing beans, and it provides a variety of ways to define beans for a variety of business scenarios in our daily work.

So the question is, do you know any ways to define beans in Spring?

I expect many of you to name the following three:

Recently, I accidentally got a brush notes written by a big boss of a BAT factory, which suddenly got through my two veins of appointment and supervision. More and more, I felt that the algorithm was not as difficult as I had imagined. The brush notes written by the BAT boss made my offer get soft

Yes, but I would say that these three methods are just appetizers, and that Spring is actually more powerful than you think.

If you don’t believe me, please read on.

1. XML file configuration bean

Let’s start with XML configuration beans, one of the earliest methods supported by Spring. Since then, as SpringBoot has grown in popularity, this method has become less used today, but I suggest that it is worth looking into.

1.1 the constructor

If you’ve configured beans in a bean.xml file before, you’ll be familiar with the following configuration:

<bean id="personService" class="com.sue.cache.service.test7.PersonService">
</bean>

This approach, which has been the most used before, uses a no-argument constructor to create beans by default.

You can also use a constructor with arguments, using the

tag.

<bean id="personService" class="com.sue.cache.service.test7.PersonService">
   <constructor-arg index="0" value="susan"></constructor-arg>
   <constructor-arg index="1" ref="baseInfo"></constructor-arg>
</bean>

Among them:

  • indexThat’s the index, starting at 0.
  • valueDenote constant value
  • refRepresents a reference to another bean

1.2 a setter method

In addition, Spring provides another way of thinking about setting the required parameters of the bean through setter methods, which is relatively less coupled and more widely used than the constructor with arguments.

First, define the Person entity:

@Data
public class Person {
    private String name;
    private int age;
}

It contains: member variables name and age, getter/setter methods.

Then, when configuring the bean in the bean.xml file, add the tag to set the parameters required for the bean.

<bean id="person" class="com.sue.cache.service.test7.Person">
   <property name="name" value="susan"></constructor-arg>
   <property name="age" value="18"></constructor-arg>
</bean>

1.3 Static factory

The key to this approach is to define a factory class that contains a static method to create the bean. Such as:

public class SusanBeanFactory { public static Person createPerson(String name, int age) { return new Person(name, age); }}

Next, define the Person class as follows:

@AllArgsConstructor
@NoArgsConstructor
@Data
public class Person {
    private String name;
    private int age;
}

It contains: member variables name and age, getter/setter methods, no-argument constructors, and full-argument constructors.

If you configure the bean in the bean.xml file, specify the static factory method with the factory-method parameter and set the parameters with

.

<bean class="com.sue.cache.service.test7.SusanBeanFactory" factory-method="createPerson">
   <constructor-arg index="0" value="susan"></constructor-arg>
   <constructor-arg index="1" value="18"></constructor-arg>
</bean>

1.4 Instance factory method

This approach also requires defining a factory class, but it contains non-static methods to create the bean.

public class SusanBeanFactory { public Person createPerson(String name, int age) { return new Person(name, age); }}

The Person class is the same as above, so I won’t go into details.

Then when you configure the beans in the bean.xml file, you need to configure the factory beans first. Then, when you configure the instance bean, you specify a reference to the factory bean through the factory-bean parameter.

<bean id="susanBeanFactory" class="com.sue.cache.service.test7.SusanBeanFactory">
</bean>
<bean factory-bean="susanBeanFactory" factory-method="createPerson">
   <constructor-arg index="0" value="susan"></constructor-arg>
   <constructor-arg index="1" value="18"></constructor-arg>
</bean>

1.5 FactoryBean

Do not know if you have noticed, the above instance factory method needs to create a factory class each time, not unified management.

In this case we can use the FactoryBean interface.

public class UserFactoryBean implements FactoryBean<User> {
    @Override
    public User getObject() throws Exception {
        return new User();
    }

    @Override
    public Class<?> getObjectType() {
        return User.class;
    }
}

In its getObject method we can implement our own logic to create objects, and in the getObjectType method we can define the type of the object.

Then, when you configure the bean in the bean.xml file, you simply configure it as a normal bean.

<bean id="userFactoryBean" class="com.sue.async.service.UserFactoryBean">
</bean>

It’s so easy.

Note: the getBean (” userFactoryBean “); Gets the object returned in the getObject method. The getBean (” & userFactoryBean “); The actual UserFactoryBean object is retrieved.

After configuring the beans in the bean.xml file in the five ways above, Spring automatically scans and parses the corresponding tags, and helps us create and instantiate the beans and place them in the Spring container.

Although configuring beans based on XML files is simple and flexible, it is suitable for small projects. However, if you encounter a more complex project, you need to configure a large number of beans, and the relationship between beans is complicated, which will lead to the rapid expansion of XML files in the long run, which is very bad for bean management.

2. The Component annotation

In order to solve the problem of too many beans, the XML file is too large, resulting in bloating and difficult to maintain. In Spring 2.5, bean definitions are supported with @Component, @Repository, @Service, and @Controller annotations.

If you look at the source code for these annotations, you’ll be surprised to find that the last three annotations are also @Component.

The @Component family of annotations is a great convenience for us. Instead of configurable the bean in the bean.xml file as before, we can simply define the bean by adding one of the four annotations: Component, Repository, Service, and Controller to the class.

@Service public class PersonService { public String get() { return "data"; }}

In fact, there is no special difference in function between these four annotations, but there is an unwritten convention in the industry:

  • Controller is generally used in the control layer
  • Services are typically used at the business level
  • Repository is typically used in the data layer
  • Component is typically used on common components

It was great. It was a free hand.

However, it is important to note that the @Component scan annotation is used to define the bean if the scan path is configured first.

The current common way to configure the scan path is as follows:

  1. Used in the ApplicationContext.xml file<context:component-scan>The label. Such as:
<context:component-scan base-package="com.sue.cache" />
  1. Add to the boot class of SpringBoot@ComponentScanNotes, such as:
@ComponentScan(basePackages = "com.sue.cache") @SpringBootApplication public class Application { public static void main(String[] args) { new SpringApplicationBuilder(Application.class).web(WebApplicationType.SERVLET).run(args); }}
  1. Directly in theSpringBootApplicationNote that it supports ComponentScan functionality:
@SpringBootApplication(scanBasePackages = "com.sue.cache") public class Application { public static void main(String[] args) { new SpringApplicationBuilder(Application.class).web(WebApplicationType.SERVLET).run(args); }}

Of course, if you need to scan the same class as SpringBoot’s entry class, there is no need to specify scanbasePackages under the same level or child of the package. Spring will look for the same level or child of the package by default.

@SpringBootApplication public class Application { public static void main(String[] args) { new SpringApplicationBuilder(Application.class).web(WebApplicationType.SERVLET).run(args); }}

In addition to the four @Component annotations described above, SpringBoot also adds the @RestController annotation, which is a special @Controller annotation and is therefore also an @Component annotation.

The @RESTController also supports the @ResponseBody annotation, which automatically converts the format of the interface response data to JSON.

The @Component family of annotations have made us love it, and are by far the most common way we define beans in our daily work.

3. JavaConfig

The @Component annotation family is very convenient to use, but the bean creation process is completely left to the Spring container and we have no control over it.

Since 3.0, Spring has supported JavaConfig to define beans. It can be viewed as a Spring configuration file, but it’s not really a configuration file, and we need to create the bean by encoding Java code. Such as:

@Configuration public class MyConfiguration { @Bean public Person person() { return new Person(); }}

Adding the @Configuration annotation to the JavaConfig class is equivalent to configuring the

tag. By adding an @Bean annotation to a method, you configure the < Bean > tag.

In addition, SpringBoot introduces a series of @Conditional annotations to control the creation of beans.

@Configuration public class MyConfiguration { @ConditionalOnClass(Country.class) @Bean public Person person() { return new Person(); }}

The @conditionalonClass annotation does this by instantiating the Person class when the Country class exists in the project. In other words, if the Country class does not exist in the project, the Person class will not be instantiated.

This functionality is very useful and acts as a switch that controls the Person class and can only be instantiated if certain conditions are met.

Conditional is used a lot in Spring and there are also:

  • ConditionalOnBean
  • ConditionalOnProperty
  • ConditionalOnMissingClass
  • ConditionalOnMissingBean
  • ConditionalOnWebApplication

If you’re interested in some of these features, check out “Spring’s code tricks you’ll love (sequel),” which I wrote in a previous article that covers them in more detail.

Let’s look at the @Conditional family as a whole in a picture:

Nice, with these features, we can finally say goodbye to the troubled XML era.

4. Import annotations

With the combination of @Configuration and @Bean described earlier, we can define beans through code. However, this approach has some limitations. It can only create bean instances defined in the class, and cannot create bean instances of other classes. What if we want to create bean instances of other classes?

You can use the @Import annotation to Import.

4.1 ordinary class

After Spring 4.2, the @Import annotation can instantiate bean instances of ordinary classes. Such as:

The Role class is defined first:

@Data
public class Role {
    private Long id;
    private String name;
}

Next, Import the Role class using the @Import annotation:

@Import(Role.class)
@Configuration
public class MyConfig {
}

The required bean is then injected at the place of the call with the @Autowired annotation.

@RequestMapping("/") @RestController public class TestController { @Autowired private Role role; @GetMapping("/test") public String test() { System.out.println(role); return "test"; }}

If you’re smart, you might notice that I haven’t defined a Role bean anywhere, but Spring can automatically create bean instances of that class. Why is that?

This is perhaps the power of the @import annotation.

At this point, some friends might ask: The @Import annotation defines beans of a single class, but what if there are more than one class that needs to define beans?

Congratulations, this is a good question because the @import annotation is also supported.

@Import({Role.class, User.class})
@Configuration
public class MyConfig {
}

Even if you want to be lazy and don’t want to write this myConfig class, SpringBoot is welcome.

@Import({Role.class, User.class}) @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class}) public class Application { public static void main(String[] args) { new SpringApplicationBuilder(Application.class).web(WebApplicationType.SERVLET).run(args); }}

You can add @import to SpringBoot’s startup class.

That would work, right?

SpringBoot’s startup class is typically annotated with the @SpringBootApplication annotation, which is also annotated with the @SpringBootConfiguration annotation.

And the @SpringBootConfiguration annotation, with the @Configuration annotation added to itTherefore, the SpringBoot startup class itself has the capability of @Configuration annotation.

Are you surprised? Surprise?

4.2 the Configuration class

The @Import annotation, which shows how to Import a generic class, also supports importing the Configuration class.

First, define a Configuration class:

@Configuration public class MyConfig2 { @Bean public User user() { return new User(); } @Bean public Role role() { return new Role(); }}

Then introduce the previous Configuration class in another Configuration class:

@Import({MyConfig2.class})
@Configuration
public class MyConfig {
}

In this way, if the MyConfig2 class is already in the scan directory or subdirectory specified by Spring, the MyConfig class may seem redundant. Because the MyConfig2 class itself is a configuration class, beans can be defined inside it.

However, if the MyConfig2 class is not in the Spring scan directory or subdirectory specified, the MyConfig2 class can also be identified as a configuration class through the import function of the MyConfig class. Now that’s a little strong.

In fact, there is a higher level of play.

Swagger is becoming increasingly popular in Spring projects as an excellent document generation framework. Let’s take the swagger2 example and show how it imports the related classes.

As we all know, when we introduce the swagger-related jar, we simply need to add the @enableswagger2 annotation on the SpringBoot boot class to enable the swagger function.

One @ EnableSwagger2 annotations in the imported Swagger2DocumentationConfiguration class.

This class is a Configuration class that imports two other classes:

  • SpringfoxWebMvcConfiguration
  • SwaggerCommonConfiguration

SpringfoxWebMvcConfiguration class will import the new Configuration class again, and by @ ComponentScan annotation scanning some other path.

Also by @ ComponentScan SwaggerCommonConfiguration annotation scanning some extra path.

With a simple @enableswagger2 annotation, we can easily import the list of beans needed for swagger and have swagger functionality.

What else to say, crazy starting point praise, it’s perfect.

4.3 ImportSelector

The Configuration class mentioned above is very powerful. But, well, it doesn’t lend itself very well to complex criteria that define these beans according to some conditions and those beans according to others.

So, how can this requirement be implemented?

At this point you can use the ImportSelector interface.

First define a class that implements the ImportSelector interface:

public class DataImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { return new String[]{"com.sue.async.service.User", "com.sue.async.service.Role"}; }}

Override the selectImports method to specify the name of the class in which the bean needs to be defined, noting that the full path, not the relative path, is included.

Then Import the myConfig class from @import:

@Import({DataImportSelector.class})
@Configuration
public class MyConfig {
}

Did your friends discover a new world?

However, there are even better uses for this annotation.

@ EnableAutoConfiguration annotations in the imported AutoConfigurationImportSelector class, and contains the system parameter name:spring.boot.enableautoconfiguration.AutoConfigurationImportSelector class implementsImportSelectorInterface.

And rewrote itselectImportsMethod, which looks for all the class names that need to be created based on some annotations, and then returns them. The isEnabled method is called before the class names are found to determine whether the search needs to continue.This method uses the ENABLED\_OVERRIDE\_PROPERTY value as its criteria.And this value is going to bespring.boot.enableautoconfiguration.

In other words, you can control whether beans need to be instantiated based on system parameters. Excellent.

In my opinion, there are two main advantages of implementing the ImportSelector interface:

  1. The related classes of a function can be put together for aspect management and maintenance.
  2. When you override the selectImports method, you can determine whether certain classes need to be instantiated based on conditions, or if one condition instantiates these beans, another condition instantiates those beans, and so on. We have the flexibility to customize the instantiation of beans.

4.4 ImportBeanDefinitionRegistrar

In this way, we can customize the bean very flexibly.

However, it is limited in its ability to customize properties such as bean names and scopes.

Where there is demand, there is a solution.

Next, let’s look at the wonders of ImportBeanDefinitionRegistrar interface.

First define CustomImportSelector class implements ImportBeanDefinitionRegistrar interface:

public class CustomImportSelector implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { RootBeanDefinition roleBeanDefinition = new RootBeanDefinition(Role.class); registry.registerBeanDefinition("role", roleBeanDefinition); RootBeanDefinition userBeanDefinition = new RootBeanDefinition(User.class); userBeanDefinition.setScope(ConfigurableBeanFactory.SCOPE_PROTOTYPE); registry.registerBeanDefinition("user", userBeanDefinition); }}

Override the registerBeanDefinitions method, where we can get the beanDefinitionRegistry object to register the bean with. However, before registering the bean, we need to create a BeanDefinition object, which allows us to customize the bean’s name, scope, and many other parameters.

Then import the above class on the MyConfig class:

@Import({CustomImportSelector.class})
@Configuration
public class MyConfig {
}

The familiar fegin function, is to use ImportBeanDefinitionRegistrar interface implementation:The specific details are not much to say, interested friends can add my WeChat to find me private chat.

5. PostProcessor

In addition, spring also provides special registered bean interface: BeanDefinitionRegistryPostProcessor.

The method of the interface postProcessBeanDefinitionRegistry has such a description:Modify the internal bean definition registry standard initialization for the application context. All regular bean definitions will be loaded, but none of the beans have been instantiated yet. This allows for the further addition of defined beans before the next post-processing phase begins.

If we use this interface to define the bean, our work becomes very simple. Need to define a class implements BeanDefinitionRegistryPostProcessor interface.

@Component
public class MyRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        RootBeanDefinition roleBeanDefinition = new RootBeanDefinition(Role.class);
        registry.registerBeanDefinition("role", roleBeanDefinition);

        RootBeanDefinition userBeanDefinition = new RootBeanDefinition(User.class);
        userBeanDefinition.setScope(ConfigurableBeanFactory.SCOPE_PROTOTYPE);
        registry.registerBeanDefinition("user", userBeanDefinition);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    }
}

Rewrite postProcessBeanDefinitionRegistry method, can obtain BeanDefinitionRegistry object in this method, it is responsible for registration of bean.

If you’re careful, you might notice that there’s also a postProcessBeanFactory method that doesn’t do any implementation.

This method is actually a method in its parent interface, BeanFactoryPostProcessor.

Modify the internal bean factory initialization after the standard bean factory of the application context. All bean definitions are loaded, but no beans will be instantiated. This allows you to override or add properties and even initialize beans.

@Component public class MyPostProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { DefaultListableBeanFactory registry = (DefaultListableBeanFactory)beanFactory; RootBeanDefinition roleBeanDefinition = new RootBeanDefinition(Role.class); registry.registerBeanDefinition("role", roleBeanDefinition); RootBeanDefinition userBeanDefinition = new RootBeanDefinition(User.class); userBeanDefinition.setScope(ConfigurableBeanFactory.SCOPE_PROTOTYPE); registry.registerBeanDefinition("user", userBeanDefinition); }}

Since both interfaces can register beans, what’s the difference between them?

  • Registered BeanDefinitionRegistryPostProcessor focuses more on the bean
  • BeanFactoryPostProcessor focuses more on modifying the properties of registered beans, although beans can also be registered.

At this point, some of you might ask: If you get the BeanDefinitionRegistry object and you can register a bean, can you also register a bean with BeanFactoryAware?

From the image below to see DefaultListableBeanFactory BeanDefinitionRegistry interface is realized.

In this way, if we can get DefaultListableBeanFactory instance of an object, then call its registration method, can not register the beans?

Without further notice, define a class that implements the BeanFactoryAware interface:

@Component public class BeanFactoryRegistry implements BeanFactoryAware { @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { DefaultListableBeanFactory registry = (DefaultListableBeanFactory) beanFactory; RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(User.class); registry.registerBeanDefinition("user", rootBeanDefinition); RootBeanDefinition userBeanDefinition = new RootBeanDefinition(User.class); userBeanDefinition.setScope(ConfigurableBeanFactory.SCOPE_PROTOTYPE); registry.registerBeanDefinition("user", userBeanDefinition); }}

Rewrite setBeanFactory method, can obtain the BeanFactory object in this method, it can be forced into DefaultListableBeanFactory object, and register through the object instance of the bean.

When you run the project with joy, you find an error:

Why is the error reported?

The order of bean creation in Spring is roughly as follows:BeanFactoryAwareThe interface is called after the bean is created successfully and the dependency injection is complete, before it is actually initialized. Registering beans at this point makes little sense, as the interface is for us to fetch beans and it is not recommended to register beans, which can cause a lot of problems.

Recently, I accidentally got a brush notes written by a big boss of a BAT factory, which suddenly got through my two veins of appointment and supervision. More and more, I felt that the algorithm was not as difficult as I had imagined. The brush notes written by the BAT boss made my offer get soft

In addition, ApplicationContextRegistry and ApplicationListener interface has a similar problem, we can use them to get bean, but does not recommend using them registered bean.

One last word (attention, don’t fuck me for nothing)

If this article is helpful to you, or enlightening, please scan the QR code and pay attention to it. Your support is the biggest motivation for me to stick to writing.

For a key three: thumb up, forward, in the watch.

Pay attention to the public account: [Su San said technology], reply in the public account: interview, code artifact, development manual, time management has super fan welfare, in addition reply: add group, you can communicate and learn from many predecessors of BAT big factory.