Netty-Netty组件了解

EventLoop EventLoopGroup

        回想一下我们在 NIO 中是如何处理我们关心的事件的?在一个 while 循环中 select 出事
件,然后依次处理每种事件。我们可以把它称为事件循环,这就是 EventLoop interface
io.netty.channel. EventLoop 定义了 Netty 的核心抽象,用于处理网络连接的生命周期中所发
生的事件。
        io.netty.util.concurrent 包构建在 JDK java.util.concurrent 包上。 而 io.netty.channel 包
中的类,为了与 Channel 的事件进行交互,扩展了这些接口/类。 一个 EventLoop 将由一个
永远都不会改变的 Thread 驱动,同时任务( Runnable 或者 Callable )可以直接提交给
EventLoop 实现,以立即执行或者调度执行。

 

线程的分配

         服务于 Channel 的 I/O 和事件的 EventLoop 包含在 EventLoopGroup 中。
        异步传输实现只使用了少量的 EventLoop (以及和它们相关联的 Thread ),而且在当前
的线程模型中,它们可能会被多个 Channel 所共享。这使得可以通过尽可能少量的 Thread
支撑大量的 Channel ,而不是每个 Channel 分配一个 Thread EventLoopGroup 负责为每个
新创建的 Channel 分配一个 EventLoop 在当前实现中,使用顺序循环(round-robin)的方
式进行分配以获取一个均衡的分布,并且相同的 EventLoop 可能会被分配给多个 Channel
        一旦一个 Channel 被分配给一个 EventLoop ,它将在它的整个生命周期中都使用这个
EventLoop (以及相关联的 Thread )。

 

        需要注意,EventLoop 的分配方式对 ThreadLocal 的使用的影响。因为一个 EventLoop
常会被用于支撑多个 Channel ,所以对于所有相关联的 Channel 来说, ThreadLocal 都将是
一样的。这使得它对于实现状态追踪等功能来说是个糟糕的选择。然而,在一些无状态的上
下文中,它仍然可以被用于在多个 Channel 之间共享一些重度的或者代价昂贵的对象,甚
至是事件。

线程管理

         在内部,当提交任务到如果当前)调用线程正是支撑 EventLoop 的线程,那么所提交
的代码块将会被(直接)执行。 否则, EventLoop 将调度该任务以便稍后执行,并将它放入
到内部队列中。当 EventLoop 下次处理它的事件时,它会执行队列中的那些任务 / 事件。

 

ChannelEventLoop(Group)ChannelFuture

         Netty 网络抽象的代表:
        Channel—Socket;
        EventLoop—控制流、多线程处理、并发;
        ChannelFuture—异步通知。
        Channel 和 EventLoop 关系如图
        从图上我们可以看出 Channel 需要被注册到某个 EventLoop 上,在 Channel 整个生命周
期内都由这个EventLoop处理IO事件,也就是说一个Channel和一个EventLoop进行了绑定,
但是一个EventLoop可以同时被多个Channel绑定。 这一点在“ EventLoop EventLoopGroup
节里也提及过。

 

Channel 接口

        基本的 I/O 操作( bind() connect() read() write() )依赖于底层网络传输所提供的原
语。在基于 Java 的网络编程中,其基本的构造是类 Socket Netty Channel 接口所提供
API ,被用于所有的 I/O 操作。大大地降低了直接使用 Socket 类的复杂性。此外, Channel
也是拥有许多预定义的、专门化实现的广泛类层次结构的根。
        由于 Channel 是独一无二的,所以为了保证顺序将 Channel 声明为 java.lang.Comparable
的一个子接口。因此,如果两个不同的 Channel 实例都返回了相同的散列码,那么
AbstractChannel 中的 compareTo() 方法的实现将会抛出一个 Error
Channel 的生命周期状态
         ChannelUnregistered : Channel 已经被创建,但还未注册到 EventLoop
        ChannelRegistered : Channel 已经被注册到了 EventLoop
         ChannelActive : Channel 处于活动状态(已经连接到它的远程节点)。它现在可以接
收和发送数据了
         ChannelInactive : Channel 没有连接到远程节点
         当这些状态发生改变时,将会生成对应的事件。这些事件将会被转发给 ChannelPipeline
