Skip to content
Spring系列随记(1) IoC

IoC, Inversion of Control, 反转控制


反转控制

举个例子,类型A与类型B都需要实例化类型C,那么由谁来创建再共享呢?还是都创建?销毁的时候顺序又是怎么样的?

组件间的相互共享与依赖会导致组件之间极为紧密的耦合,浪费资源且难以调试

解决这一问题的核心方案就是IoC(反转控制)

摘录

传统的应用程序中,控制权在程序本身,程序的控制流程完全由开发者控制,这种模式的缺点是,一个组件如果要使用另一个组件,必须先知道如何正确地创建它。

在IoC模式下,控制权发生了反转,即从应用程序转移到了IoC容器,所有组件不再由应用程序自己创建和配置,而是由IoC容器负责,这样,应用程序只需要直接使用已经创建好并且配置好的组件。为了能让组件在IoC容器中被“装配”出来,需要某种“注入”机制[1]

IoC负责组件的生命周期管理,而组件的使用与其成功的分离开来

Bean

JavaBean

JavaBean是一种符合命名规范的class,它通过getter和setter来定义属性[2]

我们可以使用Java核心库提供的Introspector枚举JavaBean的所有属性

java
public class Main {
    public static void main(String[] args) throws Exception {
        BeanInfo info = Introspector.getBeanInfo(Person.class);
        for (PropertyDescriptor pd : info.getPropertyDescriptors()) {
            System.out.println(pd.getName());
            System.out.println("  " + pd.getReadMethod());
            System.out.println("  " + pd.getWriteMethod());
        }
    }
}

SpringBean

bean 是由Spring IoC 容器实例化、组装和管理的对象

IoC容器创建与使用

我们可以使用ApplicationContext或者BeanFactory[3]创建IoC容器并获得相关的Bean

java
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
// 我们可以使用Bean的ID或者类型来获得对于的Bean的引用
UserService userService = context.getBean(UserService.class);
java
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("application.xml"));
MailService mailService = factory.getBean(MailService.class);

ApplicationContextBeanFactory的区别是:

  • ApplicationContext继承于后者,提供更多的功能,并且会一次性创建所有的Bean

Spring的Annotation

常见注解:

  • @Component:定义Bean

  • @Autowired: 引用注入

  • @Configuration: 配置类声明,现在使用AnnotationConfigApplicationContext(AppConfig.class)即可

  • @ComponentScan: 自动搜索当前类所在的包以及子包,创建标有@Component的Bean

  • @Scope: 声明创建的Bean的声明周期,默认为单例,可以声明原型

  • @PostConstruct: 初始化,注入后调用带该标记的无参数函数

  • @PreDestroy: 销毁时首先调用带该标记的无参数函数

  • @Primary: 两个同类型Bean时声明优先级

  • @Qualifier: 多个同类型Bean时指定别名,注入时(@Autowired)也需要指定别名

  • @Value: 注入资源值,一般使用classpath注入(src/main/resources)

  • @PropertySource: 在配置类中直接注入相关配置,然后使用@value(${x.xxx:默认值})

  • @Profile: 条件装配是否创建Bean

  • @Conditional: 更多复杂的条件装配,以及@ConditionalOnXXX系列

  • @Accessors(chain = true) : set返回对像,所以可以链式set

  • @Resource: 类似@Autowired,不过默认ByName

注入配置

  • 使用上面说的@PropertySource
  • 配置保存在一个配置类中,然后使用#{}从Bean读取属性

注入List

对于一系列接口相同,不同实现类的Bean,使用list时Spring会自动打包注入

例子

例子源自廖雪峰

java
public interface Validator {
    void validate(String email, String password, String name);
}

@Component
public class EmailValidator implements Validator {
    public void validate(String email, String password, String name) {
        if (!email.matches("^[a-z0-9]+\\@[a-z0-9]+\\.[a-z]{2,10}$")) {
            throw new IllegalArgumentException("invalid email: " + email);
        }
    }
}

@Component
public class PasswordValidator implements Validator {
    public void validate(String email, String password, String name) {
        if (!password.matches("^.{6,20}$")) {
            throw new IllegalArgumentException("invalid password");
        }
    }
}

@Component
public class NameValidator implements Validator {
    public void validate(String email, String password, String name) {
        if (name == null || name.isBlank() || name.length() > 20) {
            throw new IllegalArgumentException("invalid name: " + name);
        }
    }
}

@Component
public class Validators {
    @Autowired
    List<Validator> validators;

    public void validate(String email, String password, String name) {
        for (var validator : this.validators) {
            validator.validate(email, password, name);
        }
    }
}

引入第三方Bean

当Bean不在我们自己的package管理之内,我们可以在@Configuration编写一个方法创建第三方Bean返回,并加上标记@Bean


  1. 引自廖雪峰 ↩︎

  2. 布尔值用is来设置,其他用get和set ↩︎

  3. 请与FactoryBean区分开 ↩︎