参考
廖雪峰Spring教程
容器的意思可以理解为一个提供供程序正常运行,提供各种依赖的组件的包的环境。
IoC,控制反转,实际上就是将原本由代码编写者控制的各个对象(组件)的生命周期托管给底层的容器,应用层不需要一个个定义好什么时候初始化,什么时候析构释放,所有组件不再由应用程序自己创建和配置,而是由IoC容器负责,这样,应用程序只需要直接使用已经创建好并且配置好的组件。为了能让组件在IoC容器中被“装配”出来,需要某种“注入”机制。
什么是注入呢,假设类A中存在成员类B,这个B可以通过A的setter方式实例化,即依赖的函数提供setter的注入接口,然后coder需要在外部定义好依赖关系(xml文件)/或者通过注解,然后让IoC自己解析并创建依赖的各个组件
在设计上,Spring的IoC容器是一个高度可扩展的无侵入容器。所谓无侵入,是指应用程序的组件无需实现Spring的特定接口,或者说,组件根本不知道自己在Spring的容器中运行。这种无侵入的设计有以下好处:1. 应用程序组件既可以在Spring的IoC容器中运行,也可以自己编写代码自行组装配置;2. 测试的时候并不依赖Spring容器,可单独进行测试,大大提高了开发效率
什么是Bean? 简单理解就是需要被托管的组件
可以看以下代码,
public class MailService {
public void sendLoginMail(User user) {
}
public void sendRegisterMail(User user) {
}
}
/***********************************************************/
public class UserService {
private MailService mailService;
public void setMailService(MailService mailService) {
this.mailService = mailService;
}
private List<User> users;
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;
}
}
}
public User getUser(long id) {
//do something
}
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依赖MailService的两个函数,实际设置mailService时,通过设置setter搞定,IoC接口通过这个setter方法完成注入,但需要额外信息从xml中获取,以下
<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>
然后创建IoC实例,加载配置文件,让Spring容器为我们创建并装配好配置文件中指定的所有Bean
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
// 获取Bean:
UserService userService = context.getBean(UserService.class);
// 正常调用:
User user = userService.login("[email protected]", "password");
使用注解则可以直接不用再写繁琐的XML文件了,常用的注解组合有@Component @Autowired
和@Configuration @Bean
@Component 注解用于将一个类标记为组件(Component),表示它将被Spring容器实例化为一个Bean,就等同于XML中的bean 标签;@Autowired注解用于在需要依赖注入的地方标记,以实现自动装配。Spring容器会自动在上下文中查找匹配的Bean,并将其注入到被标记的字段、构造函数或方法参数中,对应bean下面的property标签。
用法如下
@Component
public class MyBean {
private AnotherBean anotherBean;
@Autowired
public MyBean(AnotherBean anotherBean) {
this.anotherBean = anotherBean;
}
@Autowired
public void setAnotherBean(AnotherBean anotherBean) {
this.anotherBean = anotherBean;
}
// ...
}
在这个示例中,MyBean 类中的 anotherBean 字段和 setAnotherBean() 方法都被标记为 @Autowired,表示它们需要被自动装配。Spring容器会自动查找匹配类型的Bean,并将其注入到这些位置。除了字段和方法参数,还可以在构造函数上使用 @Autowired 注解,以实现构造函数注入。
当在一个类中使用 @Autowired 注解标记一个字段、方法或构造函数时,底层的IOC容器会负责实例化这个类,并自动注入所需的依赖项。具体来说,当一个类被实例化时,IOC容器会检查该类中标记为 @Autowired 的字段、方法或构造函数参数。它会查找与这些依赖项类型匹配的Bean,并将它们自动注入到对应的位置。如果没有找到匹配的Bean,或者存在多个匹配的Bean时,Spring框架会抛出异常,提示依赖项无法自动注入或存在歧义。
如果组件既没有使用 @Autowired 注解标注构造函数,也没有使用 @Autowired 注解标注setter方法,IOC容器将不会自动进行依赖项的注入。在这种情况下,需要自己负责手动实例化依赖项并将其注入到组件中。
当存在多个匹配的Bean时,Spring会抛出 NoUniqueBeanDefinitionException 异常,提示依赖项的注入歧义。因此需要解决这个问题。
@Configuration和@Bean用法代码如下:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public MyBean myBean() {
return new MyBean();
}
}
当你需要定义和配置多个Bean时,可以使用 @Configuration 注解将一个类标识为配置类。在配置类中,你可以使用 @Bean 注解声明方法,该方法的返回值将被注册为一个Bean。通常,你可以在配置类中进行一些复杂的Bean创建和配置,包括外部依赖的集成、条件化的Bean定义等。
示例场景:创建和配置数据源、声明第三方库的连接器、定义自定义的Bean等。
@Value(resourcePath)
,用于注入文件,方便程序读取;@Value("${app.zone:Z}")
; @Value("#{smtpConfig.host}")
@Order(Num)
当多个Bean被注入到一个List时,若List中组件存在顺序要求,可以通过@Order指定顺序@Scope
对于Spring容器来说,当我们把一个Bean标记为@Component后,它就会自动为我们创建一个单例(Singleton),即容器初始化时创建Bean,容器关闭前销毁Bean。在容器运行期间,我们调用getBean(Class)获取到的Bean总是同一个实例。
还有一种Bean,我们每次调用getBean(Class),容器都返回一个新的实例,这种Bean称为Prototype(原型),它的生命周期显然和Singleton不同。声明一个Prototype的Bean时,需要添加一个额外的@Scope注解:
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) // @Scope("prototype")
public class MailSession {
...
}
@Bean(Name) || @Qualifier(Name)
默认情况下,对一种类型的Bean,容器只创建一个实例。但有些时候,我们需要对一种类型的Bean创建多个实例。例如,同时连接多个数据库,就必须创建多个DataSource实例。
如果我们在类中创建了多个同类型的Bean:
Spring会报NoUniqueBeanDefinitionException异常,意思是出现了重复的Bean定义。
这个时候,需要给每个Bean添加不同的名字
可以用@Bean(“name”)指定别名,也可以用@Bean+@Qualifier(“name”)指定别名。
@Configuration
@ComponentScan
public class AppConfig {
@Bean("z")
ZoneId createZoneOfZ() {
return ZoneId.of("Z");
}
@Bean
@Qualifier("utc8")
ZoneId createZoneOfUTC8() {
return ZoneId.of("UTC+08:00");
}
}
Spring的IoC容器会自动注册标记为 @Component 或其衍生注解的Bean。
当一个类被标记为 @Component、@Service、@Repository、@Controller 等注解之一时,Spring会自动扫描并注册这个类作为一个Bean。这个过程称为组件扫描(Component Scanning),Spring会自动扫描指定的包及其子包,找到所有标记为组件注解的类,并将其实例化并注册到IoC容器中。
通过自动扫描,Spring会根据类的命名约定或自定义的Bean名称规则为这些类创建默认的Bean名称。例如,对于一个标记为 @Component 的类,默认的Bean名称将是类名的首字母小写形式。你也可以通过 @Component(“beanName”) 的方式显式指定Bean的名称。
此外,Spring还支持在XML配置文件中显式地定义Bean的配置信息,无论是通过 @Bean 在 @Configuration 类中进行定义,还是直接在XML配置文件中进行定义。这些显式定义的Bean也会被注册到IoC容器中。