从零学SpringCloud系列(六):SpringCloud Feign设计原理解析

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
日志服务 SLS,月写入数据量 50GB 1个月
简介: 从零学SpringCloud系列(六):SpringCloud Feign设计原理解析

什么是Feign


Feign 的英文表意为“假装,伪装,变形”, 是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请求,而不用像Java中通过封装HTTP请求报文的方式直接调用。Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的请求,这种请求相对而言比较直观。

Feign被广泛应用在Spring Cloud 的解决方案中,是学习基于Spring Cloud 微服务架构不可或缺的重要组件。


开源项目地址:


https://github.com/OpenFeign/feign


Feign解决了什么问题?


封装了Http调用流程,更适合面向接口化的变成习惯

在服务调用的场景中,我们经常调用基于Http协议的服务,而我们经常使用到的框架可能有HttpURLConnection、Apache HttpComponnets、OkHttp3 、Netty等等,这些框架在基于自身的专注点提供了自身特性。而从角色划分上来看,他们的职能是一致的提供Http调用服务。具体流程如下

20200423110202363.png


Feign是如何设计的?


20180925130831141.png


PHASE 1. 基于面向接口的动态代理方式生成实现类


在使用feign 时,会定义对应的接口类,在接口类上使用Http相关的注解,标识HTTP请求参数信息,如下所示:

interface GitHub {
  @RequestLine("GET /repos/{owner}/{repo}/contributors")
  List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);
}
public static class Contributor {
  String login;
  int contributions;
}
public class MyApp {
  public static void main(String... args) {
    GitHub github = Feign.builder()
                         .decoder(new GsonDecoder())
                         .target(GitHub.class, "https://api.github.com");
    // Fetch and print a list of the contributors to this library.
    List<Contributor> contributors = github.contributors("OpenFeign", "feign");
    for (Contributor contributor : contributors) {
      System.out.println(contributor.login + " (" + contributor.contributions + ")");
    }
  }
}


在Feign 底层,通过基于面向接口的动态代理方式生成实现类,将请求调用委托到动态代理实现类,基本原理如下所示:

20180925131534284.png

 public class ReflectiveFeign extends Feign{
  ///省略部分代码
  @Override
  public <T> T newInstance(Target<T> target) {
    //根据接口类和Contract协议解析方式,解析接口类上的方法和注解,转换成内部的MethodHandler处理方式
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
    for (Method method : target.type().getMethods()) {
      if (method.getDeclaringClass() == Object.class) {
        continue;
      } else if(Util.isDefault(method)) {
        DefaultMethodHandler handler = new DefaultMethodHandler(method);
        defaultMethodHandlers.add(handler);
        methodToHandler.put(method, handler);
      } else {
        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
      }
    }
    InvocationHandler handler = factory.create(target, methodToHandler);
    // 基于Proxy.newProxyInstance 为接口类创建动态实现,将所有的请求转换给InvocationHandler 处理。
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);
    for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
      defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
  }
  //省略部分代码

PHASE 2. 根据Contract协议规则,解析接口类的注解信息,解析成内部表现:


20180925132945810.png


Feign 定义了转换协议,定义如下:

/**
 * Defines what annotations and values are valid on interfaces.
 */
public interface Contract {
  /**
   * Called to parse the methods in the class that are linked to HTTP requests.
   * 传入接口定义,解析成相应的方法内部元数据表示
   * @param targetType {@link feign.Target#type() type} of the Feign interface.
   */
  // TODO: break this and correct spelling at some point
  List<MethodMetadata> parseAndValidatateMetadata(Class<?> targetType);
}

默认Contract 实现


Feign 默认有一套自己的协议规范,规定了一些注解,可以映射成对应的Http请求,如官方的一个例子:


public interface GitHub {
  @RequestLine("GET /repos/{owner}/{repo}/contributors")
  List<Contributor> getContributors(@Param("owner") String owner, @Param("repo") String repository);
  class Contributor {
    String login;
    int contributions;
  }
}

上述的例子中,尝试调用GitHub.getContributors(“foo”,“myrepo”)的的时候,会转换成如下的HTTP请求:


GET /repos/foo/myrepo/contributors
HOST XXXX.XXX.XXX


