Servlet_JSP

1.一些回顾

对于Tomcat部署中 我们有一些补充的点需要在此说明一下
1.如果我们想要查询MINEType的话 可以到TOMCAT_HOME/conf/web.xml中进行查询 里面记录了不同类型对应的MINEType
2.我们客户端发送请求数据给服务器之后 服务器会调用父类中的service方法 然后在内部决定调用doGet还是doPost方法 我们也可以重写service方法自定义service的内部逻辑

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/hello3/test" method="get">
    <div>账号 <input type="text" name="username"></div>
    <div>密码 <input type="text" name="password"></div>
    <div><button type="submit">登录</button></div>
</form>
</body>
</html>
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/test")
public class TestServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println(this + "_service");
    }
}

在这里插入图片描述
3.通过response获取的字符输出流PrintWriter不需要手动关闭 当service调用完毕以后就会自动关闭该输出流
4.alt+d启动tomcat之后的默认操作中 常用的有两个:update classes and resourses、redeploy 前者主要为针对只修改的servlet进行重新加载到项目的操作 而后者则为针对添加成员的servlet进行重新加载到项目的操作

2.Servlet

所谓Servlet 其实就是server(服务) applet(小程序)的简称 即小服务程序的意思
当一个请求数据发送到服务器之后 交由一个servlet处理 他会调用其中的service方法 然后在决定要调用doGet还是doPost方法

默认情况下 一个servlet类 只会被服务器创建一个实例对象 而且是发生在第一次处理客户端的请求数据时(不同的对象哈希值对应着不同的对象 我们可以通过对象哈希值的角度来验证一下这个结论)

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

@WebServlet("/test")
public class TestServlet extends HttpServlet {
    public TestServlet(){
        System.out.println(this + "_构造方法");
    }
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println(this + "_service");
    }
}

以下是第一次发送数据以后的结果
在这里插入图片描述
接着我们修改了service的代码 然后重新加载到项目中 查看哈希值是否改变

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

@WebServlet("/test")
public class TestServlet extends HttpServlet {
    public TestServlet(){
        System.out.println(this + "_构造方法");
    }
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println(this + "_service123");
    }
}

在这里插入图片描述
从结果中就可以看出 哈希值并没有改变 说明两次发送数据用的对象是同一个对象 而且对象创建的时机是在第一次接收客户端请求数据之时(我可以通过内存层面解释一下这个问题:修改代码修改的是代码区中的内容 而对象存放的位置是堆空间 修改代码区并不会影响堆空间的东西 既然对象没有发生改变 那么就一直沿用即可)
但是如果我们是为servlet类增删成员而通过redeploy的方式重新加载servlet类的话 那么每一次的redeploy中服务器都会销毁旧的servlet对象 同时创建出新的servlet对象 所以如果是每一次的redeploy的话 那么就会根据同一个servlet类创建出不同的对象

1.注意

有些人可能会这么想 就是既然服务器只会根据一个servlet类创建一个实例的话 那么该servlet类采用的就是单例模式 这种想法是错误的
我们先来看一下单例模式的实现

public class Person {
    // 构造方法私有化
    private Person(){}
    // 提供一个私有静态的对象 静态是为了确保程序运行过程中只有一份内存 私有则是为了不被外界访问
    private static Person instance = new Person();
    // 提供一个共有静态的获取实例方法 静态是为了保证通过类访问
    public static Person getInstance(){
        return instance;
    }
}
public class Main {
    public static void main(String[] args) {
        Person p1 = Person.getInstance();
        Person p2 = Person.getInstance();
        System.out.println(p1);// Person@1b6d3586
        System.out.println(p2);// Person@1b6d3586
    }
}

从结果证明 上述代码确实实现了一个单例模式 所谓单例模式就是根据一个类只能创建一个实例 我们就可以查看一下servlet类是否可以创建多个实例即可证明servlet类是否采用了单例模式

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

@WebServlet("/test")
public class TestServlet extends HttpServlet {
    public TestServlet(){
        System.out.println(this + "_构造方法");// TestServlet@39ac9b6d_构造方法
    }
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        TestServlet ts1 = new TestServlet();// TestServlet@2c4c84b_构造方法
        TestServlet ts2 = new TestServlet();// TestServlet@3282dd6a_构造方法
        System.out.println(ts1);// TestServlet@2c4c84b
        System.out.println(ts2);// TestServlet@3282dd6a
    }
}

从ts1和ts2打印的结果不同来看 就可以证明servlet并非采取单例模式 而是可以根据一个该类创建出多个实例

当然 你也可以为同一个servlet类起多个不同的别名

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

