Servlet学习

Servlet的介绍

  • Servlet 是运行在java服务器端的程序,用于接受和响应来自客户端基于HTTP协议的请求。
  • 如果想实现Servlet的功能,可以通过实现javax.servlet.Servlet接口或者继承它的实现类。
  • 核心方法:service(),任何客户端的请求都会经过该方法。

Servlet 快速入门

创建Servlet重写类包

创建包cn.liu.servlet
创建类newServlet

重写Servlet

方法1:实现 Servlet 接口

  • 要重写所有方法
  • 支持最大程度上的自定义
package cn.liu.servlet;

import javax.servlet.*;
import java.io.IOException;

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

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

	/*
    * 所有的客户端都会经过service方法
    * */
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
		System.out.print("servlet启动了");
    }

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

    @Override
    public void destroy() {

    }
}

方法2:用继承 GenericServlet抽象类 方式重写Servlet

  • 用继承 GenericServlet 方式需要重写的只有一个service方法
  • 其他方法可以选择重写,开发Servlet变得简单
  • 这种方式和HTTP协议无关
package cn.liu.servlet;

import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;

public class newServlet extends GenericServlet {
    /*
    * ServletRequest    请求
    * ServletResponse   相应
    * */
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.print("servlet启动了");
    }
}

方法3:继承 HttpServlet 抽象类(用的多)

  • 需要重写 doGet 和 doPost 方法
  • 该方法表示请求和响应都需要和HTTP协议相关
package cn.liu.servlet;

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

public class newServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
    	/*重点就在这里写就好了*/
        System.out.print("servlet启动了");
    }

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

在web.xml配置Servlet

<?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">

    <!--servlet声明-->
    <servlet>
        <!-- servlet名称 -->
        <servlet-name>newServlet</servlet-name>

        <!-- servlet改写的功能类 -->
        <servlet-class>cn.liu.servlet.newServlet</servlet-class>
    </servlet>

    <!--servlet映射-->
    <servlet-mapping>
        <!-- servlet名称 ,要和声明里的名字一样-->
        <servlet-name>newServlet</servlet-name>

        <!-- 浏览器访问路径名称-->
        <url-pattern>/newServlet</url-pattern>
    </servlet-mapping>
</web-app>

启动服务

在浏览器路径后面加上 /newServlet,每次访问控制台就会打印servlet启动了
如:我启动的路径是http://localhost:8080/jsp_test_war_exploded
加上就是http://localhost:8080/jsp_test_war_exploded/newServlet

Servlet 生命周期

生命周期:对象从出生到死亡的过程

package cn.liu.servlet;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class newServlet extends HttpServlet {

    // 对象出生的过程(第一次使用)
    @Override
    public void init(ServletConfig config) throws ServletException {
        System.out.print("servlet启动了");
        super.init(config);
    }

    // 对象的服务的过程(每次请求都会执行这里)
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
        System.out.print("接收到了客户端的请求");
    }

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

    // 对象销毁的过程(服务停止,服务器宕机,对象死亡)
    @Override
    public void destroy() {
        System.out.print("对象销毁了");
        super.destroy();
    }
}

Servlet对象只会创建一次,销毁一次。所以Servlet对象只有一个实例。如果一个对象实例在应用中是唯一的存在,那么我们就称它为单例模式。

servlet线程问题

  • 结论:servlet线程是不安全
  • 解决:可以将其定义到doGet或doPost方法内或者使用同步功能

方法一:不去定义全局变量,定义局部变量

package cn.liu.servlet;

import javax.servlet.ServletConfig;
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 newServlet extends HttpServlet {
    // 1.定义一个用户成员变量
    // private String username;      // 方法一:不去定义全局变量,定义局部变量


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
        String username=null;// 方法一:不去定义全局变量,定义局部变量

        //2.获取用户名
        username = req.getParameter("username");
        try {
            Thread.sleep(3000);     // 谷歌浏览器在此休眠,火狐浏览器在此休眠
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 3.将用户名相应给浏览器
        PrintWriter pw = resp.getWriter();
        pw.println("welcome"+username);
        pw.close();
    }

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

}

方法二:加上同步

package cn.liu.servlet;

