miracle just wanna be better

Servlet

2019-08-12
miracle

servlet线程安全

容器收到请求后,会启动一个线程来进行相应的处理
默认情况下,容器只会为每一个servlet创建一个实例对象,如果同时有多个请求访问同一个servlet,则肯定有多个线程访问这个servlet,如果这些线程要修改servlet实例对象的某个属性,就可能发生线程安全问题

web.xml:

web.xml文件存储在/webContent/WEB-INF/web.xml
web.xml当前web项目的入口,即每一个web项目都要有一个web.xml文件
web.xml在tomcat服务器启动的时候解析和加载,启动完毕后,改动了web.xml不会生效
解析:用dom4j能读取web.xml的内容
加载:解析完毕后把读取到的内容以字符串的方式存储到内存中

WEB-INF目录:

在webapp/WEB-INF目录 此目录中的所有资源,都不能通过浏览器来访问,但是程序可以访问,一些重要的页面可以放在里面

tomcat启动过程:

  1. Initialzation processed in xx ms–>启动时首先解析和加载tomcat目录中的web.xml–>tomcat自检
  2. starting service Catalina–>正在开启服务
  3. starting servlet engine–>开启服务中的servlet引擎,引擎开启后,自动读取/加载/解析部署后的web项目的web.xml–>项目的web.xml检查
  4. Server startup in xxx ms–>启动完毕

servlet

sun公司做出一套servlet规范(接口),此套规范无法独立运行,必须放在web服务器中(tomcat)中运行,web服务器(tomcat),也可以称为web容器(放置多个web项目),有了这个前提可以做到通过浏览器请求一个url地址,最终执行了某个servlet中的doXXX方法,即通过浏览器就可以执行一段java代码,这就是设计servlet接口的目的
多个平台之间调用的时候,可以模拟servlet接口

提交方式:

  1. post(表单): 以数据块的方式发送数据给服务端,可以发送大量数据,在地址栏不会显示内容,安全.
  2. get(表单): 以数据字符串的方式发送给服务端,只能发送两百多字符,在地址栏上会显示数据内容,没有明确指定post提交,默认一律get提交

servlet运行的步骤:

  1. 浏览器依据url建立与web服务器(tomcat)的连接
  2. 浏览器对请求的数据打包并发送给服务器
  3. 服务器解析请求的数据,并分别把数据封装到request对象和response对象
  4. web应用服务器根据url的路径,寻找servlet并反射实例化servlet对象
  5. web应用服务器调用init方法,做初始化
  6. web应用服务器自动调用service方法,service方法根据method指定的值调用对应的doXXX方法
  7. 执行doXXX方法,方法中通过response对象,把数据响应给浏览器
  8. 浏览器获取到响应回来的数据(响应的实体内容),浏览器格式化显示数据到网页上

servlet生命周期

web.xml部分代码:

<servlet>
  	<servlet-name>UserLoginServletName</servlet-name>
  	<servlet-class>cn.tedu.servlet.UserLoginServlet</servlet-class>
  	<!-- Object obj=Class.forName("cn.tedu.servlet.UserLoginServlet").newInstance(); -->
  </servlet>
  <servlet-mapping>
  	<servlet-name>UserLoginServletName</servlet-name>
  	<url-pattern>/login</url-pattern>
  </servlet-mapping>
  • 浏览器请求某个url的时候,也就是在请求服务器中的servlet,如果web.xml中有此url,就寻找这个url的兄弟节点,如果有,就取出名字,拿这个名字去整个web.xml文件中查找是否有层级关系的servlet的名字,如果找到了就找的兄弟节点,取出servlet-class的值,并反射实例化对象,此servlet生命周期开始,用此对象自动调用init方法(如果有),init方法只执行一次,并做相关的初始化工作;
  • 然后自动调用service方法,service方法会根据method指定的值去调用对应的doXXX方法,只要服务器不停止,每次请求都会执行service方法,直到正常停止web应用服务器,会自动调用destroy方法,destroy方法用于销毁对象并释放内存空间,生命周期结束.
  1. 第一次请求servlet的时候生命周期开始
  2. 在服务器启动的时候生命周期开始
<servlet>  
	<load-on-startup>数字<load-on-startup/>  
</servlet>  

load-on-startup:

  • 此元素标记容器在启动的时候加载这个servlet,生命周期开始
  • 它的值必须是一个整数,表示servlet加载的顺序,值大于等于0的时候表示容器在启动的时候加载,值小于0或没有配置此项表示容器在该servlet被第一次请求的时候加载,整数值越小,优先级越高,servlet就越是优先加载,当值相同的时候,容器就按照书写的顺序加载

