Servlet详解

1、什么是Servlet?

Servlet其实就是一个遵循Servlet开发的java类。Servlet是由服务器调用的运行在服务器端

 

2、为什么要用到Servlet?

我们编写java程序想要在网上实现 聊天、发帖、这样一些的交互功能,普通的java技术是非常难完成的。sun公司就提供了Servlet这种技术供我们使用。

 

3、JAVAWEB目录结构

Servlet详解

 

以上图说明:

  • bbs目录代表一个web应用
  • bbs目录下的html,jsp文件可以直接被浏览器访问
  • WEB-INF目录下的资源是不能直接被浏览器访问的
  • web.xml文件是web程序的主要配置文件
  • 所有的classes文件都放在classes目录下
  • jar文件放在lib目录下

 

4、编写Servlet程序

创建一个自定义类,实现Servlet接口

Servlet详解

有5个方法需要重写,有init【初始化】,destroy【销毁】,service【服务】,ServletConfig【Servlet配置】,getServletInfo【Servlet信息】。

配置xml文件,光写了Servlet是不行的,Tomcat还要知道浏览器怎么访问这个Servlet。

Servlet详解

访问自己写的Servlet程序。

 

5、Servlet生命周期

  1. 加载Servlet。当Tomcat第一次访问Servlet的时候,Tomcat会负责创建Servlet的实例
  2. 初始化。当Servlet被实例化后,Tomcat会调用init()方法初始化这个对象
  3. 处理服务。当浏览器访问Servlet的时候,Servlet 会调用service()方法处理请求
  4. 销毁。当Tomcat关闭时或者检测到Servlet要从Tomcat删除的时候会自动调用destroy()方法,让该实例释放掉所占的资源。一个Servlet如果长时间不被使用的话,也会被Tomcat自动销毁
  5. 卸载。当Servlet调用完destroy()方法后,等待垃圾回收。如果有需要再次使用这个Servlet,会重新调用init()方法进行初始化操作

简单总结:

  1. 只要访问Servlet,service()就会被调用。
  2. init()只有第一次访问Servlet的时候才会被调用。
  3. destroy()只有在Tomcat关闭的时候才会被调用。

 

6、继承HttpServlet编写Servlet程序

在上面我们实现Servlet接口,要实现5个方法。这样太麻烦了!

HttpServlet类已经实现了Servlet接口的所有方法,编写Servlet时,只需要继承HttpServlet,重写你需要的方法即可,并且它在原有Servlet接口上添加了一些与HTTP协议处理方法,它比Servlet接口的功能更为强大

  • 一般我们开发的时候,都是重写doGet()和doPost()方法的。对于idea而言,创建Servlet的时候已经帮你重写好了

Servlet详解

 

7、Servlet细节

Servlet的调用图:Servlet的生命周期

Servlet详解

一个已经注册的Servlet可以被多次映射

同一个Servlet可以被映射到多个URL上。

<servlet>
  <servlet-name>Demo1</servlet-name>
  <servlet-class>zhongfucheng.web.Demo1</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>Demo1</servlet-name>
  <url-pattern>/Demo1</url-pattern>
</servlet-mapping>
<servlet-mapping>
  <servlet-name>Demo1</servlet-name>
  <url-pattern>/ouzicheng</url-pattern>
</servlet-mapping>

无论我访问的是http://localhost:8080/Demo1还是http://localhost:8080/ouzicheng。我访问的都是Demo1。

Servlet映射的URL可以使用通配符

