C/S:客户端 / 服务器
B/S:浏览器 / 服务器
JavaBean:
就是一个普通类(实体bean),包含三样标准:一个无参构造、私有属性、公共的getter和setter方法。
通常需要这么一个作为信息的传递载体。
在jsp+javabean架构中,JSP负责控制逻辑、表现逻辑、业务对象(javabean)的调用。
JSP+JavaBean模式适合开发业务逻辑不太复杂的web应用程序,这种模式下,JavaBean用于封装业务数据,JSP即负责处理用户请求,又显示数据。
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这个类
在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("运算符只能是:+ - * / ");
}
}
}
在web文件夹下,新建calculator.jsp,页面代码如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
计算器
<%
try {
calculatorBean.calculate();
} catch (Exception e) {
out.write(e.getMessage());
}
%>
----------------------------------------------------------
计算结果是:
=
----------------------------------------------------------
解释下:jsp:useBean 动作用来加载一个将在JSP页面中使用的JavaBean,id属性是动作元素的唯一标识,可以在JSP页面中引用,class属性指定Bean的完整包名。在类载入后,我们就可以通过 jsp:setProperty 和 jsp:getProperty 动作来修改和检索bean的属性。
jsp:setProperty用来设置已经实例化的Bean对象的属性,有两种用法。首先,你可以在jsp:useBean元素的外面(后面)使用jsp:setProperty,如下所示:
...
此时,不管jsp:useBean是找到了一个现有的Bean,还是新创建了一个Bean实例,jsp:setProperty都会执行。第二种用法是把jsp:setProperty放入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属性的值,转换成字符串,然后输出。语法格式如下:
...
下表是与getProperty相关联的属性:
属性 | 描述 |
---|---|
name | 要检索的Bean属性名称。Bean必须已定义。 |
property | 表示要提取Bean属性的值 |
关于JSP动作元素的更多信息,参考这里
关于JSP语法,参考这里
为便于访问,配置xml文件如下:
calculator.jsp
打开浏览器,访问http://127.0.0.1:8080/
在平时的JavaWeb项目开发中,在不使用第三方mvc开发框架的情况下,通常会选择Servlet+JSP+JavaBean开发模式来开发JavaWeb项目,Servlet+JSP+JavaBean组合开发就是一种MVC开发模式了,控制器(Controller)采用Servlet、模型(Model)采用JavaBean、视图(View)采用JSP。在讲解Servlet+JSP+JavaBean开发模式之前,先简单了解一下MVC开发模式。
因此,在Web世界里,都是Web客户端发起请求,Web服务器接收、处理并产生响应。
一般Web服务器是不能主动通知Web客户端更新内容。虽然现在有些技术如服务器推(如Comet)、还有现在的HTML5 websocket可以实现Web服务器主动通知Web客户端。
到此我们了解了在web开发时的请求/响应模型,接下来我们看一下标准的MVC模型是什么。
MVC模型是一种架构型的模式,本身不引入新功能,只是帮助我们将开发的结构组织的更加合理,使展示与模型分离、流程控制逻辑、业务逻辑调用与展示逻辑分离。如下图所示:
首先让我们了解下MVC(Model-View-Controller)的概念:
从上图我们还看到,在标准的MVC中模型能主动推数据给视图进行更新(观察者设计模式,在模型上注册视图,当模型更新时自动更新视图),但在Web开发中模型是无法主动推给视图(无法主动更新用户界面),因为在Web开发是请求-响应模型。
那接下来我们看一下在Web里MVC是什么样子,我们称其为Web MVC 来区别标准的MVC。
Web MVC中的M(模型)-V(视图)-C(控制器)概念和标准MVC概念一样,我们再看一下Web MVC标准架构,如下图所示:
在Web MVC模式下,模型无法主动推数据给视图,如果用户想要视图更新,需要再发送一次请求(即请求-响应模型)。
Servlet+JSP+JavaBean架构其实可以认为就是我们所说的Web MVC模型,只是控制器采用Servlet、模型采用JavaBean、视图采用JSP,如下图:
Servlet+JSP+JavaBean开发模式通常会结合三层结构,如下图:
Servlet+JSP+JavaBean(MVC)模式适合开发复杂的web应用,在这种模式下,servlet负责处理用户请求,jsp负责数据显示,javabean负责封装数据。Servlet+JSP+JavaBean模式程序各个模块之间层次清晰,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;
在开发数据访问层时,由于要使用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> set = prop.entrySet();
for(Map.Entry
这里在数据库中新建了张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","[email protected]",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执行测试
接口:
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","[email protected]",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);
}
}
编写一个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"%>
注册界面
用户注册
这里用到了一个日期控件,放在web/static/js/目录下,reg.jsp中的