JavaWeb-汇总


前言

本篇是我自己总结的 Java-学习路线 中的《Java-Web》的汇总,由于这部分知识我之前学过一部分所以只会更新需要复习的知识和没学过的知识,这个章节会作为长期更新的一个章节,部分知识点用到了再学,学了再更。

前端三剑客

小简笔记链接:Web前端三剑客 | 简简 (jwt1399.top)

Maven

小简笔记链接:Maven | 简简(jwt1399.top)

Tomcat

Tomcat使用

小简笔记链接:Tomcat使用 | 简简 (jwt1399.top)

Tomcat类加载

Tomcat 服务器如果要同时运行多个 Web 应用程序,那么就必须要实现不同应用程序之间的隔离。

  • Tomcat 需要分别去加载不同应用程序的类以及依赖,还必须保证应用程序之间的类无法相互访问,而传统的类加载机制无法做到这一点
  • 而且每个应用程序都有自己的依赖,如果两个应用程序使用了同一个版本的同一个依赖,那么还有必要去重新加载吗?

为了解决上述问题,Tomcat 服务器编写了一套自己的类加载机制。

首先我们要知道,Tomcat 本身也是一个 Java 程序,它要做的是去动态加载我们编写的 Web 应用程序中的类,而要解决以上提到的一些问题,就出现了几个新的类加载器,我们来看看各个加载器的不同之处:

加载器 描述
Common ClassLoader Tomcat最基本的类加载器。加载路径中的class可以被Tomcat容器本身以及各个Web应用程序访问。
Catalina ClassLoader Tomcat容器私有的类加载器。加载路径中的class对于Web应用程序不可见。
Shared ClassLoader 各个Web应用程序共享的类加载器。加载路径中的class对于所有Web应用程序可见,但是对于Tomcat容器不可见。
Webapp ClassLoader 各个Web应用程序私有的类加载器。加载路径中的class只对当前Web应用程序可见,每个Web应用程序都有一个自己的类加载器,此加载器可能存在多个实例。
JasperLoader JSP类加载器。每个JSP文件都有一个自己的类加载器,也就是说,此加载器可能会存在多个实例。

通过这样进行划分,就很好地解决了我们上面所提到的问题,但是我们发现,这样的类加载机制,破坏了JDK 的双亲委派机制,比如 Webapp ClassLoader,它只加载自己的 class 文件,它没有将类交给父类加载器进行加载,也就是说,我们可以随意创建和 JDK 同包同名的类,岂不是就出问题了?难道Tomcat的开发团队没有考虑到这个问题吗?

实际上,WebApp ClassLoader 的加载机制是这样的:WebApp ClassLoader 加载类的时候,绕开了 App ClassLoader,直接先使用 Ext ClassLoader 来加载类。这样的话,如果定义了同包同名的类,就不会被加载,而如果是自己定义的类,由于该类并不是 JDK 内部或是扩展类,所有不会被加载,而是再次回到 WebApp ClassLoader 进行加载,如果还失败,再使用 AppClassloader 进行加载。

Servlet

小简笔记链接:Servlet | 简简 (jwt1399.top)

小简笔记链接:Cookie & Session | 简简

Filter

Filter简介

有了 Session 之后,就可以很好地控制用户的登陆验证了,只有授权的用户,才可以访问一些页面,但是我们需要一个一个去进行配置,还是太过复杂,能否一次性地过滤掉没有登录验证的用户呢?

Filter过滤器相当于在所有访问前加了一堵墙,来自浏览器的所有访问请求都会首先经过Filter过滤器,只有过滤器允许通过的请求,才可以顺利地到达对应的Servlet,而过滤器不允许的通过的请求,我们可以自由地进行控制是否进行重定向或是请求转发。并且过滤器可以添加很多个。

创建过滤器

定义类,实现 Filter 接口,并重写其方法,并添加@WebFilter注解

@WebFilter("/*")   //配置Filter拦截资源的路径,/* 表示拦截所有的资源
public class TestFilter1 implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        
    }
}

现在我们发起的所有请求,一律需要经过此过滤器,并且所有的请求都没有任何的响应内容。

那么如何让请求可以顺利地到达对应的 Servlet,只需要添加一句放行语句

filterChain.doFilter(servletRequest, servletResponse);//放行,,也就是让其访问本该访问的资源。

由于我们整个应用程序可能存在多个过滤器,那么这行代码的意思实际上是将此请求继续传递给下一个过滤器,当没有下一个过滤器时,才会到达对应的Servlet进行处理,我们可以再来创建一个过滤器看看效果

@WebFilter("/*")
public class TestFilter2 implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("我是2号过滤器");
        filterChain.doFilter(servletRequest, servletResponse);
    }
}

过滤器链

过滤器链是指在一个Web应用,可以配置多个过滤器,过滤器的过滤顺序是按照类名的自然排序进行的,在经过第一个过滤器之后,会继续前往第二个过滤器,只有两个过滤器全部经过之后,才会到达我们的Servlet中。

实际上,当doFilter方法调用时,就会一直向下直到Servlet,在Servlet处理完成之后,又依次返回到最前面的Filter,类似于递归的结构,我们添加几个输出语句来判断一下:

@WebFilter("/*") 
public class TestFilter1 implements Filter {
  @Override
  public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException     {
    System.out.println("我是1号过滤器放行前逻辑代码");
    filterChain.doFilter(servletRequest, servletResponse);
    System.out.println("我是1号过滤器放行后逻辑代码");
  }
}
@WebFilter("/*") 
public class TestFilter2 implements Filter {
@Override
  public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    System.out.println("我是2号过滤器放行前逻辑代码");
    filterChain.doFilter(servletRequest, servletResponse);
    System.out.println("我是2号过滤器放行后逻辑代码");
  }
}
//输出
我是1号过滤器放行前逻辑代码
我是2号过滤器放行前逻辑代码
我是1号过滤器放行后逻辑代码
我是2号过滤器放行后逻辑代码

HttpFilter

同 Servlet 一样,Filter 也有对应的 HttpFilter 专用类,它针对 HTTP 请求进行了专门处理,因此我们可以直接继承 HttpFilter 来编写

@WebFilter("/*")
public class MainFilter extends HttpFilter {
    @Override
    protected void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
        //...
    }
}

Listener

如果我们希望,在应用程序加载的时候,或是 Session 创建的时候,亦或是在 Request 对象创建的时候进行一些操作,那么这个时候,我们就可以使用监听器来实现。

默认为我们提供了很多类型的监听器,我们这里就演示一下监听Session的创建即可:

@WebListener
public class TestListener implements HttpSessionListener {
    @Override
    public void sessionCreated(HttpSessionEvent se) {
        System.out.println("有一个Session被创建了");
    }
}

有关监听器相关内容,了解即可。

JSP

小简笔记链接:JSP学习笔记 | 简简 (jwt1399.top)

Thymeleaf

简介

Thymeleaf 是一个适用于 Web 和独立环境的现代化服务器端 Java 模板引擎

模板引擎是为了使用户界面与业务数据分离而产生的,它可以生成特定格式的文档,用于网站的模板引擎就会生成一个标准的 html 文档。

快速入门

1、首先还是新建一个 Web 项目,在创建时勾选 Thymeleaf 依赖

2、编写一个前端页面,名称为test.html,放在 resource 目录下,在 html 标签内部添加xmlns:th="http://www.thymeleaf.org"引入 Thymeleaf 定义的标签属性

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div th:text="${title}"></div>
</body>
</html>

3、编写模板引擎,设置默认页面

@WebServlet("/index")
public class HelloServlet extends HttpServlet {
    TemplateEngine engine;
    @Override
    public void init() throws ServletException {
        engine = new TemplateEngine();
        ClassLoaderTemplateResolver r = new ClassLoaderTemplateResolver();
        engine.setTemplateResolver(r);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Context context = new Context();
        context.setVariable("title", "我是标题");
        engine.process("test.html", context, resp.getWriter());
    }
}

