欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 社会 > Spring——原理:IoC

Spring——原理:IoC

2024/11/15 13:25:18 来源:https://blog.csdn.net/Ling_suu/article/details/143690496  浏览:    关键词:Spring——原理:IoC

原理:手写IoC

Spring框架的IoC是基于java反射机制实现的,需要先回顾一下java反射。

回顾Java反射

Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。简单来说,反射机制指的是程序在运行时能够获取自身的信息。

要想解剖一个类,必须先要获取到该类的Class对象。而剖析一个类或用反射解决具体的问题就是使用相关API

(1)java.lang.Class

(2)java.lang.reflect

所以,Class对象是反射的根源

自定义类

package com.ling.reflect;public class Car {// 属性private String name;private int age;private String color;// 无参数构造public Car() {}// 有参数构造public Car(String name, int age, String color) {this.name = name;this.age = age;this.color = color;}// 普通方法private void run() {System.out.println("私有方法————汽车在跑");}// get和set方法public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String getColor() {return color;}public void setColor(String color) {this.color = color;}@Overridepublic String toString() {return "Car{" +"name='" + name + '\'' +", age=" + age +", color='" + color + '\'' +'}';}
}

编写测试类

package com.ling.reflect;import org.junit.jupiter.api.Test;import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;public class TestCar {// 1、获取Class对象多种方式@Testpublic void test01() throws Exception {// 1、类名.classClass c1 = Car.class;// 2、对象.getClass()Class c2 = new Car().getClass();// 3、Class.forName("全类名")Class c3 = Class.forName("com.ling.reflect.Car");// 实例化Car car = (Car) c3.getDeclaredConstructor().newInstance();System.out.println(car); // com.ling.reflect.Car@6b0c2d26}// 2、获取构造方法@Testpublic void test02() throws Exception {Class c = Car.class;// 获取所有的构造// getConstructors()获取所有的public修饰的构造方法
//        Constructor[] constructors = c.getConstructors();// getDeclaredConstructors()获取所有的构造方法Constructor[] constructors = c.getDeclaredConstructors();for (Constructor constructor : constructors) {
//            System.out.println(constructor);/*public com.ling.reflect.Car(java.lang.String,int,java.lang.String)public com.ling.reflect.Car()*/System.out.println("方法名称:" + constructor.getName() + " 参数个数:" + constructor.getParameterCount());/*方法名称:com.ling.reflect.Car 参数个数:3方法名称:com.ling.reflect.Car 参数个数:0*/}// 指定有参数构造创建对象// 构造方法是public修饰的Constructor constructor = c.getConstructor(String.class, int.class, String.class);Car car = (Car) constructor.newInstance("宝马", 100, "红色");System.out.println(car); // com.ling.reflect.Car@7bc1a03d// 构造方法是private修饰的Constructor constructor1 = c.getDeclaredConstructor(String.class, int.class, String.class);constructor1.setAccessible(true);Car car1 = (Car) constructor1.newInstance("奔驰", 200, "黑色");System.out.println(car1); // com.ling.reflect.Car@70b0b186}// 3、获取属性@Testpublic void test03() throws Exception {Class c = Car.class;Car car = (Car) c.getDeclaredConstructor().newInstance();// 获取所有public属性Field[] fields = c.getFields();for (Field field : fields) {System.out.println("(Field)属性名称:" + field.getName() + " 属性类型:" + field.getType());}// 获取所有属性Field[] declaredFields = c.getDeclaredFields();for (Field field : declaredFields) {if (field.getName().equals("name")) {field.setAccessible(true);field.set(car, "奔驰");}System.out.println("(DeclaredField)属性名称:" + field.getName() + " 属性类型:" + field.getType());System.out.println(car);/*(DeclaredField)属性名称:name 属性类型:class java.lang.StringCar{name='奔驰', age=0, color='null'}(DeclaredField)属性名称:age 属性类型:intCar{name='奔驰', age=0, color='null'}(DeclaredField)属性名称:color 属性类型:class java.lang.StringCar{name='奔驰', age=0, color='null'}*/}}// 4、获取方法@Testpublic void test04() throws Exception {Car car = new Car("奔驰", 10, "黑色");Class c = car.getClass();// public方法Method[] methods = c.getMethods();for (Method method : methods) {System.out.println("(method)方法名称:" + method.getName() + " 方法返回值类型:" + method.getReturnType());if (method.getName().equals("toString")) {String invoke = (String) method.invoke(car);System.out.println(invoke); // Car{name='奔驰', age=10, color='黑色'}}/*(method)方法名称:getAge 方法返回值类型:int(method)方法名称:getColor 方法返回值类型:class java.lang.String(method)方法名称:setAge 方法返回值类型:void(method)方法名称:getName 方法返回值类型:class java.lang.String(method)方法名称:toString 方法返回值类型:class java.lang.StringCar{name='奔驰', age=10, color='黑色'}(method)方法名称:setName 方法返回值类型:void(method)方法名称:setColor 方法返回值类型:void(method)方法名称:equals 方法返回值类型:boolean(method)方法名称:hashCode 方法返回值类型:int(method)方法名称:getClass 方法返回值类型:class java.lang.Class(method)方法名称:notify 方法返回值类型:void(method)方法名称:notifyAll 方法返回值类型:void(method)方法名称:wait 方法返回值类型:void(method)方法名称:wait 方法返回值类型:void(method)方法名称:wait 方法返回值类型:void*/}// private方法Method[] declaredMethods = c.getDeclaredMethods();for (Method declaredMethod : declaredMethods) {System.out.println("(declaredMethod)方法名称:" + declaredMethod.getName() + " 方法返回值类型:" + declaredMethod.getReturnType());if (declaredMethod.getName().equals("run")) {declaredMethod.setAccessible(true);declaredMethod.invoke(car); // 私有方法————汽车在跑}/*(declaredMethod)方法名称:getAge 方法返回值类型:int(declaredMethod)方法名称:getColor 方法返回值类型:class java.lang.String(declaredMethod)方法名称:setAge 方法返回值类型:void(declaredMethod)方法名称:getName 方法返回值类型:class java.lang.String(declaredMethod)方法名称:run 方法返回值类型:void私有方法————汽车在跑(declaredMethod)方法名称:toString 方法返回值类型:class java.lang.String(declaredMethod)方法名称:setName 方法返回值类型:void(declaredMethod)方法名称:setColor 方法返回值类型:void*/}}
}

