🎄实现剩下的需要的注解
在SpringIoC原理实现I
中 已经实现了两个注解 分别是 @ComponentScan
和 @Scope
接着SpringIoC原理实现I
现在来实现剩下的需要的注解
📑 @Component
用于标识一个类是一个Bean
当IoC容器扫描包的时候发现一个Class对象被@Component
注解修饰 则应该将其创建并放入singletonObjects
容器进行管理
package bbm.com.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
@author Liu Xianmeng
@createTime 2023/9/14 23:33
@instruction @Component注解用于标识一个Java类是一个Bean 标识后 它将被IoC容器管理
*/
@SuppressWarnings({"all"})
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
String name() default ""; // 指定bean的名字
}
📑 @Autowired
标识一个属性应该被注入
package bbm.com.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
@author Liu Xianmeng
@createTime 2023/9/14 23:37
@instruction @Autowired修饰Bean中的变量域 如果类中的某变量被@Autowired修饰
这个变量对应的类对象就应该被注入到这个类中
所谓的注入 其实就是将singletonObjects中此变量的类对象的引用赋值给这个变量
*/
@SuppressWarnings({"all"})
@Target(ElementType.FIELD) // 用于修饰变量域
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
String name() default ""; // 指定注入的变量的名字
}
🎄创建要被IoC管理的Beans
接下来创建要被IoC管理的Beans 事实上 IoC容器的实现现在已经可以开始了 因为实现的逻辑是固定的 只不过先创建一些Beans 在实现的时候会更加清晰 逻辑步骤能对应到相应的Bean
对应于JavaEE开发的后端模式 Bean包括pojo类、Dao类(Data Access Object)、Service类、Controller类 下面一一进行创建
📑Duck鸭子pojo类 pojo类
package bbm.com.component;
/**
@author Liu Xianmeng
@createTime 2023/9/14 23:55
@instruction 鸭子pojo类 pojo类不作为组件进行管理
*/
public class Duck {
String name; // 鸭子的名字
int age; // 鸭子的年龄
public Duck() {
}
public Duck(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Duck{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
📑DuckDao
类
package bbm.com.component;
import bbm.com.annotation.Component;
import java.util.concurrent.ConcurrentHashMap;
/**
@author Liu Xianmeng
@createTime 2023/9/15 0:03
@instruction DuckDao将作为一个组件被放入IoC容器 名字为duckDao
*/
@Component(name = "duckDao")
@Scope("singleton") // 指定为单例对象
public class DuckDao {
// 用一个ConcurrentHashMap模拟数据库的数据
public static ConcurrentHashMap<String, Duck> data;
// 构造器初始化数据
public DuckDao() {
data = new ConcurrentHashMap<>();
data.put("littleYellow", new Duck("littleYellow", 2));
data.put("diandian", new Duck("diandian", 1));
data.put("littleBlack", new Duck("littleBlack", 3));
}
// 通过鸭子的名字获取鸭子对象并返回
public Duck getOneByName(String duckName) {
return data.get(duckName);
}
}
📑DuckService
类
package bbm.com.component;
/**
@author Liu Xianmeng
@createTime 2023/9/15 0:13
@instruction DuckService将作为一个组件被放入IoC容器 名字为duckService
*/
import bbm.com.annotation.Autowired;
import bbm.com.annotation.Component;
@Component(name = "duckService")
@Scope("singleton") // 指定为单例对象
public class DuckService {
// 注意使用🎯private进行修饰
@Autowired
private DuckDao duckDao; // 注入容器中此对象
// 通过鸭子的名字获取鸭子对象并返回
public Duck getOneByName(String duckName) {
// 使用注入的duckDao属性
return duckDao.getOneByName(duckName);
}
}
📑DuckController
类
package bbm.com.component;
import bbm.com.annotation.Autowired;
import bbm.com.annotation.Component;
/**
@author Liu Xianmeng
@createTime 2023/9/15 0:17
@instruction duckController将作为一个组件被放入IoC容器 名字为duckController
*/
@Component(name = "duckController")
@Scope("singleton") // 指定为单例对象
public class DuckController {
// 注意使用🎯private进行修饰
@Autowired
private DuckService duckService;
// 通过鸭子的名字获取鸭子对象并返回
public Duck getOneByName(String duckName) {
// 使用注入的duckService属性
return duckService.getOneByName(duckName);
}
}
到了这里 整个IoC实现的目录框架就搭建好了 爆红是因为 BigBigMengAnnotationApplicationContext
IoC容器类还没有实现
🎄实现IoC容器类
🎯接下来就是重头戏了 来实现IoC容器类
实现之前 先来回顾一下BigBigMengAnnotationApplicationContext
类的结构 具体的注释可见上一篇:
目前还只是伪代码
/**
@author Liu Xianmeng
@createTime 2023/9/14 17:27
@instruction IoC容器类 这个类就是要模拟实现额IoC容器类
下面将BigBigMengAnnotationApplicationContext的类名简称为 "IoC容器"
*/
public class BigBigMengAnnotationApplicationContext { // IoC容器
private Class configClass; // 配置类Class对象
private ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(); // Bean的定义信息
private ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>(); // 单例池对象
public BigBigMengAnnotationApplicationContext(Class configClass) {
[1]beanDefinitionsByScan(configClass);
[2]initialSingletonObjects();
}
public void beanDefinitionsByScan(Class configClass) {
// 通过配置configClass对象获取扫描包并执行扫描
}
public void initialSingletonObjects(){}
private Object createBean(BeanDefinition beanDefinition) {}
public Object getBean(String name) {}
}
🍭梳理实现思路
构造函数中初始化IoC容器 先完成beanDefinitionMap
的初始化 这个任务是beanDefinitionsByScan
函数完成的 然后根据beanDefinitionMap
完成singletonObjects
的初始化 过程中会使用到getBean
和createBean
两个辅助函数
使用自顶向下的顺序来实现:
[1] BigBigMengAnnotationApplicationContext(Class configClass)
[2] beanDefinitionsByScan(Class configClass)
[3] initialSingletonObjects()
[4] getBean(String name)
和createBean(BeanDefinition beanDefinition)
接下来就一步步进行实现
📑实现BigBigMengAnnotationApplicationContext
构造函数
/**
* IoC容器的构造器
*
* 构造器中完成两个任务
* (1)初始化Beans的定义信息beanDefinitionMap
* (2)遍历Beans的定义信息beanDefinitionMap初始化单例池singletonObjects
*
* @param configClass 在创建一个IoC容器对象的时候 需要传入配置类的Class对象
* 配置类的Class对象 上文已经提到 IoC容器
*/
public BigBigMengAnnotationApplicationContext(Class configClass) {
this.configClass = configClass;
beanDefinitionsByScan(this.configClass);
initialSingletonObjects();
}
📑实现beanDefinitionsByScan
函数
/**
* beanDefinitionsByScan方法完成对指定包的扫描 并将Bean信息封装到beanDefinitionMap
*/
public void beanDefinitionsByScan(Class configClass) {
/*********** 获取要扫描的包 ***********/
// 首先先获取配置类的注解
ComponentScan scanAnnotation =
(ComponentScan) this.configClass.getDeclaredAnnotation(ComponentScan.class);
// 然后通过componentScan的value值获取要扫描的包
// value()是ComponentScan注解的一个属性 带括号显得有些别扭 但这是注解的规范
String packageName = scanAnnotation.value();
System.out.println("C IoC M beanDefinitionsByScan() -> IoC要扫描的包 = " + packageName);
/*********** 根据类加载器获取要扫描的资源 ***********/
// 获取类加载器
ClassLoader classLoader = BigBigMengAnnotationApplicationContext.class.getClassLoader();
// 通过类的加载器获取到要扫描的包的资源
String path = packageName.replace(".", "/");
URL resource = classLoader.getResource(path);
System.out.println("C IoC M beanDefinitionsByScan() -> 包的完整路径 =" + resource);
/*********** 遍历指定包下的所有文件 找出.class结尾的文件 将其信息封装到beanDefinitionMap ***********/
// 获取指定包所在的包文件夹
File file = new File(resource.getFile());
// 遍历文件夹下所有的文件
if(file.isDirectory()) {
File[] files = file.listFiles();
for(File subFile : files) {
// 获取此subFile的全路径
String absolutePath = subFile.getAbsolutePath();
// 只处理.class文件
if(absolutePath.endsWith(".class")) {
// 获取类名 注意这里的类并不一定需要注入 还需要判断它是否被@Component、@Service、@Mapper等注解修饰
String className = absolutePath.substring(absolutePath.lastIndexOf("\\") + 1, absolutePath.indexOf(".class"));
// 获取全类名
String classFullName = packageName + "." + className;
try{
// 获取classFullName对应的class对象
Class<?> clazz = classLoader.loadClass(classFullName);
// 判断该class对象是否被@Component、@Service、@Mapper等注解修饰
// 我在模拟实现的过程中只定义了一个标识Bean的注解 即@Component
if(clazz.isAnnotationPresent(Component.class)) {
System.out.println("C IoC M beanDefinitionsByScan() -> " + classFullName + "是一个Spring bean, 将其注入IoC容器进行管理");
/*********** 将其信息封装到beanDefinitionMap ***********/
// 获取该class对象的@Component
Component componentAnnotation = clazz.getDeclaredAnnotation(Component.class);
// 获取该bean的名字
String beanName = componentAnnotation.name();
if ("".equals(beanName)) { // 如果没有配置名字
// 则将该类的类名首字母小写作为beanName
beanName = StringUtils.uncapitalize(className);
}
// 将Bean的信息封装到BeanDefinition对象
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setClazz(clazz);
// 获取@Scope并设置beanDefinition对象的scope信息
if(clazz.isAnnotationPresent(Scope.class)) {
Scope scope = clazz.getDeclaredAnnotation(Scope.class);
beanDefinition.setScope(scope.value());
} else {
beanDefinition.setScope("singleton");
}
// 将beanDefinition放入beanDefinitionMap
beanDefinitionMap.put(beanName, beanDefinition);
} else {
System.out.println("C IoC M beanDefinitionsByScan() -> " + classFullName + "不是一个Spring bean 不需要注入IoC容器进行管理");
}
}catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
📑实现initialSingletonObjects
函数
/**
* initialSingletonObjects方法beanDefinitionMap单例池的初始化
*/
public void initialSingletonObjects(){
// 获取所有beans的定义信息keys
Enumeration<String> keys = beanDefinitionMap.keys();
while (keys.hasMoreElements()) {
// 得到beanName
String beanName = keys.nextElement();
// 通过beanName 得到对应的beanDefinition对象
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
// 判断该bean是singleton还是prototype 只初始化创建单例对象放入singletonObjects单例池
if ("singleton".equalsIgnoreCase(beanDefinition.getScope())) {
//将该bean实例放入到singletonObjects 集合
Object bean = createBean(beanDefinition);
singletonObjects.put(beanName, bean);
}
}
}
📑实现createBean
函数
说明 getBean
与createBean
函数会项目引用 所以先实现哪个都行
/**
* createBean方法完成Bean对象的创建
*
* @param beanName 传入Bean的名字(目前用不到这个看参数 这个参数会在实现AOP的时候用到)
* @param beanDefinition 传入Bean的定义信息
* @return 返回创建的Bean对象
*/
private Object createBean(BeanDefinition beanDefinition) {
// 获取要创建的bean的Class对象 使用反射创建该对象
Class clazz = beanDefinition.getClazz();
try {
// 获取构造方法并创建对象
Constructor constructor = clazz.getDeclaredConstructor();
Object bean = constructor.newInstance();
// 接下来创建其属性
for(Field field : clazz.getDeclaredFields()) {
// 只有这个属性被@Autowired注解修饰 才需要被注入
if(field.isAnnotationPresent(Autowired.class)){
/* 按照名字进行组装 */
String name = field.getName();
// 通过getBean方法来获取要组装对象
Object fieldBean = getBean(name);
// 进行组装
field.setAccessible(true); // 因为属性都是修饰的所以需要设置可访问
field.set(bean, fieldBean);
}
}
// 所有的属性注入完成后返回当前的Bean对象
return bean;
}catch (Exception e) {
System.out.println("不存在该构造方法");
}
// 创建失败则返回null
return null;
}
📑实现getBean
函数
/**
* getBean 根据传入的Bean的name返回singletonObjects中的bean对象
*
* 这个方法会在给Bean注入属性对象的时候用到 注入属性对象的时候
* 先检查singletonObjects中是否已经存在要注入的Bean 如果已经存在则直接取出用
* 如果还未创建 则创建一个属性对象的Bean放入singletonObjects并将其引用赋值给要注入的属性
*
* @param name
* @return 返回singletonObjects中的bean对象
*/
public Object getBean(String name) {
// 先判断该name是否存在于beanDefinitionMap的KEYS当中
if(beanDefinitionMap.containsKey(name)) {
// 存在则获取该bean的定义信息
BeanDefinition beanDefinition = beanDefinitionMap.get(name);
// 然后判断是该bean的Scope是单例还是多例
if("singleton".equals(beanDefinition.getScope())) {
// 如果是单例 则直接从单例池获取
Object o = singletonObjects.get(name);
// 如果获取不到 则先创建
if(o == null) {
o = createBean(beanDefinition);
// 创建后放入单例池并返回
singletonObjects.put(name, o);
return o;
}
// 不为空则直接返回
return o;
} else {
// 如果是多例 则每次都使用createBean返回一个新的Bean返回
return createBean(beanDefinition);
}
} else {
// 不存在则抛出空指针异常
throw new NullPointerException("该Bean不存在");
}
}
📑最终的包结构
🎄测试运行
📑编写Main
函数进行测试
package bbm.com;
import bbm.com.component.Duck;
import bbm.com.component.DuckController;
import bbm.com.ioc.BigBigMengAnnotationApplicationContext;
import bbm.com.ioc.BigBigMengConfig;
/**
@author Liu Xianmeng
@createTime 2023/9/15 10:54
@instruction
*/
@SuppressWarnings({"all"})
public class Main {
public static void main(String[] args) {
// 获取IoC容器
BigBigMengAnnotationApplicationContext ioc = new BigBigMengAnnotationApplicationContext(BigBigMengConfig.class);
// 从IoC容器中获取DuckController对象
DuckController duckController = (DuckController) ioc.getBean("duckController");
// 调用duckControllerd的getOneByName方法
Duck littleBlack = duckController.getOneByName("littleBlack");
// 打印获取到的Duck对象
System.out.println(littleBlack);
}
}
🌈运行结果
🎄总结
到此为止 一个基于注解的简单的IoC容器的模拟实现就完成啦 非常nice✨