Filter是Servlet 的一种“加强版”,它主要用于对用户请求进行预处理,也可以对FilterHttpServletResponse 进行后处理,是个典型的处理链。Filter 也可对用户请求生成响应,这一点与Servlet相同,但实际上很少会使用Filter 向用户请求生成响应。
使用Filter 完整的流程是: Filter 对用户请求进行预处理,接着将请求交给Servlet 进行处理并生成响应,最后Filter 再对服务器响应进行后处理。
Filter 有如下几个用处。
Filter 有如下几个种类:
Filter 可负责拦截多个请求或响应;- 一个请求或响应也可被多个Filter 拦截。
public class LoginFilter implements Filter{
@Override
public void destroy() {
// TODO Auto-generated method stub
//释放资源
}
@Override
public void doFilter(ServletRequest arg0, ServletResponse arg1,
FilterChain arg2) throws IOException, ServletException {
// TODO Auto-generated method stub
HttpServletRequest request = (HttpServletRequest)arg0;//将request强转成http的request
System.out.println("已经拦截到用户的url");
long start = System.currentTimeMillis();
arg2.doFilter(arg0, arg1);//只是链式处理,请求还是要到目的地址,如果是重定向直接就不要这句。
long end = System.currentTimeMillis();
System.out.println("请求的时间"+(end-start));
}
@Override
public void init(FilterConfig arg0) throws ServletException {
// TODO Auto-generated method stub
//初始化配置
}
}
doFilter0方法,实现该方法就可实现对用户请求进行预处理,也可实现对服务器响应进行后处理一它们的分界线为是否调用了chain.doFilter0,执行该方法之前,即对用户请求进行预处理; 执行该方法之后,即对服务器响应进行后处理。
doFilter0方法里多了一个FilterChain 的参数,通过该参数可以控制是否放行用户请求。
@WebFilter(filterName="LoginFilter",urlPatterns="/*")
<filter>
<filter-name>LoginFilterfilter-name>
<filter-class>com.example.test.Filter.LoginFilterfilter-class>
filter>
<filter-mapping>
<filter-name>LoginFilterfilter-name>
/*
filter-mapping>
实际上Filter 和Servlet 极其相似,区别Filter 里doFilter(方法里的代码就是从多个Servlet 的service(方法里抽取的通用代码,通过使用Filter 可以实现更好的代码复用。
假设系统有包含多个Servlet,这些Servlet 都需要进行一些的通用处理: 比如权限控制、记录日志等,这将导致在这些Servlet 的service 方法中有部分代码是相同的一为了解决这种代码重复的问题,我们可以考虑把这些通用处理提取到Filter 中完成,这样各Servlet 中剩下的只是特定请求相关的处理代码,而通用处理则交给Filter 完成。
package com.example.test.Filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
@WebFilter(filterName="LoginFilter",urlPatterns="/*")
public class LoginFilter implements Filter{
private FilterConfig config;
@Override
public void destroy() {
// TODO Auto-generated method stub
//释放资源
}
@Override
public void doFilter(ServletRequest arg0, ServletResponse arg1,
FilterChain arg2) throws IOException, ServletException {
// TODO Auto-generated method stub
String encoding = config.getInitParameter("encoding");
String loginpage = config.getInitParameter("loginpage");
String prologin = config.getInitParameter("prologin");
HttpServletRequest request = (HttpServletRequest)arg0;
HttpSession seesion = request.getSession();
String requestpath = request.getRequestURI();
String username = (String) seesion.getAttribute("username");
if(username==null&&!requestpath.endsWith(loginpage)&&!requestpath.endsWith(prologin)){
request.getRequestDispatcher(loginpage).forward(arg0, arg1);
}else{
arg2.doFilter(arg0, arg1);//放行。
}
}
@Override
public void init(FilterConfig arg0) throws ServletException {
// TODO Auto-generated method stub
//初始化配置
this.config = arg0;
}
}
<filter>
<filter-name>LoginFilterfilter-name>
<filter-class>com.example.test.Filter.LoginFilterfilter-class>
param>
<param-name>encodingparam-name>
<param-value>GBKparam-value>
param>
param>
<param-name>loginpageparam-name>
<param-value>/login.jspparam-value>
param>
param>
<param-name>prologinparam-name>
<param-value>/prologin.jspparam-value>
param>
filter>
当Web 应用在Web 容器中运行时,Web 应用内部会不断地发生各种事件: 如Web 应用被启动、Web 应用被停止,用户session 开始、用户session 结束、用户请求到达等,通常来说,这些Web 事件对开发者是透明的。
实际上,ServletAPI 提供了大量监听器来监听Web 应用的内部事件,从而允许当Web 内部事件发生时回调事件监听器内的方法。
对于监听对象的不同,只要相应的实现以上的接口,实现方法即可。
@WebListener
<listener>
<listener-class>com.example.listener.MyListenerlistener-class>
listener>
在上面的我们已经知道了要监听什么,就实现什么接口,但是我们知道Java是多实现的,我们可以通过一个类实现多个接口,从而实现监听多个对象。
注意实现Seesion对象监听,一定要注意浏览器的缓存影响。
如果需要使用JSP 2 语法,其web.xml 文件必须使用Servlet 2.4 以上版本的配置文件。现在正常Servlet版本都是Servlet3.0以上。
查看Servlet的版本,到web.xml中:
<jsp-config>
<jsp-property-group>
<url-pattern>*.jsp</url-pattern> 对应的哪些jsp文件配置,可以使用正则表达式
<el-ignored>true</el-ignored> 是否允许el表达式,false为可以使用el表达式,如果true,这输出表达式,不会计算。
<page-encoding>GBK</page-encoding> jsp页面的编码
<scripting-invalid>true</scripting-invalid> 是否允许用java脚本,true是不可以使用,如果jsp包含java脚本,则运行报错。
<include-prelude></include-prelude> 隐式导入页面头
<include-coda></include-coda> 隐式导入页面尾
</jsp-property-group>
</jsp-config>
主要包括如下4 个方面。
需要注意的地方:
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<base href="<%=basePath%>">
在要主页面中,必须删除以上代码,使用这种导入方法,导入的jsp中如果有这两句代码,会出现重复,如果没有,在导入之后,会自动产生一个,也会重复。
表达式语言(Expression Language) 是一种简化的数据访问方式。使用表达式语言可以方便地访问JSP 的隐含对象
和JavaBeans 组件,在JSP 2 规范中,建议尽量使用表达式语言使JSP 文件的格式一致,避免使用Java 脚本。
${1+2}//3
${2*4}//8
${'a'>'b'}//false
使用表达式语言可以直接获取请求参数值,可以获取页面中JavaBean 的指定属性值,获取请求头
及获取page、request.session 和application 范围的属性值等,这些都得益于表达式语言的内置对象。
表达式语言包含如下11个内置对象。
可看具体例子:
<form action="request.jsp" method="post">
<input type="text" name ="username" }/>
<input type="submit" value="提交"/>
form>
<%
pageContext.setAttribute("pagecontext","pagecontext");
session.setAttribute("session", "session");
request.setAttribute("request1", "request");
application.setAttribute("application", "application");
Cookie cookie = new Cookie("name","laoqiang");
cookie.setMaxAge(12*3600);
response.addCookie(cookie);
%>
${requestScope.request1}<br>
${pageScope.pagecontext}<br>
${initParam.user}<br>
${param.username}<br>
${param["username"]}<br>
${header.host}<br>
${header["accept"]}<br>
${sessionScope.session}<br>
${applicationScope.application}<br>
${cookie.name.value}
上述的获取中,特别需要注意的有两个一个pageContext和PageScope内置对象,作用效果差不多,但是通过这个内置对象来获取,必须保证在同一个页面赋值和获取,否则无效。另外还有request,必须要保证在同一个请求中在能获取,如果地址栏改变,就默认是两次请求。
另外在获取的时候用方括号和.获取是一样的,可以看作两种方式。
加强表达式语言的功能。自定义丽数的开发步骤非常类似于标签的开发步骤,定义方式也几乎一样。区别在于自定义标签直接在页面上生成输出,而自定义函数则需要在表达式语言中使用。
函数功能大大扩充了EL 的功能,EL 本身只是一种数据访问语言,因此它不支持调用方法。如果需要在EL 中进行更复杂的处理,就可以通过函数来完成。函数的本质是:提供一种语法允许在EL 中调用某个类的静态方法。
public class MyElFunction {
public static String reverse(String text){
return new StringBuffer(text).reverse().toString();
}
public static int length(String text){
return text.length();
}
}
注意点:函数必须是static修饰,静态的,最好带返回值,因为表达式是直接输出值。
<taglib>
<tlib-version>1.0tlib-version>
<jsp-version>1.2jsp-version>
<short-name>taglibshort-name>
<uri>/WEB-INF/tag.tlduri>
<description>
A simple tab library for the examples
description>
<function>
<name>reversename>//这个name是在使用的时候, ${MyElfunction:**reverse**("dhaskda")}中的黑体字一致
<function-class>com.example.test.elfunction.MyElFunctionfunction-class>//指定函数的所在类
<function-signature>java.lang.String reverse(java.lang.String)function-signature> 指定函数的实现方法,包括返回值类型、函数名、参数
function>
<function>
<name>lengthname>
<function-class>com.example.test.elfunction.MyElFunctionfunction-class>
<function-signature>int length(java.lang.String)function-signature>
function>
taglib>
注意:这个需要注意的是如果返回值类型、参数类型是引用类型,例如String必须写成java.lang.String ,否则会出现错误。
在使用的时候必须导入标签库指令:
<%@ taglib prefix="MyElfunction" uri="/WEB-INF/tag.tld" %> 指定它的前缀名,uri正常都写该WEB-INF下的tld文件目录
${MyElfunction:reverse("dhaskda")}
${MyElfunction:length("hhhh")}
TagFile 是自定义标签的简化用法,使用Tag File 可以无须定义标签处理类和标签库文件,但仍然可以在JSP 页面中使用自定义标签。
TagFile 建立一个迭代器标签,其步骤如下。
建立Tag 文件,在JSP 所支持Tag File 规范下,Tag File 代理了标签处理类,它的格式类似于JSP
文件。可以这样理解: 如同JSP 可以代替Servlet 作为表现层一样,Tag File 则可以代替标签处理类。
Tag File 具有以下5 个编译指令。
下面是一个例子:
<%@tag pageEncoding="GBK" import="java.util.List" %>
<%@ attribute name="bgcolor" %>
<%@ attribute name="title" %> //指定该标签有两个属性
<%
List<String> list = (List<String>)request.getAttribute("a");
%>
<table border="1" bgcolor="${bgcolor}" >
<tr>
<td>
${title}
td>
tr>
<%
for(int i = 0;i
<tr>
<td>
<%=list.get(i).toString() %>
td>
tr>
<%
}
%>
table>
<%
List<String> list = new ArrayList<String>();
list.add("laoqiang");
list.add("laohe");
list.add("qianglao");
list.add("laohejia");
request.setAttribute("a", list);
%>
<mytagfile:tagfile bgcolor="#99dd99" title="这是tagfile"/>
整个tagfile没有什么难度,需要注意的地方:
<%@taglib prefix="mytagfile" tagdir="/WEB-INF/tags" %>
导入标签库的时候,tagdir放在web-inf文件下,要新建一个tags文件夹,才可以。
TagFile 是自定义标签的简化。事实上,就如同JSP 文件会编译成Servlet一样,Tag File 也会编译成标签处理类,自定义标签的后台依然由标签处理类完成,而这个过程由容器完成。
Servlet 3.0 为模块化开发提供了良好的支持,Servlet 3.0 规范不再要求所有Web 组件(如Servlet、Listener、Filter 等) 都部署在web.xml 文件中,而是允许采用“Web 模块”来部署、管理它们。
<web-fragment version="3.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-fragment_3_0.xsd">//如果没有这句,xml智能提示就没了,就要手敲啦。
<name>laohename>
<listener>
<listener-class>com.example.listener.MyListenerOnelistener-class>//这里监听器、servlet、过滤器都可以配置
listener>
<ordering>
<before>
<others/>
before>
ordering>
web-fragment>
将下面的三个文件放到一个文件目录(其中包括web-fragment和所需要类文件以及资源等等),打包成jar:
jar在window执行语句:
将打包好的jar包直拷到项目的lib目录下,就可以直接使用了。
上面我们介绍了使用web-fragment指定加载的顺序,我们同样可以在web.xml指定顺序,它会覆盖web-fragment。
<absolute-ordering>
<name>laohename>//这个name和web-fragment一致
absolute-ordering>
在以前的Servlet 规范中,如果Servlet作为控制器调用了一个耗时的业务方法,那么Servlet必须等到业务方法完全返回之后才会生成响应,这将使得Servlet 对业务方法的调用变成一种阻塞式的调用,因此效率比较低。
由上面可以看出这个异步操作,是针对Servlet。
Servlet 3.0 规范引入了异步处理来解决这个问题,异步处理允许Servlet 重新发起一条新线程去调用耗时的业务方法,这样就可避免等待。
protected void doPost(final HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
final AsyncContext as = request.startAsync();
as.setTimeout(10000);
as.start(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
Thread.sleep(5000);//模拟业务处理
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("我是执行任务返回得到结果");
request.setAttribute("result","我是异步线程的结果");
as.dispatch("/index1.jsp");
}
});
System.out.println("我是下面执行的东西");
}
}
<%
System.out.println("我在主jsp的");
boolean flag = request.isAsyncStarted();
if(flag){
request.getAsyncContext().complete();
System.out.println(flag);
System.out.println(request.getAttribute("result"));
}
%>
上面的代码主要使用了AsyncContext类的complete、dispatch、startAsync,其中使用complete方法必须在startAsync之后,否则会报异常,dispatch是将一部获取的数据结果分发给对应的jsp,complete这个方法尽量在dispatch前面。另外,complete这个方法主要用在客户端中,在客户端发送请求之后,在complete方法之后的代码是会阻塞。
当Servlet 启用异步调用的线程之后,该线程的执行过程对开发者是透明的。但在有些情况下,开发者需要了解该异步线程的执行细节,并针对特定的执行结果进行针对性处理,这可借助于Servlet 3.0提供的异步监听器来实现。
public class AsyncListener implements javax.servlet.AsyncListener {
@Override
public void onComplete(AsyncEvent event) throws IOException {
// TODO Auto-generated method stub
System.out.println("这是异步servlet执行完成");
}
@Override
public void onError(AsyncEvent event) throws IOException {
// TODO Auto-generated method stub
System.out.println("有错误");
}
@Override
public void onStartAsync(AsyncEvent event) throws IOException {
// TODO Auto-generated method stub
System.out.println("这是异步servlet开始");
}
@Override
public void onTimeout(AsyncEvent event) throws IOException {
// TODO Auto-generated method stub
System.out.println("这是异步servlet超时");
}
}
as.addListener(new AsyncListener());//注册监听器
在上述例子中,需要注意的就是监听异步开始的,好像不起作用,网上给的说法是实际在注册的时候就一步开始,这个不必深究。
Part getPart(String name): 根据名称来获取文件上传域。
涉及一个API: Part,每个Part 对象对应于一个文件上传域,该对象提供了大量方法来访问上传文件的文件类型、大小、输入流等,并提供了一个write(String file)方法将上传文件写入服务器磁盘。
@WebServlet(name="com.example.servlet.FileServlet",urlPatterns="/FileServlet")
@MultipartConfig
public class FileServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// TODO Auto-generated method stub
super.doGet(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// TODO Auto-generated method stub
resp.setCharacterEncoding("utf-8");
resp.setContentType("text/html");
PrintWriter pw = resp.getWriter();
String filename = req.getParameter("filename");
pw.write("文件名字:"+filename+"\n");
Part p = req.getPart("file");//通过文件域的name属性来获取part对象
String filetype = p.getContentType();
pw.write("文件类型:"+filetype+"\n");
Collection headers = p.getHeaderNames();
for(String headername :headers){
pw.write("文件头信息:"+headername+"\n");
}
pw.write(getServletContext().getRealPath("upload")+"/"+filename);
}
}
表单的enctype 属性指定的是表单数据的编码方式,该属性有如下三个值:
ServletContext 则提供了如下方法来动态地注册Servet、Filter。
之前已经具体讲过了,动态注册,就是在使用的时候才注册,灵活性好。