中的 ChannelHandler,其可以随后对它们做出响应。在我们的编程中,关注 ChannelActive 和
ChannelInactive 会更多一些。
重要 Channel 的方法
         eventLoop: 返回分配给 Channel EventLoop
         pipeline: 返回 Channel ChannelPipeline ,也就是说每个 Channel 都有自己的
ChannelPipeline
         isActive: 如果 Channel 是活动的,则返回 true 。活动的意义可能依赖于底层的传输。
例如,一个 Socket 传输一旦连接到了远程节点便是活动的,而一个 Datagram 传输一旦被
打开便是活动的。
         localAddress: 返回本地的 SokcetAddress
         remoteAddress: 返回远程的 SocketAddress
         write: 将数据写到远程节点,注意,这个写只是写往 Netty 内部的缓存,还没有真正
写往 socket
         flush: 将之前已写的数据冲刷到底层 socket 进行传输。
         writeAndFlush: 一个简便的方法,等同于调用 write() 并接着调用 flush()

ChannelPipeline ChannelHandlerContext

ChannelPipeline 接口

        当 Channel 被创建时,它将会被自动地分配一个新的 ChannelPipeline ,每个 Channel
有自己的 ChannelPipeline 。这项关联是永久性的。在 Netty 组件的生命周期中,这是一项固
定的操作,不需要开发人员的任何干预。
        ChannelPipeline 提供了 ChannelHandler 链的容器,并定义了用于在该链上传播 入站(也
就是从网络到业务处理) 出站(也就是从业务处理到网络) ,各种事件流的 API ,我们
代码中的 ChannelHandler 都是放在 ChannelPipeline 中的。
        使得事件流经 ChannelPipeline ChannelHandler 的工作,它们是在应用程序的初始化
或者引导阶段被安装的。这些 ChannelHandler 对象接收事件、执行它们所实现的处理逻辑,
并将数据传递给链中的下一个 ChannelHandler ,而且 ChannelHandler 对象也完全可以拦截
事件不让事件继续传递。它们的执行顺序是由它们被添加的顺序所决定的。

ChannelHandler 的生命周期

        在 ChannelHandler 被添加到 ChannelPipeline 中或者被从 ChannelPipeline 中移除时会调
用下面这些方法。这些方法中的每一个都接受一个 ChannelHandlerContext 参数。
handlerAdded 当把 ChannelHandler 添加到 ChannelPipeline 中时被调用
handlerRemoved 当从 ChannelPipeline 中移除 ChannelHandler 时被调用
exceptionCaught 当处理过程中在 ChannelPipeline 中有错误产生时被调用

ChannelPipeline 中的 ChannelHandler

         入站和出站 ChannelHandler 被安装到同一个 ChannelPipeline 中,ChannelPipeline 以双
向链表的形式进行维护管理。比如下图,我们在网络上传递的数据,要求加密,但是加密后
密文比较大,需要压缩后再传输,而且按照业务要求,需要检查报文中携带的用户信息是否
合法,于是我们实现了 5 个 Handler: 解压(入)Handler、压缩(出)handler、解密(入)
Handler、加密(出) Handler、授权(入) Handler

        如果一个消息或者任何其他的入站事件被读取 那么它会从 ChannelPipeline 的头部开
始流动,但是只被处理入站事件的 Handler 处理,也就是解压(入)Handler、解密(入)Handler、 授权(入) Handler,最终,数据将会到达 ChannelPipeline 的尾端,届时,所有处理就都结束了。
        数据的出站运动(即正在被写的数据)在概念上也是一样的。在这种情况下, 数据将从
链的尾端开始流动,但是只被处理出站事件的 Handler 处理,也就是加密(出) Handler、
压缩(出)handler,直到它到达链的头部为止。 在这之后,出站数据将会到达网络传输层,
也就是我们的 Socket
        Netty 能区分入站事件的 Handler 和出站事件的 Handler ,并确保数据只会在具有相同定
向类型的两个 ChannelHandler 之间传递。

        所以在我们编写 Netty 应用程序时要注意,分属出站和入站不同的 Handler 在业务没
特殊要求的情况下 是无所谓顺序的,正如我们下面的图所示 比如‘压缩(出)handler‘可
以放在‘解压(入)handler‘和‘解密(入) Handler‘中间,也可以放在‘解密(入) Handler
‘和‘授权(入) Handler‘之间。
        而同属一个方向的 Handler 则是有顺序的,因为上一个 Handler 处理的结果往往是下一
