servlet基础

java / 2022-08-22
0 979

程序员学习一门新的技术,永远绕不开一个永恒的话题——Hello World!,在我们学习servlet的时候也首先来实现一个Hello World!入门程序!

Hello World!入门程序

首先编写一个Servlet类,在其中返回hello world!

public class IndexServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setCharacterEncoding("utf-8");
        PrintWriter out = resp.getWriter();
        out.write("hello world!");
        out.flush();
        out.close();
    }
    @Override
    protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req, resp);
    }
}

然后需要配置web.xml让容器识别到。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
    <servlet>
        <!-- servlet名字 -->
        <servlet-name>helloworldServlet</servlet-name>
        <!-- servlet的类路径,会将这个类命名为上面配置的名字 -->
        <servlet-class>cn.fzkj.servlet.IndexServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>helloworldServlet</servlet-name>
        <!-- 拦截路径,当访问hello的时候就会交给这个servlet处理 -->
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>
</web-app>

这样配置了之后一个简单的hello world入门程序就完成了。

servlet3.0版本中,将xml配置进一步简化了,可以使用注解来完成。上面的servlet代码就可以修改为:

// 标注这是一个servlet,名字叫helloworldServlet,拦截的路径是/hello,这个注解的效果和之前的xml配置一样。
@WebServlet(name = "helloworldServlet", urlPatterns = "/hello")
public class IndexServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setCharacterEncoding("utf-8");
        PrintWriter out = resp.getWriter();
        out.write("hello world!");
        out.flush();
        out.close();
    }
    @Override
    protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req, resp);
    }
}

servlet

Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。 ——菜鸟教程

http请求通过servlet来和程序进行交互。

servlet生命周期

servlet生命周期指的是servlet从创建到销毁的整个过程。它遵循以下的几个过程:

  • 初始化后会调用init()方法
  • 调用service()方法来处理客户端请求
  • 销毁前调用destroy()方法
  • 最后由垃圾回收器回收

img

创建servlet的方式

创建servlet有三种方式,这里介绍其中两种比较常用的方式。

第一种:实现Servlet接口

public class ServletDemo1 implements Servlet {

    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        System.out.println("=== 初始化执行 ===");
    }

    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("=== 处理业务逻辑 ===");
    }

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

    @Override
    public void destroy() {
        System.out.println("=== 销毁执行 ===");
    }
}

*第二种:继承HttpServlet

其实通过查看源代码可以发现,HttpServlet类最终也是实现了Servlet接口,只是对Servlet中一些方法做了实现。

public abstract class HttpServlet extends GenericServlet {}
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {}

使用HttpServlet类创建

@WebServlet(name = "httpServletDemo", urlPatterns = "/hello")
public class ServletHttpDemo extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("hello world");
    }

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

HttpServletServlet接口做了实现,同时提供了一些用于处理http请求的方法,在继承了HttpServlet类之后就可以非常方便的处理用户请求,而不用在去关心其他。

手写一个HttpServlet

根据上面的发现,我们自己也可以来对Servlet接口进行一个实现,来达到和HttpServlet类相似的功能。说干就干。

由于Servlet中实际会调用Service方法进行业务处理,那就需要将该方法进行重写,让它支持http请求。

/**
 * @DESCRIPTION: 继承了servlet接口,实现类似于HttpServlet相似的功能
 * @AUTHOR: AnotherOne
 * @DATE: 2021/7/14 16:23
 */
public class BaseServlet implements Servlet {

    public BaseServlet() {
        // do nothing
    }

    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        System.out.println("=== BaseServlet ===");
    }

    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    // 重写该方法
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        HttpServletRequest request;
        HttpServletResponse response;
        try {
            request = (HttpServletRequest) servletRequest;
            response = (HttpServletResponse) servletResponse;
            this.service(request, response);
        }catch (Exception e){
            System.out.println("无效的请求");
        }
    }

    // 实现不同请求方式的判断与业务的转发
    protected void service(HttpServletRequest res, HttpServletResponse rep) throws ServletException, IOException {
        String method = res.getMethod();
        if (HttpType.GET.name().equalsIgnoreCase(method)){
            this.doGet(res, rep);
        } else if (HttpType.POST.name().equalsIgnoreCase(method)){
            this.doPost(res, rep);
        } else {
            System.out.println("暂不支持的请求类型");
        }
    }

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

    @Override
    public void destroy() {
        System.out.println("=== BaseServlet ===");
    }

    protected void doGet(ServletRequest req, ServletResponse res){
        System.out.println("BaseServlet get 默认实现");
    }

    protected void doPost(ServletRequest req, ServletResponse res){
        System.out.println("BaseServlet post 默认实现");
    }

    /**
     * 定义http请求枚举类
     */
     enum HttpType {
        GET,
        POST,
        PUT,
        DELETE
    }
}

