先说一下必要观念:

知道是什么, 知道怎么用, 遇到不会的回来查就是了, 不用记住, 就算你现在记住, 后面不用也会忘了

XML

extensible markup Language, 用来写配置文件的(配置较为负责且需要结构层次)

image-20240719175513159

之前用properties写配置文件(properties主要是放java的依赖), 格式是这样的

image-20240719180322096

现在用XML写, 样子是这样的

XML配置文件比properties文件更灵活和强大,适合用于需要层次结构和复杂配置的场景

Properties文件:简单的键值对格式,易于读取和写入,适用于简单的配置信息。

XML文件:结构化的标记语言,适用于复杂和层次化的配置,使用时需要解析器。

image-20240719180849982

XML解析

读取和写入XML文件通常需要使用DOM(文档对象模型)解析器或SAX(简单API for XML)解析器等工具。

通过DOM4J

使用步骤

  1. 导入jar包 dom4j.jar

  2. 创建解析器对象(SAXReader)

  3. 解析xml 获得Document对象

    读取xml 配置文件, 获得document对象, 需要用到SAXReader 类

  4. 获取根节点RootElement

    从document 对象上获取配置文件中的信息

  5. 获取根节点下的子节点

image-20240729203344125

public class test {
    @Test
    public void testRead() throws Exception{
        SAXReader saxReader = new SAXReader();

        //通过类加载器获得 指向字节码根路径下的 指定文件 输入流
        InputStream resourceAsStream = test.class.getClassLoader().getResourceAsStream("jdbc.xml");
        //通过输入流获得配置文件,解析成dom对象
        Document document = saxReader.read(resourceAsStream);

        //从document对象上获取配置文件中的信息
        /*
        * Node节点
            Element 例如<dev> <username>
            Attribute id="001"
            Text
        * */
        //获取root, 也就是<jdbc>
        Element rootElement = document.getRootElement();
        System.out.println(rootElement.getName());//应该输出 jdbc

        //获取元素下的子元素
        List<Element> elements = rootElement.elements();
        for(Element element : elements){
            System.out.println("\t"+element.getName());//应该输出 dev , test
            //从元素上获取属性
            Attribute idAttribute = element.attribute("id");
            //继续读取子元素
            List<Element> list = element.elements();
            for(Element ele: list){
                System.out.println(ele.getName() + ele.getText());
            }
        }
        //
    }
}

上面是知道xml里有哪些元素, 如果不知道怎么办? 递归

当然, 未来并不需要我们自己写......................大部分都是框架自带了

Tomcat 10

  • 干嘛的?

    是运行我们开发的app的环境之一, 是当前应用最广的JavaWeb服务器, 免费, 开源, 最新的Servlet 和JSP 规范总是能在Tomcat 中得到体现,

    往服务器上放app, app运行需要环境, 服务器上要装JRE, JRE里要装Tomcat(Springboot内置了tomcat, 所以可以直接扔JRE),

    image-20240729204634262

  • 安装

    官网下载包, 我放D盘Java里面去了

    点击bin/startup.bat启动, 打开浏览器输入 http://localhost:8080访问测试, 直接关闭cmd窗口或者运行 bin/shutdown.bat关闭tomcat

    注意: 环境变量(System变量)要配好, JAVA_HOME=D:\Java\jdk-17.0.4.1

目录介绍

如果有不清楚的部分请查阅原笔记, 因为我这里有删减

  • bin:放的是启动文件

  • conf:这是一个非常非常重要的目录,这个目录下有四个最为重要的文件:

    • server.xml:配置整个服务器信息。例如修改端口号。默认HTTP请求的端口号是:8080

    • tomcat-users.xml:存储tomcat用户的文件,这里保存的是tomcat的用户名及密码,以及用户的角色信息。可以按着该文件中的注释信息添加tomcat用户,然后就可以在Tomcat主页中进入Tomcat Manager页面了;

    • web.xml:部署描述符文件,这个文件中注册了很多MIME类型,即文档类型。这些MIME类型是客户端与服务器之间说明文档是什么类型的, html我就展示, ,

      如用户请求一个html网页,那么服务器还会告诉客户端浏览器响应的文档是text/html类型的,这就是一个MIME类型。客户端浏览器通过这个MIME类型就知道如何处理它了。

      当然是在浏览器中显示这个html文件了。但如果服务器响应的是一个exe文件,那么浏览器就不可能显示它,而是应该弹出下载窗口才对。

      MIME就是用来说明文档的内容是什么类型的!

    • context.xml:对所有应用的统一配置,通常我们不会去配置它。

  • lib:Tomcat的类库,里面是一大堆jar文件。如果需要添加Tomcat依赖的jar文件,可以把它放到这个目录中,

    不要把当前项目的dependence都放到这里面, 这个目录中的jar所有项目都可以共享之,但这样你的应用放到其他Tomcat下时就不能再共享这个目录下的jar包了,所以建议只把Tomcat需要的jar包放到这个目录下;

  • logs:这个目录中都是日志文件,记录了Tomcat启动和关闭的信息,如果启动Tomcat时有错误,那么异常也会记录在日志文件中。

  • temp:存放Tomcat的临时文件,这个目录下的东西可以在停止Tomcat后删除!

  • webapps:存放web项目的目录,其中每个文件夹都是一个项目

    如果这个目录下已经存在了目录,那么都是tomcat自带的项目。其中ROOT是一个特殊的项目,在地址栏中访问:http://127.0.0.1:8080,没有给出项目目录时,对应的就是ROOT项目.

    http://localhost:8080/examples,进入示例项目。其中examples"就是项目名,即文件夹的名字。

    部署目录和访问路径可以是不一样的

    image-20240805163439856

  • work:和JSP (java server page)技术有关, 现在已经不太用了. 运行时生成的文件,最终运行的文件都在这里。通过webapps中的项目生成的!可以把这个目录下的内容删除,再次运行时会生再次生成work目录。当客户端用户访问一个JSP文件时,Tomcat会通过JSP生成Java文件,然后再编译Java文件生成class文件,生成的java和class文件都会存放到这个目录下。

  • LICENSE:许可证。

  • NOTICE:说明文件。

