如何开始最简单的Servlet编程?(基于Tomcat服务器)
知道了如何借助Tomcat开始进行最简单的Servlet编程后,我们就可以进一步完善功能制作一个基础的网站了。
在此之前我们先了解一下Servlet的生命周期。
Servlet的生命周期
初始化init -> 处理请求service -> 销毁destroy
对生命周期而言这三个方法是最重要的,标志着开头过程和结束。
Servlet的方法:
init() | HttpServlet实例化后被调用一次 |
service() | 收到HTTP请求就会调用 |
destroy() | HttpServlet实例不再被使用时调用一次 |
doGet() | 收到GET请求后,由service方法调用 |
doPost() | 收到POST请求后,由service方法调用 |
doDelete() \ doPut()… | 收到其他请求后,由service方法调用 |
在了解Servlet工作过程之前先查看一个概念:Servlet容器。Servlet代码的运行环境就是Servlet容器,说白了就是管理许多Servlet的创建、使用、销毁的东西,如Tomcat、Jetty、Jboss,一般而言是Web服务器。
Servlet工作过程:
在Web服务器接收到第一个HTTP请求的时候,会调用一次init()加载一个对应的Servlet。
随后Web服务器用多线程的方式,每个请求对应一个线程去执行service(),响应结果再返回Web服务器。
而destroy()是不一定被调用到的,因为大部分情况程序会被强制关闭,而非老老实实被销毁然后让JVM垃圾回收。
p.s. 开发中通常都是只需要重写doXXX方法,service会根据方法调用不同的doXXX。
知道了Servlet的生命周期后,我们就需要知道,我们不仅得了解如何处理请求,我们可能还要了解请求如何生成。
Servlet编程
前后端编程
前端通过ajax构建请求
后端设置响应
基本交互
逻辑:
客户端发送ajax请求后,触发服务器的doXXX
然后getWriter.write会触发客户端的ajax中success回调函数
如下:
有表白墙代码供大家练习:
<script>
let container = document.querySelector('.container');
let button = document.querySelector('button');
button.onclick = function() {
// 1. 获取到输入框的内容
let inputs = document.querySelectorAll('input');
let from = inputs[0].value;
let to = inputs[1].value;
let message = inputs[2].value;
if (from == '' || to == '' || message == '') {
alert('当前输入框内容为空!');
return;
}
console.log(from + ", " + to + ", " + message);
// 2. 能够构造出新的 div, 用来保存用户提交的内容
let rowDiv = document.createElement('div');
rowDiv.className = 'row';
rowDiv.innerHTML = from + " 对 " + to + " 说: " + message;
container.appendChild(rowDiv);
// 3. 提交完之后, 清空输入框的内容
for (let i = 0; i < inputs.length; i++) {
inputs[i].value = '';
}
// 将输入框里取到的数据构造成JS对象(类似于JSON结构,都是{}+:的字符串)
// 完整形式如:
// let messageJson = {
// "from": from,
// "to": to,
// "message": message
// };
// 当然可以省略key对象的引号,写成下面的形式:
let messageJson = {
from: from,
to: to,
message: message
};
$.ajax ({
type:'post',
// 相对路径写法:
// 只需要写Context Path后的内容
url:'mWall',
// 绝对路径写法:
// 写localhost:8080后的内容
// url:'/newCat/mWall'
contentType: 'application/json; charset:utf8',
// JS自带的JSON转译 转成JSON对象
data:JSON.stringify(messageJson),
// 服务器是2系列的时候执行
success: function(){
alert("提交成功!");
},
// 服务器不是2系列的时候执行
error: function() {
alert("提交失败");
}
});
}
</script>
这中间的ajax代码表示,当点击了提交按钮后,会自动构造一个POST请求提交数据,内容是将input的内容打包成json格式发送给服务器。
后端代码处理这个POST请求:
@WebServlet("/mWall")
public class MessageWall extends HttpServlet {
// ObjectMapper是Jackson Datamind提供用于IO Json的类
private ObjectMapper objectMapper = new ObjectMapper();
List<Message> messageList = new ArrayList<>();
@Override
protected void doPost(HttpServletRequest req,
HttpServletResponse resp) throws ServletException, IOException {
Message message = objectMapper.readValue(req.getInputStream(), Message.class);
messageList.add(message);
resp.setContentType("application/json; charset=utf8");
resp.getWriter().write("{ \"ok\": 1}");
}
}
class Message {
public String from;
public String to;
public String message;
}
对POST的处理方式是将内容存储在List当中,并且通过getWriter().write()返回响应,响应会触发回调函数
success里的内容是一个提示,所以有
前端再整个从服务器中获取页面的操作,逻辑是构造Get请求希望服务器发送List的内容,然后通过success回调去解析:
<script>
// 从服务器获取页面操作:
// 函数在页面加载时候调用 (每次加载页面 / 刷新页面时候触发)
// 通过该函数实现从服务器获取消息列表,并显示到页面上
function load() {
$.ajax( {
type: 'get',
url: 'mWall',
success: function(body) {
// 获取的body已经是js对象的数组
// ajax会自动 根据Content-Type 进行类型转换
// 将服务器返回的Json格式字符串时 Content-Type为application/json
// 则将 将json转为js对象
// 对响应的body进行解析
// 遍历数组并将内容构造到页面上
for (let message of body) {
let newDiv = document.createElement('div');
newDiv.className = 'row';
newDiv.innerHTML = message.from + " 对" + message.to + " 说:" + message.message;
container.appendChild(newDiv);
}
}
})
}
// 函数调用写在这里,页面加载的时候执行
load();
</script>
后端响应如下:
@Override
protected void doGet(HttpServletRequest req,
HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("application/json; charset=utf8");
// 这个方法可以将对象转为Json格式的字符串 这里是List的话就转成Json数组
String respString = objectMapper.writeValueAsString(messageList);
resp.getWriter().write(respString);
}
提交后抓包可以看到:
每次刷新页面就会前端就会调用load代码然后构造了请求,获取到了json键值对
使用数据库改进数据存储方式
在pom.xml中引入Maven依赖
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.31</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
好像主要是使用mysql-connector-java
创建数据库表
把操作保存好
修改后端代码
和数据库建立连接:建立一个DBUtil类,封装connection、close等功能
public class DBUtil {
// 单例对象
private static DataSource dataSource = null;
private static DataSource getDataSource () {
if (dataSource == null) {
dataSource = new MysqlDataSource();
// localhost:3306后跟的应该是数据库的名字
((MysqlDataSource)dataSource).setURL("jdbc:mysql://127.0.0.1:3306/MessageWall?characterEncoding=utf8&useSSL=false");
((MysqlDataSource)dataSource).setUser("root");
((MysqlDataSource)dataSource).setPassword("1234");
}
return dataSource;
}
public static Connection getConnection () throws SQLException {
return getDataSource().getConnection();
}
public static void close (Connection connection, PreparedStatement statement, ResultSet resultSet) {
// 分开写可以保证一个异常了后南面的都可以执行到
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
通过JDBC完成对数据库的操作
private void save (Message message) {
Connection connection = null;
PreparedStatement statement = null;
try {
// 和数据库构建连接
connection = DBUtil.getConnection();
// 构造SQL语句
String sql = "insert into message values(?,?,?);";
statement = connection.prepareStatement(sql);
statement.setString(1,message.from);
statement.setString(2,message.to);
statement.setString(3,message.message);
// 执行SQL语句:返回值表示操作影响了几行(针对table而言)
int ret = statement.executeUpdate();
// 这里插入了一行数据,就是影响了一行
if (ret != 1) {
System.out.println("插入失败!");
} else {
System.out.println("插入成功!");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 关闭连接
DBUtil.close(connection, statement, null);
}
System.out.println("Method save has been executed!");
}
private List<Message> load (){
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
List<Message> messageList = new ArrayList<>();
try {
// 和数据库创建连接
connection = DBUtil.getConnection();
// 构造SQL语句
String sql = "select * from message;";
statement = connection.prepareStatement(sql);
// 执行SQL:executeQuery执行查询 返回结果是一个resultSet结果集
resultSet = statement.executeQuery();
// 遍历结果集
while (resultSet.next()) {
Message message = new Message();
// 读取from这一列
message.from = resultSet.getString("from");
message.to = resultSet.getString("to");
message.message = resultSet.getString("message");
messageList.add(message);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 释放资源
DBUtil.close(connection, statement, resultSet);
}
System.out.println("Method load has been executed!");
System.out.println(messageList.toString());
return messageList;
}
主要是添加这两个方法,将原来的通过类中的容器进行存储改成通过数据库进行存储。