Servlet操作与用法(保姆式教学)

Servlet介绍

什么是servlet

  • Servlet(Servlet Applet的缩写,全称 Java Servlet):适用于Java编写的服务器程序,其主要功能是在于交互式的浏览和修改数据,生成动态Web内容。狭义的Servlet是指Java语言实现的一个接口,广义的Servlet 是指任何实现了这个Servlet接口的类,一般情况下,人们将Servlet,理解为后者。
  • Servlet运行于支持Java的应用服务器中,从原理上将讲,Servlet可以响应任何类型的请求,但是绝大多数情况下Servlet只用于扩展基于HTTP协议的Web服务器
  • Servlet是一种实现动态页面的技术,是一组由Tomcat提供给程序员简单高效的开发一个web app

Servlet的主要工作

  • 允许程序员注册一个类,当Tomcat收到的某个特定的请求,执行这个类中的一些代码
  • 帮助程序员解析HTTP请求,把HTTP请求从一个字符串解析成一个HttpRequest对象
  • 帮助程序员构造HTTP响应,程序员只要给指定的HttpResponse对象填写一些属性字段,Servlet就会自动按照HTTP协议的方式构造出一个HTTP响应字符串,并通过Socket编写返回客户端

Servlet程序创建步骤

创建项目

使用IDEA创建一个Maven项目

创建好的Maven如下

通过上图我们可以了解到一些目录结构,这是Maven项目的标准结构,其中

  • src:用于存放源代码和测试代码的根目录
  • main:用于存放源代码的目录
  • test:用于存放测试代码的目录
  • java:用于存放Java代码的目录
  • resoures:用于存放依赖的资源文件
  • pom.xml:是Maven项目的核心配置文件,关于这个Maven项目的相关属性,都是在这个xml中进行配置的

引入依赖

Maven项目创建完成之后,就i会自动生成一个pom.xml文件,我们需要在这个文件中引入Servlet API依赖的jar包

  • 打开中央仓库,搜索Servlet,点击 Java Servlet API

  • 选择对应的Tomcat版本的Servlet

  • 将中央仓库提供的该版本的xml复制到项目的pom.xml中
  • 修改后的pom.xml如下

 一个项目中可以有多个依赖,每个以来都是一个<dependency>标签,引入的以来都要放在一个<dependencies>标签中,该标签用于放置项目以来的jar包,Maven会自动下载到该依赖带本地

创建目录

Web项目对于目录结构还有自己的要求只有Maven的标准目录还是不够的,需要再创建以下目录进行配置

  • 在main目录下,创建一个webapp目录:webapp目录就是用于部署到Tomcat的一个重要的目录,里面可以存放一些静态资源 
  • 在webapp目录下,创建一个WEB-INF目录
  • 在WEB-INF目录下,创建一个web.xml文件:Tomcat通过找到这个web.xml文件才能正确的处理webapp的动态资源

  • 编写 web.xml:Selvlet中的web.xml中的内容是不能为空的,里面的写法是固定的(这里的写法专属于serlet),用到的时候可以直接拷贝下面的代码
<!DOCTYPE web-app PUBLIC
		"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
		"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
	<display-name>Archetype Created Web Application</display-name>
</web-app>

 编写代码

以下编写一个让相应返回一个自定义的字符串代码:

  • 创建一个TestServlet类,并且让他继承于HttpServlet

HttpServlet 这个类来自于pom.xml中引入的Servlet API依赖的jar包

  • 在TestServlet类中重写doGet方法

doGet是HttpServlet类中的方法,此处是在子类中重写了父类的doGet

  • 为了了解doGet方法的作用,我们可以看看它的源码

  • HttpServletRequest:表示HTTP请求,Tomcat按照HTTP请求的格式把字符串格式的的请求转换为一个HttpServletRequest对象,通过这个对象就可以获取请求中的信息
  • HttpServletResponse:表示HTTP响应,通过代码可以把响应的对象构造好,然后Tomcat将响应返回给浏览器

通过doGet的源码我们可以大致的了解,它的作用就是根据收到的请求通过响应返回一个405或者400,那么我们可以重写这个方法,根据收到的请求执行自己的业务逻辑,把结果构造成响应对象

  • 在doGet方法中,通过HttpServletResponse类的getWriter()方法往响应的body中写入文本格式的数据

resp.getWriter()会获取到一个流对象,通过这个流对象就可以写入一些数据,写入的数据会被构造成一个HTTP相应的body部分,Tomcat会把整个响应转换成字符串,通过Socket写回给浏览器

  • 需要给TesttServlet加上一个特定的注解@Webservlet("/test")

上述注解表示Tomcat收到请求中,URL的Servlet Path路径为/test的请求才会调用TestServlet这个类的代码,注解中的字符串表示着URL的Servlet Path

到这里程序的编写已经完成!但是你可能会议或上述代码不是通过main方法作为入口,这是因为main方法包含在Tomcat中,我们写的程序并不能单独执行,而是需要Tomcat才能执行起来(Tomcat的伪代码我们会具体的分析这个问题)

打包程序