@WebServlet({"/test1", "/test2"})
public class TestServlet extends HttpServlet {
    public TestServlet(){
        System.out.println(this + "_构造方法");// TestServlet@39ac9b6d_构造方法
    }
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println(this + "_service");
    }
}

这样 你可以通过不同的请求路径和服务器进行交互 并且如果只是修改代码重新加载的程度 服务器也只会创建一次servlet实例 而且时机为第一次接收请求数据的时候

还有一个需要注意的地方就是:不要在servlet中定义可写(可修改)的成员变量 这样会引发线程安全问题

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

@WebServlet({"/test1", "/test2"})
public class TestServlet extends HttpServlet {
    private int age = 10;
    public TestServlet(){
        System.out.println(this + "_构造方法");// TestServlet@39ac9b6d_构造方法
    }
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        age++;
    }
}

如果同时有多个请求数据发送给服务器的话 并且同时执行到age++;语句 假设同时发送两次 那么实际的结果11可能和预期结果12是不一致的 这就是所谓的线程安全问题 解决的方案就是将成员变量从可写改成可读 即声明为常量即可

还有一个值得注意的地方就是:http请求的默认端口号为80 如果你的tomcat服务器端口号设置为80的话 那么客户端发送请求过程中就可以省略端口号(会自动添加) 我们重新加载的方式选择restart server 这样才能保留端口号的修改
在这里插入图片描述

3.crm项目

所谓crm 就是customer relationship management的简称 即用户关系管理
我们想要实现的一个项目的效果其实就是当我们点击登录 如果登录成功 就会跳转到展示用户信息的页面 如果登录失败 就会提醒我们重新登陆 并且点击可以实现跳转到登录界面的效果
而且在servlet代码中 我们需要单独封装登录成功、登录失败以及获取储存用户信息的功能
并且我们可以直接创建一个servlet类 而并非class类 该类已经有了基本的框架 包含doGet、doPost以及WebServlet等信息
我们接下来就来实现一下该项目
LoginServlet

package com.mj.servlet;

import com.mj.bean.Customer;

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.util.ArrayList;
import java.util.List;

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 设置请求数据的解码方式为UTF-8
        request.setCharacterEncoding("UTF-8");
        // 接收请求参数
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        // 然后设置响应数据的内容格式 由于展示的为html页面 因此MINEType为text/html 而编码方式设置为UTF-8
        response.setContentType("text/html;charset=utf-8");
        // 然后定义字符输出流 用于输出响应数据
        PrintWriter out = response.getWriter();
        // 判断 如果登录成功 执行登录成功的逻辑 登陆失败则执行登录失败的逻辑
        if(username.equals("123") && password.equals("123")){
            success(out);
        }else{
            fail(out);
        }
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }
    // 封装获取储存用户信息的逻辑
    private List<Customer> getCustomers(){
        // 定义一个list集合 用于存放用户信息
        List<Customer> customers = new ArrayList<>();
        // 假设我们拥有的用户信息数量为10个
        for(int i = 0; i < 10; ++i){
            // 性别奇数为男 偶数为女
            customers.add(new Customer("张三", "10086", ((i & 1) == 1 ? "男" : "女")));
        }
        return customers;
    }
    // 封装登录成功的逻辑
    private void success(PrintWriter out){
        // 先展示登录成功信息
        out.write("<h1>登录成功</h1>");
        out.write("<table>");
        out.write("<thead>");
        out.write("<tr>");
        out.write("<th>姓名</th>");
        out.write("<th>电话</th>");
        out.write("<th>年龄</th>");
        out.write("</tr>");
        out.write("</thead>");
        out.write("<tbody>");
        // 获取存放用户信息的列表
        List<Customer> customers = getCustomers();
        for(Customer customer: customers){
            out.write("<tr>");
            out.write("<td>" + customer.getName() + "</td>");
            out.write("<td>" + customer.getPhone() + "</td>");
            out.write("<td>" + customer.getGender() + "</td>");
            out.write("</tr>");
        }
        out.write("</tbody>");
        out.write("</table>");
    }
    // 封装登录失败的逻辑
    private void fail(PrintWriter out){
        // 先展示登录失败信息
        out.write("<h1>登录失败</h1>");
        // 在展示重新登录 并且跳转到登录界面
        out.write("<a href=\"http://localhost:8080/crm/login.html\">重新登录</a>");
    }
}

Customer

package com.mj.bean;

public class Customer {
    // 定义三个私有成员变量 分别为:姓名、电话以及性别
    private String name;
    private String phone;
    private String gender;
    // 定义无参和有参的构造方法
    public Customer(String name, String phone, String gender) {
        this.name = name;
        this.phone = phone;
        this.gender = gender;
    }

