JavaWeb 三大组件之 Servlet
Servlet
1.官方文档
https://tomcat.apache.org/tomcat-8.0-doc/servletapi/index.html
2.Java Web 技术体系的流程图
tomcat支持映射加载静态文件、解析Servlet
3.什么是Servlet
Servlet 在开发动态 WEB 工程中,得到广泛的应用,掌握好 Servlet 非常重要了, Servlet(基石)是 SpringMVC 的基础
Servlet(java 服务器小程序),它的特点:
1.他是由服务器端调用和执行的(一句话:是Tomcat解析和执行)
2.他是用java语言编写的, 本质就是Java类
3.他是按照Servlet规范开发的(除了tomcat->Servlet weblogic->Servlet)
4.功能强大,可以完成几乎所有的网站功能 技术栈要求高
4.Servlet 在 JavaWeb 项目位置
5.Servlet基本使用
1.Servlet开发方式说明
1.servlet3.0 前使用 web.xml , servlet3.0 版本以后(包括 3.0)支持注解, 同时支持 web.xml
配置
2.如何查看servlet 版本[如图]
2.快速入门-手动开发Servlet
需求说明
1 、 开 发 一 个 HelloServlet2、当浏览器 访问 http://localhost:8080/web 应用名/helloServlet 时,后台输出 "hi HelloServelt"
1.创建JavaWeb工程,并配置好Tomcat
2.添加servlet-api.jar(在tomcat/lib下) 到工程, 因为servlet.jar 不是jdk自带的, 要引入
3.在src 下 包HelloServlet.java ,并实现Servlet接口
HelloServlet
/**
* 1. 开发一个Servlet 需要 实现Servlet接口
* 2. 实现Servlet接口的方法5个
*/
public class HelloServlet implements Servlet {
private int count = 0; //属性
/**
* 1.初始化 servlet
* 2.当创建HelloServlet 实例时,会调用init方法
* 3. 该方法只会被调用一次
* @param servletConfig
* @throws ServletException
*/
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("init() 被调用");
}
/**
* 返回ServletConfig 也就是返回Servlet的配置
* @return
*/
@Override
public ServletConfig getServletConfig() {
return null;
}
/**
* 1. service方法处理浏览器的请求(包括get/post)
* 2. 当浏览器每次请求Servlet时,就会调用一次service
* 3. 当tomcat调用该方法时,会把http请求的数据封装成实现ServletRequest接口的request对象
* 4. 通过servletRequest 对象,可以得到用户提交的数据
* 5. servletResponse 对象可以用于返回数据给tomcat->浏览器
* @param servletRequest
* @param servletResponse
* @throws ServletException
* @throws IOException
*/
@Override
public void service(ServletRequest servletRequest,
ServletResponse servletResponse) throws ServletException, IOException {
count++;
//如果count的值,在不停的累计,说明HelloServlet是单例的
System.out.println("hi HelloServlet~ count= " + count);
//Tomcat每处理一次http请求,就生成一个新的线程
System.out.println("当前线程id= " + Thread.currentThread().getId());
//思考->从servletRequest对象来获取请求方式->
//1. ServletRequest 没有得到提交方式的方法
//2. ServletRequest 看看ServletRequest子接口有没有相关方法
//3. 老师小技巧:ctrl+alt+b => 可以看到接口的子接口和实现子类
//4. 把servletReqeust转成 HttpServletRequest引用
//5. 仍然是Java基础的OOP
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String method = httpServletRequest.getMethod();
if("GET".equals(method)) {
doGet(); //用doGet() 处理GET请求
} else if("POST".equals(method)) {
doPost(); //用doPost() 处理POST请求
}
}
/**
* 用于响应get请求的
*/
public void doGet() {
System.out.println("doGet() 被调用..");
}
/**
* 用于响应post请求的
*/
public void doPost() {
System.out.println("doPost() 被调用..");
}
/**
* 返回servlet信息,使用较少
* @return
*/
@Override
public String getServletInfo() {
return null;
}
/**
* 1. 该方法是在servlet销毁时,被调用
* 2. 只会调用一次
*/
@Override
public void destroy() {
System.out.println("destroy() 被调用...");
}
}
web.xml配置
<!--小技巧: web.xml主要用来配置该web应用使用到的Servlet
1. servlet-name: 给Servlet取名(程序员决定), 该名字唯一
2. servlet-class: Servlet的类的全路径: Tomcat在反射生成该Servlet需要使用
3. url-pattern: 这个就是该servlet访问的url的配置(路径)
4. 这时我们应该这样访问servlet http://localhost:8080/servlet/helloServlet
5. url-pattern 取名是程序员决定的
6. load-on-startup 表示在tomcat 启动时,会自动的加载servlet实例
-->
<!-- 配置HelloServlet -->
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>com.hspedu.servlet.HelloServlet</servlet-class>
<!--<load-on-startup>1</load-on-startup>-->
</servlet>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/helloServlet</url-pattern>
</servlet-mapping>
3. 浏览器调用Servlet流程分析
第一次请求helloServlet根据url-pattern拿到servlet-name
在tomcat维护的HashMap<id,Servlet>中没有这个实例
底层根据反射技术将servlet实例化 init(),并放入到Map中
后面的请求就可以通过Map直接查找到对应的Servlet
4. Servlet生命周期
●主要有三个方法
1.init()初始化阶段
2.service()处理浏览器请求阶段
3.destroy()终止阶段
● 示意图(比较重要)
1.初始化阶段
Servlet 容器(比如: Tomcat)加载 Servlet,加载完成后,Servlet 容器会创建一个 Servlet 实例并调用init()方法,init()方法只会调用一次, Servlet 容器在下面的情况装载 Servlet:
1.Servlet 容器(Tomcat)启动时自动装载某些 servlet,实现这个需要在 web.xml 文件中添加
1 表示装载的顺序
<load-on-startup>1</load-on-startup>
2.在Servlet 容器启动后,浏览器首次向Servlet 发送请求
3.Servlet 重新装载时(比如 tomcat 进行 redeploy、Restart server、关闭server【会销毁所有的 Servlet 实例(Tomcat维护的HashMap<id,Servlet>也跟着销毁)】),再次请求或者重启时会重新初始化
2.处理浏览器请求阶段(service 方法)
Tomcat将浏览器发起http请求数据封装到了ServletRequest 对象中,简化了开发流程,我们只需要关注ServletRequest对象从中获取请求数据即可。
ServletResponse封装响应数据交给Tomcat,tomcat再将响应数据返回给浏览器
1.每收到一个 http 请求,服务器就会产生一个新的线程去处理[线程]
2.创建一个用于封装 HTTP 请求消息的 ServletRequest 对象和一个代表 HTTP 响应消息的ServletResponse 对象
3.然后调用Servlet 的service()方法并将请求和响应对象作为参数传递进去
tomcat自己维护了一个线程池,线程池的线程会进行复用
3.终止阶段 destory 方法(体现 Servlet 完整的生命周期)
当web 应用被终止,或者 Servlet 容器终止运行,或者Servlet 类重新装载时,会调用destroy()
方法
, 比如重启tomcat ,或者 redeploy web 应用
5.GET和POST请求的分发处理
开发 Servlet, 通常编写 doGet、doPost 方法。来对表单的 get 和 post 请求进行分发处理
●代码演示 HelloServlet.java register.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>注册</title>
</head>
<body>
<h1>注册用户</h1>
<form action="http://localhost:8080/helloServlet"
method="post">
u: <input type="text" name="username"/><br><br>
<input type="submit" value="注册用户"/>
</form>
</body>
</html>
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("浏览器访问HelloServlet。。。。");
String name = Thread.currentThread().getName();
long id = Thread.currentThread().getId();
int hashCode = Thread.currentThread().hashCode();
System.out.println("线程名称-" + name + ",线程id-" + id + ",线程hashCode-" + hashCode);
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String method = httpServletRequest.getMethod();
System.out.println(method);
String username = servletRequest.getParameter("username");
System.out.println("username:"+username);
if("GET".equals(method)){
doGet();
}else if("POST".equals(method)){
doPost();
}
}
6.通过继承HttpServlet开发Servlet
●HttpServlet 介绍
在实际项目中,都是使用继承 HttpServlet 类开发 Servlet 程序,更加方便
public class HiServlet extends HttpServlet {
//重写HttpServlet的doGet 和 doPost
//alt +insert
/**
* 处理doGet请求
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("HiServlet doGet()...");
}
/**
* 处理doPost
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("HiServlet doPost()...");
}
}
子类调用父类的service方法(类型传递)
7.IDEA开发Servlet程序
●说明
编手动开发 Servlet 需要程序员自己配置 Servlet ,比较麻烦,在工作中,直接使用 IDEA 开发 Servlet 会更加方便
8.Servlet 注意事项和细节
1.Servlet 是一个供其他 Java 程序(Servlet 引擎)调用的 Java 类,不能独立运行
2.针对浏览器的多次 Servlet 请求,通常情况下,服务器只会创建一个 Servlet 实例对象, 也就是说 Servlet 实例对象一旦创建,它就会驻留在内存中,为后续的其它请求服务,直至web 容器退出/或者 redeploy 该 web 应用,servlet 实例对象才会销毁 【示意图】
3.在 Servlet 的整个生命周期内,init 方法只被调用一次。而对每次请求都导致 Servlet 引擎调用一次 servlet 的 service 方法。
4.对于每次访问请求,Servlet 引擎都会创建一个新的 HttpServletRequest 请求对象和一个新的 HttpServletResponse 响应对象,然后将这两个对象作为参数传递给它调用的 Servlet 的 service()方法,service 方法再根据请求方式分别调用 doXXX 方法
5.如果在
<!--load-on-startup配置表示web程序启动时就创建Servlet实例对象,更具值的从小到大的先后顺序依次创建-->
<servlet>
<servlet-name>helloServlet</servlet-name>
<servlet-class>com.llp.HelloServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>helloServlet</servlet-name>
<servlet-class>com.llp.HiServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet>
<servlet-name>helloServlet</servlet-name>
<servlet-class>com.llp.HiServlet</servlet-class>
<load-on-startup>3</load-on-startup>
</servlet>
6.Servlet-注解方式
1.快速入门
1.编写类OkServlet去继承HttpServlet
2.注解方式配置OkServlet, 一个Servlet支持配置多个urlPattern
/**
* 1. @WebServlet 是一个注解 => java基础->注解
* 2. @WebServlet 源码
* @Target({ElementType.TYPE})
* @Retention(RetentionPolicy.RUNTIME)
* @Documented => 在javadoc工具生成文档有记录
* public @interface WebServlet {
<servlet-name>helloServlet</servlet-name>
* String name() default "";
*
* String[] value() default {};
* <url-pattern>/helloServlet</url-pattern>
* String[] urlPatterns() default {};
*
<load-on-startup>1</load-on-startup>
* int loadOnStartup() default -1;
*
<init-param>
* <param-name></param-name>
* <param-value></param-value>
* </init-param>
* WebInitParam[] initParams() default {};
*
* boolean asyncSupported() default false;
*
* String smallIcon() default "";
* }
* 3. urlPatterns 对应 web.xml 的 <url-pattern></url-pattern>
* 4. {"/ok1", "/ok2"} 可以给OkServlet配置多个 url-pattern
* 5. 相当于这个@WebServlet(urlPatterns = {"/ok1", "/ok2"}) 代替了 web.xml的配置
* 底层使用了 反射+注解+IO+集合 来完成一个支撑
* 6. 浏览器可以这样访问OkServlet时,可以 http://localhost:8080/servlet/ok1 或者
* http://localhost:8080/servlet/ok2
* 我们可以根据 @interface WebServlet 源码知道可以配置哪些
* web.xml init-param 在注解中,如何指定呢?
* <init-param>
* <param-name></param-name>
* <param-value></param-value>
* </init-param>
* 9. 注解方式开发Servlet和 web.xml配置servlet 流程机制是一样
* 10. /ok1/aa /ok1/bb /ok1/cc /ok1/aa/bb/cc /ok2
* 11. *.action 这时 zs.action ls.action
* 12. http://localhost:8080/servlet/register.html
* 13. 精确路径 > 目录路径 > 扩展名路径 > /* > /
*/
@WebServlet(urlPatterns = {"/ok1","/ok2","*.ok3","ok4/*"})
public class OkServlet extends HttpServlet {
@Override
public void init(ServletConfig config) throws ServletException {
System.out.println("注解方式 OkServlet init()被调用");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("注解方式 OkServlet doPost()");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(10/0);//报错
System.out.println("注解方式 OkServlet doGet()");
System.out.println(req.getRemoteAddr());
}
}
2.简单模拟Servlet注解方式实现
与之直接读取web.xml不同之处在于,Servlet注解方式是通过包扫码获取@WebServlet注解及注解的参数来创建Servlet实例及配置参数的。
/*
@WebServlet注解,包扫描会逐级扫描
在这之前还会通过io流去获取目录从而得到包路径
*/
public class TestAnnotationServlet {
private static final HashMap<String, Object> hm = new HashMap<>();
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
//1. 首先要得到扫描的包 路径 io, 进而得到类的全路径
String classAllPath = "com.hspedu.servlet.annotation.OkServlet";
//2. 得到 OkServlet的Class对象
Class<?> aClass = Class.forName(classAllPath);
//通过反射获取注解
// Annotation[] annotations = aClass.getAnnotations();
// for (Annotation annotation : annotations) {
// if(annotation instanceof WebServlet){
// }
// }
//3. 通过class对象,得到Annotation
WebServlet annotation = aClass.getAnnotation(WebServlet.class);
System.out.println(annotation);
String[] strings = annotation.urlPatterns();
for (String url : strings) {
System.out.println("url= " + url);
}
//如果匹配url,如果是第一次,tomcat就会创建一个OkServlet实例,放入到hashmap
Object instance = aClass.newInstance();
System.out.println("instance= " + instance);//OkServlet
//简单的模拟,没有深入.
hm.put("OkServlet", instance);
System.out.println(hm);
}
}
3.Servlet urlPattern配置
1.精确匹配
配置路径 : @WebServlet("/ok/zs")
访问 servlet: localhost:8080/servlet/ok/zs
2.目录匹配
配置路径 : @WebServlet("/ok/*")
访问文件: localhost:8080/servlet/ok/aaa localhost:8080/servlet/ok/bbb
3.扩展名匹配
配置路径 : @WebServlet("*.action")
访问文件: localhost:8080/hsp/zs.action localhost:8080/hsp/ls.action
提示: @WebServlet("/*.action") , 不能带 / , 否则 tomcat 报错
4.任意匹配
配置路径 : @WebServlet("/") @WebServlet("/*") *
访问文件: localhost:8080/hsp/aaa localhost:8080/hsp/bbb localhost:8080/hsp/ccc
注意:/ 和 /*的配置,会匹配所有的请求,这个比较麻烦,要避免
4.注意事项和使用细节
1、当 Servlet 配置了 "/", 会覆盖 tomcat 的 DefaultServlet, 当其他的 utl-pattern 都匹配 不 上 时 , 都 会 走 这 个 Servlet, 这 样 会 拦 截 到 其 它 静 态 资 源 ,导致无法访问静态资源,如:
http://localhost:8080/hello.html 原本可以直接访问,这样配置之后就会映射到配置了“/”的Servlet中
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>listings</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- The mapping for the default servlet -->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
2、当 Servelt 配置了 "/*", 表示可以匹配任意访问路径
3、提示: 建议不要使用 / 和 /*, 建议尽量使用精确匹配
4、优先级遵守: 精确路径 > 目录路径 > 扩展名路径 > /* > /
7.ServletConfig
1.ServletConfig 基本介绍
- ServletConfig 类是为 Servlet 程序的配置信息的类
- Servlet 程序和 ServletConfig 对象都是由 Tomcat 负责创建
- Servlet 程序默认是第 1 次访问的时候创建,ServletConfig 在 Servlet 程序创建时,就创 建一个对应的 ServletConfig 对 象
2.ServletConfig 类能干什么
- 获取 Servlet 程序的 servlet-name 的值
- 获取初始化参数 init-param
- 获取 ServletContext 对象
3.ServletConfig 应用实例
● 需求: 编写 DBServlet.java 完成如下功能
-
在 web.xml 配置连接 mysql 的用户名和密码
-
在 DBServlet 执行 doGet()/doPost() 时,可以获取到 web.xml 配置的用户名和密码
-
思路分析图
web.xml
<servlet>
<servlet-name>DBServlet</servlet-name>
<servlet-class>com.llp.http.DBServlet</servlet-class>
<init-param>
<param-name>username</param-name>
<param-value>llp</param-value>
</init-param>
<init-param>
<param-name>password</param-name>
<param-value>llp123</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>DBServlet</servlet-name>
<url-pattern>/db</url-pattern>
</servlet-mapping>
DBServlet
public class DBServlet extends HttpServlet {
/**
* ServletConfig config 使用流程
* 1. 当DBServlet对象初始化时, tomcat会同时创建一个 ServletConfig对象
* 2. 这时如果DBServlet init() 方法中你调用 super.init(config);
* 3. 调用 父类 GenericServlet
* public void init(ServletConfig config) throws ServletException {
* this.config = config;
* this.init();
* }
* 这时就会把 Tomcat创建的 ServletConfig对象赋给 GenericServlet的属性 config
* 4. 因此如果你重写init()方法,记住如果你想在其它方法通过 getServletConfig() 方法获取ServletConfig
* , 则一定要记住 调用 super.init(config);
* @param config
* @throws ServletException
*/
@Override
public void init(ServletConfig config) throws ServletException {
//ConcurrentHashMap, 是一个线程安全的容器.
System.out.println("init" + config);
super.init(config);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//在DBServlet 执行 doGet()/doPost() 时,可以获取到web.xml配置的用户名和密码
//你是一个OOP程序员->现有的方法或对象来搞定
//DBServlet的父类GenericServlet有getServletConfig()
/**
* 1. getServletConfig() 方法是 GenericServlet
* 2. 返回的 servletConfig对象是 GenericServlet private transient ServletConfig config;
* 3. 当一个属性被 transient 修饰,表示该属性不会被串行化(有些重要信息,不希望保存到文件)
*/
ServletConfig servletConfig = getServletConfig();
System.out.println("doPost=" + servletConfig);
String username = servletConfig.getInitParameter("username");
String pwd = servletConfig.getInitParameter("pwd");
System.out.println("初始化参数username= " + username);
System.out.println("初始化参数pwd= " + pwd);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
}
8.ServletContext
1.先看一个需求: 如果我们希望统计某个 web 应用的所有 Servlet 被访问的次数,怎么办?
2.方案 1-DB
3.方案 2-ServletContext
1.ServletContext 基本介绍
1.ServletContext 是一个接口,它表示 Servlet 上下文对象
2.一个 web 工程,只有一个 ServletContext 对象实例
3.ServletContext 对象 是在 web 工程启动的时候创建,在 web 工程停止的时销毁
4.ServletContext 对象可以通过 ServletConfig.getServletContext 方法获得对 ServletContext对象的引用,也可以通过this.getServletContext()来获得其对象的引用。
5.由于一个WEB 应用中的所有 Servlet 共享同一个 ServletContext 对象,因此 Servlet 对象之间可以通过 ServletContext 对象来实现多个Servlet 间通讯。ServletContext 对象通常也被称之为域对象。【示意图】
2.ServletContext 可以做什么
1.获取 web.xml 中配置的上下文参数 context-param [信息和整个web 应用相关,而不是属于某个 Servlet]
2.获取当前的工程路径,格式: /工程路径 = 》 比 如 /servlet
3.获取工程部署后在服务器硬盘上的绝对路径—class文件目录 (比如: D:\javaweb\servlet\out\artifacts\servlet_war_exploded)
4.像 Map 一样存取数据, 多个 Servlet 共享数据
3.应用实例 1-获取工程相关信息
●需求如下:
1.获取 web.xml 中配置的上下文参数 context-param
2.获取当前的工程路径,格式: /工程路径
3.获取工程部署后在服务器硬盘上的绝对路径
●代码实现 - 具体看 项目中的代码.
public class ServletContext_ extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1.获取到ServletContext对象
ServletContext servletContext = getServletContext();
//获取web.xml的context-parameter
Object nickname = servletContext.getInitParameter("nickname");
Object name = servletContext.getInitParameter("name");
//获取项目发布会,正在的工作路径
// /表示我们的项目(发布后)的 根路径 C:\ide\IdeaProjects\llp-javase\out\artifacts\servlet_war_exploded\
String contextPath = servletContext.getContextPath();
String realPath = servletContext.getRealPath("/");
System.out.println(nickname);
System.out.println(name);
System.out.println("工程路径:"+contextPath);
System.out.println("工程绝对路径: "+realPath);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request,response);
}
}
4.应用实例 2-简单的网站访问次数计数器
1.需求分析/图解
2.使用 Chrome 访问 Servlet01, 每访问一次,就增加 1 访问次数,在后台输出,并将结果返回给浏览器显示
3.使用火狐访问 Servlet02,每访问一次,就增加 1 访问次数,在后台输出,并将结果返回给浏览器显示
2.代码实现
PayServlet
@WebServlet(name = "PayServlet",urlPatterns = {"/pay"})
public class PayServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ServletContext servletContext = getServletContext();
System.out.println("PayServlet servletContext = "+servletContext);
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.print("<h1>网站访问次数:"+ WebVisitCountUtil.getVisitCount(servletContext)+"</h1>");
writer.flush();
writer.close();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
}
OrderServlet
@WebServlet(name = "OrderServlet", urlPatterns = {"/order"})
public class OrderServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ServletContext servletContext = getServletContext();
System.out.println("OrderServlet servletContext = "+servletContext);
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.print("<h1>网站访问次数:"+ WebVisitCountUtil.getVisitCount(servletContext)+"</h1>");
writer.flush();
writer.close();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
}
工具类
public static Integer getVisitCount(ServletContext servletContext){
Object visitCount = servletContext.getAttribute("visitCount");
if (visitCount == null) {
visitCount = 1;
servletContext.setAttribute("visitCount", visitCount);
} else {
servletContext.setAttribute("visitCount", Integer.parseInt(visitCount + "") + 1);
}
return Integer.parseInt(visitCount+"");
}
}
ServletContext 对象 是在 web 工程启动的时候创建,在 web 工程停止的时销毁
一个Web工程只有一个ServletContext对象
PayServlet servletContext = org.apache.catalina.core.ApplicationContextFacade@60e34aad
OrderServlet servletContext = org.apache.catalina.core.ApplicationContextFacade@60e34aad
9.HttpServletRequest
1.HttpServletRequest 介绍
1.HttpServletRequest 对象代表客户端的请求
2.当客户端/浏览器通过 HTTP 协议访问服务器时,HTTP 请求头中的所有信息都封装在这个对象中通过这个对象的方法,可以获得客户端这些信息。
2.HttpServletRequest 类图
3.HttpServletRequest 常用方法
1.getRequestURI() 获取请求的资源路径 http://localhost:8080/servlet/loginServlet
2.getRequestURL() 获取请求的统一资源定位符(绝对路径)
http://localhost:8080/servlet/loginServlet
3.getRemoteHost() 获取客户端的 主机, getRemoteAddr()
4.getHeader() 获取请求头5.getParameter() 获取请求的参数
6.getParameterValues() 获取请求的参数(多个值的时候使用) , 比如 checkbox, 返回的数组
7.getMethod() 获取请求的方式 GET 或 POST
8.setAttribute(key, value); 设置域数据
9.getAttribute(key); 获取域数据
10.getRequestDispatcher() 获取请求转发对象, 请求转发的核心对象
4.HttpServletRequest 应用实例
1.需求: 说明: 在一个表单提交数据给 Servlet , 然后在 Servlet 通过 HttpServletRequest对象获取相关数据
2.创建表单页面register.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>注册</title>
</head>
<body>
<h1>注册用户</h1>
<form action="http://localhost:8080/servlet/requestMethods"
method="post">
u: <input type="text" name="username"/><br><br>
p: <input type="password" name="pwd"/><br><br>
选择你喜欢的人物:
<input type="checkbox" name="hobby" value="hsp">孙悟空
<input type="checkbox" name="hobby" value="lh">贝吉塔
<input type="checkbox" name="hobby" value="spls">吉连<br/><br/>
<input type="submit" value="注册用户"/>
</form>
</body>
</html>
代码实现
@WebServlet(name = "HttpServletRequest_", urlPatterns = {"/request"})
public class HttpServletRequest_ extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//这里我们使用request对象,获取表单提交的各种数据
System.out.println("HttpServletRequestMethods doPost() 被调用..");
/***********************************
* 获取和http请求头相关信息
***********************************/
System.out.println("请求的资源路径URI= " + request.getRequestURI());// /request
//http://主机/uri
System.out.println("请求的统一资源定位符(绝对路径)URL= " + request.getRequestURL());// http://localhost:8080/request
System.out.println("请求的客户端ip 地址= " + request.getRemoteAddr());//本地就是127.0.01
//思考题:如发现某个ip 在10s中,访问的次数超过 100次,就封ip
//实现思路: 1用一个集合concurrentHashmap[ip:访问次数] 2[线程/定时扫描] 3 做成处理
// 获取http请求头的信息,可以指定其他,比如 User-Agent , Host等待
System.out.println("http请求头HOST= " + request.getHeader("Host"));// localhost:8080
// 说明,如果我们希望得到请求的头的相关信息,可以使用request.getHeader("请求头字段")
System.out.println("该请求的发起地址是= " + request.getHeader("Referer"));// http://localhost:8080/register.html
// 请获取访问网站的浏览器是什么?
String userAgent = request.getHeader("User-Agent");
//Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36
System.out.println("User-Agent= " + userAgent);
// 取出Chrome, 取出最后
String[] s = userAgent.split(" ");
System.out.println("浏览器=" + s[s.length - 2].split("\\/")[0]);
// 取出操作系统
String replace = s[1].replace("(", "");
System.out.println(replace);
//获取 Cookie
// JSESSIONID=8CBBD23BDE01BAE6705E03C5C8916BD1
String cookie = request.getHeader("Cookie");
String JSESSIONID = cookie.split("=")[1];
System.out.println("取出JSESSIONID= " + JSESSIONID);
// 主要是Get / Post
System.out.println("http请求方式~= " + request.getMethod());
/***********************************
* 获取和请求参数相关信息, 注意要求在返回数据前,获取参数
***********************************/
//解决接收参数的中文乱码问题,写在 getParameter前.
request.setCharacterEncoding("utf-8");
//1. 获取表单的数据[单个数据]
//username=tom&pwd=&hobby=hsp&hobby=spls
String username = request.getParameter("username");
String pwd = request.getParameter("pwd");
//2. 获取表单一组数据
String[] hobbies = request.getParameterValues("hobby");
System.out.println("username= " + username);
System.out.println("pwd= " + pwd);
//增强for循环的快捷键 iter->回车即可 , 能使用快捷键,就使用快捷键
for (String hobby : hobbies) {
System.out.println("hobby=" + hobby);
}
//返回接收到的信息, 给浏览器回显
//本质就是在http响应头,加上 Content-Type: text/html;charset=utf-8
//说 text/html 表示返回的数据类型,浏览器会根据这个类型来解析数据
// text/plain 表示返回的数据,请浏览器使用文本方式解析
// application/x-tar 表示返回的是文件,浏览器就会以下载文件的方式处理
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.print("<h1>提交的用户名= " + username + "</h1>");
writer.flush();
writer.close();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
}
ContentType 设置为 application/x-tar 表示返回的是文件,浏览器就会以下载文件的方式处理
5.HttpServletRequest 注意事项和细节
1.获取 doPost 参数中文乱码解决方案, 注意 setCharacterEncoding("utf-8") 要写在request.getParameter()前。
2.注意:如果通过 PrintWriter writer, 有返回数据给浏览器,建议将获取参数代码写在writer.print() 之前,否则可能获取不到参数值
3.处理 http 响应数据中文乱码问题
4.理解Http 协议响应 Content-Type 的含义, 比如 text/plain application/x-tar text/html
10.请求转发
1.为什么需要请求转发
一次请求,对应一个Servlet, 如图
在实际开发中,往往业务比较复杂,需要在一次请求中,使用到多个 Servlet 完成一个任务(Servlet 链, 流水作业) 如图:
2.请求转发说明
1.实现请求转发:请求转发指一个 web 资源收到客户端请求后,通知服务器去调用另外一个 web 资源进行处理
2.HttpServletRequest 对象(也叫Request 对象)提供了一个 getRequestDispatcher 方法,该方法返回一个RequestDispatcher 对象,调用这个对象的 forward 方法可以实现请求转发
3.request 对象同时也是一个域对象,开发人员通过 request 对象在实现转发时,把数据通过 request 对象带给其它web 资源处理
setAttribute方法
getAttribute方法
removeAttribute方法
getAttributeNames方法
3.实现请求转发
请求转发原理示意图
Servlet01处理完之后,tomcat根据web.xml文件调用Servlet02,
Servlet02处理完数据之后返回给Tomcat,tomcat将数据返回给浏览器
请求转发并不会创建一个新的request对象
4.请求转发应用实例
●需求说明/图解, 如果是 tom,提示为管理员,其它是普通用户.
代码实现
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<form action="http://localhost:8080/check" method="post">
u: <input type="text" name="username"><br>
<input type="submit" value="登录">
</form>
</body>
</html>
CheckServlet
@WebServlet(name = "CheckServlet",urlPatterns = {"/check"})
public class CheckServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
String username = request.getParameter("username");
//注意:如果是同一个request对象(请求转发),那么可以在不同的servlet中,是getParameter
if("tom".equals(username)){
request.setAttribute("role","超级管理员");
}else {
request.setAttribute("role","平台管理员");
}
//获取分发器
// 1. /manageServlet写的是 要转发的servlet的url
// 2. / 会被解析成 /servlet
// 3. forward(request, response) 表示把当前servlet的request对象和response对象,传递给下一个servlet使用
RequestDispatcher requestDispatcher = request.getRequestDispatcher("/manager");
requestDispatcher.forward(request,response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request,response);
}
}
ManagerServlet
@WebServlet(name = "ManagerServlet",urlPatterns = {"/manager"})
public class ManagerServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = request.getParameter("username");
String role = (String)request.getAttribute("role");
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.write("用户名: "+username+"</br>");
writer.write("角色: "+role);
writer.flush();
writer.close();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request,response);
}
}
5.请求转发注意事项和细节
1.浏览器地址不会变化(地址会保留在第 1 个 servlet 的 url)
2.在同一次HTTP 请求中,进行多次转发,仍然是一次 HTTP 请求
3.在同一次HTTP 请求中,进行多次转发,多个 Servlet 可以共享request 域/对象的数据(因为始终是同一个 request 对象)
4.可以转发到 WEB-INF 目录下(后面做项目使用)
5.不能访问当前WEB 工程外的资源
6.因为浏览器地址栏会停止在第一个 servlet ,如果你刷新页面,会再次发出请求(并且会带数据), 所以在支付页面情况下,不要使用请求转发,否则会造成重复支付[演示]
6.获取到浏览器所在电脑的操作系统版本和位数(练习)
请编写一个 Servlet, 可以获取到浏览器所在电脑的操作系统版本和位数(32 还是 64), 显示在页面即可
@WebServlet(name = "ComputerServlet",urlPatterns = {"/computerServlet"})
public class ComputerServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//System.out.println("ComputerServlet ..");
//可以获取到浏览器所在电脑的操作系统版本和位数(32还是64), 显示在页面即可
//分析一把 http请求头
//User-Agent
// Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:97.0) Gecko/20100101 Firefox/97.0
// 我们要 Windows NT 10.0 和 Win64
// (1) Windows NT 10.0; Win64; x64; rv:97.0
// (2) Windows NT 10.0 和 Win64
// (3) 使用正则表达式
String userAgent = request.getHeader("User-Agent");
//java基础 正则表达式
String regStr = "\\((.*)\\)";
Pattern compile = Pattern.compile(regStr);
Matcher matcher = compile.matcher(userAgent);
matcher.find();// 因为我们的userAgent只有一组 ()
String group = matcher.group(0);// (Windows NT 10.0; Win64; x64; rv:97.0)
String group1 = matcher.group(1);// Windows NT 10.0; Win64; x64; rv:97.0
String[] operInfos = group1.split(";");
System.out.println("操作系统=" + operInfos[0]);//Windows NT 10.0
System.out.println("操作系统位数=" + operInfos[1].trim());// Win64
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
}
11.HttpServletResponse
1.HttpServletResponse 介绍
1.每次 HTTP 请求,Tomcat 会创建一个 HttpServletResponse 对象传递给 Servlet 程序去使用。
2.HttpServletRequest 表示请求过来的信息,HttpServletResponse 表示所有响应的信息, 如果需要设置返回给客户端的信息,通过HttpServletResponse 对象来进行设置即可
2.HttpServletResponse 类图
3.向客户端返回数据方法
1.字节流 getOutputStream(); 常用于下载(处理二进制数据)
2.字符流 getWriter(); 常用于回传字符串
3.(细节:)两个流同时只能使用一个。 使用了字节流,就不能再使用字符流,反之亦然, 否则就会报错
4.向客户端返回数据注意事项和细节
1.处理中文乱码问题-方案 1
2.处理中文乱码问题-方案 2
12.请求重定向
1.请求重定向介绍
1.请求重定向指:一个 web 资源收到客户端请求后,通知客户端去访问另外一个 web资源,这称之为请求重定向
2.请求重定向原理示意图
3.重定向和转发的区别
1.重定向会跳转到重定向指定的页面,而请求转发不会跳转
根本原因:重定向浏览器(客户端)会发起两次http请求,而请求转发是在服务端进行请求,第二次请求浏览器不会参与因此不会跳转页面
2.重定向不能共享request域中的数据本质是两次http请求会生成两个HttpServletRequest对象,而请求转发可以共享指定的是同一个HttpServletRequest对象
3.重定向不能定向到/WEB-INF下的资源,tomcat有保护的机制;
请求转发是服务端发起请求,因此可以访问/WEB-INF下的资源
4.重定向可以重定向到Web工程以外的资源比如www.baidu.com
请求转发只能在服务端发起调用,不能访问web工程以外的资源
4.请求重定向应用实例
1.需求: 演示请求重定向的使用 当访问 DownServlet 下载文件, 重定向到
DownServletNew 下载文件
download.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>下载资源</h1>
<a href="/servlet/downServlet">下载天龙八部</a>
</body>
</html>
DownServlet
@WebServlet(name = "DownServlet",urlPatterns = {"/downServlet"})
public class DownServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.sendRedirect("/servlet/downServletNew");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request,response);
}
}
DownServletNew
@WebServlet(name = "DownServlet",urlPatterns = {"/downServletNew"})
public class DownServletNew extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("application/x-tar");
PrintWriter writer = response.getWriter();
writer.write("降龙十八掌");
writer.flush();
writer.close();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request,response);
}
}
5.请求重定向注意事项和细节
1.最佳应用场景:网站迁移,比如原域名是 www.hsp.com 迁移到 www.hsp.cn ,但是百度抓取的还是原来网址.
2.浏览器地址会发生变化,本质是两次http 请求.
3.不能共享Request 域中的数据,本质是两次 http 请求,会生成两个 HttpServletRequest
对象
4.不能重定向到 /WEB-INF 下的资源
5.可以重定向到Web 工程以外的资源, 比如 到 www.baidu.com 【在前面的案例演示】
6.重定向有两种方式, 推荐使用第 1 种.
7.动态获取到 application context
String contextPath = getServletContext().getContextPath(); System.out.println("contextPath= " + contextPath); response.sendRedirect(contextPath + "/downServletNew");