在程序编写好之后,就可以使用Maven进行打包

  • 首先修改pom.xml,加入一些必要的配置(打包类型和打包后的包名)

  • packaging标签中用于设置打包类型(如果不进行修改,默认的类型是jar包,jar包是普通Java程序打包的结果,里面包含一i写.class文件;而部署在Tomcat中的压缩包一般为war包,war包里面是Java Web程序,里面除了.class文件以外还包含HTML、CSS、JavaScript、图片等...) 
  • finaName标签中用于设置打包好后的名字(包名很重要,它对应着请求中的URL的Context Path)
  • 执行打包操作(打开Maven窗口,展开Liftcycle,双击package进行打包) 

  • 打包成功之后,可以发现多了一个target目录,该目录下有一个testSevlet.war的压缩包

 部署程序

接下来我们就可以进行程序部署

  • 首先将大号的war包拷贝到Tomcat的webapps目录下

  • 启动Tomcat(bin目录下的startup.bat)

验证程序

此时通过浏览器访问 http://127.0.0.1:8080/testServlet/test 就可以看到程序实现的结果了

 注意:URL的路径分为两部分 Context Path 和 Servlet Path

  • Context Path:这个路径表示一个webapp,来与那与打包的包名
  • Servlet Path: 这个路径表示一个webapp中的一个页面,来源于对应的Servlet类@webServlet注解中的内容

安装Smart Tomcat进行部署

为了简化上述操作流程,其实是有一些更加简单的方式:

  • 对于创建项目、引入依赖、创建目录这三个步骤,其实可以使用项目模板来快速生成的,但是由于项目模板加载速度比较慢,因此这里不是很推荐
  • 对于打包和部署程序这两个步骤,其实可以使用Smart Tomcat插件来快速实现,以下将来介绍使用方式

安装Smart Tomcat

  • 在插件商店中搜索 Smart Tomcat进行安装

配置Smart Tomcat

  • 点击Edit Configurations(新版本),老版本可能是Add Configuration

  • 点击左上角的+号,并选择Smart Tomcat

  • 主要修改这三个参数

  • name:这一栏可以随意填写
  • Tomcat Servlet:表示Tomcat所在的目录
  • Deployment Directory:表示项目发布的目录
  • Context Path:表示项目路径,默认值就是项目名称
  • Servlet Port:表示服务端口
  • Admin Port:表示管理端口
  • VM options:表示JVM参数
  •  配置好的Smart Tomcat之后,Edit Configurations就会显示成name的名字,并且右面多一个三角形运行符号

使用Smart Tomcat

  • 点击三角号就可以进行正常的运行

  • 点击蓝色的链接就会跳转到项目路径,再增加Servlet Path就可以显示出该程序的结果 

Servlet运行原理

 在servlet代码中,我们并没有写main方法,那么对应的doGet方法是如何被调用的呢?响应又是如何返回给服务器的呢?

Servlet的架构

我们自己实现的Servlet实在Tomcat基础上运行的,下图显示了Web应用程序中的位置

当浏览器给服务器发送请求的时候,Tomcat作为HTTP服务器,就可以接收到这个请求,Tomcat的工作就是解析HTTP请求,并把请求交给Servlet的代码来进行进一步的处理,Servlet的代码根据请求计算生成响应对象,Tomcat再把这个响应对象构造成HTTP响应,返回给浏览器,并且Servlet的代码也经常会和数据库进行数据的传递。

Tomcat的伪代码

下面是通过Tomcat的伪代码的形式来描述Tomcat 初始化和处理请求两部分核心逻辑

Tomcat初始化流程

class Tomcat {

    // 用来存储所有的 Servlet 对象
    private List<Servlet> instanceList = new ArrayList<>();
    
    public void start() {
        // 根据约定,读取 WEB-INF/web.xml 配置文件
        // 并解析被 @WebServlet 注解修饰的类
        
        // 假定这个数组里就包含了我们解析到的所有被 @WebServlet 注解修饰的类.
        Class<Servlet>[] allServletClasses = ...;
        
        // 这里要做的的是实例化出所有的 Servlet 对象出来;
        for (Class<Servlet> cls : allServletClasses) {
            // 这里是利用 java 中的反射特性做的
            // 实际上还得涉及一个类的加载问题,因为我们的类字节码文件,是按照约定的
            // 方式全部在 WEB-INF/classes 文件夹下存放的,所以 tomcat 内部是
            // 实现了一个自定义的类加载器(ClassLoader),用来负责这部分工作。
            
            Servlet ins = cls.newInstance();
            instanceList.add(ins);
        }
        
        // 调用每个 Servlet 对象的 init() 方法,这个方法在对象的生命中只会被调用这一次
        for (Servlet ins : instanceList) {
            ins.init();
        }
        
        // 启动一个 HTTP 服务器,并用线程池的方式分别处理每一个 Request
        ServerSocket serverSocket = new ServerSocket(8080);
        // 实际上 tomcat 不是用的固定线程池,这里只是为了说明情况
        ExecuteService pool = Executors.newFixedThreadPool(100);
        
        while (true) {
            Socket socket = ServerSocket.accept();
            // 每个请求都是用一个线程独立支持,这里体现了 Servlet 是运行在多线程环境下的
            pool.execute(new Runnable() {
                doHttpRequest(socket);
            });
        }
        // 调用每个 Servlet 对象的 destroy() 方法,这个方法在对象的生命中只会被调用这一次
        for (Servlet ins : instanceList) {
            ins.destroy();
        }
    }
    
