JavaWeb之《Servlet》
在集成开发环境中开发Servlet程序
- 集成开发工具很多,其中目前使用较多为IDEA
- 实现javax.servlet接口中的五个方法
Servlet生命周期
-
Servlet对象的创建,对象上方法的调用,对象最终的销毁,javaweb程序员无权干涉。
-
是由Tomcat服务器(WEB Server)全权负责的。
-
Tomcat服务器通常我们又称为:WEB容器
-
WEB容器来管理Servlet对象的死活
-
我们自己new的Servlet对象是不受WEB容器管理的
-
研究:服务器在启动的Servlet对象有没有被创建出来?
- 在Servlet中提供一个无参数的构造方法,启动服务器的时候看看构造方法是否执行
- 经过测试得出结论:默认情况下,服务器在启动的时候Servlet并不会被初始化
- 用户没有发送请求之前,如果提前创建出来所有的Servlet对象,必然是耗费内存的。
-
怎么在服务器启动的时候创建Servlet对象
-
在servlet配置标签中用子标签
-
<load-on-startup>0<load-on-startup>
-
数字越小,启动越靠前,最小值0,用户的体验更好
-
-
用户在发送第一次请求的时候Servlet对象被实例化
-
Servlet对象被创建出来之后,Tomcat服务器马上调用了Servlet对象的init方法。(init方法在执行的时候,Servlet对象已经存在了,已经被创建出来了)
-
用户发送第一次请求的时候,init方法执行之后,Tomcat服务器马上调用Servlet对象的service方法
-
用户发送第二次请求的时候,控制台只执行了service方法,说明:
- Servlet对象是单例的(单实例的。但是要注意:Servlet对象是单实例的,但是Servlet类并不符合单例模式。我们称之为假单例)
- 无参数构造方法,init方法只在第一次用户发送请求的时候执行。也就是说无参数构造方法只执行一次。init方法也只被Tomcat服务器调用一次。
- 只要用户发送一次请求:service方法必然会被Tomcat服务器调用一次。发送100次请求,service方法会被调用100次。
-
Servlet的destroy方法只会被Tomcat服务器调用一次
- destroy方法是在什么时候被调用的?
- 在服务器关闭的时候
- 因为服务器关闭的时候要销毁Servlet对象的内存
- 服务器在销毁Servlet对象内存之前,Tomcat服务器会自动调用Servlet对象的destroy方法
- destroy方法调用的时候,对象销毁了还是没有销毁?
- destroy方法执行的时候Servlet对象还在,没有被销毁
- destroy方法是在什么时候被调用的?
-
关于Servlet类中方法的调用次数?
- 构造方法只执行一次
- init方法只执行一次
- service方法,发送N次请求则执行N次
- destroy方法,只执行一次
-
当我们Servlet类中编写一个有参数的构造方法,如果没有手动编写无参数构造方法会出现什么问题?
- 报错:500
- 一般情况下是因为服务器端的Java程序出现了异常
- 所以在Servlet开发中,不建议程序员定义构造方法
-
思考:Servlet的无参构造方法是在对象第一次创建的时候执行,并且只执行一次。init方法也是在对象第一次创建的时候执行,并且只执行一次,那么这个无参构造方法可以代替init方法吗?
- 不能
- Servlet规范中有要求,不建议手动编写构造方法,因为编写构造方法,很容易让无参构造方法消失,这个操作可能会导致Servlet对象无法实例化。所以init方法是有存在的必要的。
-
init和destroy一般用不上
适配器模式改造Servlet
-
我们编写一个Servlet类直接实现Servlet接口有什么缺点?
- 我们只需要service方法,其他方法大部分情况下是不需要使用的。代码很丑陋
-
适配器设计模式Adapter
- 编写一个标准通用的Servlet,起名:GenericServlet
- 以后所有的Servlet类都不要直接实现Servlet接口了
- 以后所有的Servlet类都要继承GenericServlet类
- GenericServlet就是一个适配器
- 把GenericServlet类设置为抽象类
- 把其中常用的方法设置为抽象方法,其他方法都固定写好
- 然后子类重写GenericServlet中的抽象方法
-
思考:GenericServlet类是否需要改造一下?怎么改造?更利于子类程序的编写?
-
思考第一个问题:我提供了一个GenericServlet之后,init方法还会执行吗?
- 还会执行,会执行GenericServlet类中的init方法
-
思考第二个问题:init方法是谁调用的?
- Tomcat服务器调用的
-
思考第三个问题:init方法中的ServletConfig对象是谁创建的?是谁传过来的?
- 都是Tomcat干的
- Tomcat服务器创建了ServletConfig对象,然后调用init方法,将ServletConfig对象传给了init方法
-
思考一下Tomcat服务器伪代码:
public class Tomcat{ public static void main(String[] args){ //... //Tomcat服务器伪代码 //创建LoginServlet对象(通过反射机制,调用无参构造方法来实例化LoginServlet对象) Class clazz = Class.forName("xxxxxxxxxxx"); Object o = clazz.newInstance(); //向下转型,Object类是Servlet类的父类 Servlet servlet = (Servlet)o; //创建ServletConfig对象 //Tomcat服务器负责将ServletConfig对象实例化出来 //调用init方法 //调用service方法 } }
-
有没有一种可能,需要我在LoginServlet类中重写init方法
- 可以把Servlet类中的init方法前设为final,创建一个无参的init方法,然后在init方法中调用该类中的另一个init方法,在子类中可以重写无参的init方法
-
ServletConfig
-
是Servlet规范中的一员
-
ServletConfig是一个接口
-
谁实现了ServletConfig这个接口?
- Tomcat(服务器)
-
一个Servlet对象中有一个ServletConfig对象,1对1。
-
ServletConfig对象是什么时候创建的?
- 在创建Servlet对象的时候,同时创建ServletConfig对象
-
ServletConfig接口到底是干什么的?有什么用?
- Configuration
- ServletConfig被翻译为:Servlet对象的配置信息对象
-
ServletConfig对象中到底包装了什么信息?
-
<servlet> <servlet-name>UserServlet</servlet-name> <servlet-class>servlets.UserServlet</servlet-class> </servlet>
-
ServletConfig对象中包装的信息是:
- web.xml文件中标签的配置信息
-
-
ServletConfig接口中有哪些方法,可以获取到web.xml文件中的初始化参数配置信息
-
<!--在<servlet></servlet>中配置--> <init-param> <param-name>key</param-name> <param-value>value</param-value> </init-param>
-
getInitParameterNames() //获取所有的初始化参数的name,是一个集合 getInitParameter(java.lang.String name) //获取指定名字的value
-
可以不用获得config对象,直接用this调用上面两个方法也可以
-
ServletContext
-
ServletContext是什么?
- 是Servlet规范中的一员
-
是谁实现的?
- Tomcat
-
是谁创建的?在什么时候创建的?
-
对于一个webapp来说,ServletContext对象只有一个
-
ServletContext对象在服务器关闭的时候销毁
-
-
怎么理解?
- Servlet对象的环境对象。(Servlet对象的上下文对象)
- ServletContext对象其实对应的就是整个web.xml文件
- 放在ServletContext对象当中的数据,所有Servlet共享的。
-
常用方法?
-
public String getInitParameter (String name); //通过初始化参数的name获取value public Enumeration<String> getInitParameterNames(); //获取所有的初始化参数的name
-
-
ServletContext的public Enumeration getInitParameterNames(),获得的是以下代码的name
<context-param> <param-name>pageSize</param-name> <param-value>10</param-value> </context-param> <!--注意:以上的配置信息属于应用级的配置信息,一般一个项目中共享的配置信息会放到以上的标签中。如果配置信息只是想给某一个serevlet作为参考,那么配置到servlet标签中即可,使用ServletConfig来获取-->
-
//获取应用的根路径(非常重要),因为在java源代码当中有一些地方可能会需要应用根路径,这个方法可以动态获取应用的根路径 //在java源码当中,不要将应用的根路径写死,因为永远也不知道这个应用在最终部署的时候,起一个什么名字 public String getContextPath(); String contextPath = application.getContextPath();
-
//获取文件的绝对路径 public String getRealPath(String path); String realPath = application.getRealPath("/index.html"); "/"代表的是web的根
-
//ServletContext对象还有另一个名字:应用域(后面还有其他域,例如:请求域、会话域) //所有的用户共享一份数据,并且这个数据很少的被修改,并且这个数据量很少。放到应用域中,会大大提升效率,因为应用域相当于一个缓存,放到缓存中的数据,下次再使用的时候,不需要从数据库中再次获取,大大提升执行效率 //存(怎么向ServletContext应用域中存数据) public void setAttribute(String name,Object value); //取(怎么从ServletContext应用域中取数据) public Object getAttribute(String name); //删(怎么删除ServletContext应用域中的数据) public void removeAttribute(String name);
HTTP协议
-
什么是协议?
- 协议其实是某些人,或者某些组织提前制定好的一套规范
-
什么是HTTP协议
- 是W3C指定的一种超文本协议
- 这种协议游走在B和S之间,B向S发数据要遵循HTTP协议。S向B发数据同样需要遵循HTTP协议,这样B和S才能解耦合
- 什么是解耦合?
- B和S互相都不依赖
-
什么是超文本?
- 不是普通文本,比如流媒体:声音,视频。。。
-
B/S表示:B/S结构的系统(浏览器访问WEB服务器的系统)
-
浏览器向WEB服务器发送数据,叫做:请求(request)
-
WEB服务器向浏览器发送数据,叫做:响应(response)
-
HTTP协议包括:
-
请求协议(B->S)
- 请求行
- 请求头
- 空白行:用来分隔头和体
- 请求体
-
响应协议 (S->B)
- 状态行
- 响应头
- 空白行
- 响应体
-
-
405表示前端发送的请求方式与后端请求的处理方式不一致时发生:
- 比如:前端是POST请求,后端的处理方式按照get方式进行处理时,发生405
- 比如:前端是GET请求,后端的处理方式按照post方式进行处理时,发生405
-
404是前端错误
-
500表示服务器端的程序出现了异常,一般认为是服务器端的错误导致的
-
以4开始的一般是浏览器端的错误导致的
-
以5开始的一般是服务器端的错误导致的
-
请求行包括三部分
- 请求方式
- get(常用)
- post(常用)
- delete
- put
- head
- options
- trace
- URI
- 什么是URI?统一资源标识符。代表网络种某个资源的名字。但是通过URI是无法定位资源的。
- 什么是URL?统一资源定位符。代表网络中某个资源,同时,通过URL是可以定位到该资源的。
- URI和URL是什么关系,有什么区别?
- URL包括URI
- http://localhost:8080/servlet/index.html 这是URL
- /servlet/index.html 这是URI
- HTTP协议版本号
- 请求方式
-
怎么向服务器发送GET请求,怎么向服务器发送POST请求?
- 到目前为止,只有一种情况可以发送POST请求:使用form表单,并且form标签中的method属性值为:method =“post”。
- 其他情况一律都是get请求
- 在浏览器地址栏上直接输入URL,敲回车,属于get请求
- 在浏览器上直接点击超链接,属于get请求
-
GET请求和POST请求有什么区别?
- GET请求发送数据的时候,数据会挂在URI的后面,并且在URI后面添加一个”?“,"?"后面是数据,这样会导致发送的数据回显在浏览器的地址栏上。(get请求在请求行上发送数据)
- GET请求只能发送普通的字符串。并且发送的字符串长度有限制,不同的浏览器限制不同,这个没有明确的规定
- GET请求无法发送大数据量,是绝对安全的
- post请求发送数据的时候,在请求体当中发送。不会回显到浏览器的地址栏上,也就是说post发送的数据,在浏览器地址栏上看不到。(post在”请求体“当中发送数据)
- POST请求可以发送任何类型的数据,包括普通字符串,流媒体等信息
- POST请求可以发送大数据量,是危险的,POST是为了提交数据,所以一般情况下拦截请求的时候,大部分会拦截POST请求
- GET请求比较适合从服务器端获取数据
- POST请求比较适合向服务器端发送数据
- GET请求支持缓存
- POST请求不支持缓存
- 任何一个get请求最终的”响应结果“都会被浏览器缓存起来。在浏览器缓存当中:一个get请求的路径对应一个资源
- 大部分form表单提交,都是post方式,因为form表单中要填写大量的数据,这些数据是收集用户的信息,一般是需要传给服务器,服务器将这些数据保存/修改等。
-
GET请求和POST请求如何选择?
- 不管你是get请求还是post请求,发送的请求数据格式是完全相同的,只不过位置不同,格式都是统一的;
- name=value&name=value&name=value&name=value
- name是什么?
- 以form表单为例:form表单中input标签的name
- value是什么?
- 以form表单为例:form表单中input标签的value
模板方法设计模式
-
什么是模板方法设计模式?
-
//模板方法 //添加final之后,这个方法无法被覆盖,这样核心的算法也可以得到保护。 //模板方法定义核心的算法骨架,具体的实现步骤可以延迟到子类当中去实现。 //核心算法一方面是得到了保护,不能被改变。另外一方面就是算法得到了重复使用。 //另外代码也得到了复用,因为算法中某些步骤的代码是固定的,这一部分代码可以写到模板类当中 public final void day(){ qichaung(); xishu(); chizaocan(); dosome(); shuijiao(); } public void qichaung(){ System.out.println("起床"); } public void xishu(){ System.out.println("洗漱"); } public void chizaocan(){ System.out.println("吃早餐"); } public abstract void dosome(); public void shuijiao(){ System.out.println("睡觉"); }
HttpServlet源码分析
-
HttpServlet在哪个包下?
- javax.servlet.http.HttpServlet
-
http包下都有哪些类和接口?
- javax.servlet.http.HttpServlet(抽象类)
- javax.servlet.http.HttpServletRequest(Http请求对象)
- javax.servlet.http.HttpServletResponse(Http响应对象)
-
HttpServletRequest对象中封装了什么信息?
- HttpServletRequest中封装了请求协议的全部内容
- Tomcat服务器(WEB服务器)将”请求协议“中的数据全部解析处理,然后将这些数据全部封装到request对象当中了。
- 也就是说,我们只要面向HttpServletRequest,就可以获取请求协议中的数据。
-
HttpServletResponse是专门响应HTTP协议到浏览器的。
-
源码分析:
public class HelloServlet extends HttpServlet { //通过无参构造方法创建对象 //调用GenericSerevlet中的init方法,先调用有SerevletConfig参数的init方法,再调用无参的init方法 //再调用Service方法 //当前端发送的请求是get请求的时候,我这里重写doGet方法 //当前端发送的请求是post请求的时候,我这里重写doPost方法 }
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { HttpServletRequest request; HttpServletResponse response; try { //将ServletRequest和ServletResponse向下转型为带有Http的HttpServletRequest和HttpServletResponse request = (HttpServletRequest)req; response = (HttpServletResponse)res; } catch (ClassCastException var6) { throw new ServletException(lStrings.getString("http.non_http")); } //调用重载的service方法 this.service(request, response); } protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //获取请求方式 //这个请求方式可能是"" // req.getMethod()方法获取的是请求方式,可能是七种之一 String method = req.getMethod(); long lastModified; if (method.equals("GET")) { lastModified = this.getLastModified(req); if (lastModified == -1L) { //如果是get,则会执行doget方法 this.doGet(req, resp); } else { long ifModifiedSince; try { ifModifiedSince = req.getDateHeader("If-Modified-Since"); } catch (IllegalArgumentException var9) { ifModifiedSince = -1L; } if (ifModifiedSince < lastModified / 1000L * 1000L) { this.maybeSetLastModified(resp, lastModified); this.doGet(req, resp); } else { resp.setStatus(304); } } } else if (method.equals("HEAD")) { lastModified = this.getLastModified(req); this.maybeSetLastModified(resp, lastModified); this.doHead(req, resp); } else if (method.equals("POST")) { //如果是post,则会执行dopost方法 this.doPost(req, resp); } else if (method.equals("PUT")) { this.doPut(req, resp); } else if (method.equals("DELETE")) { this.doDelete(req, resp); } else if (method.equals("OPTIONS")) { this.doOptions(req, resp); } else if (method.equals("TRACE")) { this.doTrace(req, resp); } else { String errMsg = lStrings.getString("http.method_not_implemented"); Object[] errArgs = new Object[]{method}; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(501, errMsg); } } //假设前端发送的是get请求,后端程序员重写的是post方法,会发生什么? //报405错误,发送的请求方式不对,和服务器不一致,不是服务器需要的请求方式 //前端发什么请求,是后端说的算
-
有些人,你会看到为了避免405错误,就会重写doget和dopost,没有必要,因为该报错的时候就应该让他报错,如果都重写了,还不如重写serevice方法
最终的Servlet开发
- 编写一个Servlet类,直接继承HttpServlet
- 重写doGet方法还是doPost方法,到底重写谁,javaweb程序员说的算
- 将Servlet类配置到web.xml文件当中
- 准备前端的页面(form表单),form表单中指定请求路径,请求方法为你重写的那个方法即可
注意
- 以后我们编写Servlet类的时候,实际上是不会去直接继承GenericServlet类的,因为我们是B/S结构的系统,这种系统是基于HTTP超文本传输协议的,在Servlet规范中,提供了一个类HttpServlet,它是专门为HTTP协议准备的一个Servlet类,我们编写的Servlet类要继承HttpServlet。(HttpServlet是HTTP协议专用的)
补充
- 向上转型和向下转型
- 父类/超类在上层
- 子类/继承类/派生类在下层
- 所以向上转型就是子类转父类
- 向下转型就是父类转子类