WebApp 的标准结构

看懂就行, 需要烂熟于心, 不明白的到时候再回来查,不用记住

一个 standard 可以用于发布的 WEB project 结构如下

image-20240805163619216

  • static 非必要目录,一般在此处放静态资源 ( css js img), 可以浏览器直接路径访问
  • WEB-INF 必要, 必须叫WEB-INF,protected resource受保护的资源目录,浏览器通过url不可以直接访问的目录
    • classes 必要,src下源代码,配置文件,编译后会在该目录下, web项目中如果没有源码,则该目录不会出现
    • lib 必要, 项目依赖的jar包编译后会出现在该目录下, web项目要是没有依赖任何jar包,则该目录不会出现
    • web.xml 必要,web项目的基本配置文件. 较新的版本中可以没有该文件,但是学习过程中还是需要该文件
  • index.html 非必要,index.html/index.htm/index.jsp为默认的欢迎页

url的组成部分和项目中资源的对应关系

image-20240805165232002

部署 project 的方式

  • 方式1 直接将编译好的项目扔在webapps目录下 (已经演示)

  • 方式2 将编译好的项目打成war包放在webapps目录下,tomcat启动后会自动解压war包(其实和第一种一样)

    怎么打成war包?

  • 方式3 可以将项目放在非webapps的其他目录下,在tomcat中通过配置文件(xml)指向app的实际磁盘路径

在磁盘的自定义目录上准备一个app

1681456447284

在tomcat的conf下创建Catalina/localhost目录,并在该目录下准备一个app.xml文件

<!-- 
	path: 项目的访问路径,也是项目的上下文路径,就是在浏览器中,输入的项目名称
    docBase: 项目在磁盘中的实际路径
 -->
<Context path="/app" docBase="D:\mywebapps\app" />
  • 启动tomcat访问测试即可

idea如何部署 - 三步走

学习: 如何用idea 创建一个javaweb project, 并且转成一个可以发布的项目, 然后放到tomcat里面来启动运行

idea的作用: javaweb project -> (build 成可以发布的)app -> (部署到 deployment) tomcat

  1. 添加 tomcat dependencies

(File -> project sturcture -> modules -> dependencies -> 点击 + -> 选择library -> 选择 tomcat)

  1. 为项目添加web资源组件

(右键项目 -> add framework support -> 选择 web)

  1. 使用tomcat部署

(运行那里 -> edit configuration -> depoyment -> 点击 + -> 选择当前module)

步骤

ps 步骤找不到的去看原笔记

image-20240806150110832

image-20240806150315334

创建 Java Web Project

  • 右键 new 的是module

  • 添加一些tomcat依赖

    找到project structure, 选中这个module, 在dependence里面添加

    image-20240806151434478

    • add framework support

    image-20240806151534917

    然后选择web application, version 为5.0

image-20240806151622622

多了一个web文件夹, 现在这个项目是一个web项目了, 我们在这开发代码, build之后才会成为app

一些注意事项

  1. 配置文件放哪里?
  • 创建resources目录,专门用于存放配置文件(都放在src下也行,单独存放可以尽量避免文件集中存放造成的混乱)

  • 标记目录为Resources root ,不标记的话则该目录不参与编译

    image-20240806152621590

  1. jar 包如何处理

    别忘记 jar包是放在 WEB-INF 目录下的

    • 在WEB-INF下创建lib目录
    • 必须在WEB-INF下,且目录名必须叫lib!!!
    • 复制jar文件进入lib目录
    • 将lib目录添加为当前项目的依赖(右键lib->add as library, 选择 module Library),后续可以用maven统一解决

Build as an App

点菜单栏的build, 选择build artifacts

其实这一步可以省略, 当我们选择用tomcat run的时候, 它会自动帮我build 并 run

image-20240806153346928

这个war文件就是上面的web文件夹

(我这里没有static因为我上面的static目录里面没放东西)

image-20240806153553827

这里的classes 是 src 和 resource 共同编译后的结果

Deploy App

image-20240806155556907

在server里面可以自己配置, 我就不截图了, 然后点击 Run!

image-20240806160126867

原理

tomcat 复制了一份自己, 在副本里deploy project, 日志里面有写

Using CATALINA_BASE: "C:\Users\Gintoki\AppData\Local\JetBrains\IntelliJIdea2022.2\tomcat\590b9067-2d37-4784-84e9-2d1ca0cb6067"....

