Java 5.0
Struts 2.0.9
Spring 2.0.6
Hibernate 3.2.4
作者: Liu Liu 转载请注明出处
基本概念和典型实用例子。
一、基本概念
Struts :作为基于 MVC 模式的 Web 应用最经典框架,两个项目 Struts 和 webwork 已经集成,成为现在的 Struts2 。目前的最新版本是 2.0.9 ( 2007-7 )。
Spring : 是一个轻型的容器,利用它可以使用一个外部 XML 配置文件方便地将对象连接在一起。每个对象都可以通过显示一个 JavaBean 属性收到一个到依赖对象的引用,留给您的简单任务就只是在一个 XML 配置文件中把它们连接好。
Hibernate 是一个纯 Java 的对象关系映射和持久性框架,它允许您用 XML 配置文件把普通 Java 对象映射到关系数据库表。使用 Hibernate 能够节约大量项目开发时间,因为整个 JDBC 层都由这个框架管理。这意味着您的应用程序的数据访问层位于 Hibernate 之上,完全是从底层数据模型中抽象出来的。
三种技术到目前已经比较成熟,而且他们都是免费的!让我们对三者集成进行一个初览(简单而不专业):
我们用 Struts 实现从 Web (网页, MVC 中的 View )到后台系统的映射( Web à Action ),然后由 Spring 管理这些 Action ,把它们作为 Bean 和其他对象一起处理。这些 Bean 之间处理业务逻辑、数据、系统状态等,且它们被 Spring 统一管理,为了区分,就算大概包括 MVC 的 MC 部分吧。然后需要持久化的数据由 Spring 和 Hibernate 之间的接口交由 Hibernate 处理(这个属于持久层)。
必须基础: 只要 Java 基础,一点 HTML 知识、 XML 基础就可以了。本文的目的就是从零开始建立第一个 Struts+Spring+Hibernate 应用。即使它是最简单的,我们也希望初学者能够从中理解一些思想,其中也包括系统架构的设计思想。
二、环境搭建
我们坚持免费才是硬道理,开源才是好事情,所以我们全部使用开源免费的工具和软件。如果使用 MyEclipse ,其中的工具将有助于简化下面演示的工程开发,但本文不用。
所需软件包如下表:
序号 | 包 | 下载地址和文件(包)名 | 说明 | |
1 | JDK5.0 | JDK5.0 | ||
2 | Eclipse WTP | Eclipse IDE for Java EE Developers 下载 All in One ,这样不用自己下载其他插件 | 包含网站开发的 Eclipse , v3.3 ,下载 All in One | |
3 | HibernateSynchronizer-3.1.9 | https://sourceforge.net/project/showfiles.php?group_id=99370 | 帮助开发 Hibernate 应用的 Eclipse 插件 | |
4 | Hibernate3 | http://sourceforge.net/project/showfiles.php?group_id=40712 à hibernate3 所指示的包 | Hibernate 支持包 | |
5 | Spring | http://sourceforge.net/project/showfiles.php?group_id=73357
| spring-framework-2.0.6-with-dependencies.zip | |
6 | SpringIDE (可选) | http://springide.org/updatesite/ 包名如 | ||
7 | Struts | http://struts.apache.org/download.cgi
| 为了较全的例子和文档,建议下载 | |
8 | Tomcat | 建议下载 v5.5 以上版本,应用服务器(支持 JSP 等) Apache 项目之一 | ||
9 | MySQL |
| Hibernate 演示需要 | |
1 、下载了 eclipse 以后安装。在所安装的目录下有两个子目录 plugins 和 features ,这是两个放 eclipse 插件的目录,即可以通过拷贝需要的文件到这些目录里面,从而给 eclipse 添加新的功能。
2 、将第 3 、 6 的包解压,将其中的 plugins 目录直接复制到 eclipse 安装目录下,选择“全部”替换。
3 、运行 eclipse ,选择一个空目录作为工作区( WorkSpace ),启动以后可以看到 Welcome.html 的欢迎界面。现在建立新工程 File à New à Project ,在打开的 New Project 窗口中选择 Web à Dynamic Web Project 。输入 Project name ,在 Target Runtime 一项选择新建 (New) ,选择你所安装的 Apache Tomcat ,在弹出窗口输入相关信息( Tomcat 安装目录等)。
新建工程流程如下图。
工程结构如下:
其中我们要写的 Java 代码在 Java Resource: src (以后直接称 src )下,网站根目录内容在 WebContent 下,类所在根目录是 WEB-INF/classes , Eclipse 会自动将 build/classes 里面已经编译的类同步过去。
向 WEB-INF 下的 lib 目录添加如下所列的 jar 包。
( 1 )这些包在下载解压后 Spring , Struts , Hibernate 的 lib 目录或者 dist/module 目录下面(如果不在,可以到网上 google 一把。列表中 mysql-*.jar 包是 MySQL 数据库的 JDBC Driver )。也可以把所有 lib 和 dist 下的 jar 包拷贝过来(可以在系统复制这些 jar 包,然后到 Eclipse 里面选中 WEB-INF 里面的 lib 包,然后粘帖就可以了)。但要注意全拷贝可能会存在冲突,如 struts*plugin.jar 等包不能引入,否则不能运行。
( 2 )这些 Jar 包是:
antlr-2.7.2.jar
cglib-nodep-2.1_3.jar
commons-beanutils-1.6.jar
commons-chain-1.1.jar
commons-collections-2.1.1.jar
commons-dbcp.jar
commons-digester.jar
commons-logging-1.0.4.jar
commons-logging-api-1.1.jar
commons-pool.jar
commons-validator-1.3.0.jar
dom4j-1.6.1.jar
el-api.jar
el-ri.jar
freemarker-2.3.8.jar
hibernate3.jar
jsf-api.jar
jta.jar
mysql-connector-java-3.0.14-production-bin.jar
ognl-2.6.11.jar
oro-2.0.8.jar
spring-hibernate3.jar
spring.jar
struts-config.xml
struts-core-1.3.5.jar
struts2-codebehind-plugin-2.0.9.jar
struts2-config-browser-plugin-2.0.9.jar
struts2-core-2.0.9.jar
struts2-jasperreports-plugin-2.0.9.jar
struts2-jfreechart-plugin-2.0.9.jar
struts2-jsf-plugin-2.0.9.jar
struts2-pell-multipart-plugin-2.0.9.jar
struts2-plexus-plugin-2.0.9.jar
struts2-sitegraph-plugin-2.0.9.jar
struts2-sitemesh-plugin-2.0.9.jar
struts2-spring-plugin-2.0.9.jar
struts2-struts1-plugin-2.0.9.jar
struts2-tiles-plugin-2.0.9.jar
tiles-api-2.0.4.jar
tiles-core-2.0.4.jar
tiles-jsp-2.0.4.jar
xwork-2.0.4.jar
三、开始工作
在 WebContent 下建立 index.jsp ,建立方式如图。
index.jsp 的内容如表,我们暂时不分析。
<%@ page contentType = "text/html; charset=UTF-8" %> < html > < head > <title>Example by Doer Liu@UTStarcom sz </title> </ head > < body > This is my JSP page. < br > < form name = "userInfoForm" action = "login.do" method = "post" > 用户名 : < input name = "username" type = "text" /> 密码 : < input name = "password" type = "password" > < input name = "sub" type = "submit" value = " 增加 " /> < input name = "res" type = "reset" value = " 重置 " /> </ form > </ body > </ html > |
此时就可以运行该工程,忙了这么久,看看效果吧。
运行方式:右键点击 index.jsp ,选择 Run/Debug As à Run on Server ,在弹出窗口中默认我们使用的 Tomcat Server ,点击 finish 完成。可以看到 eclipse 中内嵌的浏览器显示我们的网页。其中表单的输入在我们的工程中将得到输入数据(用户名和密码),这些数据会传给我们将要建立的 Action 处理。
现在来看看如何建立我们的 Action 。在 src 下新建一个 package (包)名为 action 用于保存响应 Web 请求的 Action 类。在 action 包下新建 Action 类 LoginAction ( action.LoginAction )如下,注意类的继承关系。
package action; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; import org.apache.struts.validator.DynaValidatorForm; import org.springframework.web.struts.ActionSupport;
// 我们继承 spring 提供的 Action 衍生类 org.springframework.web.struts.ActionSupport public class LoginAction extends ActionSupport {
public ActionForward execute ( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) { return mapping.findForward( "success" );
} } |
但是现在 index.jsp 的内容怎么和 LoginAction 的数据匹配呢,我们看到 LoginAction 的 execute 方法有一个属性 ActionForm ,于是我们建立一个类 forms.UserInfoForm 如下,继承 ActionForm 。
package forms; import org.apache.struts.action.ActionForm; public class UserInfoForm extends ActionForm { private String username; private String password;
public String getUsername() { return username; } public void setUsername(String username) { this .username = username; }
public String getPassword() { return password; } public void setPassword(String password) { this .password = password; } }
|
有了两个头,又有了保持内容的类,现在看看我们如何用 struts 把他们联系起来吧。
现在需要在 WEB-INF 下建立文件 struts-config.xml 。其中 form-beans 定义了表单是如何映射的,这里用我们刚刚定义的 forms. UserInfoForm 。
<? xml version = ”1.0” encoding = "ISO-8859-1" ?> <! DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd" >
< struts-config > < form-beans > < form-bean name = "userInfoForm" type = "forms.UserInfoForm" /> </ form-beans >
< action-mappings > < action attribute = "userInfoForm" path = "/login" input = "/index.jsp" type = "org.springframework.web.struts.DelegatingActionProxy" name = "userInfoForm" scope = "session" validate = "false" > < forward name = "success" path = "/success.html" /> </ action > </ action-mappings > </ struts-config > |
在 < action-mappings > 中定义了我们的 Action 。它的属性 attribute 指出 Action 的内容输入是我们自定义的 ActionForm , path 给 Action 赋予一个路径, input 指明只接受 index.jsp 的输入, < forward 标签定义了当 Action 返回 "success" 的时候,将定向到 /success.html 这个网页。 最重要的是 type ,它定义了这个处理这个请求的 Action 类,本来应该是我们自定义的 LoginAction ,但我们却用了 spring 的一个 Action ,为什么?因为我们要用 Spring 管理我们自定义的 Action 。看, struts 和 Spring 在这里就开始连接起来了。
但还有两个问题, Struts 和 Spring 又是如何知道对方的存在,如何沟通呢? Spring 如何知道把控制权交给我们自定义的 LoginAction 呢?
我们先来解决第一个问题, web.xml 是 Tomcat 这些应用服务器管理的,因此我们在这里将 struts 和 Spring 配置联系起来。这是整个 web.xml 。请看注释。
<? xml version = "1.0" encoding = "UTF-8" ?> < web-app version = "2.5" id = "WebApp" xmlns = "http://java.sun.com/xml/ns/javaee" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation = "http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" >
< display-name > Struts2+Spring2+Hibernate3 simple example by Doer Liu@UTstarcom </ display-name > <!-- filter 就理解为一些对网页请求的过滤吧 --> <!-- encodingFilter 是为了处理国际化,交由 Spring 处理,设置为 UTF-8 --> < filter > < filter-name > encodingFilter </ filter-name > < filter-class > org.springframework.web.filter.CharacterEncodingFilter </ filter-class > < init-param > < param-name > encoding </ param-name > < param-value > UTF-8 </ param-value > </ init-param > </ filter > <!-- struts 是 struts 的 filter ,这个定义就将可以将请求交给 struts 过滤一番了 --> < filter > < filter-name > struts </ filter-name > < filter-class > org.apache.struts2.dispatcher.FilterDispatcher </ filter-class > </ filter >
<!-- 那么哪些请求交给 struts 过滤呢,这里包括 /struts2spring2hib3bydoer 下和根目录 / 下的所有请求 --> < filter-mapping > < filter-name > struts </ filter-name > < url-pattern > /struts2spring2hib3bydoer/* </ url-pattern > < url-pattern > /* </ url-pattern > </ filter-mapping >
<!-- 定义一个监听器,处理整个 WebContext ,简单的理解为整个网站的上下文环境监听器吧 这个属于 Spring--> < listener > < listener-class > org.springframework.web.context.ContextLoaderListener </ listener-class > </ listener >
<!-- servlet 定义一个 servlet 为 struts 的 ActionServlet --> < servlet > < servlet-name > doertest </ servlet-name > < servlet-class > org.apache.struts.action.ActionServlet </ servlet-class > < load-on-startup > 1 </ load-on-startup > </ servlet >
<!-- servlet-mapping 将 servlet 和请求对应起来,这里是所有 *.do 的请求交由上面定义的 doertest 处理 --> < servlet-mapping > < servlet-name > doertest </ servlet-name > < url-pattern > *.do </ url-pattern > </ servlet-mapping >
<!-- 定义默认返回页,如输入 http://127.0.0.1/ 那么根目录下的 index.html 或者其他文件就被请求 --> < welcome-file-list > < welcome-file > index.html </ welcome-file > < welcome-file > index.htm </ welcome-file > < welcome-file > index.jsp </ welcome-file > < welcome-file > default.html </ welcome-file > < welcome-file > default.htm </ welcome-file > < welcome-file > default.jsp </ welcome-file > </ welcome-file-list > </ web-app >
|
通过 web.xml 两者联系上了。现在它们各自还需要一些配置。
Struts 在我们的例子里比较简单,在 build/class 下面(最终会被 eclipse 同步到网站的 WEB-INF/classes 下面)建立 struts.xml :
<! DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.0.dtd" >
< struts > < include file = "struts-default.xml" /> </ struts > |
Spring 的默认配置文件是 WEB-INF/applicationContext.xml ,目前其内容很简单,我们只是把 struts 的 Bean 放进来,如下:
映射的规则: bean 的 name 属性必须等于 struts-config.xml 里面定义的 action 的 path 属性, class 就是这个 bean 的类 action.LoginAction 。
<? xml version = "1.0" encoding = "UTF-8" ?> <! DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd" >
< beans > <!-- Action Bean , 对应的部分 struts-config.xml form-bean and action-mappings --> < bean name = "/login" class = "action.LoginAction " singleton = "false" >
</ property > </ bean >
</ beans > |
现在在 WebContent 下面建立 success 时重定向的目标 success.html ,方法和 index.jsp 类似,但选择 THML 类型,随便输入内容以便测试。这时候 struts 和 Spring 就简单的连接起来了。先停掉刚才运行起来的 Tomcat ,重新启动,运行 index.jsp ,点击网页中的按钮 < 添加 > ,看看有什么效果。
现在,然我们简略描述一下数据和请求的流程。
点击 < 添加 > , index.jsp 的这个表单发送的请求是 login.do( <form name="userInfoForm" action="login.do" method="post">) ,请求被传给后台,生成了 doertest (处理 *.do 的请求)集合的一个 servlet ,然后传到 path 为 /login 的 action ,被 Spring 的 org.springframework.web.struts.DelegatingActionProxy 处理,该类找到 name 是 /login 的 Bean ,转交处理权,等待结果。这个 Bean 就是我们的 action.LoginAction 。我们的 execute 中返回一个 forward 是 "success" 对应的网页,就是 success.html 。所以……,你已经看到了, struts 和 spring 已经联系起来了。 OK !
下面我们需要把 hibernate 整合进来了,本来考虑到例子的简单性,打算用更简单的类,但既然用三者整合,就是要有良好的设计。我们需要以下几个层次的设计:表现层,业务层,持久层。表现层就是网页;表现层和业务层之间的接口就是网页和 action 的接口,由 struts 处理了;业务层包括业务逻辑和事务管理等,由 Spring 管理,我们只是建立具体处理对象;业务层和持久层之间由数据访问对象 DAO 处理,持久层交给 hibernate 处理。贯穿这些层的是领域对象 (domain object) ,即表示现实世界的对象( base object ),如订单对象,人物信息对象等等。现在看看我们需要的剩余设计结构。
业务层:放进包 service
数据访问对象 : 放进包 dao
持久层: hibernate
领域对象:放进包 bo
既然领域对象是最基本的对象,我们就得首先建立,本例中,可以借助 HibernateSynchronizer 生成:
首先在 mysql 中创建表
CREATE TABLE `userinfo` (
`id` int(11) primary key auto_increment,
`username` varchar(20) default NULL,
`Password` varchar(20) default NULL
)
在 Eclipse 中,建立 hibernate 的 map 文件:右键点击 WEB-INF (或其他目录都可,后面会提到如何使用该文件),选择 new à other ,在弹出窗口中选择 Hibernate Mapping File 。在弹出窗口输入 url ,用户名和密码后点击 Refresh ,可以看到你选择的数据库的表,选中 userinfo 表。输入包 bo ,用来保存从数据库提取的领域对象。在 Properties 中将 Id generator 改为 native 。
HibernateSynchronizer 将在 WEB-INF 下生成 Uerinfo.hbm.xml 文件。
右键点击该文件,选择 Hibernate Synchronizer à Synchronize Files 。将自动生成 bo.base.BaseUserinfo 和 bo.Userinfo 类。这两个就是领域对象。工具正好啊!
现在 bo 包里面的对象自动生成了。
下面建立 dao 包中对象 dao.UserinfoDAO :
package dao; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; import bo.Userinfo; // 从 HibernateDaoSupport 继承,从而可以使用 getHibernateTemplate().save 保存数据。 public class UserinfoDAO extends HibernateDaoSupport {
public void save(Userinfo userinfo) { System.out.println( "saved!" ); getHibernateTemplate().save(userinfo); }
}
|
再建立 service 包中的业务对象, service.UserinfoService :
package service; import dao.UserinfoDAO; import bo.Userinfo; package service; public class LoginService { private UserinfoDAO userinfoDAO ;
public UserinfoDAO getUserinfoDAO() { System. out .println( "shit" ); return userinfoDAO ; }
public void setUserinfoDAO(UserinfoDAO userinfoDAO) { System. out .println( "LoginService:setAdminDAO" ); this . userinfoDAO = userinfoDAO; }
public void saveinfo(Userinfo userinfo) { // 进行相关业务处理,比如 validate 之类的。 userinfoDAO .save(userinfo); } } |
好了,所有我们应该建立的对象都生成了,现在把 hibernate 整合进来再进行一些后续处理。
首先,在 applicationContext.xml 文件中加入必需的 Bean 定义,成为如下内容,注意其中注释。
<? xml version = "1.0" encoding = "UTF-8" ?> <! DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd" >
< beans > <!-- Action Bean , 对应的部分 struts-config.xml form-bean and action-mappings --> < bean name = "/login" class = "action.LoginAction" singleton = "false" > <!-- property 是该 bean 的属性,如下面的 property ,在类 LoginAction 中必有字段定义 LoginService loginService; 和 getLoginService() 以及 setLoginService 方法 --> < property name = "loginService" > < ref bean = "loginService" /> </ property >
</ bean >
<!-- 定义 DBCP 的数据库连接属性,该数据源会被 hibernate 使用, DBCP 是连接池开源包,其中的 url,username,password 需要替换成你的数据库访问属性 --> < bean id = "dataSource" class = "org.apache.commons.dbcp.BasicDataSource" destroy-method = "close" > < property name = "driverClassName" > < value > com.mysql.jdbc.Driver </ value > </ property > < property name = "url" > < value > jdbc:mysql://localhost/mysql </ value > </ property > < property name = "username" > < value > root </ value > </ property > < property name = "password" > < value > doerliu </ value > </ property > </ bean >
<!-- 配置 sessionFactory, 为 Hibernate 配置属性 --> < bean id = "sessionFactory" class = "org.springframework.orm.hibernate3.LocalSessionFactoryBean" > < property name = "dataSource" > < ref local = "dataSource" /> </ property > < property name = "mappingResources" > < list > <!—Hibernate 的 map 文件在这里配置了,注意文件的相对位置。 --> < value > ../Userinfo.hbm.xml </ value > </ list > </ property > < property name = "hibernateProperties" > < props > < prop key = "hibernate.dialect" > org.hibernate.dialect.MySQLDialect </ prop > < prop key = "hibernate.show_sql" > true </ prop > </ props > </ property > </ bean >
<!-- 业务层的事务管理由该 bean 管理 --> < bean id = "transactionManager" class = "org.springframework.orm.hibernate3.HibernateTransactionManager" > < property name = "sessionFactory" > < ref local = "sessionFactory" /> </ property > </ bean >
<!-- 事务处理环境(代理)配置,为业务处理 LoginService 定义一个事务处理 *****--> < bean id = "userDAOProxy" class = "org.springframework.transaction.interceptor.TransactionProxyFactoryBean" > < property name = "transactionManager" > < ref bean = "transactionManager" /> </ property > < property name = "target" > < ref local = "loginService" /> </ property > < property name = "transactionAttributes" > < props > < prop key = "save*" > PROPAGATION_REQUIRED </ prop > < prop key = "get*" > PROPAGATION_REQUIRED,readOnly </ prop > < prop key = "is*" > PROPAGATION_REQUIRED,readOnly </ prop > </ props > </ property > </ bean >
<!-- 业务处理 Bean 定义 --> < bean id = "loginService" class = "service.LoginService" > < property name = "userinfoDAO" > < ref bean = "userinfoDAO" /> </ property > </ bean >
<!-- 数据访问对象的 Bean --> < bean id = "userinfoDAO" class = "dao.UserinfoDAO" > < property name = "sessionFactory" >< ref local = "sessionFactory" /></ property > </ bean >
</ beans > |
最后, LoginAction 可以处理请求并和业务层进行交流了。因此需要增加实质性内容:
package action;
/* @sample for training. * @author doer.liu@utstarcom * @date 2007-7-30 */ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; import org.springframework.web.struts.ActionSupport;
import bo.Userinfo;
import forms.UserInfoForm;
import service.LoginService;
// 我们继承 spring 提供的 Action 衍生类 org.springframework.web.struts.ActionSupport public class LoginAction extends ActionSupport {
LoginService loginService;
public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) { UserInfoForm userInfoForm = (UserInfoForm) form; String username = userInfoForm.getUsername(); String password = userInfoForm.getPassword(); Userinfo userinfo = new Userinfo(); userinfo.setUsername(username); userinfo.setPassword(password); loginService.saveinfo(userinfo);// 保存前台的数据,插入数据库
return mapping.findForward("success"); // 返回页。
}
public LoginService getLoginService() { return loginService; }
public void setLoginService(LoginService loginService) { System.out.println("setLoginService=" + loginService); this.loginService = loginService; } } |
Ok !整个流程到此就走通了。运行看看吧。还有什么说的呢,动手开始吧,在此基础上不断修改测试,再参考相关文档,一切都将越来越简单!——有问题,看日志!
附件是导出的WAR文件,其中lib已被清空,只要加入文中列出的lib文件即可运行(可以将WAR导入eclipse,或者将war文件放到Tomcat的webaspps下)http://dl2.csdn.net/down4/20070806/06111224839.war
当然这个例子为了清晰起见,在各种模式, java 编程习惯上是不合适的,比如应该面向接口编程,而不是统统拿类,拿对象来处理。应该定义如 ILoginService, ILoginDAO 等接口,使得系统更灵活,更易移植。当然为了说明,我们这样做是可以原谅的,但工作中切记不要只图简单!否则还不如不用这种高级优秀的构架,因为你一用就把它破坏殆尽了。
让我们前进吧
Day day up!
作者言:本文为了从各个细节说明一个基本struts2+spring2+hibernate3构建网站的架构,如果有问题和建议请留言,作者将在本周六(8.11)之前,根据问题和建议更新该文,谢谢先。