一、JavaWeb开发模式
C/S:客户端 / 服务器
B/S:浏览器 / 服务器
JavaBean:
就是一个普通类(实体bean),包含三样标准:一个无参构造、私有属性、公共的getter和setter方法。
通常需要这么一个作为信息的传递载体。
1、Model1模式
JSP+JavaBean
在jsp+javabean架构中,JSP负责控制逻辑、表现逻辑、业务对象(javabean)的调用。
JSP+JavaBean模式适合开发业务逻辑不太复杂的web应用程序,这种模式下,JavaBean用于封装业务数据,JSP即负责处理用户请求,又显示数据。
JSP+JavaBean开发模式开发web版计算器
1.新建web项目
取消src文件夹 为sources root
在src下新建文件夹结构如下
首先分析一下jsp和javabean各自的职责,jsp负责显示计算器(calculator)页面,供用户输入计算数据,并显示计算后的结果,javaBean负责接收用户输入的计算数据并且进行计算,JavaBean具有firstNum、secondNum、result、 operator属性,并提供一个calculate方法。
现在思考一个问题,firstNum和secondNum的数据类型应该使用什么?使用double类型吗?
答:不可以,若firstNum和secondNum的数据类型为double,那么他们计算的结果是不精确的
public class Demo1 {
public static void main(String[] args) {
// 浮点数运算只适合科学运算,计算的结果是不精确的
double a = 0.1;
double b = 0.006;
System.out.println(a+b);
}
}
输出:0.10600000000000001,可见两数相加结果并不精确
结论:浮点数运算只适合科学运算,计算的结果是不精确的。
那么到底firstNum和secondNum的数据类型应该使用什么呢?
答:使用BigDecimal这个类。
public static void main(String[] args) {
// 记住以后要用程序计算精确的货币运算,就一定要用BigDecimal这个类
BigDecimal a = new BigDecimal("0.1");
BigDecimal b = new BigDecimal("0.006");
System.out.println(a.add(b).toString());
}
输出:0.106
结论:以后要用程序计算精确的货币运算,就一定要用BigDecimal这个类
编写CalculatorBean,负责接收用户输入的计算数据并且进行计算
在java下,新建包org.daniel.domian,存放Bean
package org.daniel.domain;
// 封装计算器数据的bean
public class CalculatorBean {
private String firstNum = "0"; // 字段最好拥有显示值
private char operator = '+';
private String secondNum = "0";
private String result;
public String getFirstNum() {
return firstNum;
}
public void setFirstNum(String firstNum) {
this.firstNum = firstNum;
}
public char getOperator() {
return operator;
}
public void setOperator(char operator) {
this.operator = operator;
}
public String getSecondNum() {
return secondNum;
}
public void setSecondNum(String secondNum) {
this.secondNum = secondNum;
}
public String getResult() {
return result;
}
public void setResult(String result) {
this.result = result;
}
public void calculate() {
BigDecimal first = new BigDecimal(firstNum);
BigDecimal second = new BigDecimal(secondNum);
switch (this.operator) {
case '+': {
this.result = first.add(second).toString();
break;
}
case '-': {
this.result = first.subtract(second).toString();
break;
}
case '*': {
this.result = first.multiply(second).toString();
break;
}
case '/': {
if(second.doubleValue() == 0) {
throw new RuntimeException("被除数不能为0");
}
this.result = first.divide(second, 20, BigDecimal.ROUND_HALF_UP).toString();
break;
}
default:
throw new RuntimeException("运算符只能是:+ - * / ");
}
}
}
编写calculator.jsp,负责显示计算器(calculator)页面,供用户输入计算数据,并显示计算后的结果
在web文件夹下,新建calculator.jsp,页面代码如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>计算器</title>
</head>
<body>
<jsp:useBean id="calculatorBean" class="org.daniel.domain.CalculatorBean"></jsp:useBean>
<jsp:setProperty property="*" name="calculatorBean"/>
<%
try {
calculatorBean.calculate();
} catch (Exception e) {
out.write(e.getMessage());
}
%>
<br/>----------------------------------------------------------<br/>
计算结果是:
<jsp:getProperty property="firstNum" name="calculatorBean"/>
<jsp:getProperty property="operator" name="calculatorBean"/>
<jsp:getProperty property="secondNum" name="calculatorBean"/>
=
<jsp:getProperty property="result" name="calculatorBean"/>
<br/>----------------------------------------------------------<br/>
<br/>
<form action="/calculator.jsp" method="post">
<table width="40%" border="1">
<tr>
<td colspan="2">简单的计算器</td>
</tr>
<tr>
<td>第一个参数:</td>
<td>
<input type="text" name="firstNum">
</td>
</tr>
<tr>
<td>操作符</td>
<td>
<select name="operator">
<option value="+">+</option>
<option value="-">-</option>
<option value="*">*</option>
<option value="/">/</option>
</select>
</td>
</tr>
<tr>
<td>第二个参数</td>
<td>
<input type="text" name="secondNum">
</td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="计算">
</td>
</tr>
</table>
</form>
</body>
</html>
解释下:jsp:useBean 动作用来加载一个将在JSP页面中使用的JavaBean,id属性是动作元素的唯一标识,可以在JSP页面中引用,class属性指定Bean的完整包名。在类载入后,我们就可以通过 jsp:setProperty 和 jsp:getProperty 动作来修改和检索bean的属性。
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动作提取指定Bean属性的值,转换成字符串,然后输出。语法格式如下:
<jsp:useBean id="myName" ... />
...
<jsp:getProperty name="myName" property="someProperty" .../>
下表是与getProperty相关联的属性:
属性 | 描述 |
---|---|
name | 要检索的Bean属性名称。Bean必须已定义。 |
property | 表示要提取Bean属性的值 |
关于JSP动作元素的更多信息,参考这里
关于JSP语法,参考这里
为便于访问,配置xml文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<welcome-file-list>
<welcome-file>calculator.jsp</welcome-file>
</welcome-file-list>
</web-app>
打开浏览器,访问http://127.0.0.1:8080/
2、Model2模式
JSP + Servlet + JavaBean
在平时的JavaWeb项目开发中,在不使用第三方mvc开发框架的情况下,通常会选择Servlet+JSP+JavaBean开发模式来开发JavaWeb项目,Servlet+JSP+JavaBean组合开发就是一种MVC开发模式了,控制器(Controller)采用Servlet、模型(Model)采用JavaBean、视图(View)采用JSP。在讲解Servlet+JSP+JavaBean开发模式之前,先简单了解一下MVC开发模式。
Web开发中的请求-响应模型
在Web世界里,具体步骤如下:
- Web浏览器(如IE)发起请求,如访问http://www.iteye.com/
- Web服务器(如Tomcat)接收请求,处理请求(比如用户新增,则将把用户保存一下),最后产生响应(一般为html)。
- Web服务器处理完成后,返回内容给Web客户端(一般就是我们的浏览器),客户端对接收的内容进行处理(如Web浏览器将会对接收到的html内容进行渲染以展示给客户)。
因此,在Web世界里,都是Web客户端发起请求,Web服务器接收、处理并产生响应。
一般Web服务器是不能主动通知Web客户端更新内容。虽然现在有些技术如服务器推(如Comet)、还有现在的HTML5 websocket可以实现Web服务器主动通知Web客户端。
到此我们了解了在web开发时的请求/响应模型,接下来我们看一下标准的MVC模型是什么。
标准MVC模型概述
MVC模型是一种架构型的模式,本身不引入新功能,只是帮助我们将开发的结构组织的更加合理,使展示与模型分离、流程控制逻辑、业务逻辑调用与展示逻辑分离。如下图所示:
MVC(Model-View-Controller)的概念
首先让我们了解下MVC(Model-View-Controller)的概念:
- Model(模型)
数据模型,提供要展示的数据,因此包含数据和行为,可以认为是领域模型(domain)或JavaBean组件(包含数据和行为),不过现在一般都分离开来:Value Object(数据)和服务层(行为)。也就是模型提供了模型数据查询和模型数据的状态更新等功能,包括数据和业务。 - View(视图)
负责进行模型的展示,一般就是我们见到的用户界面,客户想看到的东西。 - Controller(控制器)
接收用户请求,委托给模型进行处理(状态改变),处理完毕后把返回的模型数据返回给视图,由视图负责展示。 也就是说控制器做了个调度员的工作。
从上图我们还看到,在标准的MVC中模型能主动推数据给视图进行更新(观察者设计模式,在模型上注册视图,当模型更新时自动更新视图),但在Web开发中模型是无法主动推给视图(无法主动更新用户界面),因为在Web开发是请求-响应模型。
那接下来我们看一下在Web里MVC是什么样子,我们称其为Web MVC 来区别标准的MVC。
Web MVC概述
Web MVC中的M(模型)-V(视图)-C(控制器)概念和标准MVC概念一样,我们再看一下Web MVC标准架构,如下图所示:
在Web MVC模式下,模型无法主动推数据给视图,如果用户想要视图更新,需要再发送一次请求(即请求-响应模型)。
Servlet+JSP+JavaBean开发模式介绍
Servlet+JSP+JavaBean架构其实可以认为就是我们所说的Web MVC模型,只是控制器采用Servlet、模型采用JavaBean、视图采用JSP,如下图:
Servlet+JSP+JavaBean开发模式与三层结构
Servlet+JSP+JavaBean开发模式通常会结合三层结构,如下图:
基于Servlet+JSP+JavaBean开发模式的用户登录注册
Servlet+JSP+JavaBean(MVC)模式适合开发复杂的web应用,在这种模式下,servlet负责处理用户请求,jsp负责数据显示,javabean负责封装数据。Servlet+JSP+JavaBean模式程序各个模块之间层次清晰,web开发推荐采用此种模式。
创建MVC架构的Web项目
在IDEA中新创建一个Model2Demo项目,导入项目所需要的开发包(jar包),创建项目所需要的包
项目所需要的开发包(jar包):
序号 | 开发包名称 | 描述 |
---|---|---|
1 | jstl.jar | JSP标准标签库(JSTL) |
2 | standard.jar | JSP标准标签库(JSTL) |
3 | commons-beanutils-1.9.3.jar | 工具类,用于处理bean对象 |
4 | commons-logging-1.2.jar | commons-beanutils-1.9.3.jar的依赖jar包 |
5 | commons-collections-3.2.2.jar | commons-beanutils-1.9.3.jar的依赖jar包 |
6 | mysql-connector-java-5.1.46-bin.jar | mysql驱动包 |
项目所需要的包:
序号 | 包名 | 描述 | 所属层次 |
---|---|---|---|
1 | org.daniel.domain | 存放系统的JavaBean类(只包含简单的属性以及属性对应的get和set方法,不包含具体的业务处理方法),提供给【数据访问层】、【业务逻辑层】、【Web层】来使用 | domain(域模型)层 |
2 | org.daniel.dao | 存放访问数据库的操作接口类 | 数据访问层 |
3 | org.daniel.dao.impl | 存放访问数据库的操作接口的实现类 | 数据访问层 |
4 | org.daniel.service | 存放处理系统业务接口类 | 业务逻辑层 |
5 | org.daniel.service.impl | 存放处理系统业务接口的实现类 | 业务逻辑层 |
6 | org.daniel.web.servlet | 存放作为系统控制器的Servlet(处理请求的servlet) | Web层(表现层) |
7 | org.daniel.exception | 自定义异常类 | Web层(表现层) |
8 | org.daniel.utils | 存放系统的通用工具类,提供给【数据访问层】、【业务逻辑层】、【Web层】来使用 |
由于在严格的MVC模式下,jsp被保护起来,禁止外界直接访问,用户要注册,需要拿一个表单页面,需要用一个servlet转到jsp上面去,有一部分servlet专门给用户提供用户界面,也即是说在实际开发里面有一部分servlet是用来处理请求,有一部分servlet专门用来接收请求之后转到jsp,给用户提供用户界面
这是开始的设计,jsp直接放在web下,外界可以直接访问:
如果将jsp放在/WEB-INF/views/下,外界就不能通过URL直接访问了(404)
结论:凡是位于WEB-INF目录下的jsp页面是无法直接通过URL地址直接访问的
此时的项目结构:
分层架构的代码编写
开发domain层
User:实体Bean,封装数据,其中字段的名称需与数据库字段保持一致
package org.daniel.domain;
import java.io.Serializable;
import java.util.Date;
/*
实体Bean,封装数据,其中字段的名称需与数据库字段保持一致
*/
public class User implements Serializable { //javaBean为什么要实现Serializable接口?
private Integer id;
private String username;
private String password;
private String email;
private Date birthday;
public User() {
}
public User(Integer id, String username, String password, String email, Date birthday) {
this.id = id;
this.username = username;
this.password = password;
this.email = email;
this.birthday = birthday;
}
public Integer getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
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;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", email='" + email + '\'' +
", birthday=" + birthday +
'}';
}
}
创建数据库mytest,建立表user
CREATE TABLE user(
id INTEGER PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(64),
password VARCHAR(32),
email VARCHAR(64),
birthday DATE
) DEFAULT CHARSET = utf8;
开发数据访问层(dao、dao.impl)
在开发数据访问层时,由于要使用JDBC访问数据库,先创建一个工具类DBUtils,放在utils包下
package org.daniel.utils;
import java.io.FileInputStream;
import java.io.InputStream;
import java.sql.*;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
/**
* Created by Dream on 2017/11/10.
*/
public class DBUtils {
private static String driverClass;
private static String username;
private static String password;
private static String url;
static {
//利用Properties 对象加载配置文件
Properties prop = new Properties();
InputStream is = DBUtils.class.getResourceAsStream("/dbinfo.properties");
try {
prop.load(is);
Set<Map.Entry<Object,Object>> set = prop.entrySet();
for(Map.Entry<Object,Object> entry:set){
if(entry.getKey().equals("driverClass")){
driverClass =(String) entry.getValue();
}
if(entry.getKey().equals("url")){
url =(String) entry.getValue();
}
if(entry.getKey().equals("username")){
username =(String) entry.getValue();
}
if(entry.getKey().equals("password")){
password =(String) entry.getValue();
}
}
}catch (Exception e){
e.printStackTrace();
}
try{
//加载驱动
Class.forName(driverClass);
} catch (ClassNotFoundException e){
System.out.println("加载驱动失败");
e.printStackTrace();
}
}
//获得连接
public static Connection getConnection() {
try {
return DriverManager.getConnection(url, username, password);
} catch (SQLException e){
System.out.println("连接数据库失败");
return null;
}
}
//关闭所有资源
public static void close(ResultSet rs, Statement sta,Connection con){
if(rs != null){
try{
rs.close();
}catch (Exception e){
//设为空对象,尽快让JVM垃圾处理器回收
rs = null;
}
}
if(sta != null) {
try{
sta.close();
}catch (Exception e){
sta = null;
}
}
if(con != null){
try{
con.close();
}catch (Exception e){
con = null;
}
}
}
//测试
public static void main(String[] args){
Connection con = null;
Statement sta = null;
ResultSet resultSet = null;
try{
//建立连接
con = DBUtils.getConnection();
//获得执行SQL语句的对象
sta = con.createStatement();
//执行SQL语句,返回结果
int result = sta.executeUpdate("INSERT INTO dept(id,username,address) VALUES(null,'莹莹','北京')");
if(result > 0)
System.out.println("插入成功");
resultSet = sta.executeQuery("SELECT * FROM dept");
while(resultSet.next()){
System.out.println(resultSet.getString("username"));
}
} catch(SQLException e){
e.printStackTrace();
} finally {
DBUtils.close(null,sta,con);
}
}
}
这里在数据库中新建了张dept表,用来测试我们的工具类
CREATE TABLE dept(
id INTEGER PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(64),
address VARCHAR(64)
) DEFAULT CHARACTER SET = utf8;
在实际开发中,经常需要保存一些全局的配置,提供给整个程序随时读取,从而判断当前环境的某些情况。此时,一种方法就是设置一个properties配置文件。服务器启动时,监听类中读取该配置文件的内容,并将其保存在一个类的静态变量所指向的内存区域。在这里,我们新建resources文件夹,并标为资源文件夹,在其下放置dbinfo.properties属性文件,我们编写的jdbc工具类要访问该属性文件。
driverClass=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mytest?useUnicode=true&characterEncoding=UTF-8&useSSL=false
username=root
password=123
Dao:数据访问层接口
package org.daniel.dao;
import org.daniel.domain.User;
public interface UserDao {
public void insert(User user) throws Exception;
public User select(User user) throws Exception;
public boolean selectByName(String name);
}
对于接口中的方法定义,这个只能是根据具体的业务来分析需要定义哪些方法了,但是无论是多么复杂的业务,都离不开基本的CRUD(增删改查)操作,Dao层是直接和数据库交互的,所以Dao层的接口一般都会有增删改查这四种操作的相关方法。
Dao.impl:Dao接口的实现类,对于接口的实现类命名方式,习惯以”接口名+impl”形式来命名:UserDao(接口)->UserDaoImpl(实现类)
package org.daniel.dao.impl;
import org.daniel.dao.UserDao;
import org.daniel.domain.User;
import org.daniel.utils.DBUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.text.SimpleDateFormat;
import java.util.Date;
/*
UseDao的实现类
*/
public class UserDaoImpl implements UserDao {
public void insert(User user) throws Exception{
Connection con = null;
PreparedStatement ps = null;
try{
con = DBUtils.getConnection();
String sql = "INSERT INTO user(username,password,email,birthday) VALUES (?,?,?,?)";
ps = con.prepareStatement(sql);
ps.setString(1,user.getUsername());
ps.setString(2,user.getPassword());
ps.setString(3,user.getEmail());
SimpleDateFormat spf = new SimpleDateFormat("yyyy-MM-dd");
String date = spf.format(user.getBirthday());
ps.setString(4,date);
int result = ps.executeUpdate();
}catch (Exception e){
e.printStackTrace();
throw new RuntimeException("插入失败!");
}finally {
DBUtils.close(null,ps,con);
}
}
public User select(User user) throws Exception{
Connection con = null;
ResultSet rs = null;
PreparedStatement ps = null;
User u = null;
try{
con = DBUtils.getConnection();
String sql = "SELECT * FROM user WHERE username = ? AND password = ?";
//预编译SQL语句
ps = con.prepareStatement(sql);
//参数设置
ps.setString(1,user.getUsername());
ps.setString(2,user.getPassword());
rs = ps.executeQuery();
if(rs.next()){
u = new User();
u.setId(rs.getInt(1));
u.setUsername(rs.getString(2));
u.setPassword(rs.getString(3));
u.setEmail(rs.getString(4));
u.setBirthday(rs.getDate(5));
}
}catch (Exception e){
e.printStackTrace();
}
return u;
}
public boolean selectByName(String name){
Connection con = null;
ResultSet rs = null;
PreparedStatement ps = null;
try{
con = DBUtils.getConnection();
String sql = "SELECT * FROM user WHERE username = ?";
ps = con.prepareStatement(sql);
ps.setString(1,name);
rs = ps.executeQuery();
if(rs.next()){
return true;
}
}catch (Exception e){
e.printStackTrace();
}
return false;
}
public static void main(String[] args) throws Exception{
User user = new User(null,"晶晶","123","beibei@163.com",new Date());
UserDaoImpl userDao = new UserDaoImpl();
userDao.insert(user);
User user1 = userDao.select(user);
System.out.println(user1);
System.out.println(userDao.selectByName("贝贝"));
}
}
开发完数据访问层,一定要对程序已编写好的部分代码进行测试,做一步,测试一步,以免整个程序完成后由于页面太多或者是代码量太大给查找错误造成更大的负担!我这里直接在main方法中测试,也可以引入juint.test执行测试
开发service层(service层对web层提供所有的业务服务)
接口:
package org.daniel.service;
import org.daniel.domain.User;
import org.daniel.exception.UserExistException;
public interface UserService {
public void register(User user) throws Exception;
public User login(User user);
//判断用户是否存在
public boolean isUserExist(String name) throws UserExistException;
}
实现类:
package org.daniel.service.impl;
import org.daniel.dao.UserDao;
import org.daniel.dao.impl.UserDaoImpl;
import org.daniel.domain.User;
import org.daniel.exception.UserExistException;
import org.daniel.service.UserService;
import java.util.Date;
// 对web层提供所有的业务服务
public class UserServiceImpl implements UserService {
//通过调用Dao接口实现类的方法去操纵底层数据库
UserDao userDao = new UserDaoImpl();
public void register(User user) throws Exception{
//注册也就是用户的插入
userDao.insert(user);
}
public User login(User user){
User u = null;
try{
//登录也就是用户的查找
u = userDao.select(user);
}catch (Exception e){
e.printStackTrace();
}
return u;
}
public boolean isUserExist(String name) throws UserExistException {
boolean b = userDao.selectByName(name);
//用户已经存在,需要抛出异常
if(b){
throw new UserExistException("用户已经存在!"); // 发现要注册的用户已存在,则给web层抛一个编译时异常,提醒web层处理这个异常,给用户一个友好提示
}
return b;
}
public static void main(String[] args) throws Exception{
UserServiceImpl userService = new UserServiceImpl();
User user = new User(null,"妮妮","1124","nini@163.com",new Date());
userService.register(user);
System.out.println(userService.login(user));
System.out.println(userService.isUserExist("晶晶"));
}
}
从以上代码可以看到业务逻辑层和数据访问层是紧密联系在一起的,所以业务逻辑层和数据访问层要解耦
发现要注册的用户已存在,要给web层抛一个编译时异常,提醒web层处理这个异常,给用户一个友好提示, 自定义异常类如下:
package org.daniel.exception;
public class UserExistException extends Exception{
public UserExistException() {
}
public UserExistException(String message) {
super(message);
}
public UserExistException(String message, Throwable cause) {
super(message, cause);
}
public UserExistException(Throwable cause) {
super(cause);
}
protected UserExistException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
开发Web层
编写一个RegUIServlet为用户提供注册界面,RegUIServlet收到用户请求后,就跳到reg.jsp。
package org.daniel.web.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
// 为用户提供注册界面
public class RegUIServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.getRequestDispatcher("/WEB-INF/views/reg.jsp").forward(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
在/WEB-INF/views/目录下编写用户注册的jsp页面reg.jsp。注册时候,如果用户已经存在,则将该错误信息保存在request域对象的error变量里,校验信息放在uf变量里
<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="UTF-8"%>
<html>
<head>
<title>注册界面</title>
<style type="text/css">
.label{
width: 20%
}
.controller{
width: 80%
}
</style>
<script type="text/javascript" src="/static/js/Calendar3.js"></script>
</head>
<body>
<h1>用户注册</h1>
<hr>
<form name="regForm" action="${pageContext.request.contextPath }/servlet/RegServlet" method="post">
<table border="0" width="800" cellspacing="0" cellpadding="0" >
<tr>
<td class="label">用户名:</td>
<td class="controller"><input type="text" name="username" value="${uf.username}">${error}${uf.msg.username}</td>
</tr>
<tr>
<td class="label">密码:</td>
<td class="controller"><input type="password" name="password">${uf.msg.password}</td>
</tr>
<tr>
<td class="label">确认密码:</td>
<td class="controller"><input type="password" name="repassword">${uf.msg.repassword}</td>
</tr>
<tr>
<td class="label">电子邮箱:</td>
<td class="controller"><input type="text" name="email" value="${uf.email}">${uf.msg.email}</td>
</tr>
<tr>
<td class="label">出生日期:</td>
<td class="controller">
<input name="birthday" type="text" value="${uf.birthday}"
onclick="new Calendar().show(this);" readonly="readonly" /> ${uf.msg.birthday}
</td>
</tr>
<tr>
<td colspan="2" align="center">
<input type="submit" value="注册"/>
<input type="reset" value="重置"/>
</td>
</tr>
</table>
</form>
</body>
</html>
这里用到了一个日期控件,放在web/static/js/目录下,reg.jsp中的<form name="regForm" action="${pageContext.request.contextPath }/servlet/RegServlet" method="post">指明表单提交后交给RegServlet进行处理。
编写用于处理用户注册的RegServlet,承担以下职责
- 接收客户端提交到服务端的表单数据。
- 校验表单数据的合法性,如果校验失败跳回到reg.jsp,并回显错误信息。
- 如果校验通过,调用service层向数据库中注册用户。
为了方便RegServlet接收表单数据和校验表单数据,设计一个用于校验注册表单数据的UserForm类,用于封装用户注册提交的表单数据,其中该对象的validReg方法实现对用户注册信息的校验 ,UserForm类作为实体对象,也放在domain包下
值得注意的是,这里的birthday设为String类型
package org.daniel.domain;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Map;
public class UserForm {
private Integer id;
private String username;
private String password;
private String repassword;
private String email;
private String birthday;
Map<String, String> msg = new HashMap<>();
//如果返回的msg不为空,证明是有错误消息的;否则,注册验证通过
public boolean validReg() {
if ("".equals(username)) {
msg.put("username", "用户名不能为空!");
} else if (!username.matches("\\w{3,8}")) {
msg.put("username", "用户名必须为3~8位字母组成!");
}
if ("".equals(password)) {
msg.put("password", "密码不能为空!");
} else if (!password.matches("\\w{3,8}")) {
msg.put("password", "密码必须为3~8位字母或数字!");
}
if (!password.equals(repassword)) {
msg.put("repassword", "两次密码必须输入一致!");
}
if ("".equals(email)) {
msg.put("email", "邮箱不能为空!");
} else if (!email.matches("^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$")) { //只允许英文字母、数字、下划线、英文句号、以及中划线组成
msg.put("email", "必须要符合邮箱的格式输入");
}
if ("".equals(birthday)) {
msg.put("birthday", "生日不能为空!");
} else {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
try {
sdf.parse(birthday);
} catch (Exception e) {
msg.put("birthday", "生日格式不对!");
}
}
return msg.isEmpty();
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
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;
}
public String getRepassword() {
return repassword;
}
public void setRepassword(String repassword) {
this.repassword = repassword;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getBirthday() {
return birthday;
}
public void setBirthday(String birthday) {
this.birthday = birthday;
}
public Map<String, String> getMsg() {
return msg;
}
public void setMsg(Map<String, String> msg) {
this.msg = msg;
}
}
负责处理用户注册的RegServlet
package org.daniel.web.servlet;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.locale.converters.DateLocaleConverter;
import org.daniel.domain.User;
import org.daniel.domain.UserForm;
import org.daniel.exception.UserExistException;
import org.daniel.service.UserService;
import org.daniel.service.impl.UserServiceImpl;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;
public class RegServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException,ServletException {
request.setCharacterEncoding("UTF-8"); // 处理乱码
response.setContentType("text/html;charset=utf-8");
//获取表单数据
User user = new User();
// 将form参数 自动封装UserForm对象
UserForm uf = new UserForm();
try{ // 将用户提交的注册信息注入到UserForm类中,首先进行校验,如果校验失败,则请求转发,同时将uf传递
BeanUtils.populate(uf,request.getParameterMap()); //通过request.getParameterMap()方法将form表单中的数据变为Map类型
//如果校验失败,跳回到表单页面,回显校验失败信息
if(!uf.validReg()){ //msg不为空,有错误
request.setAttribute("uf",uf);
//注册失败,重新注册
request.getRequestDispatcher("/WEB-INF/views/reg.jsp").forward(request,response);
return;
}
}catch (Exception e){
e.printStackTrace();
}
//检验是否封装到uf对象中
// System.out.println(uf.getUsername());
// System.out.println(uf.getPassword());
// System.out.println(uf.getRepassword());
// System.out.println(uf.getEmail());
// System.out.println(uf.getBirthday());
//如果执行到这里,说明校验成功,则将数据注入到User类中,调用service处理注册请求
UserService us = new UserServiceImpl();
try{
/**使用BeanUtils实现对象数据自动set,但是时间设置会出现问题
org.apache.commons.beanutils.converters.DateTimeConverter.toDate
DateConverter does not support default String to 'Date' conversion.**/
ConvertUtils.register(new DateLocaleConverter(), Date.class); // 在封装数据之前 ,注册转换器,ConvertUtils.register(转换器, 目标类型.class),将表单提交的String类型转换为java.util.Date类型
BeanUtils.populate(user,request.getParameterMap());
//调用业务逻辑
//注册的用户如果存在,抛出异常UserExistException
boolean b = us.isUserExist(user.getUsername());
//注册用户
us.register(user);
// System.out.println(b);
}catch (UserExistException e){
request.setAttribute("error",e.getMessage());
//跳回到注册页面
request.getRequestDispatcher("/WEB-INF/views/reg.jsp").forward(request,response);
}catch (Exception e){
e.printStackTrace();
}
//如果service处理成功,跳转到网站的全局消息显示页面,显示用户注册成功的消息
response.getWriter().write("注册成功!3s后跳到登录界面...");
response.setHeader("refresh","3;url="+request.getContextPath()+"/servlet/LogUIServlet");//response.setHeader("refresh","秒数;URL=otherPagename");
}
public void doPost(HttpServletRequest request,HttpServletResponse response) throws IOException,ServletException{
doGet(request,response);
}
}
ConvertUtils.register注册转换器:当用到BeanUtils的populate方法(该方法可以实现用户提交的表单数据自动注入到实体对象中,动态获取,不需要我们通过set进行设置),其实都会调用convert进行转换,但Converter只支持一些基本的类型,甚至连java.util.Date类型也不支持。 这个时候就需要给类型注册转换器。比如:ConvertUtils.register(new DateLocaleConverter(), Date.class);意思是所有需要转成Date类型的数据都要通过DateLocaleConverter这个转换器的处理。
用户注册时如果填写的表单数据校验不通过(格式不正确或者是用户名已存在),那么服务器端就将一个存储了错误提示消息【request.setAttribute("error",e.getMessage());】和表单数据的UserForm对象【 request.setAttribute("uf",uf);】存储到request对象中,然后发送回reg.jsp页面,因此我们需要在reg.jsp页面中取出request对象中UserForm对象,将出错时的提示消息显示到form表单上面,让用户知道是哪些数据填写不合法! 具体的做法是通过JSP的EL表达式${},详见前面的reg.jsp。
在web.xml中配置我们的servlet映射
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>RegServlet</servlet-name>
<servlet-class>org.daniel.web.servlet.RegServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>RegServlet</servlet-name>
<url-pattern>/servlet/RegServlet</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>LogServlet</servlet-name>
<servlet-class>org.daniel.web.servlet.LogServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LogServlet</servlet-name>
<url-pattern>/servlet/LogServlet</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>LogOutServlet</servlet-name>
<servlet-class>org.daniel.web.servlet.LogOutServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LogOutServlet</servlet-name>
<url-pattern>/servlet/LogoutServlet</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>RegUIServlet</servlet-name>
<servlet-class>org.daniel.web.servlet.RegUIServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>RegUIServlet</servlet-name>
<url-pattern>/servlet/RegUIServlet</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>LogUIServlet</servlet-name>
<servlet-class>org.daniel.web.servlet.LogUIServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LogUIServlet</servlet-name>
<url-pattern>/servlet/LogUIServlet</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>/index.jsp</welcome-file>
</welcome-file-list>
</web-app>
下面测试一下开发好的用户注册功能:
进入首页,测试注册
注册成功,跳转到登录页面
注册失败,格式不正确
注册失败,用户名已存在
这里有一个问题,如果把UserFrom中的getMsg()和setMsg()方法去掉,注册失败会报异常,不知道为什么
开发登录功能
LogUIServlet为用户提供登录界面,收到用户请求后,就跳到log.jsp
package org.daniel.web.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class LogUIServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.getRequestDispatcher("/WEB-INF/views/log.jsp").forward(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
编写用户登录的jsp页面log.jsp
<%--
Created by IntelliJ IDEA.
User: Daniel
Date: 2018/7/16
Time: 19:02
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录界面</title>
<style type="text/css">
.label{
width: 20%
}
.controller{
width: 80%
}
</style>
</head>
<body>
<h1>用户登录</h1>
<hr>
<form name="logForm" action="${pageContext.request.contextPath }/servlet/LogServlet" method="post">
<table border="0" width="800" cellspacing="0" cellpadding="0" >
<tr>
<td class="label">用户名:</td>
<td class="controller"><input type="text" name="username" ></td>
</tr>
<tr>
<td class="label">密码:</td>
<td class="controller"><input type="password" name="password"></td>
</tr>
<tr>
<td colspan="2" align="center">
<input type="submit" value="登录"/>
</td>
</tr>
</table>
</form>
</body>
</html>
表单提交后,交给LogServlet处理,登录成功跳转到首页,登录失败(用户名或密码错误),返回重新登录
package org.daniel.web.servlet;
import org.apache.commons.beanutils.BeanUtils;
import org.daniel.domain.User;
import org.daniel.service.UserService;
import org.daniel.service.impl.UserServiceImpl;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class LogServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
request.setCharacterEncoding("utf-8");
//获取表单数据
User user = new User();
try{
BeanUtils.populate(user,request.getParameterMap());//把表单注入到user对象中
//调用业务逻辑
UserService us = new UserServiceImpl();
User u = us.login(user);
//如果u不为空,说明登录成功
if(u != null){
request.getSession().setAttribute("u",user);
//用户登录成功后,跳转到首页
response.sendRedirect("/index.jsp");
}else { //否则登录失败,重新登录
request.getRequestDispatcher("/WEB-INF/views/log.jsp").forward(request, response);
}
}catch (Exception e){
e.printStackTrace();
}
}
public void doPost(HttpServletRequest request,HttpServletResponse response)throws IOException,ServletException{
doGet(request,response);
}
}
网站首页index.jsp代码如下,登录的时候将用户信息以变量u保存在session对象中,所以根据u是否为空来判断用户是否登录
<%--
Created by IntelliJ IDEA.
User: Daniel
Date: 2018/7/16
Time: 17:17
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<html>
<head>
<title>主页</title>
</head>
<body>
<h1>*****************网站*****************</h1>
<c:if test="${empty u}">
<a href="${pageContext.request.contextPath }/servlet/LogUIServlet">登录</a>
<a href="${pageContext.request.contextPath }/servlet/RegUIServlet">注册</a>
</c:if>
<c:if test="${not empty u}">
欢迎您,${u.username}
<a href="${pageContext.request.contextPath }/servlet/LogUIServlet">注销</a>
</c:if>
</body>
</html>
这里用到了JSP 标准标签库(JSTL)的核心库,需要standard.jar和jstl.jar文件。可以参考这里
下面测试一下开发好的用户登录功能
登录成功页面
开发注销功能
LogoutServlet的代码如下:
package org.daniel.web.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class LogOutServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException,ServletException {
request.getSession().invalidate(); //session销毁
response.sendRedirect(request.getContextPath()+"/index.jsp");
}
public void doPost(HttpServletRequest request,HttpServletResponse response) throws IOException,ServletException{
doGet(request,response);
}
}
用户登录成功后,会将登录的用户信息存储在session中,所以我们要将存储在session中的user删除掉或者直接销毁session,这样就可以实现用户注销了。
开发总结
暂定。。。