但这个副本并不是一个完整的tomcat

  • idea启动tomcat时,是让本地tomcat 按 副本里的configuration运行

  • idea的tomcat副本 deploy project 是通过conf/Catalina/localhost/*.xml配置文件的形式实现deploy的

    <Context path="/web01" docBase="F:\FrontCode\JavaWeb\web-tomcat\out\artifacts\demo01_web01_war_exploded"/>
    

image-20240806160827445

Servlet

前后端交互的接口

  • 首先区分动态资源和静态资源

    去蛋糕店买蛋糕

    • 直接买柜台上已经做好的 : 静态资源
    • 和柜员说要求后现场制作 : 动态资源

    动态: 需要在程序运行时通过代码运行生成的资源,在程序运行之前无法确定的数据,运行时动态生成,例如Servlet,Thymeleaf ... ...

1. Intro

Servlet (server applet) 是运行在服务端(tomcat)的Java小程序,是sun公司提供一套定义动态资源规范; 从代码层面上来讲Servlet就是一个接口

是用来接收、处理客户端请求、响应给浏览器的动态资源。

image-20240808143823708

  • 不是所有的JAVA类都能用于处理客户端请求,能处理客户端请求并做出响应的一套技术标准就是Servlet, 把Servlet称为Web应用中的控制器

  • Servlet是运行在服务端的,所以 Servlet必须在WEB项目中开发且在Tomcat这样的服务容器中运行

使用: 我们需要自己定义一个类, 实现Sevlet接口, 要重写service方法(Tomcat自动会调用, 要指定访问路径)

image-20240808144345683

2. 开发流程

html表单向某个路径(Servlet的xml中自己定义) 发送请求

image-20240810113706982

也就是说, 如果你建了很多个servlet, 写了很多个service方法, 并且在xml里配置了不同service 的路径, 那么也就意味着, 我不同的路径可以跳转到不同的页面去(或者说执行不同的操作)

需求: 校验注册时,用户名是否被占用. 通过客户端向一个Servlet发送请求,携带username,如果用户名是'atguigu',则向客户端响应 NO,如果是其他,响应YES

一共三个页面, html, UserServlet类, web.xml

这里的action="这里写的是xml中的url-pattern, 没有带 / "

image-20240808170004449

image-20240808170041971

白雪王子警告:下面的配置路径后期可以一个注解搞定,此处了解即可

在xml中配置路径

image-20240808170058149

image-20240810113944092

3. jar包导入问题 和 content -type

如果看不懂在写什么, 说明没必要懂,等需要的时候自己去看视频 again

image-20240810120323332

在关联tomcat的时候自动导入了这个jar包(servlet-api), 参与编译不参与打包

请求serlvet的时候, content-type没有了(因为没有请求静态资源, tomcat在web.xml中找不到对应的MIME, 所以没放在content-type里面)

所以我们应该自己设content-type 的 response head, 你可以告诉浏览器, 这个东西是什么类型的(浏览器就会进行相应的处理)

Multipurpose Internet Mail Extensions(MIME) indicates the nature and format of a document

image-20240810120228816

4. url-pattern

  • 基本知识

回顾: 如果你建了很多个servlet, 写了很多个service方法, 并且在 xml 里配置了不同 servlet 的路径, 那么也就意味着, 我不同的路径可以跳转到不同的页面去(或者说执行不同的操作), 那么具体工作原理见下:

image-20240810143641175

一个servlet-name可以同时对应多个url pattern

一个 servlet 标签可以对应多个 servlet-mapping标签(但其实也没必要...)

image-20240810143924337

  • 路径匹配

    有精确的, 模糊的...

    • / 匹配全部,不包括jsp文件,

      无论请求什么路径(除jsp), 都匹配这个servlet, 想访问jsp页面, 直接路径输入 xxx.jsp, 可以访问到

    • /* 匹配全部,包括jsp文件, 想访问jsp页面, 无论输什么, 都匹配这个servlet

    • /a/* 匹配所有以a前缀的路径 (后缀随便写, 都能匹配上这个servlet)

    • *.action 匹配所有以action为后缀的映射路径, 例如访问'demo02/a/c/s/xxx.action'

JSP: 它使用JSP标签在HTML网页中插入Java代码。标签通常以<%开头以%>结束。JSP是一种Java servlet,主要用于实现Java web应用程序的用户界面部分。网页开发者们通过结合HTML代码、XHTML代码、XML元素以及嵌入JSP操作和命令来编写JSP。

JSX: 是JavaScript 和HTML 结合的模板语法(就是允许你在 JavaScript 代码中写入看起来像 HTML 的标记,这使得创建交互式用户界面更直观)

5. Servlet 注解方式配置

上面的可以不用管了(也就意味着不用在web.xml中写配置信息了来确定路径了), 直接写一个直接注解, 记得写/

这里写了注解, 就不要在web.xml中写了

image-20240810154454582

这里是源码

Java EE - Technologies (oracle.com)

image-20240810154812304

6. 生命周期

大概意思就是: 做事情需要经历哪些环节

  • 生命周期方法

    image-20240810160107399

Servlet在Tomcat中是单例的,

比如说有很多台电脑同时向servlet发送请求,每台发请求的时候, 都会有线程stack

它两同时修改这个i, 那么会产生线程不安全的情况

image-20240810160619026

Servlet的成员变量在多个线程栈中是shared, 不建议在service方法中修改成员变量, 在并发请求时, 会引发线程安全问题, 加锁会降低性能

  • 调整生命周期

    在xml里调整

    image-20240810161248999

通过注解修改

image-20240810161622446

  • default-servlet

    是加载静态资源的

    servlet是请求动态资源的, 所以当请求静态资源的时候, tomcat也会拿路径去和servlet路径进行对比, 但是肯定匹配不上, 所以此时需要default-servlet, 它就会去找对应的资源, 然后用io流读取, 把这个文件放到response身上

    image-20240810162114970

7. 继承结构

直接去查吧, 反正看了也记不得的

image-20240814124520279

1682299663047

自定义Servlet中,必须要对处理请求的方法进行重写

  • 要么重写service方法
  • 要么重写doGet/doPost方法

image-20240814130605812

8. ServletConfig & ServletContext

ServletConfig

  • 为Servlet提供初始配置参数的一种对象,每个Servlet都有自己独立唯一的ServletConfig对象
  • 容器会为每个Servlet实例化一个ServletConfig对象,并通过Servlet生命周期的init方法传入给Servlet作为属性

这一段会被放到ServletConfig对象上, GenericServlet类 中的init方法调用的时候就会把ServletConfig对象传入

image-20240814142456319

目前的web.xml

image-20240823141813802

获取config对象

在service方法中写this.getServletConfig();

image-20240814143914514

也可以通过注解来配置Servlet

image-20240823141838631

ServletContext

ServletContext对象为所有的Servlet所共享, 为所有的Servlet提供初始配置参数

ServletConfig是针对某一个Servlet配置的

image-20240823160615503

怎么使用?

在web.xml里直接写

image-20240829182013709

配置好之后, 回到servlet

image-20240829182623255

路径相关问题

需求: 向web中的upload目录中写入一个文件
这节的目的是: 不能写死路径, 怎么写相对路径? --> 需要用到api

image-20240829185821583

域对象

一些用于存储数据和传递数据的对象, 不同的域对象代表不同的域, 共享数据的范围也不同

image-20240829190334883

  • ServletContext代表应用,所以ServletContext域也叫作应用域,是webapp中最大的域,可以在本应用内实现数据的共享和传递

image-20240829191017400

9. HttpServletRequest & HttpServletResponse

获取请求中的信息, 以及 设置相应信息

  • HttpServletRequest是一个接口,其 parent 接口是ServletRequest
  • HttpServletRequest在Tomcat调用service方法时传入
  • HttpServletRequest代表客户端发来的请求,所有请求中的信息都可以通过该对象获得

image-20240830170220122

常用的API

自己去笔记里查, 感觉报文里的东西都能调API获得

包括request line, request body, 以及 request parameter

image-20240830170708853

  • 这里注意区别一下URIURL

String getRequestURI(); //资源定位的要求和规范, 获取客户端请求项目中的具体资源, 动物类

例如 /demo/03/a.html

StringBuffer getRequestURL(); //获取客户端请求的url, http下一个具体的资源路径, 哺乳动物类

例如http://ip:port/demo/03/a.html

Request常用API

  • 获取请求行信息相关(方式,请求的url,协议及版本)
API功能解释
StringBuffer getRequestURL();获取客户端请求的url
String getRequestURI();获取客户端请求项目中的具体资源
int getServerPort();获取客户端发送请求时的端口(有可能是个proxy,而非真正server)
int getLocalPort();获取本应用在所在容器的端口
int getRemotePort();获取客户端程序的端口
String getScheme();获取请求协议
String getProtocol();获取请求协议及版本号
String getMethod();获取请求方式
  • 获得请求头信息相关
    | API | 功能解释 |
    | ------------------------------------- | ---------------------- |
    | String getHeader(String headerName); | 根据头名称获取请求头 |
    | Enumeration getHeaderNames(); | 获取所有的请求头名字 |
    | String getContentType(); | 获取content-type请求头 |

  • 获得请求参数相关

    无论请求是get(信息放到url里)还是post(信息放到request body里)都可以获取到
    | API | 功能解释 |
    | ------------------------------------------------------- | ------------------------------------------- |
    | String getParameter(String parameterName); | 根据请求参数名 获取 请求单个参数值 |
    | String[] getParameterValues(String parameterName); | 根据请求参数名 获取 请求多个参数值数组 |
    | Enumeration getParameterNames(); | 获取所有 请求参数名 |
    | Map<String, String[]> getParameterMap(); | 获取所有请求参数的键值对集合(参数名-参数值) |
    | BufferedReader getReader() throws IOException; | 获取读取请求体的字符输入流(非键值对数据) |
    | ServletInputStream getInputStream() throws IOException; | 获取读取请求体的字节输入流 |
    | int getContentLength(); | 获得请求体长度的字节数 |

  • 其他API

API功能解释
String getServletPath();获取请求的Servlet的映射路径 servlet05
ServletContext getServletContext();获取ServletContext对象
Cookie[] getCookies();获取请求中的所有cookie
HttpSession getSession();获取Session对象
void setCharacterEncoding(String encoding) ;设置请求体字符集

Response

  • HttpServletResponse是一个接口,其parent接口是ServletResponse
  • HttpServletResponse是Tomcat预先创建的,在Tomcat调用service方法时传入
  • HttpServletResponse代表对客户端的响应,该对象会被转换成响应的报文发送给客户端,通过该对象我们可以设置响应信息

image-20240830173257084

  • 设置响应行相关
API功能解释
void setStatus(int code);设置响应状态码
  • 设置响应头相关
API功能解释
void setHeader(String headerName, String headerValue);设置/修改响应头键值对
void setContentType(String contentType);设置content-type响应头及响应字符集(设置MIME类型)
  • 设置响应体相关
API功能解释
PrintWriter getWriter() throws IOException;获得向响应体放入信息的字符输出流
ServletOutputStream getOutputStream() throws IOException;获得向响应体放入信息的字节输出流
void setContentLength(int length);设置响应体的字节长度,其实就是在设置content-length响应头
  • 其他API
API功能解释
void sendError(int code, String message) throws IOException;向客户端响应错误信息的方法,需要指定响应码和响应信息
void addCookie(Cookie cookie);向响应体中增加cookie
void setCharacterEncoding(String encoding);设置响应体字符集

10. **Forward **和 Redirect

  • web中 间接访问项目资源的两种手段,也是Servlet控制页面跳转的两种手段
  • 请求转发通过HttpServletRequest实现,响应重定向通过HttpServletResponse实现
  • 请求转发生活举例: 张三找李四借钱,李四没有,李四找王五,让王五借给张三
  • 响应重定向生活举例:张三找李四借钱,李四没有,李四让张三去找王五,张三自己再去找王五借钱

Forward

请求ServletA处理不了, 转发给ServletB, B做一些处理, 然后响应回去

image-20240831153856154

ServletA

image-20240831160727622

@WebServlet("/servletA")
public class ServletA extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //  获取请求转发器
        //  转发给servlet  ok
        RequestDispatcher  requestDispatcher = req.getRequestDispatcher("servletB");
        //  转发给一个视图资源 ok
        RequestDispatcher requestDispatcher = req.getRequestDispatcher("welcome.html");
        //  转发给WEB-INF下的资源  ok
        RequestDispatcher requestDispatcher = req.getRequestDispatcher("WEB-INF/views/view1.html");
        //  转发给外部资源   no
        RequestDispatcher requestDispatcher = req.getRequestDispatcher("http://www.atguigu.com");
        //  获取请求参数
        String username = req.getParameter("username");
        System.out.println(username);
        //  向请求域中添加数据
        req.setAttribute("reqKey","requestMessage");
        //  做出转发动作
        requestDispatcher.forward(req,resp);
    }
}

ServletB

image-20240831160807639

@WebServlet("/servletB")
public class ServletB extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取请求参数
        String username = req.getParameter("username");
        System.out.println(username);
        // 获取请求域中的数据
        String reqMessage = (String)req.getAttribute("reqKey");
        System.out.println(reqMessage);
        // 做出响应
        resp.getWriter().write("servletB response");        
    }
}

请求转发特点(背诵)

  • 请求转发通过HttpServletRequest对象获取请求转发器实现
  • 请求转发是服务器内部的行为,对客户端是屏蔽的
  • 客户端只发送了一次请求,客户端地址栏不变
  • 服务端只产生了一对请求和响应对象,这一对请求和响应对象会继续传递给下一个资源
  • 因为全程只有一个HttpServletRequset对象,所以请求参数可以传递,请求域中的数据也可以传递
  • 请求转发可以转发给其他Servlet动态资源,也可以转发给一些静态资源以实现页面跳转
  • 请求转发可以转发给WEB-INF下受保护的资源
  • 请求转发不能转发到本项目以外的外部资源

Redirect

image-20240831163446261

先请求Servlet A, 然后在response 设置 status code: 302, location:"目标资源", 客户端一看是302, 所以就知道是一个redirect, 然后通过location提供的的地址向server发送请求

image-20240831164933738

image-20240831164949009

image-20240831165000064

响应重定向特点(背诵)

和上面的正好相反

  • 响应重定向通过HttpServletResponse对象的sendRedirect方法实现
  • 响应重定向是服务端通过302响应码和路径,告诉客户端自己去找其他资源,是在服务端提示下的客户端的行为
  • 客户端至少发送了两次请求,客户端地址栏是要变化
  • 服务端产生了多对请求和响应对象,且请求和响应对象不会传递给下一个资源
  • 因为全程产生了多个HttpServletRequset对象,所以请求参数不可以传递,请求域中的数据也不可以传递
  • 重定向可以是其他Servlet动态资源,也可以是一些静态资源以实现页面跳转
  • 重定向不可以到给WEB-INF下受保护的资源
  • 重定向可以到本项目以外的外部资源

11. web乱码和路径问题

如果出现问题, 请去找原笔记, 我目前先跳过了

乱码问题产生的根本原因是什么

数据的编码和解码使用的不是同一个字符集(字符集是一个字典, 记录着字母和1010之前的对应关系, 但是有很多的字符集, UTF-8 和 GBK 中文对应的字符也是不一样的)

使用了不支持某个语言文字的字符集

ASCII中有什么? 英文字母和一些通常使用的符号,所以这些东西无论使用什么字符集都不会乱码

image-20240831170017906

12. MVC

MVC(Model View Controller)是软件工程中的一种**软件架构模式,它把软件系统分为模型视图控制器**三个基本部分。

在开发项目的时候代码不能乱放, 需要遵循一套规范(要求), 设计, 分类, 存放都有要求, 高内聚低耦合

用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。说白了, 就是处理业务放在一起, 前端页面放在一起

M:Model 层

  1. 存放和数据库对象的实体类以及一些用于存储非数据库表完整相关的VO对象

  2. 存放一些对数据进行操作的一些业务处理代码

    1. 实体类包(pojo /entity /bean) 专门存放和数据库对应的实体类和一些VO对象
    2. 数据库访问包(dao/mapper) 专门存放对数据库不同表格CURD方法封装的一些类
    3. 服务包(service) 专门存放对数据进行业务逻辑运算的一些类

V:View 层

  1. 存放一些视图文件相关的代码 html css js等, 负责数据展示

  2. 在前后端分离的项目中,后端已经没有视图文件,该层次已经衍化成独立的前端项目

    控制层包(controller)

C:Controller 层

  1. 接收客户端请求,获得请求数据, 把参数给服务层, 让服务层来处理代码,

    如果涉及到数据库,用DAO层(data access object)

  2. 将准备好的数据响应给客户端

  1. web目录下的视图资源 html css js img 等
  2. 前端工程化后,在后端项目中已经不存在了

示意图

image-20240901153928830

会话

首先了解基本原理, 具体可以去front-end markdown笔记里面寻找关键词(也是一级标题) 会话控制

最重要概念:

Cookie 本质上是一个key=value对,一个 key-value对 可以叫做一个cookie

即一个请求头中的 key=value 形式的数据对。这个数据对会随着每次请求一起发送到服务器

Cookie是一个车, 用于客户端的数据存储和传递, 向服务器 保存和发送Session ID, 服务器通过Session ID查找服务器端保存的数据

Cookie的典型用途

  1. Session ID
  • Session-based Authentication 使用的Cookie通常包含一个 Session ID。这个ID是服务器生成的唯一标识符,用来识别用户的会话。
  • 请求头形式Cookie: session_id=abc123xyz
  • 服务器端存储:服务器根据这个 Session ID 查找存储在服务器上的会话数据,从而确认用户的身份。
  1. Token(例如JWT)
  • Token-based Authentication 使用的Cookie可能包含一个加密的 Token,如JWT(JSON Web Token)。这个Token通常包含了用户的身份信息、签名和有效期等内容。 无状态验证
  • 请求头形式Cookie: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
  • 服务器端验证:服务器通过验证Token的签名和内容来确认其合法性,而不需要在服务器上存储任何会话数据。

servlet A 响应中增加cookie, 需要使用到cookie类, 使用 addcookie ()方法增加 key-value

@WebServlet("/servletA")
public class ServletA extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 创建Cookie
        Cookie cookie1 =new Cookie("c1","c1_message");
        Cookie cookie2 =new Cookie("c2","c2_message");
        // 将cookie放入响应对象
        resp.addCookie(cookie1);
        resp.addCookie(cookie2);
    }
}

servletB从请求中读取Cookie, 使用getCookies()获取cookie

@WebServlet("/servletB")
public class ServletB extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取请求中的cookie
        Cookie[] cookies = req.getCookies();
        //迭代cookies数组
        if (null != cookies && cookies.length!= 0) {
            for (Cookie cookie : cookies) {
                System.out.println(cookie.getName()+":"+cookie.getValue());
            }
        }
   }

默认情况下Cookie的有效期是一次会话范围内,我们可以通过cookie的setMaxAge()方法让Cookie持久化保存到浏览器上

会话级Cookie 和 持久化 Cookie

cookie.setMaxAge(int expiry)参数单位是秒,表示cookie的持久化时间,如果设置参数为0,表示将浏览器中保存的该cookie删除

  • 设置持久化cookie, 使用setMaxAge(60)方法, 并放入response, 使用 resp.addCookie(cookie) 方法
@WebServlet("/servletA")
public class ServletA extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 创建Cookie
        Cookie cookie1 =new Cookie("c1","c1_message");
        cookie1.setMaxAge(60);
        Cookie cookie2 =new Cookie("c2","c2_message");
        // 将cookie放入响应对象
        resp.addCookie(cookie1);
        resp.addCookie(cookie2);
    }
}
  • ServletB接收Cookie,浏览器中间发生一次重启再请求servletB测试
@WebServlet("/servletB")
public class ServletB extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取请求中的cookie
        Cookie[] cookies = req.getCookies();
        //迭代cookies数组
        if (null != cookies && cookies.length!= 0) {
            for (Cookie cookie : cookies) {
                System.out.println(cookie.getName()+":"+cookie.getValue());
            }
        }
    }
}

访问互联网资源时不能每次都需要把所有Cookie带上。比如只在请求servletA, B的时候提交, 在请求ServletC的时候不提交, 说实话我没完全懂.........

访问不同的资源时,可以携带不同的cookie,我们可以通过cookie的setPath(String path) 对cookie的zz径进行设置

  • 从Servlet A 设置 cookie的提交路径为B

    public class ServletA extends HttpServlet {
        @Override
        protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            // 创建Cookie
            Cookie cookie1 =new Cookie("c1","c1_message");
            // 设置cookie的提交路径
            cookie1.setPath("/web03_war_exploded/servletB");
            Cookie cookie2 =new Cookie("c2","c2_message");
            // 将cookie放入响应对象
            resp.addCookie(cookie1);
            resp.addCookie(cookie2);
        }
    }
    

Session

打开一个浏览器相当于启动了一个线程,把session创建的值存在内存中,也是在这个线程中管理的;一个别的浏览器相当于一个新的线程,那么session本身就不共享

HttpSession 使用

  • 服务端在为客户端创建session时,会同时将session对象的id,即JSESSIONID以cookie的形式放入响应对象
  • 后端创建完session后,客户端会收到一个特殊的cookie,叫做JSESSIONID
  • 客户端下一次请求时携带JSESSIONID,后端收到后,根据JSESSIONID找到对应的session对象
  • 通过该机制,服务端通过session就可以存储一些专门针对某个客户端的信息了

image-20240901192034490

一般用来记住登陆状态, 用户操作历史

需求

用户提交form表单到ServletA,携带用户名,ServletA获取session 将用户名存到Session,用户再请求其他任意Servlet,获取之间存储的用户

  • 定义表单页,提交用户名,提交后
    <form action="servletA" method="post">
        用户名:
        <input type="text" name="username">
        <input type="submit" value="提交">
    </form>
  • 定义ServletA,将用户名存入session , 需要使用req.getSession();获取session对象
@WebServlet("/servletA")
public class ServletA extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取请求中的参数
        String username = req.getParameter("username");
        // 获取session对象
        HttpSession session = req.getSession();
         /*
         这里会判断有没有特殊的cookie Jsession(具体可以看下面的图)
        	 1. 有: 根据JsessionID找对应的session对象
         	 2. 没有: 创先新的session返回, 并向response中存放一个JsessionID的cookie
         */
        
         // 获取Session的ID
        String jSessionId = session.getId();
        System.out.println(jSessionId);
        // 判断session是不是新创建的session
        boolean isNew = session.isNew();
        System.out.println(isNew);
        // 向session对象中存入数据
        session.setAttribute("username",username);
    }
}

