手写模拟Spring框架核心逻辑

11 篇文章 1 订阅
6 篇文章 1 订阅

了解Spring框架工作大概流程

模拟Spring框架核心代码并不是实现真正的Spring核心源码,而是为了后续看源码进行的一个铺垫,同时我也相信在以后的某个时间段的面试中会跟面试官扯到这个犊子 ,主要还是通过手写一遍了解Spring工作的大概流程,对于Spring不单单停止在应用上,更应该往深里学,废话到这,下面正式开始。。。
我们现在用的框架大部分都是SpringCloud或者SpringBoot去启动的,但是他们的启动都是会间接使用到ClassPathXmlApplicationContext去扫描xml配置,所以我们得了解Spring的启动类,如下图:
在这里插入图片描述
或者说使用注解的方式,AnnotationConfigApplicationContext(这个启动类会自动帮我们创建对象,使用了他,就不用我们自己去new一个对象了),可以直接理解为他就是一个容器,如下图:
在这里插入图片描述
⚠️:这里再普及一点小知识,所有的Bean都是对象,但是对象不一定都是Bean,第二个就是为什么使用Spring,原因就是回到JDBC + Servlet版本的时候,项目中大量的创建、使用对象,造成依赖性特别强,不好维护,Spring就相当于项目的管理者,可以帮我们创建对象、给属性去赋值等等这些吃力不讨好的苦力活…

创建AnnotationConfigApplicationContext启动类

这里我们首先创建一个项目,不需要引入任何依赖
在这里插入图片描述
定义Component注入注解,然后创建AnnotationConfigApplicationContext启动类,


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
    String value() default "";
}

注入在我们的实体类上,但是现在是没有功能的
在这里插入图片描述

创建AnnotationConfigApplicationContext实体类,并提供一个getBean(String beanName)方法,然后在测试类里面去获取我们的bean

在这里插入图片描述

在这里插入图片描述
写到这里,空壳子就出来了,首先,我们知道AnnotationConfigApplicationContext是启动类,会帮我们去创建bean,所以我们要写一个createBean()方法,但是在创建之前要先扫描,所以我们这里要定义一个扫描的注解,去扫描需要创建的对象

  • @ComponentScan
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ComponentScan {
    String value() default "";
}
  • AppConfig,这里需要注意一个点,并非去扫描所有在com.csw.spring.service.impl下面的类,而是扫描加了@component注解的类,这里还有一个注意点就是AnnotationConfigApplicationContext并非会创建所有加了@component,而是会去加载非懒加载的单例bean
@ComponentScan("com.csw.spring.service.impl")
public class AppConfig {
}

如下图:
在这里插入图片描述

⚠️课堂小知识:什么是单例bean?什么是原型bean?

    • 单例bean又分为懒加载和非懒加载,简单来说懒加载就是每次启动了之后不会马上去创建,而是会在调用了getBean()方法之后才会去创建非懒加载就是启动了之后去创建创建单例bean最直接的方式就是在类上加@Service或者@Component
    • prototypeScope,即原型bean,每次请求时都会创建新的bean实例直接使用。创建原型Bean,就是在@Service或者@Component的基础上加个@Scope(“prototype”)
      在这里插入图片描述

在做完了以上的步骤,AnnotationConfigApplicationContext启动会去判断是不是加了@component注解和是单例bean还是原型bean,非懒加载单例bean还是懒加载单例bean,这时候我们就需要一个@Lazy的注解去区分

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Lazy {
}

AnnotationConfigApplicationContext扫描的步骤

package com.csw.spring.context;

import com.csw.spring.anno.ComponentScan;

import java.lang.annotation.Annotation;

/**
 * @author  csw
 * @date  2021/12/19 01:12:45
 * @version 1.0
 */
public class CswApplicationContext {

    private Class configClass;

    public CswApplicationContext(Class configClass) {
        this.configClass = configClass;

        //判断扫描是不是存在
        if (configClass.isAnnotationPresent(ComponentScan.class)) {
            //获取ComponentScan的信息
            ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
            String path = componentScanAnnotation.value();//获取扫描路径
            System.out.println(path);
        }
    }

    public Object getBean(String beanName) {
        return null;
    }
}

启动测试类后得到,如下图:
在这里插入图片描述
但是我们这里真正需要获取的路径是编译后的class文件,如下图:
在这里插入图片描述
课堂小知识⚠️:类加载是在本地文件夹的绝对路径上加载的
在这里插入图片描述
根据以上我们可以通过getClassLoader方法获取类加载后的文件路径

package com.csw.spring.context;

import com.csw.spring.anno.Component;
import com.csw.spring.anno.ComponentScan;
import com.csw.spring.anno.Lazy;
import com.csw.spring.anno.Scope;

import java.io.File;
import java.lang.annotation.Annotation;
import java.net.URL;

/**
 * @author  csw
 * @date  2021/12/19 01:12:45
 * @version 1.0
 */
public class CswApplicationContext {

    private Class configClass;

    public CswApplicationContext(Class configClass) {
        this.configClass = configClass;

        //判断扫描是不是存在
        if (configClass.isAnnotationPresent(ComponentScan.class)) {
            //获取ComponentScan的信息
            ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
            String path = componentScanAnnotation.value();//获取扫描路径,com.csw.spring.service.impl
            //替换com.csw.spring.service.impl为com/csw/spring/service/impl
            path = path.replace(".","/");

            //获取类加载后文件路径
            ClassLoader classLoader = CswApplicationContext.class.getClassLoader();
            URL resource = classLoader.getResource(path);
            File file = new File(resource.getFile());

            //对获取对文件路径进行循环遍历
            for (File f : file.listFiles()) {

                //获取path路径:/Users/shengwencheng/Desktop/cloud/SpringCore/spring-core/Spring/target/classes/com/csw/spring/service/impl/UserServiceImpl.class
                String s = f.getAbsolutePath();
                //只要class结尾的文件
                if(s.endsWith(".class")) {
                    //截取com到.class之间到路径
                    s = s.substring(s.indexOf("com"),s.indexOf(".class"));
                    //把转义替换成.
                    s = s.replace("\\",".");
                    try {
                        Class  clazz = classLoader.loadClass(s);
                        //判断类上是否有Component注解
                        if (clazz.isAnnotationPresent(Component.class)) {
                            Component componentAnnotation = (Component) clazz.getAnnotation(Component.class);
                            String beanName = componentAnnotation.value();

                            //判断是否是非懒加载的bean
                            if (clazz.isAnnotationPresent(Lazy.class)) {

                            }

                            //判断是否是原型bean
                            if (clazz.isAnnotationPresent(Scope.class)) {
                                Scope scopeAnnotation = (Scope) clazz.getAnnotation(Scope.class);
                                String value = scopeAnnotation.value();
                            } else {
                                //单例
                            }
                        }
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        //创建非懒加载的单例bean
    }
    public Object getBean(String beanName) {
        return null;
    }
}

通过以上代码我们拿到了具体是哪个类需要去解析,但是我们不可能通过getBean获取到bean,然后每次都要去解析,再判断他是单例的还是原型,这样的话性能太低了,于是就有了BeanDefinition这个概念。。。

创建BeanDefinition类

BeanDefinition,简单来说就是bean的定义,相当与把类解析后的结果缓存起来,用于声明bean的作用域

  • BeanDefinition
package com.csw.spring.framework;

/**
 * @author csw
 * @version 1.0
 * @date 2021/12/19 7:58 下午
 *
 * bean的定义,声明作用域
 */

public class BeanDefinition {

    private String scope; //原型的
    private boolean isLazy; //懒加载
    private Class beanClass; //bean的类型


    public String getScope() {
        return scope;
    }

    public void setScope(String scope) {
        this.scope = scope;
    }

    public boolean isLazy() {
        return isLazy;
    }

    public void setLazy(boolean lazy) {
        isLazy = lazy;
    }

    public Class getBeanClass() {
        return beanClass;
    }

    public void setBeanClass(Class beanClass) {
        this.beanClass = beanClass;
    }
}

在这里插入图片描述
课堂小知识⚠️:BeanDefinition与Bean对象的关系,先有Bean的定义才有Bean对象
在这里插入图片描述
这里继续完善扫描 - getBean和createBean方法

package com.csw.spring.context;

import com.csw.spring.anno.Component;
import com.csw.spring.anno.ComponentScan;
import com.csw.spring.anno.Lazy;
import com.csw.spring.anno.Scope;
import com.csw.spring.framework.BeanDefinition;
import com.sun.tools.javac.util.StringUtils;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

/**
 * @author  csw
 * @date  2021/12/19 01:12:45
 * @version 1.0
 */
public class CswApplicationContext {

    private Class configClass;

    private Map<String,BeanDefinition> beanDefinitionMap = new HashMap<String, BeanDefinition>(); //存放解析后的bean缓存池
    private Map<String,Object> singletonObj = new HashMap<String, Object>(); //单例池

    public CswApplicationContext(Class configClass) {
        this.configClass = configClass;

        //扫描 --得到BeanDefinition对象
        scan(configClass, beanDefinitionMap);

        //创建非懒加载的单例bean
        createNonLazySingleton(beanDefinitionMap);
    }

    private  void createNonLazySingleton(Map<String, BeanDefinition> beanDefinitionMap) {
        for (String beanName : beanDefinitionMap.keySet()) {
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            if(beanDefinition.getScope().equals("singleton") && !beanDefinition.isLazy()) {
                //创建bean
                Object valueBean = createBean(beanDefinition);
                singletonObj.put(beanName,valueBean);
            }
        }
    }

    private Object createBean(BeanDefinition beanDefinition) {
        //通过反射获取到bean对象
        Class beanClass = beanDefinition.getBeanClass();
        //getDeclaredConstructor:获取到无参到构造方法
        try {
            Object newInstance = beanClass.getDeclaredConstructor().newInstance();
            return newInstance;
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return null;
    }

    private static void scan(Class configClass, Map<String, BeanDefinition> beanDefinitionMap) {
        if (configClass.isAnnotationPresent(ComponentScan.class)) {
            //获取ComponentScan的信息
            ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
            String path = componentScanAnnotation.value();//获取扫描路径,com.csw.spring.service.impl
            //替换com.csw.spring.service.impl为com/csw/spring/service/impl
            path = path.replace(".","/");

            //获取类加载后文件路径
            ClassLoader classLoader = CswApplicationContext.class.getClassLoader();
            URL resource = classLoader.getResource(path);
            System.out.println("根据类加载获取到完整路径:" + resource);
            File file = new File(resource.getFile());

            //对获取对文件路径进行循环遍历
            for (File f : file.listFiles()) {

                //获取path路径:/Users/shengwencheng/Desktop/cloud/SpringCore/spring-core/Spring/target/classes/com/csw/spring/service/impl/UserServiceImpl.class
                String s = f.getAbsolutePath();
                //只要class结尾的文件
                if(s.endsWith(".class")) {
                    //截取com到.class之间到路径
                    s = s.substring(s.indexOf("com"),s.indexOf(".class"));
                    //把转义替换成.
                    s = s.replace("/",".");
                    System.out.println("通过字符串截取和转义替换或取到的类:" + s);
                    try {
                        Class  clazz = classLoader.loadClass(s);
                        //判断类上是否有Component注解
                        if (clazz.isAnnotationPresent(Component.class)) {

                            BeanDefinition beanDefinition = new BeanDefinition();
                            //声明bean类型
                            beanDefinition.setBeanClass(clazz);

                            Component componentAnnotation = (Component) clazz.getAnnotation(Component.class);
                            System.out.println("获取到加了Component注解到类:" + componentAnnotation);
                            String beanName = componentAnnotation.value();
                            System.out.println("获取Component注解里面的value:" + beanName);
                            //判断是否是非懒加载的bean
                            if (clazz.isAnnotationPresent(Lazy.class)) {
                                beanDefinition.setLazy(true);
                            }
                            //判断是否是原型bean
                            if (clazz.isAnnotationPresent(Scope.class)) {
                                Scope scopeAnnotation = (Scope) clazz.getAnnotation(Scope.class);
                                String value = scopeAnnotation.value();
                                beanDefinition.setScope(value);
                            } else {
                                //单例
                                beanDefinition.setScope("singleton");
                            }
                            //这里包括了项目中所有bean的定义,根据beanName在缓存里面去查找有没有传入的这个bean对象
                            beanDefinitionMap.put(beanName,beanDefinition);
                        }
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    public Object getBean(String beanName) {
        if(!beanDefinitionMap.containsKey(beanName)) {
            throw new NullPointerException();
        } else {
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            System.out.println("存放解析后的bean缓存池:" + beanDefinition.getScope());
            //判断是原型bean还是单例bean
            if(beanDefinition.getScope().equals("prototype")) {
                //创建一个bean
                Object bean = createBean(beanDefinition);
                return bean;
            } else if(beanDefinition.getScope().equals("singleton")) {
                //单例池里面获取单例bean
                Object o = singletonObj.get(beanName);
                return o;
            }
        }
        return null;
    }
}

测试结果:
在这里插入图片描述
到这里,spring基本核心逻辑已经完成了,但是业务层还缺少了通过注解注入方式。。。
在这里插入图片描述

实现@Autowired的依赖注入

@Autowired,默认先根据byType查询bean的依赖类型是否存在,存在则再根据byName进行注入

package com.csw.spring.anno;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.CONSTRUCTOR,ElementType.METHOD,ElementType.PARAMETER,ElementType.ANNOTATION_TYPE})
@Documented
public @interface Autowired {
    boolean required() default true;
}

实现:
在这里插入图片描述
课堂小知识⚠️:单例模式和单例bean的区别
单例模式:只能有一个单例
单例bean:通过@Bean定义的bean对象,bean名不同即可

UserServiceImpl则如下内容,因为容器只实现了byName查找方式,没有具体写byType查找方式,所以直接根据bean查找

package com.csw.spring.service.impl;

import com.csw.spring.anno.Autowired;
import com.csw.spring.anno.Component;
import com.csw.spring.anno.Scope;
import com.csw.spring.service.OrderService;
import com.csw.spring.service.UserService;

@Component("userServiceImpl")
@Scope("prototype")
public class UserServiceImpl implements UserService {

    @Autowired
    private OrderServiceImpl orderServiceImpl;

    public String test() {
        return orderServiceImpl.getOrder();
    }
}

运行结果:
在这里插入图片描述
到这里,我们Spring的基本运行逻辑就模拟成功了,但是如果是如下内容,我们是无法通过@authwired或者@resource去实现,这里就涉及到Spring的扩展点了,BeanNameAware

@Autowired
private String beanName;

实现BeanNameAware接口

BeanNameAware是Spring的扩展接口,主要是实现setBeanName()方法,内容很简单,如果有实现了BeanNameAware接口则回调

  • 模拟定义BeanNameAware接口
package com.csw.spring.framework;

/**
 * @author csw
 * @version 1.0
 * @date 2021/12/19 11:20 下午
 */

public interface BeanNameAware {

    void setBeanName(String name);
}

实现BeanNameAware接口

package com.csw.spring.service.impl;

import com.csw.spring.anno.Autowired;
import com.csw.spring.anno.Component;
import com.csw.spring.anno.Scope;
import com.csw.spring.framework.BeanNameAware;
import com.csw.spring.service.OrderService;
import com.csw.spring.service.UserService;

/**
 * @author csw
 * @version 1.0
 * @date 2021/12/19 11:20 下午
 */
@Component("userServiceImpl")
@Scope("prototype")
public class UserServiceImpl implements UserService, BeanNameAware {

    @Autowired
    private OrderServiceImpl orderServiceImpl;

    private String beanName;

    public String test() {
        System.out.println("beanName:" + beanName);
        return orderServiceImpl.getOrder();
    }

    public void setBeanName(String name) {
        this.beanName = name;
    }
}

模拟BeanNameAware的逻辑
在这里插入图片描述
运行结果:
在这里插入图片描述
这里再补充一个InitializingBean初始化的接口,主要是实现afterPropertiesSet方法,用于对已经注入了的属性进行认证的扩展接口,包括后面整合Spring + mybatis源码篇会说到,这里做一个铺垫

  • 定义InitializingBean接口
package com.csw.spring.framework;

/**
 * @author csw
 * @version 1.0
 * @date 2021/12/19 11:45 下午
 */

public interface InitializingBean {

    public void afterPropertiesSet();
}

  • 实现InitializingBean并重写afterPropertiesSet方法
package com.csw.spring.service.impl;

import com.csw.spring.anno.Autowired;
import com.csw.spring.anno.Component;
import com.csw.spring.anno.Scope;
import com.csw.spring.framework.BeanNameAware;
import com.csw.spring.framework.InitializingBean;
import com.csw.spring.service.OrderService;
import com.csw.spring.service.UserService;

@Component("userServiceImpl")
@Scope("prototype")
public class UserServiceImpl implements UserService, BeanNameAware, InitializingBean {

    @Autowired
    private OrderServiceImpl orderServiceImpl;

    private String beanName;

    public String test() {
        System.out.println("beanName:" + beanName);
        return orderServiceImpl.getOrder();
    }

    public void setBeanName(String name) {
        this.beanName = name;
    }

    public void afterPropertiesSet() {
        System.out.println("这里主要是对已经注入了的属性进行认证,内容随便写,扩展接口");
    }
}

具体实现逻辑:
在这里插入图片描述
运行结果:
在这里插入图片描述
这里再补充一个比较重要的点,BeanPostProcessor,Bean的后置处理器 - > 处理实例化后的bean对象,场景使用:

  • @Autowired是由AutowiredAnnotationBeanPostProcessor类实现的。
  • @Resource是由CommonAnnotationBeanPostProcessor实现的。
    包括我们可以自定义一个注解,实现依赖注入,这说明了Spring很灵活性

以上就是Spring核心逻辑的一个模拟,为后续学习Spring源码做一个铺垫,以上就是个人对Spring的一个理解 + 总结,并非Spring真正的写法,喜欢的朋友点个➕关注
代码地址:手写模拟Spring框架核心逻辑代码地址

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: Spring Framework是一个开源的Java平台,它提供了一组全面的工具来支持Java应用程序开发。它主要包括IoC容器、AOP框架、事务管理、MVC框架、DAO框架、连接池等。它可以帮助程序员更好地组织代码,减少重复性工作,提高代码质量。Spring框架需要对Java编程和设计模式有较深的了解,并对Spring框架的源码有着深入的研究。 ### 回答2: Spring框架是一个开源的Java平台,用于构建企业级应用程序。它提供了一种全面的编程和配置模型,用于开发基于Java的应用程序和服务。Spring框架意味着从头开始实现Spring核心功能。 Spring框架的基本步骤包括: 1. 创建一个核心容器类,用于管理应用程序中的Bean对象。这个容器类需要提供注册、获取和销毁Bean的功能。 2. 定义一个Bean注解,用于标识哪些类应该被容器所管理。 3. 创建一个Bean定义类,用于存储每个Bean的元数据信息,包括类名、作用域和依赖关系等。 4. 实现依赖注入功能,通过读取Bean定义中的依赖关系,将相关的Bean对象注入到目标Bean中。 5. 提供AOP(面向切面编程)功能,允许开发者在应用程序的特定点进行横切关注点的处理。 6. 实现声明式事务管理功能,使用注解或XML配置方式来处理数据库事务。 7. 提供对不同数据访问技术(如JDBC、ORM框架、NoSQL等)的支持,通过集成相应的库来简化数据访问代码。 8. 增加对Web开发的支持,如处理请求、渲染视图等。 Spring框架需要具备对Java语言的深入了解,熟悉反射、代理、设计模式等概念和技术。还需要对依赖注入、AOP、事务管理、Web开发等方面有一定的理解。实现一个完整的Spring框架是一个庞大而复杂的任务,需要经过反复的设计、开发和测试。通过Spring框架,可以更深入地理解Spring的原理和内部机制,提高对框架的使用和调试能力。 ### 回答3: Spring框架是一个相当复杂的任务,但我可以简要介绍Spring框架的一些关键步骤和组成部分。 1. 依赖注入:Spring框架核心概念之一是依赖注入。我们需要编一个容器,负责管理和维护各个对象之间的依赖关系。可以使用反射机制来实现依赖注入。 2. 控制反转:Spring框架通过控制反转(IoC)来管理对象的创建和生命周期。我们需要编一个BeanFactory,负责加载和实例化对象,并将依赖注入到相应的对象中。 3. 配置文件:Spring框架也需要一个配置文件,用于定义对象的依赖关系和属性。我们可以使用XML、注解或者JavaConfig等方式来定义配置文件。 4. AOP支持:Spring框架提供了面向切面编程(AOP)的支持,可以通过编切面和通知来实现横切关注点的统一处理。我们需要实现一个AOP框架,用于拦截和处理切面逻辑。 5. MVC模式:Spring框架也提供了一个MVC框架,用于处理Web应用程序的请求和响应。我们需要编一个DispatcherServlet,负责将请求分发给相应的控制器,并处理模型和视图的交互。 6. 整合其他技术:Spring框架还可以与其他技术进行整合,例如数据库访问、事务管理、安全性控制等。我们需要编相应的模块,将这些技术与Spring框架集成起来。 虽然这只是一个简要的介绍,Spring框架是一个非常庞大的工程,需要深入理解Spring的原理和设计思想,并具备扎实的Java编程能力。但通过Spring框架,我们可以更好地掌握Spring核心概念和原理,并加深对框架的理解。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值