接下来来写个业务类验证一下。

@WebServlet(name = "servletDemo2", urlPatterns = "/demo2")
public class ServletHttpDemo2 extends BaseServlet {

    @Override
    protected void doGet(ServletRequest req, ServletResponse res) {
//        super.doGet(req, res);
        System.out.println("子类业务处理");
    }
}

当访问/demo2时控制台可以打印出子类业务处理,说明效果不错。

ServletConfig对象

在容器创建Servlet实例对象的时候,会将一些初始化参数封装到ServletConfig对象中,并且在调用对象的init(ServletConfig config)方法时传递给该实例。所以可以通过该对象得到一些初始化参数信息。

@WebServlet(name = "httpServletDemo", urlPatterns = "/demo", initParams = {
        @WebInitParam(name = "name", value = "fzkj"),
        @WebInitParam(name = "age", value = "23")
})
public class ServletHttpDemo extends HttpServlet {

    private ServletConfig config;

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

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取配置的name和age属性值
        System.out.println(config.getInitParameter("name"));
        System.out.println(config.getInitParameter("age"));
        // 获取当前servlet名称
        System.out.println(config.getServletName());
        // 获取当前参数属性名称的枚举
        Enumeration<String> names = config.getInitParameterNames();
        while(names.hasMoreElements()){
            System.out.println(names.nextElement());
        }
        // 获取servletcontext对象
        ServletContext servletContext = config.getServletContext();
    }

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

上面代码是使用注解的方式定义初始化参数。也可以使用xml配置的方式进行。

<servlet>
    <servlet-name>indeServlet</servlet-name>
    <servlet-class>cn.fzkj.servlet.IndexServlet</servlet-class>
    <init-param>
        <param-name>namespace</param-name>
        <param-value>fzkj</param-value>
    </init-param>
    <init-param>
        <param-name>age</param-name>
        <param-value>23</param-value>
    </init-param>
</servlet>
<servlet-mapping>
    <servlet-name>indeServlet</servlet-name>
    <url-pattern>/*</url-pattern>
</servlet-mapping>

这两者的效果并没有什么不同。

上述代码运行结果:

fzkj
23
httpServletDemo
name
age
ServletContext对象

web容器在启动的时候,会为每个web应用程序创建一个ServletContext对象,它代表的就是当前web应用。

所以在同一个web应用中,所有的servlet共享同一个ServletContext对象,因此可以通过这个对象来实现不同Servlet之间的通信。

servletContext实现多个servlet之间数据共享

/**
 * @DESCRIPTION: 多个servlet数据共享 和 demo2
 * @AUTHOR: AnotherOne
 * @DATE: 2021/7/15 11:15
 */
@WebServlet(name = "demo1", urlPatterns = "/demo1")
public class Demo1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setCharacterEncoding("utf-8");
        ServletContext context = this.getServletContext();
        context.setAttribute("name", "fzkj");
        PrintWriter out = resp.getWriter();
        out.write("数据共享成功");
        out.flush();
        out.close();
    }
}

/**
 * @DESCRIPTION: TODO 多个servlet数据共享 和 demo1
 * @AUTHOR: AnotherOne
 * @DATE: 2021/7/15 11:15
 */
@WebServlet(name = "demo2", urlPatterns = "/demo2")
public class Demo2 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setCharacterEncoding("utf-8");
        ServletContext context = this.getServletContext();
        String name = (String)context.getAttribute("name");
        PrintWriter out = resp.getWriter();
        out.write("数据读取成功" + name);
        out.flush();
        out.close();
    }
}

读取web应用初始化参数

/**
 * @DESCRIPTION: TODO 读取web应用初始化参数 -> 在xml中配置<context-param></context-param>
 * @AUTHOR: AnotherOne
 * @DATE: 2021/7/15 11:28
 */
@WebServlet(name = "demo3", urlPatterns = "/demo3")
public class Demo3 extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext context = this.getServletContext();
        System.out.println(context.getInitParameter("names"));
    }
}
    <context-param>
        <param-name>names</param-name>
        <param-value>fzkj123</param-value>
    </context-param>

==是不是有一种可以通过注解的方式创建web初始化参数?后面再来研究==

ServletContext对象实现请求转发

/**
 * @DESCRIPTION: TODO 实现请求转发,转发给Demo5
 * @AUTHOR: AnotherOne
 * @DATE: 2021/7/15 11:39
 */
@WebServlet(name = "dmeo4", urlPatterns = "/demo4")
public class Demo4 extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext context = this.getServletContext();
        resp.setCharacterEncoding("utf-8");
        resp.getOutputStream().write("这是再demo4中".getBytes());
        // 转发给demo5
        RequestDispatcher rd = context.getRequestDispatcher("/demo5");
        rd.forward(req, resp);
    }
}