我们发现,浏览器得到的页面,就是已经经过模板引擎解析好的页面,而我们的代码依然是后端处理数据,前端展示数据,因此使用 Thymeleaf 就能够使得当前Web应用程序的前后端划分更加清晰。

语法基础

TemplateEngine

首先我们看看后端部分,我们需要通过TemplateEngine对象来将模板文件渲染为最终的HTML页面:

TemplateEngine engine;
@Override
public void init() throws ServletException {
    engine = new TemplateEngine();
      //设定模板解析器决定了从哪里获取模板文件,这里直接使用ClassLoaderTemplateResolver表示加载内部资源文件
    ClassLoaderTemplateResolver r = new ClassLoaderTemplateResolver();
    engine.setTemplateResolver(r);
}

此对象只需要创建一次,之后就可以一直使用了。接着我们来看如何使用模板引擎进行解析:

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    //创建上下文,上下文中包含了所有需要替换到模板中的内容
    Context context = new Context();
    context.setVariable("title", "<h1>我是标题</h1>");
    //通过此方法就可以直接解析模板并返回响应
    engine.process("test.html", context, resp.getWriter());
}

接下来就可以在前端页面中通过上下文提供的内容,来将Java代码中的数据解析到前端页面。

常用标签

标签 作用 示例
th:id 替换id <input th:id="${user.id}"/>
th:text 文本替换 <p text:="${user.name}">测试</p>
th:utext 支持html的文本替换 <p utext:="${htmlcontent}">content</p>
th:object 替换对象 <div th:object="${user}"></div>
th:value 替换值 <input th:value="${user.name}" >
th:each 迭代 <tr th:each="student:${user}" >
th:href 替换超链接 <a th:href="@{index.html}">超链接</a>
th:src 替换资源 <script type="text/javascript" th:src="@{index.js}"></script>

比如示例中编写的:

<div th:text="${title}"></div>

使用了th:text来为当前标签指定内部文本,注意任何内容都会变成普通文本,即使传入了一个HTML代码

如果我希望向内部添加一个HTML文本呢?我们可以使用th:utext属性:

<div th:utext="${title}"></div>

传入的title属性,不仅仅只是一个字符串的值,而且是一个字符串的引用,我们可以直接通过此引用调用相关的方法:

<div th:text="${title.toLowerCase()}"></div>

这样看来,Thymeleaf既能保持JSP为我们带来的便捷,也能兼顾前后端代码的界限划分。

除了替换文本,它还支持替换一个元素的任意属性,我们发现,th:能够拼接几乎所有的属性,一旦使用th:属性名称,那么属性的值就可以通过后端提供了,比如我们现在想替换一个图片的链接:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <img width="700" th:src="${url}" th:alt="${alt}">
</body>
</html>
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    Context context = new Context();
    context.setVariable("url", "https://jwt1399.top/favicon.jpg");
      context.setVariable("alt", "图片加载不出来");
    engine.process("test.html", context, resp.getWriter());
}

Thymeleaf还可以进行一些算术运算,几乎Java中的运算它都可以支持:

<div th:text="${value % 2}"></div>

同样的,它还支持三元运算:

<div th:text="${value % 2 == 0 ? 'yyds' : 'lbwnb'}"></div>

多个属性也可以通过+进行拼接,就像Java中的字符串拼接一样,这里要注意一下,字符串不能直接写,要添加单引号:

<div th:text="${name}+' 我是文本 '+${value}"></div>

表达式

链接表达式: @{…}

如果想引入链接比如link,href,src,使用@{资源地址}引入资源

<link rel="stylesheet" th:href="@{index.css}">

<script type="text/javascript" th:src="@{index.js}"></script>

<a th:href="@{index.html}">超链接</a>
变量表达式: ${…}

通过${…}进行取值

<h2>取普通字符串</h2>
<table border="0">
    <tr>
        <td th:text="'我的名字是:'+${name}"></td>
    </tr>
</table>


