个人主页:Hello Code. 本文专栏:《Java WEB从入门到实战》 Java WEB完整内容请点击前往Java WEB从入门到实战 查看 如有问题,欢迎指正,一起学习~~
Servlet
概述
- Servlet是运行在Java服务器端的程序,用于接收和响应来自客户端基于HTTP协议的请求
- 是一个接口(javax.servlet.Servlet),常用实现类:GenericServlet、HttpServlet(继承自GenericServlet)
- 所有的客户端请求都会经过service方法进行处理
- 初始化会调用init方法,销毁时会调用destroy方法
执行过程
- 客户端浏览器向Tomcat服务器发起请求
- Tomcat服务器解析请求地址(URL)
- Tomcat根据地址找到对应的项目
- 找到项目中的web.xml文件
- 解析请求资源地址(URI)
- 找到项目的资源(对应的Java类)
- 执行service方法,响应给客户端浏览器
关系视图
实现方式
- 实现Servlet接口,实现所有的抽象方法,该方式支持最大程度的自定义
- 继承GenericServlet抽象类,必须重写service方法,其他方法可选择重写。该方式让我们开发servlet变得简单。但是这种方式与HTTP协议无关
- 继承HttpServlet抽象类,需要重写doGet和doPost方法。该方式表示请求和响应都需要和HTTP协议相关
生命周期
- 对象的生命周期,就是对象从出生到死亡的过程。即:出生 =》活着 =》死亡。官方说法是对象创建到销毁的过程
- 出生:请求第一次到达Servlet时,对象就创建出来,并且初始化成功。只出生一次,将对象放到内存中
- 活着:服务器提供服务的整个过程中,该对象一直存在,每次都是执行service方法
- 死亡:当服务器停止时,或者服务器宕机时,对象死亡
出生对应的是init 方法 活着对应的是service 方法(doGet 和doPost 方法) 死亡对应的是destroy 方法
结论: Servlet对象只会创建一次,销毁一次。所以Servlet对象只有一个实例。如果一个对象实例在应用中是唯一的存在,那么就称他为单例模式
线程安全问题
- 由于Servlet采用的是单例设计模式,也就是整个应用中只有一个实例对象。所以我们需要分析这个唯一的实例对象中的类成员是否线程安全
- 模拟用户登录功能来查看Servlet线程是否安全
结论: 一个浏览器代表一个线程,多个浏览器代表多个线程。按理说应该是每个浏览器查看的都是自己的信息。但结果浏览器中数据混乱。因此,我们可以认为Servlet是线程不安全的!
解决: 定义类成员要谨慎。如果是共用的,并且只会在初始化时赋值,其它时间都是获取的话,那么是没问题的。如果不是共用的,或者每次使用都有可能对其赋值,那就要考虑线程安全的问题了,可以将其定义到doGet或doPost方法内或者使用同步功能即可
映射方式
-
具体名称的方式。访问的资源路径必须和映射配置完全相同 【常用】 <servlet>
<servlet-name>Demo</servlet-name>
<servlet-class>study.servlet.Demo</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Demo</servlet-name>
<url-pattern>/Demo</url-pattern>
</servlet-mapping>
-
/ 开头 + 通配符的方式。只要符合目录结构即可,不用考虑结尾是什么 <servlet>
<servlet-name>Demo2</servlet-name>
<servlet-class>study.servlet.Demo2</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Demo2</servlet-name>
<url-pattern>/Demo2/*</url-pattern>
</servlet-mapping>
-
通配符 + 固定格式结尾的方式。只要符合固定结尾格式即可,不用考虑前面的路径 <servlet>
<servlet-name>Demo2</servlet-name>
<servlet-class>study.servlet.Demo2</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Demo2</servlet-name>
<url-pattern>*.test</url-pattern>
</servlet-mapping>
注意: 优先级问题。越是具体的优先级越高,越是模糊的 优先级越低。第一种 > 第二种 > 第三种
多路径映射
- 我们可以给一个Servlet配置多个访问映射,从而根据不同的请求路径来实现不同的功能
- 场景分析
如果访问的资源路径是/vip,商品价格打9折 如果访问的资源路径是/vvip,商品价格打5折 如果访问的资源路径是其它,商品价格不打折 - 采用第二种映射方式实现多路径映射(
/ + 通配符)
package study.servlet;
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 Demo3 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String requestURI = req.getRequestURI();
String path = requestURI.substring(requestURI.lastIndexOf("/"));
if(path.equals("/vip")){
System.out.println("100元");
}else if(path.equals("/vvip")){
System.out.println("200元");
}else System.out.println("300元");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp);
}
}
创建时机
- 第一次访问时创建
- 优势:减少对服务器内存的浪费。提高了服务器启动的效率
- 弊端:如果有一些要在应用加载时就做的初始化操作无法完成
- 服务器加载时创建
- 优势:提前创建好对象,提高了首次执行的效率。可以完成一些应用加载时要完成的初始化操作
- 弊端:对服务器内存占用较多,影响了服务器启动的效率
- 修改Servlet创建时机。在
<servlet> 标签中,添加<load-on-startup> 标签 <load-on-startup>1</load-on-startup> 正整数代表服务器加载时创建,值越小,优先级越高。负整数或不写代表第一次访问时创建
默认Servlet
默认Servlet是由服务器提供的一个Servlet。它配置在 Tomcat 的 conf 目录中的 web.xml 中
- 它的映射路径是
<url-pattern>/<url-pattern> 。 - 我们在发送请求时,首先会在我们项目中的web.xml 中查找映射配置,找到则执行。
- 但是当找不到对应的Servlet 路径时,就去找默认的 Servlet ,由默认Servlet 处理。所以,一切都是Servlet
ServletConfig
- ServletConfig 是Servlet 的配置参数对象,在Servlet 的规范中,允许为每一个Servlet 都提供一些 初始化的配置。所以,每个Servlet 都有一个自己的ServletConfig
- 作用:在Servlet 的初始化时,把一些配置信息(键值对的形式)传递给Servlet
- 生命周期:和Servlet 相同
配置方式
- 在
<servlet> 标签中,通过 <init-param> 标签来配置。有两个子标签
<param-name> :代表初始化参数的key<param-value> :代表初始化参数的value
<servlet>
<servlet-name>servletConfigDemo</servlet-name>
<servlet-class>study.servlet.servletConfigDemo</servlet-class>
<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>
</servlet>
常用方法
返回值 | 方法名 | 说明 |
---|
String | getInitParameter(String name) | 根据参数名称获取参数的值 | Enumeration | getInitParameterNames() | 获取所有参数名称的枚举 | String | getServletName() | 获取Servlet的名称 | ServletContext | getServletContext() | 获取ServletContext对象 |
- 通过init方法,来对ServletConfig对象进行赋值
private ServletConfig config;
public void init(ServletConfig config) throws ServletException{
this.config = config;
}
- 枚举项遍历
Enumeration<String> keys = config.getInitParameterNames();
while(keys.hasMoreElements()){
String key = keys.nextElement();
String value = config.getInitParameter(key);
System.out.println(key + "--" + value);
}
package study.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.util.Enumeration;
public class ServletConfigDemo 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 {
String encoding = config.getInitParameter("encoding");
System.out.println("encoding:" + encoding);
Enumeration<String> names = config.getInitParameterNames();
while(names.hasMoreElements()){
String name = names.nextElement();
String value = config.getInitParameter(name);
System.out.println(name + "---" + value);
}
String sname = config.getServletName();
System.out.println("Servlet-name:" + sname);
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 域。它可以实现整个应用之间的数据共享
配置方式
- 在
<web-app> 标签中,通过<context-param> 标签来配置。有两个子标签 <param-name> :代表全局初始化参数的key<param-value> :代表全局初始化参数的value
<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>
常用方法
返回值 | 方法名 | 说明 |
---|
String | getInitParameter(String name) | 根据名称获取全局配置的参数 | String | getContextPath() | 获取当前应用的访问虚拟目录 | String | getRealPath(String path) | 根据虚拟目录获取应用部署的磁盘绝对路径 |
HttpServlet 类继承自GenericServlet 类 GenericServlet 类中有getServletContext 方法,可以直接获取ServletContext 对象
返回值 | 方法名 | 说明 |
---|
void | setAttribute(String name, Object value) | 向应用域对象中存储数据 | Object | getAttribute(String name) | 通过名称获取应用域对象中的数据 | void | removeAttribute(String name) | 通过名称移除应用域对象中的数据 |
package study.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;
import java.io.PrintWriter;
import java.util.Enumeration;
public class ServletContextDemo extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext servletContext = req.getServletContext();
Enumeration<String> names = servletContext.getInitParameterNames();
while(names.hasMoreElements()){
String name = names.nextElement();
String value = servletContext.getInitParameter(name);
System.out.println(name + "====" + value);
}
resp.setContentType("text/html;charset=UTF-8");
String contextPath = servletContext.getContextPath();
String realPath = servletContext.getRealPath(contextPath);
PrintWriter pw = resp.getWriter();
pw.write("虚拟目录为:" + contextPath + "<br>");
pw.write("真实目录为:" + realPath);
servletContext.setAttribute("use","lisi");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp);
}
}
注解开发
Servlet3.0 规范
- 我们使用的是Tomcat 9版本。JavaEE规范要求是8.对应的Servlet 版本应该是4.x 版本。但是,在企业开发中,稳定要远比追求新版本重要。所以我们会降低版本使用,用的是Servlet 3.1版本。
- 其实我们之前的操作全部是基于 Servlet 2.5版本规范的,也就是借助于配置文件的方式。后来随着软件开发逐步的演变,基于注解的配置开始流行。而Servlet 3.0版本也就开始支持注解开发了
- Servlet 3.0版本既保留了2.5版本的配置方式,同时有支持了全新的注解配置方式。它可以完全不需要 web.xml 配置文件,就能实现 Servlet 的配置,同时还有一些其他的新特性。
自动注解开发
实现步骤
- 创建一个 web 项目
- 定义一个类,继承HttpServlet
- 重写 doGet 和 doPost方法
- 在类上使用@WebServlet 注解并配置Servlet
- 部署并启动项目
- 通过浏览器测试
@WebServlet("/servletDemo")
public class ServletDemo extends HttpServlet{
}
手动创建容器(了解)
- Servlet 3.0 规范除了使用自动注解的配置方式外,还支持手动创建 Servlet 容器的方式。如果使用必须遵循其编写规范。在3.0 版本加入了一个新的接口:ServletContainerInitializer,需要重写onStartup方法
步骤
-
定义一个类,继承HttpServlet -
重写 doGet 和 doPost方法 -
定义一个类,实现ServletContainerInitializer接口 -
在src 目录下创建一个META-INF的包 -
在 META-INF 包下创建一个services 的包 -
在 services 包下创建一个 javax.servlet.ServletContainerInitializer 的文件 -
文件中的内容为容器实现类的全类名 -
在容器实现类中的 onStartup 方法中完成注册 Servlet public void onStartup(Set<Class<?>> set, ServletContext servletContext){
ServletDemo servletDemo = new ServletDemo();
ServletRegistration.Dynamic registration = servletContext.addServlet("ServletDemo", servletDemo);
registration.addMapping("/servletDemo");
registration.setLoadOnStartup(1);
}
-
部署并启动项目 -
通过浏览器测试
学生管理系统1
步骤
- 创建一个 web 项目
- 创建一个用于保存学生信息的html文件
- 创建一个类,继承HttpServlet
- 重写doGet 和 doPost 方法
- 在web.xml 文件中修改默认主页和配置 Servlet
- 在doGet 方法中接收表单数据保存到文件中,并响应给浏览器结果
- 部署并启动项目
- 通过浏览器测试
获取表单数据 req.getParameter(name值) :就可以通过HttpServletRequest 对象中的方法 通过表单的name属性获取到对应的表单数据
响应数据 PrintWriter pw = resp.getWriter() :通过 HttpServletResponse 对象中的方法获取输出流对象 pw.println("Save Success") :将指定内容响应到浏览器中
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>添加学生</title>
</head>
<body>
<form action="/add" method="get">
姓名:<input type="text" name="username"> <br>
年龄:<input type="number" name="age"> <br>
成绩:<input type="number" name="score"> <br>
<button type="submit">添加</button>
</form>
</body>
</html>
<?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>/studentAdd.html</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>StudentServlet</servlet-name>
<servlet-class>studentServlet.add</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>StudentServlet</servlet-name>
<url-pattern>/add</url-pattern>
</servlet-mapping>
</web-app>
package studentServlet;
import studentServlet.bean.Student;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
public class add extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = req.getParameter("username");
String age = req.getParameter("age");
String score = req.getParameter("score");
Student stu = new Student(username, Integer.parseInt(age), Integer.parseInt(score));
BufferedWriter bw = new BufferedWriter(new FileWriter("E:\\Java\\code\\StudentServlet\\stu.txt",true));
bw.write(stu.getUsername() + "," + stu.getAge() + "," + stu.getScore());
bw.newLine();
bw.close();
resp.setContentType("text/html;charset=UTF-8");
PrintWriter pw = resp.getWriter();
pw.println("添加成功,将在3秒后跳转到首页!!!");
resp.setHeader("Refresh","3;url=/index.html");
pw.close();
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
|