Servlet本质

您所在的位置:网站首页 parametermap Servlet本质

Servlet本质

#Servlet本质| 来源: 网络整理| 查看: 265

目录

        --第一阶段

1.B-S架构系统的通信原理

2.模拟Servlet的本质

3.我的第一个Servlet程序

4.Intellij IDEA部署Web程序

5.Servlet对象

Servlet对象是由谁来维护的?

Servlet对象是在什么时候被创建的?

Servlet对象在用户多次访问时创建几次?

Servlet对象在什么时候销毁?

Servlet对象的生命周期?

5.适配器模式改造Servlet

        --第二阶段

6.ServletConfig接口

7.ServletContext接口

8.HttpServlet

9.HttpServletRequest

10.HttpServletResponse

        --第三阶段

11.Tomcat9及以下版本解决请求和响应乱码问题

12.设置Webapp中的欢迎页面

        --第四阶段

13.转发和重定向

 实质

 选用规则

🍎重定向优化后的单表CRUD项目

14.基于注解开发Servet程序

🍎基于注解、模板方法设计模式优化CRUD表类爆炸现象

1.B-S架构系统的通信原理

用户在Browser发送请求,服务器接收到请求后根据客户端的请求访问信息进行响应,这是建立起的是Browser和WEB Server之间的联系;服务器端的app可能会包含动态的Java小程序,这个小程序又可能从数据库拉取信息,这个时候就需要建立WEB Server和DB Server之间的联系,WEB服务器发送请求到数据库服务器并接收数据库服务器的响应,最后WEB服务器响应Browser的请求并返回数据到Browser。它们之间的通信原理如下图所示:

在上边B-S架构的通信系统中,一共有以下这么几个角色:

开发出来的不同的浏览器(Firefox,chorme,edge...)的Browser开发商开发不同服务器的WEB Server(Tomcat,JBOSS,jetty,Weblogic...)的开发商开发Web app的Java程序员开发数据库产品的的数据库厂商

 那么,既然这么多的角色参与到通信的过程中,不同的角色之间以及不同产品之间怎么保证它们之间的信息能够正常交流呢?针对不同的角色之间指定相同的规范和协议就可解决这种问题!例如,上述几个角色之间的通信协议和规范如下图:

2.模拟Servlet的本质

测试文件源程序以及编译后的程序放在gitee远程仓库,欢迎下载模拟测试https://gitee.com/whiteinjava/imitate-servlet-realize.git测试的步骤图:

 通过测试可以了解到:

既然Tomcat已经被开发出来的。那么配置文件的文件名就不能随意更改以及配置文件的位置就不能由程序员随便放置了,否则Tomcat无法得到正确文件名或者路径进行访问

Servlet规范中规定了:

规范了哪些接口和类规范了一个Web应用中应该有哪些配置文件规范了Web应用中配置文件的名字以及存放的路径规范了配置文件的内容规范了一个合格的Webapp的目录结构

Servlet程序的开发步骤

第一步:配置文件的目录结构

在Tomcat/webapps目录下新建一个项目名称,这个项目名称就是新建webapp的名称在新建的webapp目录下新建一个名称为  WEB-INF 的目录 注意:这个目录的名字是Servlet规范中规定的,必须一模一样在WEB-INF目录的统计目录中可以上传HTML,CSS,JavaScript文件在 WEB-INF 目录下新建一个名为 classes 的目录用于存放java程序编译之后的class文件 注意:这个classes目录的名称也是Servlet中规范的,必须一模一样在 WEB-INF 目录下新建一个 lib 目录 注意:lib目录的名称也是Servlet规范的,必须一模一样。这个lib目录下存放的是webapp所需的第三方jar包,例如java连接数据库所需要的驱动jar包在WEB-INF目录下新建一个web.xml配置文件 同理,这个文件是规范所规定的。该文件最好从其他的webapp中拷贝,省去手写的不必要性😂一哥Webapp应该包含的正确的目录结构如下:

第二步:编写Java程序

这个Java程序必须实现Servlet接口 注意:编写这个Java程序的源码在哪里无所谓,最后只需要将该文件编译后的class文件放到Tomcat/webapps/项目/WEB_INF/classes文件夹下就可以了。Servlet的规范文件从Tomcat/lib/获取,名称为Servlet-api.jar

