一:thymeleaf 指令有问题
1.检查th指令语法是否错误
2.检查指令中是否有多余的符号
3.检查th:text中间是否有空格
4.检查后端代码是否有值
Attribute name cannot be null or empty
二:服务器给前端返回的数据,大部分情况下都是一个HTML页面,我们只是用Servlet,想要返回一个动态页面,返回的页面就只能通过字符串拼接的方式来进行构造,这是非常的不科学,使用模板引擎生成动态页面,也实现了前后端分离;
三:前后端彻底的进行分离页面:前端代码通过Ajax的方式来和后端进行通信,后端只负责返回Json格式的数据,后端不会再去拼接页面,都是前端拿到数据之后,自己进行拼接;
使用模板引擎,界面和逻辑进行分离(Java Servlet)
四:模板引擎中常见的类:
1.TemplateEngine,他是负责进行渲染,渲染指的是就是把动态的数据替换到HTML页面中的指定位置,模板引擎做得好象是考试卷子上的填空题一样,每个人在不同的卷子上填的是不同的内容,最终就展示出了不同的页面,模板引擎就是一个模板,依据这个模板就可以生成出不同的页面
2.WebContext,这个类就是一个键值对,目标就是将HTML中模板的变量和Java代码中的变量给关联起来;
3.ServletContextTemplateResolver叫做模板解析器对象,把之前写好的HTML模板给加载过来,并告知engine文件在哪里;
五:模板引擎的使用流程:
1)写一个HTML模板,对于这些可变的数据,使用一些特殊的指令来进行占个位置;
2)针对Thymeleaf来进行初始化
2.1创建TemplateEngine实例
2.2通过WebContextTemplateResolver,并且设置加载模板文件的路径,设置前缀和后缀
2.3把创建好的resolver实例关联到engine对象上
3)针对每一个请求,分别进行模板渲染
a)得到要进行渲染的数据(Java提前准备好一些变量)
b)通过WebContext把HTML中的变量和Java中的变量进行关联起来
c)通过调用engine对象的process方法,实现具体的渲染操作,也就相当于是卷子(HTML模板)和答题卡(WebContext)进行关联起来了;
ServletContext是一个Servlet程序在全局储存信息的空间,服务器开始就存在,服务器关闭就销毁;每一个webapp就是tomact文件中的webapps对应的子目录;
这个类创建的初心:在Tomact启动的时候,他会给每一个webapp(一个文件目录)都提供创建了一个ServletContext对象,而每一个单独的web-app有多个servlet,而这里面的所有servlet对象都共享一个ServletContext对象,可以共享一部分数据;
在这里面,我们也是可以通过ServletContext实现让TemplateEngine在一个webapp中只有一个实例

