1.0和1.1

HTTP/1.1相比HTTP/1.0带来了多项改进:

首先,HTTP/1.1默认启用长连接,减少了TCP握手的开销,这对于频繁交互的应用尤其重要。

其次,在缓存控制方面,HTTP/1.1提供了更精细的Cache-Control头部,使得缓存管理更加灵活。

第三 HTTP/1.1 通过引入Host头部,支持虚拟主机,这使得多个网站可以共享同一服务器资源,降低了成本。

第四在错误处理方面,HTTP/1.1引入了更详细的错误状态码和描述,便于开发者定位和解决问题。

这些改进使得HTTP/1.1 的网络通信得到了极大的提升。

但是 HTTP 1.1 虽然有了很多改进,但是这并不是终点,因为 HTTP 1.1 依旧有很多缺点。

最显著的缺点就是请求阻塞,也就是当一个请求在等待服务器响应时,后续请求必须等待,即使它们与前面的请求无关。这导致了队头阻塞,降低了性能。

此外还有头部数据量太大,高并发下长连接容易成为性能瓶颈,并且 HTTP 1.1 是明文传输,不支持服务端主动发送数据等缺点。

所以在后续又引入了 HTTP 2.0,而 HTTP 2.0 则是进一步引入了多路复用,头部压缩 和服务器推送等功能,解决了 HTTP 1.1 的缺点。

1.1和2

HTTP/2.0相比HTTP/1.1带来了多项性能优化和效率提升。

首先,HTTP/2.0支持多路复用,允许在单个连接中同时发送多个请求和响应,解决了HTTP/1.1中的队头阻塞问题。

其次,HTTP/2.0使用HPACK算法压缩请求和响应的头部,减少了冗余数据,提高了传输效率。

第三,HTTP/2.0引入了二进制分帧层,将HTTP消息分解为独立的帧,提高了数据传输的效率和解析速度。

第四,HTTP/2.0还允许服务器主动向客户端推送资源,减少了客户端需要发送的请求数量,提高了页面加载速度。

最后,HTTP/2.0要求使用TLS进行加密传输,增强了安全性,防止了中间人攻击。

这些改进使得HTTP/2.0成为现代网络应用的首选协议。

HTTP/2.0虽大幅提升了性能,但仍存在依赖TCP导致的建连延迟和队头阻塞问题,且头部压缩复杂、明文传输风险未根除。

而HTTP/3.0基于QUIC协议,运行在UDP上,实现更快的建连和彻底解决队头阻塞,采用更高效的QPACK头部压缩,并强制TLS加密,全面提升了传输效率和安全性。

2和3

HTTP/2.0 和 HTTP/3.0 的区别,主要集中在底层协议、并行传输能力、握手效率、头部压缩、网络适应性等层面。

HTTP/2.0 是基于 TCP 的,一旦某个数据包丢失就必须等重传,这会造成整个连接里的所有数据流都被阻塞,也就是所谓的队头阻塞问题。HTTP/3.0 用的是基于 UDP 的 QUIC 协议,可以把每个数据流都独立分配 ID,只要某个流丢包,就只重传那个流,不会拖慢其它流,这在高丢包或弱网环境下会表现得更好。

HTTP/2.0 需要先完成 TCP 三次握手,然后再进行 TLS 握手,如果要恢复会话,也会多一次 RTT。而 QUIC 把传输层和加密层放在一起,相当于只需要一次握手就能建立安全连接,比 HTTP/2.0 快了大约一半。更进一步,QUIC 还支持 0-RTT 恢复,如果客户端保存过相关密钥信息,重连时几乎可以立刻发送数据,尤其在网络波动或需要频繁断网重连的场景下非常有效。不过,要注意 0-RTT 可能带来重放攻击风险,服务端通常要配合额外的安全逻辑做校验。

