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
没有配置,就会去查找tomcat
的conf
目录的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 "";
}