【第10章】【编解码器框架】

netty 同时被 2 个专栏收录
13 篇文章 0 订阅
13 篇文章 1 订阅

【第10章】【编解码器框架】


【博文目录>>>】


【工程下载>>>】


Netty 提供了多种组件,简化了为了支持广泛的协议而创建自定义的编解码器的过程。

10.1 什么是编解码器


每个网络应用程序都必须定义如何解析在两个节点之间来回传输的原始字节,以及如何将其和目标应用程序的数据格式做相互转换。这种转换逻辑由编解码器处理,编解码器由编码器和解码器组成,它们每种都可以将字节流从一种格式转换为另一种格式。那么它们的区别是什么呢?

如果将消息看作是对于特定的应用程序具有具体含义的结构化的字节序列—它的数据。那么编码器是将消息转换为适合于传输的格式(最有可能的就是字节流);而对应的解码器则是将网络字节流转换回应用程序的消息格式。因此,编码器操作出站数据,而解码器处理入站数据。

10.2 解码器


在这一节中,我们将研究Netty 所提供的解码器类,并提供关于何时以及如何使用它们的具体示例。这些类覆盖了两个不同的用例:

  • 将字节解码为消息——ByteToMessageDecoder 和ReplayingDecoder;

  • 将一种消息类型解码为另一种——MessageToMessageDecoder。

因为解码器是负责将入站数据从一种格式转换到另一种格式的,所以知道Netty 的解码器实现了ChannelInboundHandler 也不会让你感到意外。

什么时候会用到解码器呢?很简单:每当需要为ChannelPipeline 中的下一个ChannelInboundHandler 转换入站数据时会用到。此外,得益于ChannelPipeline 的设计,可以将多个解码器链接在一起,以实现任意复杂的转换逻辑,这也是Netty 是如何支持代码的模块化以及复用的一个很好的例子。

10.2.1 抽象类ByteToMessageDecoder


将字节解码为消息(或者另一个字节序列)是一项如此常见的任务,以至于Netty 为它提供了一个抽象的基类:ByteToMessageDecoder。由于你不可能知道远程节点是否会一次性地发送一个完整的消息,所以这个类会对入站数据进行缓冲,直到它准备好处理。表10-1 解释了它最重要的两个方法。

这里写图片描述

下面举一个如何使用这个类的示例,假设你接收了一个包含简单int 的字节流,每个int都需要被单独处理。在这种情况下,你需要从入站ByteBuf 中读取每个int,并将它传递给ChannelPipeline 中的下一个ChannelInboundHandler。为了解码这个字节流,你要扩展ByteToMessageDecoder 类。(需要注意的是,原子类型的int 在被添加到List 中时,会被自动装箱为Integer。)该设计如图10-1 所示。
每次从入站ByteBuf 中读取4 字节,将其解码为一个int,然后将它添加到一个List 中。当没有更多的元素可以被添加到该List 中时,它的内容将会被发送给下一个ChannelInboundHandler。

这里写图片描述

// 代码清单10-1 ToIntegerDecoder 类扩展了ByteToMessageDecoder
// 扩展ByteToMessageDecoder 类,以将字节解码为特定的格式
public class ToIntegerDecoder extends ByteToMessageDecoder {
    @Override
    public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        // 检查是否至少有4字节可读(一个int的字节长度)
        if (in.readableBytes() >= 4) {
            // 从入站ByteBuf 中读取一个int,并将其添加到解码消息的List 中
            out.add(in.readInt());
        }
    }
}

虽然ByteToMessageDecoder 使得可以很简单地实现这种模式,但是你可能会发现,在调用readInt()方法前不得不验证所输入的ByteBuf 是否具有足够的数据有点繁琐。在下一节中,我们将讨论ReplayingDecoder,它是一个特殊的解码器,以少量的开销消除了这个步骤。

编解码器中的引用计数

引用计数需要特别的注意。对于编码器和解码器来说,其过程也是相当的简单:一旦消息被编码或者解码,它就会被ReferenceCountUtil.release(message)调用自动释放。如果你需要保留引用以便稍后使用,那么你可以调用ReferenceCountUtil.retain(message)方法。这将会增加该引用计数,从而防止该消息被释放。

10.2.2 抽象类ReplayingDecoder


ReplayingDecoder扩展了ByteToMessageDecoder类(如代码清单10-1 所示),使得我们不必调用readableBytes()方法。它通过使用一个自定义的ByteBuf实现,ReplayingDecoderByteBuf,包装传入的ByteBuf实现了这一点,其将在内部执行该调用,指调用readableBytes()方法。
这个类的完整声明是:

public abstract class ReplayingDecoder<S> extends ByteToMessageDecoder

