首页
论坛
图书
开发资料
在线文档
网址
下载
联系我们
 新闻│Java│JavaScript│Eclipse│Eclipse 英文│J2EE│J2ME│J2SE│JSP│Netbeans│Hibernate│JBuilder│Spring│Struts
站内搜索: 请输入搜索关键词

当前页面: 开发资料首页 → Java 专题 → Servlet 2.3过滤器编程

Servlet 2.3过滤器编程

摘要: Servlet 2.3过滤器编程

</td> </tr> <tr> <td height="35" align="left" valign="top" class="ArticleTeitle">

Servlet 2.3过滤器编程

寻找今天你能使用的servlet过滤器

<table width="677" border="0"> <tr> <td width="265"> </td> <td width="402">

摘要

Jason Hunter通过对一些自由而又实用的过滤器的研究以对新的servlet过滤器模型进行深入探讨。你将知道这些过滤器是如何工作以及你能用他们做什么。最后,Jason介绍了他自己为简化文件上传而做的多路请求过滤器。

在"Servlet 2.3: New Features Exposed,"中,我介绍了Servlet API 2.3中的变化并给出了一个简单的servlet过滤器模型。在随后的文章中,我将对servlet过滤器进行深入的挖掘,而你看到的这些servlet过滤器都是能从Web上免费下载的。对每一个过滤器,我将检视它是做什么的,如何工作的,以及你能从哪里得到它。

你可以在两种情况下使用本文:学习过滤器的功用,或者作为你写过滤器时的辅助。我将从几个简单的例子开始然后继续更多高级的过滤器。最后,我将向你介绍我为了支持多路请求而写的一个文件上传过滤器。

</td> </tr> </table>

Servlet 过滤器

也许你还不熟悉情况,一个过滤器是一个可以传送请求或修改响应的对象。过滤器并不是servlet,他们并不实际创建一个请求。他们是请求到达一个servlet前的预处理程序,和/或响应离开servlet后的后处理程序。就像你将在后面的例子中看到的,一个过滤器能够:

·在一个servlet被调用前截获该调用

·在一个servlet被调用前检查请求

·修改在实际请求中提供了可定制请求对象的请求头和请求数据

·修改在实际响应中提供了可定制响应对象的响应头和响应数据

·在一个servlet被调用之后截获该调用

你可以一个过滤器以作用于一个或一组servlet,零个或多个过滤器能过滤一个或多个servlet。一个过滤器实现java.servlet.Filter接口并定义它的三个方法:

1. void init(FilterConfig config) throws ServletException:在过滤器执行service前被调用,以设置过滤器的配置对象。

2. void destroy();在过滤器执行service后被调用。

3. Void doFilter(ServletRequest req,ServletResponse res,FilterChain chain) throws IOException,ServletException;执行实际的过滤工作。

服务器调用一次init(FilterConfig)以为服务准备过滤器,然后在请求需要使用过滤器的任何时候调用doFilter()。FilterConfig接口检索过滤器名、初始化参数以及活动的servlet上下文。服务器调用destory()以指出过滤器已结束服务。过滤器的生命周期和servelt的生命周期非常相似 ——在Servlet API 2.3 最终发布稿2号 中最近改变的。先前得用setFilterConfig(FilterConfig)方法来设置生命周期。

在doFilter()方法中,每个过滤器都接受当前的请求和响应,而FilterChain包含的过滤器则仍然必须被处理。doFilter()方法中,过滤器可以对请求和响应做它想做的一切。(就如我将在后面讨论的那样,通过调用他们的方法收集数据,或者给对象添加新的行为。)过滤器调用

chain.doFilter()将控制权传送给下一个过滤器。当这个调用返回后,过滤器可以在它的doFilter()方法的最后对响应做些其他的工作;例如,它能记录响应的信息。如果过滤器想要终止请求的处理或或得对响应的完全控制,则他可以不调用下一个过滤器。

循序渐进

如果想要真正理解过滤器,则应该看它们在实际中的应用。我们将看到的第一个过滤器是简单而有用的,它记录了所有请求的持续时间。在Tomcat 4.0发布中被命名为ExampleFilter。代码如下:

import java.io.*;

import javax.servlet.*;

import javax.servlet.http.*;

public class TimerFilter implements Filter {

private FilterConfig config = null;

public void init(FilterConfig config) throws ServletException {

this.config = config;

}

public void destroy() {

config = null;

}

public void doFilter(ServletRequest request, ServletResponse response,

FilterChain chain) throws IOException, ServletException {

long before = System.currentTimeMillis();

chain.doFilter(request, response);

long after = System.currentTimeMillis();

String name = "";

if (request instanceof HttpServletRequest) {

name = ((HttpServletRequest)request).getRequestURI();

}

config.getServletContext().log(name + ": " + (after - before) + "ms");

}

}

当服务器调用init()时,过滤器用config变量来保存配置类的引用,这将在后面的doFilter()方法中被使用以更改ServletContext。当调用doFilter()时,过滤器计算请求发生到该请求执行完毕之间的时间。该过滤器很好的演示了请求之前和之后的处理。注意doFilter()方法的参数并不是HTTP对象,因此要调用HTTP专用的getRequestURI()方法时必须将request转化为HttpServletRequest类型。

使用此过滤器,你还必须在web.xml文件中用标签部署它,见下:

timerFilter

TimerFilter

这将通知服务器一个叫timerFiter的过滤器是从TimerFiter类实现的。你可以使用确定的URL模式或使用标签命名的servelt 来注册一个过滤器,如:

timerFilter

