框架篇-手写spring

前言: 

本文参考2021吃透这些Java手写(Spring、Tomcat、Dubbo、JVM、Hashmap、Mybatis、Springmvc)框架源码,看完吊打面试官!_哔哩哔哩_bilibili,个人业余时间的学习笔记

一、ApplicationContext基本构造

1. Spring中ApplicationContext用法

Spring中包含ClassPathXmlApplicationContext与AnnotationConfigApplicationContext容器类,ClassPathXmlApplicationContext使用xml构造spring容器,AnnotationConfigApplicationContext使用Class构造容器,基本用法如下:

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class demo {
    public static void main(String[] args) {
        // 告诉spring配置文件,创建spring容器
        ClassPathXmlApplicationContext classPathXmlApplicationContext =
                new ClassPathXmlApplicationContext("spring.xml");
        // 以java类作为spring配置文件
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);

        applicationContext.getBean("userService", UserService.class);
    }
}

由上述示例代码可见,ApplicationContext初始化Spring容器,并提供了获取Bean的方法

2. ApplicationContext基本结构搭建

a. 代码结构

b. 测试包

测试包用以测试spring代码,调用了Spring的getBean方法,并注解Service类

详细代码见:spring_learn/src/main/java/com/demo at main · zzhao2018/spring_learn · GitHub

c. Spring包

ZApplicationContext类:spring包中的ZApplicationContext模拟真实Spring包中的ApplicationContext,实现了ApplicationContext getBean空方法,代码如下:

package com.spring;

/**
 * 容器类
 */
public class ZApplicationContext {
    private Class configClass;

    public ZApplicationContext(Class configClass) {
        this.configClass = configClass;
        // 解析配置类
        // 解析ComponentScan注解
        // 扫描路径
        // 扫描

    }

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

注解:

注解参数基本概念:

@Retention:定义某个注解的保存范围,每1个Annotation对象,都会有唯一的RetentionPolicy属性,包含以下枚举

  • RetentionPolicy.SOURCE:编译器将丢弃注释;
  • RetentionPolicy.CLASS:注释将由编译器记录在类文件中,但在运行时不需要由 VM 保留。 该选项为默认值。
  • RetentionPolicy.RUNTIME:注解将被编译器记录在类文件中,并在运行时由 VM 保留,因此可以被反射访问注解。

@Target: 说明Annotation所修饰的对象范围,每一个Annotation对象可以有多个Target,包含以下枚举值:

public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,

    /** Field declaration (includes enum constants) */
    FIELD,

    /** Method declaration */
    METHOD,

    /** Formal parameter declaration */
    PARAMETER,

    /** Constructor declaration */
    CONSTRUCTOR,

    /** Local variable declaration */
    LOCAL_VARIABLE,

    /** Annotation type declaration */
    ANNOTATION_TYPE,

    /** Package declaration */
    PACKAGE,

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE
}

ComponentScan注解:用以标注service包路径,代码如下

package com.spring;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ComponentScan {
    String value() default ""; //使用属性定义扫描路径
}

Componet注解:用以标注service类

package com.spring;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

@Scope注解:用以区分对象是单例bean还是原型bean 

package com.spring;


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

二、包扫描实现

1. 类加载器原理

a. 概述

类加载基本流程:

  • 虚拟机将描述类的数据从Class文件中加载到内存
  • 进行数据校验、转换解析和初始化,形成可以被虚拟机使用的java类型 

类加载的特点:

类型的加载、连接和初始化是在程序运行时完成的,这种策略会增加类加载时的性能开销,但为java应用程序提供高度灵活性。(编写一个面向接口的应用程序,可以等到运行时才指定其实际的实现类;用户可以自定义类加载器;让本地应用程序可以在运行时从网络或其他地方加载二进制流作为程序代码一部分)

b. 类加载时机

 类的生命周期:加载、验证、准备、解析、初始化、使用、卸载

类加载各阶段时机: 

加载时机:java虚拟机规范中没有强制约束加载阶段的时机

