手写简单Srping框架

本文围绕Spring框架展开,先介绍Spring基础架构,接着阐述自研框架的实现。包括环境准备,如下载编译Spring源码;商城项目业务的Service和Controller层搭建;补充设计模式、泛型、动态代理等知识;最后详细讲解自研框架中IoC容器、AOP和MVC的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

链接:慕课课程:剑指Java自研框架,决胜Spring源码

  提升核心竞争力:
1、快速读懂他人的,甚至是大神级的框架代码。
2、设计出让自己变得不可替代的技术方案。
  切入点:读懂Spring框架的核心源码,锻炼框架的设计能力。根据经验而言,能够写出某个方向10万行的代码,能成为该方向的行家,而Spring约有一百万行代码。
  授之以鱼不如授之以渔:
1、通过自研框架了解Spring的设计
2、深入Spring源码洞悉核心脉络
在这里插入图片描述
Spring基础架构图
在这里插入图片描述
  spring-core:
1、包含框架基本的核心工具类,其他组件都要使用到这个包里面的类
2、定义并提供资源的访问方式
  spring-bean:Spring主要面向bean编程(BOP)
1、bean的定义
2、bean的解析
3、bean的创建
  spring-context:
1、为spring提供运行时环境,保存对象的状态
2、拓展了BeanFactory
  spring-aop:最小的动态代理实现
1、JDK动态代理
2、Cglib
3、只能使用运行时织入,仅支持方法级编织,仅支持方法执行切入点
  spring-aspectj + spring-instrument :Full AspectJ
1、编译器织入
2、加载期织入
3、运行期织入

自研框架架构简图:
在这里插入图片描述

1、环境准备

1.1 下载和编译Spring源码

  下载JDK11,maven,IDEA。JDK8和JDK11是长期支持的。
  Spring官网:https://spring.io
  Spring托管到github:https://github.com/spring-projects/spring-framework。这里下载5.2.0.RELEASE版,更加稳定。5.2.0.RELEASE版链接,下载好后解压。
  接下来编译,可以参考https://github.com/spring-projects/spring-framework/blob/main/import-into-idea.md
进入解压后的目录,打开build.gradle,加入阿里云配置
在这里插入图片描述
在这里插入图片描述
windows在命令行中执行

.\gradlew :spring-oxm:compileTestJava

编译成功
在这里插入图片描述
2、在IDEA中import project,
常见问题:引入jar包超时。解决办法:手动下载,放在C:\Users\zkp.gradle\caches\modules-2\files-2.1\xxx\xxx.jar,具体目录下替换jar包即可。
编译成功
在这里插入图片描述
3、移除spring-aspects,右键,选择load/unload modules,unload
在这里插入图片描述
如果不移除,该模块会报错。
在这里插入图片描述
4、重新build即可

1.2 Spring官方文档

https://docs.spring.io/spring-framework/docs/current/reference/html/
  把Overview和Core看完,就会有大体的了解。
  软件版本:
1、GA:General Availability,官方正式发布的稳定版本。类似意思的还有RELEASE,Stable,Final。
2、RC:Release Candidate,发行候选版本,基本不再加入新功能,用作修复Bug,修复好后当做GA上线。
3、Alpha:内部测试版本,Bug较多,功能不全。
4、Beta:公开测试版,比Alpha晚一些,还会加功能,修Bug。
5、M:Milestone,开发期发型版本,边开发边发行。
在这里插入图片描述

在这里插入图片描述

2 商城项目业务

  • 前端展示系统业务需求
    1、访问首页时,显示头条列表和店铺类别列表
    2、管理员通过后台对头条和店铺类别进行增删改查

从整体到局部实现,采用MVC架构
在这里插入图片描述
单一职责原则:
1、尽可能让一个类负责相对独立的业务
2、保证类之间的耦合度低,降低类的复杂度
dao层:
HeadLine
ShopCategory
service层:
返回通用返回值,使用泛型。

2.1 Service层搭建

在这里插入图片描述

2.2 Controller层搭建

  Servlet用来接收和处理前端的请求,是Sun公司提供的动态Web资源开发技术,本质上是一段Java程序,但是无法独立运行,需要放在Tomcat、Jetty等Servlet容器中运行,运行过程如下:
在这里插入图片描述
  减少Servlet数量。参照Srping MVC,通过DispatcherServlet进行请求派发。
1、拦截所有请求
2、解析请求
3、派发给对应的Controller里面的方法进行处理
在这里插入图片描述

3 补充知识

3.1设计模式