/**
 * @DESCRIPTION: TODO 接收demo4的转发请求并处理
 * @AUTHOR: AnotherOne
 * @DATE: 2021/7/15 11:43
 */
@WebServlet(name = "dmeo5", urlPatterns = "/demo5")
public class Demo5 extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setCharacterEncoding("utf-8");
        resp.getOutputStream().write("这是再demo5中".getBytes());
    }
}

由于再demo4的处理逻辑中将请求转发给了demo5,所以实际上页面会打印这是再demo5中

Filter

Servlet 过滤器可以动态地拦截请求和响应,以变换或使用包含在请求或响应中的信息。

可以将一个或多个 Servlet 过滤器附加到一个 Servlet 或一组 Servlet。Servlet 过滤器也可以附加到 JavaServer Pages (JSP) 文件和 HTML 页面。调用 Servlet 前调用所有附加的 Servlet 过滤器。

Servlet 过滤器是可用于 Servlet 编程的 Java 类,可以实现以下目的:

  • 在客户端的请求访问后端资源之前,拦截这些请求。
  • 在服务器的响应发送回客户端之前,处理这些响应。

根据规范建议的各种类型的过滤器:

  • 身份验证过滤器(Authentication Filters)。
  • 数据压缩过滤器(Data compression Filters)。
  • 加密过滤器(Encryption Filters)。
  • 触发资源访问事件过滤器。
  • 图像转换过滤器(Image Conversion Filters)。
  • 日志记录和审核过滤器(Logging and Auditing Filters)。
  • MIME-TYPE 链过滤器(MIME-TYPE Chain Filters)。
  • 标记化过滤器(Tokenizing Filters)。
  • XSL/T 过滤器(XSL/T Filters),转换 XML 内容。

过滤器通过 Web 部署描述符(web.xml)中的 XML 标签来声明,然后映射到您的应用程序的部署描述符中的 Servlet 名称或 URL 模式。

当 Web 容器启动 Web 应用程序时,它会为您在部署描述符中声明的每一个过滤器创建一个实例。

Filter的执行顺序与在web.xml配置文件中的配置顺序一致,一般把Filter配置在所有的Servlet之前。

​ ——菜鸟教程

Filter也称之为过滤器,它是Servlet技术中最实用的技术,Web开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp, Servlet, 静态图片文件或静态 html 文件等进行拦截,从而实现一些特殊的功能。例如实现URL级别的权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。

它主要用于对用户请求进行预处理,也可以对HttpServletResponse进行后处理。使用Filter的完整流程:Filter对用户请求进行预处理,接着将请求交给Servlet进行处理并生成响应,最后Filter再对服务器响应进行后处理。

FilterChain:在一个web应用中,可以开发编写多个Filter,这些Filter组合起来称之为一个Filter链。

web服务器根据Filter在web.xml文件中的注册顺序,决定先调用哪个Filter,当第一个Filter的doFilter方法被调用时,web服务器会创建一个代表Filter链的FilterChain对象传递给该方法。在doFilter方法中,开发人员如果调用了FilterChain对象的doFilter方法,则web服务器会检查FilterChain对象中是否还有filter,如果有,则调用第2个filter,如果没有,则调用目标资源。

创建一个简单的过滤器

/**
 * @DESCRIPTION: TODO
 * @AUTHOR: AnotherOne
 * @DATE: 2021/7/15 12:47
 */
// 标注这是一个过滤器,urlPatterns用于配置拦截的url规则,符合规则的url才会被拦截
@WebFilter(filterName = "fzkj_filter_demo1", urlPatterns = "/*", initParams = {
        @WebInitParam(name = "name", value = "fzkj_filter_demo1"),
        @WebInitParam(name = "age", value = "23")
})
public class Demo1 implements Filter {

    private FilterConfig config;

    @Override
    public void init(FilterConfig config) throws ServletException {
        this.config = config;
        System.out.println("=== demo1 过滤器初始化 ===");
        // 获取当前过滤器名称
        System.out.println(config.getFilterName());
        System.out.println("=========================");
        // 获取初始化参数‘name’的值
        System.out.println(config.getInitParameter("name"));
        System.out.println("=========================");
        // 获取所有初始化参数的名称
        Enumeration<String> names = config.getInitParameterNames();
        while(names.hasMoreElements()){
            System.out.println(names.nextElement());
        }
        System.out.println("=========================");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
        System.out.println("经过 " + this.config.getFilterName() + " filter");
        // 交给下一过滤器
        chain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {
        System.out.println("=== demo1 过滤器销毁 ===");
    }
}

Filter的中的方法与用法和Servlet非常相似,不在过多赘述


持续更新~~