    public Customer() {
    }
    // 定义每一个成员变量的getter和setter方法
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }
}

login

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/crm/login" method="post">
    <div>账号 <input type="text" name="username"></div>
    <div>密码 <input type="text" name="password"></div>
    <div><button type="submit">登录</button></div>
</form>
</body>
</html>

文件架构
在这里插入图片描述
测试之后 可以发现 确实能够满足我们的需求 但是如果要将网页作为响应数据返回给客户端的话 那么就会出现大量可读性极差、难以维护的字符串拼接代码 而且我们的用户信息都是我们自己写死的(一般的解决思路就是将他们放置在数据库中)

4.Servlet处理请求的常见过程

在这里插入图片描述

5.一些细节

  • Tomcat决定了部署到其上的项目的生命周期:所有的Java类都要有一个main方法作为程序的入口 用于运行程序 但是我们可以发现对于部署到Tomcat上的Java类来说 他们没有main方法也可以运行起来 原因在于:Tomcat内置了main方法 用于启动服务器 将servlet部署到服务器上 从而决定了servlet的生命周期 而Tomcat除了提供main方法用于启动服务器、加载servlet以外 还提供了init、service、destroy等一系列接口来访问servlet
  • 关于idea启动Tomcat以后默认打开index.jsp页面的实现:在Tomcat源码中 依次将index.html、index.htm、index.jsp(有优先级)作为欢迎界面 即默认的打开界面
    在这里插入图片描述
  • idea中查看Tomcat源码的步骤:并非CTRL+单击 这样查询的结果是idea对字节码反编译的结果(查看的是jar包中的字节码文件) 而查看源码具体的步骤是先要导入源码 然后通过CTRL+SHIFT+f(在整个项目搜索)输入指定的字眼 查找对应的资源
  • 导入源码的步骤:project structure->modules->Tomcat右击选择edit->+/-Tomcat的src文件
  • ctrl+f/ctrl+shift+f:两者的区别在于 前者是在当前文件中搜索 后者则是在整个项目中进行搜索(包括导入的第三方库) 对于ctrl+shift+f 可以利用适当的筛选条件来缩小搜索的范围 比如scope/project and libraries可以定位到第三方库中进行搜索
  • ctrl+f/ctrl+shift+f和shift*2的区别在于前者是在文件/项目中搜索字符串 而后者则是根据指定的类名查找类
  • idea中的反编译和vs中的反汇编的含义:前者就是Java源代码转换为字节码的逆过程 后者则是汇编转换为机器码的逆过程
  • 重新加载代码的方式:我们之前采用的是on update action 但是也有其他不同的做法 即on frame deactivation(失去焦点) 具体就是当当前页面失去焦点就会重新加载代码 但是由于重新加载的时机为失去焦点 所以这种做法一旦启用 未免加载过于频繁 而且会进行不必要的加载 所以我们一般是禁用他(on frame deactivation->do nothing)

6.JSP

之前我们用servlet处理请求数据并返还响应数据的时候 有这么一个弊端 即通过字符串拼接的方式返还响应数据 可读性差、难以维护 所以现在引入jsp技术用以解决该弊端
所谓的JSP 其实就是JavaServer Pages的简称 是一种动态网页技术标准(JSP的本质是servlet 而servlet通常和数据库连接 JSP展示的页面内容会随着数据库数据的传输而发生改变)

1.指令

  • <%@ page %>:配置当前页面信息
  • <%@ include %>:包含其他页面
  • <%@ taglib %>:导入标签库
  • <% Java代码 %>:嵌入Java代码到_jspService方法中
  • <%-- 注释内容 --%>:JSP的注释(可以注释JSP中的html、java等等所有的代码 但是你也可以用各自不同的注释方法去注释不同的代码 但是前提是只能注释相应的部分)
  • <%! 成员变量/方法 %>:声明成员/方法
  • <%= 要输出的内容 %>:输出内容 该写法等价于out.print(需要输出的内容)(PrintWriter.print和PrintWriter.write的区别在于:前者会原封不动的将内容输出 而后者则是输出一个字节/字符 假如你的内容是数字 那么他就会解析成对应的字符并输出)

2.本质

