您的位置:首页 > 编程语言 > Java开发

Bert Ertman专访:将Spring及遗留应用迁移到Java EE 6平台

2013-11-04 15:42 267 查看
本文来源于我在InfoQ中文站的采访文章,原文地址是:http://www.infoq.com/cn/articles/migrate-spring-legacy-to-javaee6

InfoQ:你好Bert,很高兴能够采访你,请先简单介绍一下自己吧。
Bert:没问题。我叫Bert Ertman,来自荷兰,目前在Luminis工作,这是荷兰的一家服务公司。我们提供咨询服务,主要使用Java技术帮助客户完成项目。此外,我们还做一些产品开发,同时进行大量的开源创新,并借此构建我们自己的产品。我从Java发布不久之后就开始使用Java了,因此非常幸运能在大学期间就在公司实习,并且见证了Java的首个公开Beta版,以及Java Alpha 2,即1.0.0.Alpha 2。早在1995年我就使用Java编写了一些Applets,这些年来我主要就使用Java进行开发,与此同时我也亲眼目睹了Java的发展。在1999年左右,企业开发开始蓬勃发展。那时Applets、Servlet、JSP以及后面的EJB开始崭露锋芒,接下来Java 2企业版发布。在过去的10多年间,我开始使用这些技术为银行、保险公司、政府组织及警队构建企业应用。在这些年间,我使用了J2EE、Spring及Java EE等各种技术,可以这么说,我见证了一个时代的发展。
除了日常工作外,我还是NLJUG Java用户组(即荷兰Java用户组)的负责人,该组织拥有3,500名左右的成员。我们组织自己的活动,并且举办了名为“J-Fall”的会议,该会议一天就吸引了1,200个人参加。一般来说,这个会议会在每年11月的第一周召开。到目前为止,该会议已经举办了10届,非常成功。
从2008年开始,我还成为了Java Champion,对此我感到非常荣幸。这就是我的简单介绍。此外,我还是O'Reilly即将出版的新书《Building Modular Cloud Apps with OSGi》的作者。
InfoQ:谢谢。众所周知,Spring是Java领域最为流行的框架,很多开源或是闭源框架都会与之集成,那么我们为何还要从Spring迁移到Java EE 6上呢?迁移的好处与代价如何?
Bert:这是个非常好的问题。在演讲中我对这个问题做过解释,我首先就提到开发者不应该立马就开始着手进行迁移。从业务角度来说,如果有业务场景需要你这么做才是最为重要的。业务场景可能是你有一个7、8年前构建的应用。你使用Spring来构建应用,因为那时Spring是Java企业开发栈唯一正确的选择,J2EE缺乏太多的特性了,所以J2EE叫做“Java 2 Evil Edition”看起来更合适,在那个年代,使用正确的选择来构建应用是天经地义的事情。不过时过境迁,当时的很多应用都在使用现在已经过时或是陈旧的映射技术来构建,比如说Kodo或是Toplink,这些技术的发展已经有些停滞不前了,维护这些应用变得非常困难。如果老板对你说,这个已经运行了8年的应用还要再运行5到10年,同时还需要添加一些新功能,那么你将面对大量的技术债务。你首先要告诉老板的可能就是我们可以让应用再运行几年,不过需要进行重构。我们需要替换掉一些陈旧的技术,即便不迁移到Java EE而是坚持使用Spring,那么升级工作也需要花费不少时间。为了更能适应未来的发展,我倾向于使用标准技术,因为标准的兼容性会持续很长时间。举个例子,如果你觉得没问题,那么我希望将Kodo替换为JPA,因为JPA将会存在相当长的一段时间。
因此,从现在开始的5年内,你将不会再遇到现在使用Kodo时所遇到的那些问题。这是我要说的一个原因,但不管怎么说,我们面临的工作量还是不小的。不过,我还要多说几句,我不仅是要替换掉一两个组件,而是在业务场景允许的情况下将整个应用都迁移到标准上来,这样可以确保在未来几年的兼容性。我认为这对于迁移来说是个非常合适的业务场景。不过,这在很大程度上取决于你所在的公司是否有这样的业务场景。
Java EE成为令我们所关注的选择还有另外一个原因,这正是我的演讲的另外一个效应,那就是太多的人、太多的开发者们在J2EE之后就没再了解过Java EE。他们会说,J2EE不行,因此会使用Spring,他们从来也没了解过Java EE。但是,在过去的5、6年间有太多的变化,Java EE 5,特别是Java EE 6已经成为当下的主流EE平台。我的意思是,7也很好,不过它太年轻,还不能用在生产上面。支持它的应用服务器也不太多,顶多就那么一两个而已,或许一年之后就会有更多的应用服务器支持它了。不过,由于Java EE 6是主流技术,它真的太强大了,至少与Spring持平。特别是对新的开发来说,我鼓励大家——特别是那些不是Spring粉丝而只是使用这样一个企业栈的Spring开发者们——看看Java EE 6吧,你所需要的一切都在那里了。
依我来看,Java EE对于新的开发来说也是个非常不错的开发栈。不过,适于迁移的业务场景尤其对于我所说的“保守派Spring应用”,甚至是遗留的Spring应用来说是适合的。如果在3个月前使用新的Spring框架来构建Spring应用,那么在这种情况下进行迁移就不太好了。新的Spring栈也对各种Java EE 6技术提供了内建的支持,比如说提供了对JPA的支持、对Restful Web Services、JAX-RS、甚至是JSF的支持。你会看到,随着时间的流逝,Spring框架也在不断向一些Java EE 6技术看齐。
InfoQ:照你这么说,他们二者之间在不断融合,而且彼此之间的兼容性越来越好?
Bert:是的,因为一些新的技术标准,如JPA、JAX-RS等确实是非常棒的标准,这些标准也有非常不错的实现。Spring之所以会集成他们是因为他们确实棒,我觉得Spring就像是个大胶水盒子似的。它也是个集成框架。就好比你拿起这个,再拿起那个,然后在他们之间放点Spring胶水,这样就得到了一个Spring应用。不过事实上,下面的技术却是Java EE技术。Spring也在慢慢地向这个方向转变。他们对于CDI有点犹豫,因为如果开始拥抱CDI,那么Spring就没什么价值了。Spring的核心就是依赖注入,如果有足够的控制,那么整个胶水场景就不复存在了,但这是另一个话题。
InfoQ:对于迁移代价来说,你有什么例证么?
Bert:代价其实是很难计算的,因为没有两个项目是一模一样的。不过,我提议逐步迁移的原因之一就是你无需一下子就把所有事情做完,因为如果一开始就要重构一切的话,那么最后只会一团糟。我提出的迁移途径是首先替换掉真正的技术债务,他们基本上是些过时的框架,如果由于业务的原因不能再往下走,那也没问题。你只需完成迁移的一两个步骤,替换掉一些痛苦点,这也不错。
这是个渐进的迁移过程,如果你有一个项目团队,时间是两个月,他们有自己的日程安排,你也必须要遵循这个安排,因此你不能说,我们花一年时间来迁移吧。如果有几周或几个月的时间来完成部分迁移,那么你就可以开始构建新功能了。接下来可以进行后续的迁移,构建新功能,再做点迁移,然后再构建新功能。这不是一蹴而就的事情,你可以按照适合自己的节奏来做。你不必走到老板面前说,我们要花一年的时间进行迁移,没法再构建任何新功能了。我的迁移过程的一部分就是同时运行Spring与Java EE,这时很容易使用Java EE 6来创建新功能,同时应用的主要部分依旧使用Spring。在同时使用Spring与Java EE时,我们会使用一些技巧通过CDI来查找Spring Bean,这样他们就能用在Java EE 6中了,而通过Spring进行查找则是你不太想做的事情,因为你不打算在Spring上进行投资了。所有新功能都是基于Java EE标准的了。
因此,很难说一个典型的项目迁移的代价是多少。我在演讲中想要表达的是我希望人们能够考虑这一点,我想要告诉他们该如何去做。
InfoQ:我们是不是应该按照周或是月来做计划呢?
Bert:我觉得应该按照天或是周而不是月来做计划。如果按照月来计划迁移,那就意味着有很多个月无法构建新功能,没人会觉得这是个好主意。你应该小步前进,小步迭代。我的建议是从外向内进行。首先替换掉应用边缘处的痛苦之处,然后再逐步向应用核心递进。与此同时,开始使用新的Java EE技术来构建新功能,使之集成到现有的应用。
InfoQ:Spring是个一站式Java框架,提供了典型的Java项目中所需的各种功能,如Web MVC、事务管理、IoC、数据访问集成等等。最重要的是,它只需一个Servlet容器即可运行,比如说Tomcat。既然有了现成的这些功能,我们迁移到Java EE 6仅仅因为Spring是个私有技术,而EE 6是标准么?毕竟,Spring是个开源框架,我们可以获得其源代码。你对此是如何看待的?
Bert:我觉得这是两个问题。首先,也是我为何以二者之间的功能比较作为演讲开场白的原因所在,我被问了很多问题,提问的不少人最近都没有关注过Java EE。他们说,我真的需要依赖注入;或是说,我需要Spring的这个特性,在使用Java EE时我不想牺牲他们,因此我做了这个对照表格。你在企业应用中需要很多功能,如依赖注入、事务、Web框架、面向方面编程、消息、数据访问、Restful Web Services、集成测试等等,他们都是你所需的典型功能,看看Spring提供了哪些呢?
Spring提供了全部,下面来看看Java EE,看看Java EE对于这些技术提供了哪些替代方案。从表中可以看到的是Java EE技术至少与Spring所提供的持平,依我来看,Java EE提供了更好的选择。就拿依赖注入来说,Java EE并不仅仅实现了依赖注入,还提供了上下文依赖注入。你可以注入到一个作用域当中。比如说,你可以将一个Bean注入到请求或是会话当中,在请求或是会话结束时,该Bean就会脱离相应的作用域。
这确实非常强大,CDI还有一个扩展机制,我在迁移过程中使用很多。对于事务、Web等我方才提到的其他一切特性来说都是如此。现在的Java EE对于这些特性都提供了替代者。它只缺乏一样,即集成测试。这也是我总提到Arquillian测试框架的原因所在,它是由JBoss构建的开源框架,不过它并不限于JBoss,你可以在任何应用服务器上使用它。现在,你也在Java EE中拥有了真正强大的集成测试。
我认为对于新的开发或是迁移过程,从主流栈来说,Java EE至少提供了Spring所提供的一切,如果需要其他功能,你可以在此之上更进一步,特别是集成CDI。比如说,有个名为“Apache deltaspike”的开源项目,它包含了大量的CDI扩展。我认为它实际上来自于之前的Seam项目。很多Seam组件都被纳入到了deltaspike中。
他们提供了可选组件来实现安全和大量额外的功能,这些功能可以轻松集成到Java EE中,因为Java EE只是个规范,有不同的厂商来实现它,比如说JBoss、Oracle及IBM等。他们在Java EE之上提供了一整套功能。在演讲后,有人问我,他需要集成jBPM,Spring提供了很好的集成支持,那对于Java EE来说该怎么办呢?对于jBPM来说,你显然应该使用JBoss了。如果看看JBoss在常规的Java EE栈之上提供了什么,你就会发现他们提供了与自己的其他产品的很棒的集成支持,以注解的形式提供。你可以轻松将其与Java EE集成起来,就像与Spring集成一样。
额外的集成也有了,我觉得在平台的下一个版本中会有更多集成,因为CDI是进行依赖注入及与其他技术集成的一个非常重要的规范。我们看到这在Java EE中已经发生了,在Java EE 7中,它更棒了,我认为从长远来看,CDI会成为添加到Java EE中的一门重要技术,因为它使得平台具有可扩展性。此外,Spring栈还提供了Java EE中所没有的东西,比如说NoSQL集成或是社交集成等,我觉得这很好,听起来有些不可思议吧,因为无论是社交还是NoSQL,现在都还没有形成标准,将尚未成熟的东西以标准化的形式纳入到Java EE中是个非常愚蠢的做法,有可能在10到15年后你会发现它并不是一个好标准。过去已经出现过这种情况,比如说老版本的JSF。
我们已经从EJB Entity Beans上看到了这一点, 不是么?出于某些原因,愚蠢的想法将其纳入到了标准之中,现在已经很难再摆脱它了。在使用EJB时,我们不得不在14年的时间内使用Entity Beans。一旦纳入到了标准当中就很难再将其剥离出去。因此,不要做不成熟的标准,不成熟的标准是万恶之源。创新应该通过开源来实现,或者通过Spring框架、或者通过JBoss正在做的某些开源实现、或者通过Apache项目、或者通过其他开源项目。当某个东西已经很棒了,人们开始使用它时就是进行标准化并将其纳入到Java EE当中之时。因此,Java EE要比Spring滞后一些。Spring的创新周期更快,不过Java EE的目标不在于创新,而是标准化,给予你一个企业栈,你可以使用它进行构建,如果需要任何特殊的特性,请将其放在标准之上。
InfoQ:照你这么说,设定规范的主要目的就是确保企业能在未来10年,甚至15年还能继续使用它?
Bert:没错,这就是兼容性!我想很多人会说,标准并不重要。不过我认为他们忽略了这重要一点,那就是标准可以确保长时间的兼容。如果现在选择JPA,那么在未来5年,10年内,你都可以使用JPA。
InfoQ:对于Web MVC这一层次来说,Spring提供了强大的Spring MVC框架,它也可以用做RESTful Service。在EE 6中该如何实现呢,JSF还是其他规范?
Bert:Java EE 6自带了一个Web框架,即Java Server Faces,如果想要进行Web开发,你可以使用它。它是一个基于组件的服务器端有状态的Web框架,不过并不是每个Web应用都适合。我认为你应该思考自己的Web应用要做什么。如果它是个基于请求响应的模型而不是基于组件的模型,那么你就不应该使用JSF,而是使用其他Web技术。只有JSF才是Java EE一部分这个事实并不意味着就没有其他能与Java EE完美结合的Web框架可供我们使用了。事实上,现今已经有很多能与Java EE完美集成的Web框架。你可以使用Vaadin、Wicket或是GWT等Web框架,你可以在Java EE之上使用这些框架。对于Restful Service来说,Java EE有JAX-RS这个Restful Service规范,你可以使用它进行Web开发。我们现在看到了一种趋势,当前的Web应用并没有使用服务器端的Web框架。他们将Web框架应该处理的任务从服务器端迁移到了浏览器端,使用强大的JavaScript,特别是一些非常棒的JavaScript MVC框架,比如说来自Google的AngularJS,你可以在浏览器端构建真正复杂的Web应用,只在浏览器上运行HTML 5与JavaScript。为了调用服务器端,使用JAX-RS创建Restful API,它就像服务器API一样提供Restful Web Service。
我认为在未来几年中,我们会看到这样一种变迁,有状态的Web框架将会变得越来越少,也越来越不重要。这种Web应用的一切将会迁移到客户端,服务器将通过Restful API进行抽象。Java EE将会成为后端栈。后端首先是个Restful Web Service,然后是事务层,接下来是对象关系层,他们之间可能还会有其他东西。这正是Java EE真正强大之处,Web真正强大之处在于运行着HTML 5的浏览器以及Restful Web Service。
InfoQ:你认为迁移到前端是个好的趋势?
Bert:没错。这让服务器摆脱了大的有状态会话。有状态Web框架的一个问题在于他们无法伸缩。特别是对于新的应用来说,你需要处理大量用户与负载,保持无状态是最好的了;Web层也总是充满了痛苦,因为如果有会话,那么Web层就会有大量状态。现在,如果可以摆脱这些会话,那么你可以将其替换为无状态的Restful Service,然后将状态迁移到浏览器端,这是最合适的了,因为你可以使用一些新的HTML 5特性来处理离线Web内容;当连接到服务器时,你只需再次与后端同步即可。
我认为这是Web开发的一个趋势,你不必非得使用JSF,JAX-RS提供了Rest支持。不过JSF 2.x实际上是个相当不错的框架。如果只想为组件风格的Web应用寻找一个Web框架,那么你可以使用JSF 2。它对JSF 1进行了大量改进。问题在于使用JSF替换掉完美的Spring MVC应用有意义么?肯定没有。如果你是不久前使用Spring MVC框架构建了Spring 2.x应用,那么替换成JSF是毫无意义的,因为JSF是个标准。这就是我一直在说的业务场景。如果有合适的业务场景,那么可以迁移,而不应该仅仅因为JSF是个标准就迁移。
InfoQ:当然了。Spring提供了一个强大的测试框架以简化单元测试,我们可以使用模拟测试,因此就无需再启动服务器了,那么在EE 6中该如何实现呢,代价如何?
Bert:我们方才已经说过Arquillian,我在演讲中所用的示例已经展示了如何测试Java EE中的数据访问对象。数据访问对象通过JPA访问数据库,那么你该如何为这些代码编写单元测试呢?如果模拟依赖,那么你需要模拟出EntityManager,剩下的就是需要测试的一些空方法了。接下来你需要做的就是编写单元测试,测试Java是否能够调用某个方法。
这么做有点蠢。在Java EE环境中,你并不想要编写普通的单元测试,你希望针对业务逻辑的单元测试可以更容易地编写,由于Java EE现在有了CDI和依赖注入,你可以轻松地为这些Bean编写单元测试,就像为Spring编写测试那般简单。他们处于同一水平之上,对于存在依赖如DAO等的那些对象来说,他们也是差不多的。说完单元测试,下面来说说集成测试。你希望测试运行在容器中的代码,让容器中的所有服务都可用。借助于Arquillian,你可以创建这些“微部署”,如果要测试DAO,那就需要一个DAO对象,还需要包含着DAO的一两个实体,也许是其他类,然后将这3、4个类打包。接下来,创建应用的这个微部署,将其部署到正在运行着的应用服务器上,运行测试,然后卸载部署,应用服务器依然会保持运行。现在,微部署也有一些代价,不过代价并不高,现在运行测试就好像是运行JUnit测试一样,只不过这是应用的集成测试。因此,现在使用的还是JUnit,你可以通过IDE获得反馈,要么条棒变绿,要么变红,它的运行与单元测试一样简单。
现在有很多人在使用Maven,他们将单元测试与集成测试划分开来。单元测试是会频繁执行的,接下来为集成测试创建一个不同的Maven档案然后运行,每次进行重构你都会运行,或是每天至少在构建服务器上运行测试一次。你可以通过Java EE执行这两种类型的测试,不过Java EE中的某些东西只对应用服务器中的测试有意义,而这正是Arquillian发挥作用之处,只要保持部署足够小,它就能运作的很好。
InfoQ:相比于EJB 2,新的EJB标准已经有了很大的改进。不过,使用EJB与JPA来替换掉Spring的数据访问集成或是事务管理真的有必要么,特别是在现有应用运行良好的情况下?
Bert:我之前其实已经回答了这个问题:这完全取决于业务场景。如果你最近在开发Spring应用,那么你可能已经在使用Spring JPA了,如果开发的时间稍微有些久,那么你可能使用的是Spring Hibernate,并且应用也运行良好,你在这基础之上使用事务,而且对于目前的开发栈来说这依然是恰当的,那就没必要替换。
我认为只有在技术栈完全过时的情况下进行替换才是合理的。如果运行的是某些JDO实现,如KODO,或是使用了老版本的TopLink,那么我觉得替换才是有意义的,如果使用的是JDBC模板,那么我觉得你可以考虑升级成JPA。如果看看最近的Spring栈,你会发现他们也升级为使用JPA了。重申一次,替换与否取决于业务场景,如果一切运转良好,那就没必要替换;如果现在有技术债务,如果有架构上的问题,那就有升级的理由了。EJB与JPA有着良好的集成机制,最后的代码也比使用Spring JPA少很多,因为使用他们无需配置,或者是只需要很少的配置即可。
我展示了一些代码示例,使用了EJB与JPA,代码量比使用Spring少了很多。如果需要,那就可以替换,理解这些代码是很轻松的事情。这些代码非常简洁,你不用再使用那么多代码了,同时在EJB方法中也有自动化的事务边界,这与JPA编程模型的集成堪称完美。
InfoQ:虽然EE 6发布已经有一段时间了,不过很多应用依然在使用EE 5甚至是J2EE 1.4,这些应用也许使用的是Spring,你对这怎么看?
Bert:没错,我觉得这些应用也需要升级。我的意思是如果在产品上有J2EE 1.4应用,那么它已经不再被支持了。如果是J2EE应用,那么它所运行的应用服务器厂商已经在升级了。因此它已经不再支持这些技术栈了,你应该对这些应用进行升级,这是完全不同的事情:如何从J2EE 1.4升级到Java EE 6。
如果是运行在Tomcat上的Spring应用,而我有一个J2EE 5运行时,那么还应该考虑升级么?答案当然是不了。从Spring升级到旧版本的Java EE上是毫无意义的。我认为升级到JavaEE 6上才是有意义的,升级到EE 5或是J2EE 1.4上是没必要的。这是对第一个问题的回答。
InfoQ:你应该知道,在大公司中,迁移并不是一件容易的事情,特别是对于运行良好的应用来说更是如此。比如说,有一个运行在Tomcat上的Spring应用,你该如何说服公司高层将其迁移到EE 6上呢?如果迁移,那么我们还可能需要将服务器由Tomcat换成应用服务器,这很容易么?
Bert:如果问题是这样:我们有Spring,有Tomcat,想要迁移到EE 6,可否?那么管理层可能会说不行。特别是有代价的情况下更是如此,对吧?你应该找到一个业务场景,该场景会在未来几年都会发挥作用,然后看看在现有的Spring应用上做维护以及继续在Spring栈上做新的开发的风险是什么?如果不确定Spring的未来会变成什么样子,而Java EE 6则得到了长足的进步,那么在未来几年中,寻找熟悉相关技术的开发者的难度将会增加。我们还能在未来几年中找到熟悉Spring MVC的开发者么?或者这么说,现在还能找到熟悉KODO的开发者么?
我觉得应该存在这种业务场景。在当前的栈上持续维护以及慢慢迁移到新的运行时上的代价分别是什么。从许可角度来看,Tomcat是免费的,而Java EE应用服务器则是收费的。现在,已经有了很多开源的应用服务器了,质量都很不错,比如说JBoss或是Glassfish,最近又有了TomEE,它是带有一些EE扩展的Tomcat,实现了Java EE 6 Web Profile,Web Profile是Java EE 6的一个子集,它已经提供了EJB的部分特性、CDI,还有其他特性。
你现在已经可以在Tomcat等轻量级容器上使用Java EE 6的一些强大的Web特性。对于方才提到的这些开源的应用服务器来说,你也可以免费使用。厂商也会提供某些订阅模式,你基本不需付费就可以获得某些支持。我的意思是这些支持模式并不贵。对于Spring来说,每个人都会比较简单的Tomcat与功能完善的应用服务器,在开发Spring应用时,你实际上将自己的应用服务器打包到了WAR文件中。我的意思是框架实现了事务、安全、池化、对象映射等等,你将这些东西放到了自己的应用中。你实际上是在Tomcat上运行着自定义的应用服务器,对于Java EE 6来说,这些东西则是在容器中。它已经是个应用服务器了,你只需在上面部署自己的代码即可。轻量级与重量级的比较是个没意义的讨论,你可以说他们都是轻量级的,也可以说他们都是重量级的。
InfoQ:说得好,最后一个问题是关于微服务器的发展趋势:现在我们趋向于每个应用服务器上部署一个应用而不是将多个应用部署到一个应用服务器上,人们会将所有东西放到一个jar中,然后快速部署。你是如何看待这个趋势的?
Bert:我认为应用服务器有应用域的概念,虽然在每个应用服务器上部署一个应用,但你依然可以创建这些域模型,这样可以在一个单独的域应用中管理所有这些独立的应用。这提供了一个很棒的管理模型,实际上我并没有看到这种部署模型有什么差别,无论是Tomcat Spring栈还是基于Java EE的应用服务器,我都没有看到有什么差别。我的意思是我们不应该再将应用服务器看作是一个庞然大物或是非常贵的东西。如果看看最近发布的JBoss或是Glassfish,甚至是商业应用服务器,他们的启动时间变少了,占用的内存也变少了,因此启动多个实例是没问题的。你甚至可以在一台机器上运行这些实例,没问题的。他们只会启动应用所需要的那部分组件。如果没有使用EJB或是Web层,那么Web容器与EJB容器是不会启动的。
因此,我并不觉得这些部署方案存在什么差别。对于使用Arquillian进行测试的场景来说,你无需重启应用服务器,因为可以使用一个专门的应用服务器一直运行,如果需要集成测试,那么你可以执行微部署、运行测试,在测试完成后,可以自动卸载部署,应用服务器则会一直保持运行。也就是说,你无需重启应用服务器。Tomcat也是如此,如果需要修改配置文件,你也需要重启Tomcat。所以,我觉得对于这一点来说,Spring与Java EE并没有什么差别。
InfoQ:谢谢你能接受我们的采访。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: