Java Web开发笔记
问题:
读取资源文件问题
servletContext.getRealPath()
servletContext.getResouce().getPath()
setvletContext.getResourceAsStream()
1、Servlet入门(编写Hello World程序)
注意:servlet api文档http://docs.oracle.com/cd/E17802_01/products/products/servlet/2.5/docs/servlet-2_5-mr2/
根据接口文档的接收,创建一个一个servlet需要以下步骤
1)在tomcat的webapps目录下创建hello目录,然后在hello目录下创建WEB-INF/classes目录
2)在classes目录下创建一个HelloWorldServlet.java文件
3)在该源文件中定义一个HelloWorldServlet类,该类可以实现Servlet接口,为了方便,
咱们可以直接继承HttpServlet或者类,然后重写service方法,代码如下
备注:其实我们应该重写doGet( )或者doPost( )方法,因为HttpServlet中的service实现了一些处理逻辑,比如,根据请求方法调用doGet()或doPost(),或者是缓存的处理 等,但是我们这里主要是体验下servlet的执行过程,所以先重写service方法,真正项目不能这样干的,切记。对于service( )、doGet( )、doPost( )三种的关系,后面会做解析
4)编译HelloWorldServlet.java源文件
javac -d . HelloWorldServlet.java
注意:编译时候,需要将tomcat主目录下的lib中的servlet-api.jar加入到classpath,否则会编译失败
注意:编译时候,需要将tomcat主目录下的lib中的servlet-api.jar加入到classpath,否则会编译失败
5)在WEB-INF目录下创建web.xml的配置文件,并编写如下内容:
6)重启tomcat服务器,然后再浏览器上输入:http://localhost:8080/hello/helloWorld,结果如下:
搭建好的web目录结构如下
F:.
└─helloWorld
└─WEB-INF
└─classes //存放Java class文件
└─lib //存放jar包
└─web.xml //web应用部署说明文件
└─*.jsp、*.html
2、Servlet生命周期
Servlet生命周期是由部署的Servlet容器控制的。Servlet不能单独运行,它的运行完全由Servlet引擎来控制和调度
Servlet接口主要定义了以下一些方法:
1)当Servlet被首次访问时,容器会创建该Servlet的实例,这里强调“首次”,因为该servlet实例对象在整个服务器运行期间仅存在一个
2)然后调用init()方法进行Servlet对象初始化
3)然后调用service处理客户的请求
4)直到WEB服务器关闭,servlet容器会调用调用destroy()方法销户该servlet实例。
注意:
1)针对客户的多次Servlet请求,通常情况下,服务器只会创建一个servlet实例对象,也就是说
Servlet实例对象一旦被创建,它就会驻留在内存中,为后续的其他请求服务,直至web容器
退出,servlet实例对象才会销毁。
2)在Servlet的整个生命周期内,Servlet的init方法只被调用一次。而对一个Servlet的每次访问请求
都导致Servlet引擎调用一次Servlet的service方法。对于每个访问请求,Servlet引擎都会创建
新的HttpServletRequest请求对象和一个新的HttpServletResponse相应对象,然后将两个对象
作为参数传递给它的调用的Servlet的service()方法,service方法再根据请求方式分别调用doXXX方法
3、Servlet的使用
在Servlet的开发过程中,一般不直接实现Serlvet接口来编写Serlvet,而是通过继承现成的HttpServlet来开发Serlvet,然后覆盖doGet和doPost方法
HttpServlet类主要提供了一下方法:
doXXX( )方法,如doGet()
HttpServlet的调用过程
当Servlet被首次访问时,容器会创建该Servlet的实例,
1)然后调用init()方法进行Servlet对象初始化
2)然后调用service处理客户的请求,在Service方法中会根据客户请求method来判断具体执行哪个doXXX()方法
例如,如果GET,则调用doGet() ,如果是post,则调用doPost方法进行处理
3)当一个servlet许久不被访问的时候,servlet容器会调用调用destroy()方法销户该servlet实例。
4、web.xml配置
由于客户端是通过URL地址访问web服务器中的资源,所以Servlet程序若想被外界访问,必须把servlet程序映射到一个URL地址上,这个工作在web.xml文件中使用<servlet>元素和<servlet-mapping>元素完成。
<servlet>元素用于注册Servlet,它包含有两个主要的子元素:<servlet-name>和<servlet-class>,分别用于设置Servlet的注册名称和Servlet的完整类名。
<servlet-mapping>元素用于映射一个已注册的Servlet的一个对外访问路径,它包含有两个主要的子元素:<servlet-name>和<url-pattern>,分别用于指定Servlet的注册名称和Servlet的对外访问路径。例如:
1)同一个Servlet可以被映射到多个URL上,即多个<servlet-mapping>元的的<servlet-name>子元素的设置可以是同一个
2)在Servlet映射到的URL中可以使用 * 通配符,但是只能有两种固定的格式:一种是 *.扩展名;另外一种是以正斜杆( / ) 开头并以“/*"结尾,即不带后缀名的URL,则必须以正斜杆( / ) 开头
第一种方式
<servlet-mapping>
<servlet-name> servletName <servlet-name>
<url-pattern>/* <url-pattern>
</servlet-mapping>
第二种方式
<servlet-mapping>
<servlet-name> servletName <servlet-name>
<url-pattern>*.do <url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name> servletName <servlet-name>
<url-pattern>xxx/yyy/test.do <url-pattern>
</servlet-mapping>
注意:第二种方式中的第一个例子,*.do前面不能带任何内容,否则报错,例如,不能配置成如下
<servlet-mapping>
<servlet-name> servletName <servlet-name>
<url-pattern>xx/yy/*.do <url-pattern>
</servlet-mapping>
3)Servlet URL映射原则
适用通配符时,有些时候,多个Servlet映射的URL可能存在冲突,那么此时会使用最具体匹配原则进行匹配
*.后缀 的URL优先级最低
对于如下的一些映射关系:
Servlet1 映射到 /abc/*
Servlet2 映射到 /*
Servlet3 映射到 /abc
Servlet4 映射到 *.do
当请求URL为“/abc/a.html”,“/abc/*”和“/*”都匹配,哪个servlet响应
Servlet引擎将调用Servlet1。
当请求URL为“/abc”时,“/abc/*”和“/abc”都匹配,哪个servlet响应
Servlet引擎将调用Servlet3。
当请求URL为“/abc/a.do”时,“/abc/*”和“*.do”都匹配,哪个servlet响应
Servlet引擎将调用Servlet1。
当请求URL为“/a.do”时,“/*”和“*.do”都匹配,哪个servlet响应
Servlet引擎将调用Servlet2。
Servlet引擎将调用Servlet1。
当请求URL为“/a.do”时,“/*”和“*.do”都匹配,哪个servlet响应
Servlet引擎将调用Servlet2。
当请求URL为“/xxx/yyy/a.do”时,“/*”和“*.do”都匹配,哪个servlet响应
Servlet引擎将调用Servlet2。
Servlet引擎将调用Servlet2。
4)配置Servlet在web服务器启动就加载
如果在web.xml中<servlet>元素中配置了一个<load-on-startup>元素,那么WEB服务器在启动时,就会装载并创建
Servlet的实例对象以及调用Servlet实例对象的init( )方法。
例如:
注意:
<load-on-startup>元素指定的指只能为自然数(大于等于0的整数),数值越小,优先级越高,越优先被加载
5)默认的servlet
①如果某个Servlet的URL映射路径仅仅为一个正斜杆( / ) ,那么这个Servlet就成为当前Web应用程序的缺省Servlet.
②凡是在web.xml中找不到匹配的<servlet-mapping>元素的URL时,它们的访问请求都将交给缺省的Servlet处理,
也就是说,缺少的Servlet的用于处理所有其他的Servlet都不能处理的访问请求
③在<tomcat的安装目录>\conf\web.xml文件中,注册了一个名称为org.apache.catalina.servlets.DefaultServlet
的Servlet,并将这个Servlet设置为缺省的Servlet。
④当访问Tomcat服务器的某个静态HTML文件和图片时,实际上是在访问这个缺省servlet,由这个Servlet将这些
静态资源返回给浏览器
注意:一般不要自己配置默认的Servlet,不然,客户端无法访问静态的web资源
5、Servlet中常用对象
ServletConfig
ServletConfig封装了web.xml中<init-param>配置的初始化参数,该对象由Servlet容器在创建Servlet实例对象时创建,
然后调用Servlet的init( )方法时,传递给Serlvet,进而可以在Servlet中访问当前Servlet的初始化参数。
1)在web.xml配置初始化参数的案例
2)ServletConfig对象提供方法
getInitParameter(param_name) #通过参数参数名称获取参数的具体值
getServletContext() #获取当前web应用的context域对象
注意:如果重写了init( )方法,则必须调用父类的init方法,super.init(config),否则this.getServletConfig()获取不到ServletConfig对象
ServletContext
WEB容器在启动的时候,会为每个WEB应用都创建一个对应的ServletContext对象,它代表当前的WEB应用。
1)如何得到ServletContext实例对象
1)如何得到ServletContext实例对象
ServletConfig对象维护了对ServletContext的引用,可以通过ServletConfig的getServletContext对象获取
2)ServletContext的作用
① 多个Servlet通过ServletContext对象实现该WEB应用的数据共享,这个就是所谓的context域对象
Servlet可以通过ServletContext对象的setAttribute(key ,value)方法将数据存放到ServletContext对象中
然后其他的Servlet对象可以通过ServletContext对象的getAttribute( key ) 来获取这些数据
例如:
servlet1:
this.getServletConfig().getServletContext().setAttribute("abc" ,"123456789") ;
servlet2:
this.getServletConfig().getServletContext().getAttribute("abc")
② 获取WEB应用的初始化参数,就是web.xml的<context> </context>元素维护的WEB应用初始化参数
程序中的使用
③ 实现Servlet的转发
RequestDispatcher rd = context.getRequestDispatcher("/1.jsp");
rd.sendRedirect(req ,resp);
④ 访问资源文件
得到文件路径
读取资源文件的三种方式
.properties的文件
数据有关系则使用xml,没有关系则使用.properties文件
在sevlet中,“/"表示当前web应用目录
ServletContext.getResourceAsStream()
ServletContext.getRealPath()
//普通java类中读取文件
ClassLoader.getResourceAsStream(); //1)文件不能太大 2)文件只会被加载一次,所以不能实时读取更新后的文件内容
ClassLoader.getResource(file_name).getPath() //得到文件路径,然后再使用FileInputStream进行处理
ServletResponse对象
用于向客户端输出响应数据。该对象实例再每次客户端请求时,web服务器都会创建一个新的response对象,然后通过service()方法传给当前servlet。
在子类HttpServletResponse中提供了许多用户操作Http响应消息头的方法,如addHeader(name ,value) 和setHeader(name ,value) ,
这两个方法的区别是addHeader是添加HTTP消息头,而setHeader则如果已经存在消息头,则会更新,否则添加该消息头
OutputStream 和writer,都是用于输出响应报文体内容。
outputStream用于输出字节流,可以处理任何内容,而writer处理字符流,只能处理字符数据
response.getOutputStream()与response.getWriter()不能同时调用
实现文件下载
控制浏览器缓存
ServletResponse实现重定向
1)通过http消息头来实现
response.setStatus(302);
response.setHeader("location" ,"/firstServlet/index.jsp")
2)通过response提供的函数来实现
response.sendRedirect("/firstServlet/index.jsp")
特点:
1)浏览器地址栏的的地址内容会发生变化
2)浏览器发生了两次请求
例如:
response一些细节
1)getOutputStream 和 getWriter这两个方式相互排斥,不能在一个请求中同时这两个方法
2)Servlet程序向ServletOutputStream或PrintWriter对象写入的数据将被Servlet引擎从response里面获取,Servlet引擎将这些数据当做响应消息的正文,然后在与各响应状态行和各响应消息头组合后输出到客户端
3)Servlet的service方法调用结束后,Serlvet引擎会检查getOutputStream 和 getWriter返回的输出流对象是否已经调用过close,如果没有,则Servlet引擎会调用close。
4)服务器在向客户端输出时,即调用out.prinln(),并不会及时的返回到客户端,而是会放到缓冲区中,只有当缓冲区溢出或则调用out.flush() 或则response.flushBuffer()提交时,才向客户端输出,而调用response.sendRedirect()会清空缓冲区内容,所以在跳转之前写的客户端输出代码不会有效果
ServletRequest对象
获取客户端传输的参数
1)request.getParameter("key")
2)request.getParameterNames();
3)request.getParameterValues();
4)request.getParameterMap();
可以结合BeanUtils使用
request乱码问题
对于post提交方式:
对于get方式(这里无论是表单提交还是普通的超链接提交)
默认情况下,request使用ISO8859-1进行解码,所以先获取参数值,再手工转码处理
String value = request.getParameter("key")
String newValue = new String(values.getBytes("ISO8859-1"),"new charset")
request实现服务器端跳转
request.getRequestDispatcher("url").forward(req ,resp);
实现页面跳转方式总结
首先,页面跳转有浏览器端跳转和服务器跳转两种方式,
浏览器端跳转时,浏览器地址栏请求地址发生改变,浏览器总共发生了两次请求,此时这两次请求时独立的
服务器端跳转时,这种方式对客户来说是透明的,客户完成察觉不到。浏览器地址栏请求地址没变化,跳转前请求页面和跳转后页面是同一个request对象
//客户端跳转,通过SerlvetResponse对象完成,location可以为绝对URL或者相对URL,如果是相对URL,则系统在将它放入location报头之前,自动将相对URL转换成绝对URL
resp.sendRedirect(location)
等价于
response.setStatus(302);
response.setHeader("location" , location)
//服务器端跳转,通过RequestDispatcher对象完成,RequestDispatcher对象可以通过SerlvetRequest对象或者SerlvetContext对象的获取,但是两者有区别
RequestDispatcher.forward(req, resp);
//这个可以是绝对地址也可以使相对地址,如果这里使用相对地址,则同ServletContext.getRequestDispatcher,相对于application_root的地址
req.getRequestDispatcher("/servlet005/005")
//这里使用相对地址,相对于application_root的地址
ServletContext.getRequestDispatcher("/servlet005/005")
Servlet实现缓存
浏览器缓存原理
第一种, 浏览器把缓存文件的最后修改时间通过 header ”If-Modified-Since“来告诉Web服务器
1. 浏览器客户端想请求一个文档, 首先检查本地缓存,发现存在这个文档的缓存, 获取缓存中文档的最后修改时间,通过: If-Modified-Since, 发送Request给Web服务器。
2. Web服务器收到Request,将服务器的文档修改时间(Last-Modified): 跟request header 中的,If-Modified-Since相比较, 如果时间是一样的, 说明缓存还是最新的, Web服务器将发送304 Not Modified给浏览器客户端, 告诉客户端直接使用缓存里的版本
第二种, 浏览器把缓存文件的ETag, 通过header "If-None-Match", 来告诉Web服务器。
禁止浏览器缓存
resp.setHeader("Pragma","No-cache");
resp.setHeader("Cache-Control","no-cache");、
resp.setDateHeader("Expires",0);
Servlet处理表单数据
servlet容易已经帮我进行url解码,并将相关参数进行解析处理,我们仅需要调用以下一些方法便可以获取requet中的相关参数
//该方法获取某个参数名为name的参数值,如果该参数名对应多个参数,则返回第一出现的值
req.getParameter(name)
例如:
xxxx?userName=test&passwd=123456
调用getParameter("userName") 则返回test
xxxx?userName=test&passwd=123456&userName=abc
上面url出现参数userName两次,调用getParameter("userName") 也是返回test
//返回该请求中所有的参数名称,它是一个Enumeration类型
req.getParameterNames()
例如:
xxxx?userName=test&passwd=123456
调用getParameterNames() 则返回Enumeration,该对象包含userName和passwd两个参数名
//返回该请求中所有的参数名称,它是一个Map类型,该Map实例的key是参数名称,value就是参数值所组成的数组。
getParameterMap()
//获取指定参数名称的所有值,返回值为数组类型
getParameterValues(name)
xxxx?userName=test&passwd=123456&userName=abc
上面url出现参数userName两次,调用getParameter("userName") 则返回由{"test" ,"abc"}组成的数组
Servlet Web页面压缩技术—gzip
在serlvet 中使用gzip压缩技术页面进行压缩返回给客户端浏览器,使用步骤如下:
1)首先通过客户端发送的Http报文头Accept-Encoding的字段值是否包含gzip,例如:Accept-Encoding : gzip //因为不是所有客户端都支持gzip编码
2)设置返回内容编码格式:resp.setHeader("Content-Encoding", "gzip"); //告诉客户端返回的gzip编码内容
2)创建GZIPOutputStream输出流对象,OutputStream out = new GZIPOutputStream(resp.getOutputStream());
3)将输出内容写入输出流对象,out.write(content.getBytes());
4)关闭输出流对象out.close( ) //??这一步必须执行,否则无法将输出内容返回给客户端
Servlet中Cookie的管理
cookie是由服务器向客户端发送,并将内容保存在浏览器客户端
服务器端向客户端发送cookie
1、创建Cookie对象,创建Cookie对象需要传入cookie的name和value,这两个参数都是字符串类型
Cookie cookie = new Cookie("name" ,"vaue");
注意:这样创建出来的cookie是会话级别的,即,会话结束后,该cookie对象就会被浏览器清理掉,如果希望cookie保存到硬盘,则进行第二步
2、设置cookie的最大有效期限。负数表示临时的,是会话级别的, 0表示是临时的(如果是新cookie)或者要求浏览器删除掉已经存在cookie,正数表示cookie的有效期限,以秒为单位
cookie.setMaxAge(senconds) //senconds以秒为单位
3、将cookie发送到客户端。
如果cookie对象在服务器端创建后没有发送到客户端,那么该cookie是不启作用的。
HttpServletResponse.addCookie( cookie ) //把cookie添加http协议报头的Set-Cookies字段返回给客户端
服务器端读取客户端的cookies
Cookie[] cookies = HttpServletRequest.getCookies( );
因为浏览器是以主机或者域名的方式保存cookie,因此,即使当前请求的URL没有向浏览器发送过cookie,但是HttpServletRequest.getCookies( )可能不为空的。
因此,需要遍历cookies这个数组,然后根据cookie的name来判断是否我们需要的cookie
修改浏览器cookie值
因为浏览器向服务器端发送cookie信息时,仅仅是发送cookie的name和lvalue,其他的信息,如maxage都已经"丢失"
所以需要修改cookie值时,需要重新创建一个同名的cookie,同时设置它maxage等属性
Servlet会话跟踪(Session)
1、什么是session?
Session代表服务器与浏览器的一次会话过程,这个过程是连续的,也可以时断时续的,但是这个过程通过session来维护。在Servlet中,session指的是HttpSession类的对象。
2、session的原理
首先session是由服务器端产生的,在服务器端保存,然后在服务器返回响应页面时,在http响应报文头中将session的id以cookie的方式返回给浏览器吗,一般为JSESSIONID=xxxxx。后续浏览器请求页面时,再将该session id传给服务器,服务器首先根据这个session id查看有没对应的session对象,如果有,则直接取出来用。
首次访问,服务器通过Set-Cookie报头将session id发送给客户端:
第二次访问时,浏览器通过请求报头Cookie,将session id发送给服务器
3、Servlet对session的管理
Servlet中对应session接口类为HttpSession,该类提供一下方法对session进行管理
4、session误区
《1》Session创建的时间,即session是什么时候被创建的?
一
个常见的误解是以为session在有客户端访问时就被创建,然而事实是直到某server端程序调用
HttpServletRequest.getSession(true)这样的语句时才被创建,注意如果JSP没有显示的使用 <% @page
session="false"%> 关闭session,则JSP文件在编译成Servlet时将会自动加上这样一条语句
HttpSession session = HttpServletRequest.getSession(true);这也是JSP中隐含的
session对象的来历。
由于session会消耗内存资源,因此,如果不打算使用session,应该在所有的JSP中关闭它。
引申:
1)、访问*.html的静态资源因为不会被编译为Servlet,也就不涉及session的问题。
2)、当JSP页面没有显式禁止session的时候,在打开浏览器第一次请求该jsp的时候,服务器会自动为其创建一个session,并赋予其一个sessionID,发送给客户端的浏览器。以后客户端接着请求本应用中其他资源的时候,会自动在请求头上添加:
Cookie:JSESSIONID=客户端第一次拿到的session ID
这样,服务器端在接到请求时候,就会收到session ID,并根据ID在内存中找到之前创建的session对象,提供给请求使用。这也是session使用的基本原理----搞不懂这个,就永远不明白session的原理。
首次访问
第二次访问
通过图可以清晰发现,第二次请求的时候,已经添加session ID的信息。
《2》服务器端Session删除的时间
1)Session超时:超时指的是连续一定时间服务器没有收到该Session所对应客户端的请求,并且这个时间超过了服务器设置的Session超时的最大时间。
2)程序调用HttpSession.invalidate()
3)服务器关闭或服务停止
《3》session存放在哪里
服务器端的内存中。不过session可以通过特殊的方式做持久化管理
《4》session的id是从哪里来,sessionID是如何使用的
当
客户端第一次请求session对象时候,服务器会为客户端创建一个session,并将通过特殊算法算出一个session的ID,用来标识该
session对象,当浏览器下次(session继续有效时)请求别的资源的时候,浏览器会偷偷地将sessionID放置到请求头中,服务器接收到请
求后就得到该请求的sessionID,服务器找到该id的session返还给请求者(Servlet)使用。一个会话只能有一个session对象,
对session来说是只认id不认人
《5》session会因为浏览器的关闭而删除吗?
不会,session只会通过上面提到的方式去关闭。
不会,session只会通过上面提到的方式去关闭。
《6》关闭浏览器是否session也就无效了?
其实不是,关闭浏览器,只能说该浏览器客户端与服务器端的本次会话结束,因为关闭浏览器时,浏览器会把保存session id的cookie JSESSIONID删除了,
因此,该浏览器客户端下次再请求时,已经没有session id发给服务器端,因此服务器会创建一个新的session,旧的那个session,等待超时后,自动销毁