Java之Servlet

Servlet

sun公司基于java开发动态web的一门技术

其中在这些API中提供了一个接口就是Servlet

servlet有两个实现类,HttpServlet、GenericServlet,其中GenericServlet是HttpServlet的父类

在servlet3.0版本后可以不用写web.xml文件,可以直接使用注解定义加载。
我们直接在类上面加入注解

@WebServlet(urlPatterns = "/demo")

HelloServlet

  1. 新建一个普通项目,不要勾选maven中webapp模版,并删掉项目中的src目录,这个空工程就是Maven父工程

  2. 在pom.xml中导入依赖

    <!-- //mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>javax.servlet-api</artifactId>
                <version>4.0.1</version>
            </dependency>
    <!-- //mvnrepository.com/artifact/javax.servlet.jsp/javax.servlet.jsp-api -->
    <dependency>
        <groupId>javax.servlet.jsp</groupId>
        <artifactId>javax.servlet.jsp-api</artifactId>
        <version>2.3.3</version>
    </dependency>
    
    

    如果导入失败,手动下载jar包放在本地仓库中

  3. 创建子modul,勾选webapp模版

  4. 子项目中pom.xml添加<parent>标签

    关于Maven父子工程:

    在父工程的pom.xml中会多出

    <modules>
            <module>servlet01</module>
    </modules>
    

    在子工程的pom.xml中会多出(Maven3.6.0之后一开始会有,项目加载完后会被自动清除掉,自己手动添加即可)

    <parent>
        <artifactId>javaweb01</artifactId>
        <groupId>com.zh1z3ven</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    

    父项目中的jar包,子项目可以直接使用

  5. 修改子项目中的web.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <web-app xmlns="//xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="//xmlns.jcp.org/xml/ns/javaee
                          //xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
             version="4.0"
             metadata-complete="true">
    
    </web-app>
    
  6. 子项目中新建javaresources目录,并修改对应目录属性

  7. 新建com.zh1z3ven.servlet包,编写servlet程序

    写一个普通类,实现servlet接口

    这里需要注意父项目的pom.xml文件中的<scope>标签,注意如果父项目存在<scope>标签且值为provided时,子modul无法成功引用父类jar包

    在Maven中依赖的域有:compile、provided、runtime、system、test、import

    一、compile(默认)

    当依赖的scope为compile的时候,那么当前这个依赖的包,会在编译的时候被加入进来,并且在打包(mvn package)的时候也会被加入进来。
    编译范围有效,在编译与打包时都会加入进去。

    二、provided

    当依赖的scope为provided的时候,在编译和测试的时候有效,在执行(mvn package)进行打包时不会加入。比如, 我们开发一个web应用,在编译时我们需要依赖servlet-api.jar,但是在运行时我们不需要该 jar包,因为这个jar 包已由web服务器提供,如果在打包时又被加入进去,那么就可能产生冲突。此时我们就可以使用 provided 进行范围修饰。

    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 HelloServlet extends HttpServlet {
    
        //由于get和post只是请求相互实现的方式,可以互相调用,业务逻辑都一样
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            PrintWriter writer = resp.getWriter();  //响应流
            writer.print("Hello Servlet");
    
        }
    
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    				doGet(req,resp);
        }
    }
    

  8. 修改web.xml注册servlet,编写servlet映射

    <!--    注册servlet-->
        <servlet>
            <servlet-name>hello</servlet-name>
    <!--        绑定servlet实现类-->
            <servlet-class>com.zh1z3ven.servlet.HelloServlet</servlet-class>
        </servlet>
    <!--    注册servlet路由-->
        <servlet-mapping>
            <servlet-name>hello</servlet-name>
    <!--        设置访问路由-->
            <url-pattern>/hello</url-pattern>
        </servlet-mapping>
    
  9. 配置Tomcat,配置完后先点击apply再点击ok

  10. 启动测试

  11. 关于target目录

Servlet原理

客户端请求通过浏览器发送到web服务器,请求包含请求头请求体,之后变成request对象传给servlet,servlet调用service方法处理请求并返回response。而对于Servlet来说,当服务器处理请求时,先根据URL中的路径查找是否在web.xml的<servlet-mapping> (servlet映射)<url-pattern>标签有对应的值,有的话根据此Servlet对应的类名,通过该类的实例化对象调用service方法处理请求并返回响应。

