本文由玉刚说写作平台提供写作赞助,版权归玉刚说微信公众号所有
原作者:Mr.s(猪_队友)
版权声明:未经玉刚说许可,不得以任何形式转载

架构就像是一场进化史,根据不同时期的需求,演变出不同的架构,车轮滚滚,到今天,移动端框架百花齐放,让人目不暇接。但是其中的本质是磨灭不了的,换言之根本没有磨灭而是隐藏到了人们所看不到的地方,但是依旧发挥着不可或缺的作用。

为什么需要架构?

1、一个Activity走天下,包含各种模块和功能
2、臃肿类太多
3、不同功能,不同层次拎不清楚,混合在一起
4、重复代码太多,复用性为差。
5、无法协作开发,
6、耦合严重,bug太多
等等

当我们新进一个公司,接受别人的项目的时候,基本都会说句MMP,SHI一样的代码啊,啥玩意啊,搞得什么啊。

我擦,我就改了一个参数,怎么全乱套了,一个功能怎么天上地下都需要改啊。
没有经受过痛苦的人,是不会想了解架构的,只有痛苦过的人,才会急切的了解架构,好好做人,不作孽。

进化1.0 MVC

人们刚接触编程,相信第一个遇到的框架就是MVC,不管你经意还是不经意写出来的Android程序他就是MVC框架,只不过是MVC框架的某一个变种(最混乱的那种)。Android系统本身就根据MVC建造的。

  • XML的View层
  • Activity/Fragment的Controller层
  • 数据Model层。

我们编写程序刚开始应该是这种结构

image.png

View层和Controller交织在一起。在Activity里面处理各种事件和逻辑,界面的显示和更新都在这里,除了按功能分出去的模块外,能在Activity里面写的都会堆在这里面,代码常常达到上千行,别说别人,自己再回到头看的时候也是头皮发发麻。
所以我们开始试着学Android系统的MVC来梳理代码。当然每个人从众理解的也不大一样。
不管是单向依赖还是双向依赖都是为了把处理的职责交给Controller,这一点是不变的,所以不管你用的是哪一种形式都是不打紧的,没有对与错。根据自己的需求选择最适合的一种依赖关系。

image.png

优点:

由于MVC很好的分离了视图层和业务层,所以它具有以下优点

  • 耦合性低
  • 开发速度快
  • 可维护性高
  • 易于理解

缺点:

  • 由于MVC的设计思想是从Model出发,而没有考虑到View端的复杂性,这样导致的问题是Model难以符合复杂多变的View端变化。导致Model的作用很小,而很多View层的职责也转移到了Controller层。Controller变得臃肿不堪。耦合性也变高了。
  • 测试困难

MVC使用的误区

其实我在刚开始编程的时候,

误区一:一度认为Model就是实体类(Entity)

正解:是MVC的Model应该有两个功能:

  • 处理业务逻辑
  • 提供View显示的数据

误区二:把业务逻辑全部放在Controller端

正解:Model也会处理业务逻辑

这两个误区其实也和MVC的构成有关系,很容易让人误解,但是本质上还是不理解MVC,特别是对Model的作用不明白。
其实Model在MVC框架里面的作用不仅仅是Entry,Model在MVC中起着很重要的作用,它更应该是业务逻辑的真正实现层,而Controller层更应该作为一个桥接的作用,把View的求情转发给Model,再把Model处理结束的消息告诉View。这样做也就是界面和代码(逻辑)分离。

进化2.0 MVP

但是踏入误区的人太多了,大家都在Activity里面处理逻辑因为太方便了(写的很爽)。
但是这样之后,我们Activity的职责太多了,耦合也严重,所以我们就想着怎么能给Activity减负,同时把耦合也降下来,所以就想找个哥们来替Activity分担的责任,大家最后都只有一个责任,各层关系也相对好理解,大家照着这个方式写,那多好,不用加班,天天都很快乐。
既然Activty这么愿意和View搞到一起(解耦太高了),那么就让他们俩在一起(在一起,在一起,在一起),共同负责View,我们在招聘一个职业经理人(Presenter)来处理事务,有啥事都找Presenter,你看着多方便啊,MVP是一个真正意义上的隔离View的细节和复杂性的模式

image.png

至于虚线的部分,这个和楼上的MVC的虚线一样,每个人的理解不同,情况不同,选择合适的就好,不用纠结谁对对错,本质不变就可以的。

这样就简单多了,Activity和View两人各种腻歪秀恩爱,各层的关系也一目了然,大家各干各的事情,想要什么和Presenter说一声,有啥事情Presenter也会汇报,简直爽的不要不要的。Presenter也会处理和View层的交互,Presenter在手天下我有。

优点:

1、模型与视图完全分离(就像牛郎和和织女隔了一个银河,每次都需要靠燕子来传信,好可怜~~)

2、可以更高效地使用模型,因为所有的交互都发生在一个地方——Presenter内部

3、(Presener的复用)一个Presener可以用于多个视图(View),而不需要改变Presenter的逻辑。视图(View)的变化比模型(Model)的变化更频繁的多 ,所以这样超级方便。

4、(View的复用)View可以进行组件化。在MVP当中,View不依赖Model。这样就可以让View从特定的业务场景中脱离出来,可以说View可以做到对业务逻辑完全无知。它只需要提供一系列接口提供给上层操作。这样就可以做高度可复用的View组件。

5、更容易单元测试

缺点:
1、由于对视图的渲染放在了Presenter中,所以视图View和Presenter的交互会过于频繁。特别是需要修改视图的时候,Presenter也需要跟着修改,很麻烦。

2、Presenter中除了业务逻辑以外,还有大量的View->Model,Model->View的手动同步逻辑,造成Presenter比较笨重,维护起来会比较困难。

3、其实总的来说就是结构很清晰,业务逻辑也很明白,耦合低,但是就是自己写的麻烦,Presenter不好维护,工作量太大,太笨重,有点像MVC中的Activity了,职责太多了。也可以把这三个缺点总结成一句话,麻烦,尾大不掉。

进化3.0 MVVM

但是吧,你们爽了Presenter可就累坏了,大事小事都要经过Presenter,就连放个屁也要Presenter来扒裤子,日子过得久了,Presenter总会有意见啊,不带这么使唤人的。

我们就想啊,以前过多的依赖Activity造成结构混乱,耦合太高,后来有了Presenter,虽然耦合大大降低,但是还是过于依赖Presenter,为啥总会过于依赖某一个东西呢,就不能大家都干点活,耦合性也不高的吗?就不能大家有事相互通知吗?非要那么懒依赖别人吗?总要有点正能量吧。那么我们就把Presenter辞退,引入了一个新的小伙伴VM(ViewModel即 Model of View)它即包含了Modle也有View的状态。

MVVM模式中,一个ViewModel和一个View匹配,它没有MVP中的IView接口,而是完全的和View绑定,所有View中的修改变化,都会自动更新到ViewModel中,同时ViewModel的任何变化也会自动同步到View上显示。

这种自动同步的原因其实就是是ViewModel中的属性都实现了observable这样的接口,也就是说当使用属性的set的方法,都会同时触发属性修改的事件,使绑定的UI自动刷新。

在android中DataBinding帮助我们实现MVVM,在XML进行数据绑定,增加了XML的重量,不再像以前那样仅仅是布局,均衡了各部分的职责。

所以MVVM比MVP更升级一步,在MVP中,V是接口IView, 解决对于界面UI的耦合; 而MVVM干脆直接使用ViewModel和UI无缝结合, ViewModel直接就能代表UI.

image.png

优点:

1、解决了MVP大量的手动View和Model同步的问题,提供双向绑定机制。提高了代码的可维护性。大大方便了开发者。(只有试过才知道有多方便)

2、简化测试。

3、响应式编程更方便。

缺点:

1、过于简单的图形界面显得大材小用(让开发者麻烦的都是缺点)

2、视图状态较多,ViewModel的构建和维护的成本都会比较高。

3、数据绑定的声明是指令式地写在View的模版当中的,这些内容是没办法去打断点debug的。(这是很碉堡的,只能自己细心了)

进化4.0 CLEAN

进化了一代有一点,作为东家的谷歌坐不住了,给不给我面子了,来来来,我给你们一个终结的,CLean~~~
WTF?这个洋葱一个的家伙是谁?再给你一个圈就是五环了~~四个圈圈了不起啊?

image.png

官方的话说来就是:

