1、动态页面的渲染方式
1.1 服务器渲染
1.2 客户端渲染
之前咱们写的带服务器的表白墙, 就是通过客户端渲染实现的.
接下来我们先介绍服务器渲染, 再介绍客户端渲染.
2、字符串拼接 HTML
@WebServlet("/html")
public class HtmlServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String name = (String) req.getParameter("name");
resp.setContentType("text/html; charset=utf-8");
resp.getWriter().write(String.format("<h3>name: %s</h3>", name));
}
}
2.1 代码示例: 服务器版猜数字
- 其中的成员 toGuess 表示要猜的数字, count 表示已经猜了的次数.
- doGet 方法用于处理游戏的初始化. doPost 用于实现每一次 "猜" 的过程.
- 服务器直接通过字符串拼接的方式构造 HTML, 返回给浏览器.
@WebServlet("/guessNum")
public class GuessNumServlet extends HttpServlet {
// 要猜的数字
private int toGuess = 0;
// 已经猜了几次
private int count = 0;
private Random random = new Random();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html; charset=utf-8");
// get 方法返回一个初始页面, 并且在服务器中存一个随机数
// 1. 构造随机数, 并清空 count
toGuess = random.nextInt(100) + 1;
System.out.println("toGuess = " + toGuess);
count = 0;
// 2. 返回页面内容
String html = "<form action=\"guessNum\" method=\"GET\">\n" +
" <input type=\"submit\" value=\"重新开始游戏\">\n" +
"</form>\n" +
"\n" +
"<form action=\"guessNum\" method=\"POST\">\n" +
" <input type=\"text\" name=\"num\">\n" +
" <input type=\"submit\" value=\"猜\">\n" +
"</form>";
resp.getWriter().write(html);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// post 方法处理猜数字的动作.
resp.setContentType("text/html; charset=utf-8");
// 1. 读取请求中用户猜的数字
int num = Integer.parseInt(req.getParameter("num"));
// 2. 比较大小
String result = "";
if (num < toGuess) {
result = "猜低了";
} else if (num > toGuess) {
result = "猜高了";
} else {
result = "猜对了";
}
count++;
// 3. 构造返回的页面内容
String html = String.format("<form action=\"guessNum\"
method=\"GET\">\n" +
" <input type=\"submit\" value=\"重新开始游戏\">\n" +
"</form>\n" +
"\n" +
"<form action=\"guessNum\" method=\"POST\">\n" +
" <input type=\"text\" name=\"num\">\n" +
" <input type=\"submit\" value=\"猜\">\n" +
"</form>\n" +
"\n" +
"<div>已经猜了: %d 次</div>\n" +
"<div>结果: %s </div>", count, result);
resp.getWriter().write(html);
}
}
<form action="guessNum" method="GET">
<input type="submit" value="重新开始游戏">
</form>
<form action="guessNum" method="POST">
<input type="text" name="num">
<input type="submit" value="猜">
</form>
<div>已经猜了: 次</div>
<div>结果: </div>
2.2 小结:
3、使用模板引擎
3.1 什么是模板引擎
模板就类似于考试中的 "填空题" 一样. 试卷上把一句话的一些核心信息挖掉. 然后由考生填写其中缺少的部分. 不同的考生, 就可能填出完全不同的内容.
这些表情包其实都是通过 "一个模板" 来生成出来的.
3.2 Thymeleaf 使用流程
选择一个合适的版本, 比如 3.0.12
<!-- https://mvnrepository.com/artifact/org.thymeleaf/thymeleaf -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
<version>3.0.12.RELEASE</version>
</dependency>
<h3 th:text="${message}"></h3>
- th:text 是 Thymeleaf 的语法. 浏览器不能直接识别 th:text 属性.
- HelloThymeleafServlet 包含一个成员 TemplateEngine , 这个类是 Thymeleaf 的核心类, 用于完成最终的页面渲染工作.
- 在 Servlet 的 init 方法中对 TemplateEngine 进行初始化工作. 主要是创建一个解析器对象(resolver), 给解析器对象设置一些配置. 并把解析器对象关联到 TemplateEngine 对象上.
- resolver 对象在创建的时候需要指定 ServletContext 对象, 这个对象是一个 Servlet 程序中全局唯一的对象. resolver 通过这个对象进一步获取到模板文件所在的路径. resolver 再通过setPrefix 和 setSuffix 来指定哪些文件被视为模板文件, 需要被加载.
- 在 doGet 方法内部通过 engine.process 来进行具体的渲染工作. 这个操作会把具体的模板文件, 和要渲染的数据关联起来, 并把最终结果写入到 resp 对象.
- WebContext 对象用来组织要渲染的数据.
注意: resolver.setPrefix("/WEB-INF/templates/") 中的 /WEB-INF/templates/ 末尾一定要有 /如果缺少了 / , 则 resolver 加载模板文件时就会把路径当成 /WEB-INF/templateshelloThymeleaf.html , 从而无法加载模板
@WebServlet("/helloThymeleaf")
public class HelloThymeleafServlet extends HttpServlet {
// TemplateEngine 是核心对象. 切实的负责模板渲染的工作.
private TemplateEngine engine = new TemplateEngine();
@Override
public void init() throws ServletException {
// 创建解析器对象. 功能是加载 HTML 模板, 并解析.
ServletContextTemplateResolver resolver = new
ServletContextTemplateResolver(this.getServletContext());
// 设置解析器的字符集
resolver.setCharacterEncoding("utf-8");
// 设置符合哪些要求的文件要被解析器加载起来. setPrefix 表示文件前缀, setSuffix 表
示文件后缀.
resolver.setPrefix("/WEB-INF/templates/");
resolver.setSuffix(".html");
// 把解析器对象和 TemplateEngine 对象关联起来. 后面 TemplateEngine 进行具体渲染
工作时, 就需要先获取到刚刚解析好的模板.
engine.setTemplateResolver(resolver);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 从参数中获取 message
String message = req.getParameter("message");
// 创建一个 WebContext 对象. 这里包含要渲染的变量有哪些, 以及变量的值是啥.
WebContext webContext = new WebContext(req, resp, getServletContext());
// 指定针对模板中的 "message" 变量, 渲染成 message 的值.
webContext.setVariable("message", message);
// 进行渲染工作, 把模板文件, 要渲染的数据组合起来, 并把结果直接写到
resp.getWriter() 中.
engine.process("helloThymeleaf", webContext, resp.getWriter());
}
}
- 注意体会上述代码中的关联关系。resovler 的 setPrefix 和 setSuffix 指定了从哪个目录下筛选哪些文件.
- engine.process 方法的第一个参数指定了要加载哪个模板文件.
- WebContext 中指定了模板变量名和变量值的对应关系(类似于一个哈希表结构). setVariable 中的第一个参数, 要和模板文件中写的 ${message} 匹配.
- engine.process 方法会把刚才的 WebContext 里的值替换到模板中, 并把最终结果写入到 resp 对象里.
- TemplateEngine , 核心功能是通过 process() 方法完成渲染工作
- ServletContextTemplateResolver , 核心功能是加载模板文件, 为后面的渲染做准备.
- WebContext , 核心功能是组织模板变量要替换成啥样的值.
3.3 代码示例: 改进猜数字
<form action="guessNum" method="GET">
<input type="submit" value="重新开始游戏">
</form>
<form action="guessNum" method="POST">
<input type="text" name="num">
<input type="submit" value="猜">
</form>
<div th:if="${!newGame}">
<div>已经猜了: <span th:text="${count}"></span> 次</div>
<div>结果: <span th:text="${result}"></span> </div>
</div>
- 模板文件中涉及到三个变量, newGame 用来判定当前是不是一局新游戏. count 表示已经猜的次数. result 表示 "猜大了" "猜小了" 这样的结果.
- th:if 为一个条件语句. 如果后面的条件成立, 则会显示 <div th:if="${!newGame}"> 标签的内容
@WebServlet("/guessNum")
public class GuessNumWithThymeleafServlet extends HttpServlet {
private TemplateEngine engine = new TemplateEngine();
// 要猜的数字
private int toGuess = 0;
// 已经猜了几次
private int count = 0;
private Random random = new Random();
@Override
public void init() throws ServletException {
ServletContextTemplateResolver resolver = new
ServletContextTemplateResolver(getServletContext());
resolver.setPrefix("/WEB-INF/templates/");
resolver.setSuffix(".html");
resolver.setCharacterEncoding("utf-8");
engine.setTemplateResolver(resolver);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html; charset=utf-8");
// 1. 构造随机数, 并清空 count
toGuess = random.nextInt(100) + 1;
System.out.println("toGuess = " + toGuess);
count = 0;
// 2. 构造数据
WebContext webContext = new WebContext(req, resp, getServletContext());
webContext.setVariable("newGame", true);
// 3. 返回页面内容
engine.process("guessNum", webContext, resp.getWriter());
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html; charset=utf-8");
// 1. 获取到用户猜的数字
int num = Integer.parseInt(req.getParameter("num"));
// 2. 比较大小
String result = "";
if (num < toGuess) {
result = "猜低了";
} else if (num > toGuess) {
result = "猜高了";
} else {
result = "猜对了";
}
count++;
// 3. 构造要返回的数据
WebContext webContext = new WebContext(req, resp, getServletContext());
webContext.setVariable("newGame", false);
webContext.setVariable("count", count);
webContext.setVariable("result", result);
// 4. 返回数据
engine.process("guessNum", webContext, resp.getWriter());
}
}
通过上面的代码就可以明显的感受到, HTML 和 Java 已经彻底分离开. 不需要在字符串中手动拼装 HTML 了.
3.4 Thymeleaf 模板语法
- href
- src
- class
- style
<a th:href="${url1}">百度</a>
<a th:href="${url2}">搜狗</a>
2) 创建 thymeleaf.AttrServlet 类.
@WebServlet("/attr")
public class AttrServlet extends HttpServlet {
private TemplateEngine engine = new TemplateEngine();
@Override
public void init() throws ServletException {
ServletContextTemplateResolver resolver = new
ServletContextTemplateResolver(getServletContext());
resolver.setPrefix("/WEB-INF/templates/");
resolver.setSuffix(".html");
resolver.setCharacterEncoding("utf-8");
engine.setTemplateResolver(resolver);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html; charset=utf-8");
WebContext webContext = new WebContext(req, resp, getServletContext());
webContext.setVariable("url1", "http://www.baidu.com");
webContext.setVariable("url2", "http://www.sogou.com");
engine.process("thymeleafAttr", webContext, resp.getWriter());
}
}
3) 部署程序, 通过 URL http://127.0.0.1:8080/ServletHelloWorld/attr 访问
<div th:if="${!newGame}">
<div>已经猜了: <span th:text="${count}"></span> 次</div>
<div>结果: <span th:text="${result}"></span> </div>
</div>
th:each="自定义的元素变量名称 : ${集合变量名称}"
<ul>
<li th:each="person : ${persons}">
<span th:text="${person.name}"></span>
<span th:text="${person.phone}"></span>
</li>
</ul>
- persons 是一个 Java 中的 Collections 类型.
- 其中 person 相当于就是访问到 persons 中的每个元素.
class Person {
private String name;
private String phone;
public String getName() {
return name;
}
public String getPhone() {
return phone;
}
public Person(String name, String phone) {
this.name = name;
this.phone = phone;
}
}
- name 和 phone 两个属性的名字要和模板中的 名字 (形如: ${person.name} ) 匹配.
- name 和 phone 两个属性需要设置成 public, 或者提供 public 的 getter 方法.
@WebServlet("/each")
public class EachServlet extends HttpServlet {
private TemplateEngine engine = new TemplateEngine();
@Override
public void init() throws ServletException {
ServletContextTemplateResolver resolver = new
ServletContextTemplateResolver(this.getServletContext());
resolver.setCharacterEncoding("utf-8");
resolver.setPrefix("/WEB-INF/templates/");
resolver.setSuffix(".html");
engine.setTemplateResolver(resolver);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html; charset=utf-8");
List<Person> persons = new ArrayList<Person>();
persons.add(new Person("张三", "110"));
persons.add(new Person("李四", "111"));
persons.add(new Person("王五", "112"));
WebContext webContext = new WebContext(req, resp, getServletContext());
webContext.setVariable("persons", persons);
engine.process("thymeleafEach", webContext, resp.getWriter());
}
}
4) 部署程序, 使用 URL http://127.0.0.1:8080/ServletHelloWorld/each 访问服务器.
<ul>
<li th:each="person, status : ${persons}">
<span th:text="${status.index}"></span>
<span th:text="${person.name}"></span>
<span th:text="${person.phone}"></span>
</li>
</ul>
engine.process("thymeleafEach", webContext, resp.getWriter());
String html = engine.process("thymeleafEach", webContext);
resp.getWriter().write(html);
4、只创建一个引擎实例
一个完整的项目中, 只需要创建一个 TemplateEngine, 并且只初始化一次即可.
4.1 什么是 ServletContext
- Tomcat 在启动时,它会为每个Web app都创建一个对应的 ServletContext.
- 一个WEB应用中的所有 Servlet 共享同一个 ServletContext 对象.
- 可以通过 HttpServlet.getServletContext() 或者HttpServletRequest.getServletContext() 获取到当前 webapp 的 ServletContext 对象.
context 英文原义为 "环境" / "上下文". 此处的 ServletContext 对象就相当于一个 webapp 的 "上下文".
(1)ServletContext 对象的重要方法
- 从请求参数中读取一个字符串 message
- 通过 req.getServletContext() 或者 this.getServletContext() 即可获取到当前 webapp的 ServletContext
- 通过 ServletContext.setAttribute() 把 message 的值设置进去.
@WebServlet("/writer")
public class WriterServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 1. 从请求参数中读取 message 字段的值
String message = (String) req.getParameter("message");
// 2. 把这个值设置到 ServletContext 中
ServletContext context = req.getServletContext();
context.setAttribute("message", message);
// 3. 返回结果
resp.setContentType("text/plain; charset=utf-8");
resp.getWriter().write("设置 message 成功!");
}
}
2) 创建 ReaderServlet 类
- 通过 req.getServletContext() 或者 this.getServletContext() 即可获取到当前 webapp的 ServletContext
- 通过 ServletContext.getAttribute() 把 message 的值获取出来.
@WebServlet("/reader")
public class ReaderServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 1. 从 ServletContext 中读取 message
ServletContext context = req.getServletContext();
String message = (String) context.getAttribute("message");
// 2. 把结果返回给浏览器
resp.setContentType("text/plain; charset=utf-8");
resp.getWriter().write("message: " + message);
}
}
如果我们不访问 /writer, 直接访问 /reader, 那么此时得到的 message 是 null.
4.2 什么是监听器 (Listener)
- 监听 HttpSession 的创建销毁, 属性变化
- 监听 HttpServletRequest 的创建销毁, 属性变化
- 监听 ServletContext 的创建销毁, 属性变化
- 实现 ServletContextListener 接口, 并实现两个方法 contextInitialized 和contextDestroyed
- MyListener 类需要使用 @WebListener 注解修饰, 才可以正确被 Tomcat 识别.
- contextInitialized 的参数是 ServletContextEvent 对象. 这个对象表示一个 "事件". 此处我们暂不深究, 我们只需要知道通过 ServletContextEvent.getServletContext() 能获取ServletContext 即可.
@WebListener
public class MyListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent sce) {
// 这个会在 context 被创建的时候调用.
// 创建的时机在所有 Servlet 实例化之前.
System.out.println("ServletContext 创建");
// 通过 sce 的 getServletContext 方法获取到刚刚创建好的 ServletContext 对象
ServletContext context = sce.getServletContext();
context.setAttribute("message", "message 的初始值");
}
public void contextDestroyed(ServletContextEvent sce) {
// 这个会在 context 被销毁的时候调用. 此处我们不关注
}
}
2) 部署程序, 可以看到服务器启动过程中就会打印出 "ServletContext 创建" 这样的日志.
使用 URL http://127.0.0.1:8080/ServletHelloWorld/reader 来访问服务器, 可以看到
4.3 修改 Thymeleaf 引擎初始化代码
- 创建监听器, 监听 ServletContext 的创建.
- 当 ServletContext 创建完毕后, 在 contextInitialized 中创建 TemplateEngine 实例和ServletContextTemplateResolver 实例, 并完成初始化操作.
- 把创建出来的 TemplateEngine 实例放到 ServletContext 中.
- 后续 Servlet 如果需要使用 TemplateEngine , 那么直接从 ServletContext 获取到之前创建好的TemplateEngine 实例即可, 不必重新创建.
@WebListener
public class ThymeleafConfig implements ServletContextListener {
public void contextInitialized(ServletContextEvent sce) {
// 1. 先获取到 context 对象
ServletContext context = sce.getServletContext();
// 2. 创建 TemplateEngine 实例
TemplateEngine engine = new TemplateEngine();
// 3. 创建 ServletContextTemplateResolver 实例
ServletContextTemplateResolver resolver = new
ServletContextTemplateResolver(context);
// 4. 给 resolve 设置一些属性
resolver.setCharacterEncoding("utf-8");
resolver.setPrefix("/WEB-INF/templates/");
resolver.setSuffix(".html");
// 5. 关联 resolver 和 engine
engine.setTemplateResolver(resolver);
// 6. 把 engine 放到 ServletContext 中, 以备后面的其他 Servlet 类使用
context.setAttribute("engine", engine);
}
public void contextDestroyed(ServletContextEvent sce) {
}
}
@WebServlet("/helloThymeleafTest")
public class HelloThymeleaf extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String message = req.getParameter("message");
ServletContext context = getServletContext();
TemplateEngine engine = (TemplateEngine) context.getAttribute("engine");
WebContext webContext = new WebContext(req, resp, context);
webContext.setVariable("message", message);
engine.process("helloThymeleaf", webContext, resp.getWriter());
}
}
3) 部署程序, 通过 URL http://127.0.0.1:8080/ServletHelloWorld/helloThymeleafTest?message=hello 访问
4.4 小结
- TemplateEngine : 只应该有一个实例, 被初始化一次.
- ServletContextTemplateResolver : 只应该有一个实例, 被初始化一次.
- WebContext : 可以有多个实例, 每次请求都需要创建一个新的 WebContext
思考: 使用 "单例模式" 控制 TemplateEngine 能否完成同样的效果嘛?注意: 创建 TemplateEngine 需要依赖 ServletContextTemplateResolver , 而ServletContextTemplateResolver 的创建又依赖 ServletContext.如果使用脱离了 Servlet 环境的单例模式, 那么就难以获取到 ServletContext 实例。如果要是在 ServletContextListener 中使用 单例模式, 反而就不如直接把得到的TemplateEngine 放到 ServletContext 中更方便。
5、综合案例
5.1 表白墙
3) 修改 pom.xml, 引入依赖.
<?xml version="1.0" encoding="UTF-8"?>
<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>
<groupId>org.example</groupId>
<artifactId>表白墙服务器版(2)</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api
-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
<version>3.0.12.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.45</version>
</dependency>
</dependencies>
<packaging>war</packaging>
<build>
<finalName>MessageWall</finalName>
</build>
</project>
这个代码从最初的静态页面版本的表白墙拷贝过来, 稍加修改.
- 样式和页面结构不变.
- 给这几个 input 输入框外面套上一层 form 标签. <form action="message" method="POST">
- 给 "谁", "对谁", "说什么" 这三个 input 标签加上 name 属性. 这是后续提交表单的必备信息.
- 把提交按钮的 input 的 type 改成 "submit"
- 在最下方显示消息列表的部分, 做出如下调整
<div class="row" th:each="msg : ${messages}">
<span th:text="${msg.from}">小猫</span> 对 <span th:text="${msg.to}">小狗
</span> 说: <span th:text="${msg.message}">喵</span>
</div>
- 把 CSS 中的样式进行了微调. 原来是针对所有的 span 设置宽度和行高, 现在改成名为 label 的class, 并把 "谁", "对谁", "说什么" 三个 span 标签引用 label.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>表白墙</title>
<style>
* {
margin: 0;
padding: 0;
}
.container {
width: 400px;
margin: 0 auto;
}
h1 {
text-align: center;
padding: 20px 0;
}
p {
color: #666;
text-align: center;
font-size: 14px;
padding: 10px 0;
}
.row {
height: 40px;
display: flex;
justify-content: center;
align-items: center;
}
/* 要修改这里的样式 */
.label {
width: 100px;
line-height: 40px;
}
.edit {
width: 200px;
height: 30px;
}
.submit {
width: 304px;
height: 40px;
color: white;
background-color: orange;
border: none;
}
.submit:active {
background-color: #666;
}
</style>
</head>
<body>
<div class="container">
<h1>表白墙</h1>
<p>输入后点击提交, 会将信息显示在表格中</p>
<form action="message" method="POST">
<div class="row">
<span class="label">谁: </span>
<input class="edit" type="text" name="from">
</div>
<div class="row">
<span class="label">对谁: </span>
<input class="edit" type="text" name="to">
</div>
<div class="row">
<span class="label">说什么: </span>
<input class="edit" type="text" name="message">
</div>
<div class="row">
<input type="submit" value="提交" class="submit">
</div>
</form>
<div class="row" th:each="msg : ${messages}">
<span th:text="${msg.from}">小猫</span> 对 <span th:text="${msg.to}">小狗
</span> 说: <span th:text="${msg.message}">喵</span>
</div>
</div>
</body>
</html>
// 负责和数据库建立连接
public class DBUtil {
private static final String URL = "jdbc:mysql://127.0.0.1:3306/MessageWall?
characterEncoding=utf8&useSSL=false";
private static final String USERNAME = "root";
private static final String PASSWORD = "";
private static DataSource dataSource = null;
private static DataSource getDataSource() {
if (dataSource == null) {
synchronized (DBUtil.class) {
if (dataSource == null) {
dataSource = new MysqlDataSource();
((MysqlDataSource)dataSource).setUrl(URL);
((MysqlDataSource)dataSource).setUser(USERNAME);
((MysqlDataSource)dataSource).setPassword(PASSWORD);
}
}
}
return dataSource;
}
public static Connection getConnection() {
try {
return getDataSource().getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
public static void close(Connection connection,
PreparedStatement statement,
ResultSet resultSet) {
try {
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
Message 类
public class Message {
public String from;
public String to;
public String message; }
MessageServlet
- 删除 objectMapper 成员
- 清空其中的 doGet 和 doPost , 一会重新实现.
- load 和 save 方法保持原状
@WebServlet("/message")
public class MessageServlet extends HttpServlet {
private List<Message> load() {
List<Message> messages = new ArrayList<Message>();
// 1. 和数据库建立连接
Connection connection = DBUtil.getConnection();
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
// 2. 拼装 SQL
String sql = "select * from messages";
statement= connection.prepareStatement(sql);
// 3. 执行 SQL
resultSet = statement.executeQuery();
// 4. 遍历结果集合
while (resultSet.next()) {
Message message = new Message();
message.from = resultSet.getString("from");
message.to = resultSet.getString("to");
message.message = resultSet.getString("message");
messages.add(message);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 5. 释放必要的资源
DBUtil.close(connection, statement, resultSet);
}
return messages;
}
private void save(Message message) {
// 1. 和数据库建立连接
Connection connection = DBUtil.getConnection();
PreparedStatement statement = null;
try {
// 2. 拼装 SQL
String sql = "insert into messages values(?, ?, ?)";
statement = connection.prepareStatement(sql);
statement.setString(1, message.from);
statement.setString(2, message.to);
statement.setString(3, message.message);
// 3. 执行 SQL
statement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 4. 释放必要的资源
DBUtil.close(connection, statement, null);
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
// GET 方法获取页面内容
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// POST 方法发布新留言
}
}
- 在 contextInitialized 中初始化 TemplateEngine
@WebListener
public class ThymeleafConfig implements ServletContextListener {
public void contextInitialized(ServletContextEvent sce) {
System.out.println("初始化 engine");
ServletContext context = sce.getServletContext();
TemplateEngine engine = new TemplateEngine();
ServletContextTemplateResolver resolver = new
ServletContextTemplateResolver(context);
resolver.setCharacterEncoding("utf-8");
resolver.setPrefix("/WEB-INF/templates/");
resolver.setSuffix(".html");
engine.setTemplateResolver(resolver);
context.setAttribute("engine", engine);
}
public void contextDestroyed(ServletContextEvent sce) {
}
}
5. 编写 doGet 和 doPost
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws
ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
// GET 方法获取页面内容
// 1. 从数据库读取数据
List<Message> messages = load();
// 2. 再进行页面渲染
ServletContext context = getServletContext();
TemplateEngine engine = (TemplateEngine) context.getAttribute("engine");
WebContext webContext = new WebContext(req, resp, context);
webContext.setVariable("messages", messages);
engine.process("message", webContext, resp.getWriter());
}
2) doPost
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws
ServletException, IOException {
// POST 方法发布新留言
// 1. 从参数中获取到消息数据
// [注意] 要给 req 也设置字符集, 否则读取表单中的中文可能就会乱码
req.setCharacterEncoding("utf-8");
Message message = new Message();
message.from = req.getParameter("from");
message.to = req.getParameter("to");
message.message = req.getParameter("message");
System.out.println(message.from + " 对 " + message.to + " 说: " +
message.message);
// 2. 把数据插入到数据库中
save(message);
// 3. 重定向到 GET 方法的页面
resp.sendRedirect("message");
}
5.2 在线相册
1) index.html
- 每个图片是一个 figure 标签.
- figure 标签中使用 img 表示缩略图, 使用 figcaption 标签来放图片的标题.
<head>
<meta charset="UTF-8">
<title>相册</title>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<!-- 第一组图片 -->
<figure class="sample">
<img src="image/1.jpg" alt="sample1" />
<figcaption>
<div>
<h2>Deconovo</h2>
<h4>Classic Sculptures</h4>
</div>
</figcaption>
<a href="image/1.jpg"></a>
</figure>
<figure class="sample">
<img src="image/2.jpg" alt="sample2" />
<figcaption>
<div>
<h2>Deconovo</h2>
<h4>Classic Sculptures</h4>
</div>
</figcaption>
<a href="image/2.jpg"></a>
</figure>
<figure class="sample">
<img src="image/3.jpg" alt="sample3" />
<figcaption>
<div>
<h2>Deconovo</h2>
<h4>Classic Sculptures</h4>
</div>
</figcaption>
<a href="image/3.jpg"></a>
</figure>
</body>
2) style.css
/* 引入文字样式库 */
@import url(https://fonts.googleapis.com/css?family=Raleway:400,700);
/* sample 部分的整体样式 */
.sample {
font-family: 'Raleway', Arial, sans-serif;
position: relative;
overflow: hidden;
margin: 10px;
min-width: 230px;
max-width: 315px;
width: 100%;
color: #ffffff;
text-align: center;
font-size: 16px;
background-color: #000000; }
.sample *,
.sample *:before,
.sample *:after {
-webkit-box-sizing: border-box;
box-sizing: border-box;
/* 当过了 0.55s 过渡效果 */
-webkit-transition: all 0.55s ease;
transition: all 0.55s ease; }
/* 图片部分的样式 */
.sample img {
max-width: 100%;
backface-visibility: hidden;
vertical-align: top; }
/* figcaption 用作文档中插图的图像,带有一个标题 */
.sample figcaption {
position: absolute;
bottom: 25px;
right: 25px;
padding: 5px 10px 10px; }
/* 绘制线条 */
.sample figcaption:before,
.sample figcaption:after {
height: 2px;
width: 400px;
position: absolute;
content: '';
background-color: #ffffff; }
/* 上面一条线 */
.sample figcaption:before {
top: 0;
left: 0;
-webkit-transform: translateX(100%);
transform: translateX(100%);
}
/* 下面一条线 */
.sample figcaption:after {
bottom: 0;
right: 0;
-webkit-transform: translateX(-100%);
transform: translateX(-100%);
}
/* 绘制线条 */
.sample figcaption div:before,
.sample figcaption div:after {
width: 2px;
height: 300px;
position: absolute;
content: '';
background-color: #ffffff; }
/* 左面一条线 */
.sample figcaption div:before {
top: 0;
left: 0;
-webkit-transform: translateY(100%);
transform: translateY(100%);
}
/* 右面一条线 */
.sample figcaption div:after {
bottom: 0;
right: 0;
-webkit-transform: translateY(-100%);
transform: translateY(-100%);
}
/* 文字部分 */
.sample h2,
.sample h4 {
margin: 0;
text-transform: uppercase; }
.sample h2 {
font-weight: 400; }
.sample h4 {
display: block;
font-weight: 700;
background-color: #ffffff;
padding: 5px 10px;
color: #000000; }
.sample a {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0; }
/* 当鼠标放到图片时的效果, .hover 仅演示需要,可自行取消 */
.sample:hover img,
.sample.hover img {
zoom: 1;
filter: alpha(opacity=50);
-webkit-opacity: 0.5;
opacity: 0.5; }
.sample:hover figcaption:before,
.sample.hover figcaption:before,
.sample:hover figcaption:after,
.sample.hover figcaption:after,
.sample:hover figcaption div:before,
.sample.hover figcaption div:before,
.sample:hover figcaption div:after,
.sample.hover figcaption div:after {
-webkit-transform: translate(0, 0);
transform: translate(0, 0);
}
.sample:hover figcaption:before,
.sample.hover figcaption:before,
.sample:hover figcaption:after,
.sample.hover figcaption:after {
/* 过渡延时 0.15s */
-webkit-transition-delay: 0.15s;
transition-delay: 0.15s; }
/* 背景仅演示作用 */
html {
height: 100%; }
body {
background-color: #212121;
display: flex;
justify-content: center;
align-items: center;
flex-flow: wrap;
margin: 0;
height: 100%; }
1. 准备工作
3) 修改 pom.xml, 引入依赖.
<?xml version="1.0" encoding="UTF-8"?>
<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>
<groupId>org.example</groupId>
<artifactId>表白墙服务器版(2)</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api
-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
<version>3.0.12.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.45</version>
</dependency>
</dependencies>
<packaging>war</packaging>
<build>
<finalName>MessageWall</finalName>
</build>
</project>
然后修改 index.html 的内容
- 每个图片只保留 <h2> 这个主标题, 不保留 <h4> 这个副标题.
- 服务器获取到图片, 组织成一个名为 images 的 List.
- 然后根据 images 中的信息, 填充 img 标签的 src 属性(用于展示)和 a 标签的 href 属性(用于点击跳转).
<body>
<figure class="sample" th:each="image : ${images}">
<img src="image/1.jpg" alt="sample1" th:src="${image.src}"/>
<figcaption>
<div>
<h2 th:text="${image.name}">Deconovo</h2>
</div>
</figcaption>
<a href="image/1.jpg" th:href="${image.src}"></a>
</figure>
</body>
3. 初始化模板引擎
@WebListener
public class ThymeleafConfig implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
ServletContext context = sce.getServletContext();
TemplateEngine engine = new TemplateEngine();
ServletContextTemplateResolver resolver = new
ServletContextTemplateResolver(context);
resolver.setPrefix("/");
resolver.setSuffix(".html");
resolver.setCharacterEncoding("utf-8");
engine.setTemplateResolver(resolver);
context.setAttribute("engine", engine);
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
public class Image {
public String name;
public String src; }
- 图片都放在 webapp/image 目录中. 可以通过 ServletContext.getRealPath("/image") 获取到 /image 的磁盘上的绝对路径.
- 直接使用 File.listFiles() 来罗列出指定目录中的所有文件.
- 把这些文件取出来, 组织成 List<Image> , 填充到模板中
@WebServlet("/imageShow")
public class ImageServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html; charset=utf-8");
// 1. 获取当前有哪些图片
List<Image> images = getImages();
// 2. 把图片信息返回到页面上
ServletContext context = getServletContext();
TemplateEngine engine = (TemplateEngine) context.getAttribute("engine");
WebContext webContext = new WebContext(req, resp, context);
webContext.setVariable("images", images);
String html = engine.process("index", webContext);
resp.getWriter().write(html);
}
// 从 webapp/image 目录中读取图片信息
private List<Image> getImages() {
List<Image> images = new ArrayList<>();
// 1. 获取 "/image" 在磁盘上的绝对路径
ServletContext context = getServletContext();
String path = context.getRealPath("/image");
System.out.println("path: " + path);
// 2. 查看这个路径下都有哪些图片文件
File imageRoot = new File(path);
File[] files = imageRoot.listFiles();
for (File f : files) {
Image image = new Image();
image.name = f.getName();
// 在网页上点击图片, 就能跳转到图片的详细页面.
image.src = "image/" + f.getName();
images.add(image);
}
return images;
}
}
点击图片, 就可以跳转到图片的具体展示页. (这个是一个静态资源)
5. 实现图片上传功能
<form action="imageUpload" method="post" enctype="multipart/form-data">
<input type="file" name="image">
<input type="submit" value="上传">
</form>
- 通过 HttpServletRequest.getPart("image") 获取到 Part 对象.
- 通过 Part.write() 方法把请求中的数据写入到磁盘文件中, 放到 webapp/image 目录里.
- 上传成功后返回一个重定向响应, 返回到图片列表页
@MultipartConfig
@WebServlet("/imageUpload")
public class UploadServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 把图片放到 webapp/image 目录中
// 先获取到 image 所在的磁盘路径
String path = getServletContext().getRealPath("/image");
// 从请求中获取到图片的 Part 对象
Part part = req.getPart("image");
// 获取到图片名字
String name = part.getSubmittedFileName();
// 把图片放到指定目录中
part.write(path + "/" + name);
// 返回图片列表页
resp.sendRedirect("imageShow");
}
}
部署程序, 通过 URL http://127.0.0.1:8080/Image/upload.html 打开上传图片页面
上传图片后的效果.
6、附录: 代码片段
6.1 引入依赖
<!-- https://mvnrepository.com/artifact/org.thymeleaf/thymeleaf -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
<version>3.0.12.RELEASE</version>
</dependency>
6.2 初始化模板引擎
@WebListener
public class ThymeleafConfig implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
ServletContext context = sce.getServletContext();
TemplateEngine engine = new TemplateEngine();
ServletContextTemplateResolver resolver = new
ServletContextTemplateResolver(context);
// 指定模板文件所在的路径
resolver.setPrefix("/WEB-INF/templates/");
resolver.setSuffix(".html");
resolver.setCharacterEncoding("utf-8");
engine.setTemplateResolver(resolver);
context.setAttribute("engine", engine);
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
6.3 模板页面
- th:text 设置元素的值.
- th:属性 设置元素属性.
- th:if 进行条件判定.
- th:each 循环遍历
<div class="row" th:each="msg : ${messages}">
<span th:text="${msg.from}">小猫</span> 对 <span th:text="${msg.to}">小狗
</span> 说: <span th:text="${msg.message}">喵</span>
</div>
6.4 渲染页面
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws
ServletException, IOException {
// 先获取数据
// 再进行页面渲染
ServletContext context = getServletContext();
// "engine" 是之前在 监听器 里面初始化好的 TemplateEngine 对象
TemplateEngine engine = (TemplateEngine) context.getAttribute("engine");
WebContext webContext = new WebContext(req, resp, context);
// "messages" 是模板页面中的 "变量" 名
webContext.setVariable("messages", messages);
// "message" 是模板文件名.
engine.process("message", webContext, resp.getWriter());
}