Handler 的要求的输入。比如入站处理,对于收到的数据,只有先解压才能得到密文,才
能解密,只有解密后才能拿到明文中的用户信息进行授权检查,所以解压 -> 解密 -> 授权这个
三个入站 Handler 的顺序就不能乱。

 

ChannelPipeline 上的方法

        既然 ChannelPipeline 以双向链表的形式进行维护管理 Handler ,自然也提供了对应的方
法在 ChannelPipeline 中增加或者删除、替换 Handler
        addFirst、 addBefore addAfter addLast
        将一个 ChannelHandler 添加到 ChannelPipeline
        remove 将一个 ChannelHandler ChannelPipeline 中移除
        replace ChannelPipeline 中的一个 ChannelHandler 替换为另一个 ChannelHandler
        get 通过类型或者名称返回 ChannelHandler
        context 返回和 ChannelHandler 绑定的 ChannelHandlerContext
        names 返回 ChannelPipeline 中所有 ChannelHandler 的名称
        ChannelPipeline 的 API 公开了用于调用入站和出站操作的附加方法。

ChannelHandlerContext

        ChannelHandlerContext 代表了 ChannelHandler ChannelPipeline 之间的关联,每当有
ChannelHandler 添加到 ChannelPipeline 中时,都会创建 ChannelHandlerContext ,为什么需
要这个 ChannelHandlerContext ?前面我们已经说过, ChannelPipeline 以双向链表的形式进
行维护管理 Handler ,毫无疑问, Handler 在放入 ChannelPipeline 的时候必须要有两个指针
pre next 来说明它的前一个元素和后一个元素,但是 Handler 本身来维护这两个指针合适
吗?想想我们在使用 JDK LinkedList 的时候,我们放入 LinkedList 的数据是不会带这两个指
针的, LinkedList 内部会用类 Node 对我们的数据进行包装,而类 Node 则带有两个指针 pre
next
        所以,ChannelHandlerContext 的主要作用就和 LinkedList 内部的类 Node 类似。
不过 ChannelHandlerContext 不仅仅只是个包装类,它还提供了很多的方法,比如让事
件从当前 ChannelHandler 传递给链中的下一个 ChannelHandler ,还可以被用于获取底层的
Channel ,还可以用于写出站数据。

ChannelChannelPipeline ChannelHandlerContext 上的事件传播

        ChannelHandlerContext 有很多的方法,其中一些方法也存在于 Channel
Channel-Pipeline 本身上, 但是有一点重要的不同。 如果调用 Channel 或者 ChannelPipeline
的这些方法,它们将沿着整个 ChannelPipeline 进行传播。而调用位于 ChannelHandlerContext
上的相同方法,则将从当前所关联的 ChannelHandler 开始,并且只会传播给位于该
ChannelPipeline 中的下一个(入站下一个,出站上一个)能够处理该事件的 ChannelHandler

 

        我们用一个实际例子来说明,比如服务器收到对端发过来的报文,解压后需要进行解密,
结果解密失败,要给对端一个应答。
        如果发现解密失败原因是服务器和对端的加密算法不一致,应答报文只能以明文的压缩
格式发送,就可以在解密 handler 中直接使用 ctx.write 给对端应答,这样应答报文就只经过
压缩 Handler 就发往了对端;
        其他情况下,应答报文要以加密和压缩格式发送,就可以在解密 handler 中使用
channel.write() 或者 channelpipeline.write() 给对端应答,这样应答报文就会流经整个出站处理
过程。

ChannelHandlerContext API

