Servlet

1.编写一个简单的Servlet程序 

1.1 创建项目

 

 Maven是JAVA中一个常用的"构建工具".

一个程序,编写过程中,往往需要涉及到一些第三方库的依赖,另外还需要对这个写好的程序进行打包部署.

Maven存在的意义,就是为了能够方便的进行依赖管理和打包.

1.2 引入依赖

当前的代码要使用Servlet开发,而Servlet并不是java标准库自带的,就需要让maven能够把Servlet的依赖给获取出来.

在maven中央仓库中找到依赖.

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>

手动创建一个<dependencies>标签,把刚才的坐标复制到这个标签里.

此时IDEA就会自动的通过maven从中央仓库来下载这里的依赖.

 

1.3 手动创建一些必要的目录文件

 

此处的目录结构,目录名字,都是固定的.

web.xml就是告诉tomcat,现在这个目录里的东西就是一个webapp,要把加载起来.

当然,web.xml里,需要写一些固定的内容.

<!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>

1.4 编写代码

 这里简单写一个Hello World

  • 这个doGet方法不需要手动调用,doGet本质上也是一个"回调函数".

把这个方法写好之后,就会交给Tomcat,Tomcat在收到一个合适的GET请求的时候,就会自动调用doGet.

调用doGet的时候,tomcat就会解析这次的HTTP请求.生成一个HTTPServletRequest对象.(这个对象里的属性什么的都是和HTTP协议格式匹配的)

同时,Tomcat也会构造出一个空的HTTPServletResponse对象(这个空不是null,而是一个new好的,但是没有初始化属性的对象,把这个resp对象也会传递到doGet里面)

doGet要做的事情,就是根据这次请求,计算出响应.

doGet里的代码,就是根据rep里不同的参数的细节,生成一个具体的resp对象(往空对象里设置属性)

tomcat就会根据这个响应对象,转换成符合HTTP协议的响应报文,返回给浏览器了.

总之,doGet做的事,就是根据请求计算出响应.


一个Servlet程序里,可能有多个Servlet类的.

这些Servlet类,就需要在不同的情况下被执行到了.

当请求的路径中带有hello的时候,才能执行到这个HelloServlet的代码.

不同的Servlet类,就可以关联到不同的路径.

问:

这个代码写完了吗,不需要一个main方法嘛?

  • Servlet程序,不需要main方法!!!
  • 一个程序,是需要main方法,作为入口.
  • 实际上,我们写的这个代码,并不是独立的程序,而是放到Tomcat上执行的.
  • main方法其实是在tomcat里的.
  • 我们写的这些doGet之类的,都是让Tomcat来调用的.

1.5 打包程序

我们的程序是放在Tomcat上执行的,就需要对程序进行打包.

打成一个Tomcat能识别的包的格式,此时代码才会被Tomcat给加载起来.

打包,也是借助maven完成的.

 

看到BUILD SUCCESS即为打包成功.

此时,就会出现下述内容.

在maven中,默认打包生成的是jar包,但是tomcat需要的是war包!

此时就需要修改pom.xml,添加一个<packaging>

当然,也可以给打包命名

 下面重新打包,就可以把这个war包放到tomcat里了.

1.6 部署

 把写好的war包,放到Tomcat上.

具体就是,把这个war包拷贝到Tomcat的webapps的目录中.

启动Tomcat即可.

1.7 验证程序,是否能够正常工作

通过浏览器发起HTTP Get请求,触发刚才的Servle代码.

 

通过第一级路径,确定一个webapp

通过第二级路径,确定哪个Servlet

通过方法,确定执行Servlet中的哪个方法 

2. Smart Tomcat插件

观察上述程序,如果要修改代码,就得重复步骤5,6,7.比较麻烦

这个时候就可以使用Smart Tomcat插件,让IDEA和Tomcat集成起来.

第一次使用Smart Tomcat需要简单配置

 

点击这里,就可以运行了. 


  • 这是怎么回事呢?

因为我们前面启动了Tomcat,之前的Tomcat已经占用了8080,一个端口号只能被一个进程绑定.

把之前的Tomcat关闭,再启动:

此时,启动成功.

3. 一些访问出错

3.1 404

 浏览器要访问的资源,在服务器上不存在!!!

  • ①检查你请求的路径,和你服务器这边的配置,是否一致.

  • ②确认你的webapp是否被正确加载.

Smart Tomcat由于只是加载这一个webapp,如果加载失败,就会直接启动失败.

拷贝war的方式,Tomcat要加载多个webapp,如果加载失败,只会有日志.(观察是否有"部署成功")

如果web.xml没有/目录错了/内容错了/名字拼写错了,都可能引起加载失败!!!

3.2 405

①写的doXX方法,和请求发起的方法,是不匹配的.

浏览器发起GET请求,服务器代码写的是doPost

在浏览器地址栏输入URL,发起的是get请求,但是服务器写的是doPost.


②发的是Get请求,服务器写的也是doGet,但是没有把supe.doGet给删了.

 

 

3.3 500

 服务器内部错误,代码中抛出异常了.

 

3.4 空白页面

往往是没有执行getWriter.write方法.

 

3.5 无法访问此网站

这种情况,要么就是Tomcat服务器没有正确运行.

要么就是ip或端口号编写的不对.

4.Servlet API

API就是一组类和方法.

Servlet中的类很多,这里详解其中三个

4.1 HTTPServlet

我们写 Servlet 代码的时候, 首先第一步就是先创建类, 继承自 HttpServlet, 并重写其中的某些方法,让Tomcat去调用到这里的逻辑.

4.1.1 核心方法

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

①init方法:

webapp被加载的时候,执行init,使用这个方法,进行一些初始化操作.

②destory方法:

webapp在被销毁的时候(Tomcat结束)执行destory,使用这个方法进行一些收尾工作.

当然,这个方法不保证能够被调用到.

1.通过8005端口,给Tomcat发起特殊的请求,Tomcat就关闭了.(能够执行destory)

2.直接杀死Tomcat进程.(无法执行到destory了)

③service方法:

每次收到请求,都会执行service,处理每个请求.

往往使用doXX方法替代service.


[面试题]谈谈Servlet的生命周期:

生命周期,更严格的理解"什么阶段,做什么事情".

就像人的幼儿,少年,青年,中年,老年,每个时期要做的事情是不一样的.

1)webapp刚被加载的时候,调用Servlet的init方法.

2)每次收到请求的时候,调用service方法.

3)webapp要结束的时候,调用destroy方法.


4.1.2 示例一:处理请求

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

@WebServlet("/method")
public class MethodServlet extends HelloServlet{
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("doGet");
    }

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

    @Override
    protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("doPut");
    }

    @Override
    protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("doDelete");
    }
}

针对上述的这三个方法,浏览器只能比较方便的构get请求,不太方便构造其它的.使用ajax或postman. 

 

4.2 HTTPServletRequest

HTTPServletRequest是和HTTP请求数据,是匹配的. 

 4.2.1 核心方法

方法描述
String getProtocol()返回请求协议的名称和版本。
String getMethod()返回请求的 HTTP 方法的名称,例如,GET、POST 或 PUT。
String getRequestURI()从协议名称直到 HTTP 请求的第一行的查询字符串中,返回该请 求的 URL 的一部分。
String getContextPath()返回指示请求上下文的请求 URI 部分。
String getQueryString()返回包含在路径后的请求 URL 中的查询字符串。
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()用于读取请求的 body 内容. 返回一个 InputStream 对象.

①String getRequestURI()方法:

URL唯一资源定位符,描述了网络上的一个资源.

URI唯一资源标识符.

URL也可以理解成URI的一种实现方式.

②Enumeration getParameterNames()和String getParameter(String name)方法:

可以通过一些方式,给服务器传递自定义数据.

1.query string

2.body(通过post form表单的形式提交的请求的话,此时body也是键值对格式)

query string本身就是键值对结构的数据,Tomcat收到这个请求之后,就会把这个query string解析成Map,使用getParameter就可以根据key获取到value.

③Enumeration getHeaderNames()和String getHeader(String name)方法:

获取到请求头里的键值对.

Tomcat收到请求之后也会把请求头解析长Map.

④InputStream getInputStream()方法:

读取这个流对象就能得到body的内容.

Ajax也可以把提交的数据放到query string中,使用getParameter获取.

如果使用ajax POST提交json格式的数据(或者其它非form表单的格式),就需要getInputStream来获取了.


4.2.2 示例一:打印请求信息

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.Enumeration;