浏览器发送请求到Servlet响应执行请求连接中的Servlet方法,是一个怎样的过程?

用户输入url或者点击超链接Tomcat服务器接收到请求,得到客户的请求路径在web.xml的配置文件中查找该路径对应的servlet程序Tomcat通过反射创建Servlet对象,并将其转型为Servlet对象,调用对应的Servlet方法 3.我的第一个Servlet程序

在Tomcat/webapps下新建项目myFirstServlet。打开我的项目,新建目录结构WEB-INF,进入WEB-INF新建目录结构classes和lib以及web.xml配置文件。

将servlet-api.jar包配进环境变量,编写实现Servlet接口的Java程序。

编译实现Servlet接口的HelloServlet.java,将编译后生成的目录以及class文件放入myFirstServlet/WEB-INF/classes下:

 编写web.xml配置文文件,一个servlet标签对应一个servlet-mapping:

到这里项目就配置完成了,下面我们启动项目试一下:

启动服务器 

浏览器窗口输入http://localhost:8080/myFirstServlet/firstServlet并访问,可以看到Tomcat控制台成功输出:Hello,Servlet!!!。

4.Intellij IDEA部署Web程序

详细的步骤放在gitee仓库中https://gitee.com/whiteinjava/idea-use.git

5.Servlet对象 Servlet对象是由谁来维护的?

在开发servlet程序时,我们并没有书写main方法。不难想到,servlet对象的创建和servlet以及销毁是由Tomcat维护全权负责的。我么又称Tomcat服务器为WEB容器【WEB Container】,它用来维护servlet对象的创建和销毁。

WEB容器会将创建的Servlet对象放入一个HashMap集合中,方便进行管理;同时可以知道,自己创建的Servlet对象并不会被WEB容器管理。

WEB容易有下图所示的一个HashMap集合,这个集合中包含了Servlet对象和请求路径之间的关系:

Servlet对象是在什么时候被创建的?

我们在不同的Servlet类中手动添加无参构造方法,在无参构造方法中输出指定的内容。当正常配置启动Tomcat服务器时,会发现Servlet类中的无参构造方法并没有被执行。这说明默认Tomcat启动时,默认情况下,Servlet对象是不会被实例化的,只有Tomcat服务器在接收到用户请求后才创建Servlet对象,并将其与请求路径关联放入一个map集合中:

Servlet对象在用户多次访问时创建几次?

我们给Servlet类的init方法和service方法分别添加输出内容。发现当用户第一次发送请求时,构造方法和init方法执行,此后当用户再次请求时,构造方法和init方法都不会去执行,这说明Servlet对象只在用户第一次访问时创建,创建出来后只要服务器不关闭就一直存在,并且会与对应的请求路径关联(放在了HashMap集合中),我们还可以观察到Servlet对象的init方法只会执行一次,而service方法则是根据用户的请求次数而执行:

Servlet对象在什么时候销毁?

同理,我们给Servlet对象的destroy方法添加输出语句,启动Tomcat服务器发现destroy方法并不会执行。而当我们关停Tomcat服务器时,会发现Servlet对象的destory方法执行了。这说明Servlet对象如果被创建,那么会在服务器关停时被销毁:

Servlet对象的生命周期?

通过上边的步骤能够验证:

Servlet对象在服务器启动时,默认不会被创建出来,只有当用户请求时才会被服务器创建。这时,Servlet对象的生命周期开始Servlet对象的在整个服务器运行过程中只会被创建一次,init方法也只会执行一次。在用户多次请求时,该Servlet对象会多次执行service方法当服务器关停时,所有的Servlet对象会执行destory方法,所有的Servlet对象会被销毁。这时,Servlet对象的声明周期结束 5.适配器模式改造Servlet

在上面的Servlet类的创建中,我们发现,除了service方法之外,我们很少用到其他四个Servlet接口的方法。其实仔细一想,这样向存在很大的缺陷,如果一个类实现Servlet接口并且只需要用到其中的一个方法,其他四个方法都空着,而这样的类又有很多,那么这样编写的程序是不是有点ugly?