独立

  • 独立于框架。该体系结构不依赖于某些特征库软件库的存在。这允许您将此类框架用作工具,而不必将您的系统塞入其有限的约束中。
    可测试。可以在没有UI,数据库,Web服务器或任何其他外部元素的情况下测试业务规则。
  • 独立于UI。UI可以轻松更改,而无需更改系统的其余部分。例如,可以使用控制台UI替换Web UI,而无需更改业务规则。
  • 独立于数据库。您可以换掉Oracle或SQL Server,用于Mongo,BigTable,CouchDB或其他东西。您的业​​务规则未绑定到数据库。
  • 独立于任何外部机构。事实上,您的业务规则根本不了解外部世界。

依赖规则

同心圆代表软件的不同领域。一般来说,你走的越远,软件就越高。外圈是机制。内圈是政策。
使这种架构工作的首要规则是依赖规则。此规则表明源代码依赖项只能指向内部。内圈中没有任何东西可以对外圈中的某些东西一无所知。特别是,内圈中的代码不得提及在外圈中声明的内容的名称。这包括功能,类。变量或任何其他命名的软件实体。
外圈中使用的数据格式不应该被内圈使用,特别是如果这些格式是由外圈中的框架生成的话。我们不希望外圈中的任何东西影响内圈。

官方的话就是绕,其实就是依赖是能向里依赖,白话就是内圈向外圈提供服务,比如你去食堂吃饭,选择不同的窗口,对着里面喊到给我来一份鱼香肉丝。你不用管里面的师傅是不是昨天那一个,也不用管里面的菜是不是昨天的那个口味,你只管你的目的是来一份鱼香肉丝就可以了,里面的菜和师傅,也不用管你是谁,是不是个漂亮的MM,他们只管自己事情,对他们圈外的不依赖,反正你依赖我就可以了。

image.png

Clean将核心业务(Domain层)、UI相关(Presenter层)以及数据加载(Data层)彼此独立开来,不同的层之间由接口依次连接起来,但却又彼此不了解彼此的具体实现。

优点:

1、代码复用性更高
2、更易于测试
3、耦合度更小

缺点:

1、学习难度大(容易懵逼)
2、代码量大(一个小Demo都够你写一壶了)

从MVC->MVP->MVVM->MVP-Clean 后者解决了前者遗留的问题,把前者的缺点优化成了优点。一代更比一代强,但是都是需要代价的,学习成本更高,更加规范,但是并不一定就谁比谁更好,只有谁更适合你的项目,都有优劣,怎么选择需要靠自己了。
其实MVX系列的本质一直未变:

  • 关注点分离原则
  • 高内聚低耦合原则

同时也希望适度设计,不要太过追求某一点。会得不偿失。找到最经济的方案才是王道,也可以多种方案配合一起配合。

image.png

如何能够更好的将多种架构表现在一款APP里面呢?对!组件化。

组件化

image.png

组件化开发就是将一个app分成多个模块,每个模块都是一个组件(Module),开发的过程中我们可以让这些组件相互依赖或者单独调试部分组件等,但是最终发布的时候是将这些组件合并统一成一个apk,这就是组件化开发。
不同的Module我们根据职责划分选择不同的架构,可以最经济的开发一款APP。

image.png

目前成熟的方案有阿里的ARouter,通过路由的方式进行通信,感兴趣的同学可以学习下。

  • 优点

1、低耦合
2、可以针对测试(降低测试成本)
3、复用性强
4、支持并行开发
5、适合使用多进程
6、减小编译时间

  • 缺点

1、不适合小型应用或者独立开发
2、各组件开发人员联调,开发进度不一致等问题

  • 应用场景

假如一个app用户量很大,业务丰富,比如某宝,很明显不是一个小团队可以开发的了的,如果不分模块的话,工程的复杂度就太大了,编译时间估计半天都完不成,维护更不用说了,简直不是人干的了。所以很适合分模块来开发。各个模块的耦合性也能降到最低,不至于一个模块有问题,牵连别的模块。编译时间也会大大减小。每个模块相当于一个小APP,更容易灵活的编译,编写。测试也只需要测试本模块,最后联调整体就可以了。所以说业务越复杂,组件化越有优势。

小结

移动端架构的差异化体现在通信机制上。通信机制主要分为3种:
1) 对象持有
2) 接口持有
3) 路由

对象持有最简单,但是解耦率最低;
接口持有较为复杂,实现解耦的需求,但是解耦不彻底,相互持有交互方的接口。
路由机制可以实现完全解耦,实现组件化