@WebServlet("/request")
public class RequestServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //这个操作是必要的,显示告诉浏览器,你拿到的数据是html
        resp.setContentType("text/html");
        //调用req的各个方法,把得到的结果汇总到一个字符串中,统一返回到页面上.
        StringBuilder respBody = new StringBuilder();


        //下列内容是在浏览器上按照html的方式来展示的,此时\n在html中并不是换行.
        //而使用<br>标签来表示换行
        respBody.append(req.getProtocol());
        respBody.append("<br>");
        respBody.append(req.getMethod());
        respBody.append("<br>");
        respBody.append(req.getRequestURI());
        respBody.append("<br>");
        respBody.append(req.getQueryString());
        respBody.append("<br>");

        //拼接header
        Enumeration<String> headers = req.getHeaderNames();//枚举
        //迭代器遍历枚举里面的每个部分
        while (headers.hasMoreElements()){
            String header = headers.nextElement();//取回
            respBody.append(header);
            respBody.append(header + ": " + req.getHeader(header));//header的key和value
            respBody.append("<br>");
        }

        //返回结果
        resp.getWriter().write(respBody.toString());
    }
}

 


如何获取到query string和body的数据?

4.2.3 示例二:获取 GET 请求中的参数 

获取query string:

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("/parameter")
public class ParameterServlet extends HttpServlet {
    //约定,客户端使用query string传递数据
    //query string形如: username=zhangshan&password=123
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        System.out.println("username= "+ username);
        System.out.println("password= "+ password);
        resp.getWriter().write("ok");
    }
}


4.2.4 示例三: 获取 POST 请求中的参数

4.2.4.1 获取body(考虑form表单的格式)

form表单:和query string格式意义,也是键值对.

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("/parameter2")
public class Parameter2Servlet extends HttpServlet {
    //预期让客户端发送一个POST请求,同时使用form格式的数据,在body中把数据传送过来
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        System.out.println("username= "+ username);
        System.out.println("password= "+ password);
        resp.getWriter().write("ok");
    }
}

 

4.2.4.2 获取body(考虑json格式)

这种格式在开发中非常常见.

Servlet自身不能对json格式的数据进行解析,需要引入第三方库:

 <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.15.2</version>
        </dependency>
  • 那么如何使用ObjectMapper?

Map也叫做映射表.

把一个对象映射到JSON字符串,也可以把JSON字符串映射到对象.

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;


class User{
    public String username;
    public String password;
}

@WebServlet("/json")
public class JsonServlet extends HttpServlet {

    /**
     此处约定客户端body按照json格式来进行传输
     * {
     *     username:"zhangshan"
     *     password:"123"
     * }
     */

        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            ObjectMapper objectMapper = new ObjectMapper();
            User user = objectMapper.readValue(req.getInputStream(),User.class);//从字符串到对象
            System.out.println("username= " + user.username +", password= "+user.password);
            //也可以java对象转成json字符串
            String userString = objectMapper.writeValueAsString(user);
            System.out.println("userString: "+userString);
            resp.getWriter().write("ok");
    }

}

 

这个方法有很多版本,作用就是把json字符串给解析成JAVA对象.

其中这里第一个参数,是一个流对象,也就表示json从哪里来读.

请求的body是通过getlnputStream得到流对象,进一步读出来的.

请求的第二个参数,则是指定的类型.当前你这边得到的json字符串,需要转成什么样的一个java对象,需要指定一下对象的类型.


就可以定义一个类,让这个类里的属性的名字和类型都和json字符串匹配.

这里必须先知道json的结构是什么,才能够根据json结构,构造出User对象.

readValue就把req的body中的字符串读取并解析了,然后构造成了User对象,User对象中的属性,就是前面json中所体现的内容.


  • 在这里写json格式的时候,key也加上了",但是之前在注释里没写?

在ison格式中,key一定是字符串类型,所以原则上,key不写"也是完全可以的.

但是有些库/程序,检查更加严格,就必须强制写".

js的ajax方式构造json,此时key是没有引号的.

但是使用postman构造的json,此时key就需要引号.


  • 此处的public能不能写成private?

不能,会出现500.

本身,jackson会通过反射的方式,把User类里包含的public的属性给获取到.

此时,就可以根据反射这里得到的"属性名字",去json解析出来的键值对中进行匹配.

