学习JavaWeb(四)——Servlet

前言:

承接前面的内容来学习JavaWeb中的Servlet

 

 

 

 

 

 


什么是Servlet

  1. Servlet是JavaEE规范之一。规范,就是接口。
  2. Servlet是JavaWeb三大组件之一。(三大组件:Servlet程序、Filter过滤器、Listener监听器)
  3. Servlet是运行在服务器上的一个Java小程序,它可以接收客户端发送过来的请求,并相应数据给客户端。


创建Servlet程序

  1. 编写一个类去实现Servlet接口
  2. 实现Service方法,处理请求并相应数据
  3. 到web.xml中去配置servlet程序的访问地址

在上次的项目下,在包下面新建一个Java类,继承自Servlet,按下Alt+Insert来实现接口的几个方法。

public class HelloServlet implements Servlet {
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {

    }

    @Override
    public ServletConfig getServletConfig() {
        return null;
    }
    //services方法是专门用来处理请求和响应的
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("Hello Servlet!");
    }

    @Override
    public String getServletInfo() {
        return null;
    }

    @Override
    public void destroy() {

    }
}

然后我们来修改WEB-INF/web.xml,添加内容:

    <!--servlet标签给Tomcat配置servlet程序-->
    <servlet>
        <!--给servlet程序起一个别名(一般是类名)-->
        <servlet-name>HelloServlet</servlet-name>
        <!--程序的全类名-->
        <servlet-class>com.javaweb.test.HelloServlet</servlet-class>
    </servlet>
    <!--给servlet程序配置访问地址-->
    <servlet-mapping>
        <!--告诉服务器当前配置的地址给哪个Servlet程序使用-->
        <servlet-name>HelloServlet</servlet-name>
        <!--配置访问地址-->
        <!--
            / 斜杠在服务器解析的时候,表示地址为:http://ip:port/工程路径
            /Hello 表示地址为 http://ip:port/工程路径/Hello
        -->
        <url-pattern>/Hello</url-pattern>
    </servlet-mapping>

最后,我们启动程序,在自动打开的页面的url后加上 Hello访问,然后在IDEA控制台中,就可以看到输出Hello Servlet!

尚硅谷的这个流程图总结的非常好,我们来看看:


Servlet 学习

Servlet生命周期

在第一次访问时(创建实例)会调用:

  • Servlet 构造器方法
  • init 初始化方法

在每次访问都会调用

  •  service 方法

在 web 工程停止的时候调用

  • destroy 方法(释放内存)

 

Servlet 请求分发处理

我们需要判断请求的不同类型来做不同的处理

    //services方法是专门用来处理请求和响应的
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) 
                                          throws ServletException, IOException {
        System.out.println("Hello Servlet!");
        //类型转换(因为它有GetMethod()方法)
        HttpServletRequest httpServletRequest= (HttpServletRequest) servletRequest;
        String method=httpServletRequest.getMethod();
        if("GET".equals(method)){
            doGet();
        }else if("POST".equals(method)){
            doPost();
        }
    }

    public void doGet(){
        System.out.println("get");
    }
    public void doPost(){
        System.out.println("post");
    }

我们可以写一个简单的页面来测试:

<body bgcolor="#f4a460">
    <form action="http://localhost:8080/web_war_exploded/Hello" method="post">
        <input type="submit" />
    </form>
</body>

 

通过继承HttpServlet类实现

一般在实际项目开发中,都是使用继承 HttpServlet 类的方法去实现Servlet程序。

  1. 编写一个类去继承HttpServlet类
  2. 根据业务需要重写doGet或doPost方法
  3. 到web.xml中的配置Servlet程序的访问地址

我们来重写一个类来做示例,重创建一个 HelloServlet2,并且让它继承自 HttpServlet 。

在HttpServlet中,已经自动帮助我们根据请求方式分发好了get请求处理和post请求处理。

public class HelloServlet2 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("HelloServlet2的doget");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("HelloServlet2的dopost");
    }
}

然后我们去web.xml中配置,加上

    <servlet>
        <servlet-name>HelloServlet2</servlet-name>
        <servlet-class>com.javaweb.test.HelloServlet2</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>HelloServlet2</servlet-name>
        <url-pattern>/Hello2</url-pattern>
    </servlet-mapping>