类型参数S 指定了用于状态管理的类型,其中Void 代表不需要状态管理。代码清单10-2展示了基于ReplayingDecoder 重新实现的ToIntegerDecoder。

// 代码清单10-2 ToIntegerDecoder2 类扩展了ReplayingDecoder
// 扩展ReplayingDecoder<Void>以将字节解码为消息
public class ToIntegerDecoder2 extends ReplayingDecoder<Void> {

    // 传入的ByteBuf 是ReplayingDecoderByteBuf
    @Override
    public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        // 从入站ByteBuf 中读取一个int,并将其添加到解码消息的List 中
        out.add(in.readInt());
    }
}

和之前一样,从ByteBuf中提取的int将会被添加到List中。如果没有足够的字节可用,这个readInt()方法的实现将会抛出一个Error,这里实际上抛出的是一个Signal,详见io.netty.util.Signal 类。

其将在基类中被捕获并处理。当有更多的数据可供读取时,该decode()方法将会被再次调用。(参见表10-1 中关于decode()方法的描述。)请注意ReplayingDecoderByteBuf 的下面这些方面:

  • 并不是所有的ByteBuf 操作都被支持,如果调用了一个不被支持的方法,将会抛出一个UnsupportedOperationException;

  • ReplayingDecoder 稍慢于ByteToMessageDecoder。

如果对比代码清单10-1 和代码清单10-2,你会发现后者明显更简单。示例本身是很基本的,所以请记住,在真实的、更加复杂的情况下,使用一种或者另一种作为基类所带来的差异可能是很显著的。这里有一个简单的准则:如果使用ByteToMessageDecoder 不会引入太多的复杂性,那么请使用它;否则,请使用ReplayingDecoder。

更多的解码器

下面的这些类处理更加复杂的用例:

  • io.netty.handler.codec.LineBasedFrameDecoder—这个类在Netty 内部也有使用,它使用了行尾控制字符(\n 或者\r\n)来解析消息数据;

  • io.netty.handler.codec.http.HttpObjectDecoder—一个HTTP 数据的解码器。

在io.netty.handler.codec 子包下面,你将会发现更多用于特定用例的编码器和解码器实现。更多有关信息参见Netty 的Javadoc。

10.2.3 抽象类MessageToMessageDecoder


在这一节中,我们将解释如何使用下面的抽象基类在两个消息格式之间进行转换(例如,从一种POJO 类型转换为另一种):

public abstract class MessageToMessageDecoder<I> extends ChannelInboundHandlerAdapter

类型参数I 指定了decode()方法的输入参数msg 的类型,它是你必须实现的唯一方法。表10-2 展示了这个方法的详细信息。

这里写图片描述

在这个示例中,我们将编写一个IntegerToStringDecoder 解码器来扩展MessageToMessageDecoder。它的decode()方法会把Integer 参数转换为它的String表示,并将拥有下列签名:

public void decode( ChannelHandlerContext ctx, Int eger msg, List<Object> out ) throws Exception

和之前一样,解码的String将被添加到传出的List中,并转发给下一个ChannelInboundHandler。该设计如图10-2 所示。

这里写图片描述

代码清单10-3 给出了IntegerToStringDecoder 的实现。

// 代码清单10-3 IntegerToStringDecoder 类
// 扩展了MessageToMessageDecoder<Integer>
public class IntegerToStringDecoder extends MessageToMessageDecoder<Integer> {
    @Override
    public void decode(ChannelHandlerContext ctx, Integer msg, List<Object> out) throws Exception {
        // 将Integer 消息转换为它的String 表示,并将其添 加到输出的List 中
        out.add(String.valueOf(msg));
    }
}

HttpObjectAggregator

有关更加复杂的例子,请研究io.netty.handler.codec.http.HttpObjectAggregator 类,它扩展了MessageToMessageDecoder。

10.2.4 TooLongFrameException 类


由于Netty 是一个异步框架,所以需要在字节可以解码之前在内存中缓冲它们。因此,不能让解码器缓冲大量的数据以至于耗尽可用的内存。为了解除这个常见的顾虑,Netty 提供了TooLongFrameException 类,其将由解码器在帧超出指定的大小限制时抛出。

为了避免这种情况,你可以设置一个最大字节数的阈值,如果超出该阈值,则会导致抛出一个TooLongFrameException(随后会被ChannelHandler.exceptionCaught()方法捕获)。然后,如何处理该异常则完全取决于该解码器的用户。某些协议(如HTTP)可能允许你返回一个特殊的响应。而在其他的情况下,唯一的选择可能就是关闭对应的连接。