init()

每一次生命周期开始都调用此方法,一般做初始化工作
有两种初始化方式:

  1. 局部初始化
 <servlet>
    <servlet-name>SysInitServletName</servlet-name>
    <servlet-class>cn.tedu.servlet.SysInitServlet</servlet-class>
    <init-param>
      <param-name>ipRange</param-name>
      <param-value>10.8.38.1-10.8.38.100</param-value>
    </init-param>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>
    <load-on-startup>0</load-on-startup>
  </servlet>

初始化的数据仅在指定的servlet中使用

  1. 全局初始化
    <web-app>  
	  	<context-param>
   			<param-name>globalIpRange</param-name>
    		<param-value>10.8.38.1-10.8.38.100</param-value>
  		</context-param>
  		<context-param>
   			<param-name>globalEncoding</param-name>
   			<param-value>UTF-8</param-value>
 		 </context-param> 
    <web-app/>

所有的servlet,filter,listener都可以读取初始化数据
初始化方法有两个

  1. init(); 无参数
  2. init(ServeletConfig);有参数
    如果这两个方法同时存在,执行的是带参数的方法

service()

每次请求调用此方法,由此方法决定调用某个doXXX方法

doXXX()

只做三件事

  1. 获取前端提交的数据
  2. 基于前端提交的数据调用具体业务
  3. 根据业务的返回结果做响应给客户端

destroy()

方法中放置释放资源代码,对象=null;
在web容器正常停止时调用

ServletContext(servlet上下文对象)

当tomcat启动的时候,先自检自己 自检完毕后starting service开启服务 staring servlet engine 开启servlet引擎 开始读取web容器中的web项目,逐一解析加载每一个web项目的web.xml web容器(tomcat容器/web应用服务器)会为每一个web项目分配一个ServletContext,每一个web项目的ServletContext只有一个 tomcat停止服务,web项目的ServletContext就销毁了 ServletContext是一个对象,存储在tomcat容器中,是服务端对象,ServletContext对象中维护了多个map集合,可以把数据存储给ServletContext对象中,供所有的Servlet,filter,listener访问

获取ServletContext对象的方式

  1. 在servlet类中的任意方法中写this.getServletContext();
  2. 用ServletConfig对象.getServletContext();
  3. 用request.getSession().getServletContext();
  4. 用过滤器中的带参数的init方法,FilterConfig对象.getServletContext();

ServletConfig(servlet的配置对象)

在Servlet中的有参数的init()方法,参数就是ServletConfig
ServletConfig对象.getInitParameter(“key”);//获取局部参数
ServletConfig对象.getServletContext().getInitParameter();获取全局初始化数据
ServletConfig对象中存储的是当前的servlet的信息,servletConfig对象只对应一个servlet ServletConfig对象中能存储什么数据,ServletConfig.getXXX()

http协议(HyperText Transfer Protocal超文本传输协议)

有w3c指定的一种应用层面的协议,用来定义浏览器与web服务器之间如何通信以及通信的数据格式
当请求服务器的资源时,会根据http协议发送一些数据(消息头,请求头,发送的数据)
服务器接收到后,会拆开数据,服务器根据拆开的数据做服务器的处理,处理完后给响应给浏览器(响应头和响应体内容) 消息头,请求头,发送的数据,响应头,响应的内容都要遵守http协议,否则http协议无法识别这些信息
每一种浏览器都可以查看http协议的信息,也可以借助第三方工具postman等

请求的数据包组成:

  • 请求行:请求的方式+请求的资源路径+协议版本
  • 消息头:消息头中包含键值对,一般由w3c定义,通信双方通过消息头内容传递信息
  • 实体内容:只有请求的方式是post,实体内容才会有数据 响应的数据包组成:
  • 状态行:协议的类型+版本+状态码+状态码描述
  • 消息头:web服务器返回的一些消息头给浏览器
  • 实体内容:服务器返回的结果

servlet如何处理http协议
当web容易受到一个http请求时,通信数据由web容器负责封装和提供这些信息,这些信息分为两个对象
与请求数据对应的是HTTPServletRequest接口类型的对象
与响应数据对应的是HttpServletResponse接口类型的对象