<h2>取JavaBean对象</h2>
<table bgcolor="#ffe4c4" border="1">
    <tr>
        <td>介绍</td>
        <td th:text="${user.name}"></td>
    </tr>
    <tr>
        <td>年龄</td>
        <td th:text="${user['age']}"></td>
    </tr>
</table>

<h2>取List取值</h2>
<table bgcolor="#ffe4c4" border="1">
    <tr th:each="item:${userlist}">
        <td th:text="${item}"></td>
    </tr>
</table>

<h2>取Map取值</h2>
<table bgcolor="#8fbc8f" border="1">
    <tr>
        <td>place:</td>
        <td th:text="${map.get('place')}"></td>
    </tr>
    <tr>
        <td>feeling:</td>
        <td th:text="${map['feeling']}"></td>
    </tr>
</table>
选择变量表达式: *{…}

变量表达式不仅可以写成${…},而且还可以写成*{…}。

但是,有一个重要的区别:星号语法针对选定对象而不是整个上下文评估表达式。也就是说,只要没有选定的对象,美元(${…})和星号(*{...})的语法就完全一样。

什么是选定对象?使用th:object属性的表达式的结果。就可以选定对象,具体实例如下:

<div th:object="${user}">
    <p>Name: <span th:text="*{name}">小明</span></p>
    <p>Age: <span th:text="*{age}">18</span></p>
    <p>Detail: <span th:text="*{detail}">好好学习</span>.</p>
</div>

当然*{…}也可和${…}混用。上面的代码如果不使用选定对象,完全等价于:

<div>
    <p>Name: <span th:text="*{user.name}">小明</span>.</p>
    <p>Age: <span th:text="${user.age}">18</span>.</p>
    <p>Detail: <span th:text="${user.detail}">好好学习</span>.</p>
</div>
消息表达: #{…}

#{…}是用来读取配置文件中数据的。

<h2>消息表达</h2>
<table bgcolor="#ffe4c4" border="1">
    <tr>
    <td>name</td>
    <td th:text="#{jwt.name}"></td>
    </tr>
    <tr>
    <td>年龄</td>
    <td th:text="#{jwt.age}"></td>
    </tr>
    <tr>
    <td>province</td>
    <td th:text="#{province}"></td>
    </tr>
</table>

test.properties

jwt.nane=jianjian
jwt.age=20

流程控制语法

除了一些基本的操作,我们还可以使用Thymeleaf来处理流程控制语句,当然,不是直接编写Java代码的形式,而是添加一个属性即可。

th:if

如果 if 条件满足,则此标签留下;若 if 条件不满足,则此标签自动被移除

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    Context context = new Context();
    context.setVariable("eval", true);
    engine.process("test.html", context, resp.getWriter());
}
<div th:if="${eval}">我是判断条件标签</div>

th:if会根据其中传入的值或是条件表达式的结果进行判断,只有满足的情况下,才会显示此标签,具体的判断规则如下:

  • 如果值不是空的
    • 值是布尔值并且为true
    • 值是一个数字,并且是非零
    • 值是一个字符,并且是非零
    • 值是一个字符串,而不是“错误”、“关闭”或“否”
    • 值不是布尔值、数字、字符或字符串
  • 如果值为空,th:if 将计算为 false

th:unless

th:unlessth:if 效果完全相反

th:switch

多分支条件判断,可以使用th:switch属性来实现

<div th:switch="${eval}">
    <div th:case="1">我是1</div>
    <div th:case="2">我是2</div>
    <div th:case="3">我是3</div>
</div>

th:case

多分支条件判断没有default属性,可以使用th:case="*"来代替:

<div th:case="*">我是Default</div>

th:each

实现遍历,假如我们有一个存放书籍信息的List需要显示,那么如何快速生成一个列表呢?我们可以使用th:each来进行遍历操作:

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    Context context = new Context();
    context.setVariable("list", Arrays.asList("伞兵一号的故事", "倒一杯卡布奇诺", "玩游戏要啸着玩", "十七张牌前的电脑屏幕"));
    engine.process("test.html", context, resp.getWriter());
}
<ul>
    <li th:each="title : ${list}" th:text="''+${title}+''"></li>
</ul>

