首页 文章 精选 留言 我的

精选列表

搜索[AI驱动],共10000篇文章
优秀的个人博客,低调大师

阿里集团业务驱动的升级 —— 聊一聊Dubbo 3.0 的演进思路

作者 | 远云 三位一体 2020年底,阿里云提出了“三位一体”的理念,目标是希望将“自研技术”、“开源项目”、“商业产品”形成统一的技术体系,令技术的价值可以达到最大化。 阿里集团内部的 HSF 框架在经历了多年双十一流量洪峰的考验后,锻炼出了高性能和高可用的核心竞争力。而对于 Dubbo,作为国内外最受欢迎的服务治理框架之一,它的开源亲和性就不用再多说了。 Dubbo 3.0 作为三位一体架构的首推方案,在集团内被寄予厚望。它完美融合了内部 HSF 的特性,天然拥有高性能、高可用的核心能力,我们期望用它来解决内部落地问题,做到技术栈统一。目前在考拉已经大规模落地,未来也会在众多核心场景进行落地,并承载 618、双十一等复杂的业务场景。 Dubbo 3.0 带来的好处 在具体说明 Dubbo 3.0 的变化细节之前,先从两个方面说一说升级到了 Dubbo 3.0 能带来什么好处。 首先是,Dubbo 3.0 会着力提升大规模集群实践中的性能与稳定性,通过优化数据存储方式来降低单机资源损耗,并基于此保证超大规模集群的水平扩容的情况下集群的稳定性。同时,Dubbo 3.0 提出了柔性集群的概念,能够在异构体系下有效保证和提高全链路总体的可靠性和资源的利用率。 第二点是 Dubbo 3.0 代表着 Dubbo 全面拥抱云原生的里程碑。当前 Dubbo 在国内外有着基数巨大的用户群体,而随着云原生时代的到来,这些用户上云的需求越来越强烈。Dubbo 3.0 将提供一整套的解决方案、迁移路径与最佳实践,来帮助企业实现云原生转型,从而享受云原生带来的红利。 1、业务收益 那么站在业务应⽤的视角来看,如果升级到 Dubbo 3.0,能获得哪些具体的收益呢? 首先,在性能与资源利用率⽅面,Dubbo 3.0 能有效降低框架带来的额外资源消耗,从而⼤幅提升资源利用率。 从单机视⻆,Dubbo 3.0 能节省约 50% 的内存占⽤;从集群视角,Dubbo 3.0 能⽀持的集群实例规模以百万计,为未来更大规模的业务扩容打下基础;而 Dubbo 3.0 对 Reactive Stream 通信模型的支持,在⼀些业务场景下能带来整体吞吐量的⼤幅提升。 其次,Dubbo 3.0 给业务架构升级带来了更多的可能性。最直观的就是通信协议的升级,给业务架构带来了更多选择。 Dubbo 原来的协议其实在⼀定程度上束缚了微服务接⼊⽅式。举个例子,移动端、前端业务要接入 Dubbo 的后端服务,需要经过网关层的协议转换;再比如,Dubbo 只⽀持 request-response 模式的通信,这使得⼀些需要流式传输或反向通信的场景⽆法得到很好的支持。 最后,Dubbo 3.0 给业务侧的云原生升级带来了整体的解决方案。不论是底层基础设施升级带来的被动变化,还是业务为解决痛点问题进行的主动升级,当业务升级到云原生,Dubbo 3.0 通过给出云原生解决方案,可以帮助业务产品快速接入云原生。 Dubbo 3.0 概览 在明确了升级到 Dubbo 3.0 能够带来的收益之后,来看看 Dubbo 3.0 有哪些具体的改动。 1、支持全新的服务发现模型。Dubbo 3.0 尝试从应用模型入手,对其云原生主流设计模型优化其存储结构,避免在模型上带来互通问题。新模型在数据组织上高度压缩,能有效提高性能和集群的可伸缩性。 2、提出了下一代 RPC 协议 —— Triple。这是一个基于 HTTP/2 设计的完全兼容 gRPC 协议的开放性新协议,由于是基于 HTTP/2 设计的,具有极高的网关友好型和穿透性,完全兼容 gRPC 协议使得其在多语言互通方面上天然具有优势。 3、提出了统一治理规则。这套规则面向云原生流量治理,能够覆盖传统 SDK 部署、Service Mesh 化部署、VM 虚拟机部署、Container 容器部署等一系列场景。一份规则治理全部场景可以大大降低流量治理成本,使得异构体系下的全局流量治理得到统一。 4、提供接入 Service Mesh 的解决方案。面向 Mesh 场景,Dubbo 3.0 提出了两种接入方式:一种是 Thin SDK 模式,部署模型和当前 Service Mesh 主流部署场景完全一样,而 Dubbo 将进行瘦身,屏蔽掉与 Mesh 相同的治理功能,仅保留核心的 RPC 能力;第二种是 Proxyless 模式,Dubbo 将接替 Sidecar 的工作职责,主动与控制面进行通信,基于 Dubbo 3.0 的统一治理规则,应用云原生流量治理功能。 应用级服务注册发现 应用级服务发现模型的原型其实最早在 Dubbo 2.7.6 版本中已经提出来了,经过一段时间的迭代,最终形成了 Dubbo 3.0 中一个较为稳定的模型。 在 Dubbo 2.7 及以前版本中,应用进行服务注册和发现时,都是以接口为粒度,每个接口都会对应在注册中心上的一条数据,不同的机器会注册上属于当前机器的元数据信息或者接口级别的配置信息,如序列化、机房、单元、超时配置等。 所有提供此服务的服务端在进行重启或者发布时,都会在接口粒度独立地变更。举个例子,一个网关类应用依赖了上游应用的 30 个接口,当上游应用在发布时,就有 30 个对应的地址列表在进行机器的上线和下线。 以接口作为注册发现的第一公民的方式是最早的 SOA 或微服务的拆分方式,提供了单一服务、单一节点的独立和动态变更能力。随着业务的发展,单一应用依赖的服务数在不断增多,每个服务提供方的机器数量也由于业务或容量原因不断增长,从客户端整体上看,依赖的总服务地址数迅速上涨。根据这种情况,可以考虑从注册发现的流程设计上优化。 这里注意有两个特征: • 随着单体应用拆分为多微服务应用的基本完成,大规模的服务拆分和重组已经不再是痛点,大部分接口都只被一个应用提供或者固定几个应用提供。 • 大量用于标志地址信息的 URL 都是存在极大冗余的,如超时时间、序列化等。这些配置变更频率极低,却在每个 URL 中都出现。 结合以上特征,最终应用级注册发现被提出了,即以应用作为注册发现的基本维度。和接口级的主要区别是,原来一个应用如果提供了 100 个接口,需要在注册中心上注册 100 个节点;如果这个应用有 100 台机器,那每次发布,对于它的客户端来说都是 10000 个虚拟节点的变更。而应用级注册发现则只需要 1 个节点, 每次发布只变更 100 个虚拟节点。对于依赖服务数多、机器多的应用而言,是几十到上百分之一数量级的规模下降,内存占用上也将至少下降一半。 然而,技术方案的设计不仅仅需要考虑功能正确,还需要考虑现有业务的升级。因此,升级到应用级注册发现的基础,是在其功能上需要对齐接口级注册发现的能力。而无论客户端是否升级或是否开启应用级注册发现,前提都是不影响正确的业务调用。 为了提供这个保证,我们设计了一个新的组件,元数据中心,用于管理两部分数据: • 接口应用映射关系:上报和查询接口到应用的映射,可以决定客户端是否启用应用级,避免业务代码变更; • 应用级元数据快照:当一个应用不同接口之间使用的配置不同就会出现数据的分化,因此在应用级方案里,提出了元数据快照的概念,即每个应用在每次发布时都会统一生成一份元数据快照,这个快照包含当前应用的元数据版本以及当前应用提供的所有接口的配置。这个快照 ID 会存储在 URL 中,这样既提供了动态变更能力, 又在量级上减少了数据存储对内存的压力。 最后,由于这个新的服务发现与 Spring Cloud、Service Mesh 等体系下的服务发现模型是高度相似的,因此 Dubbo 可以从注册中心层面与其他体系下的节点实现互发现。 Dubbo 3.0-云原生&阿里背书、易用 Dubbo 3.0 的定位是成为云原生时代的最佳微服务框架。目前可以看到的几个趋势有 K8s 成为资源调度的事实标准、Mesh 化成为主流以及规模上的急速增长等。这些趋势的存在都对 Dubbo 提出了更高的要求。 1、用户如何在 K8s 上更方便地部署和调用 Dubbo 服务是必须要解决的问题,要解决这个问题,统一的协议和数据交换格式是必须前提。2、Mesh 化的流行带来了多元化问题,即原生 Dubbo 和 Mesh 化 Dubbo 如何共存,多语言的场景如何支持。3、规模的增长会给整个 Dubbo 架构带来更大的挑战,无论是注册中心等组件,还是客户端,都会有更多的数据和调用量。 如何在保持稳定的前提下,提供更高效的服务是 Dubbo 演进的重中之重。 这些云原生时代带来的挑战,促成了 Dubbo 发展到下一代:新协议、K8s 基础架构支持、多语言支持和规模化。 1、下一代RPC协议 作为 RPC 框架最基础的能力是完成跨业务进程的服务调用,将服务组成链、组成网,这其中最核心的载体就是 RPC 协议。 同时,由于与业务数据的紧密耦合,RPC 协议的设计与实现,也在一些方面直接决定了业务架构,比如从终端设备到后端的交互、微服务架构中多语言的采用、服务间的数据传输模型等。 Dubbo 2 提供了 RPC 的核心语义,包括协议头、标志位、请求 ID 以及请求/响应数据。但在云原生时代,Dubbo 2 协议主要面临两个挑战:一是生态不互通,用户很难直接理解二进制的协议;二是对 Mesh 等网关型组件不够友好,需要完整的解析协议才能获取到所需要的调用元数据,如一些 RPC 上下文,从性能到易用性方面都会面临挑战。 Dubbo 作为服务框架,其最重要的还是提供远程通信能力。⽼版本 Dubbo 2 RPC 协议的设计与实现,已在实践中被证实在⼀些⽅面限制了业务架构的发展,⽐如从终端设备到后端服务的交互、微服务架构中多语言的采⽤用、服务间的数据传输模型等。 在支持已有的功能和解决存在的问题的前提下,下一代的协议需要有以下特性: • 协议需要解决跨语言互通的问题。传统的多语言多 SDK 模式和 Mesh 化跨语言模式都需要一种更通用易扩展的数据传输格式。 • 协议应该提供更完善的请求模型,除了 Request/Response 模型,还应该支持 Streaming 和 Bidirectional。 • 在性能上保留 request Id 机制,以避免队头阻塞带来的性能损耗。 • 易扩展,包括但不限于 Tracing/ Monitoring 等支持,也应该能被各层设备识别,降低用户理解难度。 基于这些需求,HTTP2/protobuf 的组合是最符合的。提到这两个的组合,可能很容易就会想到 gRPC 协议。新一代的协议和 gRPC 的关系如下: 1、Dubbo 新协议是基于 GRPC 扩展的协议,这也保证了在生态系统上新协议和 GRPC 是能够互通和共享的。 2、在第一点的基础上,Dubbo 新协议将更原生的支持 Dubbo 的服务治理,提供更大的灵活性。 3、在序列化方面,由于目前大多数应用方还没有使用 Protobuf ,所以新协议会在序列化方面给予足够的支持,平滑的适配现有序列化,方便迁移到 Protobuf。 4、在请求模型上,新协议将原生支持 Reactive,这也是 gRPC 协议所不具备的。 2、Service Mesh 为了使 Dubbo 在 Service Mesh 体系下落地,在参考了众多的方案之后,最终确定了最适合 Dubbo 3.0 的两种形态的 Mesh 方案。⼀种是经典的基于 Sidecar 的 Service Mesh,另⼀种是无 Sidecar 的 Proxyless Mesh。 对于 Sidecar Mesh 方案,其部署方式和当前主流 Service Mesh 部署方案一致。Dubbo 3.0 的重点是尽量给业务应用提供完全透明的升级体验,不止是编程视角的无感升级,还包括通过 Dubbo 3.0 轻量化、Triple 协议等,让整个调用链路上的损耗与运维成本也降低到最低。这个方案也被称为 Thin SDK 方案,而 Thin 的地方就是在于去除了所有不需要的组件。 Proxyless Mesh 部署方案则是 Dubbo 3.0 规划的另⼀种 Mesh 形态,目标是不需要启动 Sidecar,由传统 SDK 直接与控制面交互。 设想一下针对以下⼏种场景会⾮常适用 Proxyless Mesh 部署方案: • 业务方期望升级 Mesh 方案,但却无法接受由于 Sidecar 进行流量劫持所带来的性能损耗,这种情况常见于核心业务场景 • 期望降低 Sidecar 运维成本,降低系统复杂度 • 遗留系统升级缓慢,迁移过程漫长,多种部署架构⻓期共存 • 多种部署环境,这里的多种部署环境包括了如 VM 虚拟机、Container 容器等多种部署方式,也包括了多种类型应用混合部署,例如 Thin SDK 与 Proxyless 方案混合部署,对性能敏感应用部署 Proxyless 模式,对于周边应用采用 Thin SDK 部署方案,多种数据面共同由统一控制面进行调度。 将这两种形态统筹来看,在不同的业务场景、不同的迁移阶段、不同的基础设施保障情况下, Dubbo 都会有 Mesh ⽅案可供选择,⽽这进⼀步的都可以通过统⼀的控制⾯进行治理。 未来的部署 1、部署在K8s上 上图是 Dubbo 3.0 未来期望在 Kubernetes 上的部署方案。Dubbo 3.0 将在服务发现模型上对其 Kubernetes 的原生服务,支持不需要部署独立的注册中心就可以实现互相调用。 2、部署在Istio上 上图是 Dubbo 3.0 未来期望在 Istio 上的部署方案。这里采用的是 Thin SDK 与 Proxyless 混合部署模式,如图中的 Pod 1 和 Pod 3,数据流量由 Dubbo Service 直接发出,而 Pod 2 部署的是 Thin SDK 模式,流量由 Sidecar 进行拦截后流出。 柔性增强规划 云原生带来了技术标准化的重大变革。如何让应用在云上更简单地创建和运行,并具备可弹性扩展的能力,是所有云原生基础组件的核心目标。借助云原生技术带来的弹性能力,应用可以在极短时间内扩容出一大批机器以支撑业务需要。 比如为了应对零点秒杀场景或者突发事件,应用本身往往需要数千甚至数万的机器数来提升性能以满足用户的需要,但是在扩容的同时也带来了诸如集群节点极多导致的节点异常频发、服务容量受多种客观因素影响导致节点服务能力不均等一系列的问题,这些都是在云原生场景下集群大规模部署时会遇到的问题。 Dubbo 期待基于一种柔性的集群调度机制来解决这些问题。这种机制主要解决的问题有两个方面,一是在节点异常的情况下,分布式服务能够保持稳定,不出现雪崩等问题;二是对于大规模的应用,能够以最佳态运行,提供较高的吞吐量和性能。 • 从单一服务视角看,Dubbo 期望的目标是对外提供一种压不垮的服务,即是在请求数特别高的情况下,可以通过选择性地拒绝一些的请求来保证总体业务的正确性、时效性。 • 从分布式视角看,要尽可能降低因为复杂的拓扑、不同节点性能不一导致总体性能的下降,柔性调度机制能够以最优的方式动态分配流量,使异构系统能够根据运行时的准确服务容量合理分配请求,从而达到性能最优。 Dubbo 3.0 路线图 Apache Dubbo 3.0.0 作为捐给 Apache 后的一个里程碑版本已经在今年 6 月份正式发布了,这代表着 Apache Dubbo 全面拥抱云原生的一个节点。 在 2021 年 11 月我们会发布 Apache Dubbo 3.1 版本,届时我们会带来 Apache Dubbo 在 Mesh 场景下部署的实现与实践。 在 2022 年 3 月我们会发布 Apache Dubbo 3.2 版本,在这个版本中我们将带来全新的大规模应用部署下智能流量调度机制,提高系统稳定性与资源利用率。 最后,Apache Dubbo 3.0 已经和阿里巴巴集团内部的 RPC 框架实现了融合,期望用它来解决内部落地问题,做到技术栈统一。未来,Apache Dubbo 3.0 将大规模落地阿里集团,承载 618、双十一等复杂业务场景。 社区会尽可能保证一个较短的发版周期,及时对已有的问题进行修复。社区衷心地希望欢迎大家向社区提交 issue 和 PR,社区的同学会尽快进行 review、回复。感谢大家的支持! ﹀ ﹀ ﹀ 想要讨论更多与 Dubbo 相关的内容,可搜寻群号:21976540 加入

