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;
}
@Override
public 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对象多种方式
@Test
public void test01() throws Exception {
// 1、类名.class
Class 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、获取构造方法
@Test
public 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、获取属性
@Test
public 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.String
Car{name='奔驰', age=0, color='null'}
(DeclaredField)属性名称:age 属性类型:int
Car{name='奔驰', age=0, color='null'}
(DeclaredField)属性名称:color 属性类型:class java.lang.String
Car{name='奔驰', age=0, color='null'}
*/
}
}
// 4、获取方法
@Test
public 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.String
Car{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 {
@Override
public 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 {
@Di
private UserDao userDao;
@Override
public 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<>();
@Override
public 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();
// 获取对象Class
Class 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. 判断当前文件类型是否是.class
if (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. 判断类上面是否有注解 @Bean
Bean 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 的 key
if (clazz.getInterfaces().length > 0) {
beanMap.put(clazz.getInterfaces()[0], instance);
} else {
beanMap.put(clazz, instance);
}
}
}
}
}
}
}
}
/**
* 根据类类型获取对应的 Bean 实例。
*
* @param clazz 类类型
* @return 对应的 Bean 实例
*/
@Override
public 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 {
@Di
private UserDao userDao;
@Override
public 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();
// 获取对象Class
Class 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. 判断当前文件类型是否是.class
if (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. 判断类上面是否有注解 @Bean
Bean 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 的 key
if (clazz.getInterfaces().length > 0) {
beanMap.put(clazz.getInterfaces()[0], instance);
} else {
beanMap.put(clazz, instance);
}
}
}
}
}
}
}
}
/**
* 根据类类型获取对应的 Bean 实例。
*
* @param clazz 类类型
* @return 对应的 Bean 实例
*/
@Override
public Object getBean(Class clazz) {
return beanMap.get(clazz);
}
}