当前位置: 首页 > article >正文

Servlet三小时速成

Servlet三小时速成

实例驱动的速成教程。自己敲一遍的话入门还是没问题的。如有错误请读着多多包涵。

Serlet的前辈:CGI 通用网关接口

CGI通过调用外部程序来处理HTTP请求,对于每个请求都会启动一个新的进程。

这就导致了许多问题,首先是请求数量的受限,不可能无限地启动进程,调用外部程序的做法也会延长处理请求的时间;另外,CGI极其依赖平台的语言(如C、C++、perl等等)

Servlet的优势

servlet很好地解决了CGI的问题。

Web容器面对多个请求的情况,使用线程来替代进程,降低了通信成本,也因此提高了性能和稳定性;另外,Servlet由JVM管理,背靠的是Java语言,移植性很好,平台依赖性不高。

速通Servlet API

主要是两个软件包:image-20241103090030353

关于servlet和httpServlet的区别:

Servlet 是其中一个接口,定义了所有 servlet 的基本功能和方法(如 init()service()destroy())。

HttpServletServlet 接口的抽象类,专门为处理 HTTP 请求而设计,扩展了 Servlet 的功能。实际用的比较多。

javax.servlet

API 文档速查:javax.servlet (Java™ EE 8 Specification APIs)

常用接口
接口描述
AsyncContext表示在 ServletRequest 上启动的异步操作的执行上下文的类。
AsyncListener当在添加了该监听器的 ServletRequest 上启动的异步操作完成、超时或出现错误时,将通知的监听器。
Filter过滤器是一个对象,用于对请求资源(如 servlet 或静态内容)或对响应资源进行过滤操作,或同时对两者进行过滤。
FilterChainFilterChain 是一个由 servlet 容器提供给开发者的对象,提供对过滤请求资源的调用链的视图。
FilterConfig过滤器配置对象,servlet 容器在初始化时用于传递信息给过滤器。
FilterRegistration通过此接口可以进一步配置过滤器。
FilterRegistration.Dynamic通过此接口可以进一步配置通过 ServletContextaddFilter 方法注册的过滤器。
ReadListener此类表示一个回调机制,将在 HTTP 请求数据可被读取而不阻塞时通知实现类。
Registration通过此接口可以进一步配置 ServletFilter
Registration.Dynamic通过此接口可以进一步配置通过 ServletContextaddServletaddFilter 方法分别注册的 ServletFilter
RequestDispatcher定义一个接收客户端请求并将其发送到服务器上任何资源(如 servlet、HTML 文件或 JSP 文件)的对象。
Servlet定义所有 servlets 必须实现的方法。
ServletConfigservlet 配置对象,servlet 容器在初始化时用于传递信息给 servlet
ServletContainerInitializer允许库/运行时在 web 应用程序的启动阶段被通知,并根据需要进行程序化注册 servletsfilterslisteners 的接口。
ServletContext定义 servlet 用于与其 servlet 容器通信的一组方法,例如获取文件的 MIME 类型、分发请求或写入日志文件。
ServletContextAttributeListener接收有关 ServletContext 属性更改的通知事件的接口。
ServletContextListener接收有关 ServletContext 生命周期更改的通知事件的接口。
ServletRegistration通过此接口可以进一步配置 Servlet
ServletRegistration.Dynamic通过此接口可以进一步配置通过 ServletContextaddServlet 方法注册的 Servlet
ServletRequest定义一个对象,用于向 servlet 提供客户端请求信息。
ServletRequestAttributeListener接收有关 ServletRequest 属性更改的通知事件的接口。
ServletRequestListener接收有关进入和离开 web 应用程序范围的请求的通知事件的接口。
ServletResponse定义一个对象,以帮助 servlet 向客户端发送响应。
SessionCookieConfig可用于配置用于会话跟踪目的的 cookie 的各种属性的类。
SingleThreadModelJava Servlet API 2.4 起已弃用,没有直接替代品。
WriteListener回调通知机制,向开发者信号可在不阻塞的情况下写入内容。
常用类
描述
AsyncEvent当在 ServletRequest 上启动的异步操作(通过调用 ServletRequest.startAsync()ServletRequest.startAsync(ServletRequest, ServletResponse))完成、超时或产生错误时触发的事件。
GenericFilter定义一个通用的、协议无关的过滤器。
GenericServlet定义一个通用的、协议无关的 servlet。
HttpConstraintElementHttpConstraint 注解值的 Java 类表示。
HttpMethodConstraintElementHttpMethodConstraint 注解值的 Java 类表示。
MultipartConfigElementMultipartConfig 注解值的 Java 类表示。
ServletContextAttributeEvent关于 web 应用程序的 ServletContext 属性更改的通知事件类。
ServletContextEvent这是关于 web 应用程序的 servlet 上下文更改的通知事件类。
ServletInputStream提供用于从客户端请求中读取二进制数据的输入流,包括一个高效的 readLine 方法,用于逐行读取数据。
ServletOutputStream提供用于向客户端发送二进制数据的输出流。
ServletRequestAttributeEvent这是关于 servlet 请求中属性更改的通知事件类。
ServletRequestEvent此类事件指示 ServletRequest 的生命周期事件。
ServletRequestWrapper提供 ServletRequest 接口的方便实现,开发者可以通过子类化来适配请求到一个 servlet。
ServletResponseWrapper提供 ServletResponse 接口的方便实现,开发者可以通过子类化来适配来自一个 servlet 的响应。
ServletSecurityElementServletSecurity 注解值的 Java 类表示。
枚举
枚举描述
DispatcherType过滤器调度类型的枚举。
SessionTrackingMode会话跟踪模式的枚举。
异常
异常描述
ServletException定义一个通用异常,servlet 在遇到困难时可以抛出该异常。
UnavailableException定义一个异常,servlet 或过滤器抛出该异常以指示其永久或暂时不可用。

javax.servlet.http

接口
接口描述
HttpServletMapping允许在运行时发现当前 HttpServletRequestHttpServlet 被调用的方式。
HttpServletRequest扩展 ServletRequest 接口,为 HTTP servlet 提供请求信息。
HttpServletResponse扩展 ServletResponse 接口,提供发送 HTTP 响应的特定功能。
HttpSession提供一种方法,以跨多个页面请求或网站访问识别用户,并存储关于该用户的信息。
HttpSessionActivationListener绑定到会话的对象可以监听会话钝化和激活的容器事件通知。
HttpSessionAttributeListener用于接收 HttpSession 属性更改的通知事件的接口。
HttpSessionBindingListener使对象在绑定到会话或从会话中解除绑定时接收到通知。
HttpSessionContext (已弃用)出于安全原因,自 Java™ Servlet API 2.1 起已弃用,无替代方案。
HttpSessionIdListener用于接收 HttpSession ID 更改通知事件的接口。
HttpSessionListener用于接收 HttpSession 生命周期更改通知事件的接口。
HttpUpgradeHandler封装升级协议处理的接口。
Part表示通过 multipart/form-data POST 请求接收的部分或表单项的类。
PushBuilder构建要推送的请求。
WebConnection封装升级请求连接的接口。
描述
Cookie创建一个 cookie,小量信息由 servlet 发送至 Web 浏览器,浏览器保存该信息,并在稍后将其发送回服务器。
HttpFilter提供一个抽象类,可被子类化以创建适合网站的 HTTP 过滤器。
HttpServlet提供一个抽象类,可被子类化以创建适合网站的 HTTP servlet。
HttpServletRequestWrapper提供 HttpServletRequest 接口的便捷实现,开发者可以通过子类化适配请求到 Servlet。
HttpServletResponseWrapper提供 HttpServletResponse 接口的便捷实现,开发者可以通过子类化适配来自 Servlet 的响应。
HttpSessionBindingEvent当对象绑定或解绑到会话时,或者当在会话中任何属性被绑定、解绑或替换时,发送给实现 HttpSessionBindingListener 的对象或 HttpSessionAttributeListener 的事件。
HttpSessionEvent表示 web 应用程序中会话更改通知事件的类。
HttpUtils (已弃用)自 Java™ Servlet API 2.3 起已弃用。
枚举
枚举描述
MappingMatchServlet 映射类型的枚举。

第一个Servlet实例:学会创建Servlet并重写doGet和doPost方法

FirstServlet.java

package com.niko;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

public class FirstServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //能在URL中携带参数,但是数据量有限,能直接在网页中访问,处理 HTTP GET 请求。一般用于从服务器获取数据。请求参数以 URL 查询字符串的形式附加在 URL 后面。

        //1.这是在终端输出
        System.out.println("It's my First Servlet!");

        //2.这是在网页输出,写入到的是writer中的输出
        PrintWriter out = resp.getWriter();
        out.println("<html>");
        out.println("<head>");
        out.println("<title>Servlet FirstServlet</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<h1>Servlet FirstServlet</h1>");
        out.println("</body>");
        out.println("</html>");

        //3.尝试获取请求中的参数
        String username= req.getParameter("username");
        String password= req.getParameter("password");
        out.println("username:"+username);
        out.println("password:"+password);

    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //参数在请求体中,处理 HTTP POST 请求。通常用于向服务器发送数据,尤其是提交表单或上传文件。
    }
}

了解了控制台输出和response输出的区别、doGet方法和doPost方法,并一共完成三个任务:

  1. 控制台输出
  2. 网页输出
  3. 获取请求中的参数

第二个Servlet实例:学习Servlet的生命周期

一个Servlet的生命周期可以简单地概括为:

创建(init)→ 服务(service)→销毁(destory)

对应了servlet提供的三个同名方法,我们可以在自己的Servlet中重写这三个方法。

创建一个SecondServlet.java

package com.niko;

import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
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.io.PrintWriter;

@WebServlet(name = "secondServlet", urlPatterns = {"/secondServlet"})
public class SecondServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html");
        PrintWriter out = resp.getWriter();
        out.println("hello!");
        String username= req.getParameter("username");
        String password= req.getParameter("password");
        //对传入的参数进行一些判断的逻辑操作
        if(username.equals("admin") && password.equals("admin")){
            out.println("密码正确!");
        }else{
            out.println("密码错误!");
        }

    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doPost(req, resp);
    }

    @Override
    public void init() throws ServletException {
        //要注意这个方法只在servlet初始化时被调用一次,后续再有请求不会再调用,除非销毁重建
        System.out.println("init...");
    }

    @Override
    public void destroy() {
        //这个方法在关闭Tomcat服务器时被调用,很显然服务器关闭了,也要销毁所有的servlet
        super.destroy();
        System.out.println("done...");
    }
    
    //发现了这两个service方法,两者都存在的情况下好像先调用了后者,并且一个是protected,一个是public
    
    /*
    * void  service(ServletRequest  req,ServletResponse response)方法是由tomcat自动调用,
    * 它将接收的客户端请求转交给HttpServlet中的另一个protected void service(HttpServletRequest  req,HttpServletResponse res)方法,
    * 此保护类型的service方法再把将请求分发给doPost()、doGet()方法进行下一步处理。
    * 所以我们完全可以重写受保护的service()方法来代替doPost()和doGet()方法。
    * */
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("request coming!");
        System.out.println("Processing...");
    }

    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        System.out.println("request coming?");
        System.out.println("Processing。。。");
    }
}

观察控制台输出,可以看到只有第一次访问网页(发送请求)后控制台输出了 init 信息,只有在tomcat服务器关闭后 才输出了destory信息。


第三个Servlet实例:网页访问次数计数与数据库登录练习

网页访问次数是通过doGet方法触发的,因此相关的逻辑应该在doGet方法中实现,用到了一个全局静态变量Count,只在类加载时被初始化,也就是调用init方法; 数据库的登录暂时先使用传统JDBC流程来实现,放在doPost方法中,使用API调试工具postman来发送post请求进行测试,预计应该判断输入的密码和用户名是否正确,并给出对应的判断信息。

对POST请求来说,参数可以通过请求体*(body)或URL查询字符串(query string)*传递。虽然规范上,POST请求的参数通常在请求体中,但为了测试方便,有时也能够通过URL传递参数。

ThirdServlet.java
package com.niko;

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.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.*;

@WebServlet(name = "ThirdServlet", urlPatterns = {"/thirdServlet"})
public class ThirdServlet extends HttpServlet {
    static int Count;

    @Override
    public void init() throws ServletException {
        Count=0;
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf-8");
        PrintWriter out = resp.getWriter();

        Count++;

        out.println(
                "<html>\n"+
                        "<body>\n"+
                        "<h1>"+"访问次数:"+ Count+"</h1>\n>"+
                        "</body>\n"+
                        "</html>"
        );

    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        //设置响应内容,已经包含了resp.setCharacterEncoding("UTF-8");
        resp.setContentType("text/HTML;charset=UTF-8");

        PrintWriter out = resp.getWriter();

        String username = req.getParameter("username");
        String password = req.getParameter("password");

        //进行JDBC操作
        String user="root";
        String pass="Aa@123456789";
        String jdbcUrl="jdbc:mysql://localhost:3306/servlet?useUnicode=true&characterEncoding=utf-8";
        String driver="com.mysql.jdbc.Driver";
        Connection conn=null;
        PreparedStatement ps=null;
        ResultSet rs=null;
        String queryDb="select * from user where username=? and password=?";

        try{
            Class.forName(driver);
            conn = DriverManager.getConnection(jdbcUrl,user,pass);
            ps = conn.prepareStatement(queryDb);
            ps.setString(1,username);
            ps.setString(2,password);
            rs=ps.executeQuery();
            //判断结果集是否存在
            if(!rs.isBeforeFirst()){
                out.println("登录失败-------->error");
            }
            else{
                
                out.println("登录成功-------->success");
            }
        } catch (ClassNotFoundException | SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

image-20241104124758803

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这是采用Body方式传入的参数。直接写在URL里也可以,但是不安全,涉及敏感数据不能使用。

第四个Servlet实例:注册与登录逻辑处理

这是个比较有挑战的项目了,但是仍然是能够顺利完成的,解决的关键在于实现多个jsp页面的跳转和数据的流向问题。

一共使用到了两个servlet文件和四个jsp文件:

LoginServlet.java
package com.niko.login;

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.io.PrintWriter;
import java.sql.*;

@WebServlet("/loginServlet")
public class LoginServlet extends HttpServlet {
    private static final String JDBC_URL = "jdbc:mysql://localhost:3306/servlet?useUnicode=true&characterEncoding=utf-8&useSSL=false";
    private static final String USER = "root";             //自己的数据库用户
    private static final String PASSWORD = "Aa@123456789"; //自己的数据库密码
    private static final String DRIVER_CLASS = "com.mysql.jdbc.Driver";
    private static final String QUERY_SQL = "select * from user where username = ? and password = ?";

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf-8");
        PrintWriter out = resp.getWriter();

        String username = req.getParameter("username");
        String password = req.getParameter("password");

        //进行JDBC操作
        try {
            Class.forName(DRIVER_CLASS);
            Connection conn= DriverManager.getConnection(JDBC_URL,USER,PASSWORD);
            if(isUserExist(conn,username,password)){//跳转逻辑在这
                req.getRequestDispatcher("success.jsp").forward(req, resp);
            }else{
                req.getRequestDispatcher("error.jsp").forward(req, resp);
            }
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }


    }

    private boolean isUserExist(Connection conn, String username, String password) throws SQLException {
        try (PreparedStatement ps = conn.prepareStatement(QUERY_SQL)) {
            ps.setString(1, username);
            ps.setString(2, password);
            try (ResultSet rs = ps.executeQuery()) {
                return rs.isBeforeFirst();
            }
        }
    }


}
RegisterServlet.java
package com.niko.login;

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.io.PrintWriter;
import java.sql.*;

@WebServlet("/registerServlet")
public class RegisterServlet extends HttpServlet {

    private static final String JDBC_URL = "jdbc:mysql://localhost:3306/servlet?useUnicode=true&characterEncoding=utf-8&useSSL=false";
    private static final String USER = "root";
    private static final String PASSWORD = "Aa@123456789";
    private static final String DRIVER_CLASS = "com.mysql.jdbc.Driver";
    private static final String INSERT_SQL = "INSERT INTO user(username, password) VALUES(?, ?)";
    private static final String QUERY_SQL = "SELECT * FROM user WHERE username=? AND password=?";

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf-8");
        PrintWriter out = resp.getWriter();

        String username = req.getParameter("username");
        String password = req.getParameter("password");

        try {
            Class.forName(DRIVER_CLASS);
            try (Connection conn = DriverManager.getConnection(JDBC_URL, USER, PASSWORD)) {
                if (isUserExist(conn, username, password)) {
                    req.getRequestDispatcher("/error.jsp").forward(req, resp);
                } else {
                    int count = registerUser(conn, username, password);
                    out.println("影响了" + count + "行数据");
                    req.getRequestDispatcher("/success.jsp").forward(req, resp);
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace(out);  // 输出到响应中
        } catch (SQLException e) {
            e.printStackTrace(out);  // 输出到响应中
        }
    }

    private boolean isUserExist(Connection conn, String username, String password) throws SQLException {
        try (PreparedStatement ps = conn.prepareStatement(QUERY_SQL)) {
            ps.setString(1, username);
            ps.setString(2, password);
            try (ResultSet rs = ps.executeQuery()) {
                return rs.isBeforeFirst();
            }
        }
    }

    private int registerUser(Connection conn, String username, String password) throws SQLException {
        try (PreparedStatement ps = conn.prepareStatement(INSERT_SQL)) {
            ps.setString(1, username);
            ps.setString(2, password);
            return ps.executeUpdate();
        }
    }
}

两个servlet的大致框架是一致的,除去固定的JDBC步骤之外,Login的逻辑是先获取请求中的数据(使用getParameter)并将这些数据和数据库中的数据比对,如果查询到了对应的表条目,则返回登录成功(跳转到success),否则返回失败(跳转到error);

Register的逻辑类似,获取数据,接着查询数据库中是否已经存在相同数据,如果查询到了,则返回注册失败(跳转到error),如果没有查询到,则更新数据库数据(Insert一条数据,并且跳转到success),注册成功。

index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登录</title>
</head>
<body>
    <h2>用户登录</h2>
    <form action="/ServletDemo/loginServlet" method="post">
        <div>
            <label for="username">用户名:</label>
            <input type="text" id="username" name="username" required>
        </div>
        <div>
            <label for="password">密码:</label>
            <input type="password" id="password" name="password" required>
        </div>
        <div>
            <input type="submit" value="登录">
        </div>
        <div>
            <input type="button" value="注册" οnclick="location.href='/ServletDemo/register.jsp'">
        </div>
    </form>
</body>
</html>

error.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>注册</title>
</head>
<body>
    <h1>失败!</h1>
</body>
</html>

success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<html>
<head>
    <title>注册</title>
</head>
<body>
    <h1>成功!</h1>
    <div>
        <input type="button" value="返回登录界面" οnclick="location.href='/ServletDemo/index.jsp'">
    </div>
</body>
</html>

register.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>注册</title>
</head>
<body>
<h2>用户注册</h2>
    <form action="/ServletDemo/registerServlet" method="post">
        <div>
            <label for="username">用户名:</label>
            <input type="text" id="username" name="username" required>
        </div>
        <div>
            <label for="password">密码:</label>
            <input type="password" id="password" name="password" required>
        </div>
        <div>
            <input type="submit" value="提交">
        </div>
    </form>
</body>
</html>

这四个jsp文件比较简单,但还是值得学习的,需要要求有一些HTML的知识。

比如使用了表单<form>来提交给对应的Servlet(这里要注意上下文路径,如果错误则不能成功跳转),使用了<div>这个分块的标签来区分“文本+输入框”的用户名块和密码块,使用了<label>标签显示文本并聚焦到关联的属性(id),使用了<input>标签显示两个能传入数据的输入框(type是submit,type改为button则会变成一个按钮,如果是按钮的话可以指定跳转路径)。

可以利用这些标签实现更为复杂的逻辑。

第五个Servlet实例:学习JSP的九个隐含对象和EL表达式

JSP隐含对象

JSP隐式对象是JSP容器为每个页面提供的Java对象,开发者可以直接使用它们而不用显式声明。

对象描述
requestHttpServletRequest类的实例,代表 HTTP 请求的对象,包含客户端发送到服务器的信息,如表单数据、URL参数等。
responseHttpServletResponse类的实例,代表 HTTP 响应的对象,用于向客户端发送数据和响应。
outJspWriter类的实例,用于向客户端输出文本内容的对象,通常用于生成HTML。
sessionHttpSession类的实例,代表用户会话的对象,可用于存储和检索用户特定的数据,跨多个页面。
applicationServletContext类的实例,代表 Web 应用程序的上下文,可以用于存储和检索全局应用程序数据。
configServletConfig类的实例,包含有关当前 JSP 页面的配置信息,例如初始化参数。
pageContextPageContext类的实例,提供对JSP页面所有对象以及命名空间的访问
page类似于 Java 类中的 this 关键字,代表当前 JSP 页面的实例,可以用于调用页面的方法。
exceptionexception 类的对象,代表发生错误的 JSP 页面中对应的异常对象,用于处理 JSP 页面中的异常情况,可用于捕获和处理页面中发生的异常。

可以看到有很多熟悉的面孔,比如request、response、out……这些对象可以直接在JSP页面中使用,

JSP的EL表达式

EL表达式提供".“和”[]"两种运算符来存取数据。两者一般通用。

${user.name}
${user[“name”]}

如果有特殊符号或者要通过变量取值,则只能用后者

${user.first-name}错误写法
${user[“first-name”]}正确写法
通过变量动态取值:
${user[param]}
EL表达式预定义的11个对象

其中斜体的是没有JSP隐含对象与之对应的,

除了pageContext对象是PageContext类型,其余都是 Map类型!!!

对象说明
pageContext提供页面级别的上下文信息,允许访问 JSP 页面的属性和方法。
param获取HTTP请求参数的单个值,返回一个字符串。
paramValues获取HTTP请求参数的多个值,返回一个字符串数组。
header获取HTTP请求头的单个值。
headerValues获取HTTP请求头的多个值,返回一个字符串数组。
cookie获取HTTP请求中的单个Cookie。
initParam获取Web应用的初始化参数,从web.xml文件中读取的参数值。
pageScope访问页面范围内的属性,只能在当前页面中访问。
requestScope访问请求范围内的属性,可以用于在请求的整个生命周期内访问。
sessionScope访问会话范围内的属性,可以跨多个请求访问。
applicationScope访问应用范围内的属性,所有用户都可以访问。
代码例子

用到一个FourthServlet和一个tryEL.jsp

FourthServlet.java

package com.niko;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.IOException;

@WebServlet(name = "fourthServlet",
        urlPatterns = "/ELexample",
        initParams = {
                @WebInitParam(name = "appName", value = "My Application")
        })
public class FourthServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 设置请求属性
        request.setAttribute("requestAttribute", "This is a request attribute");

        // 设置会话属性
        HttpSession session = request.getSession();
        session.setAttribute("sessionAttribute", "This is a session attribute");

        // 设置应用属性
        getServletContext().setAttribute("applicationAttribute", "This is an application attribute");

        // 设置Cookie
        Cookie cookie = new Cookie("user", "John_Doe");
        response.addCookie(cookie);

        // 转发到 JSP 页面
        request.getRequestDispatcher("/tryEL.jsp").forward(request, response);
    }
}

tryEL.jsp


<%--
  Created by IntelliJ IDEA.
  User: Star
  Date: 2024/11/6
  Time: 下午3:03
  To change this template use File | Settings | File Templates.
--%>

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<html>
<head>
    <title>EL表达式训练</title>
</head>
<body>
我们知道JSP有九个可以直接操作的隐含对象:request、response、out、page、pageContext、config、exception、session、application
接下来学习EL表达式!EL表达式也有11个预定义的对象:pageContext、param、paramValues、initParam、header、headerValues、cookie、pageScope、requestScope、applicationScope、sessionScope
<hr>
<h2>EL 表达式示例</h2>

<p>请求属性: ${requestScope.requestAttribute}</p>
<p>会话属性: ${sessionScope.sessionAttribute}</p>
<p>应用属性: ${applicationScope.applicationAttribute}</p>
<p>初始化参数: ${initParam.appName}</p>
<p>Cookie: ${cookie.user}</p>

<p>请求参数:</p>
<ul>
    <li>单个参数: ${param.someParam}</li>
    <li>多个参数:
        <c:forEach var="value" items="${paramValues['multiParam']}">
            ${value} <br>
        </c:forEach>
    </li>
</ul>

<p>请求头:</p>
<p>User-Agent: ${header['User-Agent']}</p>
<p>Accept:
    <c:forEach var="value" items="${headerValues['Accept']}">
        ${value} <br>
    </c:forEach>
</p>

</body>
</html>

最后访问URL:http://localhost:8080/ServletDemo/ELexample?someParam=value1&multiParam=value1&multiParam=value2

效果如下:

image-20241106193341095

学到的几个知识点:

  • initParam需要在web.xml中配置,使用注解也可以。表示的是单个Servlet中的参数。而context-param针对的是整个项目,而且似乎并不能使用注解配置,必须在web.xml中配置。它们两者都采用的是键值对的方式,都是name+value

    <context-param>
            <param-name>appName</param-name>
            <param-value>My Web Application</param-value>
        </context-param>
    
  • attribute都是通过对应对象的setter来设置的

  • <hr>标签用于输出横向线条,用于分割

第六个Servlet实例:过滤器Filter、简单的设计模式以及MVC设计架构的思路训练

什么是Filter?它的作用可以看作是请求处理过程中的“拦截器”或“中间件”,在请求到达 Servlet 之前,或者响应返回客户端之前,进行一些预处理或后处理。

正式地说,Filter 是 Java Web 应用程序中的一个重要组件,它用于对请求和响应进行预处理或后处理。Filter 通常用于实现一些跨多个 Servlet 的功能,如日志记录、权限校验、数据过滤、请求修改等。

原先的Filter是一个接口,我们想要定义自己的过滤器类,就得先实现接口中的方法,一共有三个:

  • init()负责初始化Filter
  • doFilter()值得注意的是有一个参数叫做FilterChain ,它也有一个doFilter方法,是用于将请求传递给下一个Filter对象或者Servlet
  • destroy()负责Filter相关的销毁工作

在类的前面使用一个注解@WebFilter,注解参数可以包括Filter的名字,已经要拦截的请求的URL模式(如果实际的和这个不匹配,就意味着当前的URL请求不会经过该Filter)。当然也可以在web.xml中配置,下面是一个例子

<filter>
    <filter-name>authFilter</filter-name>
    <filter-class>com.niko.filter.AuthFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>authFilter</filter-name>
    <url-pattern>/*</url-pattern>  <!-- 拦截所有请求 -->
</filter-mapping>

这是最终的一个Filter示例,用于确认是否已经登录和记录日志:

MyFilter.java

package com.niko.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;

@WebFilter(filterName = "MyFilter",urlPatterns = "/*")
public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        // 获取请求信息
        String method = httpRequest.getMethod();
        String requestURI = httpRequest.getRequestURI();
        String clientIP = httpRequest.getRemoteAddr();

        // 1. 身份验证
//        if (!isAuthenticated(httpRequest)) {
//            // 用户未登录,重定向到登录页面
//            httpResponse.sendRedirect(httpRequest.getContextPath() + "/login.jsp");
//            return;
//        }

        // 2.记录请求日志
        System.out.println("Request at " + new Date() + " - URL: " + httpRequest.getRequestURL());

        // 继续执行下一个过滤器或者目标 Servlet
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }

    // 判断用户是否已经认证(通过会话中保存的用户信息判断)
    private boolean isAuthenticated(HttpServletRequest request) {
        Object user = request.getSession().getAttribute("user");
        return user != null;  // 如果会话中有用户信息,说明已登录
    }
}

更多知识,可以查阅Servlet 编写过滤器 | 菜鸟教程

第七个Servlet实例:监听器Listener

Web三大组件:

  • Servlet

  • Filter

  • Listener

监听器可以 监听 JavaWeb 中的三大域对象:HttpServletRequest、HttpSession、ServletContext (创建和销毁),一旦被监视的对象发生相应的变化,应该采取相应的操作。

相应的,Listener有三个接口类:HttpSessionListener、ServletRequestListener、ServletContextListener

其中的方法名一般是“监听对象+Initialized/Created/Destroyed”,用于表示监听对象的销毁和重建时进行的动作。

建立MyListener.java

package com.niko.listener;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;



@WebListener
public class MyListener implements ServletContextListener {
    /**
     * @param sce
     *  你可以在这里初始化一些全局资源,比如数据库连接池、日志等
     *  比如:加载配置文件、初始化数据库连接池等
     */
    @Override
    public void contextInitialized(ServletContextEvent sce) {

        // 获取ServletContext对象
        ServletContext context = sce.getServletContext();

        // 存储信息到 ServletContext
        context.setAttribute("appMessage", "Web应用已启动,欢迎访问我们的应用!");
        context.setAttribute("appName", "Niko");
        context.setAttribute("appVersion", "1.0.0");

        System.out.println("Web应用已启动!");

        String appName = (String) sce.getServletContext().getAttribute("appName");
        String appVersion=sce.getServletContext().getAttribute("appVersion").toString();

        System.out.println("应用名: " + appName);
        System.out.println("版本号: " + appVersion);
    }

    /**
     * @param sce
     * 在Web应用关闭时调用
     * 在这里可以进行资源清理工作,比如关闭数据库连接池、清理缓存等
     */
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("Web应用已关闭!");
    }
}

件、初始化数据库连接池等
*/
@Override
public void contextInitialized(ServletContextEvent sce) {

    // 获取ServletContext对象
    ServletContext context = sce.getServletContext();

    // 存储信息到 ServletContext
    context.setAttribute("appMessage", "Web应用已启动,欢迎访问我们的应用!");
    context.setAttribute("appName", "Niko");
    context.setAttribute("appVersion", "1.0.0");

    System.out.println("Web应用已启动!");

    String appName = (String) sce.getServletContext().getAttribute("appName");
    String appVersion=sce.getServletContext().getAttribute("appVersion").toString();

    System.out.println("应用名: " + appName);
    System.out.println("版本号: " + appVersion);
}

/**
 * @param sce
 * 在Web应用关闭时调用
 * 在这里可以进行资源清理工作,比如关闭数据库连接池、清理缓存等
 */
@Override
public void contextDestroyed(ServletContextEvent sce) {
    System.out.println("Web应用已关闭!");
}

}


---

至此,Servlet应用的全貌暂且算是过了一遍,在实际开发或者是应试过程中,不要止步于此,多多阅读源码、查阅文档,灵活地利用这些机制(生命周期、过滤器、监听器、作用域……)去完成自己想完成的项目吧。

http://www.kler.cn/a/394658.html

相关文章:

  • 卷积、频域乘积和矩阵向量乘积三种形式之间的等价关系与转换
  • 如何向函数模块 FM 中传递 Range 参数
  • CCI3.0-HQ:用于预训练大型语言模型的高质量大规模中文数据集
  • python 2小时学会八股文-数据结构
  • react-redux useSelector钩子 学习样例 + 详细解析
  • 【CVPR2024】2024年CVPR的3D 目标检测的综述(还在补充中)
  • request爬虫库的小坑
  • C++ 面向接口编程而不是面向实现编程,其优点和具体措施
  • 线性DP 区间DP C++
  • Cyberchef配合Wireshark提取并解析HTTP/TLS流量数据包中的文件
  • Python中的正则表达式教程
  • 正则表达式那些事儿
  • 融合创新:CNN+LSTM在深度学习中的高效应用,助力科研发表高影响因子文章!
  • Linux之文件和目录类命令详解(2)
  • 在 Windows 11 中使用 MuMu 模拟器 12 国际版配置代理
  • Unity3D高级编程
  • 离线语音识别自定义功能怎么用?
  • C#预处理器指令#if和#endif:掌握条件编译的艺术
  • 使用 Vision 插件让 GitHub Copilot 识图问答
  • windows C#-异常处理
  • 中断的硬件框架
  • 贪心算法day 06
  • Docker 中启动 NGINX 并配置 HTTPS 443 端口
  • 如何用Java爬虫“偷窥”淘宝商品类目API的返回值
  • Linux学习,ip 命令
  • 介绍一下位操作符(c基础)