文章目录
- 一、实操注意点
- 1.1 代码修改重启问题
- 1.2 Smart Tomcat的日志
- 1.3 如何处理错误
- 一. 抓自己的包
- 二、构造一个重定向的响应,让页面重定向到百度主页
- 三、让服务器返回一个html数据
- 四、表白墙
- 4.1 约定前后端数据
- 4.2 前端代码
- 4.3 后端代码
- 4.4 保存在数据库的版本
一、实操注意点
1.1 代码修改重启问题
编写Servlet程序时,无论是修改前端还是后端代码都需要重新启动服务器
- 后端Java代码需要重启来重新编译
- 前端的静态页面可能会被Tomcat给提前加载并缓存在内存中,如果不重启,修改的前端代码可能不会同步到内存中
1.2 Smart Tomcat的日志
Smart Tomcat为了开发方便,将日志直接显示到了IDEA窗口里,并没有专门生成日志文件
- 因为开发阶段日志只是起到调试作用,需要反复修改代码
- 等到程序发布,就一定要把日志保存到文件中,此时日志是记录了服务器的运行状态
1.3 如何处理错误
- 判断前端 or 后端问题:通过fidder抓包判断
- 如果抓包发现 ajax 的http请求根本没发出来,那大概率就是前端问题
- 如果ajax的http请求发了,且内容符合要求,那大概率是后端问题
- 处理前端问题:去浏览器的开发者工具上看,不要看VSCode里的前端代码,因为js代码都是被下载到浏览器后执行的
- F12或右键检查打开开发者工具,source部分表示了当前浏览器加载了哪些前端代码
- JS代码编译问题:JS代码在执行过程中,并不像Java有先编译的过程,而是直接去执行,语法错误也只有运行过程中才能发现。一旦执行过程中出现错误,后续的代码就不能继续执行了。
一. 抓自己的包
- 思路:调用 req 的各个方法, 把得到的结果汇总到一个 StringBuilder/StringBuffer 中, 统一返回到页面上
- 为什么换行用< br> 而不是\n:该内容是在浏览器上按照 html 的方式来展示的、
/n是字符的换行符,而非HTML的换行符 - 关于乱码问题:字符集不匹配,IDEA多是使用utf8编码,浏览器则默认跟着操作系统走,Windows操作系统的编码位gbk
- 注意:实际开发中,我们多用 fidder 进行抓包
@WebServlet("/request")
public class RequestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//必要的,显式告诉浏览器, 你拿到的数据是 html
resp.setContentType("text/html");
StringBuilder respBody = new StringBuilder();
respBody.append(req.getProtocol());
respBody.append("<br>");
respBody.append(req.getMethod());
respBody.append("<br>");
respBody.append(req.getRequestURI());
respBody.append("<br>");
respBody.append(req.getContextPath());
respBody.append("<br>");
respBody.append(req.getQueryString());
respBody.append("<p>下面是枚举的</p>");
Enumeration<String> headers = req.getHeaderNames();
while (headers.hasMoreElements()){
String header = headers.nextElement();
respBody.append(header + ":" + req.getHeader(header));
respBody.append("<br>");
}
resp.getWriter().write(respBody.toString());
}
}
二、构造一个重定向的响应,让页面重定向到百度主页
- 方法一:setHeader() + setStatus()
- 原理:
- 这段代码将会涉及两个请求,一个是req,一个是“访问百度主页”的请求。
- 浏览器看到【302+location】字段,就知道要执行跳转操作了
- location:用该属性描述重定向到哪个地方
- 301 VS 302:
- 相同点:直观效果差不多,事实上3开头的效果都差不多
- 不同点:
301 ----->永久重定向,以后一直重定向
302 ----->临时重定向,以后可能不重定向了,或者重定向到其他地方
- 永久重定向 VS 临时重定向:在浏览器上可能会有些不同,但这些不同的设计是设计者的初心,实际开发中程序员并不会刻意区分
- 不同的浏览器对待永久重定向,首次访问后会搞一个缓存,后续直接访问缓存。
- 如果是临时重定向,就是每次都去服务器那请求一下
- 原理:
@WebServlet("/trans")
public class ServletTest extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setStatus(302);
resp.setHeader("location", "https://www.baidu.com");
}
}
- 写法二:resp.sendRedirect(String location),做了写法一的两个代码的工作
三、让服务器返回一个html数据
- 关于字符编码问题:
- 修改原则:更改浏览器的编码方式
因为 uft8 是更主流的编码方式,gbk只能表示简体中文,是无法表示其他例如方言的文字的 - 如何查看并修改IDEA的字符集:setting ----> encoding
- 让浏览器更改字符集:
- 请求的字符集取决于页面,而 html 有声明字符集的部分。< meta charset = “UTF-8”>
- 使用resp进行更改。要注意顺序,先header后body,因为一旦开始设置 body ,此时就相当于header和status都定性了,后续即使是修改了,也无法生效。
- 修改原则:更改浏览器的编码方式
@WebServlet("/getHtml")
public class HTMLTest extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("<div>你好</div>");
}
}
四、表白墙
4.1 约定前后端数据
- 概念:
- 指进行前后端交互接口的约定,比如前端发送什么样的请求,后端返回什么样的响应
- 前后端数据交互的格式可以随便约定,但双方一定要互相遵守
- 请求的格式:“路径是什么” 以及 “传来的数据格式是xml还是JSON”
- 响应的格式:返回的数据格式是什么,状态码是什么
4.2 前端代码
- 放前端页面:放到webapp目录下,可通过网络访问,如127.0.0.1:8080/test/messageWall.html
- 获取到输入框的内容:
- 选中元素:使用querySelector()、querySelectorAll()方法,这些是由浏览器提供的API,可以用来获取到页面的元素。如 let button = document.querySelector(‘#submit’);
- 确认行为:
- JS中,函数是可以像变量一样复制的,这里相当于是把这个函数定义出来后就赋值给button.onclick()
- 触发时机:当按钮被点击时,触发该函数,相当于是个回调函数
button.onclick = function() {
XXXXX
}
- 构造js对象:
(1)js对象也是由{}构成的键值对(JSON)就是出自这里
(2)关于引号的问题:key都是字符串类型,value则可以是任何类型,由于key必须是字符串,所以key这里的引号是可以省略的。但是省略之后,容易看不出原本是什么类型的了,所以最好还是加上。
let body = { let body = {//简化版本,该情况表示键值对和value是一样的
"from": from, from,
"to": to, to,
"message": msg msg
}; };
-
构造标签:
- 创建标签:let rowDiv = document.createElement(‘div’); 创建一个div标签
- 给该标签设置一个类:rowDiv.className = ‘row message’;
- 往标签里构造内容:rowDiv.innerHTML = from + ’ 对 ’ + to + ’ 说: ’ + msg;
- 把该标签放到其他标签之后:containerDiv.appendChild(rowDiv);把该变量放到containerDiv这个对象的末尾
-
前后端交互:、ajax方法会更具输入的参数,构造出http请求并发给服务器、但原生的API比较难用,我们一般使用JQuery封装过后的API
- 导入JQuery依赖:
-
搜索【JQuery cdn】,把< scriot>整个标签赋值过来即可,如< script src=“https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js”>
-
前端加载某个第三方库的方式很简单,直接把库对应的网络地址加进来即可
-
- success: function(body):这个body参数实际上包含了从服务器返回的响应数据。当前端成功接收到响应时调用这个函数。
- JS对象和JSON对象互转 :JS对象虽然格式上和JSON非常相似,但是仍然是两个不同的东西
- JS对象 —> JSON字符串:JSON,stringify()
- JSON字符串 —> JS对象:JSON.parse()
- JQuery的自动转换问题:
- JQuery 见到响应中的 application/json , 就会自动的把响应转换成 js 对象数组
- 故此时的body 是 js 对象数组,而不是 json 字符串了。我们也就可以直接按照数组的方式来操作 body,,每个元素都是 js 对象
- 关于url的值:相对路径方式比较常见,因为后续修改 contextPath 比较方便,可以减少耦合
- 绝对路径写法:以“/”开头,如:‘/test/massage’
- 相对路径写法:不以“/”开头,如:‘massage’。相对路径的基准是当前html所在的路径。当前html的路径是:“test/messageWall.html”
- 导入JQuery依赖:
//发送一个HTTP请求
$.ajax({
});
<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
<script>
let containerDiv = document.querySelector('.container');
let inputs = document.querySelectorAll('input');
let button = document.querySelector('#submit');
button.onclick = function() {
// 1. 获取到三个输入框的内容
let from = inputs[0].value;
let to = inputs[1].value;
let msg = inputs[2].value;
if (from == '' || to == '' || msg == '') {
return;
}
// 2. 构造新 div
//相当于<div></div>
let rowDiv = document.createElement('div');
//相当于<div className = 'row message></div>
rowDiv.className = 'row message';
//相当于<div className = 'row message>构造的内容</div>
rowDiv.innerHTML = from + ' 对 ' + to + ' 说: ' + msg;
//让创建出来的<div>加入到页面中
containerDiv.appendChild(rowDiv);
// 3. 清空之前的输入框内容
for (let input of inputs) {
input.value = '';
}
//4. 把用户输入的数据发送给服务器
let body = {
"from": from,
"to": to,
"message": msg
};
$.ajax({
type: 'post',
url: 'message',
contentType: 'application/json; charset=utf8',
data: JSON.stringify(body),
success: function(body){
//这里是把返回的结果打印到了前端的控制台处
console.log(body);
}
});
}
$.ajax({
type: 'get',
url: 'message',
success: function(body){
let container = document.querySelector('.container');
for (let i = 0; i < body.length; i++){
let message = body[i];
let div = document.createElement('div');
div.className = 'row';
div.innerHTML = message.from + " 对 " + message.to +
" 说:" + message.message;
container.appendChild(div);
}
}
})
</script>
4.3 后端代码
- 引入Jackson依赖:
- 把数据存储在服务器中:
- 内存:
- 创建一个ArrayList,把数据都存到这个顺序表中。当把List/数组转成JSON时,Jackson会自动将其整理成JSON数组,里面的每个元素也会被转换成JSON对象
- 注意,此处是把数据保存到内存中,一旦重启服务器,内存数据就无了。
- 数据库:把数据保存到数据库中,实现持久化保存
- 内存:
class Message{
public String from;
public String to;
public String message;
@Override
public String toString() {
return super.toString();
}
}
@WebServlet("/message")
public class MessageWall extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();
private List<Message> messageList = new ArrayList<>();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String respJson = objectMapper.writeValueAsString(messageList);
resp.setContentType("application/json; charset=utf8");
resp.getWriter().write(respJson);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Message message = objectMapper.readValue(req.getInputStream(), Message.class);
messageList.add(message);
System.out.println("服务器收到message:" + message);
resp.setStatus(200);
resp.getWriter().write("ok");
}
}
4.4 保存在数据库的版本
- 引入数据库的依赖:之前是直接下载Mysql驱动包,现在也可以直接使用Maven进行管理
- 创建数据库和数据表:
- 通过JDBC代码来操作数据库:
@WebServlet("/messageWall")
public class MessageServlet extends HttpServlet {
ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Message message = objectMapper.readValue(req.getInputStream(), Message.class);
try {
add(message);
} catch (SQLException e) {
throw new RuntimeException(e);
}
System.out.println("服务器收到message:" + message);
resp.setStatus(200);
resp.getWriter().write("ok");
}
private void add(Message message) throws SQLException {
DataSource dataSource = new MysqlDataSource();
((MysqlDataSource)dataSource).setURL("jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf8&useSSL=false");
((MysqlDataSource)dataSource).setUser("root");
((MysqlDataSource)dataSource).setPassword("123456");
Connection connection = dataSource.getConnection();
String sql = "insert into message values(?, ?, ?)";
PreparedStatement statement = connection.prepareStatement(sql);
statement.setString(1, message.from);
statement.setString(2, message.to);
statement.setString(3, message.message);
statement.executeUpdate();
statement.close();
connection.close();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
List<Message> messageList = null;
try {
messageList = load();
} catch (SQLException e) {
throw new RuntimeException(e);
}
resp.setContentType("application/json");
String ret = objectMapper.writeValueAsString(messageList);
resp.getWriter().write(ret);
}
public List<Message> load() throws SQLException {
DataSource dataSource = new MysqlDataSource();
((MysqlDataSource)dataSource).setURL("jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf8&useSSL=false");
((MysqlDataSource)dataSource).setUser("root");
((MysqlDataSource)dataSource).setPassword("123456");
Connection connection = dataSource.getConnection();
String sql = "select * from message";
PreparedStatement statement = connection.prepareStatement(sql);
ResultSet resultSet = statement.executeQuery();
List<Message> messageList = new ArrayList<>();
while (resultSet.next()){
Message message = new Message();
message.from = resultSet.getString("from_user");
message.to = resultSet.getString("to_user");
message.message = resultSet.getString("message");
messageList.add(message);
}
resultSet.close();
statement.close();
connection.close();
return messageList;
}
}