    public static void main(String[] args) {
    	new Tomcat().start();
    }
}
  • Tomcat的代码内置了main方法,当我们启动Tomcat的时候,就是从Tomcat的main方法开始执行的
  • 被@WebServlet注释修饰的类就会在Tomcat启动的时候就会被获取到,并且集中管理
  • Tomcat通过反射这样的语法机制来创建被@WebServlet注释修饰的类的实例
  • 这些实例被创建完之后,就会调用其中的init方法进行初始化
  • 这些实例被销毁之前,就会调用其中的destory方法进行收尾工作
  • Tomcat内部也是通过Socket API进行网络通信
  • Tomcat为了能够同时处理多个HTTP请求,采取了多线程的方式实现,因此Servlet是运行在多线程环境下的
  • Tomcat处理请求流程
class Tomcat {
    
    void doHttpRequest(Socket socket) {
        // 参照我们之前学习的 HTTP 服务器类似的原理,进行 HTTP 协议的请求解析和响应构建
        HttpServletRequest req = HttpServletRequest.parse(socket);
        HttpServletRequest resp = HttpServletRequest.build(socket);
        
        // 判断 URL 对应的文件是否可以直接在我们的根路径上找到对应的文件,如果找到,就是静态内容
            
        // 直接使用 IO 进行内容输出
        if (file.exists()) {
            // 返回静态内容
            return;
        }
        
        // 走到这里的逻辑都是动态内容了
        // 找到要处理本次请求的 Servlet 对象
        Servlet ins = findInstance(req.getURL());
        
        // 调用 Servlet 对象的 service 方法
        // 这里就会最终调用到我们自己写的 HttpServlet 的子类里的方法了
        try {
        	ins.service(req, resp);
        } catch (Exception e) {
        	// 返回 500 页面,表示服务器内部错误
        }
    }
}
  • Tomcat从socket中读到的HTTP请求是一个字符串,然后Tomcat会按照HTTP协议的格式解析成一个HttpServletRequest对象
  • Tomcat会根据URL中的Path判定这个请求是请求一个静态资源还是动态资源,如果是静态资源,直接找到对应的文件,把文件的容通过Socket返回;如果是动态资源,才会执行Servlet相关逻辑
  • Tomcat会根据URL中的Context Path和Servlet Path确定要调用哪个Servlet实例的service方法
  • 通过service方法,就会进一步调用我们重写的doGet或者doPost方法
  • Servlet的service方法的实现
class Servlet {
    public void service(HttpServletRequest req, HttpServletResponse resp) {
        String method = req.getMethod();
        if (method.equals("GET")) {
        	doGet(req, resp);
        } else if (method.equals("POST")) {
        	doPost(req, resp);
        } else if (method.equals("PUT")) {
        	doPut(req, resp);
        } else if (method.equals("DELETE")) {
        	doDelete(req, resp);
        }
        ......
    }
}
  • Servlet的service方法内部会根据当前的请求方式,决定调用其中的某个do...方法
  • 在调用do...方法的时候,就会触发多态机制,从而执行到我们自己写好的子类do...方法

Servlet API 详解

对于Servlet主要介绍三个类,分别是HttpServlet、HttpServletRequest和HttpServletResponse

其中HttpSerletRequest和HttpServletResponse是Servlet规范中规定的两个接口,HttpServlet中并灭有实现这两个接口的成员变量,它们知识HttpServlet的service和do...等方法的参数,这两个接口类的实例化是在Servlet容器中实现的。

HttpServlet

核心方法

                                        方法名称                                                调用时机

                                            init

                         在HttpServlet实例化之后被调用一次
                                         destory                      在HttpServlet实例不再使用的时候调用一次
                                        service                                    收到HTTP请求的时候调用
                                        doGet                    收到GET请求的时候调用(由service方法调用)
                                       doPost                   收到POST请求的时候调用(由Service方法调用)
                   doPut/doDelete/doOptions...                    收到其他请求的时候调用(由Service方法调用)

Servlet的生命周期:Servlet的生命周期就是Servlet对象从创建到销毁的过程,下面就来介绍其生命周期的过程

  • Servlet对象由Tomcat来进行实例化的,并且在实例化完毕之后调用init方法(只调用一次
  • Tomcat对于收到的请求,都会通过对应的Servlet的service方法来进行处理(可以调用多次
  • Tomcat结束之前,会调用Servlet的destory方法来进行回收资源(最多调用一次

注意:init和service能够保证在各自的合适时机被Tomcat调用,但是destory不一定,它是否能够被调用取决于Tomcat是如何结束的

  • 如果是直接杀死进程,那么就来不及调用destory
  • 如果是通过Tomcat的管理端口(默认是8005)进行关闭,就能够调用destory

处理GET请求示例:

  • 直接通过URL发送一个GET方法的请求,来对这个请求进行处理
@WebServlet("/get")
public class TestServlet extends HttpServlet{
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/plain");
        resp.setCharacterEncoding("UTF-8");
        resp.getWriter().write("响应了一个Get请求");
    }
}

处理POST请求示例:

由于通过浏览器URL发送的请求是GET方法的请求,因此我们需要通过其他方式来发送一个POST请求用于处理。发送POST请求的方式有Ajax、form表单或者Socket API 构造,如果单纯用于测试就比较麻烦,所以我们可以用一个软件postman,是一个强大的API调试、Http请求的工具

@WebServlet("/post")
public class TestServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("post");
    }
}

HttpServletRequest