Servlet-Mapping

  1. 一个servlet可以指定一个映射路径

    <servlet-mapping>
            <servlet-name>hello</servlet-name>
            <url-pattern>/hello</url-pattern>
    </servlet-mapping>
    
  2. 一个servlet可以指定多个映射路径

    <servlet-mapping>
            <servlet-name>hello</servlet-name>
            <url-pattern>/hello</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
            <servlet-name>hello</servlet-name>
            <url-pattern>/hello1</url-pattern>
    </servlet-mapping>
    
  3. 一个servlet可以指定通用映射路径

    <servlet-mapping>
            <servlet-name>hello</servlet-name>
            <url-pattern>/hello/*</url-pattern>
    </servlet-mapping>
    
  4. 指定一些后缀或前缀等…,例如.do

    以这个后缀结尾,不管前面uri加了多少层目录都可以访问到

    <servlet-mapping>
            <servlet-name>hello</servlet-name>
            <url-pattern>*.do</url-pattern>
    </servlet-mapping>
    
  5. 默认请求路径

    <servlet-mapping>
            <servlet-name>hello</servlet-name>
            <url-pattern>/*</url-pattern>
    </servlet-mapping>
    

处理简单报错Servlet

ErrorServelt

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 ErrorServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html");
        resp.setCharacterEncoding("utf-8");

        PrintWriter writer = resp.getWriter();
        writer.print("<h1>404</h1>");
    }

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

Web.xml中添加,这里<url-pattern>/*</url-pattern>虽然设置的默认通配符,但是url输入其他注册好的servlet还是会优先走对应的servlet。指定了固定路径的映射默认优先级最高,如果找不到才会走默认的处理请求。

<servlet>
        <servlet-name>error</servlet-name>
        <servlet-class>com.zh1z3ven.servlet.ErrorServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>error</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

ServletContext

Web容器在启动的时候会为每个Web程序创建一个ServletContext对象,代表当前的Web应用对象。这个ServletContext可以保存一些数据,也可以供Servlet从中读取数据。类似于Servlet之间的中介,实现Servlet直接的数据共享。

ServletContext:Servlet上下文

可根据HttpServlet的实例化对象调用getServletContext获取

ServletContext servletContext = this.getServletContext();

1、共享数据

在一个servlet中创建的数据可以在另一个servlet中拿到。ServletContext对象是可以被多个Servlet共用的

写入数据

 servletContext.setAttribute("username",username);   //将数据写入ServletContext中
public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Hello Servlet doGet");
        //this.getInitParameter()   初始化参数
        //this.getServletConfig()   sevlet配置
        //this.getServletContext()  sevlet上下文
      	//获得ServletContext对象
        ServletContext servletContext = this.getServletContext();   //this指代当前这个类本身

        String username = "zh1z3ven";
        servletContext.setAttribute("username",username);   //将数据写入ServletContext中
    }
}

读取数据

public class GetServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Hello GetServlet ");
        ServletContext servletContext = this.getServletContext();
        String username = (String) servletContext.getAttribute("username"); //强制类型转换

        PrintWriter writer = resp.getWriter();
        writer.print("<h1>username: </h1>" + username);
    }
}

Web.xml

<servlet>
        <servlet-name>hello</servlet-name>
        <servlet-class>com.zh1z3ven.servlet.HelloServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>hello</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>getname</servlet-name>
        <servlet-class>com.zh1z3ven.servlet.GetServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>getname</servlet-name>
        <url-pattern>/getname</url-pattern>
    </servlet-mapping>

测试结果

需要先访问/s2/hello,才会触发HelloServlet类中的doGet方法将name写入ServletContext中;再访问/s2/getname时触发GetServlet中的doGet方法去读取ServletContext中的name并根据代码回显到页面上。

2、获取初始化参数getInitParameter

参数为web.xml中的<context-param>下的参数

@Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext servletContext = this.getServletContext();
        String url = servletContext.getInitParameter("url");

        resp.setCharacterEncoding("utf-8");
        resp.setContentType("text/html");
        PrintWriter writer = resp.getWriter();
        writer.print(url);
    }

Web.xml

<context-param>
    <param-name>url</param-name>
    <param-value>jdbc:mysql://localhost:3306/mybatis</param-value>
</context-param>
<servlet>
    <servlet-name>gp</servlet-name>
    <servlet-class>com.zh1z3ven.servlet.ServletDemo03</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>gp</servlet-name>
    <url-pattern>/gp</url-pattern>
</servlet-mapping>

3、请求转发requestDispatcher

获取ServletContext上下文对象,调用requestDispatcher的forward方法转发请求

RequestDispatcher requestDispatcher = servletContext.getRequestDispatcher("/gp");   //请求转发目标路径
requestDispatcher.forward(req,resp);    //调用forward方法转发请求

Web.xml

<servlet>
    <servlet-name>sd4</servlet-name>
    <servlet-class>com.zh1z3ven.servlet.ServletDemo04</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>sd4</servlet-name>
    <url-pattern>/sd4</url-pattern>
</servlet-mapping>

class

public class ServletDemo04 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext servletContext = this.getServletContext();
        //RequestDispatcher requestDispatcher = servletContext.getRequestDispatcher("/gp");   //请求转发目标路径
        //requestDispatcher.forward(req,resp);    //调用forward方法转发请求
        servletContext.getRequestDispatcher("/gp").forward(req,resp);
    }
}

如果出现tomcat爆500的情况,可以尝试关掉tomcat然后maven clean一下项目的target,在重启tomcat可能就好了

4、读取资源文件

Properties,如果在项目的java目录下有写properties文件的话默认是不会被导出的,需要在pom.xml中多加一段build的配置,使得java目录下的资源文件也可以被导出。

但是不管是resources和java目录下的properties文件都会被打报道WEB-INF/classes目录下,也就是classpath路径

思路:利用this.getServletContext().getResourceAsStream()方法返回一个流,通过Properties类的实例化对象调用load()方法加载这个流获取数据。

public class PropertiesServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        InputStream is = this.getServletContext().getResourceAsStream("/WEB-INF/classes/com/zh1z3ven/servlet/test.properties");		//返回InputStream流

        Properties properties = new Properties();		//实例化Properties对象

        properties.load(is);		//loadInputStream流

        String username = properties.getProperty("username");
        String password = properties.getProperty("password");

        resp.getWriter().print(username + ":" + password);

    }

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

    }
}

Web.xml

<servlet>
        <servlet-name>sd5</servlet-name>
        <servlet-class>com.zh1z3ven.servlet.PropertiesServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>sd5</servlet-name>
        <url-pattern>/sd5</url-pattern>
    </servlet-mapping>

资源文件路径,同理如果是在源码目录下,只需要写好相对路径即可。

例如:此test.properties

路径为:

/WEB-INF/classes/com/zh1z3ven/servlet/test.properties

HttpServletResponse

web服务器会将客户端发来的http请求进行处理,生成两个对象HttpServletRequest和HttpServletResponse。

  • 如果要获取客户端发来的请求信息:HttpServletRequest
  • 如果要处理需要响应给客户端的信息:HttpServletResponse

1、简单分类

向客户端发送数据的方法

ServletOutputStream getOutputStream() throws IOException;		//普通输出流

PrintWriter getWriter() throws IOException;			//处理中

向浏览器发送响应头的方法

void setCharacterEncoding(String var1);

void setContentLength(int var1);

void setContentLengthLong(long var1);

void setContentType(String var1);

void setDateHeader(String var1, long var2);

void addDateHeader(String var1, long var2);

void setHeader(String var1, String var2);

void addHeader(String var1, String var2);

void setIntHeader(String var1, int var2);

void addIntHeader(String var1, int var2);

void setStatus(int var1);		

2、常见应用

  1. 向浏览器输出消息(getWriter,getOutputStream)
  2. 下载文件
    1. 获取下载文件的路径
    2. 下载的文件名
    3. 让浏览器支持我们能下载的东西
    4. 获取下载文件的输入流
    5. 创建缓冲区
    6. 获取OutputStream对象
    7. 将FileOutputStream流写入到buffer缓冲区
    8. 使用OutputStream将buffer缓冲区的数据输出到客户端
public class FileServlet extends HttpServlet {

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

        System.out.println("=====================123131===========================");
        //获取下载文件的路径
        //String realPath = this.getServletContext().getRealPath("/1.png");//获取绝对路径
        String realPath = "/Users/b/Desktop/javacode/JavaWeb_Maven/javaweb-02-servlet/response/src/main/resources/1.png";
        System.out.println("下载文件的路径: " + realPath);

        //下载的文件名
        String fileName = realPath.substring(realPath.lastIndexOf("/") + 1);
        System.out.println(fileName);

        //让浏览器支持我们能下载的东西
        resp.setHeader("Content-Disposition", "attachment;filename=" + fileName);

        //获取下载文件的输入流
        FileInputStream fis = new FileInputStream(realPath);

        //创建缓冲区
        int len = 0;
        byte[] buffer = new byte[1024];

        //获取OutputStream对象
        ServletOutputStream servletOutputStream = resp.getOutputStream();

        //将FileOutputStream流写入到buffer缓冲区
        while ((len = fis.read(buffer)) != -1){

            // 使用OutputStream将buffer缓冲区的数据输出到客户端
            servletOutputStream.write(buffer, 0, len);
        }

        //释放资源
        servletOutputStream.close();
        fis.close();

    }

重定向

方法:

void sendRedirect(String var1) throws IOException;

Demo:

public class RedirectServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.sendRedirect("/download");
    }
}

重定向与转发

重定向与转发都会使用户访问其他资源,只是发生位置不同,请求转发放生在服务端,只能访问该项目内部资源。重定向是第一次收到用户请求后让客户端重新访问一个新的url地址,客户一共请求了两次。

HttpServletRequest

1、Request常用方法

String getMethod()              //获取请求方式
String getContextPath()         //获取虚拟路径
String getServletPath()         //获取Servlet路径
String getQueryString()         //获取get请求方式参数
String getRequestURI()          //获取请求uri
StringBuffer getRequestURL()    //获取URL
String getProtocol()            //获取协议版本
String getRemoteAddr()          //获取客户端ip
String getHeader(String name)   //通过请求头的名称获取请求头的值
Enumeration<String> getHeaderNames() //获取所有的请求头名称
  
String getParameter(String var1);		//获取一个指定参数的值

Enumeration<String> getParameterNames();	

String[] getParameterValues(String var1);		//获取多个参数的值,返回一个String数组

Map<String, String[]> getParameterMap();	
public class LoginServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");      //设置编码

        String username = req.getParameter("username");
        String password = req.getParameter("password");
        String[] hobbys = req.getParameterValues("hobbys");

        System.out.println("================================");
        System.out.println(username);
        System.out.println(password);
        System.out.println(Arrays.toString(hobbys));        //获取string数组
        System.out.println("================================");

        req.getRequestDispatcher("/success.jsp").forward(req,resp);
    }

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