HTTP/2.0 使用 HPACK 来压缩头部,但它依赖顺序传输,对网络乱序的容忍度不高。而 HTTP/3.0 则是用 QPACK,在无序或丢包的条件下也能更好地解码头部,减少了因为乱序导致的性能损耗。

得益于QUIC使得HTTP/3.0在网络适应性上也更突出,它通过 Connection ID 来标识连接,这意味着用户从 Wi-Fi 切到蜂窝网络时,只要保持同一个 Connection ID,连接就能平滑迁移,不用重新握手下载证书。移动端在遇到丢包高或延迟大的情况时,QUIC 的拥塞控制也能更适配弱网环境,实现更流畅的体验。

总而言之,HTTP/3.0 的确代表了未来的发展趋势,但如何在实际项目里落地,依然要根据网络环境和服务端能力做通盘考虑。如果是固定网络而且丢包率不高,现有的 HTTP/2.0 完全能满足需求。如果业务对实时性要求很高,用户网络情况又比较复杂,比如移动端场景、大规模跨区访问或者弱网环境,HTTP/3.0 可以显著改善访问体验。不过在企业内网或对兼容性要求极高的场合,最好采用双协议或者逐步升级的方案,避免因为 UDP 流量受限或中间设备不兼容而导致服务不可用。

http和https

HTTP与HTTPS的主要区别在于安全性和数据传输的可靠性

HTTP使用明文传输数据,容易受到中间人攻击,数据可能被窃听或篡改。而HTTPS在HTTP基础上增加了TLS协议,对数据进行加密传输,确保了数据的安全性和完整性,需要使用数字证书来验证服务器身份

虽然HTTPS的加密和解密过程会增加一定的延迟,但现代硬件和优化技术已大幅减少了这种影响。此外,HTTPS有助于提高搜索引擎排名,浏览器显示安全锁标志,增强用户信任。

在实际应用中,尤其是在处理敏感信息如登录凭据、支付信息的场景下,HTTPS是必不可少的。通过使用HTTP/2.0和优化TLS配置,可以进一步减少性能开销。

随着互联网安全重要性的提升,HTTPS已成为现代网络应用的标配。

简述

安全,安全,还是 TMD 安全;1

HTTPS相比HTTP,在通信步骤中显著增加了TLS握手、证书验证和密钥交换等关键步骤。

具体而言,HTTPS在建立TCP连接后,首先进行TLS握手,客户端发送支持的TLS版本、加密方法和随机数,服务器回应并附上服务器证书;

接着,客户端验证证书合法性,确保通信对方可信;

随后,双方协商生成会话密钥,用于后续数据的加密传输。

这些额外步骤不仅确保了数据的安全性和完整性,还验证了通信双方的身份,显著提升了安全性。

OSI七层模型

TCP/IP

应用层:

我们常见的应用就是在应用层,应用层也有应用层协议,如HTTP、DNS、FTP、Telent、SMTP等

应用层不关注数据如何传输,直接把数据的传输交给下一层,即交给传输层,应用层工作于用户态,传输层及以下工作于内核态

为计算机用户服务。应用层工作都是用户态,而传输层及以下的工作则涉及到了内核态

传输层:

为应用层提供网络通信支持,但不涉及网络传输,分为 TCP 和 UDP 两个协议

应用层会把数据包传输给传输层,故传输层是给应用层提高网络支持的,注意不是支持两台设备之间的网络传输。两个重要协议,TCP(传输通知协议),UDP(基于报文传输协议)。因为应用层传过来的数据可能比较大,如果直接传不太好,如果超过MSS(TCP最大报文段长度)就会进行分段。TCP段里包含端口,用于标明传输给应用层哪个应用,接受方的端口是我们指定的,而客户端是随机分配的

网络层:

负责网络包的封装、分片、路由、转发,如IP、ICMP

负责两设备之间的网络传输。最常用的协议就是IP协议,负责寻址,告诉数据包应该往哪里走,而

网络接口层:

负责网络包在物理网络中的传输,比如网络包的封帧、MAC寻址、差错检验等