核心方法 

                                             方法                                                 作用
                        String getProtocol()                        返回协议的名称和版本号

                        String getMethod()

                        返回请求的HTTP方法名称

                         String getRequesURL()

                        返回请求的URL,不带查询字符串
                        String getRequestURI()         返回URL的一部分,不带协名,端口号,查询字符串
                        String getContextPath()

                        返回指示请求URL中的Context Path 部分

                        String getServletPath()                        返回首行中的Servlet Path 部分
                        String getQueryString()                             返回首行后面的查询字符串
                        Enumeration getParameterNames()     返回一个String 对象的枚举,包括在该请求中的参数名称
                        String getParameter(String name)   以字符串形式返回请求参数的值,如果参数不存在则返回null
                String[] getParameterValues(String name)返回一个字符串对象的数组,包括所有给定的请求参数,如果参数不存在返回null
                        Enumeration getHeaderNames()                    返回一个枚举,包括该请求中所有的头名
                        String getHeader(String name)                        以字符串形式返回指定的请求头的值
                        String getCharacterEncoding()                        返回请求正文中使用的字符编码的名称
                        String getContentType()        返回请求正文的 MIME 类型,如果不知道类型则返回 null
                        int getContentLength()以字节位单位返回请求正文的长度,并提供输入流,如果长度未知则返回-1
                        InputStream getInputStream()        用于读取请求的正文内容,返回一个 InputStream 对象
  •  示例一:通过上诉方法返回一个页面是该请求的具体HTTP请求格式
@WebServlet("/showRequest")
public class TestServlet extends HttpServlet{
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //此处返回一个HTMl,在HTML中显示 HttpRequestServlet 类中的核心方法
        //把这些API 的返回结果 通过 StringBuilder进行拼接
        resp.setContentType("text/html;charset=utf-8");
        StringBuilder html = new StringBuilder();
        html.append(req.getMethod());
        html.append(" ");
        html.append(req.getRequestURL());
        html.append("?");
        html.append(req.getQueryString());
        html.append(" ");
        html.append(req.getProtocol());
        html.append("</br>");
        Enumeration<String> headerNames = req.getHeaderNames();
        while(headerNames.hasMoreElements()){
            String headName = headerNames.nextElement();
            String header = req.getHeader(headName);
            html.append(headName);
            html.append(": ");
            html.append(header);
            html.append("</br>");
        }
        html.append("</br>");
        resp.getWriter().write(html.toString());
    }
}

  •  实例二:处理HTTP请求的body中的数据格式

如果body的内容格式是x-www-form-urlencoded(这是form表单提交的数据格式,此时body的格式就类似于 query string (是键值对的结构,键值对之间使用&分割,键与值之间使用=进行分割),使用getParameter(获取键值对)进行处理

  • 此处是要获取body的数据,由于GET方法一般没有body,这里使用POST方法演示
  • 约定body的数据格式是:x-www-form-urlencoded
  • 约定body的数据内容是:username=123&password=111
@WebServlet("/postParameter")
public class TestServlet extends HttpServlet{
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf-8");
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        resp.getWriter().write("username=" + username + "</br>" +"passwd=" + password);
    }
}

如果body的内容格式是json,首先将整个body都读取出来,再借助第三方库方法按照json的格式来进行解析,Java标准库没有内置对于json解析的方法

  • 此处是要获取body的数据,由于GET方法一般没有body,这里使用POST方法进行演示
  • 约定body的数据格式为:json
  • 约定body的数据内容为:

{

username:123,

password:111

}

  • 此处使用jackson第三方库,使用之前需要去Maven的中央仓库将jackson的依赖引入pom.xml中

jacjkson中的核心类是ObjectMapper,通过这个类的readValue(Stringcontent,Class<T> valueType)方法,就可以将json字符串转化为一个类的对象(第一个参数是json字符串,第二个参数是类对象),ObjectMapper会遍历定义的类中的每一个成员的名称,去json字符串中的key中查找,如果找到了就将对应的值返回给该成员

/自定义的将json字符串转化的类
class UserInfo{
    public String username;
    public String password;
}
@WebServlet("/jsonParameter")
public class TestServlet extends HttpServlet{
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf-8");
        //将整个body中的数据读取出来
        String body = readBody(req);
        //按照 json、格式进行解析
        ObjectMapper objectMapper = new ObjectMapper();
        UserInfo userInfo = objectMapper.readValue(body,UserInfo.class);
        resp.getWriter().write("username=" + userInfo.username + "</br>" + "passwd=" + userInfo.password);
    }

    private String readBody(HttpServletRequest req) throws IOException {
        int contextLength = req.getContentLength();
        //准备一个字节数组,来存放body内容
        byte[] buffer = new byte[contextLength];
        //获取·到InputStream对象
        InputStream inputStream = req.getInputStream();
        inputStream.read(buffer);
        //将存放在body内容的字节数组转换成字符串
        return new String(buffer,"utf-8");
    }
}

HttpServletResponse

核心方法

                           方法                                                       作用
        void setStatus(int sc)

                                           为该响应设置状态码

  void setHeader(String name,String value )设置一个带有给定的名称和值的header,如果name已经存在,则会覆盖该值
  void addHeader(String name,String value)添加一个带有给定的名称和值的header,如果name已经存在,不覆盖旧值,而是添加一个新的键值对
          void setContentType(String type)                                设置被发送到客户端的响应的内容类型
   void setCharacterEncoding(String charset)                        设置被发送到客户端的响应的字符编码,例如utf-8
           void sendRedirect(String location)                                        设置Location字段,实现重定向
                     PrintWriter getWriter()                                   用于往body中写入文本格式的数据
        OutputStream getOutputStream()                                  用于往body中写入二进制格式数据

 示例一:通过代码,构造出不同的响应状态码

