上篇account-service中已经封装了实现细节,所以接下来只要在此次基础上提供Web页面,并使用简单servlet,jsp与后台实现交互控制。以下是account-web模块的构成:
- POM部分
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.juvenxu.mvnbook.account</groupId>
<artifactId>account-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>account-web</artifactId>
<packaging>war</packaging>
<name>Account Web</name>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>account-service</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.1.7.RELEASE</version>
</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中设定,值为${project.artifactId}-${project.version}。我们可以通过<finalName>account</finalName>的配置修改主构件名称为account,之后项目生成的war包名称就会成为account.war,更方便部署。
- 主代码部分
主代码包含了2个JSP页面和4个Servlet,分别是:
- signup.jsp:账户注册页面
- login.jsp:账户登陆页面
- CaptchaImageServlet:生成验证码图片的Servlet
- LoginServlet:处理账户注册请求
- ActivateServlet:处理账户激活
- LoginServlet
不过首先要在web.xml中配置Servlet,该文件位于src/main/webapp/WEB-INF/目录下。配置如下:
//web.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<display-name>Account Service</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:/account-persist.xml
classpath:/account-captcha.xml
classpath:/account-email.xml
classpath:/account-service.xml
</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>CaptchaImageServlet</servlet-name>
<servlet-class>com.juvenxu.mvnbook.account.web.CaptchaImageServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>ActivateServlet</servlet-name>
<servlet-class>com.juvenxu.mvnbook.account.web.ActivateServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>com.juvenxu.mvnbook.account.web.LoginServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>SignUpServlet</servlet-name>
<servlet-class>com.juvenxu.mvnbook.account.web.SignUpServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>CaptchaImageServlet</servlet-name>
<url-pattern>/captcha_image</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>SignUpServlet</servlet-name>
<url-pattern>/signup</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>ActivateServlet</servlet-name>
<url-pattern>/activate</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
</web-app>
该web.xml首先配置了Web项目的显示名称,接着是一个名为ContextLoaderListener的ServletListener。该Listener来自于spring-web,用来为Web项目启动Spring的IoC容器,从而实现Bean的注入。名为contextConfigLocation的caontext-param则用来为指定的Spring配置文件的位置。这里的值是四个模块的Spring配置XML文件,例如classpath:/account-persist.xml表示从classpath的根路径获取名为account-persist.xml的文件。我们知道account-persist.xml文件在account-persist模块打包后的根路径下,这一JAR文件通过依赖的方式被引入到account-web的classpath下。web.xml中的其余部分是Servlet,包括各个Servlet的名称,类名以及相应的URL模式。
下面再构建视图文件signup.jsp文件,用来显示账户注册页面,存放在src/main/webapp/目录下。
<%@ page language="java" pageEncoding="UTF-8"
contentType="text/html; charset=UTF-8"%>
<%@ page
import="org.springframework.web.context.support.WebApplicationContextUtils"%>
<%@ page import="org.springframework.web.context.WebApplicationContext"%>
<%@ page import="org.springframework.context.ApplicationContext"%>
<%@ page import="com.juvenxu.mvnbook.account.service.*"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme() + "://"
+ request.getServerName() + ":" + request.getServerPort()
+ path + "/";
%>
<!DOCTYPE html>
<html>
<head>
<base href="<%=basePath%>">
<title>SignUp Page</title>
<style type="text/css">
.text-field {
position: absolute;
left: 40%;
background-color: rgb(255, 230, 220);
}
label {
display: inline-table;
width: 90px;
margin: 0px 0px 10px 20px;
}
input {
display: inline-table;
width: 150px;
margin: 0px 20px 10px 0px;
}
img {
width: 150px;
margin: 0px 20px 10px 110px;
}
h2 {
margin: 20px 20px 20px 40px;
}
button {
margin: 20px 20px 10px 110px
}
</style>
</head>
<body>
<%
//引入Spring的ApplicationContext类
ApplicationContext context = WebApplicationContextUtils
.getWebApplicationContext(getServletContext());
//加载后台的AccountService对象
AccountService accountService = (AccountService) context
.getBean("accountService");
//使用该对象生成一个验证码的key
String captchaKey = accountService.generateCaptchaKey();
%>
<div class="text-field">
<h2>注册新账户</h2>
<form name="signup" action="signup" method="post">
<label>账户ID:</label>
<input type="text" name="id"/><br/>
<label>Email:</label>
<input type="text" name="email"/><br/>
<label>显示名称:</label>
<input type="text" name="name"/><br/>
<label>密码:</label>
<input type="password" name="password"/><br/>
<label>确认密码:</label>
<input type="password" name="confirm_password"/><br/>
<label>验证码:</label>
<input type="text" name="captcha_value"/><br/>
<input type="hidden" name="captcha_key" value="<%=captchaKey%>"/>
<img src="<%=request.getContextPath()%>/captcha_image?key=<%=captchaKey%>"/><br/>
<button>确认并提交</button>
</form>
</div>
</body>
</html>
上述JSP中使用/captcha_image这一资源获取验证码图片。根据web.xml,该资源对应于CaptchaImageServlet,所有Servlet都放在src/main/java/目录下,代码如下:
//CaptchaImageServlet.java
package com.juvenxu.mvnbook.account.web;
import java.io.IOException;
import java.io.OutputStream;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import com.juvenxu.mvnbook.account.service.AccountService;
import com.juvenxu.mvnbook.account.service.AccountServiceException;
@SuppressWarnings("serial")
public class CaptchaImageServlet extends HttpServlet {
private ApplicationContext context; // Spring的ApplicationContext
@Override
/**
* 首先初始化ApplicationContext
*/
public void init() throws ServletException {
super.init();
context = WebApplicationContextUtils
.getWebApplicationContext(getServletContext());
}
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 获取key参数
String key = request.getParameter("key");
// 检查key是否为空
if (key == null || key.length() == 0) {
// 返回HTTP400错误,请求不合法
response.sendError(400, "No Captcha Key Found");
} else {
// 获取Spring Bean并强制类型转换
AccountService service = (AccountService) context
.getBean("accountService");
try {
// 设置response格式为image/jpeg
response.setContentType("image/jpeg");
// 将产生的字节流写入到Servlet的输出流中
OutputStream out = response.getOutputStream();
out.write(service.generateCaptchaImage(key));
out.close();
} catch (AccountServiceException e) {
response.sendError(400, e.getMessage());
}
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
}
而signup.jsp中form表单的action是signup,根据web.xml对应于SignUpServlet,代码如下:
//SignUpServlet.java
package com.juvenxu.mvnbook.account.web;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import com.juvenxu.mvnbook.account.service.AccountService;
import com.juvenxu.mvnbook.account.service.AccountServiceException;
import com.juvenxu.mvnbook.account.service.SignUpRequest;
@SuppressWarnings("serial")
public class SignUpServlet extends HttpServlet {
private ApplicationContext context; // 用于获取Spring Bean
@Override
/**
* 首先初始化ApplicationContext
*/
public void init() throws ServletException {
super.init();
context = WebApplicationContextUtils
.getWebApplicationContext(getServletContext());
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 获取HTTP POST请求,读取表单中的id,邮箱,用户名,密码,确认密码,验证主键,验证值参数
String id = req.getParameter("id");
String email = req.getParameter("email");
String name = req.getParameter("name");
String password = req.getParameter("password");
String confirmPassword = req.getParameter("confirm_password");
String captchaKey = req.getParameter("captcha_key");
String captchaValue = req.getParameter("captcha_value");
// 判断各参数是否为空,存在为空值则返回报错
if (id == null || id.length() == 0 || email == null
|| email.length() == 0 || name == null || name.length() == 0
|| password == null || password.length() == 0
|| confirmPassword == null || confirmPassword.length() == 0
|| captchaKey == null || captchaKey.length() == 0
|| captchaValue == null || captchaValue.length() == 0) {
resp.sendError(400, "Parameter Incomplete.");
return;
}
// 获取名为accountService的bean
AccountService service = (AccountService) context
.getBean("accountService");
// 初始化一个SignUpRequest实例并设置其属性
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"); // 发送账户激活邮件的地址,这里是ActivateServlet的地址
// 使用AccountService注册用户
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;
}
}
}
在这里又用到了ActivateServlet,代码如下:
//ActivateServlet
package com.juvenxu.mvnbook.account.web;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import com.juvenxu.mvnbook.account.service.AccountService;
import com.juvenxu.mvnbook.account.service.AccountServiceException;
@SuppressWarnings("serial")
public class ActivateServlet extends HttpServlet {
private ApplicationContext context;
@Override
public void init() throws ServletException {
super.init();
context = WebApplicationContextUtils
.getWebApplicationContext(getServletContext());
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String key = req.getParameter("key");
// 判断激活码是否为空
if (key == null || key.length() == 0) {
resp.sendError(400, "No activation key provided.");
return;
}
AccountService service = (AccountService) context
.getBean("accountService");
// 激活账户
try {
service.activate(key);
resp.getWriter().write("Account is activated, now you can login.");
} catch (AccountServiceException e) {
resp.sendError(400, "Unable to activate account");
return;
}
}
}
以上是注册页面及相关逻辑处理的实现。另外一个页面是login.jsp,负责处理登录。
<%@ page contentType="text/html; charset=UTF-8" language="java"%>
<html>
<head>
<style type="text/css">
.text-field {
position: absolute;
left: 40%;
background-color: rgb(255, 230, 220);
}
label {
display: inline-table;
width: 90px;
margin: 0px 0px 10px 20px;
}
input {
display: inline-table;
width: 150px;
margin: 0px 20px 10px 0px;
}
h2 {
margin: 20px 20px 20px 40px;
}
button {
margin: 20px 20px 10px 110px
}
</style>
</head>
<body>
<div class="text-field">
<h2>账户登录</h2>
<form name="login" action="login" method="post">
<label>账户ID:</label>
<input type="text" name="id"/><br/>
<label>密码:</label>
<input type="password" name="password"/><br/>
<button>确认并提交</button>
</form>
</div>
</body>
</html>
表单的action是login,LoginServlet代码如下:
//LoginServlet.java
package com.juvenxu.mvnbook.account.web;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import com.juvenxu.mvnbook.account.service.AccountService;
import com.juvenxu.mvnbook.account.service.AccountServiceException;
@SuppressWarnings("serial")
public class LoginServlet extends HttpServlet {
private ApplicationContext context;
@Override
public void init() throws ServletException {
super.init();
context = WebApplicationContextUtils
.getWebApplicationContext(getServletContext());
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String id = req.getParameter("id");
String password = req.getParameter("password");
if (id == null || id.length() == 0 || password == null
|| password.length() == 0) {
resp.sendError(400, "incomplete parameter");
return;
}
AccountService service = (AccountService) context
.getBean("accountService");
try {
service.login(id, password);
resp.getWriter().print("Login Successful!");
} catch (AccountServiceException e) {
resp.sendError(400, e.getMessage());
}
}
}
由于代码格式及内容和上面的非常相似,所以这里省略了注释,大部分代码可以参考SignUpServlet。
最后在在src/main/resources/目录下建立一个配置文件,account-service.properties,具体配置参见前面邮箱的配置,示例如下
//account.service.properties
email.protocol=smtp
email.host=localhost
email.port=25
email.username=zachary@mymail.com
email.password=123456
email.auth=true
email.systemEmail=test@mymail.com
persist.file=C\:/persist-data.xml
这样一个maven的Web项目就构建完了。在account-web的目录上执行mvn clean install,该项目的WAR包就被打包到了本地仓库上。
参考书籍:《Maven实战》第12章——许晓斌著