完了吗?不不不~~移动端不仅仅Android和ios,还有前端,上面仅仅是MVX的进化史,同时还有不同分支的进化。

当下移动端的趋势,越来越向大前端靠拢了,Android的道友们也越来越瑟瑟发抖,看到RN,FLutter等等的兴起,越来越觉得仅仅学习Android是不够的,还要学习跨平台,大前端的知识。
俗话说他山之石可以攻玉,我们对架构的思考也是如此,不能仅限于自己的一亩三分地,也要学习下友军的套路。
这类一般都是响应式视图的系统,MVVM就是个响应式,所以其实都是有联系的。

进化 5.0 Flutter/ReatNative

  • Flutter
    是Google用以帮助开发者在Ios和Android两个平台开发高质量原生应用的全新移动UI框架,也是据说要把Android替换掉的一个恐怖存在(目前现在还是个弱鸡)

  • React Native
    让开发者使用 JavaScript 和 React 编写应用,利用相同的核心代码就可以创建 Web,iOS 和 Android 平台的原生应用。React Native 的宗旨是,学习一次,高效编写跨平台原生应用。

  • ReatNative

JS 开发者可以用类似DOM编程模型就可以开发原生APP,画UI只需要画到virtual DOM 中,不需要特别关心具体的平台, RN 会把应用的JS代码编译成一个js文件,RN的整体框架目标就是为了解释运行这个js 脚本文件,通过bridge 传递到native , 然后根据数据属性设置各个对应的真实native的View。

image.png

RN官方给的框架是Flux

image.png

  • action 封装请求
  • dispatcher 注册处理器、分发请求
  • store 是处理器,处理业务逻辑,保存数据
  • view 根据store提供的数据进行展现;接受用户的输入并发出action请求。

解耦很细,做一个功能会涉及很多地方,所以人们对于这个官方架构进行简化

Reflux:

Reflux.png

这个三层框架就大大简化了flux的复杂交互。
不管是面向对象还是面向状态,其实本质上是一致的。三层框架能够很好处理视图,数据,事件,逻辑的问题。

我们也可以参考另外一种架构

Redux:

Redux.png

我们可以看出 其实也是分为三层,组件,Store,Action。组件绑定这Action和state,store reducer都是页面单例,易于管理。action为请求dto对象,是请求类型,请求数据的载体,reducer是处理请求的方法。不允许有状态,必须是纯方法。必须严格遵守输入输出,中间不允许有异步调用。不允许对state直接进行修改,要想修改必须返回新对象。

这就是 面向状态编程

react的组件只关注数据的最终状态,数据是怎么产生的并不关心。这个是RN的核心思想。

  • 优点

1、提升产品迭代速度,APP迭代周期变短
2、减少研发成本(可不,ios和android都可以下岗了)
3、提升开发测试效率(不用双端两套代码分开测试了)

  • 缺点

1、原生有些功能实现不了或者说很复杂
2、需要掌握IOS和Android的知识,个人学习成本高
3、需要兼容IOS和Android(很苦逼)

  • Flutter

今年谷歌推出了Flutter的Beta版本,离正式的出生越来越近了。我们来看看这个新同学吧。

Flutter主要解决了移动开发中的两个重要问题,一是原生应用程序的性能与平台的集成;二是提供多平台、可移植的UI工具包支持高效应用开发。

和RN的跨平台思想不同
RN是将一种设计理念延伸到两个平台,而Flutter则实现了一套代码,部署多个平台。解决了RN开发需要IOS和Android基础的窘迫和成本。

在Flutter中,每个应用程序都是Widget,这点和其他的应用框架不一样,Flutter的对象模型是统一的,也就是控件。
Flutter 基本上就是一个 V(View),响应式视图,它可以是无状态或有状态的 widget,就连 AppCompatActivity 也是一个 widget。是不是很过分?简单到粗暴。采用widget 和 state分离 ,比如我们在原生里大量重复代码来管理 Activity 的生命周期。但是在 Flutter 中,这是不需要的,从设计的角度就解决了这个问题。就像LifeCycles也是这种思想,解决了生命周期的问题。
对于这种面向状态编程的思想,架构和RN是类似,同时我们也期待Google的官方架构指导的出现,会不会有更多的惊喜呢?

image.png

  • 优点

1、响应式视图,不需要JavaScript的桥接器
2、性能更好,兼容性更好
3、代码将AOT编译为本机(ARM)代码
4、美观,可定制的UI组件,开发人员完全控制UI组件和布局
5、热重新加载

  • 缺点

1、包体很大需要打包自己的SDK
2、占用内存空间大
3、启动时间很慢
4、流畅性和原生有差距

不过因为FLutter还没与正式出版,相信FLutter会解决这些缺点的。

  • RN和Flutter差别:

对于这两者系统架构的区别 这里有两张图:

Flutter.png

ReatNative.png

我们可以看出来 主要差别就是Widgets的差别,还有Bridge中间层的区别,Flutter从理论上的速度要别RN高很多,没有了中间层的倒转,有自己的Widgets可以保证IOS和android上的统一。
实现跨平台的这两种方式也是架构设计的体现,Bridge有没有和Presenter很相似,连接着js和系统。
RN是将一种设计理念延伸到两个平台,而Flutter则实现了一套代码,部署多个平台。
不同的设计思想,会走向不同的路,到底是哪一种走的更好,这个可能就需要随着时间去检验了,虽然理论上FLutter要比RN好很多,但是现在的显示很残酷。不过FLutter才是Beta版本,潜力非常大。

image.png

写在最后

本文把目前移动端主流的架构顺了一遍,也是我自己对架构认识的一个时间轴,从刚开始的胡乱编写,到后来有些章法,不过还是对框架的领悟很浅,肯定有很多谬论错轮,希望以我个人对架构的思考可以抛砖引玉,如果可以给你一些帮助,那是极好的一件事情了。

不管是RN还是Flutter,亦或是原生的IOS和Android,其实他们本质编程思想是一致,同样,随着更多更方便的框架出来,我们的理解能力和学习能力受到了很大的挑战,很多同学都说学不动,但是事实上技术的更新换代远比我们学习新知识要快,往往我们刚学会这个,又会出了另外一个,我们只要掌握了它的核心思想,就不会那么累和无奈了。

这就是技和道的差别,通过技最终掌握道,而不是以技学技,那样没有终点。

我们在实际中如何选择架构,其实这个的因素很多,最关键的是一个经济,比如项目时间比较短,还是独立开发,我们可能就要用MVC了,或者说应用的功能相对简单,也会取用MVC,至于MVP,或者MVVM,CLEAN,这个对于大型开发,特别是协作开发,有着很大的帮助。因为我们工作不仅仅是技术,还有项目周期,老板的喜好,历史遗留问题,所以选择最经济的一个,而不是选择那个公认最好的那个。

移动端的趋势越来越走向统一了,我们发现越来越多的公司,需要大前端的人才,所以要一直学习,创造时代的人都没有说累,我们跟着学习的人更不应该喊累,没有学不动,只有不想学。同时欢迎大家来刚哥的知识星球,一起激励,一起砥砺前行。

刚哥的知识星球里有什么?
有美女~
有钱~
有道友~
有你想要的一切

大家可以点个关注,告诉我大家想要深入探究哪些问题,希望看到哪方面的文章,我可以免费给你写专题文章。。哈哈。。。
希望大家多多支持。。你的一个关注,是我坚持的最大动力。。

参考:
React Native运行原理解析
https://blog.csdn.net/xiangzhihong8/article/details/52623852
React Native vs. Flutter 评测
https://mp.weixin.qq.com/s/Or28Gf71wuJrCUP0lKGpGg
ReactNative的组件架构设计
http://react-china.org/t/reactnative/3486
为安卓开发者介绍的移动开发框架 Flutter
https://blog.csdn.net/Px01Ih8/article/details/79683595
等等