初始化时机:虚拟机规范规定有且只有5种情况必须对类进行初始化:

  • 遇到new/getstatic/putstatic/invokestatic字节码指令时,如果类没有初始化,就要进行初始化(new关键字初始化、读取或设置一个类的静态字段、调用一个类的静态方法)
  • 使用reflect包的方法对类进行反射调用,如果类没有初始化,则需要进行初始化
  • 初始化一个类时,父类没有进行初始化,则需要先触发父类初始化
  • 虚拟机启动时,用户需要指定一个要执行的主类,虚拟机会先初始化主类
  • java1.7动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,且这个方法句柄所对应的类没有进行初始化,则需要先触发初始化(????TODO)
public class SuperClass {
    static {
        System.out.println("SuperClass init");
    }

    public static int value = 123;
}

public class SubClass extends SuperClass {
    static {
        System.out.println("SubClass init");
    }
}

public class NotInitialization {
    public static void main(String[] args) {
        testSuperClass();
        System.out.println("=================");
        testArray();
        System.out.println("=================");
        testConst();
    }

    public static void testSuperClass() {
        /**
         *调用父类的static对象,会导致父类被初始化
         * 但对于静态字段,只有直接定义了这个字段的类才会被初始化,因此子类不会初始化
         */
        System.out.println(SubClass.value);
    }

    public static void testArray() {
        /**
         *没有触发SuperClass的初始化阶段
         */
        SuperClass[] sca = new SuperClass[10];
    }

    public static void testConst() {
        /**
         *常量在编译阶段会存入常量池,本质上没有直接引用到定义常量的类,不会初始化类
         */
        System.out.println(SuperClass.data);
    }


}



------------------------output--------------------------
SuperClass init
123
=================
=================
hello world

c. 类加载过程(TODO)

d. 类加载器

定义:虚拟机设计团队将类加载阶段中“通过类的全限定名来获取描述此类的二进制字节流”的动作放到java虚拟机外部实现,实现这个动作的代码模块为类加载器

类与类加载器:对于任意一个类,类加载器和类本身共同确定了其在java虚拟机的唯一性,每个类加载器都有独立的类名称空间。(同一个Class文件,被两个不同的类加载器加载,equals()、isAssignableFrom()、isInstance()方法会有所不同)

类加载器分类:

  • 启动类加载器(Bootsrap):使用C++实现,虚拟机自身的一部分,负责将存放在<JAVA_HOME>/lib目录中或-Xbootclasspath参数指向的类库加载到虚拟机内存中
  • 扩展类加载器:使用java实现,独立于虚拟机外部,并继承于java.lang.ClassLoader,加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量指定的类库。开发者可以扩展改类库
  • 应用类加载器:使用java实现,独立于虚拟机外部,并继承于java.lang.ClassLoader,负责加载用户类路径下(ClassPath)上指定的类库,开发者可以直接使用该类加载器,一般程序中默认使用的类加载器
  • 自定义类加载器

双亲委派机制:

  • 除了顶层类加载器外,其余的类加载器有自己的父类加载器。父子类加载器之间通过组合关系复用
  • 如果一个类加载器接收到了类加载请求,它首先不会自己加载类,而是把请求委托给父类加载器执行。当父类加载器反馈无法加载时,子加载器才尝试加载

双亲委派机制好处:安全,确保一些基类不会被自己编写的类加载器影响。(java.lang.Object)

2. 实现包扫描

思路:

  1. 获取AppConfig类中@ComponentScan注解,获取路径
  2. 使用类加载器获取包路径下的资源,使用类加载器加载类,判断每个类的注解中是否包含@Componet注解。若包含,则执行Service实例化操作

