我1998年开始学习Java,那时候学校里老师可能听说过Java,但是同学基本上都不知道Java。校图书馆进第一批Java的书,后面的借阅记录上都是我的名字。当时几乎所有男同学都在学C++、PB、VB、Delphi,女生很多在学ASP。所以很多同学问我学的是什么,Java是干什么的。
大学毕业以后,开始用Java做的第一个实际项目是对日外包,是2001年。日方有一套很老的系统,想用Java重构一下,要求用JSP。我下班就跑去西单图书大厦,发现那里的书都还是Servlet的, 没有JSP的!
还好,当时的公司同时进行的一项业务就是代理BEA的Weblogic(BEA是三个从SUN出来的人创建的,后来被Oracle收购)。Weblogic的产品文档里包含非常全面的JSP介绍,所以起初对JSP的学习都是从Weblogic开始的。
那时候还没听说过什么Struts。自己在SUN的官网发现了WAF的文档,全称是Web Application Framework,算是最早MVC模式的介绍。这个WAF不算是框架,只是介绍了MVC模式应该是个什么样子,如何用Servlet+JSP实现MVC模式。SUN的官网提供了少量的样例代码,剩下的都是我们根据文档自己搭建和实践。
在项目的中后期(02年下半年吧),有一次坐班车,听到后面座位上两个人在说话。一个人问:你知道Struts吗?另外一个人说:不知道。问的那个人说:就是S T R U T S这几个字母,开发Java的。我偷偷记在心里,然后第二天上网查了一下(当时没有智能机,家里也没有WIFI),才算开启了Apache这扇大门。后来在ASF上又学习了Cocoon、pluto、turbine等等很多框架。
大概02年底,对日外包项目顺利完成了,我公司开始接国内的项目。第一个国内项目是东北一所大学的科研经费审批项目。记得去给人家部署和演示的时候特别有意思。我们用了半天时间在服务器上部署好,然后去给客户演示。打开浏览器,输入ip+端口,开始操作。操作了十几分钟,所有的客户没有说一个字。越演示心里越没底,不知道客户啥反应。大概又过了几分钟,客户的主任发话了:你们的软件呢?
我们的软件呢?我给你演示了半天,这不就是我们的软件吗?最后才明白,用户认为只有下载一个类似叫setup.exe或install.exe的程序,双击,然后下一步下一步,最后桌面上出现一个快捷方式,那才算是软件!在经过片刻的不可思议之后,我认为实际用户的理念总是落后于研发人员的理念,这个我很容易想明白。但后来发现,我那些学PB、Delphi的师兄弟也不是一时半会能接受B/S结构的应用算是软件的...他们认为:你不就是写个网页吗???
再后来,从03-08年,长期从事企业应用开发,主要是基于Weblogic Platform,包括Server、Integration、Portal,其中在Portal上工作的时间最多。
其中04-05年用Weblogic Portal做深圳市最大的电子政务项目,06-07年用Weblogic平台做广东省电信的3G业务平台,08-09年用Aqualogic做南方电网的SOA。
Weblogic Server中集成了Struts,没记错的话当时是1.1版本。BEA把Struts做了升级和改造,可以在Weblogic Workshop中可视化开发,就是下面这样:
其中圆形代表Action,有Begin Action,End Action,还有普通的中间节点Action。BEA把Struts的这个升级称作Java Page Flow(Java 页面流)。一组这样的图形当中包含的Action和JSP,会定义在一个扩展名是.jpf的文件中。
后来,BEA把JPF捐献给了Apache,成为ASF下的一个开源项目Apache Beehive。
Welcome to Apache Beehivebeehive.apache.org
这个项目现在已经停止更新了。
大概从06年开始,接触到了YUI,也就是Yahoo User Interface,Yahoo开源的一套前端JS组件库。从此算是开启了我的前端之路。
07-08年开始用Extjs,作者说Ext就是Extension(扩展)的意思,扩展了YUI,提供了更丰富的适合企业开发的前端组件。但这时候,Extjs还仅仅是丰富的UI组件库,算不上框架。就是在JSP生成的HTML里面嵌入Extjs的组件。
09-11年用GWT,就是Google Web Toolkits。Google当时的想法很先进,用Java开发前端UI,最终编译成JS。有点类似于现在TS编译成JS的过程,就是打算利用上Java的强类型、面向对象等特点。这时候就已经完全前后端分离了。可以说从08年之后我就再也没写过JSP,一个页面也没写过。
10年开始用Bootstrap。这时候GWT的缺点就暴露出来了,CSS非常难改。直到13年初,开始用上了Angularjs。记得当时在智联招聘上发布职位的时候搜了一下,北京市只有用友和我们公司招聘Angularjs开发。后来就从Angularjs用到React,又用回Angular4,一直到现在都以最新版本的Angular为主,企业应用和互联网应用都有开发。移动开发主要用Ionic,React Native也用过。
为什么要详细介绍我过去和JSP以及前端框架相关的开发经历呢?是因为我想表达一个观点:如果要客观公正评价JSP是否还有必要用,特别是还有必要学,需要一个真正长期用过JSP(前后端不分离)开发,也真正长期用前端框架(前后端分离)开发的人才可以。
就像我在有些知乎答案下评论的那样:
遇到这种情况,我总想起福特的名言:“如果我当年去问顾客他们想要什么,他们肯定会告诉我:‘一匹更快的马。’”
满大街跑马车的时代,福特问顾客需要什么,顾客就说需要一匹更快的马。他们不知道汽车时代会给生活带来怎样革命性的变化。
在BP机时代,大家认为有人戴BP机已经很牛了。满大街诺基亚摩托罗拉功能机的时代,大家也都觉得够用了。问他们需要什么,他们估计会回答:充一次电能不能待机一个月?能不能把自己喜欢的MP3当彩铃?
我觉得要对比评价两代产品,应该给两代产品都熟练体验过的人去判断。从功能机时代过来的人,现在iphone都已经用到第三部了,你再问他功能机够不够用。就拿一个两代产品都具有的功能(比如都可以QQ聊天)对比,你愿意回到功能机时代还是继续用智能机。
一直抱定JSP不撒手,没动力、没能力学习前端技术,没有真正理解前后端分离开发模式的人,不可能得出公正全面的评价。
在校期间或参加培训班就学习了前端框架,参加工作后就开始前后端分离的人,也无法理解老人只用JSP或用JSP+JS前端UI组件的开发模式是个怎么回事。
上面两种人,据我实际接触中了解,大部分都认为自己的开发模式是理所当然的。就像我之前描述自己刚毕业时候的经历一样。大部分客户和我的一些同学,理所当然认为双击setup.exe,然后下一步下一步才是软件。而我理所当然认为B/S架构的也是软件,只是更便于开发和操作。
过去一年多,陆陆续续在知乎上回答了一些关于JSP的问题。当然,我的回答都是建议淘汰JSP,新人小白一定不要再学JSP了。我现在集中把这些技术因素归纳一下。
一个现代主流Java Web应用,不管前端、后端、还是微服务架构,都在淘汰JSP。
其中,我认为Java服务器端主流技术还是Spring(Spring Boot + Spring MVC + Spring Cloud)。
下面三点,第一点几乎尽人皆知,第二点有一部分人清楚,第三点却很少有人意识到。
前端框架已经非常成熟和稳定,不需要JSP
前后端分离已经不是什么趋势了,而是当前B/S架构开发的主流模式。前后端分离之后,前端只负责展现和交互,后端负责核心业务逻辑。前后端通过API进行交互,并且最好符合RESTful风格。服务器端把数据返回给前端就不再关心这些数据用在哪里、如何布局、什么样式。
这个层面的原因非常容易理解,也是绝大多数讨论JSP是否还有必要学的时候里都会提到的。
服务器端的Spring MVC/WebFlux 和 Spring Boot已经开始抛弃JSP
从Spring 5开始,在原有的基于Servlet技术的Spring MVC之外增加了一个新的编程模型,就是Spring WebFlux。
Spring WebFlux是响应式非阻塞的,而且不支持Servlet API,所以也就不支持JSP!
上图左侧是Spring 5新引入的Spring WebFlux,右侧是大家熟悉的Spring MVC,两者并列,Spring同时支持。
关于这一点,可以看Stack Overflow上面来自Spring Framework和Spring Boot团队成员Brian Clozel的回答:
Spring WebFlux - no JSP support?stackoverflow.com
新的Spring WebFlux不支持JSP,那咱们不用就好了,至少Spring MVC还是支持JSP的啊。那我们继续看。
如果我们继续使用Spring Boot+Spring MVC开发,那么Spring Boot对JSP是有限制的,看官方文档怎么说的:
链接在这里:
Spring Boot Reference Guidedocs.spring.io
其中那行备注:
If possible, JSPs should be avoided. There are several known limitations when using them with embedded servlet containers.
尽可能避免用JSP。当使用嵌入式Servlet容器时,有一些已知的限制。
关于这些限制和如何继续在Spring Boot中使用JSP,可以自己查一下,知乎里就有好多文章
Spring Boot对JSP有限制,那咱们就凑合用呗,反正我是写Java的,我的发展方向是架构师,我正打算学习微服务,正在看Spring Cloud。那咱们就继续看看Spring Cloud吧。
微服务架构下更没有JSP的用武之地
首先要明白Spring Boot和Spring Cloud的关系。可以先看我的这个回答:
Spring boot与Spring cloud 是什么关系?www.zhihu.com
还是看这张图吧:
右侧绿色的部分都是Spring Cloud的组成部分,不管是API Gateway、Config Dashboard,Service Registry,还是多个MicroServices,他们都是Spring Boot应用!或者说Spring Boot是整个Spring Cloud的基石(其实也是Spring Cloud Data Flow的基石)。
哦,你明白了,因为有Spring Boot对JSP的限制,而Spring Cloud的组成部分都是Spring Boot应用,所以Spring Cloud也对JSP有限制。其实不仅仅是表面上这个原因,咱们继续分析。
如果强行继续在Spring Cloud环境中继续使用JSP,那么JSP放在哪里?有两种方案。
那怎么才算是使用Spring Cloud的正确姿势?还是看上面那幅图,这次关注左侧三个灰色的部分。IoT(物联网 Internet of Things)、Mobile(移动应用)、Browser(浏览器端),这三个也是应用啊。
我们再看一幅图:
整个Spring体系的图出来了。还是看左侧,Your App,也就是IoT(物联网 Internet of Things)、Mobile(移动应用)、Browser(浏览器端)这三类!
Browser就是前后端分离之后的前端应用,独立开发、独立部署、只和服务器端有HTTP RESTful通信。
我们看看Spring官方给出的Spring Cloud例子,链接在这里:
Spring Projectsspring.io
customers-stores-ui是前端应用,用Angularjs实现的。例子是便于学习的,不应该引入额外的太多其他技术!为什么Spring官方的例子非要用上前端技术?不能只用服务器端开发人员熟悉的模板引擎解(包括JSP)来演示Spring Cloud吗?
我们再看另外一个例子,Spring的Petclinic大家都熟悉吧?Spring 官方例子:
spring-projects/spring-petclinicgithub.com
官方的是Monolithic(单体)应用,模板用的是Thymeleaf,自己去看代码。
用Spring Cloud实现的版本:
Spring Petclinic communitygithub.com
前端有Angular和React两种实现,服务器端有Java和Kotlin两种实现,都没有用服务器端模板。
同样的问题。为什么演示Spring Cloud的开发,要引入额外的前端技术?
答案都是同样的,Spring Cloud就必须前后端分离开发!用JSP就无法完美拆分微服务,无法利用微服务本应带来的各种优势。
总结:
我曾经在知乎某一个问题下总结过:现在JSP处于被前后端夹击的状态,生存空间越来越小了。就算你不打算管前端,只想在服务器端有所建树。微服务的前提也必须前后端分离。
放弃JSP吧,让自己的路走的宽一些。如果死守JSP不放,服务器端只能停留在SSH/SSM阶段,用Spring Boot+Spring MVC已经是你的天花板了。
yEclipse官方最新版免费下载|Myeclipse汉化下载.MyEclipse-功能最全面的Java IDE. - MyEclipse官方中文网
如果您有HTML或JSP文件要编辑,这里将介绍如何编辑。查找以下信息:
该功能在MyEclipse中是可用的。
要编辑HTML或JSP文件,请执行以下操作当中的一个:
HTML编辑器有以下模式:
打开文件时,默认模式是Design(设计),通过单击编辑器底部的适当选项卡来更改模式。
本文概述了使用源代码编辑器编辑HTML时最重要的概念和特性。
用户可以从源代码模式或设计模式访问源代码,设计模式具有双面板视图,设计模式位于顶部面板,源模式位于底部面板。
直接编辑源代码时,可以使用以下功能:
在整个源代码中,编辑器提供特定于内容的代码帮助。
Tag names(标记名称):Code assist根据当前上下文提供可用HTML标记的列表。
Tag attributes(标记属性):代码辅助提供了特定于正在编辑的标记属性列表。
Attribute values(属性值):在适当的时候,代码帮助会为您提供一个已知可能值的列表。
有两种类型的HTML验证:“输入时”和“资源更改”。
“输入时”:当对HTML文档进行更改时,编辑器总是检查HTML语法的有效性,就像Java编辑器一样。
注意:这种类型的验证错误和警告不会出现在Problems视图中。
资源更改:当资源被修改(保存、移动、复制或导入)时,资源构建器将对资源执行HTML验证。
注意:您可以通过从菜单中选择Window>Preferences,展开MyEclipse并选择Validation来设置验证参数。
要快速格式化源代码,请右键单击编辑器,并选择Source>Format。格式化器不会在内部修改HTML标记元素,它只调整HTML元素的缩进和间距。
使用Cleanup Document选项进行高级格式化和样式调整,允许您更改标记名称和属性的大小写,还有其他选项可以插入所需的标记和属性。要使用此选项,请在编辑器中右键单击并选择Source>Cleanup Document,选择Format source复选框,来将Format Document操作作为文档清理的一部分。
JSP指令元素不同的是,JSP动作元素在请求处理阶段起作用。JSP动作元素是用XML语法写成的。
利用JSP动作可以动态地插入文件、重用JavaBean组件、把用户重定向到另外的页面、为Java插件生成HTML代码。
动作元素只有一种语法,它符合XML标准:
<jsp:action_name attribute="value" />
动作元素基本上都是预定义的函数,JSP规范定义了一系列的标准动作,它用JSP作为前缀,可用的标准动作元素如下:
语法 | 描述 |
---|---|
jsp:include | 在页面被请求的时候引入一个文件。 |
jsp:useBean | 寻找或者实例化一个JavaBean。 |
jsp:setProperty | 设置JavaBean的属性。 |
jsp:getProperty | 输出某个JavaBean的属性。 |
jsp:forward | 把请求转到一个新的页面。 |
jsp:plugin | 根据浏览器类型为Java插件生成OBJECT或EMBED标记。 |
jsp:element | 定义动态XML元素 |
jsp:attribute | 设置动态定义的XML元素属性。 |
jsp:body | 设置动态定义的XML元素内容。 |
jsp:text | 在JSP页面和文档中使用写入文本的模板 |
常见的属性
所有的动作要素都有两个属性:id属性和scope属性。
id属性:
id属性是动作元素的唯一标识,可以在JSP页面中引用。动作元素创建的id值可以通过PageContext来调用。
scope属性:
该属性用于识别动作元素的生命周期。 id属性和scope属性有直接关系,scope属性定义了相关联id对象的寿命。 scope属性有四个可能的值: (a) page, (b)request, (c)session, 和 (d) application。
<jsp:include>动作元素
<jsp:include>动作元素用来包含静态和动态的文件。该动作把指定文件插入正在生成的页面。语法格式如下:
<jsp:include page="相对 URL 地址" flush="true" />
前面已经介绍过include指令,它是在JSP文件被转换成Servlet的时候引入文件,而这里的jsp:include动作不同,插入文件的时间是在页面被请求的时候。
以下是include动作相关的属性列表。
属性 | 描述 |
---|---|
page | 包含在页面中的相对URL地址。 |
flush | 布尔属性,定义在包含资源前是否刷新缓存区。 |
实例
以下我们定义了两个文件 date.jsp 和 main.jsp,代码如下所示:
date.jsp文件代码:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><p> 今天的日期是: <%= (new java.util.Date()).toLocaleString()%></p>
main.jsp文件代码:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><!DOCTYPE html><html><head><meta charset="utf-8"><title>菜鸟教程(runoob.com)</title></head><body><h2>include 动作实例</h2><jsp:include page="date.jsp" flush="true" /></body></html>
现在将以上两个文件放在服务器的根目录下,访问main.jsp文件。显示结果如下:
include 动作实例今天的日期是: 2016-6-25 14:08:17
<jsp:useBean>动作元素
jsp:useBean 动作用来加载一个将在JSP页面中使用的JavaBean。
这个功能非常有用,因为它使得我们可以发挥 Java 组件复用的优势。
jsp:useBean动作最简单的语法为:
<jsp:useBean id="name" class="package.class" />
在类载入后,我们既可以通过 jsp:setProperty 和 jsp:getProperty 动作来修改和检索bean的属性。
以下是useBean动作相关的属性列表。
属性 | 描述 |
---|---|
class | 指定Bean的完整包名。 |
type | 指定将引用该对象变量的类型。 |
beanName | 通过 java.beans.Beans 的 instantiate() 方法指定Bean的名字。 |
在给出具体实例前,让我们先来看下 jsp:setProperty 和 jsp:getProperty 动作元素:
<jsp:setProperty>动作元素
jsp:setProperty用来设置已经实例化的Bean对象的属性,有两种用法。首先,你可以在jsp:useBean元素的外面(后面)使用jsp:setProperty,如下所示:
<jsp:useBean id="myName" ... />...<jsp:setProperty name="myName" property="someProperty" .../>
此时,不管jsp:useBean是找到了一个现有的Bean,还是新创建了一个Bean实例,jsp:setProperty都会执行。第二种用法是把jsp:setProperty放入jsp:useBean元素的内部,如下所示:
<jsp:useBean id="myName" ... >... <jsp:setProperty name="myName" property="someProperty" .../></jsp:useBean>
此时,jsp:setProperty只有在新建Bean实例时才会执行,如果是使用现有实例则不执行jsp:setProperty。
jsp:setProperty动作有下面四个属性,如下表:
属性 | 描述 |
---|---|
name | name属性是必需的。它表示要设置属性的是哪个Bean。 |
property | property属性是必需的。它表示要设置哪个属性。有一个特殊用法:如果property的值是"*",表示所有名字和Bean属性名字匹配的请求参数都将被传递给相应的属性set方法。 |
value | value 属性是可选的。该属性用来指定Bean属性的值。字符串数据会在目标类中通过标准的valueOf方法自动转换成数字、boolean、Boolean、 byte、Byte、char、Character。例如,boolean和Boolean类型的属性值(比如"true")通过 Boolean.valueOf转换,int和Integer类型的属性值(比如"42")通过Integer.valueOf转换。 value和param不能同时使用,但可以使用其中任意一个。 |
param | param 是可选的。它指定用哪个请求参数作为Bean属性的值。如果当前请求没有参数,则什么事情也不做,系统不会把null传递给Bean属性的set方法。因此,你可以让Bean自己提供默认属性值,只有当请求参数明确指定了新值时才修改默认属性值。 |
<jsp:getProperty>动作元素
jsp:getProperty动作提取指定Bean属性的值,转换成字符串,然后输出。语法格式如下:
<jsp:useBean id="myName" ... />...<jsp:getProperty name="myName" property="someProperty" .../>
下表是与getProperty相关联的属性:
属性 | 描述 |
---|---|
name | 要检索的Bean属性名称。Bean必须已定义。 |
property | 表示要提取Bean属性的值 |
实例
以下实例我们使用了Bean:
package com.runoob.main;public class TestBean { private String message = "菜鸟教程"; public String getMessage() { return(message); } public void setMessage(String message) { this.message = message; }}
编译以上实例文件 TestBean.java :
$ javac TestBean.java
编译完成后会在当前目录下生成一个 TestBean.class 文件, 将该文件拷贝至当前 JSP 项目的 WebContent/WEB-INF/classes/com/runoob/main 下( com/runoob/main 包路径,没有需要手动创建)。
下面是一个 Eclipse 中目录结构图:
下面是一个很简单的例子,它的功能是装载一个Bean,然后设置/读取它的message属性。
现在让我们在main.jsp文件中调用该Bean:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><!DOCTYPE html><html><head><meta charset="utf-8"><title>菜鸟教程(runoob.com)</title></head><body><h2>Jsp 使用 JavaBean 实例</h2><jsp:useBean id="test" class="com.runoob.main.TestBean" /> <jsp:setProperty name="test" property="message" value="我爱学习..." /> <p>输出信息....</p> <jsp:getProperty name="test" property="message" /></body></html>
<jsp:forward> 动作元素
jsp:forward动作把请求转到另外的页面。jsp:forward标记只有一个属性page。语法格式如下所示:
<jsp:forward page="相对 URL 地址" />
以下是forward相关联的属性:
属性 | 描述 |
---|---|
page | page属性包含的是一个相对URL。page的值既可以直接给出,也可以在请求的时候动态计算,可以是一个JSP页面或者一个 Java Servlet. |
实例
以下实例我们使用了两个文件,分别是: date.jsp 和 main.jsp。
date.jsp 文件代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><p> 今天的日期是: <%= (new java.util.Date()).toLocaleString()%></p>
main.jsp文件代码:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><!DOCTYPE html><html><head><meta charset="utf-8"><title>菜鸟教程(runoob.com)</title></head><body><h2>forward 动作实例</h2><jsp:forward page="date.jsp" /></body></html>
现在将以上两个文件放在服务器的根目录下,访问main.jsp文件。显示结果如下:
今天的日期是: 2016-6-25 14:37:25
<jsp:plugin>动作元素
jsp:plugin动作用来根据浏览器的类型,插入通过Java插件 运行Java Applet所必需的OBJECT或EMBED元素。
如果需要的插件不存在,它会下载插件,然后执行Java组件。 Java组件可以是一个applet或一个JavaBean。
plugin动作有多个对应HTML元素的属性用于格式化Java 组件。param元素可用于向Applet 或 Bean 传递参数。
以下是使用plugin 动作元素的典型实例:
<jsp:plugin type="applet" codebase="dirname" code="MyApplet.class" width="60" height="80"> <jsp:param name="fontcolor" value="red" /> <jsp:param name="background" value="black" /> <jsp:fallback> Unable to initialize Java Plugin </jsp:fallback> </jsp:plugin>
如果你有兴趣可以尝试使用applet来测试jsp:plugin动作元素,<fallback>元素是一个新元素,在组件出现故障的错误时发送给用户错误信息。
<jsp:element> 、 <jsp:attribute>、 <jsp:body>动作元素
<jsp:element> 、 <jsp:attribute>、 <jsp:body>动作元素动态定义XML元素。动态是非常重要的,这就意味着XML元素在编译时是动态生成的而非静态。
以下实例动态定义了XML元素:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><!DOCTYPE html><html><head><meta charset="utf-8"><title>菜鸟教程(runoob.com)</title></head><body><jsp:element name="xmlElement"><jsp:attribute name="xmlElementAttr"> 属性值</jsp:attribute><jsp:body> XML 元素的主体</jsp:body></jsp:element></body></html>
<jsp:text>动作元素
<jsp:text>动作元素允许在JSP页面和文档中使用写入文本的模板,语法格式如下:
<jsp:text>模板数据</jsp:text>
以上文本模板不能包含其他元素,只能只能包含文本和EL表达式(注:EL表达式将在后续章节中介绍)。请注意,在XML文件中,您不能使用表达式如 ${whatever > 0},因为>符号是非法的。 你可以使用 ${whatever gt 0}表达式或者嵌入在一个CDATA部分的值。
<jsp:text><![CDATA[<br>]]></jsp:text>
如果你需要在 XHTML 中声明 DOCTYPE,必须使用到<jsp:text>动作元素,实例如下:
<jsp:text><![CDATA[<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">]]></jsp:text><head><title>jsp:text action</title></head><body><books><book><jsp:text> Welcome to JSP Programming</jsp:text></book></books></body></html>
你可以对以上实例尝试使用<jsp:text>及不使用该动作元素执行结果的区别。
*请认真填写需求信息,我们会在24小时内与您取得联系。