Hello, today I would like to share Spring IOC with you. Please take out your notebook and write it down.
1. The principle of the IoC
The full name of the IoC is Inversion of Control.
Why use IoC?
Let’s assume an online bookstore that gets books from the BookService:
public class BookService {
private HikariConfig config = new HikariConfig(); private DataSource dataSource = new HikariDataSource(config); public Book getBook(long bookId) { try (Connection conn = dataSource.getConnection()) { ... return book; }}Copy the code
}
To query books from the database, the BookService holds a DataSource. For instance
HikariDataSource, again having to instantiate a HikariConfig. Now, let’s keep making it up
UserService obtains the user:
public class UserService {
private HikariConfig config = new HikariConfig(); private DataSource dataSource = new HikariDataSource(config); public User getUser(long userId) { try (Connection conn = dataSource.getConnection()) { ... return user; }}Copy the code
}
Since UserService also needs to access the database, we have to instantiate a HikariDataSource as well.
Each time a method is called, a HikariDataSource needs to be instantiated, which can be a waste of resources. If a shared resource is implemented in some way, how do you ensure that it is destroyed to free the resource with all functionality intact?
So the core question is:
Who is responsible for creating components?
Who is responsible for assembling components from dependencies?
When destroying, how do I correctly destroy in the order of dependencies?
The core solution to this problem is IoC.
In traditional applications, control is in the program itself, and the control flow of the program is completely controlled by the developer, that is, instantiating classes within the program.
In IoC mode, however, control is reversed, shifting from the application to the IoC container, where all components are no longer created and configured by the application itself. In this way, the application only needs to directly use components that have already been created and configured.
In order for components to be “assembled” in an IoC container, some kind of “injection” mechanism is required. For example, BookService does not create its own DataSource, but waits for an external DataSource to be injected via the setDataSource() method:
public class BookService {
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
Copy the code
}
Injecting a DataSource instead of creating a new DataSource is a simple change that has a number of benefits:
BookService no longer cares how to create the DataSource, so it doesn’t have to write code to read database configurations;
The DataSource instance is injected into BookService and can also be injected into UserService, so sharing a component is very simple;
Testing the BookService is easier because the DataSource is injected and can use the in-memory database rather than the real MySQL configuration.
Therefore, IoC is also called Dependency Injection (DI). It solves one of the most important problems: separating the creation and configuration of components from their use, and the IoC container is responsible for managing the life cycle of components.
Because the IoC container is responsible for instantiating all components, it is necessary to tell the container how to create components and the dependencies of each component. One of the simplest configurations is through an XML file, for example:
<bean id="dataSource" class="HikariDataSource" />
<bean id="bookService" class="BookService">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="userService" class="UserService">
<property name="dataSource" ref="dataSource" />
</bean>
Copy the code
The above XML configuration file instructs the IoC container to create three JavaBean components and inject the component whose ID is dataSource into the other two components via the dataSource attribute (that is, calling the setDataSource() method).
In Spring’s IoC container, we call all components javabeans, meaning that to configure a component is to configure aBean.
DI: Dependency Injection
As we can see from the above code, dependency injection can be implemented using the set() method, but it can also be implemented using constructors:
/ / set () method
public class BookService {
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
Copy the code
}
// constructor method
public class BookService {
private DataSource dataSource;
public BookService(DataSource dataSource) {
this.dataSource = dataSource;
}
Copy the code
}
Non-invasive container
By design, Spring’s IoC container is a highly extensible, non-intrusive container. By non-intrusive, I mean that the components of the application do not need to implement Spring’s specific interfaces, or that they do not even know they are running in a Spring container. This non-invasive design has the following benefits:
Application components can either run in Spring’s IoC container, or they can write their own code to assemble configurations.
It does not rely on the Spring container when testing, and can be tested separately, greatly improving the development efficiency.
2. Assemble the Bean component
Let’s look at a specific example of a user registering for a login. The structure of the whole project is as follows:
Let’s write a MailService to send mail notifications when the user has logged in and registered successfully:
public class MailService {
private ZoneId zoneId = ZoneId.systemDefault(); public void setZoneId(ZoneId zoneId) { this.zoneId = zoneId; } public String getTime() { return ZonedDateTime.now(this.zoneId).format(DateTimeFormatter.ISO_ZONED_DATE_TIME); } public void sendLoginMail(User user) { System.err.println(String.format("Hi, %s! You are logged in at %s", user.getName(), getTime())); } public void sendRegistrationMail(User user) { System.err.println(String.format("Welcome, %s!" , user.getName())); }Copy the code
}
Write a UserService that implements user registration and login:
Notice that UserService injects a MailService through setMailService(). Then, we need to write a specific application.xml configuration file that tells Spring’s IoC container how to create and assemble beans:
The < beans XMLNS = “www.springframework.org/schema/bean…”
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userService" class="com.itranswarp.learnjava.service.UserService">
<property name="mailService" ref="mailService" />
</bean>
<bean id="mailService" class="com.itranswarp.learnjava.service.MailService" />
Copy the code
Notice that in the configuration file above, the XML Schema-related parts of the format are fixed, and we focus only on the two < beans… > configuration:
Each < bean… > each has an ID, equivalent to the unique ID of the Bean;
In userServiceBean, by injecting another Bean;
The order of the beans doesn’t matter; Spring automatically initializes them correctly based on dependencies.
As a final step, we need to create a Spring IoC container instance, load the configuration file, and have the Spring container create and assemble all the beans specified in the configuration file for us with one line of code:
ApplicationContextcontext=newClassPathXmlApplicationContext(“application.xml”);
Next, we can “pull” the assembled Bean from the Spring container and use it:
/ / get Bean:
UserService userService = context.getBean(UserService.class);
// Normal call:
User user = userService.login(“[email protected]”, “password”);
Create the Spring IoC container
ClassPathXmlApplicationContext (common)
From the code that creates the Spring container:
ApplicationContextcontext=newClassPathXmlApplicationContext(“application.xml”);
As you can see, the Spring container is ApplicationContext, it is an interface, there are a lot of implementation class, here we choose ClassPathXmlApplicationContext, said it will automatically search for the specified XML configuration files in the classpath.
From ApplicationContext we can get a Bean based on its ID, but more often we get a reference to the Bean based on its type:
UserService userService = context.getBean(UserService.class);
Where userService is an instantiated class, the resulting userService can call methods in the class.
BeanFactory
Spring also provides another IoC container called BeanFactory, which uses the method and ApplicationContext
Similar to:
BeanFactoryfactory=newXmlBeanFactory(newClassPathResource(“application.xml”));
MailService mailService = factory.getBean(MailService.class);
The difference between BeanFactory and ApplicationContext is that the implementation of BeanFactory is created on demand, that is, when the Bean is first fetched, the Bean is created, whereas the ApplicationContext creates all beans at once. In fact, the ApplicationContext interface inherits from the BeanFactory interface, and it provides additional functionality, including internationalization support, events, and notification mechanisms. In general, we always use the ApplicationContext and rarely consider using the BeanFactory.
3. Use annotations to simplify configuration
We can use Annotation configuration, we can do without XML at all, and let Spring scan beans and assemble them automatically.
Delete the XML configuration file first, and then add a few annotations to UserService and MailService.
First, we add an @Component annotation to the MailService:
@Component
public class MailService {
.Copy the code
}
The @Component annotation defines a Bean that has an optional name. The default is mailService, which starts with a lowercase class name.
Then we add an @Component annotation and an @autowired annotation to the UserService:
@Component
public class UserService {
@Autowired MailService mailService; .Copy the code
}
Using @autoWired is equivalent to injecting a Bean of the specified type into the specified field. Alternatively, you can write it directly in the constructor:
Finally, write an AppConfig class to start the container:
And what I want to say here is,
Use the implementation class is AnnotationConfigApplicationContext, so must pass in a marked the @ the class name of the Configuration.
AppConfig also annotates @ComponentScan, which tells the container to automatically search the package and subpackages of the current class, automatically create all beans labeled @Component, and assemble them according to @AutoWired.
Using @ComponentScan is handy, but pay special attention to the package hierarchy as well. In general, the launch configuration AppConfig resides in a custom top-level package, and other beans are placed in subpackages by category.
4. Customize Bean components
The Scope (@ Scope (” prototype “))
The Bean only needs one instance:
For the Spring container, when we mark a Bean as @Component, it automatically creates a Singleton for us that is created when the container is initialized and destroyed before the container is closed. While the container is running, the Bean we call getBean(Class) always gets the same instance.
Different instances are required:
There is also a Bean whose container returns a new instance each time we call getBean(Class). This Bean is called Prototype and obviously has a different life cycle than Singleton. To declare a Prototype Bean, add an additional @scope annotation:
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) // @Scope(“prototype”)
public class MailSession {
.Copy the code
}
Into the List
Sometimes we will have a series of beans with the same interface but different implementation classes. For example, when registering a user, we validate the three variables email, password, and name. To facilitate extension, let’s define the validation interface:
public interface Validator {
Void validate(String email, String password, String name);Copy the code
}
Then, the user parameters are validated with three validators:
Finally, we verify with a Validators as the entry point:
Note that The Validators are injected into a List. Spring automatically assembs all beans of type Validator into a List, so every time we add a new Type of Validator, Spring automatically assembs the Validators into a List. It’s very convenient.
Optional injection (no specified Bean)
By default, when we marked a @autowired, Spring if not find the corresponding type of Bean, it throws NoSuchBeanDefinitionException anomalies.
We can add a required = false parameter to @autowired:
@Component
public class MailService {
@Autowired(required = false) ZoneId zoneId = ZoneId.systemDefault(); .Copy the code
}
This parameter tells the Spring container to inject a Bean of type ZoneId if it finds one, and ignore it if it doesn’t.
This approach is ideal for using definitions if there are any and using defaults if there are none.
Create third-party beans (beans not in the package)
If a Bean is not in our own package management, such as ZoneId, how do we create it?
The answer is to write our own Java method in the @Configuration class to create and return it, noting that the method is marked with an @bean annotation:
@Configuration
@ComponentScan
public class AppConfig {
ZoneId createZoneId() {return zoneid.of ("Z"); }Copy the code
}
Spring calls the method labeled @Bean only once, so the Bean returned is still a singleton. @bean (prototype)
Initialization and destruction
Sometimes a Bean needs to be initialized (listening for messages, etc.) after it has injected the necessary dependencies. Sometimes you also need to clean up resources (close connection pools, etc.) when the container is closed.
To do this, we need to introduce annotations defined by JSR-250:
< < the groupId > javax.mail. An annotation/groupId > < artifactId > javax.mail. The annotation - API < / artifactId > < version > 1.3.2 < / version >Copy the code
Tag @postConstruct and @Predestroy on Bean initialization and cleanup methods:
The Spring container does the following initialization for the above beans:
Call the constructor to create the MailService instance;
Inject according to @autowired;
Call init() labeled @postconstruct to initialize.
On destruction, the container first calls the shutdown() method marked @predestroy.
Spring only looks for parameterless methods based on annotations, not their method names.
Use the alias
Aliases are used when we need to create multiple beans of the same type:
You can specify an alias using @bean (“name”) or @bean +@Qualifier(“name”).
After specifying the alias, we need to specify the name of the Bean during injection, otherwise an error will be reported:
@Component
public class MailService {
@Autowired(required = false)
Qualifier(“z”) // Specify the ZoneId to inject with name “z”
ZoneId zoneId = ZoneId.systemDefault();
.Copy the code
}
If the Bean name is not specified, the Bean with the @primary flag is injected by default:
Please click to enter picture description (maximum 18 words)
Using FactoryBeans (Factory Pattern)
Creating beans with the factory pattern requires implementation
FactoryBean interface. Let’s look at the following code:
When a Bean implements the FactoryBean interface, Spring instantiates the factory and then calls getObject() to create the actual Bean. GetObjectType () can specify the type of the Bean to be created, since the specified type does not necessarily correspond to the actual type, and can Bean interface or abstract class.
So, if you define a FactoryBean, be aware that the Bean Spring creates is actually the Bean returned by the FactoryBean’s getObject() method. To distinguish them from regular beans, we usually name them XxxFactoryBean.
5. Read files using Resource
In Java programs, we often read configuration files, resource files, and so on. When using the Spring container, we can also inject “files” to make it easy for programs to read.
The above picture shows the structure of the project. We need to read the logo.txt file. Usually, we need to write a lot of tedious code, mainly to locate the file and open the InputStream. Spring has provided an org. Springframework. Core. IO. Resource, can be injected directly into the:
You can also specify the file path, for example:
@Value(“file:/path/to/logo.txt”)
private Resource resource;
6. Inject configuration (read configuration file)
@propertysource Injection configuration
In addition to reading files as Resource does, the Spring container provides a simpler @propertysource
To automatically read configuration files. We just need to add one more comment to the @Configuration Configuration class:
After the Spring container sees the @propertysource (“app.properties”) annotation, it automatically reads the configuration file, and then we inject it normally using @Value:
@Value(“${app.zone:Z}”)
String zoneId;
Note the injected string syntax, which has the following format:
“${app.zone}” indicates that the key is app.zone value.
Startup will report an error;
“${app.zone:Z}” indicates that the key is the value of app.zone, but if the key does not exist, the default value Z is used
You can also write injected annotations to method parameters:
@BeanZoneId
createZoneId(@Value(“${app.zone:Z}”) String zoneId) {
return ZoneId.of(zoneId);
Copy the code
}
The Bean is marked where injection is needed
Another way to inject configuration is to first hold all of the configuration, for example, one, through a simple JavaBean
SmtpConfig:
Then, where it needs to be read, inject with #{smtpconfig.host} :
“#{smtpconfig.host}” means that the host property is read from the Bean named smtpConfig, calling the getHost() method.
The advantage of using a separate JavaBean to hold all the attributes and then injecting them in other beans with #{bean.property} is that multiple beans can reference a property of the same Bean. For example, if SmtpConfig decides to read the relevant configuration items from the database, the @value (“#{smtpconfig.host}”) injected by MailService can still operate without modification.
7. Assembly under conditions of use
Defining different environments
Spring has the concept of profiles for applications to represent different environments. For example, we define development, test, and production environments:
native
test
production
When a Bean is created, the Spring container can use the @profile annotation to decide whether to create it or not. For example, the following configuration:
If the current Profile is set to test, the Spring container calls createZoneIdForTest() to create the ZoneId; otherwise, createZoneId() to create the ZoneId. Notice the @ Profile (! “” Test “) indicates a non-test environment.
The JVM parameter -dspring.profiles. Active =test can be added to specify that the test environment is started when the program is run.
In fact, Spring allows you to specify multiple profiles, such as:
-Dspring.profiles.active=test,master
You can represent the test environment with the master branch code.
To satisfy multiple Profile conditions, write:
@Bean
@profile ({“test”, “master”}
ZoneId createZoneId() {
.Copy the code
}
Use Conditional annotations to decide whether or not to create a Bean
In addition to determining whether to create a Bean based on the @profile condition, Spring can also determine whether to create a Bean based on
@Conditional decides whether to create a Bean or not.
For example, we add the following annotation to SmtpMailService:
@Component
@Conditional(OnSmtpEnvCondition.class)
public class SmtpMailService implements MailService {
.Copy the code
}
It means that the SmtpMailService Bean will only be created if the OnSmtpEnvCondition condition is met.
public class OnSmtpEnvCondition implements Condition {
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return "true".equalsIgnoreCase(System.getenv("smtp"));
}
Copy the code
}
Spring only provides @Conditional annotations, so we need to implement our own judgment logic.
Well, today’s article is here, I hope to help you confused screen!