@WebServlet("/status")
public class TestServlet extends HttpServlet{
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        int status = 404;
        resp.setStatus(status);
        resp.getWriter().write("status"+status);
    }
}

Fiddler抓包结果: 

  • 实例二:在响应报头设置一个Refresh字段,实现字段刷新程序
@WebServlet("/autoRefresh")
public class TestServlet extends HttpServlet{
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //给响应一个Refresh的header,每隔一秒刷新一次
        resp.setHeader("Refresh","1");
        //返回一个当前的时间,用来显示刷新效果
        resp.getWriter().write("timestamp: "+System.currentTimeMillis());
    }
}

  • 实例三:实现重定向操作

方法一:在响应报头设置状态码和Location来实现重定向

@WebServlet("/redirect")
public class TestServlet extends HttpServlet{
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //将状态码设置为3XX
        resp.setStatus(302);
        //设置一个Location
        resp.setHeader("Location","https://blog.csdn.net/loss_rose777?type=blog");
    }
}

方法二:直接使用sendRedirect()方法来实现重定向

@WebServlet("/redirect")
public class RedirectServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        resp.sendRedirect("https://blog.csdn.net/weixin_51367845?spm=1000.2115.3001.5343");
    }
}

实现服务器版表白墙程序

 基本介绍

下面是表白墙的内容展示

下面是程序代码:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        *{
            margin: 0px;
            padding: 0px;
        }
        .container{
            width: 400px;
            margin: 0 auto;
        }
        h1{
            text-align: center;
            padding-bottom: 20px;
        }
        p{
            text-align: center;
            color: gray;
            line-height:40px;
        }
        .button{
            text-align: center;
        }
        .edit{
            margin-bottom: 20px;
            width: 200px;
            height: 30px;
        }
        .n1{
            margin-right: 60px;
        }
        .n2{
            margin-right: 41px;
        }
        .n3{
            margin-right: 60px;
        }
        .button{
            height: 40px;
            width: 300px;
            background-color: orange;
            color: white;
            border: none;
        }
        span{
            font-size: 20px;
        }
    </style>
</head>

<body>
    <div class="container">
        <h1>表白墙</h1>
        <p>输入后点击提交,会将信息显示在表格中</p>
        <div><span class="n1">谁:</span><input type="text" class="edit"></div>
        <div><span class="n2">对谁:</span><input type="text" class="edit"></div>
        <div><span class="n3">说:</span><input type="text" class="edit"></div>
        <div><input type="button" value="提交" class="button" onclick="submit()"></div>
    </div>
</body>
    <script>
        let edits = document.querySelectorAll('.edit')
        function submit(){
            let n1 = edits[0].value
            let n2 = edits[1].value
            let n3 = edits[2].value
            let div = document.createElement('div')
            div.className = "div1"
            div.innerHTML = n1+"对"+n2+"说"+n3
            let container = document.querySelector('.container')
            container.appendChild(div)
            alert(n1+"对"+n2+"说"+n3)
        }
    </script>
</html>

准备操作

1、创建一个Servlet项目

2、将之前写好的纯前端的表白前代码拷贝到webapp目录下

3、约定好前后端交互接口,该程序只需要约定两个接口

  • 从服务器获取全部留言
  • 约定请求:方法时GET,请求路径是/message
  • 约定响应:版本号为HTTP/1.1,状态码是200 OK,采用的是JSON格式
  • JSON具体请求格式:
[
    {
        from:"",
        to:"",
        message:""
    }
]
  • 通过客户端给服务器新增一个留言
  • 约定请求:方法为POST,请求路径为/message
  • 约定响应:版本号为HTTP/1.1,状态码为 200 OK,提交成功响应页面显示“提交成功”

4、创建一个MessageServlet类。@WebServlet注解为 /message,对应着约定的请求路径,通过上方的约定完成服务器代码

5、更改前端代码

代码实现

后端代码实现:

import com.fasterxml.jackson.databind.ObjectMapper;

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

//这个类表示一条消息的详细信息
class Message{
    public String from;
    public String to;
    public String message;
}
@WebServlet("/message")
public class messageServlet extends HttpServlet {
    //通过这个数组来表示所有消息
    private List<Message> messages = new ArrayList<>();
    //获取服务器所有消息操作
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("application/json;charset=utf-8");
        //获取到消息列表
        //此处需要将message数组中的数据转换成json格式返回给浏览器
        ObjectMapper objectMapper = new ObjectMapper();
        //通过ObjectMapper 的 writeValueAsString() 方法就可以将一个对象转换成一个json字符串
        String jsonString = objectMapper.writeValueAsString(messages);
        resp.getWriter().write(jsonString);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf-8");
        ObjectMapper objectMapper = new ObjectMapper();
        Message message = objectMapper.readValue(req.getInputStream(),Message.class);
        messages.add(message);
        resp.getWriter().write("提交成功!");
    }
}

前端代码实现:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>表白墙</title>
    <!-- 引入Jquery -->
    <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
    <style>
        * {
            margin: 0px;
            padding: 0px;
        }

        .container {
            width: 400px;
            margin: 0 auto;
        }

        h1 {
            text-align: center;
            padding-bottom: 20px;
        }

        p {
            text-align: center;
            color: gray;
            line-height: 40px;
        }

        .button {
            text-align: center;
        }

        .edit {
            margin-bottom: 20px;
            width: 200px;
            height: 30px;
        }

        .n1 {
            margin-right: 60px;
        }

        .n2 {
            margin-right: 41px;
        }

        .n3 {
            margin-right: 60px;
        }

        .button {
            height: 40px;
            width: 300px;
            background-color: orange;
            color: white;
            border: none;
        }

        span {
            font-size: 20px;
        }
    </style>
