(未完待续)【Netty专题】Netty实战与核心组件详解

news2024/11/15 13:48:54

目录

  • 前言
  • 阅读对象
  • 阅读导航
  • 前置知识
  • 课程内容
    • 一、Netty简介
      • 1.1 Netty是什么
      • 1.2 Netty有什么优势
    • 二、第一个Netty程序
      • 2.1 Netty简单使用示例
      • 2.2 代码解读
      • 2.3 Netty的特性
        • 2.3.1 Netty的事件
      • 2.4 Netty线程模型
    • 三、Netty核心组件详解(未完待续)
      • 3.1 EventLoopGroup和EventLoop
        • 3.1.1 EventLoop
        • 3.1.2 NioEventLoopGroup
  • 学习总结
  • 感谢

前言

Netty啊,真的是大名鼎鼎!Netty之于Java网络编程,相当于Spring之于Java开发。两者都是Java生态的金字塔尖的【框架】!所以,非常推荐Java程序员学习这个框架。

Netty有多牛逼?据说,曾经在性能上把谷歌公司一个用C++写的网络通信框架都给人干碎了。后来,后者参照了Netty的设计方案,才完成了超越。

阅读对象

需要有一定的网络编程基础。如若没有,请务必要学习下【阅读导航】中提到的系列上一篇文章。

另外,如果你们了解【设计模式】中的【责任链模式】就更好了。因为在Netty的开发中,Handler使用了【责任链模式】的方式,将各个Handler链化起来。

而且很负责地告诉大家,好多优秀的Java源码,都有【责任链模式】的影子。所以,去学习吧,能帮助你阅读源码以及提升自己的编程技巧。传送门:《史上最全设计模式导学目录(完整版)》

阅读导航

系列上一篇文章:《【Netty专题】【网络编程】从OSI、TCP/IP网络模型开始到BIO、NIO(Netty前置知识)》

前置知识

Reactor模型(未完待续)

课程内容

一、Netty简介

1.1 Netty是什么

Netty是由 JBOSS 提供的一个Java开源网络通信框架。它是一个【异步事件驱动】的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。
也就是说,Netty 是一个基于NIO的客户、服务器端的编程通信框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户、服务端应用。Netty相当于简化和流线化了网络应用的编程开发过程,例如:基于TCP和UDP的socket服务开发。

上面有个2细节很重要:

  1. Netty是一个【异步事件驱动】的网络应用程序框架(异步,事件驱动,如何理解?)
  2. Netty 是一个基于NIO的客户、服务器端的编程通信框架。NIO,NIO,NIO,讲三遍
    (PS:有心的同学这个时候应该回忆以下,Java的NIO编程里面,有什么组件,或者细节来着?)

1.2 Netty有什么优势

相比传统Java Socket编程、Java NIO,Netty具有如下明显优势

  1. 提供了更高层次的封装,API使用简单,降低了网络编程开发门槛;

传统的NIO开发,你需要独自考虑、处理网络编程中遇到的一些常见问题。如:【断线重连】、【 网络闪断】、【心跳处理】、【粘包】、【半包读写】、【网络拥塞】和【异常流】

  1. 功能强大,预制了多种编解码功能, 支持多种主流的协议;

编解码:网络编程一定要处理的环节。因为数据在网络中传输是需要转换为二进制的,不可能是明文
支持的协议:传输层有TCP、UDP、本地传输;应用层有HTTP、WebSocket等

  1. 定制能力强,通过自定义的ChannelHandler实现更灵活的拓展
  2. 性能高,对比其他主流的NIO框架,Netty的综合性能更优
  3. 成熟、稳定。Netty目前基本没有大更新了,基本上已经修复了所有JDK NIO的BUG
  4. 社区活跃
  5. 已经经历了大规模商业应用的考验,质量有保证

二、第一个Netty程序

2.1 Netty简单使用示例

话不多说,我们先来简单使用一下,开始我们的第一个Netty程序,然后再一点一点推敲。
先导入pom:

<dependency>
	<groupId>io.netty</groupId>
	<artifactId>netty-all</artifactId>
	<version>4.1.42.Final </version>
	<scope>compile</scope>