th:each语法: “单个元素名称 : ${列表}“,最后生成的结果为:

<ul>
  <li>《伞兵一号的故事》</li>
  <li>《倒一杯卡布奇诺》</li>
  <li>《玩游戏要啸着玩》</li>
  <li>《十七张牌前的电脑屏幕》</li>
</ul>

我们还可以获取当前循环的迭代状态,只需要在 th:each 中添加 iterStat 即可,从中可以获取很多信息,比如当前的顺序 ${iterStat.index}

<ul>
    <li th:each="title, iterStat : ${list}" th:text="${iterStat.index}+'.《'+${title}+''"></li>
</ul>

iterStat 属性有:

  • index:当前迭代索引,以0开头。
  • count:当前迭代索引,以1开头。
  • size:迭代变量中的元素总量。
  • current:每个迭代的迭代变量。
  • even/odd:当前迭代是偶数还是奇数。
  • first:当前迭代是否是第一个迭代。
  • last:当前迭代是否是最后一个迭代。

模板布局

在某些网页中,我们会发现,整个网站的页面,除了中间部分的内容会随着我们的页面跳转而变化外,有些部分是一直保持一个状态的。

Thymeleaf 可以轻松实现这样的操作,我们只需要将不会改变的板块设定为模板布局,并在不同的页面中插入这些模板布局,就无需每个页面都去编写同样的内容了。

模版页复用

1、编写一个模版页head.html,里面放每个页面公有的代码,例如每个页面主标题一样

<div class="head" th:fragment="head-title">
    <div>
        <h1>简简</h1>
    </div>
    <hr>
</div>

2、编写另一个页面使用模版页

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>主页</title>
</head>
<body>
    <div th:include="head.html::head-title"></div> <!--引入head页面-->
    <div>
        <h1>慢慢来,比较快</h1>
    </div>
</body>
</html>

我们可以使用th:insertth:replaceth:include这三种方法来进行页面内容替换,那么th:insertth:replaceth:include(3.0以来不推荐)有什么区别?

  • th:insert最简单:它只会插入指定的片段作为标签的主体。
  • th:replace实际上将标签直接替换为指定的片段。
  • th:includeth:insert相似,但它没有插入片段,而是只插入此片段的内容。

模版页参数传递

例如我们现在希望插入二级标题,二级标题的内容不是定死的,不同页面不一样

1、在二级标题处设置一个占位参数

<div class="head" th:fragment="head-title(sub)">
    <div>
        <h1>简简</h1>
        <h2 th:text="${sub}"></h2>
    </div>
    <hr>
</div>

2、使用模版页是顺带传入参数

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>主页</title>
</head>
<body>
    <div th:include="head.html::head-title('Web安全页')"></div> <!--传参-->
    <div>
        <h1>慢慢来,比较快</h1>
    </div>
</body>
</html>

Ajax & Axios & Json

小简笔记链接:Ajax & Axios & Json | 简简

Vue & Element

小简笔记链接:Vue & Element | 简简 (jwt1399.top)

❤️Sponsor

您的支持是我不断前进的动力,如果您恰巧财力雄厚,又感觉本文对您有所帮助的话,可以考虑打赏一下本文,用以维持本博客的运营费用,拒绝白嫖,从你我做起!🥰🥰🥰

支付宝支付 微信支付

文章作者: 简简
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 简简 !
评论
填上邮箱会收到评论回复提醒哦!!!
 上一篇
SpringMVC SpringMVC
今天开始学习我自己总结的 Java-学习路线 中的《SpringMVC》,小简从 0 开始学 Java 知识,并不定期更新所学笔记,期待一年后的蜕变吧!<有同样想法的小伙伴,可以联系我一起交流学习哦!> 📝学习资料:尚硅谷
2022-04-25
下一篇 
Vue & Element Vue & Element
Vue 是一套前端框架,免除原生 JavaScript 中的 DOM 操作,简化书写。 基于 MVVM(Model-View-View-Model) 思想,实现数据的双向绑定,将编程的关注点放在数据上。
2022-04-22
  目录