</head>

<body>
    <div class="container">
        <h1>表白墙</h1>
        <p>输入后点击提交,会将信息显示在表格中</p>
        <div><span class="n1">谁:</span><input type="text" class="edit"></div>
        <div><span class="n2">对谁:</span><input type="text" class="edit"></div>
        <div><span class="n3">说:</span><input type="text" class="edit"></div>
        <div><input type="button" value="提交" class="button"></div>
    </div>
</body>
<script>
    let edits = document.querySelectorAll('.edit')
    let Button = document.querySelector('.button');
    Button.onclick = function () {
        //1、获取到输入框中的三个请求
        let n1 = edits[0].value
        let n2 = edits[1].value
        let n3 = edits[2].value
        if (n1 == '' || n2 == '' || n3 == '') {
            return;
        }
        //2、构造新的div
        let div = document.createElement('div')
        div.className = "div1"
        div.innerHTML = n1 + "对" + n2 + "说" + n3
        let container = document.querySelector('.container')
        container.appendChild(div)
        //3、清空之前输入框的内容
        for (let input of edits) {
            input.value = '';
        }
        //4、通过ajax构造post请求,把这个请求提交给服务器
        //构造一个js对象
        let body = {
            from: n1,
            to: n2,
            message: n3
        };
        $.ajax({
            type: 'post',
            contentType: "application/json;charset=utf-8",
            //设置服务器地址的所在位置 使用相对路径表示
            url: 'message',
            //完成json对象和json格式字符串转换
            data: JSON.stringify(body),
            success: function (body) {
                //这是响应成功之后,要调用的回调函数
                console.log("消息发送给服务器成功!")
            }
        });
    }
    //在页面加载的时候,希望能够从服务器获取到所有的消息,并显示到网页中
    $.ajax({
        type: 'get',
        url: 'message',
        success: function (body) {
            //body是收到响应的正文 由于在响应中我们设置的是 application/json 所以此时收到的body会被jquery自动把它
            //从字符串转换为js对象数组,此处就不需要手动进行JSON.parse
            for (let message of body) {
                //构造新的div
                let div = document.createElement('div')
                div.className = "div1"
                div.innerHTML = message.from + "对" + message.to + "说" + message.message
                let container = document.querySelector('.container')
                container.appendChild(div)
            }
        }
    });
</script>

</html>

 

 持久化存储

上述代码,我们实现了,前端与后端的交互,以及服务器的部署,但是我么可以保证在页面刷新的时候不会将数据清楚,但是当我们服务器关闭的时候,数据还是会消失,为了解决这个问题,就需要让数据能够持久化存储

持久化存储:是把数据保存到可以永久保存的存储设备中(如硬盘),是一种将程序数据在持久状态和瞬时状态间转换的机制

持久化存储机制包括:JDBC和文件IO 

1、建立数据库

drop database if exits messagewall;
create database messagewall;

use messagewall;

drop table if exits message;
create table message (
    `from` varchar(50),
    `to` varchar(50),
    `message` varchar(1024)
);

2、在pom.xml中引入mysql的jar包

 3、将代码与数据库进行连接(主要是将list数组删除,添加一个save和load方法)

import com.fasterxml.jackson.databind.ObjectMapper;
import com.mysql.cj.jdbc.MysqlDataSource;

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 javax.sql.DataSource;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