3.1.1 门面模式

  门面模式,Facede Pattern,子系统的外部与其内部的通信必须通过统一的对象进行。
1、提供一个高层次的接口,使得子系统更易于使用
在这里插入图片描述
在这里插入图片描述

  • 通过slf4j实现对多种日志框架的兼容
    在这里插入图片描述

3.1.2 工厂模式

  简单工厂模式,其实不算设计模式,而是一种编程习惯。定义一个工厂类,根据传入的参数的值不同返回不同的实例。被创建的实例具有共同的父类或接口。
在这里插入图片描述

3.1.3 单例模式

Singleton Pattern。确保一个类只有一个实例,并对外提供统一访问方式。
饿汉模式:类被加载的时候就立即初始化并创建唯一实例。
构造函数私有,防止外界调用构造函数初始化实例,并对外提供静态方法调用实例。

public class StarvingSingleton {
    private static final StarvingSingleton starvingsingleton = new StarvingSingleton();
    private StarvingSingleton(){}
    public static StarvingSingleton getInstance() {
        return starvingsingleton;
    }
}

懒汉模式:在被客户端首次调用的时候才创建唯一实例。添加双重检查锁机制确保线程安全。

public class LazyDoubleCheckSingleton {
    private volatile static LazyDoubleCheckSingleton instance;
    private LazyDoubleCheckSingleton(){}
    public static LazyDoubleCheckSingleton getInstance() {
        // 第一次检测
        if (instance == null) {
            synchronized (LazyDoubleCheckSingleton.class) {
            	//第二次检测
                if (instance == null) {
                    instance = new LazyDoubleCheckSingleton();
                }
            }
        }
        return instance;
    }
}

3.2 泛型

  泛型,Generics。
1、让数据类型变得参数化
对应泛型时,对应的数据类型时不确定的
泛型方法被调用时,会制定具体类型
核心目标:解决容器类型在编译时安全检查的问题。

泛型类
创建对象时需要使用尖括号传入类。如:new GenericClassExample<String>。
例如:

public class GenericClassExample<T> {
	private T member;
	public GenericClassExample(T member) {
		this.member = member;
	}
	public T handleSomething(T target) {
	return target;
	}
}

泛型接口
与泛型类的用法基本相同,常用于数据类型的生产工厂接口中。无论是泛型类还是泛型接口都可以传入多个泛型,如<T, E>。在实现接口时,传入类型,普通类可以实现泛型接口,泛型类也可以实现泛型接口。
例如:

public interface GenericIFactory<T, N> {
	T nextObject();
	N nextNumber();
}
public class RobotFactory implements GenericIFactory<String, Integer> {
	...
}
public class GenericFactoryImpl <T, N> implements GenericIFactory <T, N> {
	...
}

泛型方法
既能用在泛型类,泛型接口里,也能用在普通类或者普通接口里。区别是方法声明里多了<T>。注意:泛型方法里面的泛型标识符T是独立于泛型类存在的,即使都是T,也会有泛型方法传入的类型指定。
例如:

public static <E> void printArray(E[] inputArray) {
	...
}

注意:
1、泛型的参数不支持基本类型
2、泛型相关的信息不会进入运行时阶段(泛型擦除)
在泛型里使用具备继承关系的类:
1、使用通配符?,但是泛型的类型检查将失去意义
2、给泛型加入上边界,? extend E,表示E或者继承了E的类
3、给泛型加入下边界,? super E,表示E或者E的父类

泛型字母的含义:
E——Element:在集合中使用,因为集合存放的是元素
T——Type:Java类
K——Key:键
V——Value:值
N——Number:数值类型

3.3 动态代理

3.3.1 JDK动态代理

基于反射机制实现,程序运行时动态生成类的字节码,并加载到JVM,要求被代理的类必须实现接口。但是代理类可以不实现接口。
Proxy,重要方法是newProxyInatance
InvocationHandler,重要方法是invoke
优点:
1、JDK原生,在JVM里运行较为可靠
2、平滑支持JDK版本升级

3.3.2 CGLIB动态代理

基于ASM机制实现,生成业务类的子类作为代理类。
代码生成库:Code Generarion Library。
1、不要求被代理类实现接口
2、内部主要封装了ASM Java字节码操纵框架(轻量,高性能)
3、动态生成子类以覆盖非final方法,绑定钩子回调自定义拦截器
需要在pom.xml引入cglib

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.9</version>
</dependency>

CallBack接口标记
MethodInterceptor继承了CallBack
Enhancer,核心是create方法

