「课件」原创 => Java WEB【转载请标明出处】

第一章·包名更改的 坑 ~

javax.* => jakarta.*

感兴趣的可以了解一下为什么名称被修改了:

Eclipse基金会在2019年对 Java EE 标准的每个规范进行了重命名,阐明了每个规范在Jakarta EE平台未来的角色。

新的名称Jakarta EE是Java EE的第二次重命名。2006年5月,“J2EE”一词被弃用,并选择了Java EE这个名称。在YouTube还只是一家独立的公司的时候,数字2就就从名字中消失了,而且当时冥王星仍然被认为是一颗行星。同样,作为Java SE 5(2004)的一部分,数字2也从J2SE中删除了,那时谷歌还没有上市。

因为不能再使用javax名称空间,Jakarta EE提供了非常明显的分界线。

  • Jakarta 9(2019及以后)使用jakarta命名空间。
  • Java EE 5(2005)到Java EE 8(2017)使用javax命名空间。
  • Java EE 4使用javax命名空间。

我们可以将项目直接打包为war包(默认),打包好之后,放入webapp文件夹,就可以直接运行我们通过Java编写的Web应用程序了,访问路径为文件的名称。

1. 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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>WebTest</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>WebTest</name>
    <packaging>war</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.compiler.source>1.8</maven.compiler.source>
        <junit.version>5.9.2</junit.version>
    </properties>

    <dependencies>
        <!--tomcat 10 用的是这个-->
        <dependency>
            <groupId>jakarta.servlet</groupId>
            <artifactId>jakarta.servlet-api</artifactId>
            <version>5.0.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.3.2</version>
            </plugin>
        </plugins>
    </build>
</project>

2. 更改项目访问的 URL

第二章·手动让 Tomcat 服务程序 加载 war 包

  1. 先 package 双击,然后打包出 war 包

  1. war 包复制到 Tomcat 的 webapp 目录下

  2. 双击 bin 下面的 startup.bat 启动 Tomcat 服务

  3. 然后 访问 http://localhost:8080/manager/html 查看下,我们写的 WEB 程序是否已经加载!

  1. 访问 http://localhost:8080/WebTest-1.0-SNAPSHOT/

第三章·简单编写 Servlet 程序

1. 注册 Servlet 程序

1.1 web.xml 配置方式

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <!--全局初始化参数 可以在这里设置,然后Java代码使用 context.getInitParameter("NBLOVE") 进行获取-->
    <context-param>
        <param-name>NBLOVE</param-name>
        <param-value>我是全局初始化参数</param-value>
    </context-param>
    <!--下方仅仅是在不使用 注解注册时 进行的配置,如果使用了注解 请删除!-->
    <servlet>
        <servlet-name>test</servlet-name>
        <servlet-class>org.example.webtest.TestServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>test</servlet-name>
        <url-pattern>/test</url-pattern>
    </servlet-mapping>
</web-app>

继承 Servlet 方式

package org.example.webtest;

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebServlet;

import java.io.IOException;

public class TestServlet implements Servlet {

    // 请求发过来时,进行的初始化操作写在这里
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {

    }

    // 专门设置 Servlet 配置的
    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    // 进行信息传输的核心方法,拿到 请求方对象 和 浏览器响应对象
    // 然后去分析 相关的请求方 拿到的东西,进而 去用 响应对象在 网页上进行数据展示等响应
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {

    }

    // 设置 Servlet 信息打印
    @Override
    public String getServletInfo() {
        return null; // null 就是使用默认
    }

    // 连接中断后,我们要做什么事情
    @Override
    public void destroy() {

    }
}

1.2 web.xml 隐藏配置(实现了 可以直接访问 .jsp 静态资源的原因)

<!-- The mapping for the default servlet -->
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <!-- The mappings for the JSP servlet -->
    <servlet-mapping>
        <servlet-name>jsp</servlet-name>
        <url-pattern>*.jsp</url-pattern>
        <url-pattern>*.jspx</url-pattern>
    </servlet-mapping>

1.3 注解方式(主推)=> 以继承 Servlet 举例

package org.example.webtest;

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebServlet;

import java.io.IOException;

// @WebServlet("/test") 把 /test 是注册到 Web 服务器
// 然后你用 根URL/test 即 http://localhost:8080/hello/ 配置项 的值test 就可以访问
@WebServlet(value = "/test",initParams = {
    /*@WebInitParam 是初始化参数,一般放一些 配置项 的值*/
        @WebInitParam(name = "test", value = "我是一个默认的初始化参数")
})
public class TestServlet implements Servlet {

    public TestServlet(){
        // 构造方法是在初始化之前执行的
        System.out.println("我是构造方法");
    }

    // 请求发过来时,进行的初始化操作写在这里
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        getInitParameter("test"); // 拿到 注解处设置的 初始化参数,其实是一个很鸡肋的东西
        System.out.println("初始化");
    }

    // 专门设置 Servlet 配置的
    @Override
    public ServletConfig getServletConfig() {
        System.out.println("ServletConfig");
        return null;
    }

    // 进行信息传输的核心方法,拿到 请求方对象 和 浏览器响应对象
    // 然后去分析 相关的请求方 拿到的东西,进而 去用 响应对象在 网页上进行数据展示等响应
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        // 如果加载静态资源的话,可能还会去进行请求
        System.out.println("service");
    }

    // 设置 Servlet 信息打印
    @Override
    public String getServletInfo() {
        System.out.println("getServletInfo");
        return null; // null 就是使用默认
    }

    // 连接中断后,我们要做什么事情
    @Override
    public void destroy() {
        System.out.println("destroy");
    }
}
  • 首先执行构造方法完成 Servlet 初始化
  • Servlet 初始化后调用 init () 方法。
  • Servlet 调用 service() 方法来处理客户端的请求。
  • Servlet 销毁前调用 destroy() 方法。
  • 最后,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。

2. 可能需要观看的流程图

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

3. 继承 HttpServlet 方式

3.1 关于 @WebServlet 注解

我们接着来看WebServlet注解,我们前面已经得知,可以直接使用此注解来快速注册一个Servlet,那么我们来想细看看此注解还有什么其他的玩法。

首先name属性就是Servlet名称,而urlPatterns和value实际上是同样功能,就是代表当前Servlet的访问路径,它不仅仅可以是一个固定值,还可以进行通配符匹配:

@WebServlet("/test/*")

上面的路径表示,所有匹配/test/随便什么的路径名称,都可以访问此Servlet,我们可以在浏览器中尝试一下。

也可以进行某个扩展名称的匹配:

@WebServlet("*.js")

这样的话,获取任何以js结尾的文件,都会由我们自己定义的Servlet处理。

那么如果我们的路径为/呢?

@WebServlet("/")

此路径和Tomcat默认为我们提供的Servlet冲突,会直接替换掉默认的,而使用我们的,此路径的意思为,如果没有找到匹配当前访问路径的Servlet,那么久会使用此Servlet进行处理。

我们还可以为一个Servlet配置多个访问路径:

@WebServlet({"/test1", "/test2"})

我们接着来看loadOnStartup属性,此属性决定了是否在Tomcat启动时就加载此Servlet,默认情况下,Servlet只有在被访问时才会加载,它的默认值为-1,表示不在启动时加载,我们可以将其修改为大于等于0的数,来开启启动时加载。并且数字的大小决定了此Servlet的启动优先级。

@Log
@WebServlet(value = "/test", loadOnStartup = 1)
public class TestServlet extends HttpServlet {

    @Override
    public void init() throws ServletException {
        super.init();
        log.info("我被初始化了!");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=UTF-8");
        resp.getWriter().write("<h1>恭喜你解锁了全新玩法</h1>");
    }
}

其他内容都是Servlet的一些基本配置,这里就不详细讲解了。

4. ServletContext 对象

ServletContext全局唯一,它是属于整个Web应用程序的,我们可以通过getServletContext()来获取到此对象。有些数据就应该放在 一个公共的容器当中进行保存,然后让所有的请求都能够拿到!

  • 赋值
ServletContext context = getServletContext();
context.setAttribute("test", "我是重定向之前的数据");
resp.sendRedirect("time");
  • 拿到 webapp 根目录下的 资源文件
ServletContext context = getServletContext();
InputStream inputStream = context.getResourceAsStream("/path/to/resource");

需要注意的是,路径参数应该以斜杠 / 开头,表示相对于 webapp 根目录的路径。如果资源文件位于 WEB-INF 目录下,可以使用 /WEB-INF/ 开头的路径。

想要 拿到 resource 目录下资源文件的话,就用 Resources.getResourceAsStream()

第四章·关于下载和上传

1. 下载

  • 下载链接的写法
<hr>
<a href="file" download="icon.png">点我下载高清资源</a>
@WebServlet("/file")
public class FileServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("image/png");
        OutputStream outputStream = resp.getOutputStream();
        InputStream inputStream = Resources.getResourceAsStream("icon.png");
        // 直接使用copy方法完成转换
        // 其实 这个工具类 我们都可以自己写,就是对IO 进一步的封装,仅此而已
        IOUtils.copy(inputStream, outputStream);
    }
}

2. 上传

<form method="post" action="file" enctype="multipart/form-data">
    <div>
        <input type="file" name="test-file">
    </div>
    <div>
        <button>上传文件</button>
    </div>
</form>
@MultipartConfig
@WebServlet("/file")
public class FileServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try(FileOutputStream stream = new FileOutputStream("/Users/nagocoler/Documents/IdeaProjects/WebTest/test.png")){
            Part part = req.getPart("test-file");
            IOUtils.copy(part.getInputStream(), stream);
            resp.setContentType("text/html;charset=UTF-8");
            resp.getWriter().write("文件上传成功!");
        }
    }
}

第五章·发送 xhr 请求向后端

我推荐大家直接学这个js中原生态的 xhr,而不是 jquery 中二次封装的 ajax(而且现在主流也不用它了,什么兼容IE真的变为了行外人的一个梗了~ 都什么年代了,你看现在哪家公司还搞这个??在手机用户为主流的时代,IE??你脑袋抽了?),咳咳 ~ 因为 ajax 其实并没有添附什么其他的东西和代码上的优化,仅仅只是 做了 类似于语法糖的处理。就是你用起来方便了。。

在早期的时候,我们真的就是 通过 JS 的发送 xhr 请求向后端,来实现 页面与用户 交互的 !所以千万别瞧不起这种方式!!

1. js 代码的 xhr 发送 => 代码模板

function updateTime() {
    let xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4 && xhr.status === 200) {
            document.getElementById("time").innerText = xhr.responseText
        }
    };
    xhr.open('GET', '访问的url', true);
    xhr.send();
}

2. 前端代码模板

<hr>
<div id="time"></div>
<br>
<button onclick="updateTime()">更新数据</button>
<script>
    updateTime()
</script>

3. java 后端 代码模板

@WebServlet("/time")
public class TimeServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
        String date = dateFormat.format(new Date());
        resp.setContentType("text/html;charset=UTF-8");
        resp.getWriter().write(date);
    }
}

第六章·重定向和请求转发

重定向:跳转(重新请求)到某个页面(根据提供url地址),此时响应的状态码会被设置为302!并且响应头中添加了一个Location属性,此属性表示,需要重定向到哪一个网址。

resp.sendRedirect("time");

请求转发:服务器后端程序内部进行的跳转,即把当前的 Servlet 请求,转发给 其他的 Servlet 请求去进行处理,然后返回结果。所以请求转发,是会携带着所有参数 转发到另一个 Servlet 的过程!

req.getRequestDispatcher("/time").forward(req, resp);

我们可以注意到的是,在转发的时候,我们把 当前的请求者对象、响应对象 全都传过去了。所以当然是携带着所有信息过去的。

最后总结,两者的区别为:

  • 请求转发是一次请求,重定向是两次请求
  • 请求转发地址栏不会发生改变, 重定向地址栏会发生改变
  • 请求转发可以共享请求参数 ,重定向之后,就获取不了共享参数了
  • 请求转发只能转发给内部的Servlet

第七章·Cookie、Session

  • cookie:保存在浏览器中的一些信息,就是放到 cookie 里面的。
// resp 响应过去一些 cookie 让浏览器保存
Cookie cookie = new Cookie("test", "yyds");
resp.addCookie(cookie);
resp.sendRedirect("time");
// 获取 req 携带过来的 cookie 都是个啥
for (Cookie cookie : req.getCookies()) {
    System.out.println(cookie.getName() + ": " + cookie.getValue());
}

我们可以观察一下,在HttpServletResponse中添加Cookie之后,浏览器的响应头中会包含一个Set-Cookie属性,同时,在重定向之后,我们的请求头中,会携带此Cookie作为一个属性,同时,我们可以直接通过HttpServletRequest来快速获取有哪些Cookie信息。

Cookie包含哪些信息:

  • name - Cookie的名称,Cookie一旦创建,名称便不可更改
  • value - Cookie的值,如果值为Unicode字符,需要为字符编码。如果为二进制数据,则需要使用BASE64编码
  • maxAge - Cookie失效的时间,单位秒。如果为正数,则该Cookie在maxAge秒后失效。如果为负数,该Cookie为临时Cookie,关闭浏览器即失效,浏览器也不会以任何形式保存该Cookie。如果为0,表示删除该Cookie。默认为-1。
  • secure - 该Cookie是否仅被使用安全协议传输。安全协议。安全协议有HTTPS,SSL等,在网络上传输数据之前先将数据加密。默认为false。
  • path - Cookie的使用路径。如果设置为“/sessionWeb/”,则只有contextPath为“/sessionWeb”的程序可以访问该Cookie。如果设置为“/”,则本域名下contextPath都可以访问该Cookie。注意最后一个字符必须为“/”。
  • domain - 可以访问该Cookie的域名。如果设置为“.google.com”,则所有以“google.com”结尾的域名都可以访问该Cookie。注意第一个字符必须为“.”。
  • comment - 该Cookie的用处说明,浏览器显示Cookie信息的时候显示该说明。
  • version - Cookie使用的版本号。0表示遵循Netscape的Cookie规范,1表示遵循W3C的RFC 2109规范

我们发现,最关键的其实是namevaluemaxAgedomain属性。

我们通常 是用这玩意,去保存一些 登陆过的用户名和密码,就是 前端页面 单选框选中的 "请记住我" 然后人家就把你记住了,浏览器记住之后,每次访问页面发送请求的时候,后端的服务器程序 先判断 Cookie 中是否有 这些信息。

<div>
    <label>
        <input type="checkbox" placeholder="记住我" name="remember-me">
        记住我
    </label>
</div>
  • 登陆成功后 => 让浏览器保存的 Cookie 信息
Map<String, String[]> parameterMap = req.getParameterMap();
if(parameterMap.containsKey("remember-me")){   //若勾选了勾选框,那么会此表单信息
    Cookie cookie_username = new Cookie("username", username);
    cookie_username.setMaxAge(30);
    Cookie cookie_password = new Cookie("password", password);
    cookie_password.setMaxAge(30);
    resp.addCookie(cookie_username);
    resp.addCookie(cookie_password);
}
  • 请求登录的时候 => 判断 Cookie 的模板
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    Cookie[] cookies = req.getCookies();
    if(cookies != null){
        String username = null;
        String password = null;
        for (Cookie cookie : cookies) {
            if(cookie.getName().equals("username")) username = cookie.getValue();
            if(cookie.getName().equals("password")) password = cookie.getValue();
        }
        if(username != null && password != null){
            //登陆校验
            try (SqlSession sqlSession = factory.openSession(true)){
                UserMapper mapper = sqlSession.getMapper(UserMapper.class);
                User user = mapper.getUser(username, password);
                if(user != null){
                    resp.sendRedirect("time");
                    return;   //直接返回
                }
            }
        }
    }
    req.getRequestDispatcher("/").forward(req, resp);   //正常情况还是转发给默认的Servlet帮我们返回静态页面
}

2. Session

通常用来 判断用户是否用该浏览器访问过我们的网页,进行过登录等请求。如果一次都没有进行过,然后还有 cookie,那我们可以怀疑 并不是 用户用浏览器进行的正经的访问,可能是爬虫。。

由于HTTP是无连接的,那么如何能够辨别当前的请求是来自哪个用户发起的呢?Session就是用来处理这种问题的,每个用户的会话都会有一个自己的Session对象,来自同一个浏览器的所有请求,就属于同一个会话。

它会给浏览器设定一个叫做 JSESSIONID 的 Cookie,这个值是一个随机的排列组合,也是用这个Cookie 来判断你属于哪一个会话,只要我们的浏览器携带此Cookie访问服务器,服务器就会通过Cookie的值进行辨别,得到对应的Session对象,因此,这样就可以追踪到底是哪一个浏览器在访问服务器。

需要强调的是:Session 并不是存放在浏览器中的,而是存放在服务器端的。它只不过是在与 浏览器建立会话的时候 把这个会话的 唯一标识 Session ID 保存到了 名字为 "JSESSIONID" 的 Cookie 里面!

而后端代码层面上,我们要想拿到 这个 存储到 服务器端的 Session 对象,就需要 用 请求者 通过 保存的 Session ID 去进行获取。

代码就变成了这样:HttpSession session = req.getSession();

所以不要感觉这个代码看起来很怪,它并不奇怪,它的意思是说 我们依赖于 保存在 浏览器上面的 Session ID 在我们的 后端服务器程序上,拿到 内存里面 的 Session 建立对应会话的 Session 对象。(这里很多人都讲不清楚呀~ 然后还出来误人子弟。。)

  • 拿到 session 并添加 信息
HttpSession session = req.getSession();
session.setAttribute("user", user);
  • 判断 Session 中是否有 保存的信息
HttpSession session = req.getSession();
User user = (User) session.getAttribute("user");
if(user == null) {
    resp.sendRedirect("login");
    return;
}

Session并不是永远都存在的,它有着自己的过期时间,默认时间为30分钟,若超过此时间,Session将丢失,我们可以在配置文件中修改过期时间:

<session-config>
    <session-timeout>1</session-timeout>
</session-config>

我们也可以在代码中使用invalidate方法来使Session立即失效:

session.invalidate();

现在,通过Session,我们就可以更好地控制用户对于资源的访问,只有完成登陆的用户才有资格访问首页。

第八章·过滤器 Filter、监听器 Listener

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

Filter 过滤器就出现了,它的本质实际上也是 一个 Servlet,原理是说,我们无论什么样的请求,都需要先经过它这个 Servlet 去进行一些特判,然后 再决定是否 转发到 目标的 Servlet。

添加一个过滤器非常简单,只需要实现Filter接口,并添加@WebFilter注解即可:

@WebFilter("/*")
public class MainFilter extends HttpFilter {
    @Override
    protected void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
        String url = req.getRequestURL().toString();
        //判断是否为静态资源
        if(!url.endsWith(".js") && !url.endsWith(".css") && !url.endsWith(".png")){
            HttpSession session = req.getSession();
            User user = (User) session.getAttribute("user");
            //判断是否未登陆
            if(user == null && !url.endsWith("login")){
                res.sendRedirect("login");
                return;
            }
        }
        //交给过滤链处理
        chain.doFilter(req, res);
    }
}

1. 存在多个 Filter 过滤器

由于我们整个应用程序可能存在多个过滤器,那么这行代码 chain.doFilter(req, res); 的意思实际上是将此请求继续传递给下一个过滤器,当没有下一个过滤器时,才会到达对应的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);
    }
}

切记:过滤器的过滤顺序是按照类名的自然排序进行的,因此我们要把这些过滤器的名字都要进行个调整,才能非常顺利的按照我们想要的过滤顺序去执行。

2. Listener

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

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

@WebListener
public class TestListener implements HttpSessionListener {
    @Override
    public void sessionCreated(HttpSessionEvent se) {
        System.out.println("有一个Session被创建了");
    }
}
posted @ 2024-03-15 18:29  小哞^同^学的技术博客  阅读(2)  评论(0编辑  收藏  举报