/*

这种配置使过滤器操作所有对服务器的请求(静态或动态),正是我们需要的计时过滤器。如果你连接一个简单的页面,记录输出可能如下:

2001-05-25 00:14:11 /timer/index.html: 10ms

在Tomcat 4.0 beta 5中,你可以在server_root/logs/下找到该记录文件。

此过滤器的WAR文件从此下载:

http://www.javaworld.com/jw-06-2001/Filters/timer.war

谁在你的网站上?他们在做什么?

我们下一个过滤器是由OpenSymphony成员写的clickstream过滤器。这个过滤器跟踪用户请求(比如:点击)和请求队列(比如:点击流)以向网络管理员显示谁在她的网站上以及每个用户正在访问那个页面。这是个使用LGPL的开源库。

在clickstream包中你将发现一个捕获请求信息的ClickstreamFilter类,一个像操作结构一样的Clickstream类以保存数据,以及一个保存会话和上下文事件的ClickstreamLogger类以将所有东西组合在一起。还有个BotChecker类用来确定客户端是否是一个机器人(简单的逻辑,像“他们是否是从robots.txt来的请求?”)。该包中提供了一个clickstreams.jsp摘要页面和一个viewstream.jsp详细页面来查看数据。

我们先看ClickstreamFilter类。所有的这些例子都做了些轻微的修改以格式化并修改了些可移植性问题,这我将在后面讲到。

import java.io.IOException;

import javax.servlet.*;

import javax.servlet.http.*;

public class ClickstreamFilter implements Filter {

protected FilterConfig filterConfig;

private final static String FILTER_APPLIED = "_clickstream_filter_applied";

public void init(FilterConfig config) throws ServletException {

this.filterConfig = filterConfig;

}

public void doFilter(ServletRequest request, ServletResponse response,

FilterChain chain) throws IOException, ServletException {

// 确保该过滤器在每次请求中只被使用一次

if (request.getAttribute(FILTER_APPLIED) == null) {

request.setAttribute(FILTER_APPLIED, Boolean.TRUE);

HttpSession session = ((HttpServletRequest)request).getSession();

Clickstream stream = (Clickstream)session.getAttribute("clickstream");

stream.addRequest(((HttpServletRequest)request));
}

// 传递请求

chain.doFilter(request, response);

}

public void destroy() { }

}

doFilter()方法取得用户的session,从中获取Clickstream,并将当前请求数据加到Clickstream中。其中使用了一个特殊的FILTER_APPLIED标记属性来标注此过滤器是否已经被当前请求使用(可能会在请求调度中发生)并且忽略所有其他的过滤器行为。你可能疑惑过滤器是怎么知道当前session中有clickstream属性。那是因为ClickstreamLogger在会话一开始时就已经设置了它。ClickstreamLogger代码:

import java.util.*;

import javax.servlet.*;

import javax.servlet.http.*;

public class ClickstreamLogger implements ServletContextListener,

HttpSessionListener {

Map clickstreams = new HashMap();

public ClickstreamLogger() { }

public void contextInitialized(ServletContextEvent sce) {

sce.getServletContext().setAttribute("clickstreams", clickstreams);

}

public void contextDestroyed(ServletContextEvent sce) {

sce.getServletContext().setAttribute("clickstreams", null);

}

public void sessionCreated(HttpSessionEvent hse) {

HttpSession session = hse.getSession();

Clickstream clickstream = new Clickstream();

session.setAttribute("clickstream", clickstream);

clickstreams.put(session.getId(), clickstream);

}

public void sessionDestroyed(HttpSessionEvent hse) {

HttpSession session = hse.getSession();

Clickstream stream = (Clickstream)session.getAttribute("clickstream");

clickstreams.remove(session.getId());

}

}

logger(记录器)获取应用事件并将使用他们将所有东西帮定在一起。当context创建中,logger在context中放置了一个共享的流map。这使得clickstream.jsp页面知道当前活动的是哪个流。而在context销毁中,logger则移除此map。当一个新访问者创建一个新的会话时,logger将一个新的Clickstream实例放入此会话中并将此Clickstream加入到中心流map中。在会话销毁时,由logger从中心map中移除这个流。

下面的web.xml部署描述片段将所有东西写在一块:

clickstreamFilter

ClickstreamFilter

clickstreamFilter

*.jsp

clickstreamFilter

*.html

ClickstreamLogger

这注册了ClickstreamFilter并设置其处理*.jsp和*.html来的请求。这也将ClickstreamLogger注册为一个监听器以在应用事件发生时接受他们。

两个JSP页面从会话中取clickstream数据和context对象并使用HTML界面来显示当前状态。下面的clickstream.jsp文件显示了个大概:

<%@ page import="java.util.*" %>

<%@ page import="Clickstream" %>

<%

Map clickstreams = (Map)application.getAttribute("clickstreams");

String showbots = "false";

if (request.getParameter("showbots") != null) {

if (request.getParameter("showbots").equals("true"))

showbots = "true";

else if (request.getParameter("showbots").equals("both"))

showbots = "both";

}

%>

All Clickstreams

No Bots |

All Bots |

Both

<% if (clickstreams.keySet().size() == 0) { %>

No clickstreams in progress

<% } %>

<%

Iterator it = clickstreams.keySet().iterator();

int count = 0;

while (it.hasNext()) {

String key = (String)it.next();

Clickstream stream = (Clickstream)clickstreams.get(key);

if (showbots.equals("false") && stream.isBot()) {

continue;

}

else if (showbots.equals("true") && !stream.isBot()) {

continue;

}

count++;

try {

%>

<%= count %>.

">

<%= (stream.getHostname() != null && !stream.getHostname().equals("") ?

stream.getHostname() : "Stream") %>

[<%= stream.getStream().size() %> reqs]

<%

}

catch (Exception e) {

%>

An error occurred - <%= e %>

<%

}

}

%>

这个包很容易从OpenSymphony下载并安装。将Java文件编译并放在

WEB-INF/classes下,将JSP文件放到Web应用路径下,按帮助修改web.xml文件。为防止在这些工作前的争论,你可以从

http://www.javaworld.com/jw-06-2001/Filters/clickstream.war处找到打好包的WAR文件。

为能让此过滤器能在Tomcat 4.0 beta 5下工作,我发现我不得不做一些轻微的改动。我做的改动显示了一些在servlet和过滤器的可移植性中通常容易犯的错误,所以我将他们列在下面:

·我不得不将在JSP中添加一个额外的导入语句:<%@ page import=”Clickstream” %>。在Java中你并不需要导入在同一包下的类,而在服务器上JSP被编译到默认包中,你并不需要这句导入行。但在像Tomcat这样的服务器上,JSP被编译到一个自定义的包中,你不得不明确地从默认包中导入类。

·我不得不将元素移动到web.xml文件中的和元素之后,就像部署描述DTD要求的那样。并不是所有服务器对元素都要求固定的顺序。但Tomcat必须要。

·我不得不将web.xml中的映射由/*.html和/*.jsp改成正确的*.html和*.jsp。一些服务器会忽略开头的/,但Tomcat强硬的规定开头不能有/。

·最后,我得将ClickstreamFilter类升级到最新的生命周期API,将setFilterConfig()改成新的init()和destory()方法。

可下载的WAR文件已经包含了这些修改并能通过服务器在包外运行,虽然我并没有广泛的进行测试。

压缩响应

第三个过滤器是自动压缩响应输出流,以提高带宽利用率并提供一个很好的包装响应对象的示例。这个过滤器是由来自SUN的Amy Roh编写的,他为Tomcat 4.0 的“examples”Web程序做出过贡献。你将从webapps/examples/WEB-INF/classes/compressionFilters下找到原始代码。这里的例子代码以及WAR下的都已经为了更清晰和更简单而编辑过了。

CompressionFilter类的策略是检查请求头以判定客户端是否支持压缩,如果支持,则将响应对象用自定义的响应来打包,它的getOutputStream()和getWriter()方法已经被定义为可以利用压缩过的输出流。使用过滤器允许如此简单而有效的解决问题。

我们将从init()开始看代码:

public void init(FilterConfig filterConfig) {

config = filterConfig;

compressionThreshold = 0;

if (filterConfig != null) {

String str = filterConfig.getInitParameter("compressionThreshold");

if (str != null) {

compressionThreshold = Integer.parseInt(str);

}

else {

compressionThreshold = 0;

}

}

}

注意在检索请求头前必须把request转化为HttpServletRequest,就想在第一个例子里那样。过滤器使用wrapper类CompressResponseWrapper,一个从

HttpServletResponseWrapper类继承下来的自定义类。这个wrapper的代码相对比较简单:

public class CompressionResponseWrapper extends HttpServletResponseWrapper {

protected ServletOutputStream stream = null;

protected PrintWriter writer = null;

protected int threshold = 0;

protected HttpServletResponse origResponse = null;

public CompressionResponseWrapper(HttpServletResponse response) {

super(response);

origResponse = response;

}

public void setCompressionThreshold(int threshold) {

this.threshold = threshold;

}

public ServletOutputStream createOutputStream() throws IOException {

return (new CompressionResponseStream(origResponse));

}

public ServletOutputStream getOutputStream() throws IOException {

if (writer != null) {

throw new IllegalStateException("getWriter() has already been " +

"called for this response");

}

if (stream == null) {

stream = createOutputStream();

}

((CompressionResponseStream) stream).setCommit(true);

((CompressionResponseStream) stream).setBuffer(threshold);

return stream;

}

public PrintWriter getWriter() throws IOException {

if (writer != null) {

return writer;

}

if (stream != null) {

throw new IllegalStateException("getOutputStream() has already " +

"been called for this response");

}

stream = createOutputStream();

((CompressionResponseStream) stream).setCommit(true);

((CompressionResponseStream) stream).setBuffer(threshold);

writer = new PrintWriter(stream);

return writer;

}

}

所有调用getOutputStream() 或者getWriter()都返回一个使用

CompressResponseStream类的对象。CompressionResponseStrteam类没有显示在这个例子中,因为它继承于ServletOutputStream并使用java.util.zip.GZIPOutputStream类来压缩流。

Tomcat的”examples”Web程序中已经预先配置了这个压缩过滤器并加载了一个示例servlet。示例servlet响应/CompressionTestURL(确定先前的路径是/examples)。使用我制作的有用的WAR文件,你可以用/servlet/compressionTest(再次提醒,别忘了适当的前导路径)访问此测试servlet。你可以使用如下的web.xml片段来配置这个测试:

compressionFilter

CompressionFilter

compressionThreshold

10

compressionFilter

compressionTest

compressionTest

CompressionTestServlet

CompressionTestServlet(没有在这里列出)输出压缩是否可用,如果可用,则输出压缩响应成功!

function TempSave(ElementID) { CommentsPersistDiv.setAttribute("CommentContent",document.getElementById(ElementID).value); CommentsPersistDiv.save("CommentXMLStore"); } function Restore(ElementID) { CommentsPersistDiv.load("CommentXMLStore"); document.getElementById(ElementID).value=CommentsPersistDiv.getAttribute("CommentContent"); }

文件上传过滤器

我们将看到的最后一个过滤器是处理多路/多类型数据的POST请求,该类型的请求能包含文件上传。每个多路/多类型数据POST请求包含所有参数和文件,使用一种servlet不能识别的特别的格式。历史上Servlet开发者使用第三方类来处理上传,例如在我的com.oreilly.servlet包中的MultipartRequest和MultipartParser类。这里我们将看到一种使用MultipartFilter的新方法来是处理这种请求更容易。该过滤器基于com.oreilly.servlet包下的parsers构建并已经被集成到该包中(参见Resources)。

MultipartFilter工作与观察输入的请求,当它发现一个文件上传请求时(content type:multipart/form-data),过滤器使用一个知道如何分析这种特殊content type格式的特殊请求包来将请求对象进行包装。servlet获取此特殊请求包并通过标准的getParameter()方法来无缝地访问此multipart参数,因为这个wrapper中已经重新定义了这些方法的功能。此servelt能够通过将requset转换成wrapper类型并使用wrapper中附加的getFile()方法来处理文件上传。

过滤器代码:

package com.oreilly.servlet;

import java.io.*;

import javax.servlet.*;

import javax.servlet.http.*;

public class MultipartFilter implements Filter {

private FilterConfig config = null;

private String dir = null;

public void init(FilterConfig config) throws ServletException {

this.config = config;

// Determine the upload directory. First look for an uploadDir filter

// init parameter. Then look for the context tempdir.

dir = config.getInitParameter("uploadDir");

if (dir == null) {

File tempdir = (File) config.getServletContext()

.getAttribute("javax.servlet.context.tempdir");

if (tempdir != null) {

dir = tempdir.toString();

}

else {

throw new ServletException(

"MultipartFilter: No upload directory found: set an uploadDir " +

"init parameter or ensure the javax.servlet.context.tempdir " +

"directory is valid");

}

}

}

public void destroy() {

config = null;

}

public void doFilter(ServletRequest request, ServletResponse response,

FilterChain chain) throws IOException, ServletException {

HttpServletRequest req = (HttpServletRequest) request;

String type = req.getHeader("Content-Type");

// If this is not a multipart/form-data request continue

if (type == null || !type.startsWith("multipart/form-data")) {

chain.doFilter(request, response);

}

else {

MultipartWrapper multi = new MultipartWrapper(req, dir);

chain.doFilter(multi, response);

}

}

}

init()方法确定文件上传的路径。这是multipart parser放置文件的地方,因此实际的请求并不需要驻留在内存中。它先查找uploadDir过滤器初始化参数,如果没找到,则使用默认的tempdir目录——Servlet API 2.2中的标准context属性。

doFilter()方法检查请求的content type,如果是multipart/form-data请求,则将此请求用MultipartWrapper打包。wrapper代码如下:

package com.oreilly.servlet;

import java.io.*;

import java.util.*;

import javax.servlet.*;

import javax.servlet.http.*;

public class MultipartWrapper extends HttpServletRequestWrapper {

MultipartRequest mreq = null;

public MultipartWrapper(HttpServletRequest req, String dir)

throws IOException {

super(req);

mreq = new MultipartRequest(req, dir);

}

// Methods to replace HSR methods

public Enumeration getParameterNames() {

return mreq.getParameterNames();

}

public String getParameter(String name) {

return mreq.getParameter(name);

}

public String[] getParameterValues(String name) {

return mreq.getParameterValues(name);

}

public Map getParameterMap() {

Map map = new HashMap();

Enumeration enum = getParameterNames();

while (enum.hasMoreElements()) {

String name = (String) enum.nextElement();

map.put(name, mreq.getParameterValues(name));

}

return map;

}

// Methods only in MultipartRequest

public Enumeration getFileNames() {

return mreq.getFileNames();

}

public String getFilesystemName(String name) {

return mreq.getFilesystemName(name);

}

public String getContentType(String name) {

return mreq.getContentType(name);

}

public File getFile(String name) {

return mreq.getFile(name);

}

}

wrapper构造了一个com.oreilly.servlet.MultipartRequest对象以处理上传分析并重载getParameter()方法族以使用MultipartRequest取代未加工的请求来读取参数值。wrapper也定义了不同的getFile()方法以使一个servlet接收打包过的请求以能通过调用其他方法来处理上传的文件。

Web.xml部署描述器用如下代码来添加此过滤器:

multipartFilter

com.oreilly.servlet.MultipartFilter

uploadDir

/tmp

multipartFilter

/*

uploadTest

UploadTest

uploadTest

/uploadTest

UploadText servlet如下:

import java.io.*;

import java.util.*;

import javax.servlet.*;

import javax.servlet.http.*;

import com.oreilly.servlet.*;

public class UploadTest extends HttpServlet {

public void doPost(HttpServletRequest req, HttpServletResponse res)

throws ServletException, IOException {

res.setContentType("text/html");

PrintWriter out = res.getWriter();

out.println("");

out.println("<head>UploadTest</head>");

out.println("<body>");

out.println("

UploadTest

");

// Parameters can now be read the same way for both

// application/x-www-form-urlencoded and multipart/form-data requests!

out.println("

Request Parameters:

");

Enumeration enum = req.getParameterNames();

while (enum.hasMoreElements()) {

String name = (String) enum.nextElement();

String values[] = req.getParameterValues(name);

if (values != null) {

for (int i = 0; i < values.length; i++) {

out.println(name + " (" + i + "): " + values[i]);

}

}

}

out.println("

");

// Files can be read if the request class is MultipartWrapper

// Init params to MultipartWrapper control the upload handling

if (req instanceof MultipartWrapper) {

try {

// Cast the request to a MultipartWrapper

MultipartWrapper multi = (MultipartWrapper) req;

// Show which files we received

out.println("

Files:

");

out.println("

");

Enumeration files = multi.getFileNames();

while (files.hasMoreElements()) {

String name = (String)files.nextElement();

String filename = multi.getFilesystemName(name);

String type = multi.getContentType(name);

File f = multi.getFile(name);

out.println("name: " + name);

out.println("filename: " + filename);

out.println("type: " + type);

if (f != null) {

out.println("length: " + f.length());

}

out.println();

}

out.println("

");

}

catch (Exception e) {

out.println("

");

e.printStackTrace(out);

out.println("

");

}

}

out.println("</body>");

}

}

servlet的前一半显示了过滤器如何将参数数据毫无改变的传送给接收的servlet。后半部分则显示了一个servlet是如何将请求向下传送给MultipartWrapper以获得附加的文件访问方法。

一个驱动此servlet的HTML例子如下:

<form ACTION="uploadTest" ENCTYPE="multipart/form-data" METHOD=POST>

What is your name? <input TYPE=TEXT NAME=submitter>

What is your age? <input TYPE=TEXT NAME=age>

Which file do you want to upload? <input TYPE=FILE NAME=file1>

Any other file to upload? <input TYPE=FILE NAME=file2>

<input TYPE=SUBMIT>

</form>

这是可能的输出:

UploadTest

Request Parameters:

submitter (0): Jason

age (0): 28

Files:

name: file1

filename: 4008b21.tif

type: application/octet-stream

length: 39396

name: file2

filename: null

type: null

你们也许会疑惑我们如何确信由过滤器设置的MultipartWrapper能正确的传送给接下来的servlet。在2号发布稿规范和Tomcat 4.0 beta 5中,那是不确定的。实际上,如果你试图用/servlet/UploadTest去访问此servlet,你将注意到过滤并没有正确工作,因为/servlet的调用将MultipartWrapper转给Tomcat自己的特殊wrapper了。这允许参数能被正确解析,但文件访问方法却不能正确工作。在Servlet API专家组的讨论中,我们决定servlet容器不能对过滤器的wrapper做更多的包装。这个servlet规范将被修改的更清楚。在Tomcat 4.0以后版本中将澄清这些规则。短期的办法是在请求wrapper中使用getRequest()方法来“沿着”请求去找到这个隐藏的multipart wrapper。

从如下地址下载该WAR文件:

http://www.javaworld.com/jw-06-2001/Filters/multipart.war

过滤器能力

Servlet过滤器提供了一中强大的能力来控制请求的操作和响应的发生,提供新的servlet功能而不需要太多的代码。我希望通过这些已经向你展示使用过滤器的可能情况,并教给你一些关于如何更有效的使用新的过滤器机能的技巧。

感谢这些过滤器的作者以及其他对过滤器提供有用建议的的人:Amy Roh,Criag McClanahan,Serge Knystautas,以及OpenSymphony成员。

</td> </tr> <tr>


↑返回目录
前一篇: 一个简单的投票程序
后一篇: 在Servlet中使用两种输出机制

首页 | 全站 Sitemap | 联系我们 | 设为首页 | 收藏本站
版权所有 Copyright © 2006-2007, Java 编程资料牛鼻站, All rights reserved