代码清单10-4 展示了ByteToMessageDecoder 是如何使用TooLongFrameException来通知ChannelPipeline 中的其他ChannelHandler 发生了帧大小溢出的。需要注意的是,如果你正在使用一个可变帧大小的协议,那么这种保护措施将是尤为重要的。

// 代码清单10-4 TooLongFrameException
// 扩展ByteToMessageDecoder以将字节解码为消息
public class SafeByteToMessageDecoder extends ByteToMessageDecoder {
    private static final int MAX_FRAME_SIZE = 1024;

    @Override
    public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        int readable = in.readableBytes();
        // 检查缓冲区中是否有超过MAX_FRAME_SIZE个字节
        if (readable > MAX_FRAME_SIZE) {
            // 跳过所有的可读字节,抛出TooLongFrameException 并通知ChannelHandler
            in.skipBytes(readable);
            throw new TooLongFrameException("Frame too big!");
        }
        // do something
        // ...
    }
}

到目前为止,我们已经探讨了解码器的常规用例,以及Netty 所提供的用于构建它们的抽象基类。但是解码器只是硬币的一面。硬币的另一面是编码器,它将消息转换为适合于传出传输的格式。这些编码器完备了编解码器API,它们将是我们的下一个主题。

10.3 编码器


回顾一下我们先前的定义,编码器实现了ChannelOutboundHandler,并将出站数据从一种格式转换为另一种格式,和我们方才学习的解码器的功能正好相反。Netty 提供了一组类,用于帮助你编写具有以下功能的编码器:

  • 将消息编码为字节;

  • 将消息编码为消息

我们将首先从抽象基类MessageToByteEncoder 开始来对这些类进行考察。

10.3.1 抽象类MessageToByteEncoder


前面我们看到了如何使用ByteToMessageDecoder 来将字节转换为消息。现在我们将使用MessageToByteEncoder 来做逆向的事情。表10-3 展示了该API。

这里写图片描述

你可能已经注意到了,这个类只有一个方法,而解码器有两个。原因是解码器通常需要在Channel 关闭之后产生最后一个消息(因此也就有了decodeLast()方法)。这显然不适用于编码器的场景——在连接被关闭之后仍然产生一个消息是毫无意义的。

图10-3 展示了ShortToByteEncoder,其接受一个Short 类型的实例作为消息,将它编码为Short 的原子类型值,并将它写入ByteBuf 中,其将随后被转发给ChannelPipeline 中的下一个ChannelOutboundHandler。每个传出的Short 值都将会占用ByteBuf 中的2 字节。ShortToByteEncoder 的实现如代码清单10-5 所示。

// 代码清单10-5 ShortToByteEncoder 类
// 扩展了MessageToByteEncoder
public class ShortToByteEncoder extends MessageToByteEncoder<Short> {
    @Override
    public void encode(ChannelHandlerContext ctx, Short msg, ByteBuf out) throws Exception {
        // 将Short 写入ByteBuf 中
        out.writeShort(msg);
    }
}

Netty 提供了一些专门化的MessageToByteEncoder,你可以基于它们实现自己的编码器。WebSocket08FrameEncoder 类提供了一个很好的实例。你可以在io.netty.handler.codec.http.websocketx 包中找到它。

这里写图片描述

10.3.2 抽象类MessageToMessageEncoder


你已经看到了如何将入站数据从一种消息格式解码为另一种。为了完善这幅图,我们将展示对于出站数据将如何从一种消息编码为另一种。MessageToMessageEncoder 类的encode()方法提供了这种能力,如表10-4 所示。

这里写图片描述

为了演示,代码清单10-6 使用IntegerToStringEncoder 扩展了MessageToMessageEncoder。其设计如图10-4 所示。如代码清单10-6 所示,编码器将每个出站Integer 的String 表示添加到了该List 中。

这里写图片描述

// 代码清单10-6 IntegerToStringEncoder 类
// 扩展了MessageToMessageEncoder
public class IntegerToStringEncoder extends MessageToMessageEncoder<Integer> {
    @Override
    public void encode(ChannelHandlerContext ctx, Integer msg, List<Object> out) throws Exception {
        // 将Integer 转换为String,并将其添加到List 中
        out.add(String.valueOf(msg));
    }
}

关于有趣的MessageToMessageEncoder 的专业用法,请查看io.netty.handler.codec.protobuf.ProtobufEncoder 类,它处理了由Google 的Protocol Buffers 规范所定的数据格式。

10.4 抽象的编解码器类


