p2p|Servlet(三)------Javaweb

一: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)进行关联起来了;
1.ServletContext(上下文)的介绍 ServletContext是一个Servlet程序在全局储存信息的空间,服务器开始就存在,服务器关闭就销毁;每一个webapp就是tomact文件中的webapps对应的子目录;
这个类创建的初心:在Tomact启动的时候,他会给每一个webapp(一个文件目录)都提供创建了一个ServletContext对象,而每一个单独的web-app有多个servlet,而这里面的所有servlet对象都共享一个ServletContext对象,可以共享一部分数据;
在这里面,我们也是可以通过ServletContext实现让TemplateEngine在一个webapp中只有一个实例
一个Tomact是一个进程,而很多webapp是进程中的一部分,如果说把Template搞成单例模式的话,webapp1和webapp2之间就会进行相互影响了,每一个webapp都要通过自己的TemplateEngine对象,来进行加载属于自己的模板文件,他们之间是不可以相互影响的,所以说我们希望每一个webapp都有属于自己的TemplateEngine对象,但是我们又希望让同一个webapp中的多个Servlet之间可以共享自己的webapp中的TemplateEngine
p2p|Servlet(三)------Javaweb
文章图片


在之前我们所写的代码当中,每一个Servlet都有一个TemplateEngine,这显然是不太科学的,我们希望同一个webapp中可以共享一个TemplateEngine对象;
那么可以把TemplateEngine搞成单例模式吗?这显然是不行的,单例指的是在整个进程中只有一个实例,但是此处的TemplateEngine不应该是进程中的实例,一个Tomact就是一个进程,Template而应该是webapp级别的实例,为了做到这一点我们应该把TemplateEngine放到ServletContext中进行创建;
2.ServletContext的具体用法----ServletContext是Tomact的内部代码new出来的,咱们是看不到的; 它所存在的意义是可以让同一个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得到同一个上下文对象
下面我们来写一个实例:我们创建两个Servlet对象,一个是WriteServlet,向我们的web-app里面的ServletContext设置属性,另一个是ReadServlet,向我们的web-app里面的ServletContext来读取刚才设置的属性,先进行访问write再去访问read; 一:创建WriterServlet类 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的值设置进去;
3)是我们使用ReadServlet来从ServletContext来进行读取数据,就把刚才的WriteServlet存放的数据给取出来;
当前我们写的两个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("储存message成功"); } }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的初始化操作就行了,还可以进行初始化一些其他的操作和内容
所以说我们就可以把Thymeleaf的初始化,放到contextInitialized方法里面,这样就可以保证TemplateEngine是一个webapp级别的单例
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) {} }

四.实战案例----不用AJAX 1)表白墙: 在前面我们已经写了纯前端的页面版本,在后来我们又写了一个web版本(前后端进行交互的版本,前后端通过ajax的方式来发送json数据;现在我们基于一个模板引擎来进行实现;
我们要引入模板文件,我们要在webapp中创建WEB-INF目录,在这个目录里面创建一个目录叫做template目录,把我们表白墙的前端页面方法到这个目录里面;
1)在最外面我们要加一个form标签(里面包裹input标签),里面设置action属性,表示要跳转到那个界面,我们也是主要通过form表单来提交请求;
之前我们写前后端分离的表白墙项目的时候,三个标签里面的值在
在线相册项目: 1)我们在这里面要注意目录结构:webapp里面存放image目录,里面包含着若干张图片; webapp/WEB-INF里面含有这需要进行渲染的原HTML代码文件,以及有它同级的CSS文件
2)我们在这里面只需写出后端代码即可(CSS样式不需要进行关心,和我们的后端代码没有任何关系),我们只需要写出服务器的代码即可,我们此处指需要进行关心的是我们要给前端页面通过模板引擎传入一个images数组,images数组里面的每一个元素是一个image对象,里面包含名字和src两个属性;
3)这里面的关键要点:我们使用th:each来分别进行构造多个figure标签,最后把URL和name放到合适的位置上
4)初始化模板引擎,我们也是基于listener来进行实现的,在ServletContext中创建出一个TemplateEngine实例,以备后用;
5)写一个Servlet后端代码,写一个ImageServlet来进行展示图片界面,在这个后端代码里面,就要生成一个Image对象的数组,传给网页模板,并进行返回
6)生成images数组的方法,给定一个指定目录,我们就需要从这个目录来进行扫描,看看都有哪些图片,根据目录中的图片信息来进行构建出一个image对象的数组
我们在这里面给的指定目录就是/WEB-INF/image目录,看看有多少个图片构成List
1)我们需要扫描文件路径,我们再进行扫描的时候,如何根据webapp中的image目录得到磁盘目录呢?
我们就需要进行调用String path=context.getRealPath("/image"); 这事把web-app中的路径,转换成在磁盘上面的文件路径
2)根据这个路径再扫描的过程中,看看有哪些是图片文件
3)上传图片页面:支持我们用户自己上传图片,我们先自己写一个upload.html,再写一个UploadServlet来进行处理上传请求;
1)upload.html只是一个普通的静态页面,不用放到/WEB-INF/template里面,纯纯的静态页面应该放到webapp里面下的目录
2)访问模板引擎所渲染的页面的时候,直接进行访问127.0.0.1:8080/Java200/WEN-INF里面的文件.html
如果说直接访问模板引擎的页面直接进行访问127.0.0.1:8080/Java200/Java后端代码@webServlet修饰的注解


【p2p|Servlet(三)------Javaweb】

    推荐阅读