• 简易实现Spring Framework底层机制



    大家好呀!我是小笙,本节主要是想和大家简单讲讲ioc的实现以及aop的部分实现原理

    实现Spring Framework底层机制

    思考三个问题:

    • Spring容器 如何实现依赖注入singleton,prototype(单例,多例)
    • Spring 如何实现BeanPostProcesser (后置处理器)
    • Spring 如何实现AOP切面编程机制

    注意:以下实现只是为了便于理解真正的 spring 底层机制,并非完全一致

    整体架构

    image-20220712174642209

    思路分析以及实现过程

    1.创建Spring容器

    扫描包下所有的文件,将标有特定注解的类通过反射创建bean对象,存入到ioc容器

    • 使用lnsSpringConfig.java代替bean.xml文件
    • 扫描包下文件是否被标记注解
    • 将单例bean对象实例化放入singleton 单例池中

    知识点

    类加载器 详细说明

    Bootstrap 类加载器 <=> 对应路径 jre/lib

    Ext 类加载器 <=> 对应路径 jre/lib/ext

    App 类加载器 <=> 对应路径 classpath

    1.指定需要扫描包的全路径

    使用 lnsSpringConfig.java 代替 bean.xml 文件(指定需要扫描包的全路径)

    /**
     * @author Al_tair
     * @date 2022/7/14-22:14
     * 作用类似于bean.xml文件配置扫描包的全路径
     */
    @ComponentScan(value = "com.Al_tair.spring.myComponent")
    public class ClassPathConfig {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    @ComponentScan注解是自己实现的,就是用 value 来存储指定扫描包的全路径

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ComponentScan {
        // 通过 value 指定扫描包的全路径
        String value() default "";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    主方法就是将配置类的信息传给 ioc 容器,进行扫描包进行初始化

    public class AppMain {
        public static void main(String[] args) {
            // ClassPathConfig.class 配置类信息,可以通过获取该类上的注解信息来进行 bean 对象的注入
            SpringIocApplicationContext ioc = new SpringIocApplicationContext(ClassPathConfig.class);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2.扫描包并进行分类(单例、多例)

    首先,我们需要选择存储的数据结构,我采用 Map 键值对的形式来定义 BeanDefinition 对象和 singletonObject 单例池对象

    • BeanDefinition 对象是用来存储 scope(单例或者多例) 和 bean 对象的 class 信息,方便反射初始化对象
    • singletonObject 单例池对象是提前创建来懒加载单例对象,方便获取,多例对象都是通过创建 bean 对象的时候创建
    /**
      * 定义属性 BeanDefinitionMap id <-> BeanDefinition 对象
      * ConcurrentHashMap 线程安全
      */
    private final ConcurrentHashMap<String,BeanDefinition> ioc = new ConcurrentHashMap<>();
    
    /**
      * 单例池:存放单例对象
      */
    private ConcurrentHashMap<String,Object> singletonObject = new ConcurrentHashMap<>();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    BeanDefinition 类信息

    /**
     * 用于封装/记录Bean的信息
     * 1. scope
     * 2. bean 对应的 Class 对象
     */
    public class BeanDefinition {
        private String scope;
        private Class clazz;
        // ...省略
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    然后,我们需要找到需要扫描的类(找出.class的文件,并存储类路径)

    // 1.获取注解信息:@com.Al_tair.spring.annotation.ComponentScan(value=com.Al_tair.spring.myComponent)
    ComponentScan componentScan = (ComponentScan)this.springConfig.getDeclaredAnnotation(ComponentScan.class);
    // 2.取出注解里的 value 值并转换成相对文件路径 com/Al_tair/spring/myComponent
    String ScanPackage = componentScan.value().replace('.','/');
    // 3.得到类加载器:找到资源运行的真实路径
    ClassLoader classLoader = SpringIocApplicationContext.class.getClassLoader();
    URL resource = classLoader.getResource(ScanPackage);
    // 4.获取到资源路径上的文件夹
    File file = new File(resource.getFile());
    // 通过文件夹所在位置进行遍历
    scanFiles(file);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    扫描指定文件夹下的 .class 文件,我将这个类路径放在 list 列表下,方便以后获取

    /**
      * 存储包下的全部.class文件路径
      */
    private List<String> list = new ArrayList<>();
    
    • 1
    • 2
    • 3
    • 4

    scanFiles 方法:采用递归的方式进行遍历搜索

    /**
     * 扫描所有包以及子包下的类,返回全路径
     * @param files 传入需要扫描的文件
     */
    private void scanFiles(File files){
        File[] f = files.listFiles();
        for (File file : f) {
            if(file.isDirectory()){
                scanFiles(file);
            }else{
                String absolutePath = file.getAbsolutePath();
                if(absolutePath.endsWith("class")) {
                    // 例如:com.Al_tair.spring.myComponent.myService.PrototypeServiceImpl
                    list.add(absolutePath.substring(absolutePath.lastIndexOf("\\com") + 1, absolutePath.lastIndexOf(".class")).
                            replace("\\","."));
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    最后,我们要对指定注入的 bean 对象带上 MyComponent 注解以及 区分单例还是多例的 MyScope 注解

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyComponent {
        // 通过 value 可以给组件注入对象的id名,默认id名为类名首字母小写
        String value() default "";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    /**
     * 指定一个 bean 的作用范围 【singleton,prototype】
     */
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyScope {
        /**
         * 使用value来指定 singleton,prototype
         * @return
         */
        String value() default "";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    然后我们需要遍历列表中每个类路径,并进行类加载,判断该类是否被 MyComponent 注解标注,如果有则将 id 和 Class 信息存储在 BeanDefinition 中

    for (String s : list) {
        try {
            Class<?> aClass = classLoader.loadClass(s);
            // 判断该类是否被 MyComponent 注解标注
            if(aClass.isAnnotationPresent(MyComponent.class)){
                MyComponent declaredAnnotation = aClass.getDeclaredAnnotation(MyComponent.class);
                // beanName
                String id = declaredAnnotation.value();
                if("".equals(id)){
                    // 将类名首字母小写作为 id
                    id = s.substring(s.lastIndexOf(".") + 1,s.lastIndexOf(".") + 2).toLowerCase()
                        + s.substring(s.lastIndexOf(".")+2);
                }
    		   // 将 id 和 Class 信息存储在 BeanDefinition 中
                BeanDefinition beanDefinition = new BeanDefinition();
                beanDefinition.setClazz(aClass);
                // 判断是单例还是多例
                if(aClass.isAnnotationPresent(MyScope.class)){
                    // 标注注解则从注解的 value 取出值判断是多例还是单例
                    MyScope ScopeValue = aClass.getDeclaredAnnotation(MyScope.class);
                    beanDefinition.setScope(ScopeValue.value());
                }else{
                    // 没有标注注解默认为单例对象
                    beanDefinition.setScope("singleton");
                }
                ioc.put(id,beanDefinition);
    
                // 如果是单例对象,放入单例池
                if("singleton".equals(beanDefinition.getScope())){
                    Object bean = createBean(beanDefinition,id);
                    singletonObject.put(id,bean);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    3.创建 Bean 对象

    分三种情况:

    • 依赖注入 MyAutowired 方法:在其他类 创建的时候需要注入其他 bean 对象(控制反转,动态依赖注入)
    • 单例对象直接获取该 Bean 对象(而且只能创建一次)
    • 多例对象每次获取都需要创建 Bean 对象(每个对象都是不同的)
    /**
     * 创建 bean 对象
     */
    private Object createBean(BeanDefinition beanDefinition,String beanName){
        // 得到 bean 的 Class 对象
        Class clazz = beanDefinition.getClazz();
        try {
            // 通过反射得到 bean 实例
            Object instanse = clazz.getDeclaredConstructor().newInstance();
    
            // 依赖注入
            // 1.遍历当前的所有对象的字段
            Field[] declaredFields = clazz.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                // 2. 判断字段是否被特定注解注释
                if(declaredField.isAnnotationPresent(MyAutowired.class)){
                    MyAutowired annotation = declaredField.getAnnotation(MyAutowired.class);
                    // 3.是否必须依赖注入
                    boolean required = annotation.required();
                    if(required){
                        // 4.获取字段名字
                        String name = declaredField.getName();
                        // 5.通过 getBean 来组装对象
                        Object bean = getBean(name);
                        // 6.进行组装(因为属性是私有的,所以需要进行暴破)
                        declaredField.setAccessible(true);
                        declaredField.set(instanse,bean);
                    }else{
                        // 假设都需要依赖注入
                    }
                }
            }
            return instanse;
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 如果创建对象失败
        return null;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    4.获取 Bean 对象

    通过 id 来获取 bean 对象

    public Object getBean(String id){
        BeanDefinition beanDefinition = ioc.get(id);
        if(beanDefinition != null && ioc.containsKey(id)){
            String scope = beanDefinition.getScope();
            // 如果是单例对象,直接获取
            if("singleton".equals(scope)){
                return singletonObject.get(id);
            }else{
                // 反之多例对象,需要创建 Bean
                return createBean(beanDefinition,id);
            }
        }else{
            throw  new RuntimeException("没有该 bean 对象");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    讲到这里 IOC 容器就算简单的创建出来了,接下来我们需要讲一下 AOP机制


    2.实现 AOP 机制
    1.首先需要实现添加对象初始化接口
    /**
     * 初始化 Bean 对象的接口:用来初始化 Bean 的时候调用
     */
    public interface MyInitializingBean {
        // 初始化方法:在 setter 方法执行后调用
        void afterPropertiesSet() throws Exception;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    实现了该初始化接口的类:当创建 MyServiceImpl 对象的时候将会初始化

    @MyComponent
    public class MyServiceImpl implements MyInitializingBean {
        @MyAutowired
        private MyDaoImpl myDaoImpl;
    
        @Override
        public void afterPropertiesSet() throws Exception {
            System.out.println("MyServiceImpl 初始化方法被调用");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    将初始化操作放在创建 Bean 对象方法的后面

    private Object createBean(BeanDefinition beanDefinition,String beanName){
        // 得到 bean 的 Class 对象
        Class clazz = beanDefinition.getClazz();
        // 通过反射得到 bean 实例
        try {
            Object instanse = clazz.getDeclaredConstructor().newInstance();
            // 依赖注入
            // ...省略
    
            // 创建好实例之后进行初始化
            // instanceof: 判断当前某个对象的运行类型是否为该类型实现的接口或者父类
            if(instanse instanceof MyInitializingBean){
                ((MyInitializingBean)instanse).afterPropertiesSet();
            }
            return instanse;
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 如果创建对象失败
        return null;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    2.后置处理器的实现

    MyBeanPostProcessor 接口实现:主要是在所有 bean 对象初始化前后调用(切面编程)

    /**
     * 后置处理器:会对 Spring 容器中的所有 Bean实例生效(切面编程)
     */
    public interface MyBeanPostProcessor {
        /**
         * 在 bean 对象初始化方法之前调用
         *
         * @param bean
         * @param beanName
         * @return
         * @throws BeansException
         */
        @Nullable
        default Object postProcessBeforeInitialization(Object bean, String beanName) {
            return bean;
        }
    
        /**
         * 在 bean 对象初始化方法之后调用
         *
         * @param bean
         * @param beanName
         * @return
         */
        @Nullable
        default Object postProcessAfterInitialization(Object bean, String beanName) {
            return bean;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    实现自制的后置处理器接口: AOP 动态代理机制的实现核心就是后置处理器实现类中实现

    /**
     * 实现自制化的后置处理器
     */
    @MyComponent
    public class LnsBeanPostProcessor implements MyBeanPostProcessor {
    
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) {
            // 后置处理器会在容器初始化 bean 对象之前生效
            // 通过 instanceof 关键字判断,可以用于多个对象的编程
            // 可以用来处理日志、权限、身份、事务等
            System.out.println("Before=" + beanName);
            return bean;
        }
    
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) {
            System.out.println("After=" + beanName);
            return bean;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    为了简化操作,我将后置处理器对象单独放一个容器中,方便调用对象初始化方法前后调用

        /**
         * 存放实现了 MyBeanPostProcessor 接口的后置处理器的对象
         * 实际是存放在单例池中,这里为了方便取出后置处理器的对象单独写个集合
         */
        private List<MyBeanPostProcessor> beanPostProcessorList  = new ArrayList<>();
    
    • 1
    • 2
    • 3
    • 4
    • 5

    为了添加后置处理器对象,需要使用 isAssignableFrom() 方法判断某个类是否实现了某个接口(注意区别 instanceof 关键字)

    // beanDefinitionByScan 方法
    for (String s : list) {
        try {
            Class<?> aClass = classLoader.loadClass(s);
            if(aClass.isAnnotationPresent(MyComponent.class)){
                // 判断是否实现了MyBeanPostProcessor 接口
                // 这里不能使用instanceof来判断,因为instanceof是用来判断实例对象,但是判断某个类是否实现了某个接口就要用底下这个方法
                if(MyBeanPostProcessor.class.isAssignableFrom(aClass)){
                    MyBeanPostProcessor myBeanPostProcessor = (MyBeanPostProcessor)aClass.newInstance();
                    beanPostProcessorList.add(myBeanPostProcessor);
                    continue;
                }
                MyComponent declaredAnnotation = aClass.getDeclaredAnnotation(MyComponent.class);
                // beanName
                String id = declaredAnnotation.value();
                if("".equals(id)){
                    // 将类名首字母小写作为 id
                    id = s.substring(s.lastIndexOf(".") + 1,s.lastIndexOf(".") + 2).toLowerCase()
                        + s.substring(s.lastIndexOf(".")+2);
                }
    
                BeanDefinition beanDefinition = new BeanDefinition();
                beanDefinition.setClazz(aClass);
                // 判断是单例还是多例
                if(aClass.isAnnotationPresent(MyScope.class)){
                    MyScope ScopeValue = aClass.getDeclaredAnnotation(MyScope.class);
                    beanDefinition.setScope(ScopeValue.value());
                }else{
                    beanDefinition.setScope("singleton");
                }
                ioc.put(id,beanDefinition);
    
                // 如果是单例对象,放入单例池
                if("singleton".equals(beanDefinition.getScope())){
                    Object bean = createBean(beanDefinition,id);
                    singletonObject.put(id,bean);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    最后需要在初始化方法前后添加后置处理器对象的调用

    // createBean 方法
    // 后置处理器
    for (MyBeanPostProcessor myBeanPostProcessor : beanPostProcessorList) {
        Object o = myBeanPostProcessor.postProcessBeforeInitialization(instanse,beanName);
        if(o != null){
            instanse = o;
        }
    }
    
    // 创建好实例之后进行初始化
    // instanceof: 判断当前某个对象的运行类型是否为该类型实现的接口或者父类
    if(instanse instanceof MyInitializingBean){
        ((MyInitializingBean)instanse).afterPropertiesSet();
    }
    
    // 后置处理器
    for (MyBeanPostProcessor myBeanPostProcessor : beanPostProcessorList) {
        Object o = myBeanPostProcessor.postProcessAfterInitialization(instanse,beanName);
        if(o != null){
            instanse = o;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    实现效果

    image-20220824235607075

    3.最后实现 AOP 机制,需要在后置处理器里进行操作
    /**
     * 1. 在Spring容器中,仍然把HspBeanPostProcessor当做一个Bean对象, 要在注入到容器
     * 2. 还要考虑多个后置处理器对象注入到容器问题
     */
    @Component
    public class LnsBeanPostProcessor implements BeanPostProcessor {
    
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) {
            // 可以用于日志,权限,身份, 事务等
            if (bean instanceof Car) {
                System.out.println("Car对象");
            }
            return bean;
        }
    
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) {
            // 实现AOP, 返回代理对象, 即对 Bean 对象进行包装
            if ("smartDog".equals(beanName)) {
                //使用Jdk的动态代理,返回返回bean的代理对象
                Object proxyInstance = Proxy.newProxyInstance(HspBeanPostProcessor.class.getClassLoader(),
                        bean.getClass().getInterfaces(), new InvocationHandler() {
                            @Override
                            public Object invoke(Object proxy, Method method, Object[] args)
                                    throws Throwable {
                                System.out.println("method=" + method.getName());
                                Object result = null;
                                
                                // 假如我们进行前置通知+返回通知 处理的方法是getSum
                                // 后面可以通过注解来做的更加灵活
                                if ("getSum".equals(method.getName())) {
                                    SmartAnimalAspect.showBeginLog();
                                    // 执行目标方法
                                    result = method.invoke(bean, args);
                                    // 进行返回通知的处理
                                    SmartAnimalAspect.showSuccessLog();
                                } else {
                                    // 执行目标方法
                                    result = method.invoke(bean, args);
                                }
                                return result;
                            }
                        });
                // 如果bean是需要返回代理对象的, 这里就直接return proxyInstance
                return proxyInstance;
            }
            // 如果不需要AOP, 返回 bean
            return bean;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
  • 相关阅读:
    DevOps 前端开发和 Spug
    SAHI_Yolov8:小目标检测
    注解与反射_反射
    人工智能安全国际标准化进展研究
    CSS 3 五光十色的变色龙动画的制作
    Redis 获取、设置配置文件
    配置 `PostgreSQL` 与 `Keepalived` 以实现高可用性
    静态住宅代理是什么?为什么要选择它?
    【Unity实战100例】Unity幸运大转盘之概率可控
    透视俄乌网络战之四:西方科技巨头的力量
  • 原文地址:https://blog.csdn.net/Al_tair/article/details/126533909