真正的物理层次传输数据

为网络层提供[链路级别]的传输,负责在WIFI,以太网这样的底层网络上发送原始数据包。

MAC地址为在以太网中寻址提供基础

每层对数据的封装

为什么要分层

  1. 各层相互独立,只关心本层的问题,比如 我们的MVC三层架构
  2. 高内聚低耦合
  3. 功能分解,大问题化为小问题

Bufferpool是什么?

就是MySQL在启动时向操作系统申请的一片连续的内存空间,用来提升数据库的读写性能,里面存的是一

页一页的数据。

默认大小为128mb,可以通过<font style="color:rgb(71, 101, 130);">innodb_buffer_pool_size</font> 设置,一般建议设置为物理内存的60-70%

buffer pool缓存的什么

每个缓存页还会由一个控制块来管理

如何管理buffer pool

Bufferpool和query cache的区别

  • Bufferpool是InnoDB特有的,而query Cache是server层的
  • 目的不同,Buffer pool主要是用来缓存表和索引的数据页,从而加速读取操作;而query Cache用于缓存查询结果,减少重复查询的执行时间的

Bufferpool的读写过程

读:比较简单,bufferpool里有返回,没有就去磁盘里拉取

写:直接写bufferpool,刷盘是由后台线程来执行的。异步刷新策略

bufferpool里的脏页数据什么时候刷到磁盘里

为什么mysql全表扫描时不会污染Bufferpool

解决思想

提供一个另外思考的视角,Spring 解决循环依赖的做法是 【图论环检测】 的经典实践。 依赖关系可以表示为一个有向图,Cat -> Dog -> Cat 的依赖关系可以表示为一个【有向图】 ,Bean是怕【顶点】, 依赖关系是【边】。Spring在createBean时会递归构建依赖图 (深度优先遍历DFS) , 如果发现当前路径中某个Bean正在创建中(即存在“正在创建”的顶点被重复访问),则判定存在环 【环检测】 。简化做法是采用二级缓存来获取 Cat 的引用 【在图中引入一个虚拟顶点】, 将环拆解为链式依赖 ,从而避免了无限递归。当所有依赖注入完成,再将真实顶点替换虚拟顶点。故视频中的loadingIoc缓存是用于存放虚拟顶点进行环检测的集合容器,ioc是存放真实顶点的集合容器。

Spring的落地实践解决循环依赖