image-20240901192143887

  • 其它Servlet, 从session中读取用户名, 需要使用req.getSession();获取session对象

    @WebServlet("/servletB")
    public class ServletB extends HttpServlet {
        @Override
        protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            // 获取session对象
            HttpSession session = req.getSession();
             // 获取Session的ID
            String jSessionId = session.getId();
            System.out.println(jSessionId);
            // 判断session是不是新创建的session
            boolean isNew = session.isNew();
            System.out.println(isNew);
            // 从session中取出数据
            String username = (String)session.getAttribute("username");
            System.out.println(username);
        }
    }
    

HttpSession 时效性

为什么要设置session的时效

  • 用户量很大之后,Session对象相应的也要创建很多。如果一味创建不释放,那么服务器端的内存迟早要被耗尽。
  • 客户端关闭行为无法被服务端直接侦测 ,或者客户端较长时间不操作, 就需要对session的时限进行设置

默认的session最大闲置时间(两次使用同一个session中的间隔时间) 在tomcat/conf/web.xml配置为30分钟

image-20240901193029908

也可以用API设置

// 设置最大闲置时间
session.setMaxInactiveInterval(60); //60单位是秒
// 也可以直接让session失效
session.invalidate();

三大域对象

Intro

一些用于存储数据和传递数据的对象, 不同的域对象代表不同的域, 共享数据的范围也不同

