提升核心竞争力:
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包,是谷歌公司的。