Spring中存在三层缓存,就对于单例对象来说

  • 第一层缓存:单例对象缓存池,已经实例化并且属性赋值,这里的对象是成熟对象
  • 第二层缓存:单例对象缓存池,已经实例化但还没进行属性set,这里的对象是半成品对象
  • 第三层缓存:单例工厂的缓存,里面存放的是工厂对象,实际上是一串lambda表达式,每个工厂都可以创建对应的bean,eg:private Map<String, Supplier> singletonFactories = new HashMap<>();

    Spring生成Bean的流程

    假设A和B 发生了循环依赖

    起点:先初始化A

    1. 先往creatingSet集合(一个set集合,用于存放正在被创建的bean,便于后续判断是否发生了循环依赖)里放入A
    2. 一级缓存找不到A,那么就通过无参构造化实例化A,将和A有关的一串lambda表达式放入三级缓存(此时A的实例化和属性注入就已经分离开了),set属性注入 B
    3. creatingSet里找不到B,此时还不知道出现了循环依赖,就去一级缓存找B,找不到,就往creatingSet里找B。通过无参构造实例化B,将和B有关的一串lambda表达式放入三级缓存,进行set属性注入A
    4. 通过creatingSet发现了A,就知道发生了循环依赖,然后这时候就会去二级缓存寻找,没找到,去三级缓存找,找到有关A的lambda表达式,执行lambda
      • 如果A需要被AOP,则此时提前对其进行AOP(正常的bean周期应该是初始化后才AOP的),将AOP的代理对象放入二级缓存,并从三级缓存中删除(这样能保证你AOP出来的代理对象是单例的,也就是说你这个lambda表达式是一次性的,同时是会加锁的保证线程安全),并将代理对象放入earlyProxyReference(用于存储哪些对象提前AOP了,后续有用)
      • 如果A不需要AOP,则生成A的普通对象并放入二级缓存,并从三级缓存中删除
    5. OK此时B是已经拿到对象A了(可能是proxy的,也可能是普通的)进行set注入,进行B的其他Bean的声明周期,当Bean成熟之后放入一级缓存,移除二、三级缓存(实际上此时二级缓存中没有B,三级缓存中有B)
    6. 此时返回A的初始化过程,A就拿到了B的成熟对象,set注入A中。然后进行A的其他Bean的声明周期
    7. A的其他生命周期包括AOP(是在BeanPostProcessor的after方法执行的),但这时候A可能在lambda表达式那里就提前AOP了,这就需要通过earlyProxyReference判断A是否提前AOP了,即A存不存在。防止重复AOP
    8. 将成熟的A放入1缓存,同时删除2,3缓存(实际此时三级缓存已经没有A了,在第4步已经被移除了)

    如果A和B发生了循环依赖,然后发现A和C又发生了循环依赖

    A和B走完流程要注入C,此时二级缓存其实是有A了,C就直接从二级缓存里拿A set就好了,重复A和B的流

    各级缓存的作用:

    1. 一级缓存就是用来保存Bean单例的,也就是我们getBean的源头
    2. 二级缓存用来保证Bean是单例的。因为你三级缓存lambda是一次性消耗品,因此只会生成一份Bean。那么这个生成的半成品Bean就先放入二级缓存。用于存放提前曝光的不成熟的bean
    3. 三级缓存则是用来打破循环依赖并出现AOP的情况下的循环依赖的。当发生循环依赖时,可以保证你一定是能从三级缓存获取到Bean的(因为三级缓存都是会在Bean实例化同时存入lambda表达式的)。通过三级缓存,我们可以生成半成品的普通Bean或者proxybean。然后再放入二级缓存,然后把三级缓存删掉,这样能保证Bean是单例的

    一级缓存能解决循环依赖吗?

    可以解决普通的循环依赖。但把半成品和完全品都放入一个集合里不是很优雅。AOP的循环依赖没法解决

    可不可以使用一、二级缓存解决循环依赖?

    可以,但这样如果出现AOP的话,在对象实例化时就要完成AOP代理,生成代理对象,再进行属性注入,即先创建代理对象,再对代理对象进行属性注入,这与Spring设计理念中“将Bean的初始化和代理解耦”是不符的

    那么就可以通过三级缓存中的lambda判断,只有当出现AOP + 循环依赖的时候,才提前创建代理对象,然后进行依赖注入初始化,否则创建普通对象,走正常的Bean生命周期

    可不可以使用一、三级缓存解决循环依赖?

    不可以。因为你用三级缓存这套东西的话就说明你的bean是单例的,也就是每次产出半成品都会删除。那这

    样的话,你每次都会重新去放入lambda表达式,每次出来的半成品都不是同一个。当出现A与B发生循环依

    赖,A与C也发生循环依赖的这种情况,B中的A和C中的A就不是同一个了。必须让三级缓存是单例的,且半

    成品对象必须得找个地方存,这个地方就是二级缓存

    Spring如何解决循环依赖?

    creatingSet(判断是否存在循环依赖)+三级缓存(解决循环依赖问题)+

    earlyProxyReference(防止解决循环依赖的过程中重复AOP)

    哪些循环依赖默认情况下无法解决?

    1. 构造注入下的循环依赖,因为构造注入是要求强制注入的
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @Component
    class A {
    private final B b;

    @Autowired
    A(B b) { // 构造器注入
    this.b = b;
    }
    }

    如果没有 B,A a = new A(null); 都过不去(final 成员必须赋值)。

    👉 在 对象创建的一瞬间 就要保证依赖存在。

    没有任何缓冲空间,所以是 强制注入。
    1. 两个都是多例对象,因为多例对象根本就没三级缓存这一说,每次getbean都会执行一次构造方法并给属性赋值

    其它循环依赖如何解决:

    @Lazy:

    是什么

    AOP即面向切面编程,是一种思想,是对OOP的补充。简单的来说就是把公共的逻辑抽离出来,

    让开发者可以跟关注于业务逻辑的开发

    说简单点AOP就是 在不惊动原有的代码的基础上对功能进行增强操作

    概念:

    连接点:JIONPoint,可以被AOP控制的方法(运行时刻被拦截到的方法

    通知:advice 增强,切面的具体行为

    切入点:决定在哪些方法上生效

    切面:描述切入点和通知的关系

    通知类型:

    @Before

    @After

    @AfterReturning

    @AfterThrowing 在抛错之后

    @Aroud 环绕通知:在目标方法执行之前和之后都能插入逻辑,甚至可以决定是否执行目标方法。

    总结:

    AOP的底层实现方法

    一共有三种吧,常见的像代理,还有AspectJ框架提供的两种方法

    第一种就是AJC增强,它就是指AJC编译器在编译过程中把通知的增强功能植入目标类的Class文件中

    来达到增强的效果,然后它的执行时机就是通知类上有Aspect注解,不需要component注解,该通知类

    就没有被Spring所管理。所有AOP的执行底层是通过AJC去修改Class文件,而不是动态代理,这时候没有

    Spring容器也可以进行aop增强,但需要用到maven一个插件,这种方式用得很少

    第二宗就是通过agent底层来实现aop,它是agent编译器在加载目标类的时候,因为是在加载类的时候么,

    Class文件已经编译完了,它就在加载目标类的时候修改对应的Class文件,达到增强的功能。它的执行时机

    跟AJC编译差不多的,也是通知类上有Aspect注解,不需要component注解。区别就是给JVM加一个参数 -

    javaagent,后面指定maven目录下的一个jar包路径

    第三种就是我们常用的代理增强了。代理的本质就是生成一个代理对象,类似于房东和中介的,中介就是代理

    这个代理对象和切入点有着相同的方法,然后代理对象的方法就是根据需求增强过的,然后调用切入点的方法

    不走它的方法,而是走代理对象在增强过的方法。而怎么保证代理对象和切入点有着相同的方法呢,这就分为

    两派,一种是jdk代理,一种是cglib代理。

    jdk代理是Java自带的代理,主要是采用了多态和反射的方式。它是代理对象和目标类实现同一个接口,就可

    以保证两者都有同样的方法

    jdk代理通过Proxy.newProxyInstance()获取到代理对象

    newProxyInstance方法的三个参数:

    第一个参数,类加载器

    第二个参数,接口集合,因为jdk代理需要目标对象实现接口

    第三个参数,一个函数式接口,实现增强方法逻辑,这个函数式接口也有三个参数:

    第一个参数,目标对象

    第二个参数,需要被增强的方法

    第三个参数,方法执行的参数

    proxy.newProxyInstance()方法底层会根据传入的接口集合,在运行时在本类目录下生成一个$proxy0类

    实现这些方法,然后里面的所有方法都会被InvocationHandler方法给拦截下来进行增强(包括equals和hashcode)

    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
    public final class $Proxy0 extends Proxy implements Test { 
    private static Method m1;
    private static Method m2;
    private static Method m3;

    // $Proxy0 类的构造方法
    // 参数为 invocationHandler
    public $Proxy0(InvocationHandler invocationHandler) {
    super(invocationHandler);
    }

    static {
    m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
    m2 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
    m3 = Class.forName("com.xxx.Test").getMethod("test", new Class[0]);
    }

    public final void test() {
    this.h.invoke(this, m3, null);
    return;
    }

    public final int hashCode() {
    return this.h.invoke(this, m2, null);
    }

    public final boolean equals(Object o) {
    return this.h.invoke(this, m1, new Object[]{o});
    }
    }

    cglib代理不是jdk自带的代理,需要导外部依赖,其底层是让代理类继承目标类,通过Enhancer.create获取代理对象

    create方法的两个参数:

    第一个参数:目标类

    第二个参数:一个函数式接口,实现增强方法逻辑,这个函数式接口有四个参数:

    第一个参数,代理对象自己

    第二个参数,需要被增强的方法

    第三个参数,方法执行的参数

    第四个参数,MethodProxy,代理不走反射的关键

    其实AOP的重点是如何获取目标类的原始方法,并把它放到特定的上下文环境下执行

    对于这一点,jdk代理采用的是反射,你看,我都采用反射了,我还拿不到你原始方法吗

    而cglib采用的就是继承,我都继承了,我肯定能通过super.xx拿到你的原始方法

    还有就是,代理模式则是在运行期生成新的字节码。AspectJ对代码的侵入性很强,因此没有怎么流行 但因为它是在编译器和类加载期修改字节码,故性能较高。且能突破代理只能重写方法来实现增强的限 制,如能增强构造方法,静态方法,final修饰的方法

    Spring的AOP是怎么实现的

    Spring的AOP是采用动态代理实现的

    至于是jdk还是cglib,Spring容器代理时,会有一个proxyBean类,这个类会传入目标对象和通知

    ,然后这个类还有一个boolean proxyTargetClass,如果设置为false,那就会去检查目标类是否实现了

    接口,有就用jdk,无就用cglib。如果为true,那就直接用cglib

    一个方法被多个切面AOP了?

    需要给切面标明@Order(x)x越小,说明你那个优先级越高。

    如果无标明,那就会按注册的顺序来执行,但此时是随机的,不可知的

    因为 Spring AOP 内部会把所有的切面包装成 Advisor,然后放到一个集合里。虽然这个集合是个List,但

    虽然用的是 ArrayList(顺序表),但插入顺序本身没保证稳定,因为:

    - 不同 BeanFactory 扫描/注册 Bean 的顺序可能不同。
    - 不同 JDK、不同文件系统,class 扫描顺序也可能不同。
    - 所以“默认顺序”没有规范,才会被说成是 **不可知的**。
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    @Aspect
    @Component
    public class LogAspect {
    @Before("execution(* com.example.service.*.*(..))")
    public void before() {
    System.out.println("LogAspect before");
    }
    }

    @Aspect
    @Component
    public class SecurityAspect {
    @Before("execution(* com.example.service.*.*(..))")
    public void before() {
    System.out.println("SecurityAspect before");
    }
    }
    //执行顺序不一定的



    AOP失效?

    当然这只限于代理类的

    类中自调用方法,同一个类中A调用B方法,B方法的AOP不会生效

    调用内部类方法

    调用静态方法,静态方法不会被AOP

    调用final方法,final方法不会被AOP

    目标类不符合规则,如jdk代理时没继承接口,cglib代理时是final类

    cglib 和jdk代理哪个快

    在 JDK1.6、JDK1.7、JDK1.8 逐步对 JDK 动态代理优化之后,在调用次数较少的情况下,JDK 代理效率高于 CGLib 代理效率,只有当进行大量调用的时候,JDK1.6 和 JDK1.7 比 CGLib 代理效率低一点,但是到 JDK1.8 的时候,JDK 代理效率高于 CGLib 代理。所以如果有接口使用 JDK 动态代理,如果没有接口使用 CGLIB 代理。

    Spring容器启动流程:https://javadoop.com/post/spring-ioc

    控制反转,即将我们的对象控制权交给容器

    ioc 的启动流程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    init阶段:

    scanPackage -> scanCreate(判断是否加了@component注解) -> wrapper 成 beanDefinnation(name)

    create所有非Lazy的 bean阶段:
    这时候涉及为一二级缓存:1级存放所有初始化好的bean,而2级存放所有半初始化好的bean 即创建好但依赖的组件没注入的bean
    通过反射 构造方法去create Bean
    执行一些加载前的
    执行一些加载后的

    手写IOC

    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
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    package com.lkl;

    import java.io.File;
    import java.io.IOException;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.net.URI;
    import java.net.URL;
    import java.nio.file.*;
    import java.nio.file.attribute.BasicFileAttributes;
    import java.util.*;
    import java.util.stream.Collectors;

    /**
    * @author: lkl
    * @date: 2025/4/6 21:03
    */
    public class ApplicationContext {
    public ApplicationContext(String packageName) throws Exception {
    initContext(packageName);
    }
    //一级缓存:存放完全初始化的bean
    private HashMap<String,Object> ioc=new HashMap<>();
    //二级缓存:存放还没初始化好的bean,但已经实例化了
    private HashMap<String,Object> loadingIoc=new HashMap<>();
    private HashMap<String,BeanDefinition> beanDefinitionHashMap=new HashMap<>();
    protected Object createBean(BeanDefinition beanDefinition) {
    String beanName = beanDefinition.getName();

    if (ioc.containsKey(beanName)){
    return ioc.get(beanName);
    }
    if (loadingIoc.containsKey(beanName)){
    return loadingIoc.get(beanName);
    }
    //一,二级缓存没有的话,才会去创建bean
    return doCreateBean(beanDefinition);
    }

    private Object doCreateBean(BeanDefinition beanDefinition) {
    String name = beanDefinition.getName();
    Constructor<?> constructor = beanDefinition.getConstructor();
    //先默认是无参的
    Object bean = null;
    try {
    bean = constructor.newInstance();
    loadingIoc.put(name,bean);
    //postProcess

    //依赖注入
    autowiredBean(bean,beanDefinition);

    //afterProcess


    //执行postConstructMethod
    Method postConstructMethod = beanDefinition.getPostConstructMethod();
    if (postConstructMethod!=null) {
    postConstructMethod.invoke(bean);
    }
    } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
    throw new RuntimeException(e);
    }
    ioc.put(name,loadingIoc.remove(name));
    return bean;
    }

    private void autowiredBean(Object bean, BeanDefinition beanDefinition) throws IllegalAccessException {
    for (Field filed : beanDefinition.getAutowiredFields()) {
    //通过类型 也可以通过名字
    filed.setAccessible(true);
    filed.set(bean,getBean(filed.getType()));
    }
    }

    protected BeanDefinition wrapper(Class<?> type) {
    BeanDefinition beanDefinition = new BeanDefinition(type);
    if (beanDefinitionHashMap.containsKey(beanDefinition.getName())){
    throw new RuntimeException("bean name重复----------");
    }
    beanDefinitionHashMap.put(beanDefinition.getName(),beanDefinition);
    return beanDefinition;
    }

    protected void initContext(String packageName) throws Exception {
    //先把所有的BeanDefinition加载
    scanPackage(packageName).stream()
    .filter(this::scanCreate)
    .forEach(this::wrapper);
    //再加载bean
    beanDefinitionHashMap.values()
    .forEach(this::createBean);
    }
    protected boolean scanCreate(Class<?> type){
    return type.isAnnotationPresent(Component.class);
    }
    //扫包遍历
    private List<Class<?>> scanPackage(String packageName) throws Exception {
    System.out.println("start scan package -------------");
    URL resource = this.getClass().getClassLoader().getResource(packageName.replace(".", File.separator));
    Path path = Paths.get(resource.toURI());
    List<Class<?>> classList=new ArrayList<>();
    Files.walkFileTree(path, new SimpleFileVisitor<>() {
    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
    Path absolutePath = file.toAbsolutePath();
    if (absolutePath.toString().endsWith(".class")) {
    String replaceStr = absolutePath.toString().replace(File.separator, ".");
    String className = replaceStr.substring(replaceStr.indexOf(packageName), replaceStr.length()-".class".length());
    try {
    classList.add(Class.forName(className));
    } catch (ClassNotFoundException e) {
    throw new RuntimeException(e);
    }
    //System.out.println(className);
    }
    return FileVisitResult.CONTINUE;
    }
    });

    return classList;
    }
    //根据名字返回bean
    public Object getBean(String name) {
    if (name==null) {
    return null;
    }
    Object bean = this.ioc.get(name);
    if (bean!=null) {
    return bean;
    }
    if (beanDefinitionHashMap.containsKey(name)){
    return createBean(beanDefinitionHashMap.get(name));
    }
    return null;
    }
    //返回类型相同的bean
    public <T> T getBean(Class<T> beanType) {
    String beanName = beanDefinitionHashMap.values().stream()
    .filter(a -> beanType.isAssignableFrom(a.getType()))
    .map(BeanDefinition::getName)
    .findFirst()
    .orElse(null);
    return (T)getBean(beanName);

    }

    public <T> List<T> getBeans(Class<T> beanType) {
    return beanDefinitionHashMap.values().stream()
    .filter(a -> beanType.isAssignableFrom(a.getType()))
    .map(BeanDefinition::getName)
    .map(this::getBean)
    .map(bean->(T)bean)
    .collect(Collectors.toList());

    }
    }

    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
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    package com.lkl;

    import com.lkl.sub.AutoWired;
    import com.lkl.sub.PostConstruct;

    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    import java.util.Arrays;
    import java.util.List;
    import java.util.*;
    import java.util.stream.Collectors;

    /**
    * @author: lkl
    * @date: 2025/4/6 21:21
    */
    public class BeanDefinition {
    private final String name;
    private final Constructor<?> constructor;
    private final Method postConstructMethod;
    private final List<Field> autowiredFields;
    private final Class<?> type;
    public BeanDefinition(Class<?> type){
    this.type=type;
    Component annotation = type.getDeclaredAnnotation(Component.class);
    this.name=annotation.name().isEmpty()? type.getName() : annotation.name();
    try {

    this.constructor=type.getConstructor();
    this.postConstructMethod= Arrays.stream(type.getDeclaredMethods())
    .filter(a->a.isAnnotationPresent(PostConstruct.class))
    .findFirst()
    .orElse(null);
    this.autowiredFields = Arrays.stream(type.getDeclaredFields())
    .filter(a->a.isAnnotationPresent(AutoWired.class))
    .toList();
    } catch (NoSuchMethodException e) {
    throw new RuntimeException(e);
    }


    }
    public String getName(){
    return name;
    }
    public Constructor<?> getConstructor(){
    return constructor;
    }

    public Method getPostConstructMethod() {
    return postConstructMethod;
    }

    public List<Field> getAutowiredFields() {
    return autowiredFields;
    }

    public Class<?> getType() {
    return type;
    }

    }

    两个重试机制

    1.定时脚本从数据库中捞失败的任务重新执行。因为失败是偶然的,所以我们在建表的时候在task表里基于

    status字段加了索引,且还会有一个count字段。达到Config里某一次数即会进行一个告警

    2.调度模块重试上一分钟的任务分片,这里的设计就是比较巧妙了。

    我们调度模块打捞上来的任务分片就会交给触发器从一个zset里每隔1s就会去 zrange,拿到里面的任务,然后交给执行器去触发。如果执行器执行成功了,就会延长分布式锁的时间到2min多一点,保证不会被重复执行。如果执行失败的话,就是不会去延长分布式锁的,下一分钟就会去重试上一分钟

    会有重复执行的问题吗?

    目前是有可能的,我们这个项目只能做到至少执行一次,因为这是一个分布式事务的问题。就比如说当我们回

    调业务方时,因为网络原因,我们这边以为回调失败了,但其实回调成功了。那这个业务得去做下幂等处理,

    比如说回调时把taskid传过去,业务方执行前做下查重