HTTPServletRequest对象
当客户端通过http协议访问服务器的时候,请求中所有的消息信息都封装到这个对象中,通过此对象,getXXX方法获取协议中的消息信息 比如:
getParameter(“key”);
getRemoteAddr();

HTTPServletResponse对象 提供给客户端响应数据,封装了http响应的数据,通过这个对象可以设置状态行消息头和实体内容
比如:
响应的内容为png图像
response.setContentType(“image/png”);
设置响应的内容是utf-8格式:
response.setCharacterEncoding(“UTF-8”);
设置响应到浏览器的页面:
response.sendRedirect(“login.jsp”);

转发和重定向

重定向

response.sendRedirect(“可以是jsp,也可以是servlet的url”);
相当于重新请求服务器资源(jsp,servlet)
request对象和response对象都是servlet容器新创建的对象
地址栏会显示重定向后的连接(url)
服务器象浏览器发送一个302状态码以及一个location的消息头
该消息头的值是一个地址,称之为重定向地址,浏览器收到后会立即向location中标记的地址发出请求

转发

request.getRequestDispatcher(可以是jsp,也可以是servlet).forward(request,response); 转发实际上就是请求指定jsp或servlet,也会跳转到指定目的地,相当于请求服务器资源,就是在服务器中请求服务器中的另一份资源,就是在服务器内部跳转,request对象和response对象都是原来的对象,不会新创建,如果request对象绑定了数据,转发就是把数据转发的下一个目标中,地址栏的内容不是目的地址,原来地址栏是什么,就是什么,即地址的地址不变,不带数据的转发,没有任何意义,用重定向即可
转发的步骤:

  1. 给request绑定数据 request.setAttribute(“key”,value);
  2. 把绑定了数据的request对象转到以一个目标 request.getRequestDispatcher(目标url).forword(request,response);

web开发中的乱码处理

  1. 所有的乱码都可以用下面的方式处理 汉字=new String(乱码的字节,”UTF-8”);
    比如:
    String 乱码=request.getParameter(“userName”);
    汉字=new String(乱码.getBytes(“ISO8859-1”),”UTF-8”);
    ISO8859-1也可以写成ISO-8859-1
  2. 下面的方法虽然代码简洁.但只有post提交的时候才生效 如果是get方式提交,下面的的语句不好使,此时只能方式一
    request.setCharacterEncoding(“UTF-8”);
    注意:第一种和第二种不要同时使用

tomcat版本问题

tomcat8版本及以后版本只需要使用方案二即可 方案一废弃不用,因为tomcat已经给实现处理了

  • tomcat7版本及以前版本无论get提交还是post提交,request中都是ISO8859-1
  • tomcat8版本及以后版本
    get提交request中还是ISO8859-1,但getParameter取出数据就变成utf-8;
    post提交request中还是ISO8859-1,但取出的还是ISO8859-1

jstl(java/jsp的标准标签库)

是sun公司定义的一套标准,成为javaee5.0的核心

如何使用jstl

在官网下载jstl开发包,把jstl.jar拷贝到项目中,然后构建路径 在需要的jsp页面上写taglib指令 <%@ taglib prefix=”c” url=”jstl的命名空间”%>

jstl标签分类:

核心标签库 简称 c 常用标签

  • <%@ taglib prefix=”c” uri=”http://java.sun.com/jsp/jstl/core” %>
  • 格式化标签库 简称 fmt
  • <%@ taglib prefix=”fmt” uri=”http://java.sun.com/jsp/jstl/fmt” %>
  • sql标签库 简称sql
  • <%@ taglib prefix=”sql” uri=”http://java.sun.com/jsp/jstl/sql” %>

大部分情况下使用核心标签
分支标签:

   <c:if test="" var="" scope="">
   </c:if>

当test条件为true时,执行标签体的内容
var:指定一个对象名称,此对象名称由用户定义
scope:指定绑定数据的范围
page,request,session,application

   <c:choose>
	<c:when test="">   </c:when>
	...
	<c:when test="">   </c:when>
	<c:otherwise ></c:otherwise >
   </c:choose>

  <c:forEach var="" items="" varStatus="">
  </c:forEache>

| 属性 | 描述 | 是否必要 | 默认值 | | :—— |:— | :— | :— | | items | 要被循环的信息 | Four | 无 | | begin | 开始的元素(0=第一个元素,1=第二个元素) | 否 | 0 | | end | 最后一个元素(0=第一个元素,1=第二个元素) | 否 | Last element | | step | 每一次迭代的步长 | 否 | 1 | | var | 代表当前条目的变量名称 | 否 | 无 | | varStatus | 代表循环状态的变量名称 | 否 | 无 |