alloc 返回和这个实例相关联的 Channel 所配置的 ByteBufAllocator
bind 绑定到给定的 SocketAddress ,并返回 ChannelFuture
channel 返回绑定到这个实例的 Channel
close 关闭 Channel ,并返回 ChannelFuture
connect 连接给定的 SocketAddress ,并返回 ChannelFuture
deregister 从之前分配的 EventExecutor 注销,并返回 ChannelFuture
disconnect 从远程节点断开,并返回 ChannelFuture
executor 返回调度事件的 EventExecutor
fireChannelActive 触发对下一个 ChannelInboundHandler 上的 channelActive() 方法(已
连接)的调用
fireChannelInactive 触发对下一个 ChannelInboundHandler 上的 channelInactive() 方法
(已关闭)的调用
fireChannelRead 触发对下一个 ChannelInboundHandler 上的 channelRead() 方法(已接
收的消息)的调用
fireChannelReadComplete 触发对下一个 ChannelInboundHandler 上的
channelReadComplete() 方法的调用
fireChannelRegistered 触发对下一个 ChannelInboundHandler 上的
fireChannelRegistered() 方法的调用
fireChannelUnregistered 触发对下一个 ChannelInboundHandler 上的
fireChannelUnregistered() 方法的调用
fireChannelWritabilityChanged 触发对下一个 ChannelInboundHandler 上的
fireChannelWritabilityChanged() 方法的调用
fireExceptionCaught 触发对下一个 ChannelInboundHandler 上的
fireExceptionCaught(Throwable) 方法的调用
fireUserEventTriggered 触发对下一个 ChannelInboundHandler 上的
fireUserEventTriggered(Object evt) 方法的调用
handler 返回绑定到这个实例的 ChannelHandler
isRemoved 如果所关联的 ChannelHandler 已经被从 ChannelPipeline 中移除则返回 true
name 返回这个实例的唯一名称
pipeline 返回这个实例所关联的 ChannelPipeline
read 将数据从 Channel 读取到第一个入站缓冲区;如果读取成功则触发一个
channelRead 事件,并(在最后一个消息被读取完成后)通知 ChannelInboundHandler
channelReadComplete(ctx) 方法
write 通过这个实例写入消息并经过 ChannelPipeline
writeAndFlush 通过这个实例写入并冲刷消息并经过 ChannelPipeline
当使用 ChannelHandlerContext API 的时候,有以下两点:
⚫ ChannelHandlerContext 和 ChannelHandler 之间的关联(绑定)是永远不会改变的,
所以缓存对它的引用是安全的;
⚫ 相对于其他类的同名方法,ChannelHandlerContext 的方法将产生更短的事件流,应该尽可能地利用这个特性来获得最大的性能。

 ChannelHandler

ChannelHandler 接口

        从应用程序开发人员的角度来看,Netty 的主要组件是 ChannelHandler ,它充当了所有
处理入站和出站数据的应用程序逻辑的容器。 ChannelHandler 的方法是由网络事件触发的。
事实上, ChannelHandler 可专门用于几乎任何类型的动作,例如将数据从一种格式转换为另
外一种格式,例如各种编解码,或者处理转换过程中所抛出的异常。
        举例来说,ChannelInboundHandler 是一个你将会经常实现的子接口。这种类型的
ChannelHandler 接收入站事件和数据,这些数据随后将会被你的应用程序的业务逻辑所处理。
当你要给连接的客户端发送响应时,也可以从 ChannelInboundHandler 直接冲刷数据然后输
出到对端。应用程序的业务逻辑通常实现在一个或者多个 ChannelInboundHandler 中。
        这种类型的 ChannelHandler 接收入站事件和数据,这些数据随后将会被应用程序的业
务逻辑所处理。
        Netty 定义了下面两个重要的 ChannelHandler 子接口:
                ChannelInboundHandler——处理入站数据以及各种状态变化;
                ChannelOutboundHandler——处理出站数据并且允许拦截所有的操作。

ChannelInboundHandler 接口

        下面列出了接口 ChannelInboundHandler 的生命周期方法。这些方法将会在数据被接收
时或者与其对应的 Channel 状态发生改变时被调用。正如我们前面所提到的,这些方法和
Channel 的生命周期密切相关。
        channelRegistered Channel 已经注册到它的 EventLoop 并且能够处理 I/O 时被调用
        channelUnregistered Channel 从它的 EventLoop 注销并且无法处理任何 I/O 时被调
        channelActive Channel 处于活动状态时被调用; Channel 已经连接 / 绑定并且已经就
        channelInactive Channel 离开活动状态并且不再连接它的远程节点时被调用
        channelReadComplete Channel 上的一个读操作完成时被调用
        channelRead 当从 Channel 读取数据时被调用
        ChannelWritabilityChanged
        当 Channel 的可写状态发生改变时被调用。可以通过调用 Channel isWritable() 方法
来检测 Channel 的可写性。与可写性相关的阈值可以通过
Channel.config().setWriteHighWaterMark() Channel.config().setWriteLowWaterMark() 方法来
设置
        userEventTriggered ChannelnboundHandler.fireUserEventTriggered() 方法被调用时被