如果匹配到了,就把value设置到刚才得到的属性中.

由于把username改成了private,而Jackson并不会直接针对private属性进行扫描,username就不认识了.

当然,提供对应的getter和setter方法就可以写成private

[重点理解]jackson的readValue工作过程:

  • 先把json字符串解析成键值对,放到Map中.
  • 再根据参数填入的类对象,通过反射API就可以知道,这个类里面有什么属性,每个属性的名字和类型.
  • 一次把这里的每个属性都取出来,通过属性名字查询上述的Map,把得到的值,赋给这个类的属性.

 

4.3 HTTPServletResponse

HTTPServletResponse同样也是和HTTP响应数据,是匹配的.

针对状态码,各种header,body这些属性,就可以进行"设置".

  • 请求对象,我们拿到之后的目的,是为了获取里面的属性(读).
  • 响应对象, 我们拿到之后的目的,是为了设置里面的属性(写).

对于doXX这样的方法来说,本身要做的事情就是"根据请求计算响应".

  • 请求对象,是Tomcat收到请求之后,对HTTP协议解析得到的对象.
  • 响应对象,是Tomcat创建的空的对象,我们在代码中把响应对象的属性设置好.(响应对象,相当于是一个输出型参数)

4.3.1 核心方法 

方法描述
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)设置被发送到客户端的响应的字符编码(MIME 字符集)例如, UTF-8。
void sendRedirect(String location)使用指定的重定向位置 URL 发送临时重定向响应到客户端。
PrintWriter getWriter()用于往 body 中写入文本格式数据
OutputStream getOutputStream()用于往 body 中写入二进制格式数据.

①void setCharacterEncoding(String charset)方法:

这里是告诉浏览器,要按照什么样的字符集来解析响应的body.如果不去描述清楚,可能浏览器展示的内容就会乱码.

②void addHeader(String name, String value)方法:

使用addHeader,header中可能出现,key相同的两个键值对.

一般来说,约定键值对中的key是唯一的,但实践中,有些情况,确实也会需要key不唯一.

③void sendRedirect(String location)方法:

这个是特殊的方法,用来设置"重定向"响应.


4.3.2 示例一:设置状态码

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("/status")
public class StatusServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setStatus(200);
        //resp.sendError(404);
    }
}

 

其实可以在返回状态码的同时,给body写入数据,就可以得到一些"个性化的错误页面".

resp.sendError(404):


4.3.3 示例二:重定向

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("/redirect")
public class RedirectServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //让页面被重定向到 搜狗主页
        //resp.setStatus(302);
        //重定向响应,一定腰带有Location属性,
        //resp.setHeader("Location","http://www.sogou.com");
        resp.sendRedirect("http://www.sogou.com");
    }
}

 


4.3.4 示例三:自动刷新

可以使用setHeader设置任意的响应报头.

通过refresh属性,设置浏览器自动刷新.

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("/refresh")
public class RefreshServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setHeader("refresh","2");
        //返回系统时间,方便再次观察
        resp.getWriter().write("time: "+ System.currentTimeMillis());
    }
}

 

设置这个属性之后,浏览器就会每隔两秒自动刷新一次!!!

但这里的刷新的间隔也不是精准的2000ms,会比2000稍微多点.

必将,浏览器发起请求,服务器响应,知道页面被解析出来,都是需要消耗一定的时间的.


4.3.5 示例四:编码方式匹配

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("/body")
public class BodyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //让服务器返回一个html数据
        resp.getWriter().write("<div>你好<div>");
    }
}


  • 这是怎么回事?

浏览器,默认会跟随系统的编码.

windows简体中文版,默认的编码是gbk.

一般在IDEA里面直接写一个中文字符串,就是utf8的编码.

拿着utf8的数据,浏览器按照gbk的方式来解析,势必就会出现乱码!!!

解决乱码的原则,就是编码方式匹配.

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("/body")
public class BodyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //设置响应的字符集
        resp.setContentType("text/html;charset=utf8");
        //让服务器返回一个html数据
        resp.getWriter().write("<div>你好<div>");
    }
}

字符集,其实是ContentType的一部分.


这里要注意,给resp设置属性的时候,必须要注意顺序!!

先设置header,后设置body.

一旦开始设置body,就相当于header和status都定型了,已经来不及修改了.

 

  • 28
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值