手写Spring框架——IOC实现(一)

为什么要手写Spring?

Spring是入门JavaWeb的第一个框架,是沿用至今非常久的框架之一,改变了大家刀耕火种时代的编程方式,并且围绕着Web技术的演进在不断进化与整合,已经变得非常成熟。以至于现在我这样的菜鸟后端只会按照SpringBoot的那一套约定对业务进行CRUD,不能提升自己的编程能力与设计能力。

为了在学习设计模式时有一些具体的有难度的例子进行思考与体会;为了进一步深刻体会Spring的设计理念与功能实现;为了锻炼自己主动思考的能力;我决定体验手写Spring框架,并将学习过程的感悟与问题记录下来,汇总成文章。

ioc第一版——极简实现

实现效果

测试准备xml文件resource/beans.xml

<?xml version="1.0" encoding="UTF-8" ?>
<beans>
    <bean id = "demoService" class = "com.cee.bean.impl.DemoServiceImpl"></bean>
</beans>

测试准备Java类com.cee.bean.DemoService,com.cee.bean.impl.DemoServiceImpl

// 接口
public interface DemoService {
    void sayHello();
}

// 实现类,Bean
public class DemoServiceImpl implements DemoService {
    @Override
    public void sayHello() {
        System.out.println("Hello mini Spring!");
    }
}

测试类与实现结果:

// xml实现
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext();
        DemoService demoService = (DemoService) context.getBean("demoService");
        demoService.sayHello(); // Hello mini Spring!
        // 注解实现
        AnnotationApplicationContext context2 = new AnnotationApplicationContext("com.cee");
        DemoService demoService2 = (DemoService) context2.getBean("demoServiceImpl");
        demoService2.sayHello(); // Hello mini Spring!

1. BeanDefinition定义

在这个类里面定义两个最简单的域:id 与 className。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class BeanDefinition {
    private String id;
    private String className;
}

2. 基于xml的实现ClassPathXmlApplicationContext

以面向过程的思想梳理极简版ioc流程,如图所示:
ioc-esay

  1. 解析Bean的类信息
  2. 封装BeanDefinition并保存
  3. 读取BeanDefinition并注册单例
  4. getBean(String beanName)方法获取单例Bean实例

实现:

/**
 * 1-3 在创建ClassPathXmlApplicationContext类时完成
 * 1. 解析xml
 * 2. 注册BeanDefinition
 * 3. 生成singleton对象并保存到Map
 * 4. 对外开放获取对象的接口
 */
public class ClassPathXmlApplicationContext {
    private List<BeanDefinition> beanDefinitions = new ArrayList<>();
    private Map<String, Object> singletons = new HashMap<>();
    //构造器获取外部配置,解析出Bean的定义,形成内存映像
    public ClassPathXmlApplicationContext(String fileName) {
        this.readXml(fileName);
        this.instanceBeans();
    }
    private void readXml(String fileName) {
        SAXReader saxReader = new SAXReader();
        try {
            URL xmlPath =
this.getClass().getClassLoader().getResource(fileName);
            Document document = saxReader.read(xmlPath);
            Element rootElement = document.getRootElement();
            //对配置文件中的每一个<bean>,进行处理
            for (Element element : (List<Element>) rootElement.elements()) {
                //获取Bean的基本信息
                String beanID = element.attributeValue("id");
                String beanClassName = element.attributeValue("class");
                BeanDefinition beanDefinition = new BeanDefinition(beanID,
beanClassName);
                //将Bean的定义存放到beanDefinitions 
                beanDefinitions.add(beanDefinition);
            }
        } 
    }
    //利用反射创建Bean实例,并存储在singletons中
    private void instanceBeans() {
        for (BeanDefinition beanDefinition : beanDefinitions) {
            try {
                singletons.put(beanDefinition.getId(),
Class.forName(beanDefinition.getClassName()).newInstance());
            } 
        } 
    }
    //这是对外的一个方法,让外部程序从容器中获取Bean实例,会逐步演化成核心方法
    public Object getBean(String beanName) {
        return singletons.get(beanName);
    }
}

3.基于注解的实现AnnotationApplicationContext

首先抽象出公共逻辑