为了解决这种缺陷,我们可以在Servlet接口和实现类之间在新加一个适配器抽象类,该抽象类实现Servlet接口,但不实现service方法。后来需要实现Servlet的Servlet对象只需要继承这个适配器类并实现service一个方法就好。这样即使有许多的Servlet对象需要实现Servlet接口,在这些对象继承适配器类后只需要覆写抽象适配器中的一个service方法就好了。这样就很好的解决了支架实现Servlet接口带来的代码冗余缺陷:

而且这个适配器类Java已经帮我们写好了。即GenericServlet。观察GenericServlet这个类的源码,可以发现GenericServlet这个类:

是个实现了Servlet接口的抽象类除了接口中的service方法外对其他的方法都进行了实现其对init方法进行覆写后又进行了重载并在覆写的方法中调用了重载的方法,重载后的五参数init方法可以提供给继承子类覆写。其中的init方法接收Servlet接口传递过来的config对象,并将其提升为成员变量,通过getter方法提供给子类使用源码: package jakarta.servlet; import java.io.IOException; import java.io.Serializable; import java.util.Enumeration; public abstract class GenericServlet implements Servlet, ServletConfig, Serializable { private static final long serialVersionUID = 1L; private transient ServletConfig config; public GenericServlet() { } public void destroy() { } public String getInitParameter(String name) { return this.getServletConfig().getInitParameter(name); } public Enumeration getInitParameterNames() { return this.getServletConfig().getInitParameterNames(); } public ServletConfig getServletConfig() { return this.config; } public ServletContext getServletContext() { return this.getServletConfig().getServletContext(); } public String getServletInfo() { return ""; } public void init(ServletConfig config) throws ServletException { this.config = config; this.init(); } public void init() throws ServletException { } public void log(String message) { ServletContext var10000 = this.getServletContext(); String var10001 = this.getServletName(); var10000.log(var10001 + ": " + message); } public void log(String message, Throwable t) { this.getServletContext().log(this.getServletName() + ": " + message, t); } public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException; public String getServletName() { return this.config.getServletName(); } } 6.ServletConfig接口

一个Servlet对象对应一个ServletConfig接口,这个接口中包含了该Servlet对象在web.xml中的配置信息。该接口的源码中有四个方法:

