Spring学习 (一): IoC容器

前言

参考
廖雪峰Spring教程

一、什么是IoC容器

容器的意思可以理解为一个提供供程序正常运行,提供各种依赖的组件的包的环境。

IoC,控制反转,实际上就是将原本由代码编写者控制的各个对象(组件)的生命周期托管给底层的容器,应用层不需要一个个定义好什么时候初始化,什么时候析构释放,所有组件不再由应用程序自己创建和配置,而是由IoC容器负责,这样,应用程序只需要直接使用已经创建好并且配置好的组件。为了能让组件在IoC容器中被“装配”出来,需要某种“注入”机制。

什么是注入呢,假设类A中存在成员类B,这个B可以通过A的setter方式实例化,即依赖的函数提供setter的注入接口,然后coder需要在外部定义好依赖关系(xml文件)/或者通过注解,然后让IoC自己解析并创建依赖的各个组件

在设计上,Spring的IoC容器是一个高度可扩展的无侵入容器。所谓无侵入,是指应用程序的组件无需实现Spring的特定接口,或者说,组件根本不知道自己在Spring的容器中运行。这种无侵入的设计有以下好处:1. 应用程序组件既可以在Spring的IoC容器中运行,也可以自己编写代码自行组装配置;2. 测试的时候并不依赖Spring容器,可单独进行测试,大大提高了开发效率


二、装配Bean

什么是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>
  • 每个都有一个id标识,相当于Bean的唯一ID;
  • 在userServiceBean中,通过注入了另一个Bean;
  • Bean的顺序不重要,Spring根据依赖关系会自动正确初始化

然后创建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();
    }
}
  • @Configuration 是一个注解,用于标识一个类作为配置类。配置类中可以定义Bean的创建和配置信息,以及其他配置项,如AOP、事务管理等。它表明这个类被用来声明Bean的定义。
  • @Bean 注解用于在配置类中声明一个Bean。通过在一个方法上添加 @Bean 注解,该方法的返回值将被注册为一个Bean,并由Spring容器进行管理。可以通过在 @Configuration 类中的方法上使用 @Bean 注解来创建和配置Bean。(这样子可以让一个返回的对象自动注册为Bean)

当你需要定义和配置多个Bean时,可以使用 @Configuration 注解将一个类标识为配置类。在配置类中,你可以使用 @Bean 注解声明方法,该方法的返回值将被注册为一个Bean。通常,你可以在配置类中进行一些复杂的Bean创建和配置,包括外部依赖的集成、条件化的Bean定义等。
示例场景:创建和配置数据源、声明第三方库的连接器、定义自定义的Bean等。


四、其他注解

  1. @Value:用法@Value(resourcePath),用于注入文件,方便程序读取;@Value("${app.zone:Z}"); @Value("#{smtpConfig.host}")
  2. @Order:用法@Order(Num) 当多个Bean被注入到一个List时,若List中组件存在顺序要求,可以通过@Order指定顺序
  3. @PostConstruct:一个Bean在注入必要的依赖后,需要进行初始化(监听消息等,我们通常会定义一个init()方法进行初始化,用@PostConstruct修饰
  4. @PreDestory:在容器关闭时,有时候还需要清理资源(关闭连接池等)定义一个shutdown()方法进行清理,用@PreDestory修饰
  5. @PropertySource(“app.properties”) :读取以key=value的形式写在.properties文件中的配置
  6. @Profile: 条件装配,用于表示不同环境下的装配, 分为test,native, production三个环境

@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容器中。

你可能感兴趣的:(Java,spring,学习,java)