在之前我们所写的代码当中,每一个Servlet都有一个TemplateEngine,这显然是不太科学的,我们希望同一个webapp中可以共享一个TemplateEngine对象;
那么可以把TemplateEngine搞成单例模式吗?这显然是不行的,单例指的是在整个进程中只有一个实例,但是此处的TemplateEngine不应该是进程中的实例,一个Tomact就是一个进程,Template而应该是webapp级别的实例,为了做到这一点我们应该把TemplateEngine放到ServletContext中进行创建;
它所存在的意义是可以让同一个webapp中的多个Servlet之间可以共享数据,所以ServletContext提供了一些get/set接口;所以程序员想在这多个Servlet之间共享数据,也是可以通过自定义键值对的方式来实现的
- 1)void setAttribute(String name,Object obj)向我们的ServletContext中设置键值对;
- 2)Object getAttribute(String name)根据属性名来获取到属性的值
- 3)void removeAttribute(),删除对应的属性
1)咱们之前用的HttpSession可以让程序员自己去定义一些键值对来进行存储,就像是一个时间胶囊一样,我们在登陆成功的时候会存放一些数据进去,后续再进行登录进行访问的时候再通过getAttribute()方法进行取出来使用;
2)程序员想要在这多个Servlet之间共享哪些数据,也是通过自定义键值对的方式来进行实现的;那么我们就可以想WebContext对象中来设置一些自定义的键值对,然后再别的Servlet中按照这个Key来进行访问也就可以了
3)也就是说只要是在Java200这个web-app中创建的Servlet,咱们都是可以通过getServletContext得到同一个上下文对象
1)创建一个GET请求,请求的格式类似于,http://127.0.0.1:8080/Java200/write?message=aaa
我们在WriteServlet中调用req.getparamter()方法,从请求参数中得到一个字符串message,通过req.getServletContext(),或者this.getServletContext(),就可以获取到当前webapp中的ServletContext对象;
2)通过ServletContext.setAttribute()把message的值设置进去;
当前我们写的两个Servlet对象都是用的一个webap,因为如果他们进行打包,都打到同一个war包里面了;只要是在这个webapp中创建出来的Servlet对象,在两个Servlet都是可以通过req.getServletContext得到得到同一个上下文对象WebContext;
- import javax.servlet.ServletContext;
- import javax.servlet.ServletException;
- import javax.servlet.annotation.WebServlet;
- import javax.servlet.http.HttpServlet;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import java.io.IOException;
- @WebServlet("/write")
- //这个类负责向ServletContext中写数据
- //浏览器通过一个形如/write?message=aaa访问到WriteServlet,就把这个属性message=aaa这个键值对储存到ServletContext
- public class WriteServlet extends HttpServlet {
- protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
- resp.setContentType("text/html;charset=utf-8");
- //1从请求中获取到message参数
- String flag=req.getParameter("message");
- //2获取到当前的ServletContext对象(这个对象是Tomact在加载webapp的时候自动创建的)
- ServletContext servletContext= req.getServletContext();
- //3 向ServletContext中写入键值对
- servletContext.setAttribute("flag",flag);
- //4 返回响应
- resp.getWriter().write("<h3>储存message成功<h3>");
- }
- }
-
-
- import javax.servlet.ServletContext;
- import javax.servlet.ServletException;
- import javax.servlet.annotation.WebServlet;
- import javax.servlet.http.HttpServlet;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import java.io.IOException;
- //用这个类来向我们的ServletContext中读取数据,这样就可以把刚才WriteServlet中储存的值读取出来
- @WebServlet("/read")
- public class ReadServlet extends HttpServlet {
- @Override
- protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
- resp.setContentType("text/html;charset=utf-8");
- //1获取到ServletContext对象
- ServletContext servletContext= req.getServletContext();
- //2从这里面来获取到刚才存得值
- String result=(String)servletContext.getAttribute("flag");
- resp.getWriter().write("flag"+result);
- }
- }
1)我们进行学习ServletContext对象的初心就是想用这个对象来进行存储一个TemplateEngine对象,达到说让当前的所有webapp中的所有Servlet共用同一个TemplateEngine对象
2)所以我们最终就需要把TemplateEngin初始化好,同时放到ServletContext对象里面,所以后续的Servlet就不必进行初始化TemplateEngin,后面需要在别的Servlet进行访问的时候,直接进行获取就可以了;
3)为了保证每一个其他的Servlet都能够在第一时间可以获取到一个初始化好的TemplateEngin实例,我们就需要在ServletContext在创建好之后,就第一时间的给TemplateEngin进行实例化,初始化,进行保存;
————————————————————————————————————
1)但是我们刚才分析了,ServletContext是由Tomact自动创建出来的,那么我们如何才可以让ServletContext创建好之后,就第一时间执行我们自己定义的的代码呢?所以Servlet就给我们提供了一组机制,叫做listener成为监听器; 因此我们就可以通过listener来监听我们ServletContext的初始化完毕的操作(ServletContext一旦创建好,就出发了监听器,执行我们自己所写的代码事件)
2)JS中学过的onclick操作,也是一个监听器,用户一旦进行点击,就被监听到了,然后接下来进行后续的操作;
1)这个过程中涉及到的一些接口,我们要创建Mylistener类,就要实现ServletContextListener接口,并实现两个方法,contextInitialized和contextDestoryed;
2)Mylistener类通过使用@webListener来进行注解修饰,才可以正确的被Tomact识别,contextInitialized的参数是ServletContextEvent对象,这个对象标识一个事件,此处我们并不进行深究;
我们只需要可以通过ServletContextEvent.getServletContext()方法可以获取到一个ServletContext即可;
3)当ServletContext被创建好之后,就会直接自动调用执行调用contextInitialized方法;
- import javax.servlet.ServletContext;
- import javax.servlet.ServletContextEvent;
- import javax.servlet.ServletContextListener;
- import javax.servlet.annotation.WebListener;
- import javax.servlet.annotation.WebServlet;
-
- @WebListener
- public class Mylistener implements ServletContextListener {
- //当ServletContext初始化之后,会立即执行这个方法
- @Override
- public void contextInitialized(ServletContextEvent servletContextEvent) {
- System.out.println("ServletContext已经进行了初始化,马上执行这个方法");
- //1 获取到ServletContext这个对象
- ServletContext servletContext=servletContextEvent.getServletContext();
- servletContext.setAttribute("message","我爱我的家");
- }
-
- @Override
- public void contextDestroyed(ServletContextEvent servletContextEvent) {
-
- }
- }
1)创建一个新的类实现ServletContextListener接口,重写其中的contextInitialized方法,这个方法是在初始化完毕后被自动调用的;
2)给新的类加上一个@webListener注解,加上这个注解之后才可以被tomact识别出来并进行加载;
3)在方法里面获取ServletContext是通过ServletContextEvent对象来拿到的;后续就可以调用setAttribute()方法和getAttribute()方法来进行使用即可;
4)在这里面我们就可以把TemplateEngin的初始化操作就行了,还可以进行初始化一些其他的操作和内容
- import org.thymeleaf.TemplateEngine;
- import org.thymeleaf.templateresolver.ServletContextTemplateResolver;
-
- import javax.servlet.ServletContext;
- import javax.servlet.ServletContextEvent;
- import javax.servlet.ServletContextListener;
-
- public class Mylistener implements ServletContextListener {
- @Override
- public void contextInitialized(ServletContextEvent servletContextEvent) {
-
- //1 初始化TemplateEngine并创建实例,并获取到ServletContext对象(通过方法中的参数获取到)
- ServletContext servletContext=servletContextEvent.getServletContext();
- TemplateEngine templateEngine=new TemplateEngine();
-
- //2 创建ServletContextTemplateResolver实例(模板解析器),并将两个类进行关联
- ServletContextTemplateResolver servletContextTemplateResolver=new ServletContextTemplateResolver(servletContext);
- servletContextTemplateResolver.setPrefix("/WEB-INF/template/");
- servletContextTemplateResolver.setSuffix(".html");
- servletContextTemplateResolver.setCharacterEncoding("utf-8");
- templateEngine.setTemplateResolver(servletContextTemplateResolver);
-
- //3把创建好的实例创建键值对放到ServletContext中,方便后面的Servlet进行获取,就不用在每一个Servlet里面进行初始化化操作了
- servletContext.setAttribute("TemplateEngine",templateEngine);
- System.out.println("当前webapp的TemplateEngine已经初始化完毕");
-
- //4后面的Servlet如果想要获取到templateEngine这个对象,就可以用
- //TemplateEngine templateEnginexx=(TemplateEngine) servletContext.getAttribute("TemplateEngine");
- //后续就可以调用templateEngine.process方法,HTML文件,webContext;
- }
- //ServletContext被销毁之前,会自动地执行这个方法
- @Override
- public void contextDestroyed(ServletContextEvent servletContextEvent) {
-
- }
- }
我们要引入模板文件,我们要在webapp中创建WEB-INF目录,在这个目录里面创建一个目录叫做template目录,把我们表白墙的前端页面方法到这个目录里面;
我们在这里面写的doGET方法就是向List中获取里面的所有表白数据,doPOST方法就是客户端点击提交按钮之后,发送了一条表白数据,服务器收到数据之后,创建对应的对象,添加到list里面;
- <div class="father">
- <h1>表白墙</h1>
- <p>输入点击后提交,结果将显示在墙上</p>
- <form action="message" method="post">
- <div class="line">
- <span>谁</span><input type="text" name="from">
- </div>
- <div class="line">
- <span>对谁说</span><input type="text" name="to">
- </div>
- <div class="line">
- <span>说什么</span>
- <input type="text" name="message">
- </div>
- <div class="but">
- <input type="submit" value="提交" class="submit">
- </div>
- </form>
- <!-- 添加模板这里的变量,每一个line都是表白墙上面的消息 -->
- <div class="line" th:each="message:${messages}">
- <span th:text="${message.from}"></span>对
- <span th:text="${message.to}"></span>说
- <span th:text="${message.message}"></span>
- </div>
注意://处理请求的时候,也需要给请求的对象设置字符集,因为POST请求报文body格式是application/x-www......,所以说服务器在获取到请求中的内容也是URL-Encode的结果(不知道原来是啥样的),Servlet也是不知道这个encode的结果是按照UTF-8还是其他的字符集来进行编码的
也就是说服务器在拿到数据的时候是按照utf-8的格式来进行解析的,发送的时候也要设置成utf-8的形式
- //先进行初始化TemplateEngine
- import org.thymeleaf.TemplateEngine;
- import org.thymeleaf.templateresolver.ServletContextTemplateResolver;
-
- import javax.servlet.ServletContext;
- import javax.servlet.ServletContextEvent;
- import javax.servlet.ServletContextListener;
- import javax.servlet.annotation.WebListener;
-
- @WebListener
- public class Start implements ServletContextListener {
- @Override
- public void contextInitialized(ServletContextEvent servletContextEvent) {
- //1创建templateEngin对象,用于最终的渲染操作,并且根据这个方法的参数获取到ServletContext对象
- TemplateEngine engine=new TemplateEngine();
- ServletContext context=servletContextEvent.getServletContext();
- //2创建模板解析器对象,进行设置前缀和后缀以及字符编码方式
- ServletContextTemplateResolver resolver=new ServletContextTemplateResolver(context);
- resolver.setPrefix("/WEB-INF/template/");
- resolver.setSuffix(".html");
- resolver.setCharacterEncoding("utf-8");
- //3将汽车和加油器给进行关联到一起,将resolver对象和engine给关联到一起;
- engine.setTemplateResolver(resolver);
- //4向ServletContext中设置字符串,方便后面的Servlet进行使用的时候可以访问到TemplateEngine
- context.setAttribute("engine",engine);
- }
-
- @Override
- public void contextDestroyed(ServletContextEvent servletContextEvent) {
-
- }
- }
- //正式处理两个请求
- import org.thymeleaf.TemplateEngine;
- import org.thymeleaf.context.WebContext;
-
- import javax.servlet.ServletContext;
- import javax.servlet.ServletException;
- import javax.servlet.annotation.WebServlet;
- import javax.servlet.http.HttpServlet;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import java.io.IOException;
- import java.util.ArrayList;
- import java.util.List;
-
- class JsonData{
- public String from;
- public String to;
- public String message;
- }
- @WebServlet("/message")
- public class HelloServlet extends HttpServlet {
- List<JsonData> list=new ArrayList<>();
- //先进行初始化数据
- @Override
- public void init() throws ServletException {
- JsonData jsonData=new JsonData();
- jsonData.from="黑猫";
- jsonData.to="白猫";
- jsonData.message="喵";
- list.add(jsonData);
- }
-
- @Override
- protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
- //1先进行设置返回的字符集类型
- resp.setContentType("text/html;charset=utf-8");
- //2获取到一个web-app特有的ServletContext对象
- ServletContext context=req.getServletContext();
- //3取出之前进行初始化过的TemplateEngine对象
- TemplateEngine engine=(TemplateEngine)context.getAttribute("engine");
- //4创建WebContext对象,里面主要设置的是键值对
- WebContext webContext=new WebContext(req,resp,context);
- webContext.setVariable("messages",list);//相当于是返回一个数组
- //5最终进行渲染操作
- String html= engine.process("hello",webContext);
- resp.getWriter().write(html);
- }
-
- @Override
- protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
- //处理请求的内容,将读到的内容进行解析,得到to,from,message,并创建出JsonData对象,并将它插入到List里面
- resp.setContentType("text/html;charset=utf-8");
-
- req.setCharacterEncoding("utf-8");
- //1先进行获取POST请求中的参数
- String from=req.getParameter("from");
- String to=req.getParameter("to");
- String message=req.getParameter("message");
- //2向list中添加数据
- JsonData json=new JsonData();
- json.from=from;
- json.to=to;
- json.message=message;
- list.add(json);
- //3进行模板渲染,进行返回
- ServletContext context=req.getServletContext();
- TemplateEngine engine=(TemplateEngine)context.getAttribute("engine");
- WebContext webContext=new WebContext(req,resp,context);
- webContext.setVariable("messages",list);
- String html=engine.process("hello",webContext);
- resp.getWriter().write(html);
- }
- }
- POST http://127.0.0.1:8080/Java200/message HTTP/1.1
- Host: 127.0.0.1:8080
- Connection: keep-alive
- Content-Length: 90
- Cache-Control: max-age=0
- sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="8"
- sec-ch-ua-mobile: ?0
- Upgrade-Insecure-Requests: 1
- Origin: http://127.0.0.1:8080
- Content-Type: application/x-www-form-urlencoded
- User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 SLBrowser/8.0.0.5261 SLBChan/103
- Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
- Sec-Fetch-Site: same-origin
- Sec-Fetch-Mode: navigate
- Sec-Fetch-User: ?1
- Sec-Fetch-Dest: document
- Referer: http://127.0.0.1:8080/Java200/message
- Accept-Encoding: gzip, deflate, br
- Accept-Language: zh-CN,zh;q=0.9
- Cookie: JSESSIONID=0E8B44B53CF17B1C314DD128BB217721
-
- from=%E6%9D%8E%E5%98%89%E6%AC%A3&to=%E6%9D%8E%E4%BD%B3%E4%BC%9F&message=%E5%A4%A7%E5%93%A5
- 这些都是URLEncode的结果
- 响应报文是这样的
- HTTP/1.1 200
- Content-Type: text/html;charset=utf-8
- Content-Length: 2566
- Date: Fri, 01 Jul 2022 13:03:57 GMT
- Keep-Alive: timeout=20
- Connection: keep-alive
-
-
- <!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>Document</title>
- </head>
- <body>
- <div class="father">
- <h1>表白墙</h1>
- <p>输入点击后提交,结果将显示在墙上</p>
- <form action="message" method="post">
- <div class="line">
- <span>谁</span><input type="text" name="from">
- </div>
- <div class="line">
- <span>对谁说</span><input type="text" name="to">
- </div>
- <div class="line">
- <span>说什么</span>
- <input type="text" name="message">
- </div>
- <div class="but">
- <input type="submit" value="提交" class="submit">
- </div>
- </form>
- <!-- 添加模板这里的变量,每一个line都是表白墙上面的消息 -->
- <div class="line">
- <span>黑猫</span>对
- <span>白猫</span>说
- <span>喵</span>
- </div>
- <div class="line">
- <span>黑狗</span>对
- <span>母狗</span>说
- <span>汪汪</span>
- </div>
- <div class="line">
- <span>黑猫</span>对
- <span>白猫</span>说
- <span>呵呵</span>
- </div>
- <div class="line">
- <span>李嘉欣</span>对
- <span>李佳伟</span>说
- <span>大哥</span>
- </div>
- </body>
-
- <style>
- .father{
- background-color:green;
- width:60%;
- margin:0 auto;
- box-sizing:border-box;
- }
- h1{
- /* 这里把h2标签当成一个字体,让字体水平居中,就要用到text-align */
- text-align: center;
- /* 这是设置内边距,也就是说设置字体和背景颜色顶部的距离 */
- padding:5px 0;
- }
- p{
- /* 控制P标签里面的字水平居中 */
- text-align:center;
- padding:5px 0;
- }
- .line{
- /* 设置每一行的元素,水平居中,垂直居中 */
- height:50px;
- display:flex;
- justify-content:center;
- align-items:center;
- }
- span{
- font-size:25px;
- width:120px;
- }
- input{
- height:25px;
- }
- .but{
- display:flex;
- }
- .submit{
- margin:0 auto;
- width:200px;
- }
- </style>
-
- </script>
- </body>
- </html>
1)我们在这里面要注意目录结构:webapp里面存放image目录,里面包含着若干张图片;webapp/WEB-INF里面含有这需要进行渲染的原HTML代码文件,以及有它同级的CSS文件
2)我们在这里面只需写出后端代码即可(CSS样式不需要进行关心,和我们的后端代码没有任何关系),我们只需要写出服务器的代码即可,我们此处指需要进行关心的是我们要给前端页面通过模板引擎传入一个images数组,images数组里面的每一个元素是一个image对象,里面包含名字和src两个属性;
我们在这里面给的指定目录就是/WEB-INF/image目录,看看有多少个图片构成List<Image>
1)我们需要扫描文件路径,我们再进行扫描的时候,如何根据webapp中的image目录得到磁盘目录呢?
我们就需要进行调用String path=context.getRealPath("/image");这事把web-app中的路径,转换成在磁盘上面的文件路径
2)根据这个路径再扫描的过程中,看看有哪些是图片文件
前端代码: