本文正在参与「金石方案」
前语
又迎来了一年一度的金三银四,尽管说本年的大环境不好,可是招聘仍是在火热进行中。
面试过 Java 工程师的小伙伴都知道,Spring 中的 IOC 是面试高频题,面试官上来就问,知道什么是 IOC 吗,IOC 是怎样初始化的,以及 Bean 的生命周期等相关内容。
那么接下来,让咱们运用 ChatGPT 并结合自己的了解来讲述一下 IOC 的相关内容。
什么是 IOC?
什么是 IOC?ChatGPT 是这么说的:
Spring IOC(Inversion of Control,操控回转)是 Spring 结构的中心特性之一,它的基本思想是将目标的创立和目标间的联系由程序员手动办理转移到结构中主动办理。
在传统的开发形式中,程序员经过 new
关键字手动创立目标,并在程序中直接运用这些目标。而在 Spring IOC 中,程序员不需求手动创立目标,而是将目标的创立交给 Spring 容器进行办理。Spring 容器会依据配置文件中的规则主动创立目标并办理目标之间的联系,程序员只需求运用这些目标即可,从而将程序中的操控权回转到了结构中。
经过 Spring IOC,程序员能够愈加专注于业务逻辑的完结,而不需求重视目标的创立和办理,从而提高了开发功率和程序的可保护性。
一个关于 IOC 的实践例子是一个家庭运用了主动化家居体系。这个体系或许会有不同的设备、传感器和操控器,例如智能灯泡、温度传感器、音乐播放器等。在传统的编程形式中,每个设备都需求直接衔接到主操控器,并由主操控器直接办理一切设备的功能。但运用 IOC 形式后,每个设备能够被视为独立的组件,它们能够经过一个通用的接口来与其他组件交互。这样,当需求添加或删除设备时,只需对该设备进行必要的配置,而不需求修改整个体系的代码。
例如,当用户想要调整房间的温度时,他能够直接与智能温度传感器进行交互,传感器能够将数据发送到主操控器,然后主操控器能够向空调发送指令来改动室内温度。这儿的关键在于,每个组件都被视为独立的实体,它们能够相互通讯,而无需直接与其他组件或主操控器进行耦合。这种规划形式提高了体系的可保护性和可扩展性。
详细能够参阅这篇 Spring IoC有什么好处呢?,博主提炼了一下:
操控回转(Inversion of Control) 便是依靠倒置原则的一种代码规划的思路。详细选用的办法便是所谓的依靠注入(Dependency Injection) ,这几种概念的联系大约如下:
什么是依靠倒置原则? 假设咱们规划一辆汽车,结构如下所示:
这样的规划看起来没问题,可是可保护性却很低,在后面会进行解释。那现在换一种思路进行规划:
这便是依靠倒置原则 —— 把本来的高层修建依靠底层修建倒置过来,变成底层修建依靠高层修建。高层修建决定需求什么,底层去完结这样的需求,可是高层并不必管底层是怎样完结的。这样就不会呈现前面的 “牵一发起全身” 的情况。
为了便利了解,先界说四个 Class,车,车身,底盘,轮胎。然后初始化这辆车,最终跑这辆车。代码结构如下:
这便是车子的第一个规划思路,上层修建依靠下层修建,即每一个类的结构函数都直接调用了底层代码的结构函数。这样就会呈现很严重的问题,比如说我现在要把车的轮胎从30改成动态尺度的,那么咱们需求修改整个上层的一切结构函数!
这样的规划基本是不行保护的,因为在实践工程中,有些类会有几千个底层,如果要一一修改,所消耗的本钱太大了。
那咱们经过 IOC,对车子进行第二种思路的规划,重写车类的界说:
这次再把轮胎尺度变成动态的,如果要让整个体系顺畅运转,只需求做如下修改:
这姿态就不需求修改上层的其他代码,因而 IOC 提高了体系的可保护性和可扩展性。
IOC 的初始化进程
IOC 是怎样进行初始化的?ChatGPT 是这姿态描绘的:
Spring IOC 容器的初始化进程首要分为以下六个步骤:
- 资源定位:Spring IOC 容器会依据配置文件的路径,找到配置文件并加载到内存中。一般是从 XML 文件中进行读取。
- 载入配置文件:Spring IOC 容器会读取配置文件中的 Bean 界说信息,并将其封装成对应的 BeanDefinition 目标。例如读取 property 元素并注入到 BeanDefinition 实例中。
- 实例化 Bean 目标:Spring IOC 容器依据 BeanDefinition 目标中的信息,运用 Java 反射机制实例化 Bean 目标,并将其放入 IOC 容器中。
- 依靠注入:Spring IOC 容器会依据 BeanDefinition 目标中的依靠联系,注入 Bean 之间的依靠联系。
- Bean 初始化:如果 Bean 完结了特定的接口,Spring IOC 容器会在 Bean 实例化之后调用这些接口中的办法进行初始化。
-
容器初始化完结:Spring IOC 容器初始化完结后,能够经过
getBean()
办法获取容器中的 Bean 目标。
TIPs:
单例 bean 的初始化以及依靠注入一般都在容器初始化阶段进行,只有懒加载(lazy-init为true)的单例 bean 是在运用第一次调用
getBean()
时进行初始化和依靠注入。
// AbstractApplicationContext // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory);
这儿顺带提一下 Bean 的生命周期,如下图所示:
Bean 的生命周期一般包含以下阶段:
- 实例化:当程序调用某个类的结构办法时,将创立该类的实例。这个实例便是一个 Bean。
- 特点赋值:在实例化后,程序能够运用
set
办法或许直接为特点赋值,来设置 Bean 的特点值。 - 初始化:在特点赋值后,Spring 会经过回调函数(如
init-method
)或许注解(如@PostConstruct
)来完结 Bean 的初始化操作,比如建立与其他 Bean 的相关联系等。 - 运用:在初始化完结后,Bean 就能够被运用程序运用了。
- 毁掉:当运用程序不再需求 Bean 的时候,Spring 会调用回调函数(如
destroy-method
)或许注解(如@PreDestroy
)来完结 Bean 的毁掉操作,比如整理资源、断开数据库衔接等。
TIPs:
需求留意的是,Spring 容器掌握着 Bean 的生命周期,所以一切的 Bean 操作都必须在 Spring 容器的办理下进行。一起要防止呈现循环依靠,否则或许导致 Bean 的实例化失利。
手撕阉割版 IOC
先来一个简略的练练手,主打的便是一个凸显 IOC 特性:
public class IOCContainer {
private Map<String, Object> beans; // 存储目标实例的 Map
public IOCContainer() {
beans = new HashMap<>();
}
// 注册 bean 到容器中
public void registerBean(String name, Object bean) {
beans.put(name, bean);
}
// 获取 bean 目标
public Object getBean(String name) {
return beans.get(name);
}
}
在上述代码中,将 Map 目标 beans
当成是 IOC 容器,用来存储注册进行来的 Bean,registerBean()
用来将 Bean 注册到容器傍边,getBean()
办法则是从容器中获取现已注册的 Bean 目标。
那接下来写个测试用例,运转一下这个代码:
public class MyClass {
public void sayHello() {
System.out.println("Hello World! --sid10t.");
}
}
// 在 main 办法中运用 IOC 容器
public static void main(String[] args) {
IOCContainer container = new IOCContainer();
MyClass myClass = new MyClass();
container.registerBean("myClass", myClass); // 将 MyClass 的实例注册到 IOC 容器中
MyClass instance = (MyClass) container.getBean("myClass"); // 从 IOC 容器中获取 MyClass 的实例
instance.sayHello(); // 调用 MyClass 实例的办法
}
尽管这是一个十分简略的 IOC 容器完结的示例,但它清晰地表达了 IOC 的基本思想:经过将类的创立和依靠注入交给容器来办理,从而解耦运用程序中各个组件之间的依靠联系。在实践出产环境中,咱们或许会需求愈加完善的 IOC 容器完结,但这个示例能够作为一个根底来了解 IOC 容器的工作原理。
接下来,来点进阶挑战,经过 Java 反射机制,在原有的根底上,完结 Bean 的特点注入:
private Object createBean(Class<?> clazz, Map<String, Object> properties) throws Exception {
try {
// 结构函数实例化目标
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
Constructor<?> constructor = null;
for (Constructor<?> c : constructors) {
if (c.getParameterCount() == 0) {
constructor = c;
break;
}
}
if (constructor == null) {
throw new RuntimeException("No default constructor found for bean: " + clazz);
}
Object bean = constructor.newInstance();
// 注入依靠
for (Field field : clazz.getDeclaredFields()) {
if (properties.containsKey(field.getName())) {
field.setAccessible(true);
field.set(bean, properties.get(field.getName()));
}
}
return bean;
} catch (Exception e) {
throw new RuntimeException("Failed to create bean: " + clazz, e);
}
}
经过 getDeclaredConstructors()
获取类的结构器,再经过 getParameterCount() == 0
来获取到默许结构器,即无参结构器,再运用 getDeclaredFields()
获取到相应的特点名,经过 set()
进行特点注入。
最终写一个测试用例运转一下代码:
public static void main(String[] args) throws Exception {
MyIOCContainer container = new MyIOCContainer();
// 注册 UserDTO 和 UserService
Map<String, Object> userDTOProperties = new HashMap<>();
userDTOProperties.put("url", "jdbc:mysql://localhost:3306/test");
userDTOProperties.put("username", "root");
userDTOProperties.put("password", "123456");
container.registerBean("userDTO", UserDTO.class, userDTOProperties);
Map<String, Object> userServiceProperties = new HashMap<>();
userServiceProperties.put("userDTO", container.getBean("userDTO"));
container.registerBean("userService", UserService.class, userServiceProperties);
// 获取 UserService 并调用办法
UserService userService = (UserService) container.getBean("userService");
User user = new User("sid10t.", 18);
userService.addUser(user);
}
弥补:
考虑到有些读者或许不知道怎样规划 User,UserDTO 和 UserService 类,就贴一下代码作为参阅,其实是自己随意发挥就行,没有那么谨慎;
User.java
public class User {
private String name;
private int age;
public User() {
}
// Getter and Setter
}
UserDTO.java
public class UserDTO {
private String url;
private String username;
private String password;
public UserDTO() {
}
// Getter and Setter
}
UserService.java
public class UserService {
private UserDTO userDTO;
public UserService() {
}
public UserService(UserDTO userDTO) {
this.userDTO = userDTO;
}
public void addUser(User user) {
System.out.println("UserService.addUser: " + user.getName() + ", " + user.getAge());
System.out.println("url: " + userDTO.getUrl() + ", username: " + userDTO.getUsername() + ", password: " + userDTO.getPassword());
// 这儿省略了其他办法调用
}
// Getter and Setter
}
最终来点杂乱的,在 Spring 结构中完结 Bean 的注入,作用域,初始化和毁掉办法等功能,资源定位和载入配置文件这儿就不赘述了:
public class IOCContainer {
private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();
private Map<String, Object> singletonBeanMap = new HashMap<>();
// 资源定位
...
// 载入配置文件
...
public void register(Class<?> beanClass, String beanName, Scope scope) {
beanDefinitionMap.put(beanName, new BeanDefinition(beanClass, scope));
}
public <T> T getBean(String beanName) {
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
if (beanDefinition == null) {
throw new RuntimeException("No such bean: " + beanName);
}
if (beanDefinition.getScope() == Scope.SINGLETON) {
Object singletonBean = singletonBeanMap.get(beanName);
if (singletonBean != null) {
return (T) singletonBean;
}
}
Object bean = createBean(beanDefinition);
if (beanDefinition.getScope() == Scope.SINGLETON) {
singletonBeanMap.put(beanName, bean);
}
return (T) bean;
}
private Object createBean(BeanDefinition beanDefinition) {
Class<?> beanClass = beanDefinition.getBeanClass();
try {
// 结构函数实例化目标
Constructor<?>[] constructors = beanClass.getDeclaredConstructors();
Constructor<?> constructor = null;
for (Constructor<?> c : constructors) {
if (c.getParameterCount() == 0) {
constructor = c;
break;
}
}
if (constructor == null) {
throw new RuntimeException("No default constructor found for bean: " + beanClass);
}
Object bean = constructor.newInstance();
// 注入依靠
Field[] fields = beanClass.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(Autowired.class)) {
String fieldName = field.getName();
Object dependencyBean = getBean(fieldName);
field.setAccessible(true);
field.set(bean, dependencyBean);
}
}
// 履行初始化办法
Method[] methods = beanClass.getDeclaredMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(InitMethod.class)) {
method.invoke(bean);
}
}
return bean;
} catch (Exception e) {
throw new RuntimeException("Failed to create bean: " + beanClass, e);
}
}
public void close() {
for (Object singletonBean : singletonBeanMap.values()) {
Class<?> beanClass = singletonBean.getClass();
Method[] methods = beanClass.getDeclaredMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(DestroyMethod.class)) {
try {
method.invoke(singletonBean);
} catch (Exception e) {
throw new RuntimeException("Failed to invoke destroy method for bean: " + beanClass, e);
}
}
}
}
}
}
跋文
以上便是让 ChatGPT 来描绘 IOC的一切内容了,期望本篇博文对我们有所协助!
上篇精讲:【JAVA】让 ChatGPT 来浅说 AQS
我是,期待你的重视;
创作不易,请多多支持;
系列专栏:面试精讲 JAVA