代码:

    public ZApplicationContext(Class configClass) throws ClassNotFoundException {
        this.configClass = configClass;
        // 获取配置类ComponentScan的注解,得到扫描路径
        ComponentScan componentScanAnnotation = (ComponentScan) configClass.getDeclaredAnnotation(ComponentScan.class);
        System.out.println(componentScanAnnotation.value());
        // 扫描路径下的类
        /**
         * 类加载器类型
         * Bootstrap: jre/lib下类加载器
         * Ext:jre/ext/lib下类加载器
         * App:classpath下加载器
         */
        // 获取app classLoader
        ClassLoader classLoader = ZApplicationContext.class.getClassLoader();
        // 获取类
        URL resource = classLoader.getResource(componentScanAnnotation.value().replace(".", "/"));
        System.out.println();
        File file = new File(resource.getFile());
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File f : files) {
                // 获取 类名
                String filename = f.getAbsolutePath();
                String classname = filename.substring(filename.indexOf("com"), filename.indexOf(".class")).replaceAll("\\\\", ".");
                try {
                    Class<?> clazz = classLoader.loadClass(classname);
                    // 发现一个类上有 componet 注解
                    if (clazz.isAnnotationPresent(Componet.class)) {
                        System.out.println(classname + " is bean");
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

详细代码见:spring_learn/ZApplicationContext.java at main · zzhao2018/spring_learn · GitHub 

3. 实例化Service

a. 概述

Spring中有单例bean和原型bean。单例bean在初始化后,使用每次getBean()方法均会获得唯一的bean;原型bean每次调用getBean()方法则会形成新的类。

b. 实现思路

  1. 使用@Scope标注bean是否是单例bean
  2. 将Component类基本信息存入BeanDefinition中(class/scope),并将改BeanDefinition类放入map中
  3. 判断bean是否是单例bean,若是,创建bean实例则放入单例池中
  4. 调用getBean方法时,判断传入的service是否是单例,若单例,从单例池中获取,否则直接实例化

c. 代码

// 单例/原型bean实例化流程
if (clazz.isAnnotationPresent(Componet.class)) {
    Componet componet = clazz.getDeclaredAnnotation(Componet.class);
    String beanName = componet.value();
    // 初始化BeanDefinition
    BeanDefinition beanDefinition = new BeanDefinition();
    beanDefinition.setClazz(clazz);
    if (clazz.isAnnotationPresent(Scope.class)) {
        Scope scope = clazz.getDeclaredAnnotation(Scope.class);
        beanDefinition.setScope(scope.value());
    } else {
        beanDefinition.setScope("singleton");
    }
    beanDefinitionMap.put(beanName, beanDefinition);
    // 如果是单例bean,放入单例池
    if (beanDefinition.getScope().equals("singleton")) {
        singletonObejcts.put(beanName, createBean(beanDefinition));
    }
}




// 创建bean代码
    private Object createBean(BeanDefinition beanDefinition) {
        Class clazz = beanDefinition.getClazz();
        try {
            return clazz.getDeclaredConstructor().newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

测试代码如下:

package com.demo;

import com.spring.ZApplicationContext;

public class Test {
    public static void main(String[] args) throws Exception {
        ZApplicationContext applicationContext = new ZApplicationContext(AppConfig.class);

        System.out.println(applicationContext.getBean("userService"));
        System.out.println(applicationContext.getBean("userService"));
        System.out.println(applicationContext.getBean("userService"));

    }
}

在UserService上增加注解@Scope("prototype"),运行后结果为

 去除注解@Scope,运行后结果为

 实例初始化成功

三、依赖注入实现

1. 实现思路

思路:

  • 在createBean时,获取bean对象的每一个Field,判断各个属性是否有@Autowired注解
  • 若有@Autowired注解,则从BeanDefinitionMap中获取类的元数据,调用getBean方法创建
  • 为了确保注入时,通过getBean方法获取bean时不会出现空的情况,需要将单例bean创建的流程放置在scan方法外,在scan完成后,在进行初始化时,判断父依赖是否存在,不存在则递归初始化父依赖(视频存在这个问题)

代码:

// 依赖注入处理流程
    public ZApplicationContext(Class<com.demo.AppConfig> configClass) throws Exception {
        this.configClass = configClass;
        // 扫描指定service路径,扫描类,生成beanDefine并放入map,单例bean放入单例池
        scan(configClass);
        // 扫描单例bean,放入单例池
        for (String beanName : beanDefinitionMap.keySet()) {
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            if (beanDefinition.getScope().equals("singleton")) {
                if (!singletonObejcts.containsKey(beanName)) {
                    singletonObejcts.put(beanName, createBean(beanName, beanDefinition));
                }
            }
        }
    }


    // 创建bean
    private Object createBean(String beanName, BeanDefinition beanDefinition) throws Exception {
        if (singletonObejcts.contains(beanName)) {
            return singletonObejcts.get(beanName);
        }
        Class clazz = beanDefinition.getClazz();
        try {
            Object instance = clazz.getDeclaredConstructor().newInstance();
            // 依赖注入
            for (Field field : clazz.getDeclaredFields()) {
                if (field.isAnnotationPresent(Autowired.class)) {
                    // 根据属性名称获取
                    Object bean = getBean(field.getName());
                    if (bean == null) {
                        // 获取依赖,创建bean
                        BeanDefinition fieldBeanDefinition = beanDefinitionMap.get(field.getName());
                        if (fieldBeanDefinition != null) {
                            Object dependObj = createBean(field.getName(), fieldBeanDefinition);
                            if (fieldBeanDefinition.getScope().equals("singleton")) {
                                singletonObejcts.put(field.getName(), dependObj);
                            }
                            bean = getBean(field.getName());
                        } else {
                            throw new Exception(beanName + " depend on " + field.getName() + " is null");
                        }
                    }
                    // private可访问
                    field.setAccessible(true);
                    field.set(instance, bean);
                }
            }
            if (instance instanceof BeanNameAware) {
                ((BeanNameAware) instance).setBeanName(beanName);
            }

            // beanPostProcessor 初始化前动作
            for (BeanPostProcessor beanPostProcessor : beanPostProcessors) {
                instance = beanPostProcessor.postProcessBeforeInitialization(instance, beanName);
            }

            // 初始化bean
            if (instance instanceof InitializingBean) {
                ((InitializingBean) instance).afterPropertiesSet();
            }

            // beanPostProcessor 初始化后动作
            for (BeanPostProcessor beanPostProcessor : beanPostProcessors) {
                instance = beanPostProcessor.postProcessAfterInitialization(instance, beanName);
            }
            return instance;
        } catch (Exception e) {
            throw new Exception(e.getMessage());
        }
    }

四、AOP实现

1. 代理设计模式

a. 概述

代理模式作用:通过代理,控制对象的访问,可以详细的控制访问某个对象的方法,在调用这个方法前做前置处理,调用这个方法后做后置处理(AOP的核心原理),从而实现将统一流程代码放到代理类中处理

核心角色:

  • 抽象角色:定义代理角色和真实角色的公共对外方法
  • 真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用
  • 代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作

应用场景:

  • 安全代理:屏蔽对真实角色的直接访问
  • 远程代理:通过代理类处理远程方法调用
  • 延迟加载:先加载轻量级代理对象,真正需要时再加载真实对象

分类:

  • 静态代理(静态定义代理类)
  • 动态代理:JDK自带的动态代理;javaassist字节码操作库;CGLIB;ASM

b. 静态代理

示例代码:

// 抽象角色
public interface Star {
    void signContract();

    void sing();
}


// 真实角色
public class RealStar implements Star {
    @Override
    public void signContract() {
        System.out.println("RealStar.signContract()");
    }

    @Override
    public void sing() {
        System.out.println("RealStar.sing()");
    }
}


// 代理角色
public class ProxyStar implements Star {
    private Star star;

    public ProxyStar(Star star) {
        this.star = star;
    }

    @Override
    public void signContract() {
        System.out.println("Proxy.signContract()");
    }

    @Override
    public void sing() {
        this.star.sing();
    }
}

类图:

c. 动态代理 

目的:不用手动定义代理类,动态实现代理类

优点:抽象角色中(接口)声明的所有方法都被转移到一个集中的方法处理,进而可以更灵活和统一的处理众多方法

实现方式:java.lang.reflect.Proxy(动态生成代理类);java.lang.reflect.InvocationHandler(处理器接口)

示例代码:

import designPatterns.StrategyLearn.proxyLearn.staticProxy.RealStar;
import designPatterns.StrategyLearn.proxyLearn.staticProxy.Star;

import java.lang.reflect.Proxy;

// 使用动态代理实现静态代理示例功能
public class dynamicProxy {
    public static void main(String[] args) {
        Star realStar = new RealStar();
        StarHandler handler = new StarHandler(realStar);
        Star proxy = (Star) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Star.class}, handler);
        proxy.sing();
        proxy.signContract();
    }
}




// 代理handler,只有在sing时才调用真实方法
import designPatterns.StrategyLearn.proxyLearn.staticProxy.Star;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class StarHandler implements InvocationHandler {

    private Star realStar;

    public StarHandler(Star star) {
        this.realStar = star;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("sing")) {
            return method.invoke(realStar, args);
        }
        System.out.println("proxy " + method.getName());
        return null;
    }
}

动态代理原理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值