我对移动端架构的思考相关推荐

  1. 你们对移动端架构有怎样的思考?

    架构就像是一场进化史,根据不同时期的需求,演变出不同的架构,车轮滚滚,到今天,移动端框架百花齐放,让人目不暇接.但是其中的本质是磨灭不了的,换言之根本没有磨灭而是隐藏到了人们所看不到的地方,但是依旧发 ...

  2. 实时音视频聊天中超低延迟架构的思考与技术实践

    1.前言 从直播在线上抓娃娃,不断变化的是玩法的创新,始终不变的是对超低延迟的苛求.实时架构是超低延迟的基石,如何在信源编码.信道编码和实时传输整个链条来构建实时架构?在实时架构的基础之上,如果通过优 ...

  3. 高老师架构设计思考短句集(1)

    高老师<架构&设计思考>短句集(1) << FEB 2014 >> 俗语说:「授之以渔,而非授之以鱼.」 所以,高老师教你如何思考和创新你自己的架构技术,而 ...

  4. android都图片mat_普通Android码农,该如何逆袭月薪5W的移动端架构师?

    作为一名普通安卓码农,我相信大家都有一个成为移动端架构师的梦. 毕竟,安卓行业越来越内卷,这都是一个老生常谈的话题了.如今会写xml和Activity的程序员一抓一大把,如果你只是一名普通的安卓码农, ...

  5. 各类游戏对应服务端架构

    卡牌.跑酷等弱交互服务端 卡牌跑酷类因为交互弱,玩家和玩家之间不需要实时面对面PK,打一下对方的离线数据,计算下排行榜,买卖下道具即可,所以实现往往使用简单的 HTTP服务器: 登录时可以使用非对称加 ...

  6. 各类游戏对应的服务端架构

    卡牌.跑酷等弱交互服务端 卡牌跑酷类因为交互弱,玩家和玩家之间不需要实时面对面PK,打一下对方的离线数据,计算下排行榜,买卖下道具即可,所以实现往往使用简单的 HTTP服务器: 登录时可以使用非对称加 ...

  7. python是商业组织吗_基于Python的电子商务系统的弹性架构与思考

    DOI:10. 19392 / j. cnki. 1671-7341. 201815075 基于 Python 的电子商务系统的弹性架构与思考 谢钟扬 湖南软件职业学院 湖南湘潭 411100 摘 要 ...

  8. 京东无线服务端架构演进历程

    京东无线服务端 首先,在大会上统计一下数据,不知道多少人的手机上安装了京东手机APP,从举手的比例来看,大家也能猜到.就像这个图画的一样,京东无线服务端的流量像这个瀑布一样,汹涌不绝.服务端就是APP ...

  9. 模拟“12306”服务端架构:100万人同时抢1万张火车票

    ▼数据猿年度征集评选正在进行中▼ 大数据产业创新服务媒体-聚焦数据·改变商业 数据猿官网 | www.datayuan.cn 今日头条丨一点资讯丨腾讯丨搜狐丨网易丨凤凰丨阿里UC大鱼丨新浪微博丨新浪看 ...

最新文章

  1. Microsoft Anti-Cross Site Scripting Library V1.5 发布了
  2. Microbiome:城市海滩和污水中抗生素抗性组研究
  3. 红米note3android驱动,红米note3 mtp驱动
  4. WinCE下多份BSP的维护技巧
  5. SAP UI5 应用启动(bootstrap)过程单步调试
  6. python 图例颜色_python – 来自颜色字典的matplotlib.pyplot scatterplot图例
  7. Ajax 文件上传之PHP心得
  8. 【Next Permutation】cpp
  9. 3. beanstalkd
  10. 不能说のsecret 5
  11. java编程基础总结——20.foreach遍历及lambda表达式
  12. 十行Python代码替换证件照背景颜色
  13. Oracle对索引做统计,Oracle收集索引统计信息
  14. python执行excel公式 语法_Python读取excel文件中带公式的值的实现
  15. K8S Flannel
  16. Android 进阶笔记,包含常用的技术框架、博客社区、书籍等。
  17. 文本域(Textarea)背景的美化
  18. Quick-cocos2d-x 与COCOS2DX 区别
  19. 笑话集网站最近更新网站内容采集
  20. DSP TMF320F2803x 串行通信接口SCI

热门文章

  1. H3C 802.11n的频宽模式
  2. 迅雷和BT有什么区别?迅雷是不是不毁硬盘?速度快吗?
  3. 宏观低速物理 '牛顿篇'
  4. 跟我学习搭建一个SSR
  5. c语言法定节日日历程序,一个完整的日历程序(含有农历)
  6. 计算机连共享盘被禁止用户,共享文件夹无法访问、设置文件夹访问权限、共享文件夹拒绝访问的解决方法...
  7. 【JSTL】JSP 标准标签库JSTL学习
  8. 【大数据】医疗大数据“九大业务应用”相关研究
  9. 10G SFP+万兆BIDI单纤光模块使用注意事项
  10. java ews appointment_EWS Java API 1.1创建约会 - 缺少TimeZoneDefinition