虽然我们一直将解码器和编码器作为单独的实体讨论,但是你有时将会发现在同一个类中管理入站和出站数据和消息的转换是很有用的。Netty 的抽象编解码器类正好用于这个目的,因为它们每个都将捆绑一个解码器/编码器对,以处理我们一直在学习的这两种类型的操作。正如同你可能已经猜想到的,这些类同时实现了ChannelInboundHandler 和ChannelOutboundHandler 接口。

为什么我们并没有一直优先于单独的解码器和编码器使用这些复合类呢?因为通过尽可能地将这两种功能分开,最大化了代码的可重用性和可扩展性,这是Netty 设计的一个基本原则。

在我们查看这些抽象的编解码器类时,我们将会把它们与相应的单独的解码器和编码器进行比较和参照。

10.4.1 抽象类ByteToMessageCodec


让我们来研究这样的一个场景:我们需要将字节解码为某种形式的消息,可能是POJO,随后再次对它进行编码。ByteToMessageCodec 将为我们处理好这一切,因为它结合了ByteToMessageDecoder 以及它的逆向——MessageToByteEncoder。表10-5 列出了其中重要的方法。

任何的请求/响应协议都可以作为使用ByteToMessageCodec的理想选择。例如,在某个SMTP的实现中,编解码器将读取传入字节,并将它们解码为一个自定义的消息类型,如SmtpRequest。而在接收端,当一个响应被创建时,将会产生一个SmtpResponse,其将被编码回字节以便进行传输。

这里写图片描述

10.4.2 抽象类MessageToMessageCodec


在10.3.1 节中,你看到了一个扩展了MessageToMessageEncoder 以将一种消息格式转换为另外一种消息格式的例子。通过使用MessageToMessageCodec,我们可以在一个单个的类中实现该转换的往返过程。MessageToMessageCodec 是一个参数化的类,定义如下:

public abstract class MessageToMessageCodec<INBOUND_IN,OUTBOUND_IN>

表10-6 列出了其中重要的方法。

这里写图片描述

decode() 方法是将INBOUND_IN 类型的消息转换为OUTBOUND_IN 类型的消息, 而encode()方法则进行它的逆向操作。将INBOUND_IN类型的消息看作是通过网络发送的类型,而将OUTBOUND_IN类型的消息看作是应用程序所处理的类型,将可能有所裨益。

虽然这个编解码器可能看起来有点高深,但是它所处理的用例却是相当常见的:在两种不同的消息API 之间来回转换数据。当我们不得不和使用遗留或者专有消息格式的API 进行互操作时,我们经常会遇到这种模式。

WebSocket 协议

下面关于MessageToMessageCodec 的示例引用了一个新出的WebSocket 协议,这个协议能实现Web 浏览器和服务器之间的全双向通信。我们将在第12 章中详细地讨论Netty 对于WebSocket 的支持。

代码清单10-7 展示了这样的对话可能的实现方式。我们的WebSocketConvertHandler在参数化MessageToMessageCodec时将使用INBOUND_IN类型的WebSocketFrame,以及OUTBOUND_IN类型的MyWebSocketFrame,后者是WebSocketConvertHandler本身的一个静态嵌套类。

// 代码清单10-7 使用MessageToMessageCodec
@ChannelHandler.Sharable
public class WebSocketConvertHandler extends MessageToMessageCodec<WebSocketFrame,
        WebSocketConvertHandler.MyWebSocketFrame> {
    // 将MyWebSocketFrame 编码为指定的WebSocketFrame子类型
    @Override
    protected void encode(ChannelHandlerContext ctx, WebSocketConvertHandler.MyWebSocketFrame msg,
            List<Object> out) throws Exception {
        // 实例化一个指定子类型的WebSocketFrame
        ByteBuf payload = msg.getData().duplicate().retain();
        switch (msg.getType()) {
            case BINARY:
                out.add(new BinaryWebSocketFrame(payload));
                break;
            case TEXT:
                out.add(new TextWebSocketFrame(payload));
                break;
            case CLOSE:
                out.add(new CloseWebSocketFrame(true, 0, payload));
                break;
            case CONTINUATION:
                out.add(new ContinuationWebSocketFrame(payload));
                break;
            case PONG:
                out.add(new PongWebSocketFrame(payload));
                break;
            case PING:
                out.add(new PingWebSocketFrame(payload));
                break;
            default:
                throw new IllegalStateException("Unsupported websocket msg " + msg);
        }
    }

    // 将WebSocketFrame 解码为MyWebSocketFrame,并设置FrameType
    @Override
    protected void decode(ChannelHandlerContext ctx, WebSocketFrame msg, List<Object> out) throws Exception {
        ByteBuf payload = msg.content().duplicate().retain();
        if (msg instanceof BinaryWebSocketFrame) {
            out.add(new MyWebSocketFrame(
                    MyWebSocketFrame.FrameType.BINARY, payload));
        } else if (msg instanceof CloseWebSocketFrame) {
            out.add(new MyWebSocketFrame(
                    MyWebSocketFrame.FrameType.CLOSE, payload));
        } else if (msg instanceof PingWebSocketFrame) {
            out.add(new MyWebSocketFrame(
                    MyWebSocketFrame.FrameType.PING, payload));
        } else if (msg instanceof PongWebSocketFrame) {
            out.add(new MyWebSocketFrame(
                    MyWebSocketFrame.FrameType.PONG, payload));
        } else if (msg instanceof TextWebSocketFrame) {
            out.add(new MyWebSocketFrame(
                    MyWebSocketFrame.FrameType.TEXT, payload));
        } else if (msg instanceof ContinuationWebSocketFrame) {
            out.add(new MyWebSocketFrame(
                    MyWebSocketFrame.FrameType.CONTINUATION, payload));
        } else {
            throw new IllegalStateException(
                    "Unsupported websocket msg " + msg);
        }
    }

    // 声明WebSocketConvertHandler所使用的OUTBOUND_IN 类型
    public static final class MyWebSocketFrame {
        // 定义拥有被包装的有效负载的WebSocketFrame的类型
        public enum FrameType {
            BINARY,
            CLOSE,
            PING,
            PONG,
            TEXT,
            CONTINUATION
        }

        private final FrameType type;
        private final ByteBuf data;

        public MyWebSocketFrame(FrameType type, ByteBuf data) {
            this.type = type;
            this.data = data;
        }

        public FrameType getType() {
            return type;
        }

        public ByteBuf getData() {
            return data;
        }
    }
}