调用。
注意: channelReadComplete channelRead 这两个方法非常让人搞不清两者的区别是
什么,我们先放下这个疑问,后面会有解释。

ChannelOutboundHandler 接口

        出站操作和数据将由 ChannelOutboundHandler 处理。它的方法将被 Channel Channel
        Pipeline 以及 ChannelHandlerContext 调用。
        所有由 ChannelOutboundHandler 本身所定义的方法:
        bind(ChannelHandlerContext,SocketAddress,ChannelPromise)
        当请求将 Channel 绑定到本地地址时被调用
        connect(ChannelHandlerContext,SocketAddress,SocketAddress,ChannelPromise)
        当请求将 Channel 连接到远程节点时被调用
        disconnect(ChannelHandlerContext,ChannelPromise)
        当请求将 Channel 从远程节点断开时被调用
        close(ChannelHandlerContext,ChannelPromise) 当请求关闭 Channel 时被调用
        deregister(ChannelHandlerContext,ChannelPromise)
        当请求将 Channel 从它的 EventLoop 注销时被调用
        read(ChannelHandlerContext) 当请求从 Channel 读取更多的数据时被调用
        flush(ChannelHandlerContext) 当请求通过 Channel 将入队数据冲刷到远程节点时被调
        write(ChannelHandlerContext,Object,ChannelPromise) 当请求通过 Channel 将数据写到
远程节点时被调用

ChannelHandler 的适配器

        有一些适配器类可以将编写自定义的 ChannelHandler 所需要的工作降到最低限度,因
为它们提供了定义在对应接口中的所有方法的默认实现。因为你有时会忽略那些不感兴趣的
事件,所以 Netty 提供了抽象基类 ChannelInboundHandlerAdapter (处理入站) 和
ChannelOutboundHandlerAdapter (处理出站)。
        我们可以使用 ChannelInboundHandlerAdapter ChannelOutboundHandlerAdapter 类作
为自己的 ChannelHandler 的起始点。这两个适配器分别提供了 ChannelInboundHandler
ChannelOutboundHandler 的基本实现。通过扩展抽象类 ChannelHandlerAdapter ,它们获得
了它们共同的超接口 ChannelHandler 的方法。
        不过 ChannelOutboundHandler 有个非常让人迷惑的 read 方法, ChannelOutboundHandler
不是处理出站事件的吗?怎么会有 read 方法呢?其实这个 read 方法不是表示读数据,而是
表示业务发出了读( read )数据的要求,这个要求也会封装为一个事件进行传播,这个事件
因为是业务发出到网络的,自然就是个出站事件,而且这个事件触发的就是
ChannelOutboundHandler read 方法。
        如果我们的 Handler 既要处理入站又要处理出站怎么办呢?这个时候就可以使用类
ChannelDuplexHandler ,当然也可以同时实现 ChannelOutboundHandler,
ChannelInboundHandler 这两个接口,自然就要麻烦很多了。

 

Handler 的共享和并发安全性

        ChannelHandlerAdapter 还提供了实用方法 isSharable() 。如果其对应的实现被标注为
Sharable ,那么这个方法将返回 true ,表示它可以被添加到多个 ChannelPipeline
        这就牵涉到了我们实现的 Handler 的共享性和线程安全性。回顾我们的 Netty 代码,在
pipeline 安装 Handler 的时候,我们基本上是 new Handler 的实例
        因为每个 socketChannel 有自己的 pipeline 而且每个 socketChannel 又是和线程绑定的,
所以这些 Handler 的实例之间完全独立的,只要 Handler 的实例之间不是共享了全局变量,
Handler 的实例是线程安全的。
        但是如果业务需要我们在多个 socketChannel 之间共享一个 Handler 的实例怎么办呢?
比如统计服务器接受到和发出的业务报文总数,我们就需要用一个 Handler 的实例来横跨所
有的 socketChannel 来统计所有 socketChannel 业务报文数。
        为了实现这一点,我们可以实现一个 MessageCountHandler ,并且在
MessageCountHandler 上使用 Netty @Sharable 注解,然后在安装 MessageCountHandler
例到 pipeline 时,共用一个即可。当然,因为 MessageCountHandler 实例是共享的,所以在
实现 MessageCountHandler 的统计功能时,请务必注意线程安全,我们在具体实现时就使用
Java 并发编程里的 Atomic 类来保证这一点。
        具体代码请参考包 cn.tuling.nettybasic.sharehndler