实现Spring的IoC(重点)

我们知道,IoC(控制反转)和DI(依赖注入)是Spring里面核心的东西,那么,我们如何自己手写出这样的代码呢?下面我们就一步一步写出Spring框架最核心的部分。

①搭建子模块

搭建模块:ling-spring,搭建方式如其他spring子模块

②准备测试需要的bean

添加依赖
<!--junit-->
<dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId><version>5.8.1</version>
</dependency>
创建UserDao接口
package com.ling.dao;public interface UserDao {void add();
}
创建UserDaoImpl实现
package com.ling.dao.impl;import com.ling.annotation.Bean;
import com.ling.dao.UserDao;@Bean
public class UserDaoImpl implements UserDao {@Overridepublic void add() {System.out.println("UserDaoImpl add...");}
}
创建UserService接口
package com.ling.service;public interface UserService {void add();
}
创建UserServiceImpl实现类
package com.ling.service.impl;import com.ling.annotation.Bean;
import com.ling.annotation.Di;
import com.ling.dao.UserDao;
import com.ling.service.UserService;@Bean
public class UserServiceImpl implements UserService {@Diprivate UserDao userDao;@Overridepublic void add() {System.out.println("user service add...");// 调用dao的方法userDao.add();}
}

③定义注解

我们通过注解的形式加载bean与实现依赖注入

bean注解
package com.ling.annotation;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Bean {
}
依赖注入注解
package com.ling.annotation;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Di {
}

说明:上面两个注解可以随意取名

④定义bean容器接口

package com.ling.bean;public interface ApplicationContext {Object getBean(Class clazz);
}

⑤编写注解bean容器接口实现

AnnotationApplicationContext基于注解扫描bean
package com.ling.bean.impl;import java.util.HashMap;public class AnnotationApplicationContext implements ApplicationContext {//存储bean的容器private HashMap<Class, Object> beanMap = new HashMap<>();@Overridepublic Object getBean(Class clazz) {return beanMap.get(clazz);}/*** 根据包扫描加载bean* @param basePackage*/public AnnotationApplicationContext(String basePackage) {}
}

⑥编写扫描bean逻辑

我们通过构造方法传入包的base路径,扫描被@Bean注解的java对象,完整代码如下:

package com.ling.bean.impl;import com.ling.annotation.Bean;
import com.ling.annotation.Di;
import com.ling.bean.ApplicationContext;import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;/*** AnnotationApplicationContext 类实现了 ApplicationContext 接口,用于通过注解方式管理 Bean。*/
public class AnnotationApplicationContext implements ApplicationContext {// 创建 Map 集合,用于存放 Bean 对象private Map<Class, Object> beanMap = new HashMap<>();private static String rootPath;/*** 根据包名初始化 AnnotationApplicationContext 实例,并扫描包及其子包中的类。** @param packageName 包名* @throws Exception 如果扫描过程中发生异常*/public AnnotationApplicationContext(String packageName) throws Exception {// 1. 将包名中的点(.)替换为反斜杠(\)String packagePath = packageName.replaceAll("\\.", "\\\\");// 2. 获取包的绝对路径Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packagePath);
//        Enumeration<URL> urls = this.getClass().getClassLoader().getResources(packagePath);while (urls.hasMoreElements()) {URL url = urls.nextElement();String filePath = URLDecoder.decode(url.getFile(), "UTF-8");// 获取包前面路径部分,字符串截取rootPath = filePath.substring(0, filePath.length() - packagePath.length());// 包扫描try {loadBean(new File(filePath));} catch (Exception e) {throw new RuntimeException(e);}}// 属性注入loadDi();}// 进行属性的注入private void loadDi() throws IllegalAccessException {// 实例化对象在beanFactory的map集合里面// 1、遍历beanFactory的map集合Set<Map.Entry<Class, Object>> entries = beanMap.entrySet();for (Map.Entry<Class, Object> entry : entries) {// 2、获取map集合每个对象(value),每个对象属性获取到Object obj = entry.getValue();// 获取对象ClassClass clazz = obj.getClass();// 获取每个对象的属性Field[] declaredFields = clazz.getDeclaredFields();// 3、遍历得到的每个对象属性数组,得到每个属性for (Field declaredField : declaredFields) {// 4、判断属性上面是否有@Di注解Di annotation = declaredField.getAnnotation(Di.class);if (annotation != null) {// 如果私有属性,设置可以访问declaredField.setAccessible(true);// 5、如果有@Di注解,把对象进行设置(注入declaredField.set(obj, beanMap.get(declaredField.getType()));}}}}/*** 包扫描过程,递归地实例化带有 @Bean 注解的类。** @param file 当前文件或文件夹* @throws Exception 如果实例化过程中发生异常*/private void loadBean(File file) throws Exception {// 1. 判断当前内容是否是文件夹if (file.isDirectory()) {// 2. 获取文件夹里面所有内容File[] childrenFiles = file.listFiles();// 3. 判断文件夹里面为空,直接返回if (childrenFiles == null || childrenFiles.length == 0) {return;}// 4. 若果文件夹里面不为空,遍历文件夹所有内容for (File childFile : childrenFiles) {// 4.1. 遍历得到每个 File 对象,继续判断,如果还是文件夹,递归if (childFile.isDirectory()) {// 递归loadBean(childFile);} else {// 4.2. 遍历得到 File 对象不是文件夹,是文件// 4.3. 得到包路径+类名称部分 ———— 字符串截取过程String pathWithClass = childFile.getAbsolutePath().substring(rootPath.length() - 1);// 4.4. 判断当前文件类型是否是.classif (pathWithClass.endsWith(".class")) {// 4.5. 如果是.class类型,把路径\替换成.  把.class去掉String allName = pathWithClass.replaceAll("\\\\", ".").replace(".class", "");// 4.6. 判断类上面是否有注解 @Bean, 如果有,则进行实例化过程// 4.6.1. 得到类的 Class 对象Class clazz = Class.forName(allName);// 4.6.2. 判断不是接口if (!clazz.isInterface()) {// 4.6.3. 判断类上面是否有注解 @BeanBean annotation = (Bean) clazz.getAnnotation(Bean.class);if (annotation != null) {// 4.6.4. 进行实例化Object instance = clazz.getDeclaredConstructor().newInstance();// 4.7. 把对象实例化之后,放到 map 集合当中// 4.7.1. 判断当前类如果有接口,让接口 class 作为 map 的 keyif (clazz.getInterfaces().length > 0) {beanMap.put(clazz.getInterfaces()[0], instance);} else {beanMap.put(clazz, instance);}}}}}}}}/*** 根据类类型获取对应的 Bean 实例。** @param clazz 类类型* @return 对应的 Bean 实例*/@Overridepublic Object getBean(Class clazz) {return beanMap.get(clazz);}
}

⑦java类标识Bean注解

@Bean
public class UserServiceImpl implements UserService
@Bean
public class UserDaoImpl implements UserDao 

⑧测试Bean加载

package com.ling;import com.ling.bean.ApplicationContext;
import com.ling.bean.impl.AnnotationApplicationContext;
import com.ling.service.UserService;public class Test {public static void main(String[] args) throws Exception {ApplicationContext context = new AnnotationApplicationContext("com.ling");UserService userService = (UserService) context.getBean(UserService.class);System.out.println(userService);userService.add();}
}

⑨依赖注入

package com.ling.service.impl;import com.ling.annotation.Bean;
import com.ling.annotation.Di;
import com.ling.dao.UserDao;
import com.ling.service.UserService;@Bean
public class UserServiceImpl implements UserService {@Diprivate UserDao userDao;@Overridepublic void add() {System.out.println("user service add...");// 调用dao的方法userDao.add();}
}

⑩依赖注入实现

package com.ling.bean.impl;import com.ling.annotation.Bean;
import com.ling.annotation.Di;
import com.ling.bean.ApplicationContext;import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;/*** AnnotationApplicationContext 类实现了 ApplicationContext 接口,用于通过注解方式管理 Bean。*/
public class AnnotationApplicationContext implements ApplicationContext {// 创建 Map 集合,用于存放 Bean 对象private Map<Class, Object> beanMap = new HashMap<>();private static String rootPath;/*** 根据包名初始化 AnnotationApplicationContext 实例,并扫描包及其子包中的类。** @param packageName 包名* @throws Exception 如果扫描过程中发生异常*/public AnnotationApplicationContext(String packageName) throws Exception {// 1. 将包名中的点(.)替换为反斜杠(\)String packagePath = packageName.replaceAll("\\.", "\\\\");// 2. 获取包的绝对路径Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packagePath);
//        Enumeration<URL> urls = this.getClass().getClassLoader().getResources(packagePath);while (urls.hasMoreElements()) {URL url = urls.nextElement();String filePath = URLDecoder.decode(url.getFile(), "UTF-8");// 获取包前面路径部分,字符串截取rootPath = filePath.substring(0, filePath.length() - packagePath.length());// 包扫描try {loadBean(new File(filePath));} catch (Exception e) {throw new RuntimeException(e);}}// 属性注入loadDi();}// 进行属性的注入private void loadDi() throws IllegalAccessException {// 实例化对象在beanFactory的map集合里面// 1、遍历beanFactory的map集合Set<Map.Entry<Class, Object>> entries = beanMap.entrySet();for (Map.Entry<Class, Object> entry : entries) {// 2、获取map集合每个对象(value),每个对象属性获取到Object obj = entry.getValue();// 获取对象ClassClass clazz = obj.getClass();// 获取每个对象的属性Field[] declaredFields = clazz.getDeclaredFields();// 3、遍历得到的每个对象属性数组,得到每个属性for (Field declaredField : declaredFields) {// 4、判断属性上面是否有@Di注解Di annotation = declaredField.getAnnotation(Di.class);if (annotation != null) {// 如果私有属性,设置可以访问declaredField.setAccessible(true);// 5、如果有@Di注解,把对象进行设置(注入declaredField.set(obj, beanMap.get(declaredField.getType()));}}}}/*** 包扫描过程,递归地实例化带有 @Bean 注解的类。** @param file 当前文件或文件夹* @throws Exception 如果实例化过程中发生异常*/private void loadBean(File file) throws Exception {// 1. 判断当前内容是否是文件夹if (file.isDirectory()) {// 2. 获取文件夹里面所有内容File[] childrenFiles = file.listFiles();// 3. 判断文件夹里面为空,直接返回if (childrenFiles == null || childrenFiles.length == 0) {return;}// 4. 若果文件夹里面不为空,遍历文件夹所有内容for (File childFile : childrenFiles) {// 4.1. 遍历得到每个 File 对象,继续判断,如果还是文件夹,递归if (childFile.isDirectory()) {// 递归loadBean(childFile);} else {// 4.2. 遍历得到 File 对象不是文件夹,是文件// 4.3. 得到包路径+类名称部分 ———— 字符串截取过程String pathWithClass = childFile.getAbsolutePath().substring(rootPath.length() - 1);// 4.4. 判断当前文件类型是否是.classif (pathWithClass.endsWith(".class")) {// 4.5. 如果是.class类型,把路径\替换成.  把.class去掉String allName = pathWithClass.replaceAll("\\\\", ".").replace(".class", "");// 4.6. 判断类上面是否有注解 @Bean, 如果有,则进行实例化过程// 4.6.1. 得到类的 Class 对象Class clazz = Class.forName(allName);// 4.6.2. 判断不是接口if (!clazz.isInterface()) {// 4.6.3. 判断类上面是否有注解 @BeanBean annotation = (Bean) clazz.getAnnotation(Bean.class);if (annotation != null) {// 4.6.4. 进行实例化Object instance = clazz.getDeclaredConstructor().newInstance();// 4.7. 把对象实例化之后,放到 map 集合当中// 4.7.1. 判断当前类如果有接口,让接口 class 作为 map 的 keyif (clazz.getInterfaces().length > 0) {beanMap.put(clazz.getInterfaces()[0], instance);} else {beanMap.put(clazz, instance);}}}}}}}}/*** 根据类类型获取对应的 Bean 实例。** @param clazz 类类型* @return 对应的 Bean 实例*/@Overridepublic Object getBean(Class clazz) {return beanMap.get(clazz);}
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com