10.4.3 CombinedChannelDuplexHandler 类


正如我们前面所提到的,结合一个解码器和编码器可能会对可重用性造成影响。但是,有一种方法既能够避免这种惩罚,又不会牺牲将一个解码器和一个编码器作为一个单独的单元部署所带来的便利性。CombinedChannelDuplexHandler 提供了这个解决方案,其声明为:

public class CombinedChannelDuplexHandler <I extends ChannelInboundHandler, O extends ChannelOutboundHandler>

这个类充当了ChannelInboundHandler 和ChannelOutboundHandler(该类的类型参数I 和O)的容器。通过提供分别继承了解码器类和编码器类的类型,我们可以实现一个编解码器,而又不必直接扩展抽象的编解码器类。我们将在下面的示例中说明这一点。

首先,让我们研究代码清单10-8 中的ByteToCharDecoder。注意,该实现扩展了ByteToMessageDecoder,因为它要从ByteBuf 中读取字符。

// 代码清单10-8 ByteToCharDecoder 类
// 扩展了ByteToMessageDecoder
public class ByteToCharDecoder extends ByteToMessageDecoder {
    @Override
    public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        // 将一个或者多个Character对象添加到传出的List 中
        if (in.readableBytes() >= 2) {
            out.add(in.readChar());
        }
    }
}

这里的decode()方法一次将从ByteBuf 中提取2 字节,并将它们作为char 写入到List中,其将会被自动装箱为Character 对象。

代码清单10-9 包含了CharToByteEncoder,它能将Character 转换回字节。这个类扩展了MessageToByteEncoder,因为它需要将char 消息编码到ByteBuf 中。这是通过直接写入ByteBuf 做到的。

// 代码清单10-9 CharToByteEncoder 类
// 扩展了MessageToByteEncoder
public class CharToByteEncoder extends MessageToByteEncoder<Character> {
    @Override
    public void encode(ChannelHandlerContext ctx, Character msg, ByteBuf out) throws Exception {
        // 将Character 解码为char,并将其写入到出站ByteBuf 中
        out.writeChar(msg);
    }
}

既然我们有了解码器和编码器,我们将会结合它们来构建一个编解码器。代码清单10-10 展示了这是如何做到的。

// 代码清单10-10 CombinedChannelDuplexHandler<I,O>
// 通过该解码器和编码器实现参数化CombinedByteCharCodec
public class CombinedByteCharCodec extends CombinedChannelDuplexHandler<ByteToCharDecoder, CharToByteEncoder> {
    public CombinedByteCharCodec() {
        // 将委托实例传递给父类
        super(new ByteToCharDecoder(), new CharToByteEncoder());
    }
}
  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