//这个类表示一条消息的详细信息
class Message{
    public String from;
    public String to;
    public String message;
    @Override
    public String toString() {
        return "Message{" +
                "from='" + from + '\'' +
                ", to='" + to + '\'' +
                ", message='" + message + '\'' +
                '}';
    }
}
@WebServlet("/message")
public class messageServlet extends HttpServlet {
    //通过这个数组来表示所有消息
    //private List<Message> messages = new ArrayList<>();
    //获取服务器所有消息操作
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("application/json;charset=utf-8");
        //获取到消息列表
        //此处需要将message数组中的数据转换成json格式返回给浏览器
        ObjectMapper objectMapper = new ObjectMapper();
        List<Message> messages = null;
        try {
            messages = load();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        //通过ObjectMapper 的 writeValueAsString() 方法就可以将一个对象转换成一个json字符串
        String jsonString = objectMapper.writeValueAsString(messages);
        resp.getWriter().write(jsonString);
    }
    //这个方法用来往数据库中存一条数据
    private List<Message> load() throws SQLException {
        List<Message> messages = new ArrayList<>();
        DataSource dataSource = new MysqlDataSource();
        ((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/message?characterEncoding=utf-8&useSSL=false");
        ((MysqlDataSource)dataSource).setUser("root");
        ((MysqlDataSource)dataSource).setPassword("");
        Connection connection = dataSource.getConnection();
        String sql = "select* from message";
        PreparedStatement statement = connection.prepareStatement(sql);
        ResultSet resultSet = statement.executeQuery();
        while(resultSet.next()){
            Message message = new Message();
            message.from = resultSet.getString("from");
            message.to = resultSet.getString("to");
            message.message = resultSet.getString("message");
            messages.add(message);
        }
        resultSet.close();
        statement.close();
        connection.close();
        return messages;
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf-8");
        ObjectMapper objectMapper = new ObjectMapper();
        Message message = objectMapper.readValue(req.getInputStream(),Message.class);
        try {
            save(message);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        System.out.println("消息提交成功! message=" + message);
        resp.getWriter().write("提交成功!");
    }

    private void save(Message message) throws SQLException {
        DataSource dataSource = new MysqlDataSource();
        ((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/message?characterEncoding=utf-8&useSSL=false");
        ((MysqlDataSource)dataSource).setUser("root");
        ((MysqlDataSource)dataSource).setPassword("");
        Connection connection = dataSource.getConnection();
        String sql = "insert into message value(?,?,?)";
        PreparedStatement statement = connection.prepareStatement(sql);
        statement.setString(1,message.from);
        statement.setString(2, message.to);
        statement.setString(3, message.message);
        statement.executeUpdate();
        statement.close();
        connection.close();
    }
}

Cookie和Session

Cookie

Cookie介绍我们已经在上一篇Http中说的很详细了Cookie介绍​​​​​​

 在了解Cookie之后,我们发现Cookie是不能够用于存储和用户直接相关的信息的,一是Cookie的存储空间有限,二是发送请求时占用的带宽很多,三是不安全。即这些数据不适合保存到客户端,保险粗在服务器是最合适的,通过会话(Session)的方式就能够保存这些数据。

Session会话机制介绍 

基本介绍:

在计算机中,尤其是网络应用中,Session称为“会话控制”。Session对象存储特定用户会话所需的属性及配置信息,当用户在应用程序的Web页面跳转时,存储在Session对象中变量将不会丢失,而是在整个用户会话中一直存在下去。当用户请求来程序的Web页时,如果该用户还没有会话,则Web服务器将自动会创建一个Session对象,当会话过期或放弃后,服务器会终止该会话。Session对象最常见的一个用法就是存储用户首选项,例如,如果用户知名不喜欢查看图形的时候,就可以将该信息储存在Session对象中,注意会话状态仅在支持Cookie的浏览器中保存。

会话本质:

会话本质就是一个哈希表,其中存储一些键值对结构,key叫做sessionnId,是一个不随机的,不重复的,唯一的字符串,value就是要保存的身份信息,通过HttpSession对象来保存,key和value都是Servlet自动创建的。

每个用户登录都会产生一个会话,服务器会以哈希表的方式将这些会话管理起来

一个会话的详细数据通过一个HttpSession对象来存储,并且HttpSession对象中存储的数据也是键值对结构,key和value都是程序员自定义的

 接着Cookie不适合用于存储用户相关的直接信息来讲,由于客户端不适合存储这些数据,服务器这边可以通过Session会话方式来进行保存。下面将会以用户的登录流程来介绍Session会话机制

  • 当用户登录成功之后,服务器会在Session中生成一个新的记录,并把sessionId返回给客户端(例如Http响应中可以通过Set-Cookie字段返回,其中Cookie的key为"JSESSION",value为服务器生成的sessionId的具体的值)
  • 然后客户端只需要保存这个sessionId,当后续再给服务器发送请求的时,请求中会带上sessionIdd(例如HTTP请求中会带上Cookie字段用于传递Session)
  • 服务器收到请求之后,就会根据请求中的sessionId在Session中查询对应用户的身份信息,在后续操作

Session会话机制的好处

  • 是客户端很轻量,不在保存太多的数据
  • 客户端和服务器之间传输的数据量少,节省带宽
  • 数据都在服务器存储,即使客户端出现问题,数据也不会丢失

注意:Servlet的session默认是保存在服务器内存中的,如果重启服务器Session数据会消失 

Cookie和Session的区别

  • Cookie是客户端存储数据的一种机制,可以存储身份信息,也可以存储其他信息,是键值对结构的
  • Session是服务器存储数据的一种机制,主要适用于存储身份相关的信息,是键值对结构的
  • Cookie和Session经常配合使用,但是不是必须的
    • Cookie也完全可以保存一些数据在客户端,这些数据不一定是用户的身份信息,不一定是sessionId
    • Session中的sessionId也不需要非得通过Cookie和Set-Cookie来传递

Servlet中Cookie和Session的核心方法

HttpServletRequest类中相关的方法

                                             方法                                                作用
                        HttpSession getSession(参数)在服务器中获取会话,参数如果是true,当不存在会话时,会创建一个会话(包括生成一个新的sessionId和HttpSession对象),并通过Set-Cookie将sessionId返回给客户端;参数如果是false,当不存在会话时会返回null。如果存在sessionIOd且合法,就根据这个sessionId找到对应的HttpSession对象并返回
                                Cookie[] getCookies()返回一个数组,包含客户端发送请求的所有Cookie对象,会自动把Cookie中的格式解析成键值对

HttpServletresponse类中的方法 

                                            方法                                                作用
                     void addCookie(Cookie cookie)                                把指定的cookie添加到响应中

 HttpSession类中相关方法

  • HttpSession是Java平台对session机制的实现规范,因为它仅仅是个接口,具体实现为每个Web应用服务器的提供商
  • 服务器会为每一个用户创建一个和独立的HttpSession,表示为一个会话,并且为一个会话,并且一个HttpSession对象中包含多个键值对,可以往HttpSession中存储需要的数据
                                          方法                                                  作用
                    Object getAttribute(String name)该方法返回在Session会话中会有指定名称的对象,如果没有指定名称的对象,返回null
          void setAttribute(String name,Object value)        该方法使用指定名称绑定一个对象到该Session会话中
                                boolean isNew()                        判断当前会话是否是先创建的

Cookie类中的相关方法

  • 这个类描述了一个Cookie,通过Cookie类创建的对象,每一个对象就是一个键值对
  • HTTP的Cookie字段中实际上存储的是多个键值对,每一个键值对在Servlet中都对应一个Cookie对象
                                          方法                                               作用
                                String getName()该方法返回的Cookie名称(这个值是Set-Cookie字段设置给浏览器的,创建之后不会改变)
                                String getValue()

                        该方法获取与Cookie关联的值

                     void setValue(String newValue)

                        该方法设置与Cookie关联的值

实现用户登录功能

接下来将使用上述的Session和Cookie相关的方法来实现一个用户登录功能,并且可以记录访问页面次数

登录功能实现思路:

  1. 读取用户提交的用户和代码
  2. 对用密码进行校验
  3. 判断是否登录成功
  4. 创建会话,保存自定义信息
  5. 重定向到指定页面

登录功能实现流程:

  • 实现一个登录页面
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <form action="login" method="post">
        <input type="text" name="username"><br>
        <input type="password" name="password"><br>
        <input type="submit" value="登录">
    </form>
</body>
</html>

  • 实现一个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 javax.servlet.http.HttpSession;
import java.io.IOException;

@WebServlet("/login")
public class loginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf-8");
        //1、从请求中获取到用户名和密码
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        //2、对账号密码进行校验
        if(username==null||username.isEmpty()||password==null||password.isEmpty()){
            resp.getWriter().write("账号或密码不可以为空!");
            return;
        }
        //3、判断是否登录成功(假设用户名为 admin,密码为 1234。不过账号密码应该用数据库存储,这里只是用来测试)
        if(!username.equals("admin") || !password.equals("1234")){
            resp.getWriter().write("<h3>账号或密码错误!</h3>");
            return;
        }
        //4、登录成功,创建一个会话,用来记录当前用户信息
        HttpSession session = req.getSession(true);
        //通过这个操作,程序员就可以给session增加自定义信息,如访问次数
        session.setAttribute("visitCount",0);
        //5、把登录成功的结果返回给客户端(这里的反馈不是简单的提示登陆成功,而是直接跳转到指定页面)
        resp.sendRedirect("index");
    }
}
  • 通过实现一个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 javax.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/index")
public class IndexServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf8");
        //只有登陆成功参数才是true,这里是拿参数,所以填false
        HttpSession session = req.getSession(false);
        //判断当前用户是否登录
        if(session == null){
            //返回登录界面
            resp.sendRedirect("login.html");
            return;
        }
        //表示用户登录过,获取会话中的访问次数
        Integer visitCount = (Integer) session.getAttribute("visitCount");
        visitCount+=1;
        session.setAttribute("visitCount",visitCount);
        resp.getWriter().write("<h3>visitCount = " + visitCount + "</h3>");
    }
}

实现效果如下:

上传文件操作

上传文件是日常开发中的一类常见的需求,在Servlet中也进行支持

Servlet中上传文件的核心操作

HttpServletrequest类中的核心方法

方法作用
Part getPart(String name)获取请求中给定name的文件
Collection<Part> getParts()获取所有的文件

Part类中的相关方法

方法作用
String getSubmittedFileName()获取提交的文件名
String getContentType()获取提交文件类型
long getSize()

获取文件大小,单位为字节

void write(String path)把提交的文件数据写入磁盘文件

上传文件操作实现

1、写一个前端页面,用于上传文件