</dependency>

然后引入服务端代码:

/**
 * Netty服务端
 *
 * @author zhangshen
 * @date 2023/10/21 14:52
 * @slogan 编码即学习,注释断语义
 **/
public class NettyServer {
    static final int PORT = 9999;
    public static void main(String[] args) throws InterruptedException {

        EventLoopGroup bossEventLoopGroup = new NioEventLoopGroup();
        EventLoopGroup wokerEventLoopGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossEventLoopGroup, wokerEventLoopGroup)
                    .channel(NioServerSocketChannel.class)
                    .localAddress(new InetSocketAddress(PORT))
                    .childHandler(new ChannelInitializer<SocketChannel>() {

                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new NettyServerHandler());
                        }
                    });
            System.out.println("Netty服务端正在启动...");

            // 异步绑定到服务器,sync()会阻塞到完成
            ChannelFuture channelFuture = bootstrap.bind().sync();

            // 对通道关闭进行监听,closeFuture是异步操作,监听通道关闭
            // 通过sync()同步等待通道关闭处理完毕,这里会阻塞等待通道关闭完成
            channelFuture.channel().closeFuture().sync();
        } finally {
            bossEventLoopGroup.shutdownGracefully().sync();
        }
    }
}


/**
 * Netty服务端,自定义handler
 *
 * @author zhangshen
 * @date 2023/10/21 15:01
 * @slogan 编码即学习,注释断语义
 **/
public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("Netty服务器:客户端连接已建立");
        super.channelActive(ctx);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

        ByteBuf in = (ByteBuf) msg;
        System.out.println("Netty服务器收到消息:" + in.toString(CharsetUtil.UTF_8));

        String responseMsg = "你好啊,Netty客户端";
        ByteBuf buf = Unpooled.copiedBuffer(responseMsg, CharsetUtil.UTF_8);
        ctx.writeAndFlush(buf);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        super.exceptionCaught(ctx, cause);
    }
}

接着是Netty客户端代码示例:


/**
 * Netty客户端代码示例
 *
 * @author zhangshen
 * @date 2023/10/21 15:05
 * @slogan 编码即学习,注释断语义
 **/
public class NettyClient {

    static final int NETTY_SERVER_PORT = 9999;

    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(eventLoopGroup)
                    .channel(NioSocketChannel.class)
                    .remoteAddress(new InetSocketAddress("127.0.0.1", NETTY_SERVER_PORT))
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new NettyClientHandler());
                        }
                    });

            // 异步连接到服务器,sync()会阻塞到完成,和服务器的不同点
            ChannelFuture channelFuture = bootstrap.connect().sync();

            // 阻塞当前线程,直到客户端的Channel被关闭
            channelFuture.channel().closeFuture().sync();
        } finally {
            eventLoopGroup.shutdownGracefully().sync();
        }
    }
}

/**
 * Netty客户端代码,自定义handler
 *
 * @author zhangshen
 * @date 2023/10/21 15:05
 * @slogan 编码即学习,注释断语义
 **/
public class NettyClientHandler extends SimpleChannelInboundHandler<ByteBuf> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
        System.out.println("客户端收到消息:" + msg.toString(CharsetUtil.UTF_8));
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        String msgAfterTCP = "你好啊,Netty服务器";
        ctx.writeAndFlush(Unpooled.copiedBuffer(msgAfterTCP, CharsetUtil.UTF_8));
        ctx.alloc().buffer();
    }
}

2.2 代码解读

我们先来简单总结下服务端NettyServer的流程:

  1. 先声明一个ServerBootstrap(Bootstrap的一种)、EventLoopGroup。服务端声明了2个EventLoopGroup,其实1个也可以。这个跟Reactor模式有关
  2. 初始化bootstrap。通过链式调用设置一些属性,比如:group()、localAddress()、childHandler()(其实这些方法,我们可以看成Java POJO中的setXxx() )。最核心的是新增了一个自定义的NettyClientHandler
  3. 然后bootstrap.bind()
  4. 监听通道关闭
  5. 在finally块内关闭EventLoopGroup

而客户端NettyClient呢,它的流程如下:

  1. 先声明一个Bootstrap、EventLoopGroup
  2. 初始化bootstrap。通过链式调用设置一些属性,比如:group()、channel()、remoteAddress()、handler()(其实这些方法,我们可以看成Java POJO中的setXxx() )。最核心的是新增了一个自定义的NettyServerHandler
  3. 然后bootstrap.connect()
  4. 监听通道关闭
  5. 在finally块内关闭EventLoopGroup

看看,从代码主流程来看,其实客户端跟服务端基本没什么很大的区别。当然这不是主要的东西。最重要的是,大家发现没有,这里出现了好几个陌生的API,这就是Netty提供给我们的核心组件!这些组件会在后面给大家详解介绍,也是本文的核心所在!这些组件,分别是:EventLoopGroupBootstra(ServerBootstrap)NioServerSocketChannel(NioSocketChannel)ChannelHandlerChannelPipelineByteBuf

这些API组件有什么特别吗?
同学们还记得我们说【Netty是什么吗】?【Netty 是一个基于NIO的客户、服务器端的编程通信框架】啊!那同学们还记得Java NIO 3个核心组件吗?Channel通道Selector多路复用器ByteBuffer缓冲区嘛(其实还要考虑一个多线程)。
好了,就算我不说你们通过英文翻译也稍微能一一对应上了,既然Netty是基于NIO的,那NIO的这些细节,肯定也会被包含在Netty的组件中!比如:

  • EventLoopGroup:直译【事件循环组】。NIO代码里面很多while(true),然后不断循环检测事件发生,像不像?对的,EventLoopGroup可以看成是一个【线程池】
  • Channel:通道嘛,这个大家最容易理解了。可能有些朋友还不明白Channel通道是什么,其实就是BIO演变到NIO之后,Socket被封装到了Channel里面

OK,更多详细介绍我会在【三、Netty核心组件详解】中讲到。

2.3 Netty的特性

我们在最开始介绍Netty的时候,有这么描述过:它是一个【异步事件驱动】的网络应用程序框架。并且向大家抛出了这么个问题:如何理解【异步事件驱动】?
如果你们看了我上一篇文章其实不难理解。【异步事件驱动】=【异步】+【事件驱动】。

  • 异步:跟同步相对。异步在编程中的体现就是,新开一条线程去处理任务
  • 事件驱动:其实就是Reactor模式在Netty中的体现。Netty是基于Reactor模式设计的

只不过稍微有些不同的是:Netty对于事件的定义。

2.3.1 Netty的事件

Netty 使用不同的事件来通知我们状态的改变或者是操作的状态。这使得我们能够基于已经发生的事件来触发适当的动作。Netty 事件是按照它们与入站或出站数据流的相关性进行分类的。

  1. 可能由入站数据或者相关的状态更改而触发的事件包括:
    • 连接已被激活或者连接失活;
    • 数据读取;
    • 用户事件;
    • 错误事件
  2. 出站事件是未来将会触发的某个动作的操作结果,这些动作包括:
    • 打开或者关闭到远程节点的连接;
    • 将数据写到或者冲刷到套接字

每个事件都可以被分发给 ChannelHandler 类中的某个用户实现的方法,既然事件分为入站和出站,用来处理事件的 ChannelHandler 也被分为可以处理入站事件的 Handler 和出站事件的 Handler,当然有些 Handler 既可以处理入站也可以处理出站。
Netty 提供了大量预定义的可以开箱即用的 ChannelHandler 实现,包括用于各种协议(如 HTTP 和 SSL/TLS)的 ChannelHandler。

基于 Netty 的网络应用程序中根据业务需求会使用 Netty 已经提供的 ChannelHandler 或者自行开发 ChannelHandler,这些 ChannelHandler 都放在 ChannelPipeline 中统一管理,事件就会在 ChannelPipeline 中流动,并被其中一个或者多个 ChannelHandler 处理。

它的原理图如下:
在这里插入图片描述

2.4 Netty线程模型

在这里插入图片描述

模型解读:
1) Netty 抽象出两组线程池BossGroup和WorkerGroup,BossGroup专门负责接收客户端的连接, WorkerGroup专门负责网络的读写

