广告设计网站官网,专业做电脑系统下载网站,佳木斯市建设局网站,看广告赚钱Maven 使用 Maven 构建 Web应用 1️⃣ Web 项目的目录结构2️⃣ account-service2.1 account-service的 POM2.2 account-service 的主代码 3️⃣ account-web3.1 account-web 的POM3.2 account-web 的主代码 4️⃣ 使用 jetty-maven-plugin 进行测试5️⃣ 使用 Cargo 实现自动… Maven · 使用 Maven 构建 Web应用 1️⃣ Web 项目的目录结构2️⃣ account-service2.1 account-service的 POM2.2 account-service 的主代码 3️⃣ account-web3.1 account-web 的POM3.2 account-web 的主代码 4️⃣ 使用 jetty-maven-plugin 进行测试5️⃣ 使用 Cargo 实现自动化部署5.1 部署至本地 Web 容器5.2 部署至远程 Web 容器 总结 到目前为止讨论的只有打包类型为 JAR 或者 POM 的 Maven 项目。但在现今的互联网时代我们创建的大部分应用程序都是Web 应用在Java 的世界中 Web 项目的标准打包方式是WAR 。 因此本文介绍一个WAR 模块 ——account-web。在介绍该模块之前本文会先实现 account-service 。 此外还介绍如何借助 jetty-maven-plugin 来快速开发和测试Web 模块以及使用Cargo 实现 Web 项目的自动化部署。 1️⃣ Web 项目的目录结构
我们都知道基于Java 的 Web 应用其标准的打包方式是WAR 。WAR 与 JAR 类似 只不过它可以包含更多的内容如JSP 文件、Servlet、Java 类、web.xml 配置文件、依赖 JAR 包、静态 web 资源 ( 如 HTML、CSS、JavaScript 文件)等。一个典型的 WAR 文件会有如下目录结构
- war/ META-INF/ WEB-INF/| classes/| | ServletA.class| | config.properties| | ...| | | lib/| | dom4j-1.4.1.jar| | mail-1.4.1.jar| | ...| || web.xml| img/| css/| index.html sample.jsp一个WAR 包下至少包含两个子目录 META-INF 和 WEB-INF 。前者包含了一些打包元数据信息我们一般不去关心后者是WAR 包 的核心WEB-INF 下必须包含一个Web 资源表述文件 web.xml, 它的子目录 classes 包含所有该Web 项目的类而另一个子目录 lib 则包含所有该Web 项目的依赖JAR包 classes和 lib目录都会在运行的时候被加入到 Classpath 中 。 除了META-INF 和 WEB-INF 外一般的 WAR 包都会包含很多 Web 资源例如你往往可以在 WAR 包的根目录下看到很多html 或者 jsp 文件。此外还能看到一些文件夹如 img、css和js, 它们会包含对应的文件供页面使用。
同任何其他 Maven项目一样Maven 对 Web 项目的布局结构也有一个通用的约定。不过首先要记住的是用户必须为Web 项目显式指定打包方式为 war, 如代码所示。
projectgroupIdcom.xiaoshan.mvnbook/groupIdartifactIdsample-war/artifactIdpackagingwar/packagingversion1.0-SNAPSHOT/version
/project如果不显式地指定packagingMaven 会使用默认的 jar 打包方式从而导致无法正确打包 Web 项目Web项目的类及资源文件同一般JAR 项目一样默认位置都是 src/main/java/ 和 src/main/resources, 测试类及测试资源文件的默认位置是 src/test/java/ 和 src/test/resources/ 。 Web 项目比较特殊的地方在于它还有一个 Web 资源目录其默认位置是 src/main/webapp/ 。一个典型的 Web 项目的 Maven 目录结构如下 project| pom.xml| src/ main/| java/| | ServletA.java| | ...| || resources/| | config.properties| | ...| || webapp/| WEB-INF/| | web.xml| || img/| || css/| || js/| | index.html| sample.jsp| test/ java/ resources/在 src/main/webapp/目录下必须包含一个子目录 WEB-INF, 该子目录还必须要包含 web.xml 文件 。src/main/webapp 目录下的其他文件和目录包括 html、jsp、css、JavaScript 等它们与WAR 包中的Web 资源完全一致。
在使用Maven 创建Web 项目之前必须首先理解这种Maven 项目结构和WAR 包结构的对应关系。有一点需要注意的是WAR 包中有一个 lib 目录包含所有依赖JAR 包但Maven项目结构中没有这样一个目录这是因为依赖都配置在POM中Maven 在用WAR方式打包的时候会根据 POM 的配置从本地仓库复制相应的JAR 文件。 2️⃣ account-service
本节将完成背景案例项目读者可以回顾前面的文章除了之前实现的 account-email 、account-persist 和 account-captcha 之外该项目还包括 account-service 和 account-web 两个模块。 其中 account-service 用来封装底层三个模块的细节并对外提供简单的接口而 account-web仅包含一些涉及Web 的相关内容如 Servlet 和 JSP 等。
2.1 account-service的 POM
account-service 用来封装 account-email 、account-persist 和 account-captcha 三个模块的细节因此它肯定需要依赖这三个模块。 account-service 的 POM内容如代码所示。
project xmlnshttp://maven.apache.org/POM/4.0.0xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0http://maven.apache.org/maven-v4.0.0.xsdmodelVersion4.0.0/modelVersionparentgroupIdcom.xiaoshan.mvnbook.accountartifactIdaccount-parent/artifactIdversion1.0.0-SNAPSHOT/version/parentartifactIdaccount-service/artifactIdnameAccount Service/namepropertiesgreenmail.version1.3.1b/greenmail.version/propertiesdependenciesdependencygroupId${project.groupId}/groupIdartifactIdaccount-email/artifactIdversion${project.version}/version/dependencydependencygroupId${project.groupId}/groupIdartifactIdaccount-persist/artifactIdversion$(project.version)/version/dependencydependencygroupId${project.groupId}/groupIdartifactIdaccount-captcha/artifactIdversion${project.version}/version/dependencydependencygroupIdjunit/groupIdartifactIdjunit/artifactId/dependencydependencygroupIdcom.icegreen/groupIdartifactIdgreenmail/artifactIdversion${greenmail.version}/versionscopetest/scope/dependency/dependenciesbuildtestResourcestestResourcedirectorysrc/test/resources/directoryfilteringtrue/filtering/testResource/testResources/build
/project与其他模块一样account-service 继承自 account-parent, 它依赖于 account-email 、account-pernsist 和account-captcha三个模块。由于是同一项目中的其他模块 groupld 和 version都完全一致因此可以使用Maven属性 ${project.groupId} 和 ${project.vertion} 进行替换这样可以在升级项目版本的时候减少更改的数量。项目的其他配置如 junit 和 greenmail 依赖以及测试资源目录过滤配置都是为了单元测试。前面的文章已经介绍过这里不再赞述。
2.2 account-service 的主代码
account-service 的目的是封装下层细节对外暴露尽可能简单的接口。先看一下这个接 口是怎样的见代码
package com.xiaoshan.mvnbook.account.service;public interface AccountService
{String generateCaptchaKey() throws AccountServiceException;byte[] generateCaptchaImage(String captchaKey) throws AccountServiceException;void signup(SignUpRequest signUpRequest) throws AccountServiceException;void activate(String activationNumber) throws AccountServiceException;void login(String id, string password) throws AccountServiceException;
}正如介绍的那样该接口提供5个方法。generateCaptchaKey() 用来生成一个验证码的唯一标识符。generateCaptchalmage() 根据这个标识符生成验证码图片图片以字节流的方式返回。用户需要使用signUp() 方法进行注册注册信息使用SignUpRequest 进行封装这个SignUpRequest 类是一个简单的 POJO, 它包含了注册 ID、email、用户名、密码、验证码标识、验证码值等信息。注册成功之后用户会得到一个激活链接该链接包含了一个激活码这个时候用户需要使用 activate() 方法并传入激活码以激活账户。最后login() 方法用来登录。
下面来看一下该接口的实现类 AccountServicelmpl.java 。 首先它需要使用3个底层模块的服务如代码所示。
public class AccountServiceImpl implements AccountService {private AccountPersistService accountPersistService;private AccountEmailService accountEmailService;private AccountCaptchaService accountCaptchaService;public AccountPersistService getAccountPersistService(){return accountPersistService;}public void setAccountPersistService(AccountPersistService accountPersistService){this.accountPersistServiceaccountPersistService;}...
}三个私有变量来自 account-persist 、account-email 和 account-captcha 模块它们都有各自的 get() 和 set() 方法并且通过 Spring 注入。 AccountServicelmpl.java 借助 accountCaptchaService 实现验证码的标识符生成及验证码图片生成如代码清单所示。
public byte[] generateCaptchaImage(String captchaKey) throws AccountServiceException
{ try{return accountCaptchaService.generateCaptchaImage(captchaKey);}catch(AccountCaptchaException e){throw new AccountServiceException(Unable to generate Captcha Image.,e); }
}public String generateCaptchaKey() throws AccountServiceException
{try{return accountCaptchaService.generateCaptchaKey();}catch(AccountCaptchaException e){throw new AccountServiceException(Unable to generate Captcha key.,e);}
} 稍微复杂一点的是 signUp() 方法的实现见代码
private MapString,String activationMap new HashMapString,String();public void signUp(signUpRequest signUpRequest) throws AccountServiceException
{try{if(!signUpRequest.getPassword().equals(signUpRequest.getConfirmPassword())){throw new AccountServiceException(2 passwords do not match.);}if(!accountCaptchaService.validateCaptcha(signUpRequest.getCaptchaKey(), signUpRequest.getCaptchaValue())){throw new AccountServiceException(Incorrect Captcha.);}Account account new Account();account.setId(signUpRequest.getId());account.setEmail(signUpRequest.getEmail());account.setName(signUpRequest.getName());account.setPassword(signUpRequest.getPassword());account.setActivated(false);accountPersistService.createAccount(account);String activationId RandomGenerator.getRandomString();activationMap.put(activationId ,account.getId());String link signUpRequest.getActivateServiceUrl().endsWith(/) ? signUpRequest.getActivateServiceUrl() activationId : signUpRequest.getActivateServiceUrl() ?key activationId;accountEmailService.sendMail(account.getEmail(),Please Activate Your Account,link);}catch(AccountCaptchaException e){throw new AccountServiceException(Unable to validate captcha.,e); }catch(AccountPersistException e){throw new AccountServiceException(Unable to create account.,e);}catch(AccountEmailException e){throw new AccountServiceException(Unable to send actiavtion mail.,e);}
}signUp() 方法首先检查请求中的两个密码是否一致接着使用 accountCaptchaService 检查验证码下一步使用请求中的用户信息实例化一个 Account 对象并使用 accountPersistService将用户信息保存。下一步是生成一个随机的激活码并保存在临时的 activateMap 中 然后基于该激活码和请求中的服务器 URL创建一个激活链接并使用 accountEmailService 将该链接发送给用户。如果其中任何一步发生异常 signUp() 方法会创建一个一致的 AccountServiceExepetion 对象提供并抛出对应的异常提示信息。
最后再看一下相对简单的 activate() 和 login() 方法见代码
public void activate(String activationId) throws AccountServiceException
{string accountId activationMap.get(activationId);if(accountId null){throw new AccountServiceException(Invalid account activation ID.);}try{Account account accountPersistService.readAccount(accountId);account.setActivated(true);accountPersistService.updateAccount(account);}catch(AccountPersistException e){throw new AccountServiceExceptiont(Unable to activate account.); }
}
public void login(String id,String password) throws AccountServiceException
{try{Account accountaccountPersistService.readAccount(id);if(account null){throw new AccountServiceException(Account does not exist.);}if(!account.isActivated()){throw new AccountServiceException(Account is disabled.);}if(!account.getPassword().equals(password)){throw new AccountServiceException(Incorrect password.);}}catch(AccountPersistException e){throw new AccountServiceException(Unable to log in.,e);}
} activate() 方法仅仅是简单根据激活码从临时的 activationMap 中寻找对应的用户 ID, 如 果找到就更新账户状态为激活。login() 方法则是根据ID 读取用户信息检查其是否为激 活并比对密码如果有任何错误则抛出异常。
除了上述代码之外 account-service 还包括一些 Spring 配置文件和单元测试代码这里就不再详细介绍。
3️⃣ account-web
account-web 是本maven系列背景案例中唯一的 Web 模块旨在用该模块来阐述如何使用 Maven 来构建一个Maven项目。由于account-service已经封装了所有下层细节account-web 只需要在此基础上提供一些Web 页面并使用简单Servlet 与后台实现交互控制。大家将会看到一个具体Web 项目的 POM 是怎样的也将能体会到让Web 模块尽可能简洁带来的好处。
3.1 account-web 的POM
除了使用打包方式 war之外Web 项目的POM 与一般项目并没多大的区别。account- web的 POM 代码见代码
?xml version1.0?
project xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0http://maven.apache.org/xsd/maven-4.0.0.xsdxminshttp://maven.apache.org/POM/4.0.0xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancemodelVersion4.0.0/modelVersionparentgroupIdcom.xiaoshan.mvnbook.account/groupIdartifactIdaccount-parent/artifactIdversion1.0.0-SNAPSHOT/version/parentartifactIdaccount-web/artifactIdpackagingwar/packagingnameAccount Web/namedependenciesdependencygroupId${project.groupId}/groupIdartifactIdaccount-service/artifactIdversion${project.version}/version/dependencydependencygroupIdjavax.servlet/groupIdartifactIdservlet-api/artifactIdversion2.4/versionscopeprovided/scope/dependencydependencygroupIdjavax.servlet.jsp/groupIdartifactIdjsp-api/artifactIdversion2.0/versionscopeprovided/scope/dependencydependencygroupIdorg.springframework/groupIdartifactIdspring-web/artifactId/dependency/dependencies
/project如上述代码所示account-web 的 packaging 元素值为 war, 表示这是一个Web 项目需要以 war 方式进行打包。 account-web 依赖于 servlet-api 和 jsp-api 这两个几乎所有 Web 项目都要依赖的包它们为 servlet 和 jsp的编写提供支持。需要注意的是。这两个依赖的范围是 provided, 表示它们最终不会被打包至war 文件中这是因为几乎所有 Web 容器都会提供这两个类库如果 war包中重复出现就会导致潜在的依赖冲突问题。 account-web 还依赖于 account-service 和 spring-web, 其中前者为 Web 应用提供底层支持后者为 Web 应用提供 Spring 的集成支持。
在 一 些 Web 项目中可能会看到 finalName 元素的配置。该元素用来标识项目生成 的主构件的名称。该元素的默认值已在超级 POM 中设定值为 p r o j e c t a r t i f a c t l d − {project artifactld}- projectartifactld− {project.version}。因此代码对应的主构件名称为 account-web-1.0.0-SNAPSHOT.war 。不过这样的名称显然不利于部署不管是测试环境还是最终产品环境我们都不想在访问页面的时候输入冗长的地址因此我们会需要名字更为简洁的 war 包。这时可以如下所示配置 finalName 元素
finalNameaccount/finalName经此配置后项目生成的 war 包名称就会成为 account.war, 更方便部署。
3.2 account-web 的主代码
account-web 的主代码包含了2个JSP 页面和 4个Servlet, 它们分别为
signup.jsp: 账户注册页面。login.jsp: 账户登录页面。CaptchalmageServlet: 用来生成验证码图片的Servlet。LoginServlet: 处理账户注册请求的Servlet。ActivateServlet: 处理账户激活的 Servlet。LoginServlet: 处理账户登录的 Servlet。
Servlet 的配置可以从 web.xml 中获得该文件位于项目的 src/main/webapp/WEB-INF/ 目录。其内容见代码
!DOCTYPE web-app PUBLIC
-//Sun Microsvstems.Inc.//DTD Web Application 2.3//EN
http://java.sun.com/dtd/web-app_23.dtd
web-appdisplay-nameSample Maven Project:Account Service/display-namelistenerlistener-classorg.springframework.web.context.ContextLoaderListener/listener-class/listenercontext-paramparam-namecontextConfigLocation/param-nameparam-valueclasspath:/account-persist.xm1classpath:/account-captcha.xmlclasspath:/account-email.xmlclasspath:/account-service.xml/param-value/context-paramservletservlet-nameCaptchaImageServlet/servlet-namegervlet-class com.xiaoshan.mvnbook.account.web.CaptchaImageServlet /servlet-class /servletservletservlet-nameSignUpServlet/servlet-nameservlet-classcom.xiaoshan.mvnbook.account.web.SignUpServlet/servlet-class /servletservletservlet-nameActivateServlet/servlet-nameservlet-classcom.xiaoshan.mvnbook.account.web.ActivateServlet/servlet-class/servletservletservlet-nameLoginServlet/servlet-nameservlet-classcom.xiaoshan.mvnbook.account.web.LoginServlet/servlet-class/servletservlet-mapping servlet-nameCaptchaImageServlet/servlet-nameurl-pattern/captcha_image/url-pattern/servlet-mappingservlet-mappingservlet-nameSignUpServlet/servlet-nameurl-pattern/signup/url-pattern/servlet-mappingservlet-mappingservlet-nameActivateServlet/servlet-nameurl-pattern/activate/url-pattern/servlet-mappingservlet-mappingservlet-nameLoginServlet/servlet-nameurl-pattern/login/url-pattern/servlet-mapping
/web-app web.xml 首先配置了该 Web项目的显示名称接着是一个名为 ContextLoaderListener 的ServletListener。该listener 来自 springweb, 它用来为 Web项目启动 Spring的 IoC容器从而 实现Bean的注入。名为 contextConfigLocation 的 context-param 则用来指定 Spring 配置文件的位置。这里的值是四个模块的 Spring配置XML 文件例如 classpath://account-persist.xml 表 示从classpath 的根路径读取名为 account-persist.xml 的文件。我们知道 account-persist.xml 文件在 account-persist 模块打包后的根路径下这一 JAR 文件通过依赖的方式被引入到 accoumt-web 的 classpath下 。 weh.xml 中的其余部分是Servlet, 包括各个 Servlet 的名称、类名以及对应的 URL 模式。 下面来看一个位于 src/main/webapp/ 目录的 signup.jsp 文件该文件用来呈现账户注册 页面。其内容如代码
% page contentTypetext/html;charsetUTF-8 languagejava %
% page importcom.xiaoshan.mvnbook.account.service.*,org.springframework.context.ApplicationContext,org.springframework.web.context.support.WebApplicationContextUtils% html
head
style typetext/css
...
/tyle
/headbody
% ApplicationContext context WebApplicationContextUtils.getWebApplicationContext(getServletContext());AccountService accountervice (AccountService)context.getBean(accountService);String captchaKey accountervice.generateCaptchaKey();
%
div classtext-field
h2注册新账户/h2
form namesignup actionsignup methodpostlabel账户ID:/labelinput typetext nameid/inputbr/labelEmail:/labelinput typetext nameemail/inputbr/label显示名称/labelinput typetext namename/inputbr/label密码/labelinput typepassword namepassword/inputbr/label确认密码/labelinput typepassword nameconfirm_password/inputbr/label验证码/labelinput typetext namecaptcha_value/inputinput typehidden namecaptcha_key value% captchaKey % /img src% request.getContextPath() %/captcha_image?key% captchaKey % /br/button确认并提交/button
/form
/div
/body
/html该 JSP 的主题是一个 name 为 signup 的 HTML FORM, 其中包含了ID、Email、 名称、密码等字段这与一般的 HTML内容并无差别。不同的地方在于该JSP 文件引入了 Spring 的 ApplicationContext类并且用此类加载后台的 accountService, 然后使用 accountService 先生成一个验证码的key, 再在 FORM 中使用该key 调用 captcha_image 对应的Servlet 生成其标识的验证码图片。需要注意的是上述代码中略去了css 片段。
上述 JSP中使用到了/captcha_image 这一资源获取验证码图片。根据 web.xml, 我们知道该资源对应了 CaptchalmageServlet。 下面看一下它的代码见代码
package com.xiaoshan.mvnbook.account.web;
import java.io.IOException;
import ..public class CaptchaImageServlet extends HttpServlet{private ApplicationContext context;private static final long serialVersionUID5274323889605521606L;overridepublic void init() throws ServletException{super.init();context WebApplicationContextUtils.getWebApplicationContext(getServletContext());}public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException{String keyrequest.getParameter(key);if(keynull || key.length()0){response.sendError(400,No Captcha Key Found);}else{AccountService service(AccountService)context.getBean(accountService);try{response.setContentType(image/jpeg);OutputStream out response.getOutputStream();out.write(service.generateCaptchaImage(key));out.close();}catch(AccountServiceException e){response.sendError(404,e.getMessage());}}}
}CaptchalmageServlet在 init() 方法中初始化 Spring的 ApplicationContext, 这一context用来获取SpringBean。Servlet的 doGet()方法中首先检查key参数如果为空则返回HTTP400 错误标识客户端的请求不合法如果不为空则载入 AccountService实例。该类的 generateCaptchalmage() 方法能够产生一个验证码图片的字节流我们将其设置成 image/jpeg 格式并写入到 Servlet相应的输出流中客户端就能得到验证码图片。
代码中FROM的提交目标是signup, 其对应了SignUpServlet。其内容如代码
public class signUpServlet extends HttpServlet{private static final long serialVersionUID4784742296013868199L;private ApplicationContext context;Overridepublic void init() throws ServletException{super.init();context WebApplicationContextUtils.getWebApplicationContext(getServletContext());}Overrideprotected void doPost(HttpServletRequest req,HttpServletResponse resp) throws ServletException, IOException{String id req.getParameter(id);String email req.getParameter(email);String name req.getParameter(name);String password req.getParameter(password);String confirmPasswordreq.getParameter(confirm_password);String captchaValue req.getParameter(captcha_value);if(id null || id.length()0 || emailnull || email.length()0 || name null || name.length()0 || password null || password.length()0 || confirmPassword.length()0 || captchaKey null || captchaKey.length()0 || captchaValue null || captchaValue.length()0){resp.sendError(400,Parameter Incomplete.);return;}AccountService service(AccountService)context.getBean(accountService);SignUpRequest request new SignUpRequest();request.setId(id);request.setEmail(email);request.setName(name);request.setPassword(password);request.setConfirmPassword(confirmPassword);request.setCaptchaKey(captchaKey);request.setCaptchaValue(captchaValue);request.setActivateServiceUrl(getServletContext().getRealPath(/)activate);try{service.signUp(request);resp.getWriter().print(Account is created,please check your mail box for activation link.);}catch(AccountServiceException e){resp.sendError(400,e.getMessage());return;}}
}SignUpServlet 的 doPost() 接受客户端的 HTTP POST请求首先它读取请求中的 id、name、email等参数然后验证这些参数的值是否为空如果验证正确则初始化一个SignUpRequest 实例其包含了注册账户所需要的各类数据。其中的 activateServiceUrl 表示服务应该基于什么地址发送账户激活链接邮件这里的值是与signup 平行的 activate地址这正是 ActivationServlet 的地址。SignUpServlet使用AccountService注册账户所有的细节都已经封装在AccountService中如果注册成功服务器打印一条简单的提示信息。 上面介绍了一个JSP和两个Servlet,它们都非常简单。鉴于篇幅的原因这里就不再详细解释另外几个JSP及Servlet。 4️⃣ 使用 jetty-maven-plugin 进行测试
在进行Web 开发的时候我们总是无法避免打开浏览器对应用进行测试比如为了验证程序功能、验证页面布局尤其是一些与页面相关的特性手动部署到Web 容器进行测 试似乎是唯一 的方法。近年来出现了很多自动化的Web 测试技术如 Selenium, 它能够录制 Web 操作生成各种语言脚本然后自动重复这些操作以进行测试。应该说这类技术方法是未来的趋势但无论如何手动的、亲眼比对验证的测试是无法被完全替代的。测试 Web 页面的做法通常是将项日打包并部署到 Web 容器中本节介绍如何使用jetty-maven-plugin, 以使这些步骤更为便捷。
在介绍 jetty-maven-plugin 之前笔者要强调一点虽然手动的 Web 页面测试是必不可 少的但这种方法绝不应该被滥用。现实中常见的情况是很多程序员即使修改了一些较底层的代码(如数据库访问、业务逻辑), 都会习惯性地打开浏览器测试整个应用这往往是没有必要的。可以用单元测试覆盖的代码就不应该依赖于Web 页面测试且不说页面测试更加耗时耗力这种方式还无法自动化更别提重复性了。因此 Web 页面测试应该仅限于页面的层次例如JSP 、CSS 、JavaScript 的修改其他代码修改(如数据访问),请编写单元测试。
传统的 Web 测试方法要求我们编译、测试、打包及部署这往往会消耗数10秒至数分钟的时间jetty-maven-plugin 能够帮助我们节省时间它能够周期性地检查项目内容发现变更后自动更新到内置的Jetty Weh 容器中。换句话说它帮我们省去了打包和部署的步 骤 。jetty-maven-plugin 默认就很好地支持了Maven 的项目目录结构。在通常情况下我们只需要直接在IDE 中修改源码 IDE 能够执行自动编译jetty-maven-plugin 发现编译后的文件变化后自动将其更新到 Jetty 容器这时就可以直接测试Web 页面了。
使用jetty-maven-plugin 十分简单。指定该插件的坐标并且稍加配置即可见代码
plugingroupIdorg.mortbay.jetty/groupIdartifactIdjetty-maven-plugin/artifactIdversion7.1.6.v20100715/versionconfigurationscanIntervalSeconds10/scanIntervalSecondswebAppConfigcontextPath/test/contextPath/webAppConfig/configuration
/pluginjetty-maven-plugin 并不是官方的 Maven 插件它的 groupld 是 org.mortbay.jetty, 上述 代码中使用了Jetty7 的最新版本。在该插件的配置中scanlntervalSeconds 顾名思义表示该插件扫描项目变更的时间间隔这里的配置是每隔10秒。需要注意的是如果不进行配置 该元素的默认值是0,表示不扫描用户也就失去了所谓的自动化热部署的功能。上述代码中 webappConfig 元素下的 contextPath 表示项目部署后的 context path。例如这里的值为/test, 那么用户就可以通过http://hostname:port/test/ 访问该应用。
下一步启动 jetty-maven-plugin。不过在这之前需要对 settings.xml 做个微小的修改。前 面介绍过默认情况下只有 org.apache.maven.plugins 和 org.codehaus.mojo 两个groupld 下的插件才支持简化的命令行调用即可以运行 mvn help:system, 但 mvn jetty:run 就不行了。因为 maven-help-plugin 的 groupld 是 org.apache.maven.plugins, 而 jetty-maven-plugin 的 groupld 是 org.mortbay.jetty。为了能在命令行直接运行 mvn jetty:run, 用户需要配置 settings.xml 如下
settingspluginGroupspluginGrouporg.mortbay.jetty/pluginGroup/pluginGroups
/settings现在可以运行如下命令启动 jetty-maven-plugin:
$mvn jetty:runjetty-maven-plugin 会启动 Jetty, 并且默认监听本地的8080 端口并将当前项目部署到 容器中同时它还会根据用户配置扫描代码改动。
如果希望使用其他端口可以添加 jetty.port参数。例如
$mvn jetty:run -Djetty.port9999现在就可以打开浏览器通过地址 http://localhost:9999/test/ 测试应用了。要停止Jetty, 只需要在命令行输人 Ctrl C 即可。
启动 Jetty之后用户可以在 IDE 中修改各类文件如JSP、HTML、CSS、JavaScript 甚至是 Java 类。只要不是修改类名、方法名等较大的操作jetty-maven-plugin 都能够扫描到变更并正确地将变化更新至 Web 容器中这无疑在很大程度上帮助了用户实现快速开发和 测试。
上面的内容仅仅展示了jetty-maven-plugin 最核心的配置点如果有需要还可以自定义 web.xml 的位置、项目 class 文件的位置、web 资源目录的位置等信息。用户还能够以 WAR包的方式部署项目甚至在 Maven的生命周期中嵌入 jetty-maven-plugin 。例如先启动Jetty 容器并部署项目然后执行一些集成测试最后停止容器。有兴趣进一步研究的读者可以访问该页面 http://wiki.eclipse.org/Jetty/Feature/Jetty_Maven_Plugin。
5️⃣ 使用 Cargo 实现自动化部署
Cargo是一组帮助用户操作 Web 容器的工具它能够帮助用户实现自动化部署而且它 几乎支持所有的Web 容器如 Tomcat 、JBoss 、Jetty 和 Glassfish 等 。Cargo 通过cargo-maven2-plagin 提供了Maven集成 Maven 用户可以使用该插件将 Web项目部署到 Web 容器中。虽然 cargo-maven2-plugin 和 jetty-maven-plugin 的功能看起来很相似但它们的目的是不同的 jetty-maven-plugin 主要用来帮助日常的快速开发和测试而 cargo-maven2-plugin 主要服务于自动化部署。例如专门的测试人员只需要一条简单的 Maven 命令就可以构建项目并部署到 Web 容器中然后进行功能测试。本节以 Tomcat 6为例介绍如何自动化地将 Web 应用部署至本地或远程 Web 容器中。
5.1 部署至本地 Web 容器
Cargo 支持两种本地部署的方式分别为 standalone 模式和 existing 模式。在 standalone 模式中 Cargo会 从Web 容器的安装目录复制一份配置到用户指定的目录然后在此基础上 部署应用每次重新构建的时候这个目录都会被清空所有配置被重新生成。而在 existing模式中用户需要指定现有的Web 容器配置目录然后 Cargo 会直接使用这些配置并将 应用部署到其对应的位置。代码展示了standalone 模式的配置样例
plugingroupIdorg.codehaus.cargo/groupIdartifactIdcargo-maven2-plugin/artifactIdversion1.0/versionconfigurationcontainercontainerIdtomcat6x/containerIdhomeD:\cmd\apache-tomcat-6.0.29/home/containerconfigurationtypestandalone/typehome${project.build.directory}/tomcat6x/home/configuration/configuration
/plugincargo-maven2-plugin 的 groupld 是 org.codehaus.cargo, 这不属于官方的两个 Maven 插件 groupld, 因此用户需要将其添加到 settings.xml的 pluginGroup 元素中以方便命令行调用。
上述 cargo-maven2-plugin的具体配置包括了 container 和configuration 两个元素configuration的子元素 type 表示部署的模式(这里是 standalone) 。与之对应的configuration 的 home 子元素表示复制容器配置到什么位置这里的值为${project.build.directory}/tomcat6x, 表示构建输出目录即 target/下的 tomcat6x子目录。container 元素下的 containerld 表 示容器的类型home元素表示容器的安装目录。基于该配置Cargo 会从 D:\cmd\apache-tomcat-6.0.29目录下复制配置到当前项目的target/tomcat6x/ 目录下。 现在要让 Cargo 启动 Tomcat 并部署应用只需要运行
$mvn cargo:start以 account-web 为例现在就可以直接访问地址的账户注册页面了。 默认情况下 Cargo 会让 Web 容器监听8080 端口。可以通过修改 Cargo 的 cargo.servlet.port 属性来改变这一配置如代码
plugingroupIdorg.codehaus.cargo/groupIdartifactIdcargo-maven2-plugin/artifactIdversion1.0/versionconfigurationcontainercontainerIdtomcat6x/containerIdhomeD:\cmd\apache-tomcat-6.0.29/home/containerconfigurationtypestandalone/typehome${project.build.directory}/tomcat6x/homepropertiescargo.servlet.port8081/cargo.servlet.port/properties/configuration/canfiguration
/plugin要将应用直接部署到现有的Web容器下.需要配置Cargo 使用existing 模式如代码
plugingroupIdorg.codehaus.cargo/groupIdartifactIdcargo-maven2-plugin/artifactIdversion1.0/versionconfigurationcontainercontainerIdtomcat6x/containerIdhomeD:\cmd\apache-tomcat-6.0.29/home/containerconfigurationtypeexisting/lypehomeD:\cmd\apache-tomcat-6.0.29/home/conflguration/configuration
/plugin上述代码中 configuration元素的 type子元素的值为existing, 而对应的 home 子元素表示 现有的Weh容器目录基于该配置运行 mvn cargo:start 之后便能够在Tomcat 的 webapps 子目录看到被部署的Maven项目。
5.2 部署至远程 Web 容器
除了让Cargo直接管理本地Web容器然后部署应用之外也可以让 Cargo 部署应用至远 程的正在运行的Web容器中。当然前提是拥有该容器的相应管理员权限。相关配置如代码
plugingroupIdorg.codehaus.cargo/groupIdartifactIdcargo-maven2-plugin/artifactldversion1.0/versionconfigurationcontainercontainerIdtomcat6x/containeridtyperemote/type/containerconfigurationtyperuntime/typepropertiescargo.remote.userraneadmin/cargo.remote.usernamecargo.remote.passwordadmin123/cargo.remote.passwordcargo.tomcat.manager.urlhttp://localhost:9080/manager/cargo.tomcat.manager.url/properties/configuration/configuration
/plugin对于远程部署的方式来说container 元素的 type 子元素的值必须为 remote 。如果不显式指定Cargo 会使用默认值 installed, 并寻找对应的容器安装目录或者安装包对于远程部署方式来说安装目录或者安装包是不需要的。上述代码中 configuration 的 type 子元素值为 runtime, 表示既不使用独立的容器配置也不使用本地现有的容器配置而是依赖于一个已运行的容器。properties 元素用来声明一些容器热部署相关的配置。例如这里的Tomcat 6 就需要提供用户名、密码以及管理地址。需要注意的是这部分配置元素对于所有容器来说不是一致的读者需要查阅对应的Cargo 文档。
有了上述配置后就可以让 Cargo 部署应用了。运行命令如下
$mvn cargo:redeploy如果容器中已经部署了当前应用 Cargo 会先将其卸载然后再重新部署。 由于自动化部署本身就不是简单的事情再加上Cargo 要兼容各种不同类型的Web 容器因此 cargo-maven2-plugin 的相关配置会显得相对复杂这个时候完善的文档就显得尤为重要。如果想进一步了解 Cargo, 可访问 http://cargo.codehaus.org/Maven2 plugin。 总结
本文介绍了 用 Maven 管理Web 项目因此首先讨论了Web 项目的基本结构然后分析实现了背景案例的最后两个模块 account-service 和 account-web, 其中后者是一个典 型的Web 模块。开发Web 项目的时候大家往往会使用热部署来实现快速的开发和测试 jetty-maven-plugin 可以帮助实现这一 目标。最后讨论的是自动化部署这一技术的主角是 Cargo, 有了它可以让Maven 自动部署应用至本地和远程 Web 容器中。 ⏪ 温习回顾上一篇点击跳转 《【Maven教程】十使用 Hudson 进行持续集成—— 从Hudson的安装到任务创建 ~》 ⏩ 继续阅读下一篇点击跳转 《》