资源管理和 SimpleChannelInboundHandler

        回想一下我们在 NIO 中是如何接收和发送网络数据的?都是首先创建了一个 Buffer ,应
用程序中的业务部分和 Channel 之间通过 Buffer 进行数据的交换:
        Netty 在处理网络数据时,同样也需要 Buffer ,在 Read 网络数据时由 Netty 创建 Buffer
Write 网络数据时 Buffer 往往是由业务方创建的。不管是读和写, Buffer 用完后都必须进行
释放,否则可能会造成内存泄露。
        在 Write 网络数据时,可以确保数据被写往网络了, Netty 会自动进行 Buffer 的释放,
但是如果 Write 网络数据时,我们有 outBoundHandler 处理了 write() 操作并丢弃了数据,没
有继续往下写,要由我们负责释放这个 Buffer ,就必须调用 ReferenceCountUtil.release 方法,
否则就可能会造成内存泄露。
        在 Read 网络数据时,如果我们可以确保每个 InboundHandler 都把数据往后传递了,也
就是调用了相关的 fireChannelRead 方法, Netty 也会帮我们释放,同样的,如果我们有
InboundHandler 处理了数据,又不继续往后传递,又不调用负责释放的 ReferenceCountUtil.release 方法,就可能会造成内存泄露。
        但是由于消费入站数据是一项常规任务,所以 Netty 提供了一个特殊的被 称为 SimpleChannelInboundHandler 的 ChannelInboundHandler 实现。这个实现会在数
据被 channelRead0() 方法消费之后自动释放数据。
        同时系统为我们提供的各种预定义 Handler 实现,都实现了数据的正确处理,所以我们
自行在编写业务 Handler 时,也需要注意这一点: 要么继续传递,要么自行释放

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【自学笔记】01Java基础-08Java常用API:04包装类

记录Java基础-常用API-有关时间日期的类。 1 包装类 其实就是8种基本数据类型对应的引用类型,因为基本数据类型不能直接参与面向对象编程。具有将基本数据类型转换为对象的功能,并且实现了多种接口,支持集合框架和泛型。 包装类的主要特点和…

记录汇川:H5U与Fctory IO测试8

主程序: 子程序: IO映射 子程序: 出料程序 子程序: 重量程序 子程序: 自动程序 Fctory IO配置: HMI配置 实际动作如下: Fctory IO测试8

【一】创建Python TK GUI窗口,并简单设置窗口

文章目录 背景系统环境开始一个简单GUI启动一个GUI窗口(不完成功能)简单配置GUI窗口(大小、位置、图标) 运行示例 背景 这是一个系列文章。下一篇【【二】为Python Tk GUI窗口添加一些组件和绑定一些组件事件】 使用pyth…

AIGC大模型必备知识——LLM ,你知道它是如何训练的吗?小白必读深度好文

Look!👀我们的大模型商业化落地产品📖更多AI资讯请👉🏾关注Free三天集训营助教在线为您火热答疑👩🏼‍🏫 近年来,人工智能(AI)领域经历了令人瞩目…

FineBI实战项目一(17):热门商品Top10分析开发

点击新建组件,创建热门商品Top10组件。 选择柱状图,拖拽cnt(总数)到横轴,拖拽goodName到纵轴。 选择排序规则。 修改横轴和纵轴的标签名称 切换到仪表板,拖拽组件到仪表板 效果如下:

今天去面一个点工,HR要我会数据库,Linux还有Python,这合理吗

软件测试出路在哪? 业务编程!! 1、软件测试的变化趋势 变化趋势1: 功能测试是核心,但是价值降低 目前测试这个行业,还是有大量的点工。但是行业的进步,技术的创新,导致了企业的需求…

mapper向mapper.xml传参中文时的乱码问题

1.起因: 在idea中进行模糊查询传参时,发现在idea中查中文查不出记录,在navicate中可以查出来。 2.猜测: 1.idea中的编码问题导致的乱码。 2.idea和navicate的编码一致性导致的乱码。 3.mapper向mapper.xml传参后出现乱码。 3.解…

「 典型安全漏洞系列 」02.SQL注入详解