不就是Reactor模型的主从架构吗

2)BossGroup和WorkerGroup类型都是NioEventLoopGroup

NIO是一种IO方式,epoll,BIO都是。所以,其实还有EpollEventLoopGroup,以此类推

3)NioEventLoopGroup 相当于一个事件循环线程组, 这个组中含有多个事件循环线程 ,每一个事件循环线程是NioEventLoop
4)每个NioEventLoop都有一个selector , 用于监听注册在其上的socketChannel的网络通讯
5)每个Boss NioEventLoop线程内部循环执行的步骤有 3 步

  1. 处理accept事件 , 与client 建立连接 , 生成 NioSocketChannel
  2. 将NioSocketChannel注册到某个worker NIOEventLoop上的selector
  3. 继续处理任务队列的任务 , 即runAllTasks

6)每个worker NIOEventLoop线程循环执行的步骤

  1. 轮询注册到自己selector上的所有NioSocketChannel 的read, write事件
  2. 处理 I/O 事件, 即read , write 事件, 在对应NioSocketChannel 处理业务
  3. runAllTasks处理任务队列TaskQueue的任务 ,一些耗时的业务处理一般可以放入线程池中慢慢处理,这样不影响数据在 pipeline 中的流动处理

7)每个worker NIOEventLoop处理NioSocketChannel业务时,会使用 pipeline (管道),管道中维护了很多 handler 处理器用来处理 channel 中的数据

三、Netty核心组件详解(未完待续)

3.1 EventLoopGroup和EventLoop

EventLoop,直译:事件循环;EventLoopGroup直译:事件循环组。虽然我不知道这是啥,但是基本上可以猜测:后者是对前者做管理的类。所以我们主要了解一下EventLoop先。
回想一下我们在 NIO 中是如何处理我们关心的事件的?很简单,就是在一个 while 循环中 select 出事件,然后依次处理每种事件。这不就是【事件循环】嘛。

3.1.1 EventLoop

EventLoop是Netty 的核心接口,用于处理网络连接的生命周期中所发生的事件。它的类结构如下:
在这里插入图片描述
再来看看接口定义:
在这里插入图片描述
再看看对应的实现类:
在这里插入图片描述
看,我们用到的NioEventLoop就在里面了。如果大家翻开里面的源码,会发现,NioEventLoop里面有几个重要的属性,我这边用伪代码写一下:(有一些属性是在父类中的)

class NioEventLoop {
	Selector selector;
	Thread thread;
	Queue<Runnable> taskQueue;
	SelectedSelectionKeySet selectedKeys;
}

由上面的伪代码可以看到,NioEventLoop 中:

  1. 维护了一个线程任务队列(是否似曾相识?线程池呀!),支持异步提交执行任务,线程启动时会调用 NioEventLoop 的 run 方法,执行 I/O 任务和非 I/O 任务:
    • I/O 任务,即 selectionKey 中 ready 的事件,如 accept、connect、read、write 等,由 processSelectedKeys 方法触发
    • 非 IO 任务,添加到 taskQueue 中的任务,如 register0、bind0 等任务,由 runAllTasks 方法触发
  2. 维护了一个Selector选择器。熟悉NIO的朋友估计就知道这是啥了
  3. 维护了一个SelectionKeySet。这个不知道大家有没有印象,在NIO模型中我说:向Selector注册了Channel和感兴趣事件后就会被包装成一个SelectionKey

可是我们知道的,一台电脑顶多就几千条线程,所以,能分配到一个Netty中的线程数必然也不会多,或者说我们创建出来的EventLoop必然也不会太多。那,Netty号称百万并发量是如何实现的呢?
所以这里势必会有一种对应关系:1个EventLoop管理N个连接(或者说Channel)

线程的分配
异步传输实现只使用了少量的 EventLoop(以及和它们相关联的 Thread),而且在当前的线程模型中,它们可能会被多个 Channel 所共享。这使得可以通过尽可能少量的 Thread 来

3.1.2 NioEventLoopGroup

NioEventLoopGroup,主要管理 eventLoop 的生命周期,可以理解为一个线程池,内部维护了一组线程,每个线程(NioEventLoop)负责处理多个 Channel 上的事件,而一个 Channel 只对应于一个线程。