有HttpServletRequest, HttpSession, ServletContext 这些类

image-20240829190334883

ServletContext代表应用,所以ServletContext域也叫作应用域,是webapp中最大的域,可以在本应用内实现数据的共享和传递

没错. 是三大, 为什么上面是四个?

  • 请求域对象是HttpServletRequest ,传递数据的范围是一次请求之内及请求转发
  • 会话域对象是HttpSession,传递数据的范围是一次会话之内,可以跨多个请求
  • 应用域对象是ServletContext,传递数据的范围是本应用之内,可以跨多个会话, 可以在本应用内实现数据的共享和传递

举例子

热水器摆放位置不同,使用的范围就不同

  1. 摆在张三工位下,就只有张三一个人能用
  2. 摆在办公室的公共区,办公室内的所有人都可以用
  3. 摆在楼层的走廊区,该楼层的所有人都可以用
  • HttpServletRequest 请求域

    不能跨请求域, 因为向不同的servlet 发请求发的都不是同一个请求, 互相拿不到东西的

    但是如果是同一个请求, 或者请求转发可以拿到, 因为转发的是同一个

image-20240902164629652

  • HttpSession 会话域

    只要两个请求使用同一个会话, 那就可以跨域

    我用两个浏览器发请求, 那就是不同的session, 互相肯定拿不到

    image-20240902164858774

  • ServletContext 应用域

    都能跨域

    image-20240902165000802

  • 所有域在一起

    image-20240902165026503

API

域都能用, 是同一个API

API功能
void setAttribute(String name,String value)向域对象中添加/修改数据
Object getAttribute(String name);从域对象中获取数据
removeAttribute(String name);移除域对象中的数据
  • ServletA向三大域中放入数据

    @WebServlet("/servletA")
    public class ServletA extends HttpServlet {
        @Override
        protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            // 向请求域中放入数据
            req.setAttribute("request","request-message");
            //req.getRequestDispatcher("servletB").forward(req,resp);
            
            // 向会话域中放入数据
            HttpSession session = req.getSession();
            session.setAttribute("session","session-message");
            
            // 向应用域中放入数据
            ServletContext application = getServletContext();
            application.setAttribute("application","application-message");
    
        }
    }
    
  • ServletB从三大域中取出数据

    @WebServlet("/servletB")
    public class ServletB extends HttpServlet {
        @Override
        protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            // 从请求域中获取数据
            String reqMessage =(String)req.getAttribute("request"); //返回的是object, 自己转成String
            System.out.println(reqMessage);
            
            // 从会话域中获取数据
            HttpSession session = req.getSession();
            String sessionMessage =(String)session.getAttribute("session");
            System.out.println(sessionMessage);
            // 从应用域中获取数据
            ServletContext application = getServletContext();
            String applicationMessage =(String)application.getAttribute("application");
            System.out.println(applicationMessage);
        }
    }
    
  • 请求转发时,请求域可以传递数据, 请求域内一般放本次请求业务有关的数据,如:查询到的所有的部门信息
  • 同一个会话内,不用请求转发,会话域可以传递数据, 会话域内一般放本次会话的客户端有关的数据,如:当前客户端登录的用户
  • 同一个APP内,不同的客户端,应用域可以传递数据, 应用域内一般放本程序应用有关的数据 如:Spring框架的IOC容器