然后将Test.html中表单的发送地址发送给Hello2,然后测试一下,和上面效果一样。

 

使用IDEA直接生成

在需要生成Servlet文件的包那里,右击,下面有个创造Servlet文件,点击即可。

非常方便。

它会在对应的web.xml中生成<Servlet></Servlet>,你再给它写一下servlet-mapping即可

    <servlet-mapping>
        <servlet-name>HelloServlet3</servlet-name>
        <url-pattern>/Hello3</url-pattern>
    </servlet-mapping>

 

Servlet继承体系

ServletConfig类

基本了解

我们学一下另一个东西换换脑——ServletConfig类

ServletConfig类是Servlet程序的配置信息类。

三大作用

  1. 可以获取Servlet程序的别名——servlet-name的值
  2. 获取初始化参数 init-param
  3. 获取 ServletContext 对象

 

回头实现Servlet接口的那个类——HelloServlet,看看初始化的方法 init,我们就会发现init的参数就是一个ServletConfig对象。

我们首先可以修改xml配置文件来添加初始化参数,以我们的第一个类为例:

    <!--servlet标签给Tomcat配置servlet程序-->
    <servlet>
        <!--给servlet程序起一个别名(一般是类名)-->
        <servlet-name>HelloServlet</servlet-name>
        <!--程序的全类名-->
        <servlet-class>com.javaweb.test.HelloServlet</servlet-class>
        <!--init-param是初始化参数-->
        <init-param>
            <!--参数名-->
            <param-name>name</param-name>
            <!--参数值-->
            <param-value>root</param-value>
        </init-param>
        <init-param>
            <!--参数名-->
            <param-name>url</param-name>
            <!--参数值-->
            <param-value>is-hash.com</param-value>
        </init-param>
    </servlet>

然后,修改类中的init方法:

    public void init(ServletConfig servletConfig) throws ServletException {
        System.out.println("HelloServlet程序的别名是"+servletConfig.getServletName());
        System.out.println("初始化参数username的值是"+servletConfig.getInitParameter("name"));
        System.out.println("初始化参数url的值是"+servletConfig.getInitParameter("url"));
        System.out.println(servletConfig.getServletContext());
    }

然后,启动项目(http://localhost:8080/web_war_exploded/Hello)就会看到打印:

HelloServlet程序的别名是HelloServlet
初始化参数username的值是root
初始化参数url的值是is-hash.com
org.apache.catalina.core.ApplicationContextFacade@5848558f

这个ServletContext我们后面会介绍。

 

补充:

Servlet程序和ServletConfig对象都是由Tomcat负责创建,我们负责使用。

Servlet程序默认是第一次访问的时候创建,ServletConfig是每个Servlet程序创建时就创建一个对应的ServletConfig对象。

不止init,在其他方法中也可以得到ServletConfig对象。

例如我们的HelloServlet2中,在doGet方法内可以使用如下命令得到HelloServlet2对应的ServletConfig对象:

        ServletConfig servletConfig=getServletConfig();

一定要注意:这里得到的ServletConfig一定是对应的自己的那个Servlet类的ServletConfig。

还有一点,如果在子类(HttpServlet )中调用init方法,一定要加上下面的语句:

super.init(config);

原因很简单,在父类(GenericServlet)的init方法中,定义了这样:

    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.init();
    }

如果重写init而不执行父类的init,意味着父类的init被完全覆盖,也就不会将config保存给自己的属性config,这样得到的config就不能使用,是一个空指针。

 

ServletContext类

基础了解:

上面我们出现了一个ServletContext对象,那么这个是什么呢?

ServletContext也是一个接口,他表示Servlet上下文对象。

一个web工程只有一个ServletContext对象实例。

ServletContext对象是在Web工程启动时创建,在停止时销毁(重新部署就会销毁这个对象),而只要你有这个对象没被销毁,那么可以工程的任何地方得到这个对象。

ServletContext对象是一个域对象

域对象:是可以像Map一样存取数据的对象。

这里的域指的是存取数据的操作范围(这里是整个web工程)。