优秀的个人博客,低调大师

Spring注解驱动开发之七——BeanPostProcessor 执行原理、在spring 中的应用

本文包含以下内容: BeanPostProcessor 执行原理 断点查看 总结运行过程中的执行原理 BeanPostProcessor 在spring 中的应用 ApplicationContextAwareProcessor帮助组件注入IOC 容器 InitDestroyAnnotationBeanPostProcessor用于处理 @PostConstruct 、@PreDestroy 注解 BeanValidationPostProcessor 数据校验 1.BeanPostProcessor 执行原理 1)断点查看代码 1.在MyBeanPostProcessor添加断点,运行测试方法通过观察栈,查看执行流程 下方长长的英文是调试中右下角的窗口: 2. <init>:84, AnnotationConfigApplicationContext 初始化容器 public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) { this(); register(annotatedClasses); refresh(); } 3. refresh:543, AbstractApplicationContext 调用refresh()函数 ,实例化所有剩余的单例 // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); 4 .finishBeanFactoryInitialization:867, AbstractApplicationContext // Instantiate all remaining (non-lazy-init) singletons. beanFactory.preInstantiateSingletons(); preInstantiateSingletons:761, DefaultListableBeanFactory,具体通过getBean函数进行初始化剩余的Bean getBean(beanName); 5. doGetBean:302, AbstractBeanFactory 这一步 通过getSingleton 获取单实例, 通过初始化createBean 方法创建Bean // Create bean instance. if (mbd.isSingleton()) { sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() { @Override public Object getObject() throws BeansException { try { return createBean(beanName, mbd, args); } catch (BeansException ex) { // Explicitly remove instance from singleton cache: It might have been put there // eagerly by the creation process, to allow for circular reference resolution. // Also remove any beans that received a temporary reference to the bean. destroySingleton(beanName); throw ex; } } }); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } 6. createBean:483, AbstractAutowireCapableBeanFactory,调用初始化,获取Bean 实例 Object beanInstance = doCreateBean(beanName, mbdToUse, args); 7. doCreateBean:555, AbstractAutowireCapableBeanFactory 进入到具体创建Bean 中 通过populateBean() 进行填充Bean 操作 Object exposedObject = bean; try { populateBean(beanName, mbd, instanceWrapper); if (exposedObject != null) { exposedObject = initializeBean(beanName, exposedObject, mbd); } } 8.最后可以看到 initializeBean:1620, AbstractAutowireCapableBeanFactory 中执行 applyBeanPostProcessorsBeforeInitialization和 applyBeanPostProcessorsAfterInitialization进行 postProcessBeforeInitialization()函数和postProcessAfterInitialization()函数的调用;中间调用 invokeInitMethods(beanName, wrappedBean, mbd) ;进行 初始化 函数的调用 Object wrappedBean = bean; if (mbd == null || !mbd.isSynthetic()) { wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); } try { invokeInitMethods(beanName, wrappedBean, mbd); } catch (Throwable ex) { throw new BeanCreationException( (mbd != null ? mbd.getResourceDescription() : null), beanName, "Invocation of init method failed", ex); } if (mbd == null || !mbd.isSynthetic()) { wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); } 9. applyBeanPostProcessorsBeforeInitialization:409,AbstractAutowireCapableBeanFactory 最后通过 getBeanPostProcessors() 扫描所有的 BeanPostProcessor 直到遇到null 进行返回退出循环 @Override public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName) throws BeansException { Object result = existingBean; for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) { result = beanProcessor.postProcessBeforeInitialization(result, beanName); if (result == null) { return result; } } return result; } 2) 总结 运行过程中的执行原理 通过查看断点调试结果, 可以看到,创建单例容器Bean 通过 populateBean() 函数进行填充,invokeInitMethods(beanName, wrappedBean, mbd); 函数进行初始化,然后前后有applyBeanPostProcessorsBeforeInitialization和applyBeanPostProcessorsAfterInitialization 进行对所有的BeanPostProcessor 扫描执行。 2.BeanPostProcessor 在spring 中的应用 在spring 中大量使用BeanPostProcessor ,进行处理,下面举例3个进行断点、代码测试: 1).ApplicationContextAwareProcessor 帮助组件注入IOC 容器 在Dog 中实现 implements ApplicationContextAware 接口,编写, setApplicationContext 将容器保存至属性方法中 @ComponentpublicclassDogimplementsApplicationContextAware{ private ApplicationContext applicationContext; public Dog(){ System.out.println("dog constructor..."); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { // TODO Auto-generated method stub this.applicationContext = applicationContext; }} 1. postProcessBeforeInitialization:97,ApplicationContextAwareProcessor 可以看到 通过 postProcessBeforeInitialization 函数进行将容器进行赋值 2.通过invokeAwareInterfaces判断Bean 的类型进行类型转换,并进行赋值 private void invokeAwareInterfaces(Object bean) { if (bean instanceof Aware) { if (bean instanceof EnvironmentAware) { ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment()); } if (bean instanceof EmbeddedValueResolverAware) { ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver); } if (bean instanceof ResourceLoaderAware) { ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext); } if (bean instanceof ApplicationEventPublisherAware) { ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext); } if (bean instanceof MessageSourceAware) { ((MessageSourceAware) bean).setMessageSource(this.applicationContext); } if (bean instanceof ApplicationContextAware) { ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext); } } } 2).InitDestroyAnnotationBeanPostProcessor 用于处理 @PostConstruct 、@PreDestroy 注解。 查看 InitDestroyAnnotationBeanPostProcessor 源码, postProcessBeforeInitialization 函数代码,如下: @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass()); try { metadata.invokeInitMethods(bean, beanName); } catch (InvocationTargetException ex) { throw new BeanCreationException(beanName, "Invocation of init method failed", ex.getTargetException()); } catch (Throwable ex) { throw new BeanCreationException(beanName, "Failed to invoke init method", ex); } return bean; } 使用 findLifecycleMetadata(bean.getClass()); 获取所有的生命周期函数,通过 metadata.invokeInitMethods(bean, beanName); 进行代理调用对应的生命周期函数 public void invokeInitMethods(Object target, String beanName) throws Throwable { Collection<LifecycleElement> initMethodsToIterate = (this.checkedInitMethods != null ? this.checkedInitMethods : this.initMethods); if (!initMethodsToIterate.isEmpty()) { boolean debug = logger.isDebugEnabled(); for (LifecycleElement element : initMethodsToIterate) { if (debug) { logger.debug("Invoking init method on bean '" + beanName + "': " + element.getMethod()); } element.invoke(target); } }} 3).BeanValidationPostProcessor 数据校验 用于进行数据校验,查看 BeanValidationPostProcessor的源码 postProcessBeforeInitialization 和 postProcessAfterInitialization 代码如下,进行 doValidate()函数进行数据校验。 @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (!this.afterInitialization) { doValidate(bean); } return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (this.afterInitialization) { doValidate(bean); } return bean; } -END- 可以关注我的公众号,免费获取价值1980元学习资料 点击“在看”,学多少都不会忘~ 本文分享自微信公众号 - 阿聪的全栈之路(gh_ffab7c84fb0c)。如有侵权,请联系 support@oschina.cn 删除。本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册