Feign 默认的协议规范


20200423110652916.png

具体FeignContract 是如何解析的,不在本文的介绍范围内,详情请参考代码:

https://github.com/OpenFeign/feign/blob/master/core/src/main/java/feign/Contract.java


基于Spring MVC的协议规范SpringMvcContract:


当前Spring Cloud 微服务解决方案中,为了降低学习成本,采用了Spring MVC的部分注解来完成 请求协议解析,也就是说 ,写客户端请求接口和像写服务端代码一样:客户端和服务端可以通过SDK的方式进行约定,客户端只需要引入服务端发布的SDK API,就可以使用面向接口的编码方式对接服务:


20180925192337661.png


我们团队内部就是按照这种思路,结合Spring Boot Starter 的特性,定义了服务端starter,


服务消费者在使用的时候,只需要引入Starter,就可以调用服务。这个比较适合平台无关性,接口抽象出来的好处就是可以根据服务调用实现方式自有切换:


可以基于简单的Http服务调用;

可以基于Spring Cloud 微服务架构调用;

可以基于Dubbo SOA服务治理

这种模式比较适合在SaSS混合软件服务的模式下自有切换,根据客户的硬件能力选择合适的方式部署,也可以基于自身的服务集群部署微服务


至于Spring Cloud 是如何实现 协议解析的,可参考代码:

https://github.com/spring-cloud/spring-cloud-openfeign/blob/master/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringMvcContract.java


当然,目前的Spring MVC的注解并不是可以完全使用的,有一些注解并不支持,如@GetMapping,@PutMapping 等,仅支持使用@RequestMapping 等,另外注解继承性方面也有些问题;具体限制细节,每个版本能会有些出入,可以参考上述的代码实现,比较简单。


上述问题在小编采用的springboot2.2.5+springcloud Hoxton.SR3版本中已经不存在,可以支持@GetMapping,@PutMappin 等


Spring Cloud 没有基于Spring MVC 全部注解来做Feign 客户端注解协议解析,个人认为这个是一个不小的坑。在刚入手Spring Cloud 的时候,就碰到这个问题。后来是深入代码才解决的… 这个应该有人写了增强类来处理,暂且不表,先MARK一下,是一个开源代码练手的好机会。


PHASE 3. 基于 RequestBean,动态生成Request


根据传入的Bean对象和注解信息,从中提取出相应的值,来构造Http Request 对象:


20180925200321161.png


PHASE 4. 使用Encoder 将Bean转换成 Http报文正文(消息解析和转码逻辑)


Feign 最终会将请求转换成Http 消息发送出去,传入的请求对象最终会解析成消息体,如下所示:

20180926133002447.png


在接口定义上Feign做的比较简单,抽象出了Encoder 和decoder 接口:


public interface Encoder {
  /** Type literal for {@code Map<String, ?>}, indicating the object to encode is a form. */
  Type MAP_STRING_WILDCARD = Util.MAP_STRING_WILDCARD;
  /**
   * Converts objects to an appropriate representation in the template.
   *  将实体对象转换成Http请求的消息正文中
   * @param object   what to encode as the request body.
   * @param bodyType the type the object should be encoded as. {@link #MAP_STRING_WILDCARD}
   *                 indicates form encoding.
   * @param template the request template to populate.
   * @throws EncodeException when encoding failed due to a checked exception.
   */
  void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException;
  /**
   * Default implementation of {@code Encoder}.
   */
  class Default implements Encoder {
    @Override
    public void encode(Object object, Type bodyType, RequestTemplate template) {
      if (bodyType == String.class) {
        template.body(object.toString());
      } else if (bodyType == byte[].class) {
        template.body((byte[]) object, null);
      } else if (object != null) {
        throw new EncodeException(
            format("%s is not a type supported by this encoder.", object.getClass()));
      }
    }
  }
}
public interface Decoder {
  /**
   * Decodes an http response into an object corresponding to its {@link
   * java.lang.reflect.Method#getGenericReturnType() generic return type}. If you need to wrap
   * exceptions, please do so via {@link DecodeException}.
   *  从Response 中提取Http消息正文,通过接口类声明的返回类型,消息自动装配
   * @param response the response to decode 
   * @param type     {@link java.lang.reflect.Method#getGenericReturnType() generic return type} of
   *                 the method corresponding to this {@code response}.
   * @return instance of {@code type}
   * @throws IOException     will be propagated safely to the caller.
   * @throws DecodeException when decoding failed due to a checked exception besides IOException.
   * @throws FeignException  when decoding succeeds, but conveys the operation failed.
   */
  Object decode(Response response, Type type) throws IOException, DecodeException, FeignException;
  /** Default implementation of {@code Decoder}. */
  public class Default extends StringDecoder {
    @Override
    public Object decode(Response response, Type type) throws IOException {
      if (response.status() == 404) return Util.emptyValueOf(type);
      if (response.body() == null) return null;
      if (byte[].class.equals(type)) {
        return Util.toByteArray(response.body().asInputStream());
      }
      return super.decode(response, type);
    }
  }
}