我们来看看他和Map对象的对比:

存数据 取数据 删除数据
Map put get remove()
域对象 setAttribute() getAttribute() removeAttribute()

ServletContext类的四个作用

  1. 获取web.xml中配置的上下文参数
  2. 获取当前的工程路径(/工程名)
  3. 获取工程部署后在服务器磁盘上的绝对路径
  4. 像Map一样存储数据

 

我们来新建一个Servlet文件来做实验,记得配置好xml文件:

    <servlet-mapping>
        <servlet-name>ContextServlet</servlet-name>
        <url-pattern>/Context</url-pattern>
    </servlet-mapping>

然后创建一个 “上下文参数” 来做示例,直接在web.xml中添加:

    <context-param>
        <param-name>Context</param-name>
        <param-value>ContextValue</param-value>
    </context-param>

下面是ContextServlet.java的内容:

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
                                           throws ServletException, IOException {
        ServletContext context=getServletConfig().getServletContext();
        //根据键得到Context-param上下文参数的值
        //别被getInitParameter这个误导,他与一个Servlet对象的Init-param是两回事
        String username=context.getInitParameter("Context");
        System.out.println("上下文参数Context的值是:"+username);
        //得到当前路径
        System.out.println("当前工程路径:"+context.getContextPath());
        //得到主机中的具体路径
        /*
        这里解释一下为什么是"/",http://ip:port/工程名,"/"就是
        映射到IDEA代码的web目录(目录名web,不是模块名)
        */
        System.out.println("工程部署的路径是"+context.getRealPath("/"));
    }

直接访问(Get请求)/工程名/Context,控制台输出:

上下文参数Context的值是:ContextValue
当前工程路径:/web_war_exploded
工程部署的路径是F:\Idea的平时作品\JavaWeb\out\artifacts\web_war_exploded\

下面我们再来验证第四个功能,像Map一样存储数据:

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取ServletContext对象
        ServletContext context=getServletContext();
        context.setAttribute("key1","value1");
        System.out.println("Context1 key1的值是:"+context.getAttribute("key1"));
    }
输出:
Context1 key1的值是:value1

 


HTTP

什么是http协议

又回到基础问题了

什么是协议?

协议,是指双方或多方相互约定好,大家都需要遵守的规则,叫协议。

所谓HTTP协议,就是指客户端和服务器之间通信时发送的数据,需要遵守的规则,叫HTTP协议。

Http协议中的数据又叫报文

 

GET请求

  • 请求行
    • 请求的方式:GET
    • 请求的资源路径(+?+请求参数)
    • 请求的协议的版本号:HTTP/1.1
  • 请求头
    • key-value组成,不同的键值对表示不同的含义

 

POST请求

  • 请求行
    • 请求的方式:POST
    • 请求的资源路径(+?+请求参数)
    • 请求的协议的版本号:HTTP/1.1
  • 请求头
    • key-value组成,不同的键值对表示不同的含义
  • 请求体:就是发送给服务器的数据

 

常用请求头

  1. Accept:表示客户端可以接受的数据类型
  2. Accept-Languege:表示客户端可以接收的语言类型
  3. User-Agent:表示客户端浏览器的信息
  4. Host:表示请求时的服务器ip和端口号

 

常见请求方式

GET请求

  1. form标签 method=“get”
  2. a 标签
  3. link标签引入css
  4. Script标签引入js文件
  5. img标签引入图片
  6. iframe引入html页面
  7. 在浏览器输入url访问

POST请求

  1. form标签 method=“post”

 

响应的HTTP协议介绍

  • 响应行
    • 响应的协议和版本号
    • 响应状态码
    • 响应状态描述符
  • 响应头
    • key-value组成,不同的键值对表示不同的含义
  • 响应体:就是回传给客户端的数据

常见HTTP响应码:

  1. 200:表示请求成功
  2. 302:表示请求重定向
  3. 404:表示请求服务器已经收到了,但是你要的数据不存在(找不到页面)
  4. 500:表示服务器已经收到请求,但是服务器内部错误(代码出问题)

 

MIME类型说明

MIME是HTTP协议中数据类型。