package jakarta.servlet; import java.util.Enumeration; public interface ServletConfig { //返回指定servlet对象在web.xml文件中的servlet-name标签中的内容 String getServletName(); //返回当前Servlet对象对应的ServletContext接口对象 ServletContext getServletContext(); //返回该servlet对象在web.xml文件中的初始化变量名称返回对应的初始化参数信息 String getInitParameter(String name); //返回该Servlet对象位于web.xml文件中的所有初始化参数的name Enumeration getInitParameterNames(); }

观察GenericServlet可以发现,其中已经为我们提供了获取初始化参数信息的方法,因此我们不需要再去得到ServletConfig对象,直接调用GenericServlet为我们提供的方法即可:

7.ServletContext接口

ServletContext对象是作为应用及对象存在的,在一个应用中,所有的Servlet对应共享一个ServletContext对象,假设这个对象是application。ServletContext中常用的方法有以下5个:

application.getInitParameterNames() 获取当前Web应用中的web.xml中的全局初始化参数的name

 application.getInitParameter(name) 获取当前web应用中web.xml配置文件中初始化参数name对应的初始化参数信息application.getContextPath()动态获取应用的根名称,例如这个项目的名称是ServletContext,则获取的结果为/ServletContextapplication.getRealPath(fileName)动态获取Web应用中文件的绝对路径。默认是从项目的根路径开始查找:application.log(content) web应用在执行过程中记录日志,日志文件存放在catalina/log/中。application.log(content,exception) 记录日志并抛出异常信息,日志文件也存放在catalina/log/中,异常信息会在该文件中记录,控制台不会输出该日志记录的异常信息。 关于日志文件:

此外,ServletContext对象还有另一个名字:应用域(后面还会有请求域,会话域...)。因为SeervletContext对象被同一个web软件中的所有servlet对象共有,因此如果所有的servlet对象都共享同一份数据,为了访问的高效性,我们可以将这份共享数据放在ServletContext对象中。这份共享数据应当是数据量小并且不会经常性改变的:因为数据量过大会占用太多的堆内存和服务器的资源,影响服务器的性能;又因为数据是共享的,如果涉及到修改操作,必然存在线程并发所带来的的安全问题,所以放在ServletContext对象中的数据一般都是只读的。我们可以通过以下三个方法向ServletContext对象中放置数据、取出数据和删除数据:

application.setAttribute(name,obj) 向ServletContext对象中添加共享数据/属性application.getAttribute(name) 获取ServletContext对象中的共享数据/属性application.removeAttribute(name) 删除ServletContext对象中的共享资源

ServletContext接口的学习源码:

ServletContext对象的练习代码https://gitee.com/whiteinjava/servlet-exercise.git

8.HttpServlet

HttpServlet这个抽象类继承了GenericServlet并且对其中的service方法进行了覆写后向子类提供了新的带有HttpServletRequest和HttpServletResponse参数的service方法供子类覆写实现功能。这个类是专门用来响应http协议的。

该类的继承关系如下:

继承HttpServlet类的Servlet所属服务器在接收到用户请求时的处理过程:

9.HttpServletRequest

这个对象中包含了用户发送请求的所有信息。Tomcat中的RequestFacade实现了这个接口。当用户发送请求时,Tomcat会将用户的请求信息封装到这个接口对象中,传给Servlet对象使用。这个接口中常用来获取用户信息的方法有:

Map(String,String[]) getParametersMap(); //得到包含前端输入信息的Map对象, //其中key值是String,value值是String[] String getParamter(String name); //根据key值获取对应value的第一个数据 String[] getParamterValues(String name); //返回以该name值为key的所有values Enumeration getParamterNames(); //返回前端的所有key值 String getRemoteAddr(); //获取请求服务器客户端IP地址 RequestDispatcher getReqestDispatcher(String path).forward(request,response); //获得path下文件的转发器,当前的Servlet跳转到指定的路径下,路径不带项目名,但是需要带 / void setCharacterEncoding(String env) throws java.io.UnsupportedEncodingException; //Tomcat9版本及之前,前端提交的是中文会出现乱码,可以通过该方法指定编码为"UTF-8"即可解决。该方法只对post请求起作用 String getContextPath(); //动态获取项目的名称 String getMethod(); //获取前端的请求方式 String getRequestURI(); //获取请求的URI,带项目名 String getServletPath(); //获取Servlet路径,不带项目名 import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Arrays; import java.util.Enumeration; import java.util.Map; import java.util.Set; public class TestHSR extends HttpServlet { //HttpServletRequest是一个抽象类 @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username = request.getParameter("username"); System.out.println(username); //根据表单中的name获得该表单中的值 System.out.println("===================="); //得到表单中所有的key-value Map parameterMap = request.getParameterMap(); Set strings = parameterMap.keySet(); for (String s : strings) { System.out.println(s + " -- " + Arrays.toString(parameterMap.get(s))); } System.out.println("===================="); //获得表单中所有的names Enumeration parameterNames = request.getParameterNames(); while (parameterNames.hasMoreElements()) { String name = parameterNames.nextElement(); System.out.println(name + " -- " + Arrays.toString(parameterMap.get(name))); } //获得表单中某个name对应的所有values所有 System.out.println("===================="); String[] hobbies = request.getParameterValues("hobby"); System.out.println("hobby" + Arrays.toString(hobbies)); } } //表单信息: index page 用户注册 用户名: 密 码: 爱 好:唱 跳 rap

请求域对象

每个request对象都有一个请求域对象,这个请求域对象中可以缓存当前请求对象的信息,其他的请求对象无法访问到该请求域对象的信息。

10.HttpServletResponse

一个HttpServletRequest对应一个HttpServletResponse。该响应对象中包含了响应信息的所有内容。

常用方法:

常用方法介绍void setContentType(String type)设置被发送到客户端的响应消息的内容类型void setCharacterEncoding(String charset)设置相应消息内容的编码格式void setRedirect(String path)为重定向设置跳转链接(待项目名)PrintWriter getWriter()得到客户端页面的输出流

11.Tomcat9及以下版本解决请求和响应乱码问题

解决请求乱码:

get请求乱码:

在servlet.xml文件中connector标签中新加 URIEncoding="UTF-8"

post请求乱码

调用request对象的setCharacterEncoding("UTF")方法解决

解决响应乱码:

调用response对象的setContextType("text/html;charset=UTF-8")方法即可解决

12.设置Webapp中的欢迎页面

Webapp中的欢迎页面是指用户在访问时不需要指定具体的文件路径或者Servlet对象就可以直接进行访问的页面。即URI只填写部署的项目名即可跳转到指定的页面,称这个页面为Webapp的欢迎页面。

欢迎页面的设置方法:

在catalina/config/web.xml中Tomcat对全局做了欢迎页面的配置,观察这个设置,可以知道当我们把一个文件的名称设置为其下的任意一个名称时,Tomact便将其配置成为我们Webapp的欢迎页面:

 当配置的欢迎页面有多个时,Tomcat服务器按照从上至下的顺序查找页面作为欢迎页,如果找不到xml文件中配置的页面,则继续向下查找页面作为webapp的欢迎页。

当然,在我们自己的webapp中,我们可以在自己的xml文件中配置项目的欢迎页面,我们称这种配置为局部配置,局部配置的优先级是高于全部配置的,因此,当我们在自己项目的xml文件中对欢迎页面做了配置,tomcat就针对我们的配置设置欢迎页面。

13.转发和重定向 实质

转发实是一次请求,而重定向是重新建立一个请求。不管转发多少次,转发都共用一个请求对象;而每次重定向,都会有一个新的请求对象。转发实质上是由WEB服务器完成的,从当前资源跳转到另一个资源,请求的对象相同;而重定向是由当前响应对象发送给浏览器路径,浏览器发送新的请求对象,请求响应路径的过程。转发和重定向对数据资源的类型没有要求。转发和重定向的流程图如下:

 选用规则 转发:当一个Servlet向请求域中绑定了数据,另一个Servlet需要当前Servlet中的数据,可以从当前Servlet向另一个Servlet转发。重定向:除了上边的转发情况,其他基本都是用重定向 🍎重定向优化后的单表CRUD项目

My first servlet demo which is optimized by redirect.https://gitee.com/whiteinjava/servlet-exercise.git

14.基于注解开发Servet程序

@WebServle        Servlet类的信息注解,属性:

name:指定Servlet的名字urlPatterns:该Servlet的映射路径,是一个字符串数组。当映射路径只有一条时,可以省略数组的 '{ }'loadOnStartup:在服务器启动时加载该Servletvalue属性:等同于urlPatterns,都是一个字符串数组。当映射路径只有一条时,可以省略value以及数组的'{ }'@WebInitParam:初始化参数,又是一个注解,类型为一个数组,属性如下:

@WebInitParam        初始化参数参数注解,属性:

name:初始化参数的名字value:初始化参数的值 import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebInitParam; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.Enumeration; @WebServlet(name = "ListInfo", urlPatterns = {"/student/list"}, loadOnStartup = 1, initParams = {@WebInitParam(name = "root", value = "**B**S**"), @WebInitParam(name = "jiayou", value = "加油")}) public class StudentListServlet extends HttpServlet { public StudentListServlet() { System.out.println("无参构造执行!"); } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); PrintWriter writer = response.getWriter(); writer.print("欢迎进入学生列表页面"); writer.print(this.getServletName() + ""); Enumeration initParameterNames = this.getInitParameterNames(); while (initParameterNames.hasMoreElements()) { String name = initParameterNames.nextElement(); String value = this.getInitParameter(name); writer.print(name + " = " + value + " "); } } }

上述的注解相当于在web.xml文件中作如下配置:

ListInfo com.shuai.javaweb.servet.StudentListServlet root **B**S** jiayou 加油 1 ListInfo /student/list 🍎基于注解、模板方法设计模式优化CRUD表类爆炸现象

对于一个小小的单表增删改查的项目,就需要如此之多的Servlet类来支持。如果工程的规模很大,那么采用原先方式用到的Servlet类的数量将会产生爆炸增长现象。现在通过模板方法设计模式以及注解的使用对类爆炸现象进行优化:

采用注解和模板方法设计模式优化后的单表CRUD操作https://gitee.com/whiteinjava/servlet-exercise.git



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3