目前Feign 有以下实现:

20200423111516971.png

PHASE 5. 拦截器负责对请求和返回进行装饰处理


在请求转换的过程中,Feign 抽象出来了拦截器接口,用于用户自定义对请求的操作:

public interface RequestInterceptor {
  /**
   * 可以在构造RequestTemplate 请求时,增加或者修改Header, Method, Body 等信息
   * Called for every request. Add data using methods on the supplied {@link RequestTemplate}.
   */
  void apply(RequestTemplate template);
}


比如,如果希望Http消息传递过程中被压缩,可以定义一个请求拦截器:

public class FeignAcceptGzipEncodingInterceptor extends BaseRequestInterceptor {
  /**
   * Creates new instance of {@link FeignAcceptGzipEncodingInterceptor}.
   *
   * @param properties the encoding properties
   */
  protected FeignAcceptGzipEncodingInterceptor(FeignClientEncodingProperties properties) {
    super(properties);
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public void apply(RequestTemplate template) {
    //  在Header 头部添加相应的数据信息
    addHeader(template, HttpEncoding.ACCEPT_ENCODING_HEADER, HttpEncoding.GZIP_ENCODING,
        HttpEncoding.DEFLATE_ENCODING);
  }
}


PHASE 6. 日志记录


在发送和接收请求的时候,Feign定义了统一的日志门面来输出日志信息 , 并且将日志的输出定义了四个等级:


2020042311164820.png

public abstract class Logger {
  protected static String methodTag(String configKey) {
    return new StringBuilder().append('[').append(configKey.substring(0, configKey.indexOf('(')))
        .append("] ").toString();
  }
  /**
   * Override to log requests and responses using your own implementation. Messages will be http
   * request and response text.
   *
   * @param configKey value of {@link Feign#configKey(Class, java.lang.reflect.Method)}
   * @param format    {@link java.util.Formatter format string}
   * @param args      arguments applied to {@code format}
   */
  protected abstract void log(String configKey, String format, Object... args);
  protected void logRequest(String configKey, Level logLevel, Request request) {
    log(configKey, "---> %s %s HTTP/1.1", request.method(), request.url());
    if (logLevel.ordinal() >= Level.HEADERS.ordinal()) {
      for (String field : request.headers().keySet()) {
        for (String value : valuesOrEmpty(request.headers(), field)) {
          log(configKey, "%s: %s", field, value);
        }
      }
      int bodyLength = 0;
      if (request.body() != null) {
        bodyLength = request.body().length;
        if (logLevel.ordinal() >= Level.FULL.ordinal()) {
          String
              bodyText =
              request.charset() != null ? new String(request.body(), request.charset()) : null;
          log(configKey, ""); // CRLF
          log(configKey, "%s", bodyText != null ? bodyText : "Binary data");
        }
      }
      log(configKey, "---> END HTTP (%s-byte body)", bodyLength);
    }
  }
  protected void logRetry(String configKey, Level logLevel) {
    log(configKey, "---> RETRYING");
  }
  protected Response logAndRebufferResponse(String configKey, Level logLevel, Response response,
                                            long elapsedTime) throws IOException {
    String reason = response.reason() != null && logLevel.compareTo(Level.NONE) > 0 ?
        " " + response.reason() : "";
    int status = response.status();
    log(configKey, "<--- HTTP/1.1 %s%s (%sms)", status, reason, elapsedTime);
    if (logLevel.ordinal() >= Level.HEADERS.ordinal()) {
      for (String field : response.headers().keySet()) {
        for (String value : valuesOrEmpty(response.headers(), field)) {
          log(configKey, "%s: %s", field, value);
        }
      }
      int bodyLength = 0;
      if (response.body() != null && !(status == 204 || status == 205)) {
        // HTTP 204 No Content "...response MUST NOT include a message-body"
        // HTTP 205 Reset Content "...response MUST NOT include an entity"
        if (logLevel.ordinal() >= Level.FULL.ordinal()) {
          log(configKey, ""); // CRLF
        }
        byte[] bodyData = Util.toByteArray(response.body().asInputStream());
        bodyLength = bodyData.length;
        if (logLevel.ordinal() >= Level.FULL.ordinal() && bodyLength > 0) {
          log(configKey, "%s", decodeOrDefault(bodyData, UTF_8, "Binary data"));
        }
        log(configKey, "<--- END HTTP (%s-byte body)", bodyLength);
        return response.toBuilder().body(bodyData).build();
      } else {
        log(configKey, "<--- END HTTP (%s-byte body)", bodyLength);
      }
    }
    return response;
  }
  protected IOException logIOException(String configKey, Level logLevel, IOException ioe, long elapsedTime) {
    log(configKey, "<--- ERROR %s: %s (%sms)", ioe.getClass().getSimpleName(), ioe.getMessage(),
        elapsedTime);
    if (logLevel.ordinal() >= Level.FULL.ordinal()) {
      StringWriter sw = new StringWriter();
      ioe.printStackTrace(new PrintWriter(sw));
      log(configKey, sw.toString());
      log(configKey, "<--- END ERROR");
    }
    return ioe;
  }


PHASE 7 . 基于重试器发送HTTP请求


Feign 内置了一个重试器,当HTTP请求出现IO异常时,Feign会有一个最大尝试次数发送请求,以下是Feign核心


代码逻辑:

final class SynchronousMethodHandler implements MethodHandler {
  // 省略部分代码
  @Override
  public Object invoke(Object[] argv) throws Throwable {
   //根据输入参数,构造Http 请求。
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    // 克隆出一份重试器
    Retryer retryer = this.retryer.clone();
    // 尝试最大次数,如果中间有结果,直接返回
    while (true) {
      try {
        return executeAndDecode(template);
      } catch (RetryableException e) {
        retryer.continueOrPropagate(e);
        if (logLevel != Logger.Level.NONE) {
          logger.logRetry(metadata.configKey(), logLevel);
        }
        continue;
      }
    }
  }

重试器有如下几个控制参数:



20200423111807581.png

具体的代码实现可参考:

https://github.com/OpenFeign/feign/blob/master/core/src/main/java/feign/Retryer.java


PHASE 8. 发送Http请求


Feign 真正发送HTTP请求是委托给 feign.Client 来做的:

public interface Client {
  /**
   * Executes a request against its {@link Request#url() url} and returns a response.
   *  执行Http请求,并返回Response
   * @param request safe to replay.
   * @param options options to apply to this request.
   * @return connected response, {@link Response.Body} is absent or unread.
   * @throws IOException on a network error connecting to {@link Request#url()}.
   */
  Response execute(Request request, Options options) throws IOException;
  }


Feign 默认底层通过JDK 的 java.net.HttpURLConnection 实现了feign.Client接口类,在每次发送请求的时候,都会创建新的HttpURLConnection 链接,这也就是为什么默认情况下Feign的性能很差的原因。可以通过拓展该接口,使用Apache HttpClient 或者OkHttp3等基于连接池的高性能Http客户端,我们项目内部使用的就是OkHttp3作为Http 客户端。


如下是Feign 的默认实现,供参考:

public static class Default implements Client {
    private final SSLSocketFactory sslContextFactory;
    private final HostnameVerifier hostnameVerifier;
    /**
     * Null parameters imply platform defaults.
     */
    public Default(SSLSocketFactory sslContextFactory, HostnameVerifier hostnameVerifier) {
      this.sslContextFactory = sslContextFactory;
      this.hostnameVerifier = hostnameVerifier;
    }
    @Override
    public Response execute(Request request, Options options) throws IOException {
      HttpURLConnection connection = convertAndSend(request, options);
      return convertResponse(connection).toBuilder().request(request).build();
    }
    HttpURLConnection convertAndSend(Request request, Options options) throws IOException {
      final HttpURLConnection
          connection =
          (HttpURLConnection) new URL(request.url()).openConnection();
      if (connection instanceof HttpsURLConnection) {
        HttpsURLConnection sslCon = (HttpsURLConnection) connection;
        if (sslContextFactory != null) {
          sslCon.setSSLSocketFactory(sslContextFactory);
        }
        if (hostnameVerifier != null) {
          sslCon.setHostnameVerifier(hostnameVerifier);
        }
      }
      connection.setConnectTimeout(options.connectTimeoutMillis());
      connection.setReadTimeout(options.readTimeoutMillis());
      connection.setAllowUserInteraction(false);
      connection.setInstanceFollowRedirects(true);
      connection.setRequestMethod(request.method());
      Collection<String> contentEncodingValues = request.headers().get(CONTENT_ENCODING);
      boolean
          gzipEncodedRequest =
          contentEncodingValues != null && contentEncodingValues.contains(ENCODING_GZIP);
      boolean
          deflateEncodedRequest =
          contentEncodingValues != null && contentEncodingValues.contains(ENCODING_DEFLATE);
      boolean hasAcceptHeader = false;
      Integer contentLength = null;
      for (String field : request.headers().keySet()) {
        if (field.equalsIgnoreCase("Accept")) {
          hasAcceptHeader = true;
        }
        for (String value : request.headers().get(field)) {
          if (field.equals(CONTENT_LENGTH)) {
            if (!gzipEncodedRequest && !deflateEncodedRequest) {
              contentLength = Integer.valueOf(value);
              connection.addRequestProperty(field, value);
            }
          } else {
            connection.addRequestProperty(field, value);
          }
        }
      }
      // Some servers choke on the default accept string.
      if (!hasAcceptHeader) {
        connection.addRequestProperty("Accept", "*/*");
      }
      if (request.body() != null) {
        if (contentLength != null) {
          connection.setFixedLengthStreamingMode(contentLength);
        } else {
          connection.setChunkedStreamingMode(8196);
        }
        connection.setDoOutput(true);
        OutputStream out = connection.getOutputStream();
        if (gzipEncodedRequest) {
          out = new GZIPOutputStream(out);
        } else if (deflateEncodedRequest) {
          out = new DeflaterOutputStream(out);
        }
        try {
          out.write(request.body());
        } finally {
          try {
            out.close();
          } catch (IOException suppressed) { // NOPMD
          }
        }
      }
      return connection;
    }
    Response convertResponse(HttpURLConnection connection) throws IOException {
      int status = connection.getResponseCode();
      String reason = connection.getResponseMessage();
      if (status < 0) {
        throw new IOException(format("Invalid status(%s) executing %s %s", status,
            connection.getRequestMethod(), connection.getURL()));
      }
      Map<String, Collection<String>> headers = new LinkedHashMap<String, Collection<String>>();
      for (Map.Entry<String, List<String>> field : connection.getHeaderFields().entrySet()) {
        // response message
        if (field.getKey() != null) {
          headers.put(field.getKey(), field.getValue());
        }
      }
      Integer length = connection.getContentLength();
      if (length == -1) {
        length = null;
      }
      InputStream stream;
      if (status >= 400) {
        stream = connection.getErrorStream();
      } else {
        stream = connection.getInputStream();
      }
      return Response.builder()
              .status(status)
              .reason(reason)
              .headers(headers)
              .body(stream, length)
              .build();
    }
  }

Feign 的性能怎么样?


Feign 整体框架非常小巧,在处理请求转换和消息解析的过程中,基本上没什么时间消耗。真正影响性能的,是处理Http请求的环节。


如上所述,由于默认情况下,Feign采用的是JDK的HttpURLConnection,所以整体性能并不高,刚开始接触Spring Cloud 的同学,如果没注意这些细节,可能会对Spring Cloud 有很大的偏见。


我们项目内部使用的是OkHttp3 作为连接客户端。


文章参考https://blog.csdn.net/luanlouis/article/details/82821294,基于SpingCloud Hoxton.SR3版本对文档内容进行了调整。

目录
相关文章
|
1月前
|
存储 算法 Java
解析HashSet的工作原理,揭示Set如何利用哈希算法和equals()方法确保元素唯一性,并通过示例代码展示了其“无重复”特性的具体应用
在Java中,Set接口以其独特的“无重复”特性脱颖而出。本文通过解析HashSet的工作原理,揭示Set如何利用哈希算法和equals()方法确保元素唯一性,并通过示例代码展示了其“无重复”特性的具体应用。
41 3
|
22天前
|
算法 Java 数据库连接
Java连接池技术,从基础概念出发,解析了连接池的工作原理及其重要性
本文详细介绍了Java连接池技术,从基础概念出发,解析了连接池的工作原理及其重要性。连接池通过复用数据库连接,显著提升了应用的性能和稳定性。文章还展示了使用HikariCP连接池的示例代码,帮助读者更好地理解和应用这一技术。
37 1
|
2天前
|
JavaScript 前端开发 API
Vue.js响应式原理深度解析:从Vue 2到Vue 3的演进
Vue.js响应式原理深度解析:从Vue 2到Vue 3的演进
18 0
|
27天前
|
数据采集 存储 编解码
一份简明的 Base64 原理解析
Base64 编码器的原理,其实很简单,花一点点时间学会它,你就又消除了一个知识盲点。
68 3
|
8天前
|
API 持续交付 网络架构
深入解析微服务架构:原理、优势与实践
深入解析微服务架构:原理、优势与实践
12 0
|
9天前
|
存储 供应链 物联网
深入解析区块链技术的核心原理与应用前景
深入解析区块链技术的核心原理与应用前景
|
9天前
|
存储 供应链 安全
深度解析区块链技术的核心原理与应用前景
深度解析区块链技术的核心原理与应用前景
18 0
|
1月前
|
JSON Java 数据格式
【微服务】SpringCloud之Feign远程调用
本文介绍了使用Feign作为HTTP客户端替代RestTemplate进行远程调用的优势及具体使用方法。Feign通过声明式接口简化了HTTP请求的发送,提高了代码的可读性和维护性。文章详细描述了Feign的搭建步骤,包括引入依赖、添加注解、编写FeignClient接口和调用代码,并提供了自定义配置的示例,如修改日志级别等。
87 1
|
1月前
|
负载均衡 算法 Java
蚂蚁面试:Nacos、Sentinel了解吗?Springcloud 核心底层原理,你知道多少?
40岁老架构师尼恩分享了关于SpringCloud核心组件的底层原理,特别是针对蚂蚁集团面试中常见的面试题进行了详细解析。内容涵盖了Nacos注册中心的AP/CP模式、Distro和Raft分布式协议、Sentinel的高可用组件、负载均衡组件的实现原理等。尼恩强调了系统化学习的重要性,推荐了《尼恩Java面试宝典PDF》等资料,帮助读者更好地准备面试,提高技术实力,最终实现“offer自由”。更多技术资料和指导,可关注公众号【技术自由圈】获取。
蚂蚁面试:Nacos、Sentinel了解吗?Springcloud 核心底层原理,你知道多少?
|
1月前
|
前端开发 Java 应用服务中间件
21张图解析Tomcat运行原理与架构全貌
【10月更文挑战第2天】本文通过21张图详细解析了Tomcat的运行原理与架构。Tomcat作为Java Web开发中最流行的Web服务器之一,其架构设计精妙。文章首先介绍了Tomcat的基本组件:Connector(连接器)负责网络通信,Container(容器)处理业务逻辑。连接器内部包括EndPoint、Processor和Adapter等组件,分别处理通信、协议解析和请求封装。容器采用多级结构(Engine、Host、Context、Wrapper),并通过Mapper组件进行请求路由。文章还探讨了Tomcat的生命周期管理、启动与停止机制,并通过源码分析展示了请求处理流程。

推荐镜像

更多
下一篇
无影云桌面