Filter

Intro

Filter,即过滤器,是JAVAEE技术规范之一, 过滤请求的, 需要实现一个接口, 几乎所有开发都会用到

放到请求和servlet之间, 当作过滤, response也可以过滤(看下图)

作用目标资源的请求进行过滤的一套技术规范, 是Java Web项目中最为实用的技术之一

  • Filter的工作位置是项目中所有目标资源之前, 容器在创建HttpServletRequest和HttpServletResponse对象后,会先调用Filter的doFilter方法

  • Filter的doFilter方法可以控制请求是否继续,如果放行,则请求继续,如果拒绝,则请求到此为止,由过滤器本身做出响应

  • Filter不仅可以对请求做出过滤,也可以在目标资源做出响应前,对响应再次进行处理

  • Filter是GOF中责任链模式的典型案例

  • Filter的常用应用包括但不限于: 登录权限检查,解决网站乱码,过滤敏感字符,日志记录,性能分析... ...

    工作位置

    image-20240902170300957

过滤过程

image-20240902171800070

使用

LoginFilter

下面是filter 的三步走

image-20240902183617378

image-20240902184437180

servlet 1

image-20240902184455278

web.xml

image-20240902190308221

写了个例子, 没放在这里, 因为你知道怎么用就可以了

生命周期

了解即可

阶段对应方法执行时机执行次数
创建对象构造器web应用启动时1
初始化方法void init(FilterConfig filterConfig)构造完毕1
过滤请求void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)每次请求多次
销毁default void destroy()
  • 代码

    package com.atguigu.filters;
    
    import jakarta.servlet.*;
    import jakarta.servlet.annotation.WebServlet;
    
    import java.io.IOException;
    
    
    @WebServlet("/*")
    public class LifeCycleFilter implements Filter {
        public LifeCycleFilter(){
            System.out.println("LifeCycleFilter constructor method invoked");
        }
        
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            //可以读取初始配置信息, 但是要自己去web.xml里面配置
            System.out.println("LifeCycleFilter init method invoked");
        }
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            System.out.println("LifeCycleFilter doFilter method invoked");
            filterChain.doFilter(servletRequest,servletResponse);
        }
    
        @Override
        public void destroy() {
            System.out.println("LifeCycleFilter destory method invoked");
        }
    }
    
    
  • web.xml

    image-20240902200552975