@Slf4j
public class BaseApplicationContext {
    protected final List<BeanDefinition> beanDefinitions = new ArrayList<>();

    protected final Map<String, Object> singletonMap = new HashMap<>();


    protected void instanceBeans() {
        for (BeanDefinition bd : beanDefinitions) {
            try {
                Object singletonBean = Class.forName(bd.getClassName()).getDeclaredConstructor().newInstance();
                singletonMap.put(bd.getId(), singletonBean);
            }catch (ClassNotFoundException | InvocationTargetException | InstantiationException |
                    IllegalAccessException | NoSuchMethodException e) {
                log.error("创建singleton对象出错:", e);
            }
        }
    }

    public Object getBean(String beanName) {
        return singletonMap.get(beanName);
    }
}

其次实现AnnotationApplicationContext

@Slf4j
public class AnnotationApplicationContext extends BaseApplicationContext {

    public AnnotationApplicationContext(String packageName) {
        List<Class<?>> classes = scanComponentClasses(packageName);
        resolveBeanDefinition(classes);
        super.instanceBeans();
    }

    /**
     * 扫描某一个包下带注解的类
     * @param packageName 包名
     * @return 类
     */
    public List<Class<?>> scanComponentClasses(String packageName) {
        List<Class<?>> classes = new ArrayList<>();
        try {
            List<File> files = getFiles(packageName);
            for (File file : files) {
                String className = getClassName(file, packageName);
                Class<?> clazz = Class.forName(className);
                if (clazz.isAnnotationPresent(Component.class)) {
                    classes.add(clazz);
                }
            }
        } catch (Exception e) {
            log.error("扫描注解过程发生错误:", e);
        }
        return classes;
    }

    /**
     * 递归调用API获取待扫描包下的类文件
     * @param currentPackage 当前扫描的包
     * @return 包下的所有文件
     * @throws Exception 资源未找到
     */
    private List<File> getFiles(String currentPackage) throws Exception {
        List<File> fileList = new ArrayList<>();
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        String path = currentPackage.replace('.', '/');
        URL resource = classLoader.getResource(path);
        if (resource == null) {
            throw new Exception("资源 " + path + " 未找到!");
        }
        File directory = new File(resource.toURI());
        File[] files = directory.listFiles();
        if (files != null) {
            for (File file : files) {
                if (file.isDirectory()) {
                    fileList.addAll(getFiles(currentPackage + "." + file.getName()));
                } else if (file.getName().endsWith(".class")) {
                    fileList.add(file);
                }
            }
        }
        return fileList;
    }

    /**
     * 获取类名
     */
    private String getClassName(File file, String packageName) {
        String filePath = file.getAbsolutePath();
        filePath = filePath.replace(File.separatorChar, '.');
        int pkgIndex = filePath.indexOf(packageName);
        String className = filePath.substring(pkgIndex, filePath.length() - 6); // Remove ".class"
        return className;
    }

    /**
     * 注册BeanDefinition
     * @param classes 带@Component的类
     */
    private void resolveBeanDefinition(List<Class<?>> classes) {
        for (Class<?> aClass : classes) {
            String beanClassName = aClass.getName();
            String beanId = getSimpleClassName(beanClassName);
            BeanDefinition beanDefinition = new BeanDefinition(beanId, beanClassName);
            beanDefinitions.add(beanDefinition);
        }
    }

    private static String getSimpleClassName(String fullClassName) {
        String[] parts = fullClassName.split("\\.");
        return convertFirstCharacterToLowerCase(parts[parts.length - 1]);
    }

    private static String convertFirstCharacterToLowerCase(String str) {
        if (str == null || str.isEmpty()) {
            return str;
        }
        return str.substring(0, 1).toLowerCase() + str.substring(1);
    }

}

总结

本文主要实现了一个极简版ioc,搭建了ioc的极简流程,并以面向过程的方式将流程实现。下一章将对基于xml的实现进行简单解耦,并给出一些问题分析。

参考链接

列出主要参考链接。

  1. 极客时间专栏《手把手带你写一个MiniSpring》
  2. 参考源码:注意切换分支
  3. 极客时间专栏《设计模式之美》
  4. chatgpt4.0 turbo
  5. Spring创始人Rod Johnson Talks
  6. Rod Johnson介绍
  • 26
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值