MIME的英文全称是“Multipurpose Internet Mail Extensions”(多功能Internet邮件扩充服务)。

MIME类型的格式是“大类型/小类型”,并与某一种文件的扩展名相对应。

 


常见知识点:

HttpServletRequest

每次有请求进入Tomcat服务器,Tomcat服务器就会把请求过来的Http协议信息封装到Request对象中。 然后传递到service方法(doGet和doPost)中给我们使用。我们可以通过HttpServletRequest对象,获取到所有请求的信息。

我们可以在doGet/doPost里面得到这个参数

常用方法:

  1. getRequestRUI( ):获取请求的资源路径
  2. getRequestURL():获取请求的统一资源定位符(绝对路径)
  3. getRemoteHost( ):获取客户端的ip地址
  4. getHeader( ):获取请求头
  5. getParameter( ):获取请求的参数
  6. getParameterValues( ):获取请求的参数(多个值的时候使用)
  7. getMethod( ):获取请求的方式GET或者POST
  8. setAttribute(key,value):设置域数据
  9. getAttribute(key):获取域数据
  10. getRequestDispatcher():获取请求转换对象

 

如何获取客户端请求的参数

利用好API方法即可,示例:

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //改变编码方式需要在获取参数前调用
        request.setCharacterEncoding("UTF-8");
        //通过name基本属性来得到value
        String username=request.getParameter("username");
        String password=request.getParameter("password");
        String[] hobby=request.getParameterValues("hobby");

        System.out.println(username);
        System.out.println(password);
        System.out.println(Arrays.asList(hobby));
    }

request.setCharacterEncoding("UTF-8");常常用来解决POST请求中文乱码问题。

 

请求的转发

请求转发是指,服务器收到请求之后,从一次资源跳转到另一个资源的操作叫做请求转发。

下面来看看示例:

ContextServlet.java:

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String username=request.getParameter("username");
    System.out.println("ContextServlet得到用户名:"+username);
    //设置一个域变量
    request.setAttribute("key","first");
    //请求转发必须要以斜杠打头
    //斜杠代表地址是:http://ip:port/工程名/
    //Dispatcher可以理解为调度
    RequestDispatcher requestDispatcher
            =request.getRequestDispatcher("/Hello3");
    //进行请求转发
    requestDispatcher.forward(request,response);
}

HelloServlet3.java

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String username=request.getParameter("username");
        System.out.println("HelloServlet3得到用户名:"+username);
        //查看Context是否有记录域变量
        Object key1= request.getAttribute("key");
        System.out.println("ContextServlet是否有记录"+key1);
        //处理自己的业务
        System.out.println("HelloServlet3 处理自己的业务");
    }

然后我们使用一个表单随便输入点东西然后提交给/Context(映射到ContextServlet类)

控制台打印:

ContextServlet得到用户名:奥术大师多
HelloServlet3得到用户名:奥术大师多
ContextServlet是否有记录first
HelloServlet3 处理自己的业务

 

base标签

如果利用请求转发去转发一个页面,由于路径问题可能会存在这样的一种情况

解决这个问题,就可以使用base标签

base标签可以设置当前页面中所有相对路径工作时,参照哪个路径来进行跳转

示例:

这个就设置好了本页面中所有相对路径的参考地址

 

HttpServletResponse类

HttpServletResponse类的作用

HttpServletResponse类和HttpServletRequest类一样。每次请求进来,Tomcat服务器都会创建一个Response对象传递给Servlet程序去使用。

我们如果需要设置返回给客户端的信息,就可以通过 HttpServletResponse 对象来设置。

 

两个输出流的说明

  • 字节流–getOutputStream():常用于下载(传递二进制数据)
  • 字符流–getWriter():常用于回传字符串(常用)

两个流同时只能使用一个。

 

如何往客户端回传字符数据

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //得到字符流
        PrintWriter writer=response.getWriter();
        writer.println("response content");
    }

然后访问/Context,映射到这个类里面,客户端页面就会显示 response content

这里注意一下,直接这样使用是不能回传中文,如果你通过流输出中文,会显示成问号,我们可以打印回传字符集看一下:

System.out.println(response.getCharacterEncoding());

打印:ISO-8859-1

字符集不支持中文

