preface
Spring plays an important role in the vast Java architecture, and it brings 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.
Which begs the question, do you know of any ways to define beans in Spring?
I expect a lot of people to say three things:
Recently, I accidentally got a copy of the notes written by a big boss of BAT factory. All of a sudden, I got through to both my supervisor and my supervisor. I felt that the algorithm was not as difficult as I imagined.
BAT boss wrote the brush notes, let me get the offer soft
That’s true, but I’d like to say that these three options are just appetizers, and spring is actually more powerful than you might think.
If you don’t believe me, please continue to read.
1. The XML file configures the bean
Let’s start with XML configuration beans, which spring originally supported. Since then, as SpringBoot has become more and more popular, this method has been used very little, but I suggest we still need to know about it.
1.1 the constructor
If you’ve ever configured a bean in a bean.xml file, you’ll be familiar with the following configuration:
<bean id="personService" class="com.sue.cache.service.test7.PersonService">
</bean>
Copy the code
This approach, the most used before, defaults to using a no-argument constructor to create beans.
Of course, we can also use the parameter constructor, using the
tag to complete the configuration.
<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>
Copy the code
Among them:
index
Represents the subscript, starting at 0.value
Represents constant valueref
Indicates a reference to another bean
1.2 a setter method
In addition, Spring provides another way of setting parameters required by beans through setter methods, which are less coupled and more widely used than parameter constructors.
Define the Person entity first:
@Data
public class Person {
private String name;
private int age;
}
Copy the code
It contains member variables name and age, and getter/setter methods.
Then, when configuring the bean in the bean.xml file, add the
<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>
Copy the code
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 newPerson(name, age); }}Copy the code
Next, define the Person class as follows:
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Person {
private String name;
private int age;
}
Copy the code
It contains member variables name and age, getter/setter methods, no-parameter constructors, and full-parameter constructors.
Then when configuring the bean in the bean.xml file, specify the static factory method with the factory-method parameter and set the related 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>
Copy the code
1.4 Example factory method
This approach also requires defining a factory class that contains non-static methods for creating beans.
public class SusanBeanFactory {
public Person createPerson(String name, int age) {
return newPerson(name, age); }}Copy the code
The Person class is the same as above, so I won’t go into that.
Then when configuring beans in the bean.xml file, you need to configure the factory beans first. You then specify a reference to the factory bean with the factory-bean parameter when configuring the instance bean.
<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>
Copy the code
1.5 FactoryBean
I don’t know if you noticed, but the instance factory method above requires the creation of a factory class each time, so there is no unified management.
Here we can use the FactoryBean interface.
public class UserFactoryBean implements FactoryBean<User> {
@Override
public User getObject(a) throws Exception {
return new User();
}
@Override
publicClass<? > getObjectType() {returnUser.class; }}Copy the code
In its getObject method we can implement our own logical object creation, and in the getObjectType method we can define the type of the object.
When you then configure the beans in the bean.xml file, you simply configure them as normal beans.
<bean id="userFactoryBean" class="com.sue.async.service.UserFactoryBean">
</bean>
Copy the code
It’s so easy.
Note: the getBean (” userFactoryBean “); Get the object returned from the getObject method. The getBean (” & userFactoryBean “); You get the actual UserFactoryBean object.
After configuring the bean in the bean.xml file, Spring will automatically scan and parse the corresponding tags, and help us create and instantiate the bean, and then put it into 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 complex project, you need to configure a large number of beans, and the relationship between beans is complex, which over time will lead to a rapid expansion of XML files, which is very bad for bean management.
2. The Component annotation
To solve the problem of XML files being too large to maintain when there are too many beans. In spring2.5, annotations such as @component, @repository, @service, @controller define beans.
If you look at the source code for these annotations, you’ll be surprised to see that the last three annotations are also @Component.
The @Component family of annotations is a great convenience. Instead of configuring beans in the bean.xml file, we can easily define beans by adding any of the four annotations Component, Repository, Service, or Controller to the class.
@Service
public class PersonService {
public String get(a) {
return "data"; }}Copy the code
There are no specific functional differences between the four annotations, but there is an unwritten convention in the industry:
- Controller is usually used at the control level
- Services are typically used at the business layer
- Repository is typically used in the data layer
- Component is used for common components
Oh, that’s great. It’s really freeing our hands.
It is important to note, however, that the way beans are defined by @Component scanning annotations requires that the scan path be configured first.
Currently, you can configure scan paths as follows:
- Used in the applicationContext.xml file
<context:component-scan>
The label. Such as:
<context:component-scan base-package="com.sue.cache" />
Copy the code
- To the boot class of SpringBoot
@ComponentScan
Notes, such as:
@ComponentScan(basePackages = "com.sue.cache")
@SpringBootApplication
public class Application {
public static void main(String[] args) {
newSpringApplicationBuilder(Application.class).web(WebApplicationType.SERVLET).run(args); }}Copy the code
- Directly in the
SpringBootApplication
Annotated, which supports ComponentScan:
@SpringBootApplication(scanBasePackages = "com.sue.cache")
public class Application {
public static void main(String[] args) {
newSpringApplicationBuilder(Application.class).web(WebApplicationType.SERVLET).run(args); }}Copy the code
Of course, there is no need to specify scanBasePackages if the class you want to scan is at the same level or child of the springBoot entry class. Spring will default to the same level or child of the Entry class.
@SpringBootApplication
public class Application {
public static void main(String[] args) {
newSpringApplicationBuilder(Application.class).web(WebApplicationType.SERVLET).run(args); }}Copy the code
In addition to the four @Component annotations mentioned above, SpringBoot also adds the @RestController annotation, which is a special @Controller annotation and therefore the @Component annotation.
@RestController also supports the @responseBody annotation, which automatically converts the interface response data to JSON.
The @Component family of annotations has become so addictive that it is by far the most common way to define beans in our daily work.
3. JavaConfig
The @Component annotations are handy to use, but bean creation is entirely in the hands of the Spring container.
Since 3.0, Spring has supported the JavaConfig way of defining beans. It can be thought of 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(a) {
return newPerson(); }}Copy the code
The @Configuration annotation on the JavaConfig class is equivalent to configuring the
tag. Adding an @bean annotation to a method is equivalent to configuring the < Bean > tag.
In addition, SpringBoot has introduced a series of @Conditional annotations to control bean creation.
@Configuration
public class MyConfiguration {
@ConditionalOnClass(Country.class)
@Bean
public Person person(a) {
return newPerson(); }}Copy the code
The function of the @conditionalonClass annotation is to instantiate the Person class only if a Country class exists in the project. In other words, if the Country class does not exist in the project, the Person class is not instantiated.
This functionality is useful as a switch that controls the Person class and can only be instantiated if certain conditions are met.
Other conditionals that are commonly used in Spring are:
- ConditionalOnBean
- ConditionalOnProperty
- ConditionalOnMissingClass
- ConditionalOnMissingBean
- ConditionalOnWebApplication
If you’re interested in any of these features, check out an article I wrote earlier that covers them in more detail.
Here’s a picture of the @Conditional family as a whole:
Nice, with these capabilities, we can finally put the troublesome XML era behind us.
4. Import annotations
By combining @Configuration with @Beans as described earlier, we can define beans in code. However, this approach has some limitations. It can only create bean instances defined in this class, but not 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 spring4.2, the @import annotation can instantiate bean instances of ordinary classes. Such as:
First we define the Role class:
@Data
public class Role {
private Long id;
private String name;
}
Copy the code
Next Import the Role class with the @import annotation:
@Import(Role.class)
@Configuration
public class MyConfig {}Copy the code
The required bean is then injected at the invocation via the @AutoWired annotation.
@RequestMapping("/")
@RestController
public class TestController {
@Autowired
private Role role;
@GetMapping("/test")
public String test(a) {
System.out.println(role);
return "test"; }}Copy the code
You might be smart enough to notice that I haven’t defined a Role bean anywhere, but Spring automatically creates bean instances of that class. Why?
This is perhaps the power of the @import annotation.
At this point, some friends might ask: the @import annotation can define a bean of a single class, but what if there are multiple classes that need to define beans?
Congratulations, that’s a good question because the @import annotation is also supported.
@Import({Role.class, User.class})
@Configuration
public class MyConfig {}Copy the code
Even if you want to be lazy and not 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); }}Copy the code
You can add @import to the boot class of SpringBoot.
That works too, right?
The bootstrap class of SpringBoot is annotated with @SpringBootApplication, which is annotated with @SpringBootConfiguration.
And the @SpringBootConfiguration annotation, and then the @Configuration annotationSo, the SpringBoot boot class itself comes with the @Configuration annotation.
Are you surprised? Is it a surprise?
4.2 the Configuration class
The @import annotation, shown above, imports normal classes. It also supports importing the Configuration class.
Define a Configuration class:
@Configuration
public class MyConfig2 {
@Bean
public User user(a) {
return new User();
}
@Bean
public Role role(a) {
return newRole(); }}Copy the code
Then introduce the previous Configuration class in another Configuration class:
@Import({MyConfig2.class})
@Configuration
public class MyConfig {}Copy the code
This way, if the MyConfig2 class is already in a spring-specified scan directory or subdirectory, the MyConfig class is a bit redundant. Because the MyConfig2 class is itself a configuration class, it defines beans.
However, if the MyConfig2 class is not in the specified Spring scan directory or subdirectory, it can also be recognized as a configuration class through the import function of the MyConfig class. That’s kind of cool.
But there’s more to it.
Swagger is becoming increasingly popular in Spring projects as an excellent document generation framework. Next, let’s take Swagger2 as an example to show how it imports related classes.
As we all know, swagger can be enabled by adding @enablesWagger2 annotation to springBoot startup class after introducing swagger jar package.
One @ EnableSwagger2 annotations in the imported Swagger2DocumentationConfiguration class.
This class is a Configuration class that imports two more 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.
In this way, we can easily import a set of Beans for Swagger with a simple @enablesWagger2 annotation and have Swagger functionality.
What else is there to say? It’s amazing. It’s perfect.
4.3 ImportSelector
The Configuration class mentioned above is very powerful. But it’s not a very good place to add complex judgment conditions, defining these beans according to some conditions and those beans according to other conditions.
So, how do you implement this requirement?
Now you can use the ImportSelector interface.
Start by defining 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"}; }}Copy the code
Override the selectImports method to specify the name of the class in which the bean is to be defined, taking care to include the full path, not the relative path.
Then Import the MyConfig class @import:
@Import({DataImportSelector.class})
@Configuration
public class MyConfig {}Copy the code
Have your friends discovered a new world?
But there are even more awesome uses for this note.
@ EnableAutoConfiguration annotations in the imported AutoConfigurationImportSelector class, and contains the system parameter name:spring.boot.enableautoconfiguration
.AutoConfigurationImportSelector class implementsImportSelector
Interface.
And rewrote itselectImports
Method that looks for all the class names for which the bean is to be created based on some annotations and returns those class names. Before searching these class names, the isEnabled method is called to determine whether to continue searching.This method is based on the value of ENABLED_OVERRIDE_PROPERTY.And this value right here is zerospring.boot.enableautoconfiguration
.
In other words, it controls whether the bean needs to be instantiated based on system parameters. Excellent.
I personally see two main benefits to implementing the ImportSelector interface:
- The related classes of a function can be put together for aspect management and maintenance.
- When you override the selectImports method, you can determine whether certain classes need to be instantiated based on conditions, or whether a condition instantiates those beans, another condition instantiates those beans, and so on. We can customize the instantiation of beans very flexibly.
4.4 ImportBeanDefinitionRegistrar
We do have a lot of flexibility to customize beans in this way.
However, its ability to customize, or limited, it can not customize bean name and scope properties.
Where there is a need, 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); }}Copy the code
Override the registerBeanDefinitions method, where we can get the BeanDefinitionRegistry object and de-register the bean. However, before registering the bean, we need to create the BeanDefinition object, which can define the bean’s name, scope, and many other parameters.
Then import the above class on MyConfig:
@Import({CustomImportSelector.class})
@Configuration
public class MyConfig {}Copy the code
The familiar fegin function, is to use ImportBeanDefinitionRegistrar interface implementation:No more details, interested friends can add my wechat to find my 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 of the application context. All regular bean definitions will be loaded, but no beans have been instantiated yet. This allows you to further add that the bean is defined before the next post-processing phase begins.
If we use this interface to define beans, 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 {}}Copy the code
Rewrite postProcessBeanDefinitionRegistry method, can obtain BeanDefinitionRegistry object in this method, it is responsible for registration of bean.
However, if you are careful, you may notice that there is also a postProcessBeanFactory method that does nothing.
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 attributes 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); }}Copy the code
Since both interfaces can register beans, what is the difference between them?
- Registered BeanDefinitionRegistryPostProcessor focuses more on the bean
- BeanFactoryPostProcessor focuses more on modifying the properties of already registered beans, although beans can be registered as well.
If you need BeanDefinitionRegistry objects to register beans, do you need BeanDefinitionRegistry objects to register beans?
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?
Quick, 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); }}Copy the code
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 an error reported?
The bean creation process in Spring is roughly as follows:
The BeanFactoryAware interface is invoked after the bean has been created and dependency injection has been completed, but before it is actually initialized. It doesn’t make sense to register the bean at this point, because the interface is for us to get the bean, and it’s not recommended to register the bean, which can cause a lot of problems.
Recently, I accidentally got a copy of the notes written by a big boss of BAT factory. All of a sudden, I got through to both my supervisor and my supervisor. I felt that the algorithm was not as difficult as I imagined.
BAT boss wrote the brush notes, let me get the offer 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.