  • 上传一个文件一啊不能使用post请求的表单实现
  • 通过from表单构造上传文件,要加上以恶搞enctype字段,它表示的是body中的数据格式,它的默认值为:x-www-form-urlencoded,这里要修改成:multipart/form-data,他是上传文件或者图片的数据格式
  • 第一个input中的type的值为file,它表示第一个输入框为文件选择框,naem的值与后端中通过getPart获取指定文件的操作有关
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <form action="upload" method="post" enctype="multipart/form-data">
        <input type="file" name="Myfile" id="">
        <input type="submit" value="上传">
    </form>
</body>
</html>

2、写一个Servlet用于处理上传的文件

  • 上传文件操作还需要给Servlet嘉善一个@MultipartConfig注解,否则服务器代码无法受用getPart()方法
  • getPart()方法中的参数和form表单input="file"标签的name属性对应
  • 客户端一次可以提交多个文件,getPart()方法根据name属性来获取不同的文件
  • 写入磁盘文件操作的类路径之间可以使用两个反斜杠\\,也可以使用一个正斜杠/
  • 写入磁盘文件操作的路径最后为保存后的文件名,包括文件后缀
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.IOException;

@MultipartConfig
@WebServlet("/upload")
public class fileServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf-8");
        //通过getPart方法获取到前端传来的文件
        Part part = req.getPart("MyFile");
        //获取文件名
        String fileName = part.getSubmittedFileName();
        System.out.println("文件名:"+fileName);
        //获取提交的文件类型
        String fileType = part.getContentType();
        System.out.println("文件类型:"+fileType);
        //获取文件的大小
        long fileSize = part.getSize();
        System.out.println("文件大小为:"+fileSize);
        part.write("D:\\test\\text.mp4");
        resp.getWriter().write("上传成功!");
    }
}

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

lose_rose777

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

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

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

打赏作者

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

抵扣说明:

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

余额充值