import javax.servlet.ServletConfig;
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 newServlet extends HttpServlet {
    // 1.定义一个用户成员变量
     private String username;      // 方法一:不去定义全局变量,定义局部变量


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
        // String username=null;// 方法一:不去定义全局变量,定义局部变量

        synchronized (this){    //对象锁,保证同一线程
            //2.获取用户名
            username = req.getParameter("username");
            try {
                Thread.sleep(3000);     // 谷歌浏览器在此休眠,火狐浏览器在此休眠
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            // 3.将用户名相应给浏览器
            PrintWriter pw = resp.getWriter();
            pw.println("welcome"+username);
            pw.close();
        }
    }

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

Servlet 映射方式

映射方式 都在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">
    
    <!--servlet声明-->
    <servlet>
        <!-- servlet名称 -->
        <servlet-name>newServlet</servlet-name>

        <!-- servlet改写的功能类 -->
        <servlet-class>cn.liu.servlet.newServlet</servlet-class>
    </servlet>

    <!--servlet具体名称的方式映射-->
    <servlet-mapping>
        <!-- servlet名称 ,要和声明里的名字一样-->
        <servlet-name>newServlet</servlet-name>

        <!-- 浏览器访问路径名称-->
        <url-pattern>/newServlet</url-pattern>
    </servlet-mapping>
</web-app>

只能访问http://localhost:8080/jsp_test_war_exploded/newServlet
一个对应一个才能访问到newServlet功能类

第二种:/ 开头 + 通配符的方式。只要符合目录结构即可,不用考虑结尾是什么。

 <?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">

    <!--servlet声明-->
    <servlet>
        <!-- servlet名称 -->
        <servlet-name>newServlet</servlet-name>

        <!-- servlet改写的功能类 -->
        <servlet-class>cn.liu.servlet.newServlet</servlet-class>
    </servlet>

    <!--servlet/ 开头 + 通配符的方式映射-->
    <servlet-mapping>
        <!-- servlet名称 ,要和声明里的名字一样-->
        <servlet-name>newServlet</servlet-name>

        <!-- 浏览器访问路径名称-->
        <url-pattern>/newServlet/*</url-pattern>
    </servlet-mapping>
</web-app>

访问路径:

  • http://localhost:8080/jsp_test_war_exploded/newServlet/qwe
  • http://localhost:8080/jsp_test_war_exploded/newServlet/asd
    都能访问到newServlet功能类

应用场景

vip打九折,vvip打,其他不打折

package cn.liu.servlet;

import javax.servlet.ServletConfig;
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 newServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
        // 定义一个商品的金额
        int money = 1000;

        // 获取访问资源路径
        String path = req.getRequestURI();
        path =path.substring(path.lastIndexOf("/"));

        // 条件判断
        if ("/vip".equals(path)){
            System.out.print("商品原价为:"+ money + "。优惠后是:"+(money*0.9));
        }else if ("/vvip".equals(path)){
            System.out.print("商品原价为:"+ money + "。优惠后是:"+(money*0.5));
        }else{
            System.out.print("商品原价为:"+ money );
        }
    }

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

访问路径:

  • http://localhost:8080/jsp_test_war_exploded/newServlet/vip打九折
  • http://localhost:8080/jsp_test_war_exploded/newServlet/vvip打五折
    访问其他都不打折

第三种:通配符 + 固定格式结尾的方式。只要符合固定结尾格式即可,不用考虑前面的路径。

<?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">

    <!--servlet声明-->
    <servlet>
        <!-- servlet名称 -->
        <servlet-name>newServlet</servlet-name>

        <!-- servlet改写的功能类 -->
        <servlet-class>cn.liu.servlet.newServlet</servlet-class>
    </servlet>

    <!--servlet通配符 + 固定格式结尾的方式映射-->
    <servlet-mapping>
        <!-- servlet名称 ,要和声明里的名字一样-->
        <servlet-name>newServlet</servlet-name>

        <!-- 浏览器访问路径名称-->
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
</web-app>

访问路径:

  • http://localhost:8080/jsp_test_war_exploded/qwe.do
  • http://localhost:8080/jsp_test_war_exploded/qwe/asd.do
    只要是.do结尾都能访问到newServlet功能类

注意:优先级问题。越是具体的优先级越高,越是模糊优先级越低。
第一种 -> 第二种 -> 第三种

Servlet 创建时机

第一次访问时创建

  • 优势:减少对服务器内存的浪费。提高了服务器启动的效率。
  • 弊端:如果有一些要在应用加载时就做的初始操作,无法完成。

服务器加载时创建

  • 优势:提前创建好对象,提高首次执行的效率。可以完成一些应用加载时要做的初始化操作。
  • 弊端:对服务器内存占用较多,影响了服务器启动效率。

修改创建时机

web.xml<servlet>标签中,添加<load-on-startup>1</load-on-startup>标签

<?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">

    <!--servlet声明-->
    <servlet>
        <!-- servlet名称 -->
        <servlet-name>newServlet</servlet-name>

        <!-- servlet改写的功能类 -->
        <servlet-class>cn.liu.servlet.newServlet</servlet-class>

        <!-- 修改创建时机 -->
        <load-on-startup>1</load-on-startup>
    </servlet>

    <!--servlet映射-->
    <servlet-mapping>
        <!-- servlet名称 ,要和声明里的名字一样-->
        <servlet-name>newServlet</servlet-name>

        <!-- 浏览器访问路径名称-->
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
</web-app>

正整数代表服务器加载时创建,值越小、优先级越高。负整数或不写代表第一次访问时创建。

默认Servlet

如果在项目里的web.xml没有配置,就会去查找tomcatconf目录的web.xml中的配置

ServletConfig

  • ServletConfig是Servlet的配置参数对象,在Servlet的规范中,允许为每一个Servlet都提供一些初始化的配置,所以,每一个Servlet都有一个自己的ServletConfig
  • 作用:在Servlet的初始化时,把一些配置信息传递Servlet。
  • 生命周期:和Servlet相同。

ServletConfig配置方式

  • 在标签中,通过标签来配置。有两个子标签。
  • :表示初始化参数的key。
  • :表示初始化参数的value。
<?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">

    <!--servlet声明-->
    <servlet>
        <!-- servlet名称 -->
        <servlet-name>newServlet</servlet-name>

        <!-- servlet改写的功能类 -->
        <servlet-class>cn.liu.servlet.newServlet</servlet-class>

        <!-- 配置ServletConfig -->
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>desc</param-name>
            <param-value>This is ServletConfig</param-value>
        </init-param>

        <!-- 修改创建时机 --><!-- <load-on-startup>要放到 <init-param> 后面-->
        <load-on-startup>1</load-on-startup>
    </servlet>

    <!--servlet映射-->
    <servlet-mapping>
        <!-- servlet名称 ,要和声明里的名字一样-->
        <servlet-name>newServlet</servlet-name>

        <!-- 浏览器访问路径名称-->
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
</web-app>

ServletConfig常用方法

返回值 方法名 说明
String getIntParameter(String name) 根据参数名称获取参数的值
Enumeration<String> getIntParameterNames() 获取所有参数名称的枚举
String getServletName() 获取Servlet的名称
String getServletContext() 获取ServletContext对象
package cn.liu.servlet;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
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;
import java.util.Enumeration;

public class ServletDemo02 extends HttpServlet {
    // 1.声明ServletConfig
    private ServletConfig config;

    // 2.通过init方法,来对ServletConfig对象进行赋值
    @Override
    public void init(ServletConfig config) throws ServletException {
        this.config = config;
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
        // 3.演示ServletConfig常用方法

        // - 根据key获取value
        String encodingValue = config.getInitParameter("encoding");
        System.out.println(encodingValue);

        // - 获取所有的key
        Enumeration<String> keys = config.getInitParameterNames();
        while (keys.hasMoreElements()){
            // 获取到每一个key
            String key = keys.nextElement();
            String value = config.getInitParameter(key);
            System.out.println(value);
        }

        // - 获取Servlet的名称
        String servletName= config.getServletName();
        System.out.println(servletName);

        // - 获取ServletContext对象
        ServletContext servletContext = config.getServletContext();
        System.out.println(servletContext);
    }

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

ServletContext

  • ServletContext 是应用上下文对象(应用域对象)。每一个应用中只有一个ServletContext对象。
  • 作用:可以配置和获取应用的全局初始化参数,可以实现Servlet之间的数据共享。
  • 生命周期:应用一加载则创建,应用被停止则被销毁。

域对象

  • 域对象指的是对象作用域。也就是有作用范围。域对象可以实现数据的共享。不同作用范围的域对象,共享数据的能力也不一样。
  • 在Servlet规范中,一共有4个域对象。ServletContext就是其中的一个。它也是web应用中最大的作用域,也叫application域。他可以实现整个应用之间的数据共享!

ServletContext配置

  • <web-app>标签中,通过<context-param>标签来配置。有两个子标签。
  • <param-name>:代表全局初始化参数的key。
  • <param-value>:代表全局初始化参数的value。
<?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">

    <!--servlet声明-->
    <servlet>
        <!-- servlet名称 -->
        <servlet-name>newServlet</servlet-name>

        <!-- servlet改写的功能类 -->
        <servlet-class>cn.liu.servlet.newServlet</servlet-class>
    </servlet>

    <!--servlet映射-->
    <servlet-mapping>
        <!-- servlet名称 ,要和声明里的名字一样-->
        <servlet-name>newServlet</servlet-name>

        <!-- 浏览器访问路径名称-->
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>

    <!-- 配置ServletContext-->
    <context-param>
        <param-name>globalEncoding</param-name>
        <param-value>UTF-8</param-value>
    </context-param>
    <context-param>
        <param-name>globalDesc</param-name>
        <param-value>This is ServletContext</param-value>
    </context-param>
</web-app>

ServletContext常用方法

返回值 方法名 说明
String getIntParameter(String name) 根据名称获取全局配置参数
String getContextPath() 获取当前应用的访问虚拟目录
String getRealPath(String path) 根据虚拟目录获取应用部署的磁盘绝对路径
void setAttribute(String name,Object value) 向应用域对象中储存数据
Object getAttribute(String name) 通过名称获取应用域对象中的数据
void removeAttribute(String name) 通过名称删除应用域对象中的数据
package cn.liu.servlet;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class ServletDemo02 extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
        // 1.获取ServletContext对象
        ServletContext context = getServletContext();

        // 2.常用方法演示
        // - 获取全局配置参数,根据key获取value
        String value = context.getInitParameter("globalDesc");
        System.out.println(value);

        // - 获取应用的访问虚拟目录
        String contextPath= context.getContextPath();
        System.out.println(contextPath);

        // - 根据虚拟目录获取应用部署的磁盘绝对路径
        String realPath = context.getRealPath("/");
        System.out.println(realPath);

		// - 设置共享数据
        context.setAttribute("userName","liuxiuhui");

        // - 获取共享数据
        Object userName = context.getAttribute("userName");
        System.out.println(userName);

        // - 删除共享数据
        context.removeAttribute("userName");
    }

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

Servlet注解开发

每创建一次Servlet就要在配置文件里加多一个<servlet>servlet-mapping标签显得很臃肿,开发者们就想到用注解的方式减少这种麻烦的操作,通过在类上方使用@WebServlet('/newservlet')方式配置,就用不到web.xml配置文件了

自动注解开发

package cn.liu.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 java.io.IOException;

//自动注解
@WebServlet("/newServlet")
public class ServletDemo02 extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
        System.out.print("servlet启动了");
    }

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

自动注解参数

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WebServlet {
    //指定Servlet的名称。等效于web.xml文件<servlet>标签下的<servlet-name>
    String name() default "";
	//用于映射Servlet。等效于<url-pattern>
    String[] value() default {};
    String[] urlPatterns() default {};
	//指定Servlet的加载时机。等效于<load-on-startup>
    int loadOnStartup() default -1;
	//指定Servlet的初始化参数。等效于<init-param>
    WebInitParam[] initParams() default {};
	//指定Servlet是否支持异步
    boolean asyncSupported() default false;
	//指定Servlet的小图标
    String smallIcon() default "";
	//指定Servlet的大图标
    String largeIcon() default "";
	//指定Servlet的描述信息
    String description() default "";
	//指定Servlet的显示名称
    String displayName() default "";
}

手动注解开发(麻烦,不去看了)

上一篇:java 和 javax


下一篇:Spring文档之使用 JSR 330 标准注解