public class AlipayMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        beforePay();
        Object result = methodProxy.invokeSuper(o, args);
        afterPay();
        return result;
    }
    private void beforePay() {
        System.out.println("before");
    }
    private void afterPay() {
        System.out.println("after");
    }
}
public class CglibUtil {
    public static <T> T createProxy(T targetObject, MethodInterceptor methodInterceptor) {
        return (T) Enhancer.create(targetObject.getClass(), methodInterceptor);
    }
}
public class ProxyDemo {
    public static void main(String[] args) {
        CommonPayment commonPayment = new CommonPayment();
        AlipayMethodInterceptor alipayMethodInterceptor = new AlipayMethodInterceptor();
        CommonPayment payment = CglibUtil.createProxy(commonPayment, alipayMethodInterceptor);
        payment.pay();
    }
}

优点:
1、被代理对象无需实现接口,能实现代理类的无侵入
2、两者效率其实已经接近了,CGLIB目前并没有快许多
SpingAOP底层机制:
CGLIB和JDK动态代理共存
默认策略:
Bean实现了接口则用JDK,否则使用CGLIB

自研框架

框架具备的最基本功能:
1、解析配置,xml,注解等,自研框架使用注解
2、定位与注册对象
3、注入对象
4、提供通用的工具类

4 IoC容器的实现

需要实现的点:
创建注解、提取标记对象、实现容器、依赖注入

4.1 创建注解

@Target(ElementType.TYPE):表示这个注解作用在类上
@Retention(RetentionPolicy.RUNTIME):表示生命周期为运行时
Controller层对应注解@Controller
Service层对应注解@Service
DAO层对应注解@Repository
@Component是通用的需要被容器管理的类的注解

4.2 提取标记对象

extractPackageClass需要完成的事情
1、获取到类加载器
2、通过类加载器获取到加载的资源信息
3、依据不同的资源类型,采用不同的方式获取资源的集合
类加载器ClassLoader

URL统一资源定位符
某个资源的唯一地址
通过获取java.net.URL实例获取协议名,资源名路径等信息
在这里插入图片描述
这里修改一点,使用getSource获取路径后,如果路径中存在中文,会进行编码,因此需要解码
在这里插入图片描述

4.3 实现容器

使用单例模式,这里倾向于使用饿汉模式。因为饿汉模式更简单,且我们需要容器在启动时初始化,方便后续使用。
注意:反射可以突破private的构造函数的限制。
解决:枚举。无视反射的攻击。枚举的构造函数本身就是私有的,外界无法访问。此外序列化和反序列化攻击也能被抵挡,也就是将实例序列化,再反序列化。阅读ObjectInputStream的readObject源码。

public class EnumStarvingSingleton {
    private EnumStarvingSingleton(){}
    public static EnumStarvingSingleton getInstance() {
        return ContainerHolder.HOLDER.instance;
    }
    private enum ContainerHolder{
        HOLDER;
        private EnumStarvingSingleton instance;
        ContainerHolder(){
            instance = new EnumStarvingSingleton();
        }
    }
}

容器的组成部分:
保存Class对象及其实例的载体,使用ConcurrentHashMap
容器的加载,
1、配置的管理和获取
2、获取指定范围内的Class对象
3、依据配置提取Class对象,连同实例一并存入容器
容器的操作方式
增加、删除操作
根据Class获取对应实例
获取所有的Class和实例
通过注解来获取被注解标注的Class
通过超类获取对应子类Class
获取容器载体保存Class数量
clazz.isAssignableFro(clazz1)方法,判断传入的类是否是clazz的实现类或子类或本身。

4.4 依赖注入

目前容器里管理的Bean实例仍可能是不完备的,里面某些成员变量还没有被创建出来。
实现思路:
定义相关的注解标签
实现创建被注解标记的成员变量实例,并将其注入到成员变量里
依赖注入的使用

5 AOP的实现

切面Aspect:将横切关注点逻辑进行模块化封装的实体对象
通知Advice:好比是Class里面的方法,还定义了织入逻辑的时机
连接点JoinPoint:允许使用Advice的地方
SpringAOP默认只支持方法级别的JoinPoint
切入点PointCut:定义一些列规则对JoinPoint进行筛选
目标对象Target:符合PointCut条件,要被织入横切逻辑的对象