学习总结

感谢

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1119643.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

learn C++ NO.11——string类模拟实现

前言 本篇文章主要是讲string类的模拟实现&#xff0c;模拟实现的是string类的常用接口以及成员函数。让读者对string类有更深的理解。适当的“造轮子”&#xff0c;有助于我们的语言学习。 简单描述string类 string类其实就是一个管理字符数组的线性表&#xff0c;我们可以…

常见面试题-Redis专栏(二)

theme: cyanosis typora-copy-images-to: imgsRedisson 分布式锁&#xff1f;在项目中哪里使用&#xff1f;多久会进行释放&#xff1f;如何加强一个分布式锁&#xff1f; 答&#xff1a; 首先入门级别的分布式锁是通过 setnx 进行实现&#xff0c;使用 setnx 实现有四个注意…

6.MySQL内置函数

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 日期函数 current_date() 当前日期 select 可以做表达式和函数的计算。 current_time() 当前时间 current_timestamp() 当前日期加时间 注意&#xff1a;值得说明的是这三个函数底层调用的都是同一个函数&#xff0c;只不…

C语言关键字

关键字作用 关键字是 C 语言中预先保留的单词 C语言关键字 注意&#xff1a;在定义变量或者常量的时候&#xff0c;不要使用关键字

设计模式:组合模式(C#、JAVA、JavaScript、C++、Python、Go、PHP)

简介&#xff1a; 组合模式&#xff0c;它是一种用于处理树形结构、表示“部分-整体”层次结构的设计模式。它允许你将对象组合成树形结构&#xff0c;以表示部分和整体的关系。这种模式的主要目的是简化客户端代码&#xff0c;并使客户端以一致的方式处理单个对象和组合对象。…

数字图像处理实验记录五(图像的空间域增强-锐化处理)

前言&#xff1a; 文章目录 一、基础知识1&#xff0c;什么是锐化&#xff1f;2&#xff0c;为什么要锐化&#xff1f;3&#xff0c;怎么进行锐化&#xff1f; 二、实验要求任务1&#xff1a;任务2&#xff1a;任务3&#xff1a; 三、实验记录&#xff1a;任务1&#xff1a;任…

计算机网络第三章习题

1.假定1km长的CSMA/CD网络的数据率为1Gb/s。设信号在网络上的传播 速率为200000km/s。求能够使用此协议的最短帧长. 问题刨析: 逻辑链: 最短帧长数据传输率x争用期2τ(2倍端到端所需要的时间) 题目已经给出数据率为1Gb/s,所以我们要知道争用期2τ是多少. 端到端所需要的时间信…

订单 延后自动关闭,五种方案优雅搞定!

前 言 在开发中&#xff0c;往往会遇到一些关于延时任务的需求。例如 生成订单30分钟未支付&#xff0c;则自动取消生成订单60秒后,给用户发短信 对上述的任务&#xff0c;我们给一个专业的名字来形容&#xff0c;那就是延时任务 。那么这里就会产生一个问题&#xff0c;这个…

画程序流程图

一。在线程序流程图。类图和时序图 Integrations | Mermaid 二。VSCODE画UML图和各种种 1.下载plantuml.jarReleases plantuml/plantuml GitHubGenerate diagrams from textual description. Contribute to plantuml/plantuml development by creating an account on GitHu…

B-tree(PostgreSQL 14 Internals翻译版)

概览 B树(作为B树访问方法实现)是一种数据结构&#xff0c;它使您能够通过从树的根向下查找树的叶节点中所需的元素。为了明确地标识搜索路径&#xff0c;必须对所有树元素进行排序。B树是为有序数据类型设计的&#xff0c;这些数据类型的值可以进行比较和排序。 下面的机场代…

【在英伟达nvidia的jetson-orin-nx上使用调试摄像头-初步调试USB摄像头与Camera Conn.#0/#1接口-基础测试】

【在英伟达nvidia的jetson-orin-nx上使用调试摄像头-初步调试USB摄像头与Camera Conn.#0/#1接口-基础测试】 1、概述2、实验环境3、 物品说明&#xff08;0&#xff09;各种各样摄像头&#xff08;1&#xff09;USB摄像头&#xff08;2&#xff09;CSI摄像头&#xff08;3&…

【JavaEE】线程安全的集合类 -- 多线程篇(9)

线程安全的集合类 多线程环境使用 ArrayList多线程环境使用队列多线程环境使用哈希表 多线程环境使用 ArrayList 自己使用同步机制 (synchronized 或者 ReentrantLock)Collections.synchronizedList(new ArrayList); synchronizedList 是标准库提供的一个基于 synchronized 进…

【AIGC】百度文库文档助手之 - 一键生成PPT

百度文库文档助手之 - 一键生成PPT 引言一、文档助手&#xff1a;体验一键生成PPT二、文档助手&#xff1a;进阶用法三、其它生成PPT的方法3.1 ChatGPT3.2 文心一言 引言 就在上个月百度文库升级为一站式智能文档平台&#xff0c;开放四大AI能力&#xff1a;智能PPT、智能总结、…

正则表达式提取http和http内容

http.* 这样匹配到的就是我们要的内容 取反正则&#xff1a;^((?!要取反的正则表达式).)*$ 取反&#xff1a;^((?!http.).)$ 这样匹配到的就是我们不要的内容 提取域名 /[(http|ftp|https):\/\/]?([\w_-](?:(?:\.[\w_-])))([\w.,?^%&:\/~#-]*[\w?^%&\/~#-]…

APK与小程序渗透

文章目录 APK与小程序渗透1. APK2. 小程序2.1 源代码2.2 小程序的默认下载位置 3. 安装证书3.1 openssl配置环境变量3.2 安装证书 APK与小程序渗透 由于APK和小程序与服务器通信还是采用的是https协议&#xff0c;只是使用了加密。只要获取到了HTTP的请求报文就可以回归到Web渗…

单目3D目标检测论文汇总

基于语义和几何约束的方法 1. Deep3DBox 3D Bounding Box Estimation Using Deep Learning and Geometry [CVPR2017] https://arxiv.org/pdf/1612.00496.pdfhttps://zhuanlan.zhihu.com/p/414275118 核心思想&#xff1a;通过利用2D bounding box与3D bounding box之间的几何约…

『C语言进阶』字符函数和内存函数(2)

&#x1f525;博客主页&#xff1a; 小羊失眠啦. &#x1f516;系列专栏&#xff1a; C语言、Linux、Cpolar ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 文章目录 一、strtok函数1.1 函数认识1.2 注意事项 二、strerror函数2.1 函数认识2.2 注意事项 三、memcpy函数3.1 函数…

1024渗透测试如何暴力破解其他人主机的密码(第十一课)

1024渗透测试如何暴力破解其他人主机的密码(第十一课) 1 crunch 工具 crunch是一个密码生成器&#xff0c;一般用于在渗透测试中生成随机密码或者字典攻击。下面是常见的一些使用方法&#xff1a; 生成密码字典 生成6位数字的字典&#xff1a;crunch 6 6 -t -o dict.txt …

【LeetCode】145. 二叉树的后序遍历 [ 左子树 右子树 根结点]

题目链接 文章目录 Python3方法一&#xff1a; 递归 ⟮ O ( n ) ⟯ \lgroup O(n) \rgroup ⟮O(n)⟯方法二&#xff1a; 迭代 ⟮ O ( n ) ⟯ \lgroup O(n) \rgroup ⟮O(n)⟯方法三&#xff1a; Morris ⟮ O ( n ) 、 O ( 1 ) ⟯ \lgroup O(n)、O(1) \rgroup ⟮O(n)、O(1)⟯写…

学成在线第二天-查询课程、查询课程分类、新增课程接口实现以及跨域的处理思路和全局异常处理的使用以及面试题

目录 一、接口的实现 二、跨域的处理思路 三、全局异常处理 四、面试题 五、总结 一、接口的实现 1. 查询课程接口 思路&#xff1a; 典型的分页查询 按需查询 模糊查询的查询 controller&#xff1a; ApiOperation(value "课程列表", notes "课程…