我们可以尝试设置一下字符集:

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setCharacterEncoding("UTF-8");
        //得到字符流
        PrintWriter writer=response.getWriter();
        writer.println("有意思");
    }

再做测试我们会发现,客户端浏览器已经不现实问号了,但是显示了乱码。

这是因为我们只是把服务器端的字符集改变了,浏览器的字符集与服务器端的字符集不统一,所以会造成这个情况。

我们可以在客户端浏览器上去改,但是这样很不现实,因为以后项目展示出来不可能让客户一个一个去改。

所以我们可以直接在服务器端下手,添加响应头,这样浏览器就可以按照UTF-8来显示了:

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setCharacterEncoding("UTF-8");
        //添加响应头,设置浏览器也使用UTF-8字符集
        response.setHeader("Content-Type","text/html;charset=UTF-8");
        //得到字符流
        PrintWriter writer=response.getWriter();
        writer.println("有意思");
    }

或者,你可以直接设置内容类型:

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //它会设置服务器和客户端都使用UTF-8字符集,同时设置响应头
        //此方法一定要在获取流对象之前设置才有效!
        response.setContentType("text/html;charset=UTF-8");
        //得到字符流
        PrintWriter writer=response.getWriter();
        writer.println("有意思");
    }

响应头信息:

Content-Length: 11
Content-Type: text/html;charset=UTF-8
Date: Fri, 24 Apr 2020 11:25:56 GMT
Server: Apache-Coyote/1.1

 

 

请求重定向

第一种方法

请求重定向,是指客户端给服务器发请求,然后服务器告诉客户端说。我给你一些新地址,你去新的地址访问(因为之前的地址可能已经被废弃)。

我们来测试:

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("经过Context文件");
        //设置响应状态码302,表示重定向
        response.setStatus(302);
        //设置响应头,说明新的地址在哪里
        response.setHeader("Location","http://127.0.0.1:8080/web_war_exploded/Hello3");
    }

在Hello3对应的那个类里面写上:

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("经过Hello3");
    }

然后我们访问Context,控制台就会输出:

经过Context文件
经过Hello3

请求转发和请求重定向可能很像,但其实很不一样:

本质区别:请求转发只是发了一次请求,请求重定向发了两次请求。

  • 地址栏
    • 请求转发:地址栏还是当初请求的地址栏;
    • 请求重定向:地址栏不会是初次的地址栏,地址栏最后一次相应的地址栏。
  • request对象
    • 请求转发:在最终的servlet中,request对象和中转的request对象是同一个对象;
    • 请求重定向:在最终的servlet中,request对象和中转的request对象不是同一个对象。
  • /的意义:
    • /代表当前web应用的根目录http://localhost:8080/servlet02,是当前web的根目录;
    • 请求重定向:/代表当前web站点的根目录http://localhost:8080是当前web站点。
  • 范围
    • 请求转发:只能转到当前web资源;
    • 请求重定向:可以重定向到外部资源。

302请求重定向的特点:

  1. 浏览器地址栏会发生变化
  2. 两次请求
  3. 不共享Request域中数据
  4. 不能访问WEB-INF下的资源
  5. 可以访问工程外的资源

第二种方法

一行代码搞定:

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("经过Context文件");
        response.sendRedirect("http://127.0.0.1:8080/web_war_exploded/Hello3");
    }

 

 


补充:

JavaEE项目的三层架构

分层是为了解耦。解耦就是为了降低代码的耦合度。方便项目后期的维护与升级。

分层常常对应包:

  •  web层
    • com.atluoluo.web/servlet/controller
  • service层
    • com.atluoluo.services:Service 接口包
    • com.atluoluo.services.impl:Service 接口实现类
  • dao持久层
    • com.atluoluo.dao:Dao接口包
    • com.atluoluo.dao.impl:Dao接口实现类
  • 实体bean对象
    • com.atluoluo.pojo/entity/domain/bean:JavaBean类
  • 测试包
    • com.atluoluo.test/junit
  • 工具类
    • com.atluoluo.utils

 

 


 

 

商业转载 请联系作者获得授权,非商业转载 请标明出处,谢谢

 

 

 

发表评论