装配Bean
我们前面讨论了为什么要使用Spring的IoC容器,因为让容器来为我们创建并装配Bean能获得很大的好处,那么到底如何使用IoC容器?装配好的Bean又如何使用?
我们来看一个具体的用户注册登录的例子。整个工程的结构如下:
spring-ioc-appcontext
├── pom.xml
└── src
└── main
├── java
│ └── com
│ └── itranswarp
│ └── learnjava
│ ├── Main.java
│ └── service
│ ├── MailService.java
│ ├── User.java
│ └── UserService.java
└── resources
└── application.xml
首先,我们用Maven创建工程并引入spring-context
依赖:
- org.springframework:spring-context:6.0.0
我们先编写一个MailService
,用于在用户登录和注册成功后发送邮件通知:
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()));
}
}
再编写一个UserService
,实现用户注册和登录:
public class UserService {
private MailService mailService;
public void setMailService(MailService mailService) {
this.mailService = mailService;
}
private List<User> users = new ArrayList<>(List.of( // users:
new User(1, "bob@example.com", "password", "Bob"), // bob
new User(2, "alice@example.com", "password", "Alice"), // alice
new User(3, "tom@example.com", "password", "Tom"))); // tom
public User login(String email, String password) {
for (User user : users) {
if (user.getEmail().equalsIgnoreCase(email) && user.getPassword().equals(password)) {
mailService.sendLoginMail(user);
return user;
}
}
throw new RuntimeException("login failed.");
}
public User getUser(long id) {
return this.users.stream().filter(user -> user.getId() == id).findFirst().orElseThrow();
}
public User register(String email, String password, String name) {
users.forEach((user) -> {
if (user.getEmail().equalsIgnoreCase(email)) {
throw new RuntimeException("email exist.");
}
});
User user = new User(users.stream().mapToLong(u -> u.getId()).max().getAsLong() + 1, email, password, name);
users.add(user);
mailService.sendRegistrationMail(user);
return user;
}
}
注意到UserService
通过setMailService()
注入了一个MailService
。
然后,我们需要编写一个特定的application.xml
配置文件,告诉Spring的IoC容器应该如何创建并组装Bean:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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" />
</beans>
注意观察上述配置文件,其中与XML Schema相关的部分格式是固定的,我们只关注两个<bean ...>
的配置:
- 每个
<bean ...>
都有一个id
标识,相当于Bean的唯一ID; - 在
userService
Bean中,通过<property name="..." ref="..." />
注入了另一个Bean; - Bean的顺序不重要,Spring根据依赖关系会自动正确初始化。
把上述XML配置文件用Java代码写出来,就像这样:
UserService userService = new UserService();
MailService mailService = new MailService();
userService.setMailService(mailService);
只不过Spring容器是通过读取XML文件后使用反射完成的。
如果注入的不是Bean,而是boolean
、int
、String
这样的数据类型,则通过value
注入,例如,创建一个HikariDataSource
:
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test" />
<property name="username" value="root" />
<property name="password" value="password" />
<property name="maximumPoolSize" value="10" />
<property name="autoCommit" value="true" />
</bean>
最后一步,我们需要创建一个Spring的IoC容器实例,然后加载配置文件,让Spring容器为我们创建并装配好配置文件中指定的所有Bean,这只需要一行代码:
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
接下来,我们就可以从Spring容器中“取出”装配好的Bean然后使用它:
// 获取Bean:
UserService userService = context.getBean(UserService.class);
// 正常调用:
User user = userService.login("bob@example.com", "password");
完整的main()
方法如下:
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
UserService userService = context.getBean(UserService.class);
User user = userService.login("bob@example.com", "password");
System.out.println(user.getName());
}
}
ApplicationContext
我们从创建Spring容器的代码:
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
可以看到,Spring容器就是ApplicationContext
,它是一个接口,有很多实现类,这里我们选择ClassPathXmlApplicationContext
,表示它会自动从classpath中查找指定的XML配置文件。
获得了ApplicationContext
的实例,就获得了IoC容器的引用。从ApplicationContext
中我们可以根据Bean的ID获取Bean,但更多的时候我们根据Bean的类型获取Bean的引用:
UserService userService = context.getBean(UserService.class);
Spring还提供另一种IoC容器叫BeanFactory
,使用方式和ApplicationContext
类似:
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("application.xml"));
MailService mailService = factory.getBean(MailService.class);
BeanFactory
和ApplicationContext
的区别在于,BeanFactory
的实现是按需创建,即第一次获取Bean时才创建这个Bean,而ApplicationContext
会一次性创建所有的Bean。实际上,ApplicationContext
接口是从BeanFactory
接口继承而来的,并且,ApplicationContext
提供了一些额外的功能,包括国际化支持、事件和通知机制等。通常情况下,我们总是使用ApplicationContext
,很少会考虑使用BeanFactory
。
练习
在上述示例的基础上,继续给UserService
注入DataSource
,并把注册和登录功能通过数据库实现。
小结
Spring的IoC容器接口是ApplicationContext
,并提供了多种实现类;
通过XML配置文件创建IoC容器时,使用ClassPathXmlApplicationContext
;
持有IoC容器后,通过getBean()
方法获取Bean的引用。