IoC, Inversion of Control, 反转控制
反转控制
举个例子,类型A与类型B都需要实例化类型C,那么由谁来创建再共享呢?还是都创建?销毁的时候顺序又是怎么样的?
组件间的相互共享与依赖会导致组件之间极为紧密的耦合,浪费资源且难以调试
解决这一问题的核心方案就是IoC(反转控制)
摘录
传统的应用程序中,控制权在程序本身,程序的控制流程完全由开发者控制,这种模式的缺点是,一个组件如果要使用另一个组件,必须先知道如何正确地创建它。
在IoC模式下,控制权发生了反转,即从应用程序转移到了IoC容器,所有组件不再由应用程序自己创建和配置,而是由IoC容器负责,这样,应用程序只需要直接使用已经创建好并且配置好的组件。为了能让组件在IoC容器中被“装配”出来,需要某种“注入”机制[1]
IoC负责组件的生命周期管理,而组件的使用与其成功的分离开来
Bean
JavaBean
JavaBean是一种符合命名规范的class,它通过getter和setter来定义属性[2]
我们可以使用Java核心库提供的Introspector
枚举JavaBean的所有属性
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
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
// 我们可以使用Bean的ID或者类型来获得对于的Bean的引用
UserService userService = context.getBean(UserService.class);
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("application.xml"));
MailService mailService = factory.getBean(MailService.class);
ApplicationContext
与BeanFactory
的区别是:
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会自动打包注入
例子
例子源自廖雪峰
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