1.Servlet的执行流程
1. 从浏览器输入地址(发起请求)开始分析: http://localhost:80/xx/hello
- localhost 也可能是ip 或者域名 ---》定位服务器主机
- 80 定位到tomcat
- xx 上下文路径,tomcat/conf/server.xml 中的
<Context docBase=”F:workspaceday11-servletwebapp” path=”xx” /> 中的path
通过上面的步骤找到了 docBase后面的项目的真实路径
根据约定去找到项目中的配置文件 web.xml
2. web.xml内容分析:
- 找到url-pattern的值为 /hello
- 通过上一步中对应servlet-name 找到对应的servlet标签
- 找到对应的Servlet的全限定类名(包名+类名), com.gs.controller.HelloServlet
仅仅是一个字符串而已,交给Tomcat进行处理, 并通过反射来创建对象并调用方法
3. Tomcat对Servlet的创建:
- 通过上获得 servlet的完全限定名为参数使用反射来创建一个对象
Class clz = Class.forName(“com.gs.controller.HelloServlet”)
Clz.newInstance() 要求自己的Servlet类必须有公共的无参数的构造方法,否则会包NoSuchMethodException异常
2.如果是第一次访问,就会创建一个Servlet的对象,并缓存起来
3. 如果是第二次访问 (或者 n 次访问) 直接先从缓存中看有没有,有直接使用调用service方法
<welcome-file-list> <welcome-file>index.jsp</welcome-file> <welcome-file>index.html</welcome-file> </welcome-file-list> <servlet> <!-- 给servlet可以任意取名,一般类名相同 --> <servlet-name>HelloServlet</servlet-name> <!-- servlet的全限定类名,也就是servlet的位置 --> <servlet-class>com.gs.controller.HelloServlet</servlet-class> </servlet> <servlet-mapping> <!-- 跟上面的servlet名字相同 --> <servlet-name>HelloServlet</servlet-name> <!-- 浏览器通过该url找到servlet,斜杠别丢 --> <url-pattern>/helloServlet</url-pattern> </servlet-mapping>
Servlet的生命周期
javax.servlet.Servlet接口中的方法:
public void init(ServletConfig config): 初始化方法
public void service(ServletRequest req, ServletResponse res):服务方法
public void destroy():销毁方法
public ServletConfig getServletConfig():返回当前Servlet的配置信息对象.
public String getServletInfo():该方法返回Servlet的信息(作者,版权等).
--------------------------------------------------------------------
Servlet的生命周期方法:
public void init(ServletConfig config):初始化方法,在第一次请求时调用,只在最初的时候调用一次.
public void service(ServletRequest req, ServletResponse res):服务方法
public void destroy():销毁方法
Servlet生命周期可分为5个步骤
- 加载Servlet。当Tomcat第一次访问Servlet的时候,Tomcat会负责创建Servlet的实例
- 初始化。当Servlet被实例化后,Tomcat会调用init()方法初始化这个对象
- 处理服务。当浏览器访问Servlet的时候,Servlet 会调用service()方法处理请求
- 销毁。当Tomcat关闭时或者检测到Servlet要从Tomcat删除的时候会自动调用destroy()方法,让该实例释放掉所占的资源。一个Servlet如果长时间不被使用的话,也会被Tomcat自动销毁
- 卸载。当Servlet调用完destroy()方法后,等待 jvm 垃圾回收。如果有需要再次使用这个Servlet,会重新调用init()方法进行初始化操作。
细节注意点:
1:Servlet类必须使用public修饰,Servlet的构造器,必须是公共无参数的。(上面执行流程提到过)
构造器先执行,创建Servlet对象:init,service,destory方法,都是非static方法,都得使用对象调用.
2:一个Servlet类在整个生命周期中最多只有一个对象
Servlet什么时候初始化?
1. 之前我们学习过,Servlet的生命周期:
构造 -> init方法 -> [service](循环) -> distory(正常关闭) . 也就是说只有在访问的时候servlet才创建。
2. 启动的时候就创建Servlet:
<load-on-startup>1</load-on-startup> 注意中间的数据越小启动越早。
配置一个普通的servlet
<servlet>
<servlet-name>AServlet</servlet-name>
<servlet-class>com.servlet.AServlet</servlet-class>
<load-on-startup>4</load-on-startup><!--加这个标签,tomcat启动时会自动加载
指定的servlet 值是servlet的加载顺序!!! 数字越小代表加载的优先级越高-->
</servlet>
2. get方式和post方式有何区别
1.请求参数的位置上:
- GET方式:请求参数放在URL地址后面,以?的方式来进行拼接
- POST方式:请求参数放在HTTP请求包中
2. 数据长度上:
- GET方式:在URL地址后附带的参数是有限制的,其数据容量通常不能超过1K。
- POST方式:可以在请求的实体内容中向服务器发送数据,传送的数据量无限制。
3.用途和安全上
- GET方式提交的数据在地址栏可见,所以不安全。一般用来获取数据,多用于超链接请求。
- POST方式提交的数据在地址栏不可见,安全。一般用来提交数据,多用于HTML的表单提交(处理订货表单、在数据库中加入新数据行)。
3、Servlet的继承结构:
- Servlet接口:定义了Servlet应该具有的基本方法
- GenericServlet抽象类,实现了Servlet接口的同时,也实现了ServletConfig接口和Serializable这两个接口 。运用装饰者模式,为自己附加了ServletConfig装饰身份。ServletConfig接口中直接提供了getServletContext方法,所以当我们在实现Servlet时直接继承GenericServlet这个抽象类,就可以直接使用getServletContext方法来获取ServletContext对象了。
- HttpServlet也是一个抽象类,但是他内部没有抽象方法(有抽象方法的类一定是抽象类,但抽象类中不一定有抽象方法),在通用Servlet的基础上基于HTTP协议进行了进一步的强化:继承了GenericServlet类,复写了GenericServlet中的Service方法,Service方法体内的代码会自动判断用户的请求方式,如为GET请求,则调用HttpServlet的doGet方法,如为Post请求,则调用doPost方法。因此,开发人员在编写Servlet时,通常只需要继承HttpServlet,然后覆写doGet或doPost方法,而不要去覆写service方法。
总结一句话:FirstServlet(自定义servlet)继承于抽象类HttpServlet,HttpServlet继承于抽象类GenericServlet,GenericServlet实现了Servlet接口。
实现Servlet的三种方式
方式一:编写一个类去实现Servlet接口(必须重写Servlet接口里面所有的抽象方法)
方式二:编写一个类去继承GenericServlet抽象类(重写生命周期的service方法(抽象方法))GenericServle抽象类它实现了Servlet接口,还实现了ServletConfig接口(这个接口中提供了
一个getServletContext方法)可以在编写一个类中直接调用getServletContext方法就可以获得ServletContext对象。(开发中不常用)
方式三:编写一个类去继承HttpServlet抽象类(没有抽象方法!根据页面的提交方式决定重写doGet或者doPost方法)
4. HttpServletRequest是ServletRequest接口的子接口,表示HTTP协议的请求对象
HttpPServletResponse是ServletResponse的子接口,表示HTTP协议的响应对象
/* 1. String getContextPath():获取项目的上下文路径,<Context path="上下文" ../> 2. String getHeader(String headName):根据指定的请求头获取对应的值. 3. String getRequestURI():返回当期请求的资源路径名称. 上下文路径/资源名 4. StringBuffer getRequestURL():返回当前浏览器地址栏的数据 5. String getRemoteAddr():返回请求服务器的客户端所在主机的IP地址 6. String getParameter(String name):根据参数名称,获取对应参数的值. 7. String[] getParameterValues(String name):根据参数名称,获取该参数的多个值. 8. Enumeration<String> getParameterNames():获取所有请求参数的名字 9. Map<String,String[]> getParameterMap():返回请求参数组成的Map集合. key:参数名称 , value:参数值,封装在String数组中 10. request.setCharacterEncoding("utf-8"); */ request.setCharacterEncoding("utf-8"); response.setContentType("text/html;charset=utf-8"); String path = request.getContextPath(); //获取项目的上下文路径 System.out.println(path); // "/TestServlet111" String host = request.getHeader("host"); //根据请求头的名称查找对应的值 System.out.println(host); // "localhost:8080" String uri = request.getRequestURI(); //获取请求的资源路径名称 System.out.println(uri); // "/TestServlet111/helloServlet" StringBuffer url = request.getRequestURL();//返回当前浏览器的地址栏数据 System.out.println(url); // "http://localhost:8080/TestServlet111/helloServlet" String ip = request.getRemoteAddr();//返回发起请求的客户端的所在主机的ip地址 System.out.println(ip); // "127.0.0.1" 也有可能 "0:0:0:0:0:0:0:1" System.out.println(request.getParameter("username")); //根据参数名称,获取对应参数的值 String[] hobbys = request.getParameterValues("hobby"); //根据参数名称,获取该参数的多个值. for (String h : hobbys) { System.out.println(h); } Enumeration<String> names = request.getParameterNames(); //获取所有请求参数的名字 while(names.hasMoreElements()) { System.out.println(names.nextElement()); //比如: username password gender等 } //返回请求参数组成的Map集合. key:参数名称 , value:参数值,封装在String数组中 Map<String, String[]> params = request.getParameterMap(); System.out.println(params.get("password")[0]); // "张三" System.out.println(Arrays.toString(params.get("hobby"))); // "[看电影, 打游戏]" /* response.setContentType("text/html;charset=utf-8"); OutputStream getOutputStream(): 获取字节输出流. :文件下载 Writer getWriter(): 获取字符输出流. :输出内容 注意:以上两个方法不能共存,只能使用其中一个。否则,报错 */ response.getOutputStream().print("hello world"); // hello world }
5:请求中文乱码问题
1. 第一种解决乱码的问题:
request.setCharacterEncoding("UTF-8"); 只支持POST,不支持GET
2. 第二种:
在server.xml中修改编码:URIEncoding="UTF-8" 只支持GET,不支持POST
推荐第一种和第二种同时使用
3. 第三种:
String name = req.getParameter("name");
//将原来的码给转成二进制
byte[] nameBytes = name.getBytes("ISO-8859-1");
//再通过UTF-8的编码把这个数据转成相应的字符串
String nameStr = new String(nameBytes,"UTF-8");
GET与POST都支持
注:第二种与第三种在GET的情况下是冲突的,不能同时存在
6. servlet初始化参数
1、servlet初始化参数,在web.xml中配置
一、ServletConfig:代表当前Servlet在web.xml中的配置信息.
- String getServletName() -- 获取当前Servlet在web.xml中配置的名字
- String getInitParameter(String name) -- 获取当前Servlet指定名称的初始化参数的值
- Enumeration getInitParameterNames() -- 获取当前Servlet所有初始化参数的名字组成的枚举
- ServletContext getServletContext() -- 获取代表当前web应用的ServletContext对象
在Servlet的配置文件中,可以使用一个或多个<init-param>标签为servlet配置一些初始化参数。
当servlet配置了初始化参数后,web容器在创建servlet实例对象时,会自动将这些初始化参数封装到ServletConfig对象中,并在调用servlet的init方法时,将ServletConfig对象传递给servlet。进而,程序员通过ServletConfig对象就可以得到当前servlet的初始化参数信息。
这样做的好处是:如果将数据库信息、编码方式等配置信息放在web.xml中,如果以后数据库的用户名、密码改变了,则直接很方便地修改web.xml就行了,避免了直接修改源代码的麻烦。
代码举例:
新建一个名为ServletConfigTest的Servlet,然后在web.xml中的<servlet>标签下,通过<init-param>标签为这个servlet配置两个初始化参数:
<servlet> <servlet-name>ServletConfigTest</servlet-name> <servlet-class>com.vae.servlet.ServletConfigTest</servlet-class> <init-param> <param-name>name1</param-name> <param-value>value1</param-value> </init-param> <init-param> <param-name>encode</param-name> <param-value>utf-8</param-value> </init-param> </servlet>
12 public class ServletConfigTest extends HttpServlet { 13 14 public void doGet(HttpServletRequest request, HttpServletResponse response) 15 throws ServletException, IOException { 16 17 ServletConfig config = this.getServletConfig(); //拿到init方法中的ServletConfig对象 18 19 // --获取当前Servlet 在web.xml中配置的名称(用的不多) 20 String sName = config.getServletName(); 21 System.out.println("当前Servlet 在web.xml中配置的名称:"+sName); 22 23 // --获取当前Servlet中配置的初始化参数(只能获取一个)经常用到 24 // String value = config.getInitParameter("name2"); 25 // System.out.println(value); 26 27 // --获取当前Servlet中配置的初始化参数(全部获取)经常用到 28 Enumeration enumration = config.getInitParameterNames(); 29 while(enumration.hasMoreElements()){ 30 String name = (String) enumration.nextElement(); 31 String value = config.getInitParameter(name); 32 System.out.println(name+":"+value); 33 } 34 } 35 36 public void doPost(HttpServletRequest request, HttpServletResponse response) 37 throws ServletException, IOException { 38 doGet(request, response); 39 } 40 }
2、使用注解完成servlet初始化参数。
注意web.xml中: metadata-complete="true": 值为true 时不扫描类上的WebServlet注解. 值为false 时要扫描,默认缺省。
@WebServlet( name = "servletParameterServlet", urlPatterns = {"/servletParameter"}, initParams = {@WebInitParam(name = "server", value = "mysql") , @WebInitParam(name = "database", value = "user")} ) public class ServletParameterServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //使用servletConfig类进行获取 ServletConfig sc = this.getServletConfig(); String server = sc.getInitParameter("server"); String database = sc.getInitParameter("database"); resp.getWriter().println("server : [" + server + "] database : [" + database + "]"); } }
相当于使用注解如下:
7. ServletContext对象
1. 什么是ServletContext对象
ServletContext代表的是一个web应用的环境(上下文)对象,ServletContext对象内部封装是该web应用的信息,ServletContext对象 一个web应用只有一个。
2. ServletContext对象的生命周期?
创建:该web应用被加载(服务器启动或发布web应用(前提,服务器启动状态))
销毁:web应用被卸载(服务器关闭,移除该web应用)
3. 怎样获得ServletContext对象的三种方法
1)Servlet类中有个init(ServletConfig config)方法,可以通过该方法获得ServletContext对象;
例:ServletContext servletContext=config.getServletContext();
2)可以通过this关键字来获得ServletContext对象;
例:ServletContext servletContext=this.getServletContext();
注意:
super.getServletContext() this.getServletContext() getServletContext()
这三个方法都是一样的,都是相当于从父类中的获取的方法。
3)用过session对象来获取:ServletContext sc = request.getSession().getServletContext();
4.ServletContext的作用
(2)获得web应用中某个资源的绝对路径(重要 )
String getRealPath(String path) 例如获得 : F:workspaceservlet_jspwebapp
(3)获取当前应用的上下文路径. String getContextPath(): 也就是 Tomcat/conf/server.xml中的path值
(4)ServletContext是一个域对象(重要)
什么是域对象? 存储数据的区域就是域对象
域:域对象在不同的资源之间来共享数据,保存数据,获取数据。
ServletContext域对象的作用范围:整个web应用(所有的web资源都可以随意向servletcontext域中存数据,数据可以共享)
域对象的通用的方法:
保存共享数据: setAtrribute(String name, Object obj); //第一个参数为字符串,第二个是Object(也就是任意类型)
获取共享数据: getAtrribute(String name);
删除共享数据: removeAtrribute(String name);
8.获取资源文件的三种方法.
1.采用ServletContext对象获取 。 特征:必须有web环境,任意文件,任意路径。
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //拿到全局对象 ServletContext sc = this.getServletContext(); //获取p1.properties文件的路径 String path = sc.getRealPath("/WEB-INF/classes/p1.properties"); System.out.println("path=" + path); //创建一个Properties对象 Properties pro = new Properties(); pro.load(new FileReader(path)); System.out.println(pro.get("k")); }
2.采用resourceBundle获取。 只能拿取properties文件,非web环境。
//采用resourceBundle拿取资源文件,获取p1资源文件的内容,专门用来获取.properties文件 ResourceBundle rb = ResourceBundle.getBundle("p1"); System.out.println(rb.getString("k"));
3.采用类加载器获取: 任意文件,任意路径。
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //通过类加载器 //1.通过类名 ServletContext.class.getClassLoader() //2.通过对象 this.getClass().getClassLoader() //3.Class.forName() 获取 Class.forName("ServletContext").getClassLoader InputStream input = this.getClass().getClassLoader().getResourceAsStream("p1.properties"); //创建Properties对象 Properties pro = new Properties(); try { pro.load(input); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } //拿取文件数据 System.out.println("class:" + pro.getProperty("k")); }
9. Servlet线程安全问题
由于Servlet容器是多线程单实例的模型(一个Servlet类在整个生命周期中最多只有一个对象),当两个或多个线程同时访问同一个Servlet时,可能会发生多个线程同时访问同一资源的情况,数据可能会变得不一致。
1. 解决servlet线程安全问题:
- 把全局变量改为局部变量。
- 使用synchronized对doGet进行同步, protected synchronized void doGet(HttpServletRequest request, HttpServletResponse response)
采用这种方式明显不合适,因为这样T2必须要等T1执行完毕以后才可以执行,大大的影响了效率。
3.如果是静态资源则加上final表示这个资源不可以改变。比喻 final static String url="jdbc:mysql://localhost:3306/blog";
4. 实现 SingleThreadModel 接口,和同步差不多,影响性能,已经过时。
参考:https://www.cnblogs.com/LipeiNet/p/5699944.html
https://www.cnblogs.com/tanjian/articles/1633162.html
参考文献 :
https://blog.csdn.net/qq_38353993/article/details/83277339
https://www.jianshu.com/p/fb574ba6f1a9
https://www.cnblogs.com/smyhvae/p/4140877.html