引言:SQL注入是一个老生常谈且又非常重要的漏洞,导致许多热点的数据泄露事件。尽管学习起来相对简单,但它可能用于某些高危漏洞的利用。这使得它成为初学者的兴趣点,甚至对于更有经验的用户来说,SQL注入也是基本知识。…

快速打通 Vue 3(四):标签的 ref 属性与 Vue3 生命周期

很激动进入了 Vue 3 的学习,作为一个已经上线了三年多的框架,很多项目都开始使用 Vue 3 来编写了 这一组文章主要聚焦于 Vue 3 的新技术和新特性 如果想要学习基础的 Vue 语法可以看我专栏中的其他博客 Vue(一):Vue 入…

【数据结构】--二叉树递归题记

最近写了几道关于二叉树的剑指offer题,和小伙伴们分享一下心得。 🌈对称的二叉树 请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。 思路分析: 对于二叉树的问题来说肯…

谷达冠楠:抖音开网店创业怎么做

随着互联网的发展,越来越多的人选择在网上创业。而抖音作为目前最火的短视频平台之一,也成为了许多人开网店的首选。那么,如何在抖音上开网店创业呢?下面就来详细介绍一下。 第一步:注册账号 首先,你需要在抖音上注册…

登录模块的实现

一.前期的准备工作 1.页面的布局 (1)表单的校验: 利用element-ui提供的文档绑定rules规则后实现校验 (2)跨域的配置 : 利用proxy代理来解决跨域的问题 (3)axios拦截器的配置 两个点:1. 在请求拦截的成功回调中,如果token,因为调用其它的接口需要token才能调取。 在请…

【排序】对各种排序的总结

文章目录 前言1. 排序算法的复杂度及稳定性分析2. 排序算法的性能测试2.1 重复率较低的随机值排序测试2.2 重复率较高的随机值排序测试 前言 本篇是基于我这几篇博客做的一个总结: 《简单排序》(含:冒泡排序,直接插入排序&#x…

【Docker】概述与安装

🥳🥳Welcome Huihuis Code World ! !🥳🥳 接下来看看由辉辉所写的关于Docker的相关操作吧 目录 🥳🥳Welcome Huihuis Code World ! !🥳🥳 一. Docker的概述 1.Docker为什么出现 2…

【AI视野·今日CV 计算机视觉论文速览 第285期】Mon, 8 Jan 2024

AI视野今日CS.CV 计算机视觉论文速览 Mon, 8 Jan 2024 Totally 66 papers 👉上期速览✈更多精彩请移步主页 Daily Computer Vision Papers Denoising Vision Transformers Authors Jiawei Yang, Katie Z Luo, Jiefeng Li, Kilian Q Weinberger, Yonglong Tian, Yue…

YOLOv8-Seg改进:轻量化改进 | 超越RepVGG!浙大阿里提出OREPA:在线卷积重参数化

🚀🚀🚀本文改进:OREPA在线卷积重参数化巧妙的和YOLOV8结合,并实现轻量化 🚀🚀🚀YOLOv8-seg创新专栏:http://t.csdnimg.cn/KLSdv 学姐带你学习YOLOv8,从入门到创新,轻轻松松搞定科研; 1)手把手教你如何训练YOLOv8-seg; 2)模型创新,提升分割性能; 3)独家…

(css样式)权限控制el-button是否显示 + 鼠标悬浮停留才会显示el-button

前提: (1)当前物理席位是主任席(seatType‘1’) (2)管制席位TWR_ONE账号发布了内容:管制席——zhouzebiao——。。。。 (3)主任席tatai账号发布了内容&…

C++|47.动态数组 48.C++的std:vector使用优化

动态数组 动态数组叫vector,也是一种定义好的类/数据结构。“定义好”意味着 vector处在std命名空间之中。 vector的存在代表着一种可以调用的数据结构,不用 动态的意思是可以将该数组的大小进行动态调整。 也就意味着起初vector是没有固定大小的。 它是…

Git 基础指令

Git 基础指令 本章涵盖了我们在使用 Git 完成各种操作时将会用到的各种基本命令。 在学习完本章之后,我们应该能够配置并初始化一个仓库(repository)、开始或停止跟踪(track)文件、暂存(stage)…

QT第四天

头文件&#xff1a; #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include<QTime>//时间类 #include<QTimerEvent>//定时器事件类 #include<QtTextToSpeech> //语言播报类 #include<QPushButton> namespace Ui { class Widget; }clas…