打赏
文章很值,打赏犒劳作者一下
相关推荐
<p style="font-size:16px;color:#666666;"> <img src="https://img-bss.csdn.net/202001311426171105.png" alt="" /> </p> <p style="font-size:16px;color:#666666;"> <strong><span style="font-size:20px;">课程目标</span></strong> </p> <p style="font-size:16px;color:#666666;"> 《从零开始学Scrapy网络爬虫》从零开始,循序渐进地介绍了目前流行的网络爬虫框架Scrapy。即使你没有任何编程基础,学习起来也不会有压力,因为我们有针对性地介绍了Python编程技术。另外,《从零开始学Scrapy网络爬虫》在讲解过程中以案例为导向,通过对案例的不断迭代、优化,让读者加深对知识的理解,并通过14个项目案例,提高学习者解决实际问题的能力。 </p> <p style="font-size:16px;color:#666666;"> <br /> </p> <p style="font-size:16px;color:#666666;"> <strong><span style="font-size:20px;">适合对象</span></strong> </p> <p style="font-size:16px;color:#666666;"> 爬虫初学者、爬虫爱好者、高校相关专业的学生、数据爬虫工程师。 </p> <p style="font-size:16px;color:#666666;"> <br /> </p> <p style="font-size:16px;color:#666666;"> <span style="font-size:20px;"><strong>课程介绍</strong></span> </p> <p style="font-size:16px;color:#666666;"> 《从零开始学Scrapy网络爬虫》共13。其中,第1~4为基础篇,介绍了Python基础、网络爬虫基础、Scrapy框架及基本的爬虫功能。第5~10为进阶篇,介绍了如何将爬虫数据存储于MySQL、MongoDB和Redis数据库中;如何实现异步AJAX数据的爬取;如何使用Selenium和Splash实现动态网站的爬取;如何实现模拟登录功能;如何突破反爬虫技术,以及如何实现文件和图片的下载。第11~13为高级篇,介绍了使用Scrapy-Redis实现分布式爬虫;使用Scrapyd和Docker部署分布式爬虫;使用Gerapy管理分布式爬虫,并实现了一个抢票软件的综合项目。 </p> <p style="font-size:16px;color:#666666;"> <span style="color:#FF0000;">      由于目标网站可能会对页面进行改版或者升级反爬虫措施,如果发现视频中的方法无法成功爬取数据,敬请按照页面实际情况修改XPath的路径表达式。视频教程主要提供理论、方法支撑。我们也会在第一时间更新源代码,谢谢!</span> </p> <p style="font-size:16px;color:#666666;"> <img src="https://img-bss.csdn.net/202001311426306665.png" alt="" /> </p> <p style="font-size:16px;color:#666666;"> <strong><span style="font-size:20px;">课程特色</span></strong> </p> <p style="font-size:16px;"> <img src="https://img-bss.csdn.net/202001311426415123.png" alt="" /> </p> <div> <br /> </div>
<div style="color:rgba(0,0,0,.75);"> <span style="color:#4d4d4d;"> </span> <div style="color:rgba(0,0,0,.75);"> <span style="color:#4d4d4d;"> </span> <div style="color:rgba(0,0,0,.75);"> <div style="color:rgba(0,0,0,.75);"> <span style="color:#4d4d4d;">当前课程中商城项目的实战源码是我发布在 GitHub 上的开源项目 newbee-mall (新蜂商城),目前已有 6300 多个 star,</span><span style="color:#4d4d4d;">本课程是一个 Spring Boot 技术栈的实战类课程,课程共分为 3 大部分,前面两个部分为基础环境准备和相关概念介绍,第三个部分是 Spring Boot 商城项目功能的讲解,让大家实际操作并实践上手一个大型的线上商城项目,并学习到一定的开发经验以及其中的开发技巧。<br /> 商城项目所涉及的功能结构图整理如下:<br /> </span> </div> <div style="color:rgba(0,0,0,.75);">   </div> <div style="color:rgba(0,0,0,.75);"> <p style="color:#4d4d4d;"> <img alt="modules" src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9uZXdiZWUtbWFsbC5vc3MtY24tYmVpamluZy5hbGl5dW5jcy5jb20vcG9zdGVyL3N0b3JlL25ld2JlZS1tYWxsLXMucG5n?x-oss-process=image/format,png" /> </p> </div> <p style="color:rgba(0,0,0,.75);"> <strong><span style="color:#e53333;">课程特色</span></strong> </p> <p style="color:rgba(0,0,0,.75);">   </p> <div style="color:rgba(0,0,0,.75);">   </div> <div style="color:rgba(0,0,0,.75);"> <ul> <li> 对新手开发者十分友好,无需复杂的操作步骤,仅需 2 秒就可以启动这个完整的商城项目 </li> <li> 最终的实战项目是一个企业级别的 Spring Boot 大型项目,对于各个阶段的 Java 开发者都是极佳的选择 </li> <li> 实践项目页面美观且实用,交互效果完美 </li> <li> 教程详细开发教程详细完整、文档资源齐全 </li> <li> 代码+讲解+演示网站全方位保证,向 Hello World 教程说拜拜 </li> <li> 技术栈新颖且知识点丰富,学习后可以提升大家对于知识的理解和掌握,可以进一步提升你的市场竞争力 </li> </ul> </div> <p style="color:rgba(0,0,0,.75);">   </p> <p style="color:rgba(0,0,0,.75);"> <span style="color:#e53333;">课程预览</span> </p> <p style="color:rgba(0,0,0,.75);">   </p> <div style="color:rgba(0,0,0,.75);">   </div> <div style="color:rgba(0,0,0,.75);"> <p style="color:#4d4d4d;"> 以下为商城项目的页面和功能展示,分别为: </p> </div> <div style="color:rgba(0,0,0,.75);"> <ul> <li> 商城首页 1<br /> <img alt="" src="https://img-bss.csdnimg.cn/202103050347585499.gif" /> </li> <li> 商城首页 2<br /> <img alt="" src="https://img-bss.csdn.net/202005181054413605.png" /> </li> <li>   </li> <li> 购物车<br /> <img alt="cart" src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9uZXdiZWUtbWFsbC5vc3MtY24tYmVpamluZy5hbGl5dW5jcy5jb20vcG9zdGVyL3Byb2R1Y3QvY2FydC5wbmc?x-oss-process=image/format,png" /> </li> <li> 订单结算<br /> <img alt="settle" src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9uZXdiZWUtbWFsbC5vc3MtY24tYmVpamluZy5hbGl5dW5jcy5jb20vcG9zdGVyL3Byb2R1Y3Qvc2V0dGxlLnBuZw?x-oss-process=image/format,png" /> </li> <li> 订单列表<br /> <img alt="orders" src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9uZXdiZWUtbWFsbC5vc3MtY24tYmVpamluZy5hbGl5dW5jcy5jb20vcG9zdGVyL3Byb2R1Y3Qvb3JkZXJzLnBuZw?x-oss-process=image/format,png" /> </li> <li> 支付页面<br /> <img alt="" src="https://img-bss.csdn.net/201909280301493716.jpg" /> </li> <li> 后台管理系统登录页<br /> <img alt="login" src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9uZXdiZWUtbWFsbC5vc3MtY24tYmVpamluZy5hbGl5dW5jcy5jb20vcG9zdGVyL3Byb2R1Y3QvbWFuYWdlLWxvZ2luLnBuZw?x-oss-process=image/format,png" /> </li> <li> 商品管理<br /> <img alt="goods" src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9uZXdiZWUtbWFsbC5vc3MtY24tYmVpamluZy5hbGl5dW5jcy5jb20vcG9zdGVyL3Byb2R1Y3QvbWFuYWdlLWdvb2RzLnBuZw?x-oss-process=image/format,png" /> </li> <li> 商品编辑<br /> <img alt="" src="https://img-bss.csdnimg.cn/202103050348242799.png" /> </li> </ul> </div> </div> </div> </div>
<p style="color:#333333;"> <strong> </strong> </p> <p style="font-family:"color:#222226;font-size:14px;background-color:#FFFFFF;"> <strong><span style="color:#337FE5;">[为什么要学习Spring Cloud微服务]</span> </strong> </p> <p style="font-family:"color:#222226;font-size:14px;background-color:#FFFFFF;"> <strong><span style="color:#4D555D;"></span> </strong> </p> <p class="ql-long-24357476" style="font-family:"color:#222226;font-size:14px;background-color:#FFFFFF;"> <strong><span style="font-family:"background-color:#FFFFFF;">SpringCloud作为主流微服务框架,<span style="color:#4D555D;">已成为各互联网公司的首选框架,国内外企业占有率持续攀升,</span>是Java工程师的必备技能。</span><span style="font-family:"background-color:#FFFFFF;">就连大名鼎鼎的阿里巴巴</span><span style="font-family:"background-color:#FFFFFF;">dubbo</span><span style="font-family:"background-color:#FFFFFF;">也正式更名为</span><span style="font-family:"background-color:#FFFFFF;">Spring Cloud Alibaba</span><span style="font-family:"background-color:#FFFFFF;">,成为了</span><span style="font-family:"background-color:#FFFFFF;">Spring Cloud </span><span style="font-family:"background-color:#FFFFFF;">微服务中的一个子模块。</span><span style="font-family:"background-color:#FFFFFF;"></span><span style="font-family:"background-color:#FFFFFF;">Spring Cloud是企业架构转型、个人能力提升、架构师进阶的不二选择。</span> </strong> </p> <p style="color:#333333;"> <strong><strong><br /> </strong> </strong> </p> <strong><span style="font-family:"color:#337FE5;font-size:14px;background-color:#FFFFFF;">【推荐你学习这门课的理由】</span><br /> </strong> <p> <br /> </p> <p> <span>1、</span><span style="color:#222226;font-family:"font-size:14px;background-color:#FFFFFF;">本课程总计</span><span style="background-color:#FFFFFF;">29</span><span style="color:#222226;font-family:"font-size:14px;background-color:#FFFFFF;">课时,<span style="color:#333333;">从微服务是什么、能够做什么开始讲起,绝对的零基础入门</span></span><span></span> </p> <p> <span style="background-color:#FFFFFF;">2、<span style="color:#333333;">课程附带全部26个项目源码,230页高清PDF正版课件</span><span style="color:#333333;"></span></span> </p> <p> <span style="background-color:#FFFFFF;"><b><br /> </b></span> </p> <p> <span style="background-color:#FFFFFF;"><b><span style="color:#337FE5;">【课程知识梳理】</span></b></span> </p> <p> <span style="background-color:#FFFFFF;"><b>1、</b></span><span style="color:#333333;">先讲解了什么是单体架构、什么是微服务架构、他们之间有什么区别和联系,各自有什么优缺点。</span> </p> <p> <span style="color:#333333;">2、</span><span style="color:#333333;">从本质入手,使用最简单的Spring Boot搭建微服务,让你认清微服务是一种思想和解决问题的手段,而不是新兴技术。</span> </p> <p style="color:#333333;"> 3、讲解Spring Boot 与Spring Cloud 微服务架构之间的联系,原生的RestTemplate工具,以及Actuator监控端点的使用。 </p> <p style="color:#333333;"> 4、带着微服务所带来的各种优缺点,为大家引入服务发现与注册的概念和原理,从而引入我们的第一个注册中心服务Eureka。 </p> <p style="color:#333333;"> 5、引入负载均衡的理念,区分什么是服务端负载均衡,什么是客户端负载均衡,进而引入Ribbon负载均衡组件的详细使用。 </p> <p style="color:#333333;"> 6、为了解决微服务之间复杂的调用,降低代码的复杂度,我们引入了Feign声明式客户端,让你几行代码搞定服务的远程调用。 </p> <p style="color:#333333;"> 7、最后为大家介绍了整个微服务体系应该包含什么,学习路线是什么,应该学习什么。 </p> <p style="color:#333333;"> <strong><br /> </strong> </p> <p style="color:#333333;"> <strong><span style="color:#337FE5;">【</span><strong><span style="color:#337FE5;">学习方法</span></strong><span style="color:#337FE5;"></span><span style="color:#337FE5;">】</span></strong> </p> <p style="color:#333333;"> 每一节课程均有代码,最好的方式是静下心来,用一天的时间,或者两个半天时间来学习。 </p> <p style="color:#333333;"> 一边听我的讲解,一边使用我提供的项目代码进行观察和运行。 </p> <p style="color:#333333;"> 只要你能跟住我的节奏,你就可以搞定微服务。 </p> <br />
套餐中一共包含5门程序员必学的数学课程(共47讲) 课程1:《零基础入门微积分》 课程2:《数理统计与概率论》 课程3:《代码学习线性代数》 课程4:《数据处理的最优化》 课程5:《马尔可夫随机过程》 哪些人适合学习这门课程? 1)大学生,平时只学习了数学理论,并未接触如何应用数学解决编程问题; 2)对算法、数据结构掌握程度薄弱的人,数学可以让你更好的理解算法、数据结构原理及应用; 3)看不懂大牛代码设计思想的人,因为所有的程序设计底层逻辑都是数学; 4)想学习新技术,如:人工智能、机器学习、深度学习等,这门课程是你的必修课程; 5)想修炼更好的编程内功,在遇到问题时可以灵活的应用数学思维解决问题。 在这门「专为程序员设计的数学课」系列课中,我们保证你能收获到这些: ①价值300元编程课程大礼包 ②应用数学优化代码的实操方法 ③数学理论在编程实战中的应用 ④程序员必学的5大数学知识 ⑤人工智能领域必修数学课 备注:此课程只讲程序员所需要的数学,即使你数学基础薄弱,也能听懂,只需要初中的数学知识就足矣。 如何听课? 1、CSDNapp:我的-我的内容库-我的课程 2、程序员学院app:我的-学习 3、pc端CSDN官网:https://edu.csdn.net/ 我的订阅-收费课 购课后如何领取免费赠送的编程大礼包? 购课后,添加助教微信: csdn590,按提示领取大礼包
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页

打赏

Wang-Junchao

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值