通配符有两种格式:

  • *.扩展名
  • 正斜杠(/)开头并以“/*”结尾。

如果.扩展名和正斜杠(/)开头并以“/”结尾两种通配符同时出现,匹配的是哪一个呢?

  • 看谁的匹配度高,谁就被选择
  • *.扩展名的优先级最低

Servlet映射的URL可以使用通配符和Servlet可以被映射到多个URL上的作用:

隐藏网站是用什么编程语言写的【.php,.net,.asp实际*问的都是同一个资源】

用特定的后缀声明版权【公司缩写】

<servlet>
  <servlet-name>Demo1</servlet-name>
  <servlet-class>zhongfucheng.web.Demo1</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>Demo1</servlet-name>
  <url-pattern>*.jsp</url-pattern>
</servlet-mapping>
<servlet-mapping>
  <servlet-name>Demo1</servlet-name>
  <url-pattern>*.net</url-pattern>
</servlet-mapping>
<servlet-mapping>
  <servlet-name>Demo1</servlet-name>
  <url-pattern>*.asp</url-pattern>
</servlet-mapping>
<servlet-mapping>
  <servlet-name>Demo1</servlet-name>
  <url-pattern>*.php</url-pattern>
</servlet-mapping>

 

8、Servlet是单例的

为什么Servlet是单例的

浏览器多次对Servlet的请求,一般情况下,服务器只创建一个Servlet对象,也就是说,Servlet对象一旦创建了,就会驻留在内存中,为后续的请求做服务,直到服务器关闭

每次访问请求对象和响应对象都是新的

对于每次访问请求,Servlet引擎都会创建一个新的HttpServletRequest请求对象和一个新的HttpServletResponse响应对象,然后将这两个对象作为参数传递给它调用的Servlet的service()方法service方法再根据请求方式分别调用doXXX方法

线程安全问题

当多个用户访问Servlet的时候,服务器会为每个用户创建一个线程当多个用户并发访问Servlet共享资源的时候就会出现线程安全问题

原则:

  • 如果一个变量需要多个用户共享,则应当在访问该变量的时候,加同步机制synchronized (对象){}
  • 如果一个变量不需要共享,则直接在 doGet() 或者 doPost()定义.这样不会存在线程安全问题

 

9、 load-on-startup

如果在<servlet>元素中配置了一个<load-on-startup>元素,那么WEB应用程序在启动时,就会装载并创建Servlet的实例对象、以及调用Servlet实例对象的init()方法

Servlet详解

作用:

为web应用写一个InitServlet,这个servlet配置为启动时装载,为整个web应用创建必要的数据库表和数据

完成一些定时的任务【定时写日志,定时备份数据】

 

10、在web访问任何资源都是在访问Servlet

当你启动Tomcat,你在网址上输入http://localhost:8080。为什么会出现Tomcat小猫的页面?

这是由缺省Servlet为你服务的

我们先看一下web.xml文件中的配置,web.xml文件配置了一个缺省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>

<servlet-mapping>
  <servlet-name>default</servlet-name>
  <url-pattern>/</url-pattern>
</servlet-mapping>

什么叫做缺省Servlet?凡是在web.xml文件中找不到匹配的元素的URL,它们的访问请求都将交给缺省Servlet处理,也就是说,缺省Servlet用于处理所有其他Servlet都不处理的访问请求

既然我说了在web访问任何资源都是在访问Servlet,那么我访问静态资源【本地图片,本地HTML文件】也是在访问这个缺省Servlet【DefaultServlet】

总结:无论在web中访问什么资源【包括JSP】,都是在访问Servlet。没有手工配置缺省Servlet的时候,你访问静态图片,静态网页,缺省Servlet会在你web站点中寻找该图片或网页,如果有就返回给浏览器,没有就报404错误

 

11、ServletConfig对象

ServletConfig对象有什么用?

通过此对象可以读取web.xml中配置的初始化参数。

现在问题来了,为什么我们要把参数信息放到web.xml文件中呢?我们可以直接在程序中都可以定义参数信息,搞到web.xml文件中又有什么好处呢

好处就是:能够让你的程序更加灵活【更换需求,更改配置文件web.xml即可,程序代码不用改】

获取web.xml文件配置的参数信息

  • 为Demo1这个Servlet配置一个参数,参数名是name,值是123

<servlet>
  <servlet-name>Demo1</servlet-name>
  <servlet-class>
123.web.Demo1</servlet-class>
  <init-param>
    <param-name>name</param-name>
    <param-value>
123</param-value>
  </init-param>
</servlet>
<servlet-mapping>
  <servlet-name>Demo1</servlet-name>
  <url-pattern>/Demo1</url-pattern>
</servlet-mapping>

在Servlet中获取ServletConfig对象,通过ServletConfig对象获取在web.xml文件配置的参数

Servlet详解

 

12、ServletContext对象

什么是ServletContext对象?

当Tomcat启动的时候,就会创建一个ServletContext对象。它代表着当前web站点

ServletContext有什么用?

  1. ServletContext既然代表着当前web站点,那么所有Servlet都共享着一个ServletContext对象,所以Servlet之间可以通过ServletContext实现通讯
  2. ServletConfig获取的是配置的是单个Servlet的参数信息,ServletContext可以获取的是配置整个web站点的参数信息
  3. 利用ServletContext读取web站点的资源文件
  4. 实现Servlet的转发【用ServletContext转发不多,主要用request转发】

Servlet之间实现通讯

ServletContext对象可以被称之为域对象

到这里可能有一个疑问,域对象是什么呢?其实域对象可以简单理解成一个容器【类似于Map集合】

实现Servlet之间通讯就要用到ServletContext的setAttribute(String name,Object obj)方法
第一个参数是关键字,第二个参数是你要存储的对象

//获取到ServletContext对象
ServletContext servletContext = this.getServletContext();
String value = "123";
//MyName作为关键字,value作为值存进   域对象【类型于Map集合】
servletContext.setAttribute("MyName", value);

//获取ServletContext对象
ServletContext servletContext = this.getServletContext();
//通过关键字获取存储在域对象的值
String value = (String) servletContext.getAttribute("MyName");
System.out.println(value);

获取web站点配置的信息

如果我想要让所有的Servlet都能够获取到连接数据库的信息,不可能在web.xml文件中每个Servlet中都配置一下,这样代码量太大了!并且会显得非常啰嗦冗余。

  • web.xml文件支持对整个站点进行配置参数信息所有Servlet都可以取到该参数信息

<context-param>
  <param-name>name</param-name>
  <param-value>123</param-value>
</context-param>

//获取到ServletContext对象
ServletContext servletContext = this.getServletContext();
//通过名称获取值
String value = servletContext.getInitParameter("name");
System.out.println(value);

 

13、Request对象

什么是HttpServletRequest

HttpServletRequest对象代表客户端的请求,当客户端通过HTTP协议访问服务器时,HTTP请求头中的所有信息都封装在这个对象中,开发人员通过这个对象的方法,可以获得客户这些信息。

简单来说,要得到浏览器信息,就找HttpServletRequest对象

HttpServletRequest常用方法

 获得客户机【浏览器】信息

  • getRequestURL方法返回客户端发出请求时的完整URL。
  • getRequestURI方法返回请求行中的资源名部分。
  • getQueryString 方法返回请求行中的参数部分。
  • getPathInfo方法返回请求URL中的额外路径信息。额外路径信息是请求URL中的位于Servlet的路径之后和查询参数之前的内容,它以“/”开头。
  • getRemoteAddr方法返回发出请求的客户机的IP地址
  • getRemoteHost方法返回发出请求的客户机的完整主机名
  • getRemotePort方法返回客户机所使用的网络端口号
  • getLocalAddr方法返回WEB服务器的IP地址。
  • getLocalName方法返回WEB服务器的主机名

获得客户机请求头

  • getHeader方法
  • getHeaders方法 
  • getHeaderNames方法 

获得客户机请求参数(客户端提交的数据)

  • getParameter方法
  • getParameterValues(String name)方法
  • getParameterNames方法 
  • getParameterMap方法

HttpServletRequest应用

1、防盗链

什么是防盗链呢?比如:我现在有海贼王最新的资源,想要看海贼王的要在我的网页上看。现在别的网站的人看到我有海贼王的资源,想要把我的资源粘贴在他自己的网站上。这样我独家的资源就被一个CTRL+C和CTRL+V抢走了?而反盗链就是不能被他们CRTL+C和CRTL+V。

想要看我的资源,就必须经过我的首页点进去看

想要实现这样的效果,就要获取Referer这个消息头判断Referer是不是从我的首页来的。如果不是从我的首页来的,跳转回我的首页

//获取到网页是从哪里来的
String referer = request.getHeader("Referer");

//如果不是从我的首页来或者从地址栏直接访问的,
if ( referer == null || !referer.contains("localhost:8080/123/index.jsp") ) {

  //回到首页去
  response.sendRedirect("/123/index.jsp");
  return;
}

//能执行下面的语句,说明是从我的首页点击进来的,那没问题,照常显示
response.setContentType("text/html;charset=UTF-8");
response.getWriter().write("路飞做了XXXXxxxxxxxxxxxxxxxx");

2、表单提交数据【通过post方式提交数据】
在Servlet中获取到提交的数据

3、超链接方式提交数据

常见的get方式提交数据有使用超链接,sendRedirect()

sendRedirect("servlet的地址?参数名="+参数值 &"参数名="+参数值);

<a href="/123/Servlet?username=xxx">使用超链接将数据带给浏览器</a>

接收数据

//接收以username为参数名带过来的值
String username = request.getParameter("username");
System.out.println(username);

4、解决中文乱码问题

request.setCharacterEncoding("UTF-8");

Tomcat服务器默认编码是ISO 8859-1,而浏览器使用的是UTF-8编码。浏览器的中文数据提交给服务器,Tomcat以ISO 8859-1编码对中文编码,当我在Servlet读取数据的时候,拿到的当然是乱码。而我设置request的编码为UTF-8,乱码就解决了。

注意:上面的设置只对post请求有效,为啥呢?

post方法是怎么进行参数传递的。当我们点击提交按钮的时候,数据封装进了Form Data中http请求中把实体主体带过去了【传输的数据称之为实体主体】,既然request对象封装了http请求,所以request对象可以解析到发送过来的数据,于是只要把编码设置成UTF-8就可以解决乱码问题了

get方式不同,它的数据是从消息行带过去的,没有封装到request对象里面,所以使用request设置编码是无效的。

要解决get方式乱码问题也不难,我们既然知道Tomcat默认的编码是ISO 8859-1,那么get方式由消息体带过去给浏览器的时候肯定是用ISO 8859-1编码了

//此时得到的数据已经是被ISO 8859-1编码后的字符串了,这个是乱码
String name = request.getParameter("username");

//乱码通过反向查ISO 8859-1得到原始的数据
byte[] bytes = name.getBytes("ISO8859-1");

//通过原始的数据,设置正确的码表,构建字符串
String value = new String(bytes, "UTF-8");

总结:

  • post方式直接改request对象的编码
  • get方式需要手工转换编码
  • get方式也可以修改Tomcat服务器的编码,不推荐,因为会太依赖服务器了!
  • 提交数据能用post就用post

5、实现转发

一般的原则:可以使用request就尽可能使用request。因为ServletContext代表着整个web应用,使用ServletContext会消耗大量的资源,而request对象会随着请求的结束而结束,资源会被回收使用request域进行Servlet之间的通讯在开发中是非常频繁的

6、转发和重定向的区别

实际发生位置不同,地址栏不同

  • 转发是发生在服务器的
    • 转发是由服务器进行跳转的,细心的朋友会发现,在转发的时候,浏览器的地址栏是没有发生变化的,在我访问Servlet111的时候,即使跳转到了Servlet222的页面,浏览器的地址还是Servlet111的。也就是说浏览器是不知道该跳转的动作,转发是对浏览器透明的。通过上面的转发时序图我们也可以发现,实现转发只是一次的http请求一次转发中request和response对象都是同一个。这也解释了,为什么可以使用request作为域对象进行Servlet之间的通讯。
  • 重定向是发生在浏览器的
    • 重定向是由浏览器进行跳转的,进行重定向跳转的时候,浏览器的地址会发生变化的。曾经介绍过:实现重定向的原理是由response的状态码和Location头组合而实现的。这是由浏览器进行的页面跳转实现重定向会发出两个http请求request域对象是无效的,因为它不是同一个request对象

用法不同

很多人都搞不清楚转发和重定向的时候,资源地址究竟怎么写。有的时候要把应用名写上,有的时候不用把应用名写上。很容易把人搞晕。记住一个原则:给服务器用的直接从资源名开始写,给浏览器用的要把应用名写上

  • request.getRequestDispatcher("/资源名 URI").forward(request,response)
    • 转发时"/"代表的是本应用程序的根目录【zhongfucheng】
  • response.send("/web应用/资源名 URI");
    • 重定向时"/"代表的是webapps目录

能够去往的URL的范围不一样

  • 转发是服务器跳转只能去往当前web应用的资源
  • 重定向是服务器跳转,可以去往任何的资源

传递数据的类型不同

  • 转发的request对象可以传递各种类型的数据,包括对象
  • 重定向只能传递字符串

跳转的时间不同

  • 转发时:执行到跳转语句时就会立刻跳转
  • 重定向:整个页面执行完之后才执行跳转

转发和重定向使用哪一个?

根据上面说明了转发和重定向的区别也可以很容易概括出来。转发是带着转发前的请求的参数的。重定向是新的请求

典型的应用场景:

  • 转发: 访问 Servlet 处理业务逻辑,然后 forward 到 jsp 显示处理结果,浏览器里 URL 不变
  • 重定向: 提交表单,处理成功后 redirect 到另一个 jsp,防止表单重复提交,浏览器里 URL 变了

RequestDispatcher再说明

RequestDispatcher对象调用forward()可以实现转发上面已经说过了。RequestDispatcher还有另外一个方法include(),该方法可以实现包含,有什么用呢?

我们在写网页的时候,一般网页的头部和尾部是不需要改变的。如果我们多个地方使用Servlet输出网头和网尾的话,需要把代码重新写一遍。而使用RequestDispatcher的include()方法就可以实现包含网头和网尾的效果了