Advice的种类:
BeforeAdvice:在JoinPoint前被执行的Advice
AfterAdice:类似finally块
AfterReturningAdvice:在JoinPoint执行流程正常返回后被执行
AfterThrowingAdvice:JpintPoint执行过程中抛出异常才会触发
AroundAdvice:在JoinPoint前和后都执行,最常用的Advice
单个Aspect的执行顺序:
在这里插入图片描述
多个Aspect的执行顺序:
在这里插入图片描述
Introduction——引入型Advice
为目标类引入新接口,不需要目标类做任何实现
使得目标类在使用的过程中转型成新接口对象,调用新接口的方法

ClassLoader
1、通过带有包名的类来获取对应class文件的二进制字节流
2、根据读取的字节流,将代表的静态存储结构转化为运行时数据结构
3、生成一个代表该类的Class对象,作为方法区该类的数据访问入口
根据一定规则改定或者生成新的字节流,将切面逻辑织入其中——动态代理机制

5.1 AOP 1.0

使用CGLIB来实现:不需要业务类实现接口,相对灵活。三步完成AOP1.0:
第一步、解决标记的问题,定义横切逻辑的骨架
1、定义与横切逻辑相关的注解
  创建@Aspect标签,放在横切逻辑类上,表示这个类将来要织入到其他目标类中。
  创建@Order标签,因为可能有不同的Aspect类放在一个类,因此需要定义织入顺序。
2、定义供外部使用的横切逻辑骨架
  创建DefaultAspect抽象类,里面有before,afterThrowing,afterReturning方法,作为逻辑骨架。这样,Aspect的类只需要继承这个抽象类,选择性地实现这三个方法,就能实现对应逻辑的织入。每个Aspect类都需要继承DefaultAspect类。
第二步、定义Aspect横切逻辑以及被代理方法的执行顺序
1、创建MethodInterceptor的实现类,对应多个Aspect,最终只调用一次invokeSuper方法。先升序执行各个Aspect的before方法,再逆序执行各个Aspect的after…方法。新建AspectListExecutor类。新建AspectInfo类,封装DefaultAspect和orderIndex两个成员变量。
2、定义必要的成员变量——被代理的类以及Aspect列表
3、按照Order对Aspect进行排序
4、实现对横切逻辑以及被代理对象方法的定序执行
第三步、将横切逻辑织入到被代理的对象以生成动态代理对象
ProxyCreator类,创建代理对象。
AspectWeaver类,里面的doAop方法能够实现所有Aspect类的织入。

5.2 AOP 2.0

使用最小的改造成本,换取尽可能大的收益
SpringAOP的发展史
1、SpringAOP1.0是由Spring自研的,使用起来不是很方便,需要实现各种接口,继承指定类
2、SpringAOP2.0继承了AspectJ,复用AspectJ语法树,仅仅用AspectJ的切面语法,并没有使用ajc编译工具,避免增加学习成本,织入机制沿用JDK和CGLIB动态代理机制
AspectJ框架
提供了完整的AOP解决方案,是AOP的Java实现版本。
1、定义切面语法以及切面语法的解析机制
2、提供了强大的织入工具
在这里插入图片描述
AspectJ框架的织入时机:
编译时织入:利用ajc,将切面逻辑织入到类里生成class文件(静态)
编译后织入:利用ajc,修改javac编译出来的class文件(静态)
类加载期织入:利用java agent,在类加载的时候织入切面逻辑(LTW)

引入AspectJ
1、让Pointcut更加灵活
2、只需要引入AspectJ的切面表达式和相关的定位解析机制
修改@Aspect,value删去,改成String类型的pointcut。
创建PointcutLocator类,成员变量pointcutParser是pointcut解析器,pointcutExpression是表达式解析器。roughMatches方法判断被代理的类是否是该Aspect的目标类,accurateMatches方法判断该方法是否是该Aspect的目标方法。为每个Aspect类创建PointcutLoactor。

6 MVC的实现

MVC模块。
DispatcherServlet获取HTTP请求和发送HTTP响应。在获得HTTP请求后,委托给RequestProcessorChain处理,为了实现简单,只处理GET和POST请求。
在这里插入图片描述

DispatcherServlet
1、解析请求路径和请求方法
2、依赖容器,建立并维护Controller方法与请求的映射
3、用合适的Controller方法去处理特定的请求
/*优先级最高,会处理所有请求;/优先级低。

RequestProcessor

ControllerRequestProcessor

1、针对特定请求,选择匹配的Controller方法进行处理
2、解析请求里的参数及其对应的值,并赋值给Controller方法的参数
3、选择合适的Render,为渲染做准备

ResultRender

对于JSON,使用Gson包,是谷歌公司的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值