• SpringMvc手写简单实现篇 - IOC容器、DI依赖注入篇


    写Java的人Spring肯定用过了吧,但是有多少人知道原理呢? 由于版本的迭代,spring体系的越来越完整,代码分析起来也就越复杂,我们先从简单手写的案例来驱动分析源码吧。

    spring手写简单版IOC容器和DI依赖注入

    通过这些代码了解一个大概,然后再去实践分析源码!

    1.预先准备

    新建一个空的maven项目,代码结构如下

    • action 和 service,接口就不贴了
    @TController
    public class HelloAction {
    
        @TAutowired
        HelloService helloService;
    
        public String hello(String name) {
            return helloService.sayHello(name);
        }
    }
    
    @TService
    public class HelloServiceImpl implements HelloService {
        
        public String sayHello(String name) {
            return "my name is " + name;
        }
    }
    
    复制代码
    annotation
    application.properties
    

    #需要扫描的包路径 packscanner=com.xxx.demo

    实现内容都在TestApplicationContext,也就是上下文中

    2.TestApplicationContext上下文

    2.1 上下文 成员变量和init()

    public class TestApplicationContext {
    
        //保存配置
        public Properties contextConfig = new Properties();
        //保存需要加载的clasName
        public List classNames = new ArrayList();
        //ioc容器
        public ConcurrentHashMap ioc = new ConcurrentHashMap();
     //构造方法 main方法启动时调用
        public TestApplicationContext(String... classpath) {
            init(classpath);
        }
        public void init(String... classpath) {
            //初始化配置 这里就一个
            doLoadConfig(classpath[0]);
            //扫描对应的类
            doScanner(contextConfig.getProperty("packscanner"));
            //实例化对象 并加到IOC容器 加了注解的类
            doInstance();
            //依赖注入
            doAutowired();
            System.out.println("application is init");
        }
    }
    复制代码

    2.2 初始化配置doLoadConfig

    private void doLoadConfig(String config) {
     //读流转配置
        InputStream inputStream = this.getClass().getClassLoader()
                .getResourceAsStream(config.replace("classpath:", ""));
        try {
            contextConfig.load(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    复制代码

    2.3 扫描对应的class类 doScanner

    //保存className
    private void doScanner(String packscanner) { //com.xxx.demo
     //替换成目录形式
        URL url = this.getClass().getResource("/" + packscanner.replaceAll("\\.", "/"));
        File file = new File(url.getPath());
        for (File f : file.listFiles()) {
            if (f.isDirectory()) {
             //com.xxx.demo.action
                doScanner(packscanner + "." + f.getName());
            } else {
                if (!f.getName().endsWith(".class")) continue;
                //com.xxx.demo.action.HelloAction
                this.classNames.add(packscanner + "." + f.getName().replaceAll("\\.class", ""));
            }
        }
    }
    复制代码

    2.4 对加了注解的类初始化,并添加到IOC容器 doInstance

    private void doInstance() {
        for (String className : this.classNames) {
            try {
                Class clazz = Class.forName(className);
    
                //只有加了注解的类才初始化
                if (!clazz.isAnnotationPresent(TController.class) && !clazz.isAnnotationPresent(TService.class)) {
                    continue;
                }
                //实例化对象 源码中并不是这个流程 这里方便理解 忽略细节 
                Object instance = clazz.newInstance();
    
                //保存到IOC容器 beanName -> instance 生成bean name 首字母小写
                String beanName = toFristLowerCase(clazz.getSimpleName());
                //如果是service 看是否有自定义名称
                if(clazz.isAnnotationPresent(TService.class)){
                    TService service = clazz.getAnnotation(TService.class);
                    if(!"".equals(service.value())){
                        beanName = service.value();
                    }
                }
    
                //比如beanName 相同的暂不考虑 主要是体现思想
                this.ioc.put(beanName,instance);
    
                //如果是接口实现的 需要把接口全路径对应到service
                Class[] interfaces = clazz.getInterfaces();
                for (Class i : interfaces) {
                    this.ioc.put(i.getName(),instance);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    //首字母小写
    private String toFristLowerCase(String simpleName) {
        char[] chars = simpleName.toCharArray();
        chars[0] += 32;
        return new String(chars);
    }
    复制代码

    2.5 @Autowired依赖注入 doAutowired

    //简化版的实现 先了解这个思路
    private void doAutowired() {
     //ioc里面的都是已经初始化的类
        for (Map.Entry entry : this.ioc.entrySet()) {
            //判断声明的方法里是否有 @TestAutowired注解的
            Object instance = entry.getValue();
            Class clazz = instance.getClass();
            //所有声明的方法
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                if(!field.isAnnotationPresent(TAutowired.class)) continue;
                //默认beanName是全路径
                String name = field.getType().getName();
                //通过注解value上的value来确定beanName 等同于@Qualifier(value="xxx") 这里就简化了
                TAutowired annotation = field.getAnnotation(TAutowired.class);
                if(!"".equals(annotation.value())){
                    name = annotation.value();
                }
                
                Object autowiredService = this.ioc.get(name);
                if(autowiredService == null) continue;
       //授权 反射赋值
                field.setAccessible(true);
                try {
                    field.set(instance,autowiredService);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    复制代码

    3.Test测试

    public static void main(String[] args) {
        TestApplicationContext context = new TestApplicationContext("classpath:application.properties");
        HelloAction action = (HelloAction) context.getBean(HelloAction.class);
        String hello = action.hello("张三");
        System.out.println(hello);
    }
    
    //输出结果
    application is init
    my name is 张三    
    复制代码

    理解这个思想以后, 我们是不是可以猜想一下AOP是怎么实现的了 ?

     

  • 相关阅读:
    2基于matlab的卡尔曼滤波,可更改 状态转移方程, 控制输入,观测方程,设置生成的信号的噪声标准差,设置状态转移方差Q和观测方差R等参数,程序已调通。
    NFT Insider #74:AnimocaBrands 完成 1.1 亿美元融资,YGGSEA社区举办中秋节NFT赠送活动
    【毕业季】Three.js动态相册
    MySQL初识
    ssh secure shell Client连接问题
    字节也开始缩招了...
    使用BWGS进行基因型数据预测
    C++(第一篇):C++基础入门知识
    书生·浦语大模型开源体系(六)作业
    mysql虚拟列问题
  • 原文地址:https://blog.csdn.net/m0_71777195/article/details/126567714