Java Web 开发之 API Boy 的进阶之路
一、JavaWeb 技术
1. Servlet
首先介绍 JavaWeb 开发中的第一个名词 Servlet。
通过维基百科上的介绍,我们知道:
1)严格定义的 Servlet 是一个 Java 的接口,并未实现,仅仅定义了 Web 服务器响应请求的一些方法。
2)广义的 Servlet 是指,实现了这个接口方法的一个 Java 类。
3)Servlet 运行在 Web 服务器(比如 Tomcat)中,当 Web 服务器接收到一个 Http 请求,Web 服务器解析出 request,并生成一个空的 response,把这两个参数传给 Servlet,接下来在 Servlet 中进行逻辑处理,最终把结果数据 response 给到 Web 服务器,再响应给请求端。
2. Jsp
了解了 Servlet 之后,不得不提的一个名词是 Jsp,虽然现在前后端分离的 Web 开发方式下,已经比较少听到 Jsp 了,不过还是值得我们了解一下。
从浏览器请求一个 HTML 网页文件,可以渲染出一个静态网页。通过维基百科上的介绍,我们知道:
1)Jsp 文件是一个类似于 HTML 文件的文件,不过其中可以写一些 Java 代码。
2)Jsp 文件中,HTML 部分为静态模版,Java 代码部分可以动态获取一些数据填充在模版中。
3)Jsp 文件最终运行时候,还是被编译转换成了一个 Servlet,由 Servlet 来实现动态获取数据的逻辑,并拼接生成最终的 HTML 文件。
4)Jsp 可以根据客户端的请求,动态的生成 HTML 等 Web 网页,最java培训终返回给浏览器渲染出网页。
3. Servlet 容器(以 Tomcat 为例)
现在进行 Java Web 的开发,通常直接就上 Spring MVC/Spring Boot 框架了,在 Controller 里面就开始处理请求 request→做逻辑→最后返回 response,让初学者很容易混淆 HTTP 服务器,Servlet 容器与 Spring 框架的关系。这里先介绍一个常见的 Web 服务器 Apache Tomcat,它本身包含了 HTTP 服务器,所以可以响应 HTTP 请求;同时它实现了对 Servlet 和 JSP 的支持,即我们自己编写的 Servlet 类可以运行在 Tomcat 这个 Servlet 容器中。
介绍到这里,大家是否已经对 Java Web 开发流程有了一些更清晰的认识:
1)前端发送的 Http 请求到 Tomcat,Tomcat 中包含可以响应 HTTP 请求的 HTTP 服务器。
2)Tomcat 先解析 HTTP 请求中的参数,new 一个 request 对象并赋值,同时会 new 一个空的 response 对象。
3)再利用 Tomcat 的 Servlet 引擎,把我们写的用来做逻辑处理的 Servlet 类 new 成对象,并把 request 对象和 response 对象作为参数传入到 Servlet 的方法中。
如果请求的是静态资源,比如 HTML 页面,Tomcat 会用自带的 DefaultServlet 处理请求,并将资源直接返回给前端。(正规的项目通常不会用 Tomcat 做静态资源服务器,因为所有资源请求都会通过 DefaultServlet 处理,会占用大量线程,极大影响性能,通常会前置一个静态资源服务器(nginx,apache),不仅做负载均衡,还能够高效处理返回静态文件。)
如果请求的是动态资源,则用的是我们自己写的 Servlet 进行处理。
4)Servlet 类中进行逻辑处理,最后把结果数据 set 到 response 对象中。
5)再给回到 Tomcat,再给回到前端。
所以我们用 Spring/Spring Boot 框架的时候,主要逻辑代码其实是在 Servlet 类及其调用类中,然后网络通信相关的工作是 Web 服务器帮忙做的。
Tomcat 服务器 = Web 服务器 + Servlet/JSP 容器。
4. Listener
Java Web 开发时候,主要逻辑代码的入口是 Servlet 的实现类,除此之外,还可以通过监听器 Listener 实现一部分逻辑。在 JavaWeb 中 Listener 是 Servlet 规范定义的一种特殊类,主要用于监听指定对象(JavaWeb 三大域对象)的特定事件(创建、销毁以及其属性变更),并进行响应处理。Listener 是 Servlet 提供的扩展功能接口,有了这些扩展功能接口,我们就可以比较方便实现一些功能,比如统计网站的同时在线人数,可以使用 HttpSessionListener,当 Session 创建时在线人数+1,Session 销毁时在线人数-1,如果没有提供 Listener,实现起来就会更加复杂。
介绍 Listener 之前,先介绍一下 JavaWeb 的三大域对象,ServletContext、HttpSession、ServletRequest。域对象有点像大型的 map,可以通过 getAttribute()和 setAttribute()方法,存储/获取数据。
ServletContext 域:
生命周期:当我们的 Web 应用被加载进 Servlet 容器时,Web 服务器创建代表整个 Web 应用的 ServletContext 对象,当服务器关闭或 Web 应用被移除时,ServletContext 对象跟着销毁。
作用范围:整个 Web 应用(应用范围)。
ServletRequest 域:
生命周期:在 Servlet 的 service(request, response) 方法调用前,由 Web 服务器创建 ServletRequest,然后传入 service 方法。整个请求结束,ServletRequest 生命结束。
作用范围:(请求范围)整个请求链(请求转发也存在)。
HttpSession 域:
生命周期:HttpSession 在第一次调用 servletRequest.getSession() 方法时,服务器会检查是否已经有对应的 session,如果没有就在内存中创建一个 session 并返回。
作用范围:一次会话。
在 JavaWeb 中 Listener 主要用于监听三大作用域的创建、销毁,以及其属性变更,常用的 Listener 有六个:
ServletContextListener
ServletContextAttributeListener
HttpSessionListener
HttpSessionAttributeListener
ServletRequestListener
ServletRequestAttributeListener
每当 Tomcat 创建或销毁三大域对象,三大域对象调用 get/setAttribute()方法,都会被这些 Listener 发现,然后触发调用指定的方法。Listener 的实现是利用“观察者模式(Observer)”,有兴趣的读者可以自行搜索一下。
5. Filter
Filter 有什么用?我们先看一个业务场景,我们的 Web 应用,我们希望在用户访问之前,先进行用户身份的确认,任何一次的访问,不管是访问 Servlet 的动态资源,还是访问 Html 的静态资源,都需要进行身份的确认。
我们知道从 3 中知道,在访问动态资源的 Servlet 时,我们是可以前置一段权限校验的代码,但是每一个 Servlet 都需要加上这一段严重耦合的重复代码,非常不合理;在访问静态资源,会通过 Tomcat 自带的 DefaultServlet 进行处理,我们没有办法修改它,没有办法添加权限校验的代码。
所以这里就需要一个新的环节,位于用户请求和 Servlet 之间,每一个用户请求,都先经过这个环节,这个环节中可以进行逻辑处理,然后再决定请求是否到达 Servlet,这个环节就是 Filter。
Filter 的底层实现用到了责任链模式(FilterChain),有兴趣的同学可以自行查阅一下,可以将多个写好的 Filter 给串起来,请求访问的时候按照顺序执行。Filter 在使用上是非常简单的,和 Listener 一样,只需要配置 xml 或者通过注解的方式配置,然后实现对应接口即可。Filter 是属于 Tomcat 服务器的,Tomcat 也有容器对象管理的功能,在初始化的时候,将配置好的 Filter 注册到 FilterChain 中。
介绍到这里,Java Web 的开发流程就比较清晰了:
1)项目启动,Tomcat 的 Main()函数启动。
2)创建 ServletContext。
3)读取 web.xml,通过反射的方式创建 Listener。
4)给 ServletContext 注入 ServletContextListener。
5)ServletContext 初始化时,我们写的 Listener 被执行。
6)读取 web.xml,通过反射的方式创建 Filter,并放入一个 FilterChain 中,等待请求到达。
7)请求到达,读取 web.xml,通过反射的方式创建 Servlet,创建 request 和 response 并传给 Servlet。
8)执行 FilterChain 中的 Filter,我们写的 Filter 被执行。
9)Servlet 处理请求,响应结果,我们写的 Servlet 被执行。
6. Servlet 映射器
Servlet 还有一点可能会造成一些困惑的地方,我们知道在 web.xml 中配置<servlet-mapping>标签或者注解方式配置:
但是对于每一个请求的 URL,是怎么对应上正确的 Servlet 的呢?具体的映射规则是由 Tomcat 中的 Mapper 类提供的,其中提供了精准匹配、前缀匹配、扩展名匹配等 7 种匹配规则,匹配之后:
对于静态资源,Tomcat 会交由一个叫做 DefaultServlet 的类来处理;
对于 JSP,Tomcat 会交由一个叫做 JspServlet 的类来处理;
对于 Servlet ,Tomcat 会交由一个叫做 InvokerServlet 的类来处理。
现在在实际的开发中,已经不会直接利用 Tomcat 的 Mapper 类来做映射了,一般会利用 Spring MVC 模块中的 HandlerMapping 来做映射,可以参考下一节的第 4 部分。
7. Cookie 与 Session
由于客户端与服务端是通过 HTTP 协议通信,但 HTTP 是一种无状态协议,即客户端第二次来访时,服务端无法判断该客户端是否来访过。会话跟踪常用的有两种技术:Cookie 和 Session,Session 底层依赖于 Cookie。
在学习 Web 开发之前,接触到 Cookie 就是,偶尔需要清除浏览器的 Cookie 缓存,大概知道 Cookie 是一个存在于客户端的缓存信息。实际也就是这样,Cookie 是一份数据,是服务端响应给客户端,并且存储在客户端的一份小数据。下次客户端访问服务端时,会自动带上这个 Cookie。Cookie 也可以设置不同的保持方式和存活时间,比如存在浏览器内存(浏览器关闭即清除)或者可以持久化到磁盘等。这样访问同一个网站的不同页面,只需要登陆一次,其他页面都可以通过 Cookie 进行鉴权。
不过 Cookie 也有一些不足,比如将密码等敏感信息存在客户端会有安全问题,Cookie 包含信息太多会有传输效率问题等,所以就有了 Session。Session 是存在于服务端的东西,像一个大 Map,以键值对的形式存储数据。将之前要通过 Cookie 回传给客户端的数据保存在客户端的 Session 中,并生成一个 JSESSIONID,此时只需要将 JSESSIONID 通过 Cookie 回传给客户端即可,客户端下次请求只需要带上 JSESSIONID,即可在服务端找到对应的信息。
8. 跨域问题及解决方案
Ajax 跨域问题,这个课程讲的非常详细了,简洁明了,大家可以直接观看:Ajax 跨域完全讲解(https://www.imooc.com/learn/947)。
二、Spring Framework
在了解了 Java Web 开发的底层技术之后,我们就可以通过使用框架来提升开发效率了。框架就是对这些底层技术进行一系列的封装,简化我们的开发流程,提升研发效率。Java 企业应用开发,应用最广的应该就是 Spring 框架了。打开 Spring 的官网,可以看到 Spring 框架中包含了非常多的组件,这里我简单介绍一下 Spring Framework 中的部分核心能力,包括 Spring Core(提供控制反转(IoC)和依赖注入(DI))、Spring AOP(提供面向切面编程能力)和 Spring MVC(Spring Framework 中的重要 Web 模块),简单来说,Spring 是一个控制反转(IoC)和面向切面(AOP)的容器框架。
1. 控制反转(IoC)和依赖注入(DI)
控制反转(IoC)就是,原本代码中我们需要自己 new 对象,给对象赋值,自己控制对象的创建和依赖,比如
现在控制权反转给了 Spring 框架,Spring 容器框架会帮忙 new 各种对象,并记录着对象之间的关系,当需要依赖一个对象的时候,直接找 Spring 容器,Spring 容器自动注入,比如
Spring 框架负责控制对象的生命周期和对象之间的关系。IoC 容器在其他语言中也有应用,并非 Spirng 特有。IoC 容器实际上就是个 map(key,value),里面存放各种对象。IoC 容器负责创建对象,将对象连接在一起,配置这些对象,直到它们被完全销毁。有大量的资料介绍 IoC 和 DI 的好处,比如解耦依赖,增加可扩展性。我能感受到一个比较直观的好处,比如 sevice 接口的实现类是 ServiceImpl1,然后要改成 ServiceImpl2,不用 Spring 这里就需要修改源码再重新编译;如果用 Spring 的话,修改配置文件即可,依赖注入的时候即会注入新的实现类。
2. Spring AOP
AOP(Aspect Oriented Programming 面向切面编程),最典型的应用就是通过将一些公共代码,封装成一个切面类,从而在需要使用的地方,非常方便的切入复用,大量减少重复代码。比如日志采集,权限鉴定,事务管理等。
Spring AOP 的实现是基于动态代理,动态代理做了什么,即原始对象 Target,执行方法 do();动态代理之后得到动态代理对象 Proxy,在执行方法 do()的前后,会执行代理前逻辑和代理后逻辑。底层实现方式有两种:一种是 JDK 动态代理(JDK Proxy),另一种是 CGLib(Code Generation Library(基于字节码操作)) 的方式。表现形式是:
3. ContextLoaderListener
在第一部分的 4 中介绍了 Listener,主要介绍了 Tomcat 负责的三大域对象相关的 6 个 Listener。在学习 Spring 之后,需要再介绍一个监听器 Listener,ContextLoaderListener,它是由 Spring 容器框架负责并提供的。在使用 Spring 的项目中,我们需要在 web.xml 中配置它,比如这样:
我们通过 ContextLoaderListener 的代码实现,来看看它实现了什么功能。
Spring 实现了 Tomcat 提供的 ServletContextListener 接口,继承了 ContextLoader,写了一个监听器 ContextLoaderListener 来监听 Web 项目启动。一旦项目启动,会触发 ContextLoaderListener 中的特定方法,initWebApplicationContext()方法就是用来初始化 Spring 的 IoC 容器。
ContextLoaderListener 的作用就是启动 Web 容器时,通过 web.xml 中的配置,读取在 contextConfigLocation 中定义的 xml 文件,通过反射创建对象,自动装配 ApplicationContext 的配置信息,并产生 WebApplicationContext(即 IoC 容器)对象,然后将这个对象放置 ServletContext 的属性里。这样我们就可以通过 Servlet 得到 WebApplicationContext 对象,并利用这个对象访问 Spring 容器管理的 bean 对象。简单来说,就是上面这段配置为项目提供了 Spring 支持,初始化了 Ioc 容器。
4. Spring MVC
介绍 Spring MVC 之前,需要简单介绍一下 Web 开发的 MVC 模式。即:
M(Model),主要代表了逻辑处理、数据处理层(service、dao、entity);
V(View),主要代表了前端展示层(jsp、html);
C(Controller),主要代表了接受请求,调用对应的 Model 层,根据 Model 结果渲染对应的 View 层。
对应上 Web 请求就是,用户 Http 请求到达 Servlet(Controller),然后调用相应的逻辑处理类(Model),最后把结果数据交给 Jsp 模版(View)渲染,最后将渲染完成的结果页面返回给用户,这样的模式就称为 MVC 模式。要说 MVC 的优点,就需要提一下特别早期,前后端没有分离时候的开发模式,Java 代码和 Html 代码直接耦合,前后端开发互相依赖,都开发完才能测试。当过渡到 MVC 模式后,前端同学可以专注写 View 层,后段同学写 Model 层,然后通过 Controller 将 Model 层的结果数据,给到 View 层进行渲染。
Spring MVC 是一款很优秀的 MVC 框架,是 Spring 的一个子模块,Spring 大家族中 的 Web 模块,可以让我们的开发更便捷。直接从官方文档可以清晰的了解到 Spring MVC 框架的功能:Spring MVC 框架是基于 DispatcherServlet 来设计的,DispatcherServlet 根据可配置的映射(Mapping),将请求映射到相应的 handlers(Model)类中进行逻辑处理;当在 handlers 中处理完获得结果数据,加上指定的逻辑 view,一起传回 DispatcherServlet;DispatcherServlet 通过 ViewResolver 将逻辑 view 和对应的模版文件,解析成一个真正的 View 对象,View 对象将 Model 中的数据渲染进来;最后返回给 DispatcherServlet,再返回给请求方。尤其是现在前端通过 Vie/React 来开发,后台只需要提供一个 json 返回即可,通过 Spring MVC 更加简单,加上一个注解,直接将 handlers 处理完的结果数据,通过 json 形式返回给前端去渲染。
5. Interceptor
在上一部分的第 5 节中介绍了 Filter,它依赖于 Servlet 容器,是实现了 AOP 编程思想的过滤器。作为当前最强的 Java Web 框架,Spring 当然也提供了类似的能力,即 Spring MVC 中的 Interceptor。Interceptor 在使用方法(xml 配置/注解)上和 Filter 是类似的,而且都利用了 AOP 的设计思想,不过在实现上还是有一些不同,这里主要说明一下区别:
1)Filter 依赖于 Servlet 容器,属于 Servlet 规范的一部分,而 Interceptor 依赖于 Spring MVC 框架;
2)Filter 是由 Servlet 容器的函数回调(doFilter()方法)实现的,而 Interceptor 是通过动态代理(Java 反射)实现的;
3)Filter 的生命周期由 Servlet 容器管理,Interceptor 可以通过 Spring IoC 容器来管理,更加方便使用;
4)Filter 拦截范围在 Http 请求的前后,Interceptor 可以在 Spring 的各个组件和各个方法间进行拦截加强。
6. Spring 开发的两种方式 XML 和注解
Spring IoC 容器中对象的定义,以及对象之间的依赖注入关系,可以通过 XML 配置和注解两种方式实现。
这里简单介绍一下 Java 中的注解,注解跟注释有所不同,注释就是写给人看的,而注解的目的,是可以提供给程序看的,作用有点类似于 XML 配置文件,比如:
怎么定义注解就不介绍了,定义了 @ComponentScan 这样一个注解,并且在类/方法上使用了注解之后,怎么样生效呢?这部分工作是框架帮忙做的,框架会通过 Java 反射的方式,扫描出代码里出现了相关注解的类/方法,然后获取到注解里面的参数信息,然后进行逻辑处理,即通过注解的方式来减少复杂的配置文件。
Spring 框架通过 XML/注解的方式,定义 IoC 容器的对象(Bean),对象之间的 DI(依赖注入)关系,方式比较灵活,组合方式多样,我这里提供一份讲解清楚的资料供大家学习。
最后主要讲一下 XML 方式相比注解方式最大的缺点,当项目变大之后,XML 方式会导致配置复杂繁琐,并且会有很多冗余信息。所以在上手极快,开箱即用的 Spring Boot 中,默认就采用注解的方式进行配置,并且提供大量默认配置,极大降低了使用门槛。
三、Spring Boot
Spring Boot 并不是一个新技术,而是将原来的 Spring 项目进行了大量的默认配置,按照“Convention over configuration”(习惯优于配置)的理念,可以不用或者仅需很少的 Spring 配置,就能快速创建一个能够独立运行的基于 Spring 框架的项目(内嵌 Servlet 容器的 jar 包)。由于非常方便,直接通过一个 Spring Boot 搭建 Web Server 的例子,直观了解一下 Spring Boot。
1. Spring Boot 简介
IntelliJ IDEA 14.1 开始已经支持 Spring Boot 了,创建 Spring Boot 项目操作步骤如下:在 File 菜单里面选择 New > Project,然后选择 Spring Initializr,接着一步步 Next 即可,只需要几秒钟,就可以构建好一个 Spring Boot 框架的项目。
点击完之后,就已经初始化了一个 Spring Boot 框架的项目了,项目结构如下:
可以看到,除了几个空目录外,包含如下几个文件:
pom.xml:Maven 构建说明文件。
DemoApplication.java:一个带有 main()方法的类,用于启动应用程序(关键)。
DemoApplicationTests.java:一个空的 Junit 测试类,它加载了一个使用 Spring Boot 字典配置功能的 Spring 应用程序上下文。
application.properties:一个空的 properties 配置文件,可以根据需要,添加配置属性,作用是对一些默认配置的配置值进行修改。
Greeting.java:一个用于 Demo 的 POJO 类。
GreetingController.java:一个用于处理请求的 Controller 类。
直接点击右上角的运行按钮,项目就启动起来了,可以看到 GreetingController 类中,有一个 @GetMapping 注解中的参数是/greeting,打开浏览器,请求 http://localhost:8080/greeting,就可以从浏览器发送 Http 请求,通过基于 Spring Boot 框架的 Web Server 处理请求,并返回一个 json 字符串。
Spring Boot 项目的 pom.xml 和普通的 Spring 项目的 pom.xml 有一些区别,首先它有一个<parent>父级依赖,在 spring-boot-starter-parent 中提供了很多默认的配置,这些配置可以大大简化我们的开发。通过继承 spring-boot-starter-parent,默认具备了如下功能:Java 版本、源码的文件编码方式(UTF-8)、依赖管理、打包支持、动态识别资源、识别插件配置、识别不同的配置(如:application-dev.properties 和 application-dev.yml),以上继承来的特性有的并非直接继承自 spring-boot-starter-parent,而是继承自 spring-boot-starter-parent 的父级 spring-boot-dependencies。并且可以通过<properties>标签覆盖 parent 中包含依赖包的版本,例如截图中设置了<java.version>1.8</java.version>。
Spring Boot 提供了很多“开箱即用”的依赖模块,都是以 spring-boot-starter-xx 作为命名的。比如需要 web 能力,直接依赖 spring-boot-starter-web,Maven 会帮忙处理好 Web 能力背后所需的复杂依赖关系。
最后就是 Spring Boot Maven 插件,spring-boot-maven-plugin 提供了许多方便的功能:把项目打包成一个可执行 JAR(uber-JAR),包括把应用程序的所有依赖打入 JAR 文件内,并为 JAR 添加一个描述文件,其中的内容能让你用 java -jar 来运行应用程序;搜索 public static void main()方法来标记为可运行类。
DemoApplication.java 是一个很关键的启动类,程序的入口就是这里。@SpringBootApplication 是 Sprnig Boot 项目的核心注解,主要目的是开启自动配置。main 方法就是一个标准的 Java 应用的 main 的方法,主要作用是作为项目启动的入口。此处通过对 @SpringBootApplication 注解和 SpringApplication.run(DemoApplication.class, args)方法的底层实现,来了解 Spring Boot 为何能够如此方便。
1)@SpringBootApplication 注解
可以看到,@SpringBootApplication 注解其实是由一些注解组合而来,其中起作用的注解分别是:
@SpringBootConfiguration(起作用的部分是其背后的 @Configuration)
@EnableAutoConfiguration
@ComponentScan
其中,@Configuration 和 @ComponentScan 这两个注解都是第二部分的 6 中提到过的 Spring 的常用注解。
先看 @Configuration,Spring IoC 容器要用 JavaConfig 形式的配置类,需要使用 @Configuration 注解,Spring Boot 社区推荐使用基于 JavaConfig 的配置形式,这里的启动类标注了 @Configuration 之后,它也成为了一个 Spring IoC 容器的配置类。
再看 @ComponentScan,它对应 XML 配置中的<context:component-scan base-package=""/>这个标签,@ComponentScan 的功能其实就是自动扫描并加载符合条件的 bean 文件(比如 @Component、@Repository、@Service、@Controller 等),最终将这些文件定义的 bean 加载到 IoC 容器中。我们可以通过 basePackages 等属性指定 @ComponentScan 扫描的文件夹范围,如果不指定,Spring 框架默认从声明 @ComponentScan 所在类的 package 进行扫描。所以 Spring Boot 的启动类最好是放在 root package 下,可以默认扫描整个项目。
最后看 @EnableAutoConfiguration,它到定义中有 @Import(EnableAutoConfigurationImportSelector.class),借助 EnableAutoConfigurationImportSelector,@EnableAutoConfiguration 可以帮助 Spring Boot 应用将所有需要的依赖 @Configuration 配置都加载到当前 Spring Boot 的 IoC 容器。
@EnableAutoConfiguration 会将哪些符合条件配置类加载进来呢,通过左上角 Scroll from source 这个按钮,找到 EnableAutoConfiguration.java 的工程,然后找到 META-INF/spring.factories 配置文件,其中 key=org.springframework.boot.autoconfigure.EnableAutoConfiguration 对应的配置类们,即为 @EnableAutoConfiguration 需要加载的配置类。
2)SpringApplication.run(DemoApplication.class, args) 方法
SpringApplication.run()方法做的事情比较多,比如创建一个 SpringApplication 对象实例,创建完之后,初始化 SpringApplication 对象,然后执行 run 方法。这个过程主要是加载各种各种预定义的配置文件,执行 SpringApplicationRunListener 的初始化方法,创建对应的 ApplicationContext(IoC 容器)并初始化等等,总的来说就是进行各种判断、各种配置文件加载、各种初始化。
四、Spring Cloud
在熟悉了 Spring Boot 之后,随着开发系统的规模越来越大,就会从单体架构的服务向分布式集群发展,而且随着微服务概念的兴起,微服务的治理也变得重要起来,这时候 Spring Cloud 这类框架就出现了。
原创作者:王展雄
评论