静态导入和动态导入的三点区别:
Scope属性用于指定JavaBean实例的作用范围:
重定向
重定向是response的另外一个用处,与forward不同的是,重定向会丢失所有的请求参数和request范围的属性,因为重定向将生成第二次请求,与前一次请求不在同一个request范围内,所以发送一次请求的请求参数和request范围的属性全部丢失。
HttpServletResponse提供了一个sendRedirect(String path)方法,该方法用于重定向到path资源,即重新向path资源发送请求。
forward和redirect对比
执行redirect后生成第二次请求,而forward依然是上一次请求。
redirect的目标页面不能访问原请求的请求参数,因为是第二次请求了,所有原请求的请求参数、request范围的属性全部丢失。forward的目标页面可以访问原请求的请求参数,因为依然是同一次请求,所有原请求的请求参数、request范围的属性全部存在。
地址栏改为重定向的目标URL。相当于在浏览器地址栏里输入新的URL后按回车键。而forward地址栏里请求的URL不会改变
增加Cookie
Cookie与session的不同之处在于:session会随浏览器的关闭而失效,但Cookie会一直存放在客户端机器上,除非超出Cookie的生命期限。
增加Cookie也是使用response内置对象完成的,response对象提供了如下方法。
void addCookie(Cookie cookie):增加Cookie。
增加Cookie请按如下步骤进行。
1
2
3
4
5
6
7
8
9
10
|
<%
//增加cookie
String name = request.getParameter(
"name");
//以获取到的请求参数为值,创建一个Cookie对象
Cookie c =
new
Cookie(
"username" , name);
//设置Cookie对象的生存期限
c.setMaxAge(
24 *
3600);
//向客户端增加Cookie对象
response.addCookie(c);
%>
|
session对象也是一个非常常用的对象,这个对象代表一次用户会话。一次用户会话的含义是:从客户端浏览器连接服务器开始,到客户端浏览器与服务器断开为止,这个过程就是一次会话。
session通常用于跟踪用户的会话信息,如判断用户是否登录系统,或者在购物车应用中,用于跟踪用户购买的商品等。
session对象是HttpSession的实例,HttpSession有如下两个常用的方法。
setAttribute(String attName,Object attValue):设置session范围内attName属性的值为attValue。
getAttribute(String attName):返回session范围内attName属性的值。
关于session还有一点需要指出,session机制通常用于保存客户端的状态信息,这些状态信息需要保存到Web服务器的硬盘上,所以要求session里的属性值必须是可序列化的,否则将会引发不可序列化的异常。
session的属性值可以是任何可序列化的Java对象。
创建Servlet实例有两个时机。
每个Servlet的运行都遵循如下生命周期。
在标准的MVC模式中,Servlet仅作为控制器使用。Java EE应用架构正是遵循MVC模式的,对于遵循MVC模式的Java EE应用而言,JSP仅作为表现层(View)技术,其作用有两点:
Servlet则仅充当控制器(Controller)角色,它的作用类似于调度员:所有用户请求都发送给 Servlet,Servlet调用Model来处理用户请求,并调用JSP来呈现处理结果;或者Servlet直接调用JSP将应用的状态数据呈现给用户。
Model通常由JavaBean来充当,所有业务逻辑、数据访问逻辑都在Model中实现。实际上隐藏在Model下的可能还有很多丰富的组件,例如DAO组件、领域对象等。
下面是MVC中各个角色的对应组件。
Filter可认为是Servlet的一种”加强版”,它主要用于对用户请求进行预处理,也可以对HttpServletResponse进行后处理,是个典型的处理链。Filter也可对用户请求生成响应,这一点与Servlet相同,但实际上很少会使用Filter向用户请求生成响应。使用Filter完整的流程是:Filter对用户请求进行预处理,接着将请求交给Servlet进行处理并生成响应,最后Filter再对服务器响应进行后处理。
Filter有如下几个用处。
Filter有如下几个种类。
创建一个Filter只需两个步骤:
创建Filter必须实现javax.servlet.Filter接口,在该接口中定义了如下三个方法。
前面已经提到,Filter可以认为是Servlet的”增强版”,因此配置Filter与配置Servlet非常相似,都需要配置如下两个部分:
区别在于,Servlet通常只配置一个URL,而Filter可以同时拦截多个请求的URL。因此,在配置Filter的URL模式时通常会使用模式字符串,使得Filter可以拦截多个请求。与配置Servlet相似的是,配置Filter同样有两种方式:
@WebFilter(filterName=”log”, urlPatterns={“/*”})
@WebFilter修饰一个Filter类,用于对Filter进行配置,它支持如表所示的常用属性
@WebFilter支持的常用属性
属 性 | 是否必需 | 说 明 |
---|---|---|
asyncSupported | 否 | 指定该Filter是否支持异步操作模式。关于Filter的异步调用请参考2.15节 |
dispatcherTypes | 否 | 指定该Filter仅对那种dispatcher模式的请求进行过滤。该属性支持ASYNC、ERROR、FORWARD、INCLUDE、REQUEST 这5个值的任意组合。默认值为同时过滤5种模式的请求 |
displayName | 否 | 指定该Filter的显示名 |
filterName | 指定该Filter的名称 | |
initParams | 否 | 用于为该Filter配置参数 |
servletNames | 否 | 该属性值可指定多个Servlet的名称,用于指定该Filter仅对这几个Servlet执行过滤 |
urlPatterns/value | 否 | 这两个属性的作用完全相同。都指定该Filter所拦截的URL |
在web.xml文件中配置Filter与配置Servlet非常相似,需要为Filter指定它所过滤的URL,并且也可以为Filter配置参数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<filter>
<filter-name>log
filter-name>
<filter-class>lee.LogFilter
filter-class>
filter>
<filter-mapping>
<filter-name>log
filter-name>
<url-pattern>/*
url-pattern>
filter-mapping>
|
实际上Filter和Servlet极其相似,区别只是Filter的doFilter()方法里多了一个FilterChain的参数,通过该参数可以控制是否放行用户请求。
假设系统有包含多个Servlet,这些Servlet都需要进行一些的通用处理:比如权限控制、记录日志等,这将导致在这些Servlet的service方法中有部分代码是相同的–为了解决这种代码重复的问题,我们可以考虑把这些通用处理提取到Filter中完成,这样各Servlet中剩下的只是特定请求相关的处理代码,而通用处理则交给Filter完成。
由于Filter和Servlet如此相似,所以Filter和Servlet具有完全相同的生命周期行为,且Filter也可以通过元素或@WebFilter的initParams属性来配置初始化参数,获取Filter的初始化参数则使用FilterConfig的getInitParameter()方法。
对于Java Web应用来说,要实现伪静态非常简单:可以通过Filter拦截所有发向.html请求,然后按某种规则将请求forward到实际的.jsp页面即可。现有的URL Rewrite开源项目为这种思路提供了实现,使用URL Rewrite实现网站伪静态也很简单。
下载URL Rewrite应下载其src项(urlrewritefilter-3.2.0-src.zip),下载完成后得到一个urlrewritefilter-3.2.0-src.zip文件,将该压缩文件解压缩,得到如下文件结构。
web.xml
1
2
3
4
5
6
7
8
9
10
|
<filter>
<filter-name>UrlRewriteFilter
filter-name>
<filter-class>org.tuckey.web.filters.urlrewrite.UrlRewriteFilter
filter-class>
filter>
<filter-mapping>
<filter-name>UrlRewriteFilter
filter-name>
<url-pattern>/*
url-pattern>
filter-mapping>
|
上面的配置片段指定使用URL Rewrite Filter拦截所有的用户请求。
在应用的WEB-INF路径下增加urlrewrite.xml文件,该文件定义了伪静态映射规则,这份伪静态规则是基于正则表达式的。
下面是本应用所使用的urlrewrite.xml伪静态规则文件。
1
2
3
4
5
6
7
8
9
10
|
xml version="1.0" encoding="GBK"
<urlrewrite>
<rule>
<from>/userinf-(\w*).html
from>
<to type="forward">/userinf.jsp?username=$1
to>
rule>
urlrewrite>
|
上面的规则文件中只定义了一个简单的规则:所有发向/userinf-(\w).html的请求都将被forward到user.jsp页面,并将(\w)正则表达式所匹配的内容作为username参数值。根据这个伪静态规则,我们应该为该应用提供一个userinf.jsp页面,该页面只是一个模拟了一个显示用户信息的页面
userinf.jsp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<%
@ page contentType="text/html; charset=GBK" language="java" errorPage=""
%>
<%
//获取请求参数
String user = request.getParameter("username");
%>
<html xmlns="http://www.w3.org/1999/xhtml">
<head><title> <%=
user
%>的个人信息 title>head>
<body>
<%
//此处应该通过数据库读取该用户对应的信息
//此处只是模拟,因此简单输出:
out.println("现在时间是:" + new java.util.Date() + "
");
out.println("用户名:" + user);
%>
body> html>
|
当Web应用在Web容器中运行时,Web应用内部会不断地发生各种事件:如Web应用被启动、Web应用被停止,用户session开始、用户session结束、用户请求到达等,通常来说,这些Web事件对开发者是透明的。
实际上,Servlet API提供了大量监听器来监听Web应用的内部事件,从而允许当Web内部事件发生时回调事件监听器内的方法。
使用Listener只需要两个步骤:
常用的Web事件监听器接口有如下几个。
下面先以ServletContextListener为例来介绍Listener的开发和使用,ServletContextListener用于监听Web应用的启动和关闭。该Listener类必须实现ServletContextListener接口,该接口包含如下两个方法。
为Web应用配置Listener也有两种方式:
在web.xml中使用元素进行配置时只要配置如下子元素即可。
listener-class:指定Listener实现类。
使用ServletContextAttributeListener
ServletContextAttributeListener用于监听ServletContext(application)范围内属性的变化,实现该接口的监听器需要实现如下三个方法。
ServletRequestListener用于监听用户请求的到达,实现该接口的监听器需要实现如下两个方法。
ServletRequestAttributeListener则用于监听ServletRequest(request)范围内属性的变化,实现该接口的监听器需要实现attributeAdded、attributeRemoved、attributeReplaced三个方法。
HttpSessionListener用于监听用户session的创建和销毁,实现该接口的监听器需要实现如下两个方法。
HttpSessionAttributeListener则用于监听HttpSession(session)范围内属性的变化,实现该接口的监听器需要实现attributeAdded、attributeRemoved、attributeReplaced三个方法。
表达式语言(Expression Language)是一种简化的数据访问方式。使用表达式语言可以方便地访问JSP的隐含对象和JavaBeans组件,在JSP 2规范中,建议尽量使用表达式语言使JSP文件的格式一致,避免使用Java脚本。
表达式语言的语法格式是: ${expression}
如果想输出$
符号,则在$
前加转义字符\
表达式语言支持的算术运算符和逻辑运算符
所有java语言里的算术运算符都支持,甚至java不支持的运算符也支持。
div(除)、mod(取余)、lt(小于)、gt(大于)、ge(大于等于)、le(小于等于)、eq(等于)、ne(不等于)
表达式语言不仅可在数字与数字之间比较,还可在字符与字符之间比较,字符串的比较是根据其对应UNICODE值来比较大小的。
表达式语言把所有数值都当成浮点数处理,所以3/0的实质是3.0/0.0,得到结果应该是Infinity。
表达式语言的内置对象
表达式语言包含如下11个内置对象。
${param.name}
、 ${param["name"]
Tag File中只有如下几个内置对象。
Servlet 3.0规范在javax.servlet.annotation包下提供了如下Annotation。
在以前的Servlet规范中,如果Servlet作为控制器调用了一个耗时的业务方法,那么Servlet必须等到业务方法完全返回之后才会生成响应,这将使得Servlet对业务方法的调用变成一种阻塞式的调用,因此效率比较低。
Servlet 3.0规范引入了异步处理来解决这个问题,异步处理允许Servlet重新发起一条新线程去调用耗时的业务方法,这样就可避免等待。
Servlet 3.0的异步处理是通过AsyncContext类来处理的,Servlet可通过ServletRequest的如下两个方法开启异步调用、创建AsyncContext对象:
重复调用上面的方法将得到同一个AsyncContext对象。AsyncContext对象代表异步处理的上下文,它提供了一些工具方法,可完成设置异步调用的超时时长,dispatch用于请求、启动后台线程、获取request、response对象等功能。
AsyncServlet.java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
"/async",asyncSupported=
true)
(urlPatterns=
public
class AsyncServlet extends HttpServlet {
public void doGet(
HttpServletRequest request,
HttpServletResponse response)
throws
IOException,
ServletException {
response.setContentType(
"text/html;charset=GBK");
PrintWriter out = response.getWriter();
out.println(
"
out.println(
"进入Servlet的时间:" +
new java.util.
Date() +
".
");
out.flush();
//创建AsyncContext,开始异步调用
AsyncContext actx = request.startAsync();
//设置异步调用的超时时长
actx.setTimeout(
30*
1000);
//启动异步调用的线程
actx.start(
new
Executor(actx));
out.println(
"结束Servlet的时间:" +
new java.util.
Date() +
".
");
out.flush();
}
}
|
下面是线程执行体的代码。
Executor.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public
class
Executor
implements
Runnable {
private AsyncContext actx =
null;
public Executor(AsyncContext actx) {
this.actx = actx;
}
public void run() {
try {
//等待5秒钟,以模拟业务方法的执行
Thread.sleep(
5 *
1000);
ServletRequest request = actx.getRequest();
List
books.
add(
"疯狂Java讲义");
books.
add(
"经典Java EE企业应用实战");
books.
add(
"疯狂XML讲义");
request.setAttribute(
"books" , books);
actx.dispatch(
"/async.jsp");
}
catch(Exception e){
e.printStackTrace();
}
}
}
|
该线程执行体内让线程暂停5秒来模拟调用耗时的业务方法,最后调用AsyncContext的dispatch方法把请求dispatch到指定JSP页面。
被异步请求dispatch的目标页面需要指定session=”false”,表明该页面不会重新创建session。
async.jsp:
1
2
3
4
5
6
7
8
9
10
|
<%
@ page contentType="text/html; charset=GBK" language="java" session="false"
%>
<%
@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"
%>
<ul> <c:forEach items="${books}" var="book">
<li>${book}li>
c:forEach>
ul>
<%
out.println("业务调用结束的时间:" + new java.util.Date());
//完成异步调用
request.getAsyncContext().complete();
%>
|
为Servlet开启异步调用有两种方式:
当Servlet启用异步调用的线程之后,该线程的执行过程对开发者是透明的。但在有些情况下,开发者需要了解该异步线程的执行细节,并针对特定的执行结果进行针对性处理,这可借助于Servlet 3.0提供的异步监听器来实现。
异步监听器需要实现AsyncListener接口,实现该接口的监听器类需要实现如下4个方法。
提供了异步监听器之后,还需要通过AsyncContext来注册监听器,调用该对象的addListener()方法即可注册监听器。例如在上面的Servlet中增加如下代码即可注册监听器:
1
2
3
|
AsyncContext actx = request.startAsync();
//为该异步调用注册监听器
actx.addListener(
new
MyAsyncListener());
|
虽然上面的MyAsyncListener监听器类可以监听异步调用开始、异步调用完成两个事件,但从实际运行的结果来看,它并不能监听到异步调用开始事件,这可能是因为注册该监听器时异步调用已经开始了的缘故。
在Filter中进行异步调用与在Servlet中进行异步调用的效果完全相似
Servlet 3.0还有一个改变是改进了部分API,这种改进很好地简化了Java Web开发。其中两个较大的改进是:
HttpServletRequest增加了对文件上传的支持。
ServletContext允许通过编程的方式动态注册Servlet、Filter。
HttpServletRequest提供了如下两个方法来处理文件上传。
Part getPart(String name):根据名称来获取文件上传域。
Collection getParts():获取所有的文件上传域。
上面两个方法的返回值都涉及一个API:Part,每个Part对象对应于一个文件上传域,该对象提供了大量方法来访问上传文件的文件类型、大小、输入流等,并提供了一个write(String file)方法将上传文件写入服务器磁盘。
为了向服务器上传文件,需要在表单里使用文件域,这个文件域会在HTML页面上产生一个单行文本框和一个”浏览”按钮,浏览者可通过该按钮选择需要上传的文件。除此之外,上传文件一定要为表单域设置enctype属性。
表单的enctype属性指定的是表单数据的编码方式,该属性有如下三个值。
如果将enctype设置为application/x-www-form-urlencoded,或不设置enctype属性,提交表单时只会发送文件域的文本框里的字符串,也就是浏览者所选择文件的绝对路径,对服务器获取该文件在客户端上的绝对路径没有任何作用,因为服务器不可能访问客户机的文件系统。
UploadServlet.java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
"upload" , urlPatterns={
"/upload"})
(name=
public
class UploadServlet extends HttpServlet {
public
void service(HttpServletRequest request , HttpServletResponse response) throws IOException , ServletException {
response.setContentType(
"text/html;charset=GBK");
PrintWriter out = response.getWriter();
//获取普通请求参数
String fileName = request.getParameter(
"name");
//获取文件上传域
Part
part = request.getPart(
"file");
//获取上传文件的类型
out.println(
"上传文件的类型为:" +
part.getContentType() +
"
");
//获取上传文件的大小
out.println(
"上传文件的的大小为:" +
part.getSize() +
"
");
//获取该文件上传域的Header Name
Collection<
String> headerNames =
part.getHeaderNames();
//遍历文件上传域的Header Name、Value
for (
String headerName : headerNames) {
out.println(headerName +
"--->" +
part.getHeader(headerName) +
"
");
}
//将上传的文件写入服务器
part.write(getServletContext().getRealPath(
"/uploadFiles") +
"/" + fileName );
//①
}
}
|
上面Servlet使用了@MultipartConfig修饰,处理文件上传的Servlet应该使用该Annotation修饰。接下来该Servlet中HttpServletRequest就可通过getPart(String name)方法来获取文件上传域–就像获取普通请求参数一样。
与Servlet 3.0所有Annotation相似的是,Servlet 3.0为@提供了相似的配置元素,我们同样可以通过在元素中添加子元素来达到相同的效果。
上面Servlet上传时保存的文件名直接使用了name请求参数,实际项目中一般不会这么做,因为可能多个用户会填写相同的name参数,这样将导致后面用户上传的文件覆盖前面用户上传的图片。实际项目中可借助于java.util.UUID工具类生成文件名。
http://howiefh.github.io/2015/03/13/jsp-servlet-note/#more