大部分项目都少不了spring的身影,为什么大家对他如此青睐,而且对他的追捧丝毫没有减退之势呢?
Spring是一个轻量级的DI和AOP容器框架。
说它轻量级有一大部分原因是相对与EJB的(虽然本人从没有接触过EJB的应用),重要的是,Spring是非侵入式的,基于spring开发的应用一般不依赖于spring的类。
DI:称作依赖注入(Dependency Injection),和控制反转一个概念,具体的讲,当一个角色需要另外一个角色协助的时候,在传统的程序设计中,通常有调用者来创建被调用者的实例。但是在spring中创建被调用者将不再有调用者完成,因此叫控制反转。创建被调用对象有Spring来完成,在容器实例化对象的时候主动的将被调用者(或者说它的依赖对象)注入给调用对象,因此又叫依赖注入。
AOP:Spring对面向切面编程提供了强有力的支持,通过它让我们将业务逻辑从应用服务(如事务管理)中分离出来,实现了高内聚开发,应用对象只关注业务逻辑,不再负责其它系统问题(如日志、事务等)。Spring支持用户自定义切面。
面向切面编程是面向对象编程的有力补充。面向对象编程将程序分成各个层次的对象,面向切面的程序将运行过程分解成各个切面。AOP是从运行程序的角度去考虑程序的结构,提取业务处理过程的切面,OOP是静态的抽象,AOP是动态的抽象,是对应用执行过程的步骤进行抽象,从而获得步骤之间的逻辑划分。
容器:Spring是个容器,因为它包含并且管理应用对象的生命周期和配置。如对象的创建、销毁、回调等。
框架:Spring作为一个框架,提供了一些基础功能,(如事务管理,持久层集成等),使开发人员更专注于开发应用逻辑。
看完了Spring是什么,再来看看Spring有哪些优点
1.使用Spring的IOC容器,将对象之间的依赖关系交给Spring,降低组件之间的耦合性,让我们更专注于应用逻辑
2.可以提供众多服务,事务管理,WS等。
3.AOP的很好支持,方便面向切面编程。
4.对主流的框架提供了很好的集成支持,如hibernate,Struts2,JPA等
5.Spring DI机制降低了业务对象替换的复杂性。
6.Spring属于低侵入,代码污染极低。
7.Spring的高度可开放性,并不强制依赖于Spring,开发者可以自由选择Spring部分或全部
Struts2 是一个相当强大的Java Web开源框架,是一个基于POJO的Action的MVC Web框架。它基于当年的Webwork和XWork框架,继承其优点,同时做了相当的改进。Struts2现在在Java Web开发界的地位可以说是大红大紫,从开发人员的角度来分析,Struts2之所以能够如此的深入开发人员之心,与其优良的设计是分不开的。
我在项目开发过程中,一个具体的功能的开发流程是:拿到一个具体的功能需求文档和设计好的前台界面(在开发中我不负责设计页面),分析需要从前台传递哪些参数,确定参数的变量名称,在Action中设置相应的变量,这些参数在前台如何显示,并将页面上的一些控件适当使用Struts2提供的服务器端控件来代替,编写Action对应的方法来完成业务逻辑,最后,做一些与配置文件相关的设置。当然实际的开发比这个过程要复杂,涉及到数据库,验证,异常等处理。但是使用Struts2进行开发,你的关注点绝大部分是在如何实现业务逻辑上,开发过程十分清晰明了。
OGNL提供了在Struts2里访问各种作用域中的数据的简单方式,你可以方便的获取Request,Attribute,Application,Session,Parameters中的数据。大大简化了开发人员在获取这些数据时的代码量。
Struts2 的拦截器是一个Action级别的AOP,Struts2中的许多特性都是通过拦截器来实现的,例如异常处理,文件上传,验证等。拦截器是可配置与重用的,可以将一些通用的功能如:登录验证,权限验证等置于拦截器中以完成一些Java Web项目中比较通用的功能。在我实现的的一Web项目中,就是使用Struts2的拦截器来完成了系统中的权限验证功能。
Struts2的Action都是简单的POJO,这样可以方便的对Struts2的Action编写测试用例,大大方便了Java Web项目的测试。
在Struts2添加扩展是一件愉快而轻松的事情,只需要将所需要的Jar包放到WEB-INF/lib文件夹中,在struts.xml中作一些简单的设置就可以实现扩展。常用的Struts2的扩展可以通过这个链接找到:
http://cwiki.apache.org/S2PLUGINS/home.html
Struts2已经把模块化作为了体系架构中的基本思想,可以通过三种方法来将应用程序模块化:
将配置信息拆分成多个文件
把自包含的应用模块创建为插件
创建新的框架特性,即将与特定应用无关的新功能组织成插件,以添加到多个应用中去。
为应用程序添加全局的Result,和在配置文件中对异常进行处理,这样当处理过程中出现指定异常时,可以跳转到特定页面,这一功能十分实用。
我们用struts2时采用的传统的配置文件的方式,并没有使用传说中的0配置。spring3 mvc可以认为已经100%零配置了(除了配置spring mvc-servlet.xml外)。
机制:spring mvc的入口是servlet,而struts2是filter(这里要指出,filter和servlet是不同的。以前认为filter是 servlet的一种特殊),这样就导致了二者的机制不同,这里就牵涉到servlet和filter的区别了。
性能:spring会稍微比struts快。spring mvc是基于方法的设计,而sturts是基于类,每次发一次请求都会实例一个action,每个action都会被注入属性,而spring基于方法,粒度更细,但要小心把握像在servlet控制数据一样。spring3 mvc是方法级别的拦截,拦截到方法后根据参数上的注解,把request数据注入进去,在spring3 mvc中,一个方法对应一个request上下文。而struts2框架是类级别的拦截,每次来了请求就创建一个Action,然后调用setter getter方法把request中的数据注入;struts2实际上是通过setter getter方法与request打交道的;struts2中,一个Action对象对应一个request上下文。
参数传递:struts是在接受参数的时候,可以用属性来接受参数,这就说明参数是让多个方法共享的。
设计思想上:struts更加符合oop的编程思想, spring就比较谨慎,在servlet上扩展。
intercepter的实现机制:struts有以自己的interceptor机制,spring mvc用的是独立的AOP方式。这样导致struts的配置文件量还是比spring mvc大,虽然struts的配置能继承,所以我觉得论使用上来讲,spring mvc使用更加简洁,开发效率Spring MVC确实比struts2高。spring mvc是方法级别的拦截,一个方法对应一个request上下文,而方法同时又跟一个url对应,所以说从架构本身上spring3 mvc就容易实现restful url。struts2是类级别的拦截,一个类对应一个request上下文;实现restful url要费劲,因为struts2 action的一个方法可以对应一个url;而其类属性却被所有方法共享,这也就无法用注解或其他方式标识其所属方法了。spring3 mvc的方法之间基本上独立的,独享request response数据,请求数据通过参数获取,处理结果通过ModelMap交回给框架方法之间不共享变量,而struts2搞的就比较乱,虽然方法之间也是独立的,但其所有Action变量是共享的,这不会影响程序运行,却给我们编码,读程序时带来麻烦。
另外,spring3 mvc的验证也是一个亮点,支持JSR303,处理ajax的请求更是方便,只需一个注解@ResponseBody ,然后直接返回响应文本即可。送上一段代码:
[java] view plain copy
在CODE上查看代码片派生到我的代码片
@RequestMapping(value=“/whitelists”) public String index(ModelMap map) { Account account = accountManager.getByDigitId(SecurityContextHolder.get().getDigitId()); List groupList = groupManager.findAllGroup(account.getId()); map.put(“account”, account); map.put(“groupList”, groupList); return “/group/group-index”; } // @ResponseBody ajax响应,处理Ajax请求也很方便 @RequestMapping(value=“/whitelist/{whiteListId}/del”) @ResponseBody public String delete(@PathVariable Integer whiteListId) { whiteListManager.deleteWhiteList(whiteListId); return “success”; }
1 在Action实现类方面的对比:Struts 1要求Action类继续一个抽象基类;Struts 1的一个具体问题是使用抽象类编程而不是接口。Struts 2 Action类可以实现一个Action接口,也可以实现其他接口,使可选和定制的服务成为可能。Struts 2提供一个ActionSupport基类去实现常用的接口。即使Action接口不是必须实现的,只有一个包含execute方法的POJO类都可以用 作Struts 2的Action。
2 线程模式方面的对比:Struts 1 Action是单例模式并且必须是线程安全的,因为仅有Action的一个实例来处理所有的请求。单例策略限制了Struts 1 Action能做的事,并且要在开发时非凡小心。Action资源必须是线程安全的或同步的;Struts 2 Action对象为每一个请求产生一个实例,因此没有线程安全问题。
3 Servlet依靠方面的对比:Struts 1 Action依靠于Servlet API,因为Struts 1 Action的execute方法中有HttpServletRequest和HttpServletResponse方法。Struts 2 Action不再依靠于Serzvlet API,从而答应Action脱离Web容器运行,从而降低了测试Action的难度。 当然,假如Action需要直接访问HttpServletRequest和HttpServletResponse参数,Struts 2 Action仍然可以访问它们。但是,大部分时候,Action都无需直接访问HttpServetRequest和 HttpServletResponse,从而给开发者更多灵活的选择。
4 可测性方面的对比:测试Struts 1 Action的一个主要问题是execute方法依靠于Servlet API,这使得Action的测试要依靠于Web容器。为了脱离Web容器测试Struts 1的Action,必须借助于第三方扩展:Struts TestCase,该扩展下包含了系列的Mock对象(模拟了HttpServetRequest和HttpServletResponse对象),从而 可以脱离Web容器测试Struts 1的Action类。Struts 2 Action可以通过初始化、设置属性、调用方法来测试。
5 封装请求参数的对比:Struts 1使用ActionForm对象封装用户的请求参数,所有的ActionForm必须继续一个基类:ActionForm。普通的JavaBean不能用 作ActionForm,因此,开发者必须创建大量的ActionForm类封装用户请求参数。虽然Struts 1提供了动态ActionForm来简化ActionForm的开发,但依然需要在配置文件中定义ActionForm;Struts 2直接使用Action属性来封装用户请求属性,避免了开发者需要大量开发ActionForm类的烦琐,实际上,这些属性还可以是包含子属性的Rich 对象类型。假如开发者依然怀念Struts 1 ActionForm的模式,Struts 2提供了ModelDriven模式,可以让开发者使用单独的Model对象来封装用户请求参数,但该Model对象无需继续任何Struts 2基类,是一个POJO,从而降低了代码污染。
6 表达式语言方面的对比:Struts 1整合了JSTL,因此可以使用JSTL表达式语言。这种表达式语言有基本对象图遍历,但在对集合和索引属性的支持上则功能不强;Struts 2可以使用JSTL,但它整合了一种更强大和灵活的表达式语言:OGNL(Object Graph Notation Language),因此,Struts 2下的表达式语言功能更加强大。
7 绑定值到视图的对比:Struts 1使用标准JSP机制把对象绑定到视图页面;Struts 2使用“ValueStack”技术,使标签库能够访问值,而不需要把对象和视图页面绑定在一起。
8 类型转换的对比:Struts 1 ActionForm 属性通常都是String类型。Struts 1使用Commons-Beanutils进行类型转换,每个类一个转换器,转换器是不可配置的;Struts 2使用OGNL进行类型转换,支持基本数据类型和常用对象之间的转换。
9 数据校验的对比:Struts 1支持在ActionForm重写validate方法中手动校验,或者通过整合Commons alidator框架来完成数据校验。Struts 2支持通过重写validate方法进行校验,也支持整合XWork校验框架进行校验。
10 Action执行控制的对比:Struts 1支持每一个模块对应一个请求处理(即生命周期的概念),但是模块中的所有Action必须共享相同的生命周期。Struts 2支持通过拦截器堆栈(Interceptor Stacks)为每一个Action创建不同的生命周期。开发者可以根据需要创建相应堆栈,从而和不同的Action一起使用。
(1) 对象/关系数据库映射(ORM)
它使用时只需要操纵对象,使开发更对象化,抛弃了数据库中心的思想,完全的面向对象思想
(2) 透明持久化(persistent)
带有持久化状态的、具有业务功能的单线程对象,此对象生存期很短。这些对象可能是普通的JavaBeans/POJO,这个对象没有实现第三方框架 或者接口,唯一特殊的是他们正与(仅仅一个)Session相关联。一旦这个Session被关闭,这些对象就会脱离持久化状态,这样就可被应用程序的任 何层自由使用。(例如,用作跟表示层打交道的数据传输对象。)
(3) 事务Transaction(org.hibernate.Transaction)
应用程序用来指定原子操作单元范围的对象,它是单线程的,生命周期很短。它通过抽象将应用从底层具体的JDBC、JTA以及CORBA事务隔离 开。某些情况下,一个Session之内可能包含多个Transaction对象。尽管是否使用该对象是可选的,但无论是使用底层的API还是使用 Transaction对象,事务边界的开启与关闭是必不可少的。
(4) 它没有侵入性,即所谓的轻量级框架
(5) 移植性会很好
(6) 缓存机制,提供一级缓存和二级缓存
(7) 简洁的HQL编程
(1) Hibernate在批量数据处理时有弱势
(2) 针对单一对象简单的增删查改,适合于Hibernate,而对于批量的修改,删除,不适合用Hibernate,这也是OR框架的弱点;要使用数据库的特定优化机制的时候,不适合用Hibernate
Hibernate和iBATIS 优缺点比较
(注意:iBATIS 是MyBATIS的前生,也就是1.0版本)
Hibernate功能强大,数据库无关性好,O/R映射能力强, Hibernate对数据库结构提供了较为完整的封装,Hibernate的O/R Mapping实现了POJO 和数据库表之间的映射,以及SQL 的自动生成和执行。程序员往往只需定义好了POJO 到数据库表的映射关系,即可通过Hibernate 提供的方法完成持久层操作。程序员甚至不需要对SQL 的熟练掌握, Hibernate/OJB 会根据制定的存储逻辑,自动生成对应的SQL 并调用JDBC 接口加以执行。Hibernate的缺点就是学习门槛不低,要精通门槛更高,而且怎么设计O/R映射,在性能和对象模型之间如何权衡取得平衡,以及怎样用 好Hibernate方面需要你的经验和能力都很强才行,但是Hibernate现在已经是主流O/R Mapping框架,从文档的丰富性,产品的完善性,版本的开发速度都要强于iBATIS。
iBATIS入门简单, 即学即用,提供了数据库查询的自动对象绑定功能,而且延续了很好的SQL使用经验,对于没有那么高的对象模型要求的项目来说,相当完美。iBATIS的缺 点就是框架还是比较简陋,功能尚有缺失,虽然简化了数据绑定代码,但是整个底层数据库查询实际还是要自己写的,工作量也比较大,而且不太容易适应快速数据 库修改。当系统属于二次开发,无法对数据库结构做到控制和修改,那iBATIS的灵活性将比Hibernate更适合。系统数据处理量巨大,性能要求极为 苛刻,这往往意味着我们必须通过经过高度优化的SQL语句(或存储过程)才能达到系统性能设计指标。在这种情况下iBATIS会有更好的可控性和表现。
1. iBATIS需要手写sql语句,也可以生成一部分,Hibernate则基本上可以自动生成,偶尔会写一些Hql。同样的需求,iBATIS的工作量比 Hibernate要大很多。类似的,如果涉及到数据库字段的修改,Hibernate修改的地方很少,而iBATIS要把那些sql mapping的地方一一修改。
2. iBatis 可以进行细粒度的优化
比 如说我有一个表,这个表有几个或者几十个字段,我需要更新其中的一个字段,iBatis 很简单,执行一个sql UPDATE TABLE_A SET column_1=#column_1# WHERE id=#id# 但是用 Hibernate 的话就比较麻烦了,缺省的情况下 hibernate 会更新所有字段。 当然我记得 hibernate 有一个选项可以控制只保存修改过的字段,但是我不太确定这个功能的负面效果。
例 如:我需要列出一个表的部分内容,用 iBatis 的时候,这里面的好处是可以少从数据库读很多数据,节省流量SELECT ID, NAME FROM TABLE_WITH_A_LOT_OF_COLUMN WHERE …一般情况下Hibernate 会把所有的字段都选出来。比 如说有一个上面表有8个字段,其中有一两个比较大的字段,varchar(255)/text。上面的场景中我为什么要把他们也选出来呢?用 hibernate 的话,你又不能把这两个不需要的字段设置为lazy load,因为还有很多地方需要一次把整个 domain object 加载出来。这个时候就能显现出ibatis 的好处了。如果我需要更新一条记录(一个对象),如果使用 hibernate,需要现把对象 select 出来,然后再做 update。这对数据库来说就是两条sql。而iBatis只需要一条update的sql就可以了。减少一次与数据库的交互,对于性能的提升是非常重 要。
3. 开发方面:
开发效率上,我觉得两者应该差不多。可维护性方面,我 觉得 iBatis 更好一些。因为 iBatis 的 sql 都保存到单独的文件中。而 Hibernate 在有些情况下可能会在 java 代码中保sql/hql。相对Hibernate“O/R”而言,iBATIS 是一种“Sql Mapping”的ORM实现。(iBatis 是将sql写在配置文件中的,而hibernate是自己生成的) 而iBATIS 的着力点,则在于POJO 与SQL之间的映射关系。也就是说,iBATIS并不会为程序员在运行期自动生成SQL 执行。具体的SQL 需要程序员编写,然后通过映射配置文件,将SQL所需的参数,以及返回的结果字段映射到指定POJO。使用iBATIS 提供的ORM机制,对业务逻辑实现人员而言,面对的是纯粹的Java对象,这一层与通过Hibernate 实现ORM 而言基本一致,而对于具体的数据操作,Hibernate会自动生成SQL 语句,而iBATIS 则要求开发者编写具体的SQL 语句。相对Hibernate而言,iBATIS 以SQL开发的工作量和数据库移植性上的让步,为系统设计提供了更大的自由空间。
4. 运行效率
在不考虑 cache 的情况下,iBatis 应该会比hibernate 快一些或者很多。
1. 提供了一种管理对象的方法,可以把中间层对象有效地组织起来。一个完美的框架“黏合剂”。
2. 采用了分层结构,可以增量引入到项目中。
3. 有利于面向接口编程习惯的养成。
4. 目的之一是为了写出易于测试的代码。
5. 非侵入性,应用程序对Spring API的依赖可以减至最小限度。
6. 一致的数据访问介面。
6. 一个轻量级的架构解决方案
1. 中断了应用程序的逻辑,使代码变得不完整,不直观。此时单从Source无法完全把握应用的所有行为。
2. 将原本应该代码化的逻辑配置化,增加了出错的机会以及额外的负担。
3. 时光倒退,失去了IDE的支持。在目前IDE功能日益强大的时代,以往代码重构等让人头痛的举动越来越容易。而且IDE还提供了诸多强大的辅助功能,使得 编程的门槛降低很多。通常来说,维护代码要比维护配置文件,或者配置文件+代码的混合体要容易的多。
4. 调试阶段不直观,后期的bug对应阶段,不容易判断问题所在。
Struts、Spring、Hibernate能流行这么多年经久不衰,自然有它的道理。J2EE最先出现的时候,我们一般是采用 JSP+Servlet+JavaBean+EJB的架构,尤其是1998年~2000年这段时间,互联网的泡沫从兴起到破裂,其波澜壮阔程度,丝毫不亚 于2008年开始的这次经济危机,在那个年代,是否掌握EJB开发技术将直接决定你的薪水能否翻一倍或者几倍。不过,Spring的作者Rod Johnson在2002年根据多年经验撰写了《Expert o-ne-on-One J2EE Design and Development》,其后又发表了著名的《Expert o-ne-on-one J2EE Development without EJB》一书,则彻底地改变了传统的J2EE一统天下的开发架构,基于Struts+Hibernate+Spring的J2EE架构也逐渐得到人们的认 可,甚至在大型的项目架构中也逐渐开始应用。下面我们分别说说Spring、Struts和Hibernate的优缺点。
Spring 是一个开源框架,是为了解决企业应用程序开发复杂性而创建的。框架的主要优势之一就是其分层架构,使得每个层之间和类与类之间的关系,由原来的强绑定与强 耦合转变为不绑定和松耦合,直接面向接口编程,把设计与实现相分离的原则发挥到极致,对于单元测试也产生了很深远的影响。在Spring出现之前,如果某 个模块没有完成,做单独模块的单元测试还是很困难的。Spring同时也是 J2EE 应用程序开发的集成框架,因为J2EE是讲究分层理念的,Spring使得J2EE每个层之间的模块职能更加清晰。
不过,对于大型项目的开发,Spring使得原来难以维护的类与类之间的强耦合关系,转变为更加难以维护的XML文件配置,这个工作量也是非常巨大 的,而且更容易出错。而且,随着每个应用 模块的升级,这些模块之间的版本,也不会是同步更新的,对于同一个公共组件,有的模块用的可能是1.0版本,而另 外一个功能模块用的可能是2.0版本,可怕的是1.0版本和2.0版本之间,可能还不兼容,Spring对于这些需求,就无能为力了。所以,有人说 Spring不适合大型项目开发,也是有一定道理的。最近Spring也加入了OSGI标准的实现,也是为了解决不同版本之间同时存在的这些问题。不过, 随着Spring加入的功能越来越多,Spring也就失去了轻量开源框架的特点,变得越来越笨重。
虽然Spring现在也支持了所谓的免配置,可以通过@Autowired标签自行绑定,还可以通过 设置自动绑定加载所有的Hibernate对象,但是如果这些上百个或者数十个中的任何一个Entity对象加载失败,则整个Spring服务就启动不起 来了,这与难于部署的EJB有啥区别呢?而且,令人可笑的是,由于使用了@Autowired标签,相当一部分开发人员不再面向接口编程了,对于 Class A的实例,美其名曰由Spring自行绑定,接口也好,实际实现类也好,就在Spring配置一下就可以了。而Spring最核心的就是面向接口编程和 IOC,没有了面向接口编程,用一个 A a=new A() 来实例化一个Class,有什么不可以呢?少写了一行代码,引入了一个重量级的Spring,究竟为啥使用Spring呢?
对于Hibernate的流行,则是由于开发人员和客户,对于Entity EJB(实体EJB)臃肿的身材及部署的困难,是在极度失望情绪下造成的。既然是轻量级解决方案,那么分布式就不是可选项,没有分布式,那么EJB就无用 武之地了。话又说回来了,Rod Johnson前些年就因为强调绝大部分企业应用是不需要分布式的,从而推出了自己轻量级的Spring解决方案。但是最近一年,随着云计算架构的兴起, 架构是否支持分布式,又是必选项了。技术架构的选型,就跟法国巴黎流行时装一样,今年流行短袖,明年流行下摆,真是典型的十年河东,十年河西。所以,像 SOA、云计算、SaaS、物联网这些大名词,不仅会给客户带来很大的困惑,同样也会给程序员、系统分析师、系统架构师、技术总监带来困惑。从肯定到否 定,再到自我否定,真是符合大自然螺旋式上升的发展规律。
而对于Struts,它一经推出,几乎打败了当时的所有竞争对手。Struts的伟大之处,在于它对网页数据的动态绑定。虽然数据绑定不是一个新名 词,微软在1991年推出Visual Basic1.0的时候,就创造性地发明了让VB程序员又爱又恨的数据绑定,但是对于Web 编程,Struts也还是把数据绑定首次应用到了Web编程上。它能够让人们用Set和Get的形式取得网页数据,而不是单一的黑盒式的 request.getParameter(),从而使得网页数据取值进入面向对象(OO)化时代。
Struts、Hibernate以及Spring本身都是制作精良的框架,但是对于自己产品和项目的应用,一经整合在一起,却不一定很适用。比如 说,对于数据库相关的MIS(管理信息系统)系统中,增加、修改、删除、查询功能是最基本、最常见、必不可少的。对于这些最基本的功能,不同的架构师,则 会做出不同的选择。有的架构师,选择了自动生成的理念,做一个代码自动生成器,设计好数据库表结构,单击一个脚本,或者用Eclipse插件的形式,做个 图形化生成界面,自动生成SSH框架,完成数据库的增加、修改、删除、查询操作。这么做的好处是数据库修改了,代码自动生成就可以了,使得程序员不用再维 护这些无聊的代码。不过缺陷也是致命的,一是随着Struts、Hibernate、Spring的升级,这个工具也不得不跟着升级,而做这个工具的程序 员,可能早就离开公司了,后续版本无法维护;二是如果有的业务逻辑跟这些生成的代码有交叉,数据库变更后,代码也无法再次生成了。三是公司的系统架构,则 被严重限制在SSH架构基础上,再也无法改变。有人会问:即使限制在这三种架构上,有何不好吗?假设有客户问,你的框架支持云计算吗?你总不能说”由于 Struts、Hibernate、Spring 不支持云计算架构,所以我也不支持”以此取得客户谅解吧。因此,依赖于第三方架构的产品体系架构,随着时间的推移,受到的限制会越来越大。