JSP的本质就是Servlet 我们可以验证一下

  • 首先启动tomcat 在控制台中找到CATALINA_BASE 然后在文件资源管理器中查找
  • 接着点击work文件夹 一路点击下去 最终就能够找到jsp对应的servlet文件(找不到你可以尝试同时打开那个界面)
  • 双击打开这个servlet 可以发现他继承自HttpJspBase类 我们可以在idea中双击shift进行查找
  • 我们都知道 每一个servlet类在运行开始时会首先调用service方法 而该jsp对应的servlet调用的是其父类中的service方法 在service方法内部 调用的是_jspService 而在这个方法内部 就可以看到我们所写的html代码了
  • 所以说 jsp的本质依旧是以字符串拼接的形式返回响应数据 只不过不需要我们进行麻烦的编写 他会帮我们转换 我们只需要完成转换前的html网页即可
  • 有了JSP帮我们转换成字符串拼接形式的代码 就完美的解决了难以维护、可读性差的弊端了
    在这里插入图片描述
    在这里插入图片描述

3.改进字符串拼接

  • test.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <base href="" target="_self">
</head>
<body>
<form action="/first/test.jsp" method="post">
    <p>用户名 <input type="text" name="username"></p>
    <p>密码 <input type="text" name="password"></p>
    <p><button type="submit">登录</button></p>
</form>
</body>
</html>
  • test.jsp
<%--
  Created by IntelliJ IDEA.
  User: 19938
  Date: 2024/5/8/008
  Time: 9:37
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<%
    // 分别获取usernmae和password的请求参数值
    String username = request.getParameter("username");
    String password = request.getParameter("password");
    // 如果两个参数军符合条件的话 那么就显示登录成功 如果不符合的话 那么显示登录失败
    if(username.equals("123") && password.equals("123")){
%>
<h1 style="color: red">登陆成功</h1>
<%
    }else{
%>
<h1 style="color: blue">登录失败</h1>
<%
    }
%>
</body>
</html>

4.改进字符串优化(添加success、fail等功能)

  • 由于Java代码和html代码频繁的互相嵌套 导致代码编写十分困难 有时候甚至比直接编写Servlet代码更难受 所以JSP不推荐这么使用 如此的用法导致可读性差、难以维护
  • 现在流行前后端分离 而不再像以前那样前端后台一起开发JSP

5.JSP处理请求的常见过程

其实我们可以发现jsp在处理请求的时候和servlet是一致的 再次证明两者是一致的
在这里插入图片描述

6.两种处理请求方法的弊端

我们现在有两种处理请求的方法 分别为:Servlet、JSP

  • Servlet:需要频繁的进行包含html代码的字符串拼接 可读性差、难以维护
  • JSP:需要频繁的交织html和Java代码 可读性差、难以维护 并且不符合前后端分离的趋势

7.EL表达式、JSTL标签库

  • EL是Expression Language的简称
    • ${obj.property} 本质为obj.getProperty() 由于类的封装性 导致无法直接访问成员变量 以至于通过getter方法进行属性的访问(属性和成员变量还是有所区别的 属性是getter/setter方法去掉get/set剩余的字符串部分小写的结果)
    • ${obj[“property”]} 本质就类似于Java中的obj.get(“property”) 即键找值 使用场景一般是获取Map中的value
    • ${obj[propertyVar]} 本质也使用了类似于Java中的obj.get(propertyVar) 但是和${obj[“property”]}不同的地方在于[]的内容 这边是key变量 他的操作路径是:先要获取key变量值 放置于[]内 然后在调用类似于Java中的get方法进行键找值操作
  • JSTL是JSP Standard Tag Library的简称 即JSP标准标签库
    • 下载:点击这里进行下载
      • 你可以点击Binary README 查看哪些是需要下载的 可以发现 由于我们不需要XML tag library和JSTL 1.0 所以可以省略三个jar包的下载 只要求我们下载前两个jar包即可(impl和spec)
        在这里插入图片描述
      • 下载完成之后 你可以将jar包和你的项目相关联 首先你要你的Java EE项目中的web-inf文件夹中创建一个lib文件夹用于储存jar包(有别于之前Java项目中存放jar包的方式 即在src中创建一个lib用于存放jar包) 仅仅导入jar包还不够 还必须让jar包和当前项目真正产生联系才行 选中导入的jar包 右击选择add as library 作用范围选择module library 即只在当前module中使用
      • 这样就真正将项目和导入jar包关联在一起了
      • 使用的时候 需要通过taglib指令将关联的JSTL标签库导入
      • 常用的标签
        • <c:if test=“条件”>
        • <c:forEach items=“集合” var=“元素” varStatus=“循环相关的信息”> varStatus用于获取循环相关的信息 比如:每一次遍历的索引值
        • <c:choose>、<c:when test=“条件”>、<c:otherwise>
  • EL表达式和JSTL标签库都可以简化JSP代码 并且使得servlet和jsp各司其职、分工明确
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

axihaihai

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值