filterChain

一个web项目中,可以同时定义多个过滤器,多个过滤器对同一个资源进行过滤时,工作位置有先后,整体形成一个工作链,称之为过滤器链

  • 过滤器链中的过滤器的顺序由filter-mapping顺序决定
  • 每个过滤器过滤的范围不同,针对同一个资源来说,过滤器链中的过滤器个数可能是不同的
  • 如果某个Filter是使用ServletName进行匹配规则的配置,那么这个Filter执行的优先级要更低

image-20240902201929503

  • 三个过滤器代码

    public class Filter1  implements Filter {
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            System.out.println("filter1 before chain.doFilter code invoked");
    
            filterChain.doFilter(servletRequest,servletResponse);
    
            System.out.println("filter1 after  chain.doFilter code invoked");
    
        }
    }
    
    
    public class Filter2 implements Filter {
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            System.out.println("filter2 before chain.doFilter code invoked");
    
            filterChain.doFilter(servletRequest,servletResponse);
    
            System.out.println("filter2 after  chain.doFilter code invoked");
    
        }
    }
    
    
    public class Filter3 implements Filter {
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            System.out.println("filter3 before chain.doFilter code invoked");
    
            filterChain.doFilter(servletRequest,servletResponse);
    
            System.out.println("filter3 after  chain.doFilter code invoked");
    
        }
    }
    
  • web.xml

    控制执行顺序, 控制过滤哪个servlet, 当然也可以用注解

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
             version="5.0">
        
        <!--下面是三个filter-->
        <filter>
            <filter-name>filter1</filter-name>
            <filter-class>com.atguigu.filters.Filter1</filter-class>
        </filter>
    
        <filter>
            <filter-name>filter2</filter-name>
            <filter-class>com.atguigu.filters.Filter2</filter-class>
        </filter>
    
        <filter>
            <filter-name>filter3</filter-name>
            <filter-class>com.atguigu.filters.Filter3</filter-class>
        </filter>
    
        <!--filter-mapping的顺序决定了过滤器的工作顺序-->
        <filter-mapping>
            <filter-name>filter1</filter-name>
            <url-pattern>/servletC</url-pattern>
        </filter-mapping>
    
        <filter-mapping>
            <filter-name>filter2</filter-name>
            <url-pattern>/servletC</url-pattern>
        </filter-mapping>
    
        <filter-mapping>
            <filter-name>filter3</filter-name>
            <url-pattern>/servletC</url-pattern>
        </filter-mapping>
    
    </web-app>
    

    工作流程

    image-20240902202344607

  • 一个比较完整的Filter的XML配置

    <!--配置filter,并为filter起别名-->
    
    <filter>
        <filter-name>loggingFilter</filter-name>
        <filter-class>com.atguigu.filters.LoggingFilter</filter-class>
        <!--配置filter的初始参数-->
        <init-param>
            <param-name>dateTimePattern</param-name>
            <param-value>yyyy-MM-dd HH:mm:ss</param-value>
        </init-param>
    </filter>
    
    
    <!--为别名对应的filter配置要过滤的目标资源-->
    <filter-mapping>
        <filter-name>loggingFilter</filter-name>
        <!--通过映射路径确定过滤资源-->
        <url-pattern>/servletA</url-pattern>
        <!--通过后缀名确定过滤资源-->
        <url-pattern>*.html</url-pattern>
        <!--通过servlet别名确定过滤资源-->
        <servlet-name>servletBName</servlet-name>
    </filter-mapping>
    
    
  • 将xml配置转换成注解方式实现

    
    @WebFilter(
            filterName = "loggingFilter",
        
        	//定义初始参数的
            initParams = {@WebInitParam(name="dateTimePattern",value="yyyy-MM-dd HH:mm:ss")},
        	//通过路径, 后缀名 过滤
            urlPatterns = {"/servletA","*.html"},
        	//通过servlet别名 过滤
            servletNames = {"servletBName"}
    )
    public class LoggingFilter  implements Filter {
        private SimpleDateFormat dateFormat ;
    
        /*init初始化方法,通过filterConfig获取初始化参数
        * init方法中,可以用于定义一些其他初始化功能代码
        * */
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            // 获取初始参数
            String dateTimePattern = filterConfig.getInitParameter("dateTimePattern");
            // 初始化成员变量
            dateFormat=new SimpleDateFormat(dateTimePattern);
        }
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            // 参数父转子
            HttpServletRequest request =(HttpServletRequest)  servletRequest;
            HttpServletResponse  response =(HttpServletResponse)  servletResponse;
            // 拼接日志文本
            String requestURI = request.getRequestURI();
            String time = dateFormat.format(new Date());
            String beforeLogging =requestURI+"在"+time+"被请求了";
            // 打印日志
            System.out.println(beforeLogging);
            // 获取系统时间
            long t1 = System.currentTimeMillis();
            // 放行请求
            filterChain.doFilter(request,response);
            // 获取系统时间
            long t2 = System.currentTimeMillis();
            String afterLogging =requestURI+"在"+time+"的请求耗时:"+(t2-t1)+"毫秒";
            // 打印日志
            System.out.println(afterLogging);
    
        }
    }
    

Listener

有点类似于 js 的 事件

监听器:专门用于对域对象身上发生的事件或状态改变进行监听和相应处理的对象