本章内容:
1. servlet概念及相关接口简介
2. servet 执行过程
3. servlet路径映射
4. 缺省servlet --应用
5. servlet生命周期(重点) --理解(重点)
6. Servlet自动加载
7. Servlet线程安全
8. servletConfig对象
9. Servlet相关接口详解
10. ServletContext对象 --知识点
如何开发一个Servlet
步骤:
1)编写java类,继承HttpServlet类
2)重写doGet和doPost方法
3)Servlet程序交给tomcat服务器运行!! (在web.xml文件中进行配置)
Tomcat加载顺序
问题:访问次URL: http://localhost:8080/day10/first
前提: tomcat服务器启动时,首先加载webapps中的每个web应用的web.xml配置文件。
- http://: http协议
- localhost: 到本地的hosts文件中查找是否存在该域名对应的IP地址127.0.0.1
- 8080: 找到tomcat服务器
- /day10 在tomcat的webapps目录下找 day10的目录(sp中用的是tomcat6,确实有加载多个web app)
- /first 资源名称。
1)在day10的web.xml中查找是否有匹配的url-pattern的内容(/first)
2)如果找到匹配的url-pattern,则使用当前servlet-name的名称到web.xml文件中查询是否相同名称的servlet配置
3)如果找到,则取出对应的servlet配置信息中的servlet-class内容:
字符串: gz.itcast.a_servlet.FirstServlet
通过反射:
a)构造FirstServlet的对象
b)然后调用FirstServlet里面的方法
Servlet的映射路径
精确匹配 or 模糊匹配
/* http://localhost:8080/day10/任意路径
*.后缀名 http://localhost:8080/day10/任意路径.do
如:*.do *.action * .html(伪静态)
注意:
1)url-pattern要么以 / 开头,要么以*开头。
2)不能同时使用两种模糊匹配,例如 /itcast/*.do是非法路径
3)当有输入的URL有多个servlet同时被匹配的情况下:
3.1 精确匹配优先。(长的最像优先被匹配)
3.2 以后缀名结尾的模糊url-pattern优先级最低!!!
servlet缺省路径
apache-tomcat-9.0.0.M11\conf路径下的web.xml文件
servlet的缺省路径(<url-pattern>/</url-pattern>
)是在tomcat服务器内置的一个路径。该路径对应的是一个DefaultServlet(缺省Servlet)。这个缺省的Servlet的作用是用于解析web应用的静态资源文件。
问题: URL输入http://localhost:8080/day10/index.html 如何读取文件????
1)到当前day10应用下的web.xml文件查找是否有匹配的url-pattern。
2)如果没有匹配的url-pattern,则交给tomcat的内置的DefaultServlet处理
3)DefaultServlet程序到day10应用的根目录下查找是存在一个名称为index.html的静态文件。
4)如果找到该文件,则读取该文件内容,返回给浏览器。
5)如果找不到该文件,则返回404错误页面。
结论: 先找动态资源,再找静态资源。
Sevlet的生命周期(重点)
Servlet的生命周期: servlet类对象什么时候创建,什么时候调用什么方法,什么时候销毁。
Servlet程序的生命周期由tomcat服务器控制的!
Servlet重要的四个生命周期方法
构造方法: 创建servlet对象的时候调用。默认情况下,第一次访问servlet的时候创建servlet对象。构造方法只调用1次。证明servlet对象在tomcat是单实例的。
init方法: 创建完servlet对象的时候调用。只调用1次。
service方法: 每次发出请求时调用。调用n次。
destroy方法: 销毁servlet对象的时候调用。停止服务器或者重新部署web应用时销毁servlet对象。只调用1次。
伪代码演示servlet的生命周期
Tomtcat内部代码运行:
1)通过映射文件找到servlet-class的内容,字符串: gz.itcast.a_servlet.FirstServlet
2)通过反射构造FirstServlet对象
2.1 得到字节码对象
Class clazz = class.forName(“gz.itcast.a_servlet.FirstServlet”);
2.2 调用无参数的构造方法来构造对象
Object obj = clazz.newInstance(); —1.servlet的构造方法被调用
3)创建ServletConfig对象,通过反射调用init方法
3.1 得到方法对象
Method m = clazz.getDeclareMethod(“init”,ServletConfig.class);
3.2 调用方法
m.invoke(obj,config); –2.servlet的init方法被调用
4)创建request,response对象,通过反射调用service方法
4.1 得到方法对象(这里调用方法对象,继续使用反射,是因为这样的代码有通用性?)
Method m =clazz.getDeclareMethod(“service”,HttpServletRequest.class,HttpServletResponse.class);
4.2 调用方法
m.invoke(obj,request,response); –3.servlet的service方法被调用
5)当tomcat服务器停止或web应用重新部署,通过反射调用destroy方法
5.1 得到方法对象
Method m = clazz.getDeclareMethod(“destroy”,null);
5.2 调用方法
m.invoke(obj,null); –4.servlet的destroy方法被调用
用时序图来演示servlet的生命周期
Servlet的自动加载
默认情况下,第一次访问servlet的时候创建servlet对象。如果servlet的构造方法或init方法中执行了比较多的逻辑代码,那么导致用户第一次访问sevrlet的时候比较慢。
改变servlet创建对象的时机: 提前到加载web应用的时候!!
在servlet的配置信息中,加上一个< load-on-startup >即可!!
<servlet>
<servlet-name>LifeDemo</servlet-name>
<servlet-class>gz.itcast.c_life.LifeDemo</servlet-class>
<!-- 让servlet对象自动加载 -->
<load-on-startup>1</load-on-startup> 注意: 整数值越大,创建优先级越低!!
</servlet>
有参的init方法和无参的init方法
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
/**
* 有参数的init和无参的init方法
* @author APPle
*
*/
public class InitDemo extends HttpServlet {
/**
* 有参数的init方法
* 该方法是servlet的生命周期方法,一定会被tomcat服务器调用
*/
/**
* 注意:如果要编写初始代码,不需要覆盖有参数的init方法(因为如果覆盖了父类的这个有参构造方法,则会默认调用子类里重写的这个方法)
*/
/*@Override
public void init(ServletConfig config) throws ServletException {
System.out.println("有参数的init方法");
}*/
/**
* 无参数的init方法
* 该方法是servlet的编写初始化代码的方法。是Sun公司设计出来专门给开发者进行覆盖,然后在里面编写servlet的初始逻辑代码的方法。
*/
@Override
public void init() throws ServletException {
System.out.println("无参数的init方法");
}
}
Servlet的多线程并发问题
注意: servlet对象在tomcat服务器是单实例多线程的。
因为servlet是多线程的,所以当多个servlet的线程同时访问了servlet的共享数据,如成员变量,可能会引发线程安全问题。
解决办法:
1)把使用到共享数据的代码块进行同步(使用synchronized关键字进行同步)
2)建议在servlet类中尽量不要使用成员变量。如果确实要使用成员,必须同步。而且尽量缩小同步代码块的范围。(哪里使用到了成员变量,就同步哪里!!),以避免因为同步而导致并发效率降低。
所以,如果Servlet中一定要有成员变量的话,需要加锁处理
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* servlet的多线程并发问题
* @author APPle
*
*/
public class TheradDemo extends HttpServlet {
int count = 1;
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
synchronized (TheradDemo.class) {//锁对象必须唯一。建议使用类对象
response.getWriter().write("你现在是当前网站的第"+count+"个访客"); //线程1执行完 , 线程2执行
//线程1还没有执行count++
/*try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
count++;
}
}
}
ServletConfig对象
1、作用
ServletConfig对象: 主要是用于加载servlet的初始化参数。在一个web应用可以存在多个ServletConfig对象(一个Servlet对应一个ServletConfig对象)
2、对象创建和得到
创建时机: 在创建完servlet对象之后,在调用init方法之前创建。
得到对象: 直接从有参数的init方法中得到!
3、servlet的初始化参数配置
<servlet>
<servlet-name>ConfigDemo</servlet-name>
<servlet-class>gz.itcast.f_config.ConfigDemo</servlet-class>
<!-- 初始参数: 这些参数会在加载web应用的时候,封装到ServletConfig对象中 -->
<init-param>
<param-name>path</param-name>
<param-value>e:/b.txt</param-value>
</init-param>
</servlet>
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.Enumeration;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ConfigDemo extends HttpServlet {
/**
* 以下两段代码GenericServlet已经写了,我们无需编写!!
*/
/*private ServletConfig config;*/
/**
* 1)tomcat服务器在加载web应用的时候,把这些参数封装到ServletConfig对象中
* 2)tomcat服务器调用init方法传入ServletConfig对象
*/
/*@Override
public void init(ServletConfig config) throws ServletException {
super.init(config)
}*/
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
/**
* 读取servlet的初始参数
*/
String path = this.getServletConfig().getInitParameter("path");
File file = new File(path);
//读取内容
BufferedReader br = new BufferedReader(new FileReader(file));
String str = null;
while( (str=br.readLine())!=null ){
System.out.println(str);
}
//查询当前servlet的所有初始化参数
Enumeration<String> enums = this.getServletConfig().getInitParameterNames();
while(enums.hasMoreElements()){
String paramName = enums.nextElement();
String paramValue = this.getServletConfig().getInitParameter(paramName);
System.out.println(paramName+"="+paramValue);
}
//得到servlet的名称
String servletName = this.getServletConfig().getServletName();
System.out.println(servletName);
}
}
注意: servlet的参数只能由当前的这个sevlet获取!!!!
ServletConfig的API:
java.lang.String getInitParameter(java.lang.String name) 根据参数名获取参数值
java.util.Enumeration getInitParameterNames() 获取所有参数
ServletContext getServletContext() 得到servlet上下文对象
java.lang.String getServletName() 得到servlet的名称
ServletContext对象
ServletContext对象 ,叫做Servlet的上下文对象。表示一个当前的web应用环境。一个web应用中只有一个ServletContext对象。
1、1 ServletContext对象的核心API(作用)
java.lang.String getContextPath() –得到当前web应用的路径
java.lang.String getInitParameter(java.lang.String name) –得到web应用的初始化参数
java.util.Enumeration getInitParameterNames()
void setAttribute(java.lang.String name, java.lang.Object object) –域对象有关的方法
java.lang.Object getAttribute(java.lang.String name)
void removeAttribute(java.lang.String name)
RequestDispatcher getRequestDispatcher(java.lang.String path) –转发(类似于重定向)
java.lang.String getRealPath(java.lang.String path) –得到web应用的资源文件
java.io.InputStream getResourceAsStream(java.lang.String path)
=========
对象创建和得到
Sun公司设计
1)创建ServletContext对象 ServletContext context = new ServletContext()
2)创建ServletConfig对象 ServetConfig config = new ServletConfig();
config.setServletContxt(context);
class ServletConfig{
ServletContext context;
public ServletContext getServletContxt(){
return contxt;
}
}
public void init( ServletConfig config ){
得到ServletConfig对象
从ServletConfig对象中得到ServletContext对象
SerlvetContext context = config.getServletContext();
}
=======
得到web应用路径
java.lang.String getContextPath() 用在请求重定向的资源名称中
“idea里是在tomcat里配置的应用路径,当运行该web程序后,后缀部分的第一个地址就固定了”
import java.io.IOException;
/**
* 得到web应用路径
* @author APPle
*
*/
public class ContextDemo1 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//1.得到ServletContext对象
//ServletContext context = this.getServletConfig().getServletContext();
ServletContext context = this.getServletContext(); //(推荐使用)
//2.得到web应用路径 /day10
/**
* web应用路径:部署到tomcat服务器上运行的web应用名称
*/
String contextPath = context.getContextPath();
System.out.println(contextPath);
/**
* 案例:应用到请求重定向
*/
response.sendRedirect(contextPath+"/index.html");
}
}
======
得到web应用的初始化参数(全局)
java.lang.String getInitParameter(java.lang.String name) –得到web应用的初始化参数
java.util.Enumeration getInitParameterNames()
配置文件中:
web应用参数可以让当前web应用的所有servlet获取!
<context-param>
<param-name>webAppRootKey</param-name>
<param-value>zz_site</param-value>
</context-param>
以下参数,只有特定servlet才可使用
<servlet>
<servlet-name>ConfigDemo</servlet-name>
<servlet-class>gz.itcast.f_config.ConfigDemo</servlet-class>
<!-- 初始参数: 这些参数会在加载web应用的时候,封装到ServletConfig对象中 -->
<init-param>
<param-name>path</param-name>
<param-value>e:/b.txt</param-value>
</init-param>
</servlet>
=========
域对象有关的方法
域对象:作用是用于保存数据,获取数据。可以在不同的动态资源之间共享数据。
两种方案:
1、response.sendRedirect(“/Servlet2?name=eric”)
2、String request.getParameter(“name”)
方案1: 可以通过传递参数的形式,共享数据。局限:只能传递字符串类型,而且会暴露参数
方案2: 可以使用域对象共享数据,好处:可以共享任何类型的数据!
ServletContext就是一个域对象!作用范围在整个web应用中有效
保存数据:void setAttribute(java.lang.String name, java.lang.Object object)
获取数据: java.lang.Object getAttribute(java.lang.String name)
删除数据: void removeAttribute(java.lang.String name)
/**
* 保存数据
* @author APPle
*
*/
public class ContextDemo3 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//1.得到域对象
ServletContext context = this.getServletContext();
//2.把数据保存到域对象中
//context.setAttribute("name", "eric");
context.setAttribute("student", new Student("jacky",20));
System.out.println("保存成功");
}
}
/**
* 获取数据
* @author APPle
*
*/
public class ContextDemo4 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//1.得到域对象
ServletContext context = this.getServletContext();
//2.从域对象中取出数据
//String name = (String)context.getAttribute("name");
Student student = (Student)context.getAttribute("student");
//System.out.println("name="+name);
System.out.println(student);
}
}
所有域对象:
HttpServletRequet 域对象
ServletContext域对象
HttpSession 域对象
PageContext域对象
转发
RequestDispatcher rd = this.getServletContext().getRequestDispatcher("/GetDataServlet");
rd.forward(request, response);
1)转发
a)地址栏不会改变
b)转发只能转发到当前web应用内的资源
c)可以在转发过程中,可以把数据保存到request域对象中
2)重定向
a)地址栏会改变,变成重定向到地址。
b)重定向可以跳转到当前web应用,或其他web应用,甚至是外部域名网站。
c)不能在重定向的过程,把数据保存到request中。
request.getRequestDispacher(“路径”).forward(request,response); 等价于context里的方法
结论: 如果要使用request域对象进行数据共享,只能用转发技术!
web应用中定向时地址的写法
public class PathDemo extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
//目标资源: target.html
/**
* 思考: 目标资源是给谁使用的。
* 给服务器使用的:转发,超链接,form提交 “/”表示在当前web应用的根目录(webapp下)
* 给浏览器使用的:重定向, “/”表示在webapps的根目录下(多个webapp时)
*/
/**
* 1.转发
*/
request.getRequestDispatcher("/target.html").forward(request, response);
/**
* 2.请求重定向
*/
response.sendRedirect("/testMethod.html");
/**
* 3.html页面的超连接href
*/
response.getWriter().write("<html><body><a href='/target.html'>超链接</a></body></html>");
/**
* 4.html页面中的form提交地址
*/
response.getWriter().write("<html><body><form action='/target.html'><input type='submit'/></form></body></html>");
}
}
得到web应用中的资源文件
通用的写法,不会因为项目在不同机器上部署而出错
/**
* 读取web应用下的资源文件(例如properties)
* @author APPle
*/
public class ResourceDemo extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
/**
* . 代表java命令运行目录。java运行命令在哪里?? 在tomcat/bin目录下
* 结论: 在web项目中, . 代表在tomcat/bin目录下开始,所以不能使用这种相对路径。
*/
//读取文件。在web项目下不要这样读取。因为.表示在tomcat/bin目录下
/*File file = new File("./src/db.properties");
FileInputStream in = new FileInputStream(file);*/
/**
* 使用web应用下加载资源文件的方法
*/
/**
* 1. getRealPath读取,返回资源文件的绝对路径
*/
String path = this.getServletContext().getRealPath("/WEB-INF/classes/db.properties");
System.out.println(path);
/*File file = new File(path);
FileInputStream in = new FileInputStream(file);*/
/**
* 2. getResourceAsStream() 得到资源文件,返回的是输入流
*/
InputStream in = this.getServletContext().getResourceAsStream("/WEB-INF/classes/db.properties");
Properties prop = new Properties();
//读取资源文件
prop.load(in);
String user = prop.getProperty("user");
String password = prop.getProperty("password");
System.out.println("user="+user);
System.out.println("password="+password);
}
}