开发servlet常见的错误

  • 404是客户端请求服务端的资源不存在
  • 405错误web服务器无法定位doXXX方法
  • 500错误:全都是服务端的java代码错误(间接或直接)
  • 200是请求和响应成功
  • 302是重定向的时候响应的状态码

base64

  • java端: java.util.Base64
//加密: 
Base64.getEncoder().encodeToString(upwd.getBytes()));  
//解密  
Base64.getDecoder().decode(加密的数据);  
  • js客户端: 加密:
    window.btoa(“test”);//把test加密
    解密:
    window.atob(“加密的数据”);

过滤器Filter

过滤器就是一个servlet的特例
是servlet2.3规范中定义的中小型,可插入的web组件,用来拦截servlet
容器的请求和响应过程以便查看,提取或以某种方式操作正在客户端和服务器之间交换的数据

实现过滤器的步骤:

  1. 创建一个java类实现javax.servlet.Filter接口
  2. 在实现类中的doFilter方法添加过滤器的过滤算法
  3. 在web.xml中添加
<filter></filter><filter-mapping></filter-mapping>

在filter和servlet中url-pattern可以写

  • /*

    匹配所有请求

  • *.jsp

    匹配所有的jsp

  • /user/*

    匹配url中带有/user/为开头的所有请求

  • *.do

    匹配所有以.do为结尾的请求

  • /login

    匹配只有login的请求

生命周期

  • 当服务启动时,会加载web.xml中的内容,如果文件中有项,那么就读取节点中的内容,然后反射实例化对象,生命周期开始,然后通过过滤对象自动调用init方法,做当前过滤的初始化,init方法只执行一次,如果中是/*,代表所有的请求都要被此过滤器过滤/拦截,只要请求任何资源都要经过过滤器的doFilter方法,在此方法中写过滤条件,可以根据过滤条件来决定是否filterChain.doFilter(),如果调用此语句,会寻找下一个过滤器,若果进入下一个过滤器的doFilter方法,如果没有就直接访问要请求的资源,请求的资源响应完毕后,会按照过滤器的调用顺序逐层返回,最后所有过滤器的doFilter方法执行完毕,如果正常停止服务器,会执行destroy方法,然后生命周期结束

局部变量

FilterConfig:是过滤器的配置对象,存储的是局部的数据,FilterConfig对象
只在过滤器的init(FilterConfig filterConfig)方法中出现

<filter>
  <init-param>
  		<param-name>encoding</param-name>
  		<param-value>utf-8</param-value>
  	</init-param>
</filter>

特点

过滤器不能太多,过滤原则也不能太复杂
降低系统性能,如果是/*,多有请求都要经过过滤器
实现系统功能的热插拔,去掉,就是取消功能 添加,就是添加功能 只要在原有的业务前添加功能,调用业务之后添加功能,就可以用 过滤器,拦截器,aop.

监听器

servlet规范中定义一种特殊的组件,用来监听servlet容器产生的事件,并作出相应处理
容器产生的事件有两大类:

生命周期相关的事件:

监听器监听,如果生命周期开始就触发做相应的处理
如果生命周期结束就触发做相应的处理

  • ServletRequestListerner request对象的监听器接口
  • requestCreated() 当请求request对象创建了就触发调用此方法
  • requestDestroyed() 当请求request对象销毁了就触发调用此方法
  • HttpSessionListener session对象的监听器接口
  • sessionCreated() 当服务器第一次创建session对象就触发调用此方法
  • sessionDestroyed() 当服务器销毁session对象就触发调用此方法
  • ServletContextListener servletContext对象的监听器接口
  • contextInitialized() 当服务器给某一个应用创建ServletContext就触发调用此方法
  • contextDestroyed() 当服务器中某一个应用销毁ServletContext就触发调用此方法

    绑定数据相关的事件

    监听器监听:
    如果某个对象中添加数据就触发做相应的处理
    如果某个对象中数据被移除就触发做相应的处理
    如果某个对象中数据被替换了就触发做相应的处理
    当调用 setAttribute()方法时触发
    当调用removeAttriute()方法时触发
    当调用replaceAttribute()方法时触发

  • ServletRequestAttributeListener 请求对象中的数据变化监听器接口
  • attributeAdded() request对象中数据被添加则触发此方法
  • attributeRemoved() request对象中数据被移除则触发此方法
  • attributeReplace() request对象中数据被替换则触发此方法
  • HttpSessionAttributeListener session对象中的数据变化监听器接口
  • attributeAdded() session对象中数据被添加则触发此方法
  • attributeRemoved() session对象中数据被移除则触发此方法
  • attributeReplace() session对象中数据被替换则触发此方法
  • ServletContextAttributeListener servlet上下文数据变化监听器接口
  • attributeAdded() servletContext对象中数据被添加则触发此方法
  • attributeRemoved() servletContext对象中数据被移除则触发此方法
  • attributeReplace() servletContext对象中数据被替换则触发此方法

实现监听器的步骤:

  • 1.新建一个java类实现上面的六个中某一个接口
  • 2.编写对应方法的监听逻辑
  • 3.在web.xml中,注册监听器

监听器的生命周期:

在tomcat启动的时候就生命周期开始
如果有对应的条件触发了,就执行对应监听处理方法
在tomcat正常停止的时候,监听器对象消失

在tomcat中启动,监听器,过滤器,servlet的生命周期开始的顺序

最先是监听器生命周期开始
其次是过滤器生命周期开始
最后servlet生命周期开始
带有load-on-startup,启动时开始
不带load-on-startup,但需要带servlet-mapping则启动后,第一次请求时开始

EL表达式

使用EL表达式访问bean对象的属性数据,可以使用如下两种方式

  1. ${对象名.方法名去掉get,然后把剩下的字符串的第一个字母小写}
  2. ${对象名[“方法名去掉get,然后把剩下的字符串的第一个字母小写”]} 比如:
    ${user.name}
    ${user[“name”]}
    就是替换了<%=user.getName()%>

通过el表达式可以从指定的作用域中查找数据

查找数据的原则:

  • 先从当前页的对象(pageContext)中寻找是否有此数据,如果有就取出
  • 如果没有就从请求对象(request)中寻找是否有此数据,如果有就取出
  • 若果没有就从会话对象(session)中寻找是否有此数据,如果有就取出
  • 如果没有就从servlet上下文对象(ServletContext/application)中寻找是否有此数据,如果有就取出
  • 如果没有就真的没有了,不会在页面上显示异常,也不显示null,就什么都不显示

取出数据的方式:

  • 先从当前页的对象(pageContext)中寻找是否有此数据,如果有就取出${pageScope.名称}
  • 如果没有就从请求对象(request)中寻找是否有此数据,如果有就取出${requestScope.名称}
  • 若果没有就从会话对象(session)中寻找是否有此数据,如果有就取出${sessionScope.名称}
  • 如果没有就从servlet上下文对象(ServletContext/application)中寻找是否有此数据,如果有就取出${applicationScope.名称} 如果没有就真的没有了,不会在页面上显示异常,也不显示null,就什么都不显示

注意,xxxScope可以不写,那么就按照上面的原则,逐层查找,找到就取出
没有找到就什么都不显示,建议写xxxScope,精确查找

用EL表达式输出简单的运算结果

  • 算数运算: + - * / % 注意:+符号,只能做求和,不能做字符串连接
    比如:
    ${1+1} //2
  • 逻辑运算: && || ! and or not 比如:
    ${true and false} //false
  • 关系运算:
  •      >=           <         <=          ==        !=
    
  • gt ge lt le eq ne
  • great than great equal less than less equal equals not equals 比如:
  • ${1 gt 2} //false
  • ${1 > 2} //false
    empty:用户判断一个字符串是否为空,或者一个集合是否为空
    以下情况结果都为true:
  • 空字符串
  • 空的集合
  • 值为null
  • 找不对应的值 上面的四种情况都可以用empty来判断
    比如:
  • ${empty null} //true
  • ${empty “”} //true
  • ${empty page.data}

用EL表达式获取请求的参数

上文是用表单或问好传值的方式传送数据
下文假设取userName的值${param.userName} ,底层就是request.getParameter(“userName”);
下文假设取多个数据
${paramValues.hobby},底层就是request.getParameterValues(“hobby”);

注意: 上文用request.setAttribute(“key”,value)
下文用Object obj=request.getAttribute(key);
下文用${requestScope.key}

注意: 上文中用表单提交或问号传值,request对象不没有丢失,是用转发到
下一个页面
在下一个页面(下文)中 ${param.userName}取单值

  • ${paramValues.hobby}取多值
  • ${paramValues.hobby[0]}
  • ${paramValues.hobby[1]}

上一篇 JDBC

Comments

Content