打印

[jsp] 用TOMCAT作简单的jsp web开发

把一些朋友的建议提到楼顶,要看帖子的话先看看,避免误导了大家。
vkuang的话:
复制内容到剪贴板
代码:
楼主很卖力,但是jsp不是这样学的...
楼主很卖力,但是jsp不是这样学的.最少先有j2ee基础吧..
这样写出来的程序和asp php有什么区别呢?一样的无头绪,开发速度慢.
jsp只是用来做表现的,可以说和html区别不大,真正的业务逻辑很少会在jsp里出现(某些大型项目的部分地方会才需要这么搞复杂)
目录:
复制内容到剪贴板
代码:
开发环境配置 -----------------------------------------------------------------------1楼
IDE eclipse的安装-------------------------------------------------------------------2楼
第二个jsp文件------------------------------------------------------------------------7楼
Get/Post方法-----------------------------------------------------------------------10楼
JSP中实现文件上传---------------------------------------------------------------13楼
JSP中实现文件上传2(Eclipse得简单使用)--------------------------------14楼
jsp页面上传完成版总结-----------------------------------------------------------15楼
Application,session变量-----------------------------------------------------------16楼
request,response对象简介-------------------------------------------------------17楼
通过response对象实现文件下载--------------------------------------------------18楼
Cookie操作的例子。
JSP中实现文件下载-----------------------------------------------------------------19楼
数据库连接----------------------------------------------------------------------------20楼
连接池的概念与简单实现----------------------------------------------------------22楼
从最简单的留言本开始-1----------------------------------------------------------26楼
-开发环境配置
所在的部门做的是VB + Oralce的erp开发,Java方面的知识已经忘得差不多了,最近部门的项目不是很紧,准备写点东西,在win操作系统下,从环境配置开始,一点一点进行java方面的web开发。希望能对大家有点帮助。
       许多人做java的web开发还是在windows操作系统下,而要做开发的第一步,就是配置开发环境。Java的开发环境,基本的就是java虚拟机和web服务器,当然,数据库服务器环境也是比较重要的一个环节,但是由于开发时所用的数据库有很多,比如Oracle,MySQL,SQLServer,Access等等,而且对于web开发者来说,只要数据库的服务存在,提供了数据库链接的基本信息(url,username,password,driverClass),利用JDK或者第三方提供的JDBC驱动类的包,很容易链接到数据库。所以现在所说的java web开发环境,主要针对java虚拟机和web服务器来说。而在开发过程中,许多开发者喜欢用的web服务器是tomcat,所以就简单讲讲JDK和TOMCAT的安装配置。
       而开发所用的IDE,只能用我经常用的Eclipse为例了。
       JDK的安装,版本选择。
       JDK有很多版本,J2SE 1.3.1 ,1.4.2 ,1.5 ,1.6。。。下载JDK安装文件,请到sun公司的网站进行下载。
比如,现在JDK1.6的下载页面为http://java.sun.com/javase/downloads/index.jsp
而以前的版本的下载页面为http://java.sun.com/javase/downloads/previous.jsp
这里选择1.4.2版本的JDK。
注:
       开发过程中,JDK版本的选择,如果是由你决定,那么可以根据个人的喜欢下载相应的版本就可以了。同时要注意所选择的web服务器对JDK版本的要求。如果项目统一要求,或者以前已经有了开发好的项目,那么就用指定的版本的JDK。毕竟,不同版本的JDK中类和包的结构可能存在一些差别。
       这里,选择1.4.2版本的JDK。
       在下载页面,(http://java.sun.com/j2se/1.4.2/download.html)可以看到提供了JDK,JRE以及JDK文档的下载。JRE是给操作系统提供Java运行环境,比如IE中的Applet程序的运行支持。开发者要用JDK,JDK安装后,自带JRE。
       PS>建议下栽JDK安装文件的同时下栽JDK文档,这是开发过程中最好的帮手,可以查到各种类,方法的说明和参数,以及可能抛出的异常等基本信息。如果要做Java WEB开发,这个帮助文档是不应该缺少的。
       安装JDK以及JDK版本的管理。
       JDK有不同的版本,开发过程中不同的项目使用的JDK版本可能不同。所以建议安装的时候把JDK安装到某个目录下,并给每个JDK的文件夹用JDK版本命名,同时建议不要安装在系统盘上,安装到别的盘下,避免重装系统等情况下对JDK有影响。(重装系统后原有的JDK能否使用没有确认。)比如,我的JDK都安装在E盘JDK目录下,已有的JDK是1.6版本的和1.3.1_12版本,现在安装的时候,就在同级目录下建了1.4.2_13版本的目录。

设定好安装目录后,开始进行安装,安装目录只是个人喜好,对JDK没有影响。安装完成后,重启,设定生效。重启后,在开始-运行-进入cmd窗口,输入 java –version,正常的情况下会打印出你JDK的版本信息。表明安装成功。

PS>安装了多个版本的JDK,如何更改当前系统使用的JDK?
比如我的机器现在有的JDK分别为1.3.1_12,1.4.2_13,1.6三个版本。
安装的目录分别为:
       1.3.1_12                      C:\jdk1.3.1_12
       1.4.2_13                           E:\JDK\j2sdk1.4.2_13
       1.6                           E:\JDK\jdk1.6
如果我要用1.3.1_12版本的JDK,只要在“我的电脑”点右键进入系统属性界面,详细设定窗口点击环境变量,在系统环境变量中找到path的参数,在参数前输入你的JDK目录\bin;确定后,再打开一个新的cmd窗口,输入java –version,版本信息是不是变了?比如,我想用1.3.1_12版本的JDK,加上C:\jdk1.3.1_12\bin;后,再次查看版本信息,就变成如下的信息了。


注意要打开一个新的cmd窗口,原来的cmd窗口打印不出新的版本信息。

PS>有时候安装了某个程序或者服务后,会发现JDK版本被更改。比较常见的是Oracle9i安装后,发现JDK的版本信息变成了1.3.1的,这是因为安装Oracle过程中,Oracle自己安装了JDK。安装Oracle后,在“我的电脑”点右键进入系统属性界面,详细设定窗口点击环境变量,在系统环境变量中找到path的参数,在这里察看Oracle设定的参数,会发现几个Oracle安装目录下的jre参数,一般有1.3.1,1.1.8,这2个版本,把他们删掉后你自己的JDK就可以正常使用了。

TOMCAT的下栽及安装。
Tomcat要到http://tomcat.apache.org/网站上下载。可以选择不同的版本,选择的时候注意其对JDK版本的要求,如果tomcat对JDK版本有要求的时候,下栽目录会有提示。
在这里选择tomcat4.0.6,exe用的tomcat进行下栽安装。
安装的文件有zip的exe的等。2者都可以,个人感觉zip的解压了就可以用,要方便些。
解压完了,打开tomcat的目录,可以看到以下几个接触比较多的文件夹。Bin,common,里,conf,webapps,work。
Bin:这个文件夹下放着tomcat的批处理文件,tomcat的开始和关闭的批处理文件都在这里。Startup.bat,shutdown.bat
Common,lib:tomcat提供的jar包在这2个文件夹下,开发项目时设定classpath的时候,可以到这里找要用的jar包。
Conf:tomcat的配置文件,其中server.xml可能经常会用到,可以在里面进行配置新的应用,加载数据源等各种配置。
Webapps:正常情况下,用户自己配置的应用是放在这里的。当然,这不是绝对的,可以在刚才说的server.xml里进行设定,那么你的程序放在哪里都可以了。
Work:这个目录还是比较有用的,开发过程中写好的jsp文件,在tomcat中初次运行的时候,会被tomcat解释成java文件并编译成class文件放在这里。有时候jsp文件出异常的时候,报错的文件不是用户自己写的文件,而是和jsp文件名有些相似的java文件,那就到这里来找。
现在,到bin目录下点击startup.bat来启动tomcat服务。如果按照我上面介绍的步骤,可以发现tomcat启动不了。

这是因为有些参数我们还没有设定。
打开startup.bat文件。(rem是注释,就不需要去管了,摘出来会被运行的语句)
@echo off
if "%OS%" == "Windows_NT" setlocal
if not "%CATALINA_HOME%" == "" goto gotHome
set CATALINA_HOME=.
if exist "%CATALINA_HOME%\bin\catalina.bat" goto okHome
set CATALINA_HOME=..
:gotHome
if exist "%CATALINA_HOME%\bin\catalina.bat" goto okHome
echo The CATALINA_HOME environment variable is not defined correctly
echo This environment variable is needed to run this program
goto end
:okHome

set EXECUTABLE=%CATALINA_HOME%\bin\catalina.bat

if exist "%EXECUTABLE%" goto okExec
echo Cannot find %EXECUTABLE%
echo This file is needed to run this program
goto end
:okExec

set CMD_LINE_ARGS=
:setArgs
if ""%1""=="""" goto doneSetArgs
set CMD_LINE_ARGS=%CMD_LINE_ARGS% %1
shift
goto setArgs
:doneSetArgs

call "%EXECUTABLE%" start %CMD_LINE_ARGS%

:end
这种语言并不熟悉,不过可以大致看它的意思。在确定操作系统为windows后,判断CATALINA_HOME变量是否定义,如果没有定义,将当前的tomcat目录作为tomcat服务器的目录。如果定义的话,将定义的CATALINA_HOME作为tomcat服务器的目录。
执行catalina.bat批处理,在这个批处理里,我们看到这句话,JAVA_HOME       Must point at your Java Development Kit installation.,这个表明要运行tomcat,我们必须设定JAVA_HOME为某个安装了的JDK。所以,在“我的电脑”点右键进入系统属性界面,详细设定窗口点击环境变量,在系统环境变量中设定JAVA_HOME为自己的JDK目录。

设定完成后,再次点击startup.bat,正常情况下,应该看到如下界面。

表明tomcat服务已经正常启动。这时候打开IE,输入http://localhost:8080/,可以看到tomcat正常启动的画面。


这时候,tomcat安装已经正常完了。
PS>tomcat的默认端口是8080,这个可以在server.xml中进行配置,以后再作介绍。有时候如果别的服务占用了8080的端口,可以去更改tomcat的端口,来让tomcat可以正常运行。

安装好的tomcat的主页面中,有一些链接的信息还是有些用处的。
JSP,Servlet,WebDAV Examples这些,提供了一些简单的jsp和servlet,让你了解tomcat下web开发的一些最基本的方式。
Tomcat Documentation中提供了关于当前版本的Tomcat的各种资料,有些很详细,有些比较潦草。不过看看还是很有帮助的。
如果有兴趣,看看server.xml,web.xml那里边有许多被注释掉的sample。可以了解tomcat的各种配置等信息。

IDE:
我用的IDE是Eclipse,Eclipse的安装比较简单,暂时就不提了。

环境配置好了,我们不妨写出第一个jsp文件,来看看在tomcat下能否正常运行。
文件名:HelloWorld.jsp
文件内容:
复制内容到剪贴板
代码:
<%
String str = "Hello World";
out.print(str);
%>
很简单,定义一个字符串,将这个字符串打印出来。
把这个文件拷贝到tomcat下的webapps/examples/文件夹下。
然后在IE中输入以下地址
http://localhost:8080/examples/HelloWorld.jsp
正常的情况下将会看到打印出的字符串。


OK,第一个jsp程序成功运行。
之所以作这样一个页面,是要带出2个小问题点。
一,       JSP以及Java是严格区分大小写的,以前没做过jsp开发的朋友要注意这点。
比如,你输入http://localhost:8080/examples/helloworld.jsp,将无法访问到这个页面。
二,       成功运行后,我们到work目录下去找这个文件,在work\Standalone\localhost\examples目录下,可以看到一个HelloWorld$jsp.java和HelloWorld$jsp.class,这就是tomcat把我们写的jsp文件生成java文件,然后在编译成class文件。Tomcat运行的时候直接调用这个class文件。
打开,看一下源码。
复制内容到剪贴板
代码:
package org.apache.jsp;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import org.apache.jasper.runtime.*;
public class HelloWorld$jsp extends HttpJspBase {
    static {
    }
    public HelloWorld$jsp( ) {
    }
    private static boolean _jspx_inited = false;
    public final void _jspx_init() throws org.apache.jasper.runtime.JspException {
    }
    public void _jspService(HttpServletRequest request, HttpServletResponse  response)
        throws java.io.IOException, ServletException {
        JspFactory _jspxFactory = null;
        PageContext pageContext = null;
        HttpSession session = null;
        ServletContext application = null;
        ServletConfig config = null;
        JspWriter out = null;
        Object page = this;
        String  _value = null;
        try {
            if (_jspx_inited == false) {
                synchronized (this) {
                    if (_jspx_inited == false) {
                        _jspx_init();
                        _jspx_inited = true;
                    }
                }
            }
            _jspxFactory = JspFactory.getDefaultFactory();
            response.setContentType("text/html;ISO-8859-1");
            pageContext = _jspxFactory.getPageContext(this, request, response,
                        "", true, 8192, true);
            application = pageContext.getServletContext();
            config = pageContext.getServletConfig();
            session = pageContext.getSession();
            out = pageContext.getOut();
            // begin [file="/HelloWorld.jsp";from=(0,2);to=(3,0)]
               
                String str = "Hello World";
                out.print(str);
            // end
        } catch (Throwable t) {
            if (out != null && out.getBufferSize() != 0)
                out.clearBuffer();
            if (pageContext != null) pageContext.handlePageException(t);
        } finally {
            if (_jspxFactory != null) _jspxFactory.releasePageContext(pageContext);
        }
    }
}
有意思吧。做java开发,可以有点这样的想法,所有的程序,最终都可以看作给java虚拟机运行的一堆类文件。Jsp,开发中要用到的jar包,甚至是tomcat本身,所以,开发中出现问题的时候,其实就是你用错误的方法操作了一堆别人提供的类,所以,1,要知道游戏规则(语法规范),2,要知道你在跟谁玩,并且要了解跟你玩的对象(开发过程中要用到的各种资源),这2点都知道的话,出了问题也很容易找出来了。

[ 本帖最后由 SinNeR 于 2007-5-8 00:00 编辑 ]
附件: 您所在的用户组无法下载或查看附件,您需要注册/登录后才能查看!
本帖最近评分记录
  • 赛扬 威望 +10 已加入精华1的精品文章 2007-4-6 10:01
  • 5do8 威望 +4 原创内容,初学者很使用 2007-3-24 10:26
ForgotteN

IDE eclipse的安装

eclipse可以到http://www.eclipse.org/downloads/下载,拿版本3.2.2为例,简单介绍下安装过程。
下载后,解压到指定的目录,发现可以直接运行。初次运行的时候,会弹出窗口,提示设定eclipse的工作空间,选择选择一个工作空间,并勾上以后不再提示,这样以后这个对话创就不会弹出。这样,eclipse就安装完成了,以后会以eclipse为基础,介绍怎么样利用eclipse用tomcat做服务器,开发简单的jsp,servlet程序。
这里先说一点,在操作系统上存在多个版本的JDK的时候,eclipse怎样更改JDK版本。
拿3.2.2版本为例,打开window->preferences,(中文版的是 窗口->首选项),有eclipse的各种设置信息,其中java栏中有Installed JRE选项,在这里可以添加和更改当前安装的不同版本的JDK。

比如,现在eclipse识别的JDK是1.4.2_13,已经安装的1.4.2_11没有识别出来,在这里可以手动添加。点击ADD按钮,弹出如下界面,在jre name中填写版本信息,在JRE home directory中选中JDK安装的目录,Eclipse会自动读出可用的jar包和可用的javaDocs。点击OK后,该版本的JDK添加成功。然后可以选中某个你开发需要的JDK版本。
当然,在建立工程开发的过程中也可以更改JDK版本,这个以后再介绍。

到现在,tomcat下的jsp开发只是提了冰山一角。具体开发中还有许多知识。但是看了以上部分后,开发过程中就可以轻松的搭建环境,为开发做好准备工作。(这里没有说数据库的搭建,和第三方技术支持的搭建。因为这些知识本身已经不属于一个web程序员的范畴了,对于一个web开发的程序员来说,只要知道第三方控件的接口或者数据库连接的参数即可,至于数据库搭建的各种知识,则是DBA这类人的任务了。)
附件: 您所在的用户组无法下载或查看附件,您需要注册/登录后才能查看!
ForgotteN
恕我直言啊.....  java 不适合新人的.  
我属于全能全不能的... 流行的一些web程序  都写过一些代码.  我深有体会, java 是要比其它都难掌握的.... 原理,基本和.net 差不多,但它的领域太大....要掌握的东西太多.

就象你说的,写了这么多,也只是   "tomcat下的jsp开发只是提了冰山一角"

所以,我觉得,没必要在论坛中讲述这些具体的东西.何况此类文章,已经有很多了.

相反,类似

 提示:您可以先修改部分代码再运行
这些经验之谈,才是更好的方式.

当然,我没有别的意思.我还是赞赏版主的勤奋的

TOP

赞楼主,写得太好了

TOP

赞一下~

文章很有针对性,对于不会jsp、又想跑代码的同学来说很实用啊!

TOP

java 好比是在温暖的海里游泳,适合渴求学习的人们,

在游的过程中会遇到很多东西,比如说遇到了航母(比如:EJB),你可以到上面去逛逛,但是NCIS(一部美剧)里面提醒过在那上面逛可千万别迷路;

好不容易开始在航母上不迷路了,下去之后游不久又可能会遇上一游艇(Spring)虽然轻量一些,但完全转通也不容易。

虽然累一点,不过还是喜欢java,因为这里面有很多伟大的程序员,造着很多伟大的船,让你去参观,休息,甚至可以让你免费去驾驶。
never stop improving

TOP

用TOMCAT作简单的Java web开发-第二个jsp文件

题外话:
呵呵,感谢楼上的各位的鼓励,只是一点碎碎的入门,自己技术不是很强,有幸被红色黑客推荐为斑竹,有幸得到蓝色等管理者的信任,成为后台区的斑竹。经典藏龙卧虎,高手云集,技术单薄的我,只有尽力兢兢业业的管理下论坛,做些自己能做的事情。而公司不允许上url中有bbs,game,news,sexy等等关键字的网站,上了的话,会被关小黑屋的。所以,只有在每天下班回来,9,10点开始,上来看下论坛。被俺家领导严正警告,每次下班回来,眼里只有经典论坛。-_-b。主要是感觉白天不在论坛,所以晚上还是要拿些时间来到这里,白天的管理,这里向后台其他斑竹说声,辛苦你们了。
我是打算这个帖子写一小段时间的,从搭建环境开始,然后用这个环境,一步一步写出jsp文件,写出简单的Servlet,最后用Servlet写出一个简单的MVC结构的框架。力求最后这个Servlet每一步,每个小细节都能介绍到,达到最后能够对简单的Servlet开发有个简单的认识和理解的目的。同时,在介绍过程中,把自己有一些点点滴滴的经验,想法说出来,虽然不成熟,不够专业,不过一些小细节,对开发还是有一点点帮助的。如果有很多朋友关心此帖并且能得到帮助的话,有问题可以在这里发问,能够解决的话,最后达到一个小的汇总也是我的希望。
最后说句jsp web开发入门,因为这个问题在后台区好像是每隔一段时间就会出现一次。仁者见仁,智者见智,我也只是谈谈我的看法。
一,确立你的目标。
学jsp要达到什么程度,要能够写jsp页面,能够通过全部是jsp页面(无class)来做成一个网站?还是要学到servlet知识,能够搭建简单的基于MVC结构的servlet系统,能够快速的理解并使用已经成形的servlet框架。还是能够自己分析用户需求,根据用户需求选择合适的bs系统,搭建出合适的J2EE框架,能够熟练的使用各种J2EE技术。
给自己定好目标,才有前进的方向。对自己要求不同,付出的努力是不同的,实现目标所需要的时间和精力也不同,当然,得到的回报也是不同的。
如果刚入手的话,建议先从简单的jsp为基础的开发开始,当然,如果你有机会接触比较成熟的J2EE项目,那么,开始就向纵深发展也是可能的。这个因人而异,因环境而异。
我就曾经看到过一个比较知名的网站设计开发公司,他们给一个客户作的jsp网站里面全部是jsp文件,没有一个class文件。并不是说这样不好,但是看到一个所有的处理,数据库的连接,业务逻辑等等全部和HTML tag糅合在一起的jsp程序,确实感觉不是很爽。但是这样的网站仍然能够满足他们的客户的要求。在没有严重问题的情况下,能够满足客户的要求,应该也算一个成功的项目。但是换一个客户,换一套业务的时候,这堆jsp文件的移植性基本等于0。没有利用好jsp web开发的优点,这也算是败笔之处。
所以,要确立好自己的目标,戒骄戒躁,一步一步来实现自己的目的。想像学asp那样,在一个星期里写出1个留言本。1个月内利用已经成形的后台管理程序开发出一个自己的网站管理系统。3个月左右就能自己开发大部分简单的asp程序,还是有些困难。学jsp的话,就要定好目标,一步一个脚印,一点一点的积累自己的知识。
二,最好还是要有面向对象的基础和java的基础。
当然,刚开始学习的时候,并不一定需要这些。但是随着学习的深入,这些基础还是很有必要的。在论坛里有时候看到有些朋友问的问题,其实他们不是被困扰在jsp开发中,而是被某个类的用法所困扰。或者被某段代码的实现方法所困扰。
有了对java的整体的认识和理解,比如,数据类型,各种对象,以及面向对象在java中的体现等等,在看别人的程序和自己编写程序的时候,会有很大的帮助,并且很容易找到自己问题发生的场所并对应解决。
Java提供的那相对庞大的类库,有许多丰富多彩的类供我们使用,而且SUN在提供JDK的同时,也提供了JDK的帮助文件。那里对每个类,每个方法都有详尽的介绍。当然,它只简单的告诉你能做什么。但是并没有告诉你什么时候,该怎么样来用它。这些知识,就需要知识的积累和经验的积累。比如,java提供了许多容器类,数组,ArrayList,Vector,Hashtable等,他们有各自的特点,也有各自的优势,同时处理数据的时候,效率也不同。那么在使用的时候,就要根据自己要处理的数据的数据结构,要对数据作的操作,以及数据的数量,来选择合适的容器类来存储数据,使数据处理的时候,在效率上,实现难易上等方面达到一个最优的平衡。这,首先就要知道这些类的特点,然后,就是经验的积累了。
其实,我们并没有必要理解所有的JDK的类,这也是不现实的,在理解了一部分类,并对java开发有了一定的认识后,电脑里有一份JDK帮助文档就可以了。遇到不会用的类,不会用的方法是很正常的事情,这个时候就要查JDK帮助文档了。
如果愿意花一段时间学习java的话,可看的书有Thinking In java和Java核心思想这2本书。这2本书都是千页的大部头,而且有许多文字需要揣摩理解,变成自己的东西,不是1天2天能掌握的。需要时时看,时时理解。
三,对jsp开发要有个整体的概念。
刚使用一种语言的时候,最令人兴奋而且容易产生动力的就是能自己开发出一个能运行的程序。那样才会有成就感,是了,就是成就感,每个人都需要成就感的。所以许多人刚上来还是不太愿意闷头看Thinking In Java这样的书,确实,很有可能闷了半年,对java的理解并没有深入,最后连一个jsp程序都写不出来。而别人上来就学jsp的,可能已经在向你展示他做的系统了。所以,刚才说的二可以先无视。等你真的感觉到学习这些知识的必要性的时候,再回头看看吧。但是这样可能造成的一点就是,自己在开发过程中经常遇到使用java类的问题。
那么,就从jsp开始吧,jsp开始的话,就要理解jsp页面里各种tag的意义,了解你使用的web服务器,以及jsp程序运行的基本原理等。这方面,没看过什么书,曾经翻看过一个同事的孙卫琴的JSP,TOMCAT等一系列的书,感觉还可以吧。一点一点啃下来,还是能学到不少东西的。

总体上感觉,1和3是必要的,2在开始的时候可以自己选择。
还有一点就是,如果你在一个jsp的项目里,那么进步的速度绝对比自学要快很多的。自己可以得到锻炼,同时能学到别人解决问题的方法,从别人那里得到经验和帮助。
总之,让程序运行起来不是我们的最终目的,知道程序怎么运行才是我们的最终目的。

现在开始介绍第二个jsp文件。
在开发环境配置中已经做成了一个jsp文件并让它运行,但是我们是在tomcat下的webapps/sample文件夹下运行的这个jsp文件。怎么搭建我们自己的应用呢?或者说自己的web系统,自己的机能呢?首先,我们发现tomcat所运行的网站程序都放在webapps下(实际不是这样的,是可以设定的,下面马上就说到。)上次的那个jsp文件的路径是webapps/examples/HelloWorld.jsp,而访问的时候是http://localhost:8080/examples/HelloWorld.jsp。似乎我们只要在webapps下建立一个文件夹,再放进去这个jsp文件,就可以访问了,是不是这样呢?尝试一下看看。我们在webapps下建立一个文件夹myJSP,并把上次能够运行的jsp文件HelloWorld.jsp放到下面,启动tomcat,然后在IE中输入以下地址http://localhost:8080/myJSP/HelloWorld.jsp,结果发现报以下错误。

为什么呢?因为在tomcat的conf/server.xml中,配置设定了当前可用的网站应用。Tomcat在启动的时候,会先到这里读取各个配置好的网站应用,找到之后,服务器才会识别这些应用。(当然,如果你的网站应用已经配置好了web.xml,也没有必要在server.xml中配置设定,这个以后再提)现在我们就为自己的网站配置应用,打开conf/server.xml,找到</host>,在这个tag上面加上如下代码。
复制内容到剪贴板
代码:
<Context path="/JSP" docBase="myJSP" reloadable="true" />
Context有3个参数。
Path :服务器所识别的文件路径
docBase :文件所放置的位置(相对地址的话,tomcat会识别为webapps下的,如果是绝对路径,就是你本机的绝对路径了。所以说tomcat所运行的网站程序都放在webapps下是不对的)
reloadable :是否可以reload,一般情况下设定为true就可以。
这样设定完成后,重启tomcat(不重启的话,tomcat不会重新读取server.xml文件)。
访问http://localhost:8080/JSP/HelloWorld.jsp,发现可以正常访问了。
注意这个时候访问的URL中是JSP而不是myJSP,因为我们在context中设定的path是/JSP。
关于TOMCAT的启动。
复制内容到剪贴板
代码:
1.TOMCAT关闭的时候,可以点击shutdown.bat,或者直接在tomcat运行窗口按CTRL + C键就可以结束这个运行窗口。
2.TOMCAT下开发的程序有修改的时候,如果是jsp文件的话,不需要重新启动tomcat,直接刷新页面,jsp文件就会被重新编译。而其他的修改,比如xml,properties配置文件,以及java class的修改的时候,需要重新启动TOMCAT,不然这些修改不会被重新加载。
现在开始在myJSP这个文件夹下编写第二个jsp文件。
显示日期的类。名字为HelloDate.jsp
代码也很简单,如下:
复制内容到剪贴板
代码:
<%@ page    contentType="text/html;
charset=GBK"    
pageEncoding="GBK" %>
<html>
<head>
<title>Test DATE</title>
</head>
<body>
<h2><%=new java.util.Date().toString() %></h2>
</body>
</html>
编写完成,拷贝到刚才建立的myJSP文件夹下,不需要重启TOMCAT,访问http://localhost:8080/JSP/HelloDate.jsp可以看到如下画面。打印出了系统当前时间。

现在介绍下jsp下嵌入一部分java代码的基本样式。
Jsp中的编码主要通过<%%>包含。有一些基本的形式,跟asp比较相似。
Expression形式
复制内容到剪贴板
代码:
<%= ... %>
主要是用来输出java代码中的变量,上面程序中的<%=new java.util.Date().toString() %>其中的代码new java.util.Date().toString()其实也是返回一个String的变量。
Scriptlet(Java编码)
复制内容到剪贴板
代码:
<% ... %>
主要用来包含java的执行代码。比如:
<% int sum =0;
for(int i=0; i<10; i++) {
    sum += i;
}
%>
   
@Page指令的属性
复制内容到剪贴板
代码:
   <%@ page
     [ language="java" ]
     [ extends="package .class" ]
     [ import="{package .class | package.*}, ..." ]
     [ session="true|false" ]
     [ buffer="none|8kb|sizekb" ]
     [ autoFlush="true|false" ]
     [ isThreadSafe="true|false" ]
     [ info="text" ]
     [ errorPage="relativeURL" ]
     [ contentType="mimeType [ ;charset=characterSet ]" |
       "text/html ; charset=ISO-8859-1" ]
     [ isErrorPage="true|false" ]
   %>
    
上面为@page指令的常用属性,[]中括号表示其属性可设定也可不设定。像上面的jsp文件中的
复制内容到剪贴板
代码:
<%@ page    contentType="text/html;
charset=GBK"    
pageEncoding="GBK" %>
就设定了传输的数据的类型,编码方式,以及文件的编码方式。
开发中文简体bs系统,经常指定页面的编码方式为GBK或者gb2312的,日文bs系统的话,常用的编码方式是Shift_JIS,中文繁体应该是big5吧?
一些使用page指令的例子。
   <%@ page import="java.util.*, java.lang.*" %>
   <%@ page buffer="5kb" autoFlush="false" %>
   <%@ page errorPage="error.jsp" %>

Include 指令 (包含其他的文件)
复制内容到剪贴板
代码:
<%@ include file="relativeURL" %>
了解了以上的基本知识,我们修改一下这个jsp文件。
复制内容到剪贴板
代码:
<%@ page    contentType="text/html;
charset=GBK"    
pageEncoding="GBK" %>
<%@ page import="java.util.Date"%>
<html>
<head>
<title>Test DATE</title>
</head>
<body><%
    Date dt = new Date();
    out.println("<h2>" + dt + "</h2>");
%>
</body>
</html>
再次访问这个页面,显示正常。
至此,简单的jsp页面制作完成。这时候我们再回头看看TOMCAT将这个jsp文件生成的java文件。在\work\Standalone\localhost\JSP下找到这个java文件(为什么是JSP而不是myJSP呢?),HelloDate$jsp.java。
复制内容到剪贴板
代码:
package org.apache.jsp;
import java.util.Date;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import org.apache.jasper.runtime.*;
public class HelloDate$jsp extends HttpJspBase {
    static {
    }
    public HelloDate$jsp( ) {
    }
    private static boolean _jspx_inited = false;
    public final void _jspx_init() throws org.apache.jasper.runtime.JspException {
    }
    public void _jspService(HttpServletRequest request, HttpServletResponse  response)
        throws java.io.IOException, ServletException {
        JspFactory _jspxFactory = null;
        PageContext pageContext = null;
        HttpSession session = null;
        ServletContext application = null;
        ServletConfig config = null;
        JspWriter out = null;
        Object page = this;
        String  _value = null;
        try {
            if (_jspx_inited == false) {
                synchronized (this) {
                    if (_jspx_inited == false) {
                        _jspx_init();
                        _jspx_inited = true;
                    }
                }
            }
            _jspxFactory = JspFactory.getDefaultFactory();
           [color=Green] response.setContentType("text/html; \r\ncharset=GBK");[/color]
            pageContext = _jspxFactory.getPageContext(this, request, response,
                        "", true, 8192, true);
            application = pageContext.getServletContext();
            config = pageContext.getServletConfig();
            session = pageContext.getSession();
            out = pageContext.getOut();
            // HTML // begin [file="/HelloDate.jsp";from=(2,21);to=(3,0)]
                out.write("\r\n");
            // end
            // HTML // begin [file="/HelloDate.jsp";from=(3,34);to=(8,6)]
                out.write("\r\n<html>\r\n<head>\r\n<title>Test DATE</title>\r\n</head>\r\n<body>");
            // end
            // begin [file="/HelloDate.jsp";from=(8,8);to=(11,1)]
               
                    Date dt = new Date();
                    out.println("<h2>" + dt + "</h2>");
                 
            // end
            // HTML // begin [file="/HelloDate.jsp";from=(11,3);to=(14,0)]
                out.write("\r\n</body>\r\n</html>\r\n");
            // end
        } catch (Throwable t) {
            if (out != null && out.getBufferSize() != 0)
                out.clearBuffer();
            if (pageContext != null) pageContext.handlePageException(t);
        } finally {
            if (_jspxFactory != null) _jspxFactory.releasePageContext(pageContext);
        }
    }
}
和我们的源文件对应一下,发现<%@page import=”java.util.Date”%>被解释成import java.util.Date;在文件开始的位置被导入。
<%@ page       contentType="text/html;
charset=GBK"       
pageEncoding="GBK" %>
被解释成response.setContentType("text/html; \r\ncharset=GBK");
而jsp页面里的HTML代码被用out.print()方法输出。
真正写的那个java代码被直接搬过来。

看完了上面的页面后,会发现,我们写的jsp文件,最终都被解释成java文件。大家有兴趣不妨看看自己的jsp文件生成的java文件,或者写出各种tag,看看最终服务器会把他们解释成什么样子,会发现对象无处不在,你能够使用的东西,归根结底都是java的对象。所以说,如果有了面向对象的概念和java的基础,jsp开发起来会进步很快。
另,
            application = pageContext.getServletContext();
            config = pageContext.getServletConfig();
            session = pageContext.getSession();
            out = pageContext.getOut();
这4句也很有意思,它告诉了我们平时我们在jsp中用的session,out之类的对象是从哪里来的。以后有机会再介绍相关的知识吧。

另,大家如果看下上次帖子里那个很简单的jsp生成的java文件,会发现一些细节的不同。
比如response.setContentType("text/html; \r\ncharset=GBK");这句就不同,设定的编码方式不同,这是jsp开发中出现乱码的一个原因,在不指定编码方式的情况下,将用系统的默认编码方式,那么,中文乱码的问题就出现了。当然,中文乱码还有一些情况,以后有机会整理一下现象及对应的方法吧。

注:如果感觉看work目录下TOMCAT生成的java文件很烦,看不懂,可以不管这部分介绍,对开发没有影响。有兴趣的可以看看http://localhost:8080/index.html下的JSP Examples,代码很简单,主要是要理解它使用的技术。

[ 本帖最后由 SinNeR 于 2007-4-25 20:05 编辑 ]
附件: 您所在的用户组无法下载或查看附件,您需要注册/登录后才能查看!
ForgotteN

TOP

引用:
原帖由 warran 于 2007-3-24 06:39 发表
恕我直言啊.....  java 不适合新人的.  
我属于全能全不能的... 流行的一些web程序  都写过一些代码.  我深有体会, java 是要比其它都难掌握的.... 原理,基本和.net 差不多,但它的领域太大....要掌握的东西太多 ...
谢谢鼓励,呵呵,我也是觉得java入手不是那么快的事情。但是在经典总要作出点什么。
看到有的朋友问的jsp的问题,感觉还是经验不足,所以想写个类似汇总的东西,里边能把自己多少有一点的经验说出来,方便新手解决问题。因为有时候新手遇到问题的时候,会感觉无从下手。
以前写了个jsp debug的时候要注意的基本的东西。前一阵想写个帖把jsp web开发经常遇到的问题汇总,不过最近接触java少,许多以前遇到的问题向不起来了。
所以这次打算从环境配置开始,一点一点写学习java的过程。先是jsp,jsp一些基本技术,比如get,post方法,简单的数据库访问,容器的用法,怎么快速用别人提供的接口之类的。然后写点jsp可能会常用的class,重在分析是怎么写出来的,而不是提供源码。接着从最简单的servlet开始,介绍下数据库连接,简单的连接池,properties文件的读取,变量级别,简单的taglib,简单的MVC结构。最后是一套简单的servlet框架。其中穿插自己编程,遇到问题的解决方法以及注意事项。

想法是让新手从第一帖开始,一点一点,最后也能自己写出独立的class来,不过自己技术太差,可能理解的就有偏差,而且不一定有太多人对这样的帖子感兴趣。还希望能得到高手的帮助和指点,即时指出不足和错误的地方,我会快速对应的。
ForgotteN

TOP

怎么说了,其实很多东西都是在压力下学习和接受的,当然也要有兴趣。
曾经沧海难为水,除却巫山不是云.

TOP

用TOMCAT作简单的Java web开发-Get/Post方法

今天说说JSP页面之间传值。说起这个之前,说说jsp中request,response,session,out,application对象都是哪里来的。
在最初接触动态网站开发的时候,用 到request,session之类的对象的时候,总感觉很奇怪,不知道这些对象哪里来的,于是对服务器有一种很神秘的感觉,似乎服务器里做了许多非常人可以实现的不可思议的事情。
我们知道,java中,我们是这样使用对象的。比如有一个对象ObjectA,这个对象有setName(String str)方法和getName()方法(非静态方法)。如果要使用这个对象的这2个方法,我们必须初始化这个对象,然后才可以使用这些方法。
复制内容到剪贴板
代码:
    //初始化对象
    ObjectA objA = new ObjectA();
    //使用这个对象的方法。
    objA.setName(“SinNeR”);
    String sname = objA.getName();
另,我们所开发的程序,可以看成是就是在用别人提供的很成熟的类来实现我们的功能。(当然里边有很多的技术和知识)TOMCAT下,jsp页面中的代码最终被TOMCAT解释成了java的类,那么session,request这些东西有什么变化呢?实际上他们没有变化,看看昨天HelloDate.jsp生成的java文件。
我们会发现,我们写的jsp代码,都被生成到public void _jspService(HttpServletRequest request, HttpServletResponse  response)方法中,而在这个方法中在运行我们的代码之前,已经作了一些操作。
首先,request, response对象是作为参数传入的。
而application,config,session,out对象在方法开始被定义,在执行过程中从pageContent对象中取得。
所以说来,我们所用的applicaion,config,session,out,request,response对象,并不是很神秘的东西,它们也只是普通的对象,我们只要知道这些对象提供了什么方法,以及一些重要方法的用途就能够很好地运用它们。

现在简单的介绍get/post方法。
页面间传值有get和post方法之分,而它们代码的差别似乎只是form控件中的action属性不同,执行起来的现象也只是get方法会把参数显示在URL的后面,而post方法不会。
Get方法。
测试get方法很简单,我们需要一个有form控件的html页面和一个接受form控件提交的值的jsp页面。
一般情况下,Jsp页面中取得前页面传递的值方法是request对象的getParameter()方法。
其实,这里也就是要介绍这点,知道的朋友直接跳过即可。
先写一个有form表单的html文件,名字叫HelloGet.html
代码很简单,记得method设定为get,action为这个form提交的页面。Form中有2个input控件。
复制内容到剪贴板
代码:
<html>
<head>
<title>Test DATE</title>
<meta http-equiv="Content-Type" content="text/html; charset=GBK">
</head>
<body>
<form name="frm" method="get" action="HelloGet.jsp">
USERNAME : <input type="text" name="username" value="">
AGE : <input type="text" name="age" value="">
<input type="submit" name="submit" value="submit">
</form>
</body>
</html>
然后再写一个取得form提交的参数的jsp页面HelloGet.jsp(即刚才action指向的页面)。
复制内容到剪贴板
代码:
<%@ page     contentType="text/html; charset=GBK" %>
<html>
<head>
<title>get method</title>
</head>
<body>
<%
    request.setCharacterEncoding("GBK");
    String username = request.getParameter("username");
    String age      = request.getParameter("age");
    out.println("<h3> hello," + username + ". you are " + age + "</h3>");
%>
</body>
</html>
这里我们没有见过的代码只有request的2个方法。
request.setCharacterEncoding(String str)
request.getParameter(String str)
setCharacterEncoding()方法是设定取得request对象中的参数和数据的时候使用的编码方式。一般情况下,在中文系统下,设定了服务器的编码方式为GBK的时候(charset=GBK),是不需要设定这句的,但是我现在的系统是日文系统,如果不强制指定编码方式,传递中文参数的时候会出现乱码。
不妨用//注释掉这句话,再让程序执行看看。
getParameter(String str)是取得指定name参数所对应的值。这里,name就是form表单中input控件的名字。
在tomcat下,访问这个HTML页面,输入数据,点击submit按钮,正常的话会跳转到HelloGet.jsp页面,并打印出你提交的信息。如:



大家注意第2页的URL地址栏,会看到被传过来的各个参数,注意中文在传输过程中编码方式被转换过。
很简单吧,POST方法其实和get方法一样,只是提交的过程中在URL后面不会显示提交的参数。例如:
包含form表单的页面。HelloPost.html
复制内容到剪贴板
代码:
<html>
<head>
<title>Test DATE</title>
<meta http-equiv="Content-Type" content="text/html; charset=GBK">
</head>
<body>
<form name="frm" method="post" action="HelloGet.jsp">
USERNAME : <input type="text" name="username" value="">
AGE : <input type="text" name="age" value="">
<input type="submit" name="submit" value="submit">
</form>
</body>
</html>
接受提交的JSP页面仍然用HelloGet.jsp
访问这个HTML页面,输入数据,点击submit按钮,正常的话会跳转到HelloGet.jsp页面,并打印出你提交的信息。注意这时候地址栏中没有了提交的参数。


很简单,似乎就完了。但是有一点,就是提交的form中有上传文件的控件的时候,参数是怎么传递的呢?
首先,了解下文件上传的时候form表单的参数要求。
首先,method为post,form表单有个enctype参数,默认的情况下为application/x-www-form-urlencoded,文件上传时,要设定为multipart/form-data,form表单中肯定要有1个或n个type为file的input控件。我们先写出这个HTML页面。存为HelloPostFile.html
复制内容到剪贴板
代码:
<html>
<head>
<title>Test DATE</title>
<meta http-equiv="Content-Type" content="text/html; charset=GBK">
</head>
<body>
<form name="frm" method="post" action="HelloPostFile.jsp" ENCTYPE="multipart/form-data">
USERNAME : <input type="text" name="username" value="">
AGE : <input type="text" name="age" value="">
<input type="file" name="filept">
<input type="submit" name="submit" value="submit">
</form>
</body>
</html>
测试这个页面的时候,却发现取不到name和age参数。难道没传过去?没有的,只是enctyoe更改了属性后,传输的方式也变了。现在我们找到这些传输的数据。
页面跳转的过程中传输的数据是存放在request中的,我们可以从request中将其取出来。在这里,我们以流的方式将数据取出来,使用的方法是request.getInputStream(),这个方法返回的是ServletInputStream对象,Stream这类流对象有一个特点,说得通俗些就是它们像流水一样,操作一次之后,无法返回重新操作。
操作流有许多方法,一种常见的方法就是将这个流一段一段的取出来,进行分析。分段有的按照固定的大小,有的按行读取。这里,按照行读取。
使用的方法是ServletInputStream对象的readLine([]byte b,int offset,int length)
参数说明
b – 用来存放读取数据的数组。
off – 存入数组中的起始位置。
len – 读取的最大行数。
这个方法返回的是读取的byte的数目。
为了避免读取的一行数据过多,byte数组b尽量设定的大一些。但也没必要太夸张。
下面先写出这段jsp代码。
复制内容到剪贴板
代码:
<%@ page     contentType="text/html; charset=GBK" %>
<%@ page     import="javax.servlet.ServletInputStream" %>
<html>
<head>
<title>get method</title>
<meta http-equiv="Content-Type" content="text/html; charset=GBK">
</head>
<body>
<%
    request.setCharacterEncoding("GBK");
        int BUFSIZE = 1024 * 8;        
        int rtnPos = 0;
        byte[] buffs = new byte[ BUFSIZE * 8 ];
        ServletInputStream sis = request.getInputStream();
        while( (rtnPos = sis.readLine( buffs, 0, buffs.length )) != -1 ){
            String strBuff = new String( buffs, 0, rtnPos );
            out.print(strBuff);
            out.print("<br>");
        }
%>
</body>
</html>
刚才已经介绍了代码中的主要方法,现在没有看到的方法就是new String( buffs, 0, rtnPos )这是一个String对象初始化的方法。将byte数组中指定开始,结束索引的数据生成一个字符串。

而程序的逻辑,如果有过文件读取操作基础的话,很容易理解这段代码的逻辑。
大体逻辑如下
1.       按行读取取得的流中的数据。
2.       如果取得的数据的长度为-1,表明读取完了,结束循环。
3.       如果读取的长度不为-1,表明成功读取,进入循环操作。
4.       将读取的byte数组生成一个新的字符串,在页面中打印这个字符串,打印换行符。
将这个文件存为HelloPostFile.jsp。然后启动tomcat,运行程序,在文件选择框里选择一个小的文本文件,(因为这里面将文件里的数据全打印出来了,如果文件太大,会很慢的)

运行,会发现打印出一段数据。一会再分析这个数据,现在存在一个小问题,如果在输入参数或者上传的文件中有中文的话,很可能出现乱码。我这里是日文的操作系统,在参数输入中文的时候,出现了乱码。
为什么会出现乱码呢?因为数据被我们进行了一遍过滤,将数据取出来,然后重新生成字符串。而new String( buffs, 0, rtnPos )方法生成字符串对象的时候,使用的是默认的编码方式,这样还是存在危险的,所以用new String( buffs, 0, rtnPos ,”GBK”)来代替这个方法,这次再次运行,乱码没有出现。这个方法与原来的方法的功能相似,只是这个方法指定了编码方式为GBK,这样,就会按照指定的编码方式生成字符串。运行后,可以看到如下的界面。

在这个界面里,可以找到类似这样的数据。
复制内容到剪贴板
代码:
-----------------------------7d72ac15110506
Content-Disposition: form-data; name="username"
测试
这里注意到我们的参数名和设定的值都被传输过来即可。
还会看到和以下类似的数据。
复制内容到剪贴板
代码:
Content-Disposition: form-data; name="filept"; filename="C:\Documents and Settings\uki.TYO\デスクトップ\SinNeR.txt"
Content-Type: text/plain
其中有你的type为file的控件的名字,文件的路径,以及文件的type。在它的下面,打印出了你上传的文件的内容。
由此可见,所有的数据都可以由request对象的getInputStream()方法取得。这样,我们可以自己分析这个流,并将其重新设定,以给我们的程序所用。具体实现方法,等谈到怎么实现文件上传的时候再说吧。
对于get/post方法页面间传值来说,这已经跑题了。

注意:
复制内容到剪贴板
代码:
通过从request对象的getInputStream()方法取得页面传输的数据,我们可以看到,似乎作文件上传并不是很难。
技术方面的无非以下几点:
1.    能够从request中取得传递的数据流,并将其读取出来。
2.知道读取出来的流的格式,能够用正确的方法分析,解析这个流。
3.能够将一些参数等非文件的流还原给request对象,以使页面的其他功能不受到影响。
4.要知道怎么进行文件操作,可以将文件中的数据存储到服务器指定的目录下。
以上看似很简单,但是细节上还是有不少问题的,需要考虑周密,才能保证写出来的代码bug不会太多。文件上传这个功能,留到以后再作介绍。

Enctype为multipart/form-data时,我们打印出了request对象中getInputStream()取得的流。那可以看看Enctype为application/x-www-form-urlencoded时情况下,打印出来getInputStream()方法取得的流是什么样子的。不过这样的操作会造成request.getParameter()取不到参数。
具体原因,上面已经说过。

注意:
复制内容到剪贴板
代码:
HttpServletRequest对象的getInputStream()是会抛出异常的,但是由于现在还没有介绍到异常,所以没有主动捕获异常,这是非常不可取的。
[ 本帖最后由 SinNeR 于 2007-3-29 19:10 编辑 ]
附件: 您所在的用户组无法下载或查看附件,您需要注册/登录后才能查看!
ForgotteN

TOP

引用:
原帖由 asp910 于 2007-3-29 12:54 发表
怎么说了,其实很多东西都是在压力下学习和接受的,当然也要有兴趣。
确实是这样的,所以我也在犹豫帖子写下去的必要性。首先自己的技术不行,所以很可能误导新手,另一点,有很多书,学习的人都不愿意去看,更不要说这个帖子了。坚持下吧,写到简单的servlet就停手。
ForgotteN

TOP

回复 #11 SinNeR 的帖子

呵呵,其实这些基础方面的东西,我是很脆弱的,看了楼主写的,在对应的知识点方面应该比市面上那些所谓的书籍介绍的好。
学习这东西,对现在的项目来说,很多公司可能很少用这些了,一般都是用开源的框架或者公司自己开发的平台,底层的东西越来越少,感觉到了一定的时候还真的去从头学一下那些东西。你PM给我的话,看了一些,就介绍了一些基本的东西,其他也没有时间来慢慢细说了,本身自己的水平也有限。
曾经沧海难为水,除却巫山不是云.

TOP

用TOMCAT作简单的Java web开发-JSP中实现文件上传

用TOMCAT作简单的Java web开发-JSP中实现文件上传
介绍了jsp页面中get/post方法传递参数后,在后面介绍到的文件上传部分,我们似乎已经看到了我们能够通过HttpServletRequest对象的getInputStream()方法取得form表单传递的文件以及其他参数的流,能够得到它,原则上我们就已经可以实现jsp间文件上传的功能了。剩下的主要就是细节问题了。今天就从准备工作开始,一点一点介绍怎么实现jsp间的文件上传。大体分以下步骤。
复制内容到剪贴板
代码:
1.    准备工作,分析需要的技术。技术包括Java方面的,以及文件上传的技术文档。
2.分析业务流程以及实现的一些细节问题。
3. 初步实现功能。
4. 抽出class,使此功能共通化。
5. 测试。
其中5测试是程序开发中必要的一个环节,即使你的代码能够实现你的功能,你也要测试在不同的情况下程序的正确性。因为用户并不一定会完全按照你预想的操作步骤来执行你的程序,那么,就需要考虑到在不同情况下程序的反应,避免出错。但是这里,就进行简单测试了。如果有朋友对这个功能感兴趣,使用的时候,出现问题可以反馈给我。

好了,现在开始进行准备工作,分析需要的技术。
a,对String对象要非常熟悉,因为在我们取得String流之后,按行取得字符串之后,剩下的操作主要就是对String对象的各种操作。String对象是基础,对它的熟悉是必要的,要了解它的各种方法的用法。
b,对java的文件操作要非常熟悉,至少,要了解如何生成一个文件。
c,要了解文件上传的一些知识。前2点都是很容易就可以找到来学习的。现在说说文件上传知识从哪里来的。首先,文件上传的时候,form表单的enctype为multipart/form-data。这很容易让我们想到要查找DHTML的知识,到微软的MSDN上找到FORM表单的介绍,发现并没有提供我们想要了解的知识。那么,就去W3C(http://www.w3.org/TR/REC-html32.html)去找找看吧,在w3c网站上找到的FORM信息如下:
复制内容到剪贴板
代码:
FORM
<!ENTITY % HTTP-Method "GET | POST"
        -- as per HTTP specification
        -->
<!ELEMENT FORM - - %body.content -(FORM)>
<!ATTLIST FORM
        action %URL #IMPLIED  -- server-side form handler --
        method (%HTTP-Method) GET -- see HTTP specification --
        enctype %Content-Type; "application/x-www-form-urlencoded"
        >
This is used to define an HTML form, and you can have more than one form in the same document. Both the start and end tags are required. For very simple forms, you can also use the ISINDEX element. Forms can contain a wide range of HTML markup including several kinds of form fields such as single and multi-line text fields, radio button groups, checkboxes, and menus.
action
This specifies a URL which is either used to post forms via email, e.g. action="mailto:foo@bar.com", or used to invoke a server-side forms handler via HTTP, e.g. action="http://www.acme.com/cgi-bin/register.pl"
method
When the action attribute specifies an HTTP server, the method attribute determines which HTTP method will be used to send the form's contents to the server. It can be either GET or POST, and defaults to GET.
enctype
This determines the mechanism used to encode the form's contents. It defaults to application/x-www-form-urlencoded.
Further details on handling forms are given in RFC 1867.
似乎也没有讲到我们要的信息,注意最后一句,Further details on handling forms are given in RFC 1867. 那么我们再找RFC 1867文档来看看,搜索后在http://www.faqs.org/rfcs/rfc1867.html找到了RFC 1867的介绍。翻看了一下,在Example中发现了这么一段
复制内容到剪贴板
代码:
6. Examples
   Suppose the server supplies the following HTML:
     <FORM ACTION="http://server.dom/cgi/handle"
           ENCTYPE="multipart/form-data"
           METHOD=POST>
     What is your name? <INPUT TYPE=TEXT NAME=submitter>
     What files are you sending? <INPUT TYPE=FILE NAME=pics>
     </FORM>
   and the user types "Joe Blow" in the name field, and selects a text
   file "file1.txt" for the answer to 'What files are you sending?'
   The client might send back the following data:
        Content-type: multipart/form-data, boundary=AaB03x
        --AaB03x
        content-disposition: form-data; name="field1"
        Joe Blow
        --AaB03x
        content-disposition: form-data; name="pics"; filename="file1.txt"
        Content-Type: text/plain
         ... contents of file1.txt ...
        --AaB03x--
   If the user also indicated an image file "file2.gif" for the answer
   to 'What files are you sending?', the client might client might send
   back the following data:
        Content-type: multipart/form-data, boundary=AaB03x
        --AaB03x
        content-disposition: form-data; name="field1"
        Joe Blow
        --AaB03x
        content-disposition: form-data; name="pics"
        Content-type: multipart/mixed, boundary=BbC04y
        --BbC04y
        Content-disposition: attachment; filename="file1.txt"
        Content-Type: text/plain
        ... contents of file1.txt ...
        --BbC04y
        Content-disposition: attachment; filename="file2.gif"
        Content-type: image/gif
        Content-Transfer-Encoding: binary
          ...contents of file2.gif...
        --BbC04y--
        --AaB03x--
是不是发现这里有许多东西在昨天的getInputStream()的输出里很相似?OK!就是它了,简单看了下整篇介绍,对文件上传有了大体的了解。
注意:
复制内容到剪贴板
代码:
如果仔细观看给的文档的时候,会发现Content-type: multipart/form-data, boundary=AaB03x这句我们没有看到过,这句是哪里来的呢?查看下request方法,发现有个getContentType()方法,在JSP中输出一看,发现是multipart/form-data; boundary=---------------------------7d723f29b07f4,是不是跟文档中的那个非常相似。
原来,在这里,定义了这个随机的字符串,---------------------------7d723f29b07f4,getInputStream()中取得的每个参数,都是以这个字符串做分割的,对比一下,发现getInputStream()中对应的字符串比ContentType中的这个字符串前端多了”--”,而结束的字符串又在后端多了”--”,这些信息很有用,对我们分析getInputStream()取得的流很有帮助!
技术方面,主要就是这3点了,主要是通过自己的学习和搜索引擎得到的。现在开始分析如何实现文件上传的功能。
在分析之前,我们要考虑一点细节问题。比如,如果form中普通的input控件有2个名字相同,在getInputStream流中是什么样的呢?如果上传多个文件,在getInputStream流中是什么样的呢?如果form中普通的input控件和文件上传控件名字相同,在getInputStream流中是什么样的呢?这些可能不太符合规范,但是如果可以这样写,我们就要考虑这种情况出现的时候,该怎样应对。动手前想的越多,写出来的代码可能存在的bug可能会越少。
现在就尝试一下,写一个html页面,form中有2个相同name的input控件,有2个相同name的文件上传控件,有一个input控件和文件上传控件的名字相同。代码如下:
HelloUpFormat.html
复制内容到剪贴板
代码:
<html>
<head>
<title>Test DATE</title>
<meta http-equiv="Content-Type" content="text/html; charset=GBK">
</head>
<body>
<form name="frm" method="post" action="HelloGet.jsp" ENCTYPE="multipart/form-data">
USERNAME : <input type="text" name="username" value="">
USERNAME2(same name as USERNAME) : <input type="text" name="username" value="">
<br>
TEST input 1<input type="text" name="input_file" value="">
TEST file 1(same name as TEST input 1)<input type="file" name="input_file">
<br>
file pt1<input type="file" name="filept">
same name as file pt1<input type="file" name="filept">
<input type="submit" name="submit" value="submit">
</form>
</body>
</html>
提交到昨天写的HelloGet.jsp页面。注意选择上传文件的时候选择不同的文件,以便区分清楚。

提交后显示的数据如下:
复制内容到剪贴板
代码:
-----------------------------7d72451bb07f4
Content-Disposition: form-data; name="username"
SinNeR
-----------------------------7d72451bb07f4
Content-Disposition: form-data; name="username"
不可饶恕的罪人
-----------------------------7d72451bb07f4
Content-Disposition: form-data; name="input_file"
和上传文件控件名一样
-----------------------------7d72451bb07f4
Content-Disposition: form-data; name="input_file"; filename="C:\Documents and Settings\uki.TYO\デスクトップ\1.txt"
Content-Type: text/plain
aaaaaaaaaaaaaaaaaaaaa
-----------------------------7d72451bb07f4
Content-Disposition: form-data; name="filept"; filename="C:\Documents and Settings\uki.TYO\デスクトップ\2.txt"
Content-Type: text/plain
bbbbbbbbbbbbbbbbbbbbbbbbbbb
-----------------------------7d72451bb07f4
Content-Disposition: form-data; name="filept"; filename="C:\Documents and Settings\uki.TYO\デスクトップ\3.txt"
Content-Type: text/plain
cccccccccccccccccccccccccccccccc
-----------------------------7d72451bb07f4
Content-Disposition: form-data; name="submit"
submit
-----------------------------7d72451bb07f4--
会发现相同的名称并没有影响参数以及文件的提交。那么,我们在实现功能的时候也要注意,保留用户的提交信息。不要丢失参数,同时,要正常的支持多文件上传的功能。
另外,如果文件上传控件没有选择文件的时候,得到的流是这样的。
复制内容到剪贴板
代码:
-----------------------------7d71343b07f4
Content-Disposition: form-data; name="input_file"; filename=""
Content-Type: application/octet-stream
这点也要注意。

至此,jsp文件上传的功能和技术准备我们大体已经准备的差不多了。现在开始分析业务流程以及实现细节的问题了。
Jsp页面程序的大体流程为:
1. 取得要分析的流。
2. 分析取得的流,将普通的参数保存,留作页面取得用。将文件的流和文件名保存,在服务器端保存文件。
3. 提供方法,使用户可以方便的取得要用的参数(这个不是必须的)
这样来看,难点就在对取得的流的分析上。这,就需要自己认真的思考了。
考虑后,可以大体画出以下的流程图。


大家可以尝试着走一遍这个流程图看看能不能走得通。
上面的流程图似乎已经比较清晰,比较严谨。但是有一个问题,如果上传的文件中有Content-Disposition: form-data; name="input_file"这样的字符串,这个流程图就会错误的把它当成一个新的参数来进行分析,这样程序就出现了严重的错误!这是一个危险的bug。那么,我们就要修正这个流程,仔细观察会发现,Content-Disposition: form-data; name="input_file"这个设定参数名字的字符串至会出现在-----------------------------7d71343b07f4的下面,所以,每次读到-----------------------------7d71343b07f4的时候,立刻再读一行,来读取参数的name,这样就避免了上面的问题。流程修改如下:


这时候可能细心的人会想到,如果上传的文件中还有-----------------------------7d71343b07f4字符串,那么对流的分析岂不是也乱了?这个不用担心,因为-----------------------------7d71343b07f4是一个随机的字符串,用户的文件碰巧和这个字符串一样的概率是微之甚微的。
这样,我们就分析完大体的流成了。剩下的工作就是编码实现了。
编码之前,还要交待一些程序实现的细节问题。
1.对于参数的value,用什么对象保存来好。
如开始所看到的,对应同一个name的参数,可能有几个value,如果我们用String来存储的话,就会造成数据丢失。很容易就想到了数组,但是数组在初始化的时候,必须为其定义大小,而在分析之前,我们是不知道同一个name的value有多少个的。这里,我们用ArrayList对象来存储value。
ArrayList对象其实也是一个类似数组的容器,但是它在初始化的时候不需要指定大小。每次有新的元素,尽管加进去就可以。
和数组相似,ArrayList也存在索引(index)。以下是ArrayList的一些常用方法。
复制内容到剪贴板
代码:
//初期化
ArrayList al = new ArrayList();
//返回ArrayList的大小。
int size = al.size();
//向ArrayList中添加数据。
al.add(object);
//取得索引为i的元素,注意索引越界。
al.get(i);
//判断一个元素在ArrayList中的索引位置,不存在的时候返回-1
al.indexOf(object);
//常用的遍历ArrayList的方法。
for(int i=0;i<al.size();i++)
    al.get(i);
2.       参数怎么保存。
传递过来的参数中,名字不同,会有很多,而且不知道他们的大小。我们用什么来保存它呢?
注意到这种数据的形式是,有一个name,对应一个value对象。这个,跟java中的Hashtable非常相似。
所以我们选择用Hashtable来保存数据。Hashtable比较适合保存一个name对应一个value对象的数据。
复制内容到剪贴板
代码:
//初期化
Hashtable ht = new Hashtable();
//存放数据
ht.put(name,value);
//取出对应name的数据。
ht.get(name);
3.       文件的简单操作。
新建一个文件,并写入文件的内容,一般的情况下会以流的形式写入文件。
关于文件操作的介绍也很多,这里暂时就不详细介绍了。
复制内容到剪贴板
代码:
//新建一个文件。
  File file = new File( FILEPATH );
//新建一个文件输出流。
FileOutputStream baos = new FileOutputStream( file );
//新建一个buffer输出流
BufferedOutputStream bos = new BufferedOutputStream( baos );
//向流中写入数据
bos.write( buffs, 0, rtnPos );
baos.flush();
有了以上的知识。

我们可以初步对代码进行实现了。注意,这是一个稍显复杂的过程,所以要一步一步,先写好大的逻辑,再写细节,这样可以保持清晰地结构,避免一下子就想写完,结果最后整的自己也搞不清逻辑了。同时写的过程中记得加入注释,这也是良好的习惯。对看清source有很大的帮助。
首先,我们拿出昨天的HelloGet.jsp,将我们刚才的流程图作为注释写入代码。
复制内容到剪贴板
代码:
<%@ page     contentType="text/html; charset=GBK" %>
<%@ page     import="javax.servlet.ServletInputStream" %>
<html>
<head>
<title>get method</title>
<meta http-equiv="Content-Type" content="text/html; charset=GBK">
</head>
<body>
<%
    request.setCharacterEncoding("GBK");
        //定义name,value变量,以及文件标志位
int BUFSIZE = 1024 * 8;        
        int rtnPos = 0;
        byte[] buffs = new byte[ BUFSIZE * 8 ];
        ServletInputStream sis = request.getInputStream();
        //读取1行数据
        while( (rtnPos = sis.readLine( buffs, 0, buffs.length )) != -1 ){
            String strBuff = new String( buffs, 0, rtnPos );
            //判断取得的数据
            //1. 如果是开始符
                       //如果name存在
                              //如果文件标志位为true
//进行文件存储操作。
                              //如果文件标志位为flase
                                 //进行参数设定操作。
                    //重新初始化name,value以及文件标志位
                    //读取1行数据。
                           //如果字符中包含"Content-Disposition: form-data;",读取name
                           //如果字符中包含"filename",设定文件标志位为true,读取文件名字。
//2. 如果是结束符
                       //如果name存在
                              //如果文件标志位为true
                                 //进行文件存储操作。
                              //如果文件标志位为flase
                                 //进行参数设定操作。
//4. 如果不是1,2的情况。
                   //value相加。
            //out.print(strBuff);
            //out.print("<br>");
        }
%>
</body>
</html>
可以看出上面的条件判断是分级别的,我们先实现第一级判断。在这里注意到我们的程序没有从contentType中取得开始符得操作。这里我们需要添加进去。具体取的过程中用到的String对象的indexOf和substring方法,都是基础的基础的方法,大家自己查下吧。
复制内容到剪贴板
代码:
<%@ page     contentType="text/html; charset=GBK" %>
<%@ page     import="javax.servlet.ServletInputStream" %>
<html>
<head>
<title>get method</title>
<meta http-equiv="Content-Type" content="text/html; charset=GBK">
</head>
<body>
<%
    request.setCharacterEncoding("GBK");
        //定义name,value变量,以及文件标志位
        String name = null;
        String value = null;
        boolean fileFlag = false;
int BUFSIZE = 1024 * 8;        
        int rtnPos = 0;
        byte[] buffs = new byte[ BUFSIZE * 8 ];
        //取得开始符。
        String contentType = req.getContentType();
        int index = contentType.indexOf( "boundary=" );
String boundary = "--" + contentType.substring( index + 9 );
               String endBoundary = boundary + "--";
        //取得流。
        ServletInputStream sis = request.getInputStream();
        //读取1行数据
        while( (rtnPos = sis.readLine( buffs, 0, buffs.length )) != -1 ){
            String strBuff = new String( buffs, 0, rtnPos );
            //判断取得的数据
            //1. 如果是开始符
            if( strBuff.startsWith( boundary ) ){
                       //如果name存在
                              //如果文件标志位为true
//进行文件存储操作。
                              //如果文件标志位为flase
                                 //进行参数设定操作。
                    //重新初始化name,value以及文件标志位
                    //读取1行数据。
                           //如果字符中包含"Content-Disposition: form-data;",读取name
                           //如果字符中包含"filename",设定文件标志位为true,读取文件名字。
//2. 如果是结束符
}else if( strBuff.startsWith( endBoundary ) ){
                       //如果name存在
                              //如果文件标志位为true
                                 //进行文件存储操作。
                              //如果文件标志位为flase
                                 //进行参数设定操作。
//4. 如果不是1,2的情况。
}else{
                   //value相加。
            //out.print(strBuff);
            //out.print("<br>");
            }
        }
%>
</body>
</html>
按照这样的步骤,一步一步完成下一步的逻辑,这个时候要确保自己的每句代码的正确性,因为jsp是无法预编译的,提前细心一些,就避免了调试过程中问题的出现。
为下一步添加代码。
复制内容到剪贴板
代码:
<%@ page     contentType="text/html; charset=GBK" %>
<%@ page     import="javax.servlet.ServletInputStream" %>
<html>
<head>
<title>get method</title>
<meta http-equiv="Content-Type" content="text/html; charset=GBK">
</head>
<body>
<%
    request.setCharacterEncoding("GBK");
        //定义name,value变量,以及文件标志位
        String name = null;
        String value = null;
        boolean fileFlag = false;
int BUFSIZE = 1024 * 8;        
        int rtnPos = 0;
        byte[] buffs = new byte[ BUFSIZE * 8 ];
        //取得开始符。
        String contentType = req.getContentType();
        int index = contentType.indexOf( "boundary=" );
String boundary = "--" + contentType.substring( index + 9 );
               String endBoundary = boundary + "--";
        //取得流。
        ServletInputStream sis = request.getInputStream();
        //读取1行数据
        while( (rtnPos = sis.readLine( buffs, 0, buffs.length )) != -1 ){
            String strBuff = new String( buffs, 0, rtnPos );
            //判断取得的数据
            //1. 如果是开始符
            if( strBuff.startsWith( boundary ) ){
                       //如果name存在
                if ( name != null && name.trim().length() > 0 ){
                              //如果文件标志位为true
//进行文件存储操作。
                              //如果文件标志位为flase
                                 //进行参数设定操作。
                }
                    //重新初始化name,value以及文件标志位
                    name = new String();
                value = new String();
                fileFlag = false;
//读取1行数据。
rtnPos = sis.readLine( buffs, 0, buffs.length );
                           //如果字符中包含"Content-Disposition: form-data;",读取name
                           //如果字符中包含"filename",设定文件标志位为true,读取文件名字。
//2. 如果是结束符
}else if( strBuff.startsWith( endBoundary ) ){
                       //如果name存在
                if ( name != null && name.trim().length() > 0 ){
                              //如果文件标志位为true
                                 //进行文件存储操作。
                              //如果文件标志位为flase
                                 //进行参数设定操作。
                }
//4. 如果不是1,2的情况。
}else{
                   //value相加。
            //out.print(strBuff);
            //out.print("<br>");
            }
        }
%>
</body>
</html>
在这里注意一下if ( name != null && name.trim().length() > 0 )这句语句。Java开发中,空指针异常是经常发生的,对没有初始化的对象调用方法,那时候就会抛出空指针异常。那这句话中name.trim()会不会抛出空指针异常呢?回答是不会的,为什么?因为java中条件判断语句是从左向右执行的,如果已经能确定这个条件的结果,那么程序就不会执行下一个判断的。比如A && B 的与条件判断,如果A为false,那么A && B 一定是false,程序就不会去执行B的条件判断,直接返回false。在这里,如果name 为 null,那么前边的条件为false,程序就不会执行后面的语句。只有在name不为null的情况下,才会执行后面的语句。换句话说,name.trim().length()被执行的时候,name永远不会为null。自然不会抛出空指针异常。这是一个小技巧,要利用好。相反,如果写成if (name.trim().length() > 0 && name != null )就很容易出错了。
接着完成下边的注释对应的代码。这时候注意,value对应的为参数的时候,存储value应该为ArrayList数组,而value对应的为文件内容的时候,直接写入文件即可,同时,要给存放name的Hashtable初始化。逻辑开始复杂了,要慢慢的对应。
复制内容到剪贴板
代码:
<%@ page     contentType="text/html; charset=GBK" %>
<%@ page     import="javax.servlet.ServletInputStream" %>
<%@ page     import="java.util.*" %>
<%@ page     import="java.io.*" %>
<html>
<head>
<title>get method</title>
<meta http-equiv="Content-Type" content="text/html; charset=GBK">
</head>
<body>
<%
    request.setCharacterEncoding("GBK");
        //定义name,value变量,以及文件标志位
        String name = null;
        String value = null;
        //ArrayList value = null;//更改存储value的对象。
        boolean fileFlag = false;
        // TMP_DIR
        String TMP_DIR = "C:\\";
        File tmpFile = null;
        //file name
        String fName = null;
        
        FileOutputStream baos = null;
        BufferedOutputStream bos = null;
        //定义存放参数的Hashtable。
        Hashtable paramHt = new Hashtable();
int BUFSIZE = 1024 * 8;        
        int rtnPos = 0;
        byte[] buffs = new byte[ BUFSIZE * 8 ];
        //取得开始符。
        String contentType = request.getContentType();
        int index = contentType.indexOf( "boundary=" );
String boundary = "--" + contentType.substring( index + 9 );
               String endBoundary = boundary + "--";
        //取得流。
        ServletInputStream sis = request.getInputStream();
        //读取1行数据
        while( (rtnPos = sis.readLine( buffs, 0, buffs.length )) != -1 ){
            String strBuff = new String( buffs, 0, rtnPos );
            //判断取得的数据
            //1. 如果是开始符
            if( strBuff.startsWith( boundary ) ){
                       //如果name存在
                if ( name != null && name.trim().length() > 0 ){
                              //如果文件标志位为true
                    if (fileFlag ){
//进行文件存储操作。
                        bos.flush();
                        baos.close();
                        bos.close();
                        baos = null;
           bos = null;
}else{
                              //如果文件标志位为flase
                                 //进行参数设定操作。
                        Object obj = paramHt.get(name);
                        ArrayList al = null;
if ( obj == null ){                                        al = new ArrayList();
}else{
    al = (ArrayList)obj;    
}
al.add(value);
paramHt.put(name, al);
}
                }
                    //重新初始化name,value以及文件标志位
                    name = new String();
                value = new String();
                fileFlag = false;
//读取1行数据。
rtnPos = sis.readLine( buffs, 0, buffs.length );
if (rtnPos != -1 ){
strBuff = new String( buffs, 0, rtnPos );
                           //如果字符中包含"Content-Disposition: form-data;",读取name
                    if (strBuff.toLowerCase().startsWith( "content-disposition: form-data; " )){
                                    int nIndex = strBuff.toLowerCase().indexOf( "name=\"" );
                                    int nLastIndex = strBuff.toLowerCase().indexOf( "\"", nIndex + 6 );
            name = strBuff.substring( nIndex + 6, nLastIndex );
}
                           //如果字符中包含"filename",设定文件标志位为true,读取文件名字。
                    int fIndex = strBuff.toLowerCase().indexOf( "filename=\"" );
                    if (fIndex != -1 ){
    fileFlag = true;
                        int fLastIndex = strBuff.toLowerCase().indexOf( "\"", fIndex + 10 );
         fName = strBuff.substring( fIndex + 10 , fLastIndex );
                                fIndex = fName.lastIndexOf( "\\" );
                                if( fIndex == -1 ){
                                    fIndex = fName.lastIndexOf( "/" );
                                    if( fIndex != -1 ){
                                fName = fName.substring( fIndex + 1 );
                                    }
                                    }else{
    fName = fName.substring( fIndex + 1 );
}
if (fName == null || fName.trim().length() == 0){
    fileFlag = false;
    sis.readLine( buffs, 0, buffs.length );
sis.readLine( buffs, 0, buffs.length );
sis.readLine( buffs, 0, buffs.length );
continue;
}
}
sis.readLine( buffs, 0, buffs.length );
sis.readLine( buffs, 0, buffs.length );
                }
//2. 如果是结束符
}else if( strBuff.startsWith( endBoundary ) ){
                       //如果name存在
                if ( name != null && name.trim().length() > 0 ){
                              //如果文件标志位为true
                    if (fileFlag ){
//进行文件存储操作。
                        bos.flush();
                        baos.close();
                        bos.close();
                        baos = null;
           bos = null;
}else{
                              //如果文件标志位为flase
                                 //进行参数设定操作。
                        Object obj = paramHt.get(name);
                        ArrayList al = null;
if ( obj == null ){                                        al = new ArrayList();
}else{
    al = (ArrayList)obj;    
}
al.add(value);
paramHt.put(name, al);
}
                }
//4. 如果不是1,2的情况。
}else{
                   //value相加。
                if (fileFlag ){
                if ( baos == null && bos == null ) {
                       tmpFile = new File( TMP_DIR + fName );
                       baos = new FileOutputStream( tmpFile );
                        bos = new BufferedOutputStream( baos );
                }
                    bos.write( buffs, 0, rtnPos );
baos.flush();
}else{
    value = value + strBuff;
}
            }
        }
%>
</body>
</html>
浩浩荡荡的写完了,调试一下。修改了一些简单的bug后,程序居然跑起来了。运行一下开始讲的那个HTML。会发现文件上传成功。这里要注意的是,TMP_DIR设定的是服务器端你上传的文件的存放地址。
到那里去看一下,发现原来的文件内容,
复制内容到剪贴板
代码:
aaaaaaaaaaaaaaaaaaaaa
变成了
复制内容到剪贴板
代码:
Content-Type: text/plain
aaaaaaaaaaaaaaaaaaaaa
这是不正确的,所以,在文件读取的时候,要跳过2行的数据,不然存储的文件和原来的文件不同。修改后,读取的文件就正常了。
至此,文件上传的功能已经完成。而且传递的参数也被我们存储到一个Hashtable中了。如果开发者要用的话,可以取到。
但是现在就没有问题了么?还存在一个问题,就是当上传文件的控件没有指定文件的时候,会出现异常。这是漏掉的一个环节。发现这个时候服务器端取得的数据是如下形式的。
复制内容到剪贴板
代码:
-----------------------------7d71fe14b07f4
Content-Disposition: form-data; name="input_file"; filename=""
Content-Type: application/octet-stream
所以在文件名不存在的时候,直接跳过3行数据,然后让循环继续进行。
复制内容到剪贴板
代码:
if (fName == null || fName.trim().length() == 0){
    fileFlag = false;
    sis.readLine( buffs, 0, buffs.length );
sis.readLine( buffs, 0, buffs.length );
sis.readLine( buffs, 0, buffs.length );
continue;
}
至此,文件上传告一段落了。但是还有几个小问题。
复制内容到剪贴板
代码:
1.文件上传的覆盖问题。现在的文件上传,如果上传相同的文件名,文件会覆盖。这,对于多用户的web系统来说,不是一个好现象。
这个问题可以避免,最简单的就是在文件名后边加上时间,YYYYMMDDHHmmss的形式,最安全的应该是毫秒级别的,那样问题的发生概率将几乎等于0。
2.文件上传后,一般情况下,程序都是要知道上传的文件的地址的。这个也容易实现,代码进行适当的修改就可以了。
3.文件上传的代码过于复杂,如果上传文件的时候,就在jsp中加入这么一段代码,会使程序看起来很乱。
这个也可以解决,将会把它抽出为一个class文件。
4.别的传入的参数,只能用setAttribute方法设定到request中,无法设定到parameter中。不知道有没有更好的方法。
5.整个程序没有捕捉异常。
至此,文件上传的功能已经可以简单实现。下一次,我们就把这些代码抽出到1个class中并介绍接口。
之所以絮絮叨叨的写了这么多,只是希望新手能看到怎么样一点一点实现我们要求的功能。因为,前3讲之后,除了java的一些类的不熟悉,其他的问题大家的困难是一样的。希望这样写,能对用户分析问题,解决问题有点帮助。

[ 本帖最后由 SinNeR 于 2007-3-30 18:48 编辑 ]
附件: 您所在的用户组无法下载或查看附件,您需要注册/登录后才能查看!
ForgotteN

TOP

用TOMCAT作简单的Java web开发-JSP中实现文件上传2
抽出class,这一次是借jsp中文件上传来主要讲一下在Eclipse下怎么创建工程,编写类文件。自从第一次介绍Eclipse安装后,一直没有用它,这,算是不小的浪费。
今天,就把昨天的jsp中的文件上传的代码在Eclipse中生成一个类文件。并编译通过,最终达到可以给jsp使用的目的。
首先,打开Eclipse。

可以看到如上图的工作区域,各区域大致如下:
1区域,为工程显示区域,这里显示着你当前拥有的各个工程。
2区域,为代码显示区域,这也是程序员平时闷头干活的区域-_-b。
3区域,一般情况下为打开的类的方法,变量显示区域。如果在用别人写好的,庞杂的类,而且比较陌生的话,可以在这里看一下。自己写的类,已经耳熟能详,那就不用去管了。
4区域,显示你的工程编译后出现的问题,以及全工程检索关键字的时候,显示检索结果。
平时主要用的就是,1,2,4区域,当然,要熟悉Eclipse的各个菜单。
新建工程:
在1区域点击右键,选择新建->project(工程)弹出如下对话框。

点中java,选择java project,点击下一步,进入如下界面。

填写工程名称,选择建立工程的目录。默认的话,会在eclipse的workspace下建立工程。并进入如下界面。

设定java环境。第一个对话框中是设定src目录和编译的class目录,在没有特定的工程的情况下,默认即可。第二个对话框是设定要引入的工程,默认也不选即可。第三个对话框是选择JDK和要用的jar包。如果你的工程需要相应的jar包,可以添加进去。JDK如果有多个版本,可以修改,选择自己需要的版本。

比如,这里我要用1.4.2_13的JDK,选中JDK,点击编辑,探出如下界面。

如果里面有自己选择的JDK,选中终了即可。没有的话,点击新规。

在JRE名中输入自己的JDK版本名称。然后在下面的JRE路径中选择自己安装的JDK路径,Eclipse会自动读入JRE的包。点击OK,然后终了,发现JDK的版本已经变更。

OK后,一个工程建立成功。

工程建立成功后,可以随时修改工程个各种信息。
现在新建我们的JSP上传文件类。选中src文件夹,点击右键,新规->class,进入如下界面。为类起一个名字后,其他默认即可,比如起名JspFileUpload,然后点击终了,这个类文件新建成功。

这样,一个类新建成功。关于类的各种动态静态方法,构造函数,以及静态动态变量。可以参考一些书籍的java入门介绍。这里就不多说了。
注意,这个类用到HttpServletRequest对象,需要引入包含HttpServletRequest对象的包。引入的方法是,在工程点击右键选择工程属性,在其中的lib里选择加入外部jar,到tomcat/common/lib/下找到servlet.jar,导入即可。
这里我们考虑如何将那个jsp文件中的java代码抽出一个class文件。首先,我们想一下,在完成整个功能中我们需要JSP提供给我们什么,其实,只要页面提供request对象就可以了,因为所有数据都是从request的getContentType和getInputStream方法中取得的。不过,如果能够让用户指定文件上传的目录,这是友好的,所以,也需要用户设定文件上传的路径。另外,我们要返回给用户什么,用户比较关心的是文件上传是否成功。成功上传的文件名。以及和表单中提供的其他参数的值。
经过以上分析,大体,这个类用户可见的方法,以及接口,差不多清晰了。
可以开始编写这个类了,至于类中的各种成员变量,成员函数为什么有静态非静态区分,有公有私有的区分,这就是设计一个class时的知识了,不是1句话2句话可以说清楚的。如果有朋友有疑问,可以问一下,在我理解的范围内,我会尽量解答。
下一个回复介绍生成的类及用法。

[ 本帖最后由 SinNeR 于 2007-3-30 22:06 编辑 ]
附件: 您所在的用户组无法下载或查看附件,您需要注册/登录后才能查看!
ForgotteN

TOP

用TOMCAT作简单的jsp web开发--jsp页面上传完成版总结

刚才和lp看完电影,把jsp页面抽出class调整了一下。最近总上经典,是感觉既然当了斑竹,就该留下点什么。lp这几天也半开玩笑半生气的说,一回来就上经典,就发帖,你干脆娶经典作lp得了。想想,这几天是有点夸张,以后放慢速度了。保持1星期1帖吧,那样也能多想写,多总结些。
发帖的初衷就是有时候看到有的朋友问的问题,似乎还没有走进java的门,希望这样的帖子,能对新手一点帮助,也就满足了。有时候随意的一段话,其实也是自己的一点经验,而有时候之所以絮絮叨叨,是想把问题说的清楚明白,让高手见笑了。因为在入门的时候,每一个小环节都可能郁闷半天,如果看到我的某段话,有所帮助的话,即使我说十句有一句有帮助,我也满足了。因为我在不停的说话。

现在把总结的jsp页面上传类发布出来。代码肯定还会存在问题,有bug的话,告诉我,我及时修正。

名称:jsp页面上传类
作者:SinNeR
Mail:vogoals@hotmail.com

特点:
1。可以多文件上传。
2。返回上传后的文件名。
3。form表单中的其他参数也可以得到。

先贴上传类,JspFileUpload
复制内容到剪贴板
代码:
package com.vogoal.util;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Hashtable;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
/*
* vogoalAPI 1.0
* Auther SinNeR@blueidea.com
* by vogoal.com
* mail: vogoals@hotmail.com
*/
/**
* JSP上传文件类
*
* @author SinNeR
* @version 1.0
*/
public class JspFileUpload {
    /** request对象 */
    private HttpServletRequest request = null;
    /** 上传文件的路径 */
    private String uploadPath = null;
    /** 每次读取得字节的大小 */
    private static int BUFSIZE = 1024 * 8;
    /** 存储参数的Hashtable */
    private Hashtable paramHt = new Hasptable();
    /** 存储上传的文件的文件名的ArrayList */
    private ArrayList updFileArr = new ArrayList();
    /**
     * 设定request对象。
     *
     * @param request
     *            HttpServletRequest request对象
     */
    public void setRequest(HttpServletRequest request) {
        this.request = request;
    }
    /**
     * 设定文件上传路径。
     *
     * @param path
     *            用户指定的文件的上传路径。
     */
    public void setUploadPath(String path) {
        this.uploadPath = path;
    }
    /**
     * 文件上传处理主程序。&#65533;&#65533;&#65533;&#65533;&#65533;&#65533;&#65533;B
     *
     * @return int 操作结果 0 文件操作成功;1 request对象不存在。 2 没有设定文件保存路径或者文件保存路径不正确;3
     *         没有设定正确的enctype;4 文件操作异常。
     */
    public int process() {
        int status = 0;
        // 文件上传前,对request对象,上传路径以及enctype进行check。
        status = preCheck();
        // 出错的时候返回错误代码。
        if (status != 0)
            return status;
        try {
            // &#65533;&#65533;参数或者文件名&#65533;u&#65533;&#65533;
            String name = null;
            // 参数的value
            String value = null;
            // 读取的流是否为文件的标志位
            boolean fileFlag = false;
            // 要存储的文件。
            File tmpFile = null;
            // 上传的文件的名字
            String fName = null;
            FileOutputStream baos = null;
            BufferedOutputStream bos = null;
            // &#65533;&#65533;存储参数的Hashtable
            paramHt = new Hashtable();
            updFileArr = new ArrayList();
            int rtnPos = 0;
            byte[] buffs = new byte[BUFSIZE * 8];
            // &#65533;取得ContentType
            String contentType = request.getContentType();
            int index = contentType.indexOf("boundary=");
            String boundary = "--" + contentType.substring(index + 9);
            String endBoundary = boundary + "--";
            // &#65533;从request对象中取得流。
            ServletInputStream sis = request.getInputStream();
            // 读取1行
            while ((rtnPos = sis.readLine(buffs, 0, buffs.length)) != -1) {
                String strBuff = new String(buffs, 0, rtnPos);
                // 读取1行数据&#65533;n&#65533;&#65533;
                if (strBuff.startsWith(boundary)) {
                    if (name != null && name.trim().length() > 0) {
                        if (fileFlag) {
                            bos.flush();
                            baos.close();
                            bos.close();
                            baos = null;
                            bos = null;
                            updFileArr.add(fName);
                        } else {
                            Object obj = paramHt.get(name);
                            ArrayList al = new ArrayList();
                            if (obj != null) {
                                al = (ArrayList) obj;
                            }
                            al.add(value);
                            System.out.println(value);
                            paramHt.put(name, al);
                        }
                    }
                    name = new String();
                    value = new String();
                    fileFlag = false;
                    fName = new String();
                    rtnPos = sis.readLine(buffs, 0, buffs.length);
                    if (rtnPos != -1) {
                        strBuff = new String(buffs, 0, rtnPos);
                        if (strBuff.toLowerCase().startsWith(
                                "content-disposition: form-data; ")) {
                            int nIndex = strBuff.toLowerCase().indexOf(
                                    "name=\"");
                            int nLastIndex = strBuff.toLowerCase().indexOf(
                                    "\"", nIndex + 6);
                            name = strBuff.substring(nIndex + 6, nLastIndex);
                        }
                        int fIndex = strBuff.toLowerCase().indexOf(
                                "filename=\"");
                        if (fIndex != -1) {
                            fileFlag = true;
                            int fLastIndex = strBuff.toLowerCase().indexOf(
                                    "\"", fIndex + 10);
                            fName = strBuff.substring(fIndex + 10, fLastIndex);
                            fName = getFileName(fName);
                            if (fName == null || fName.trim().length() == 0) {
                                fileFlag = false;
                                sis.readLine(buffs, 0, buffs.length);
                                sis.readLine(buffs, 0, buffs.length);
                                sis.readLine(buffs, 0, buffs.length);
                                continue;
                            }else{
                                fName = getFileNameByTime(fName);
                                sis.readLine(buffs, 0, buffs.length);
                                sis.readLine(buffs, 0, buffs.length);
                            }
                        }
                    }
                } else if (strBuff.startsWith(endBoundary)) {
                    if (name != null && name.trim().length() > 0) {
                        if (fileFlag) {
                            bos.flush();
                            baos.close();
                            bos.close();
                            baos = null;
                            bos = null;
                            updFileArr.add(fName);
                        } else {
                            Object obj = paramHt.get(name);
                            ArrayList al = new ArrayList();
                            if (obj != null) {
                                al = (ArrayList) obj;
                            }
                            al.add(value);
                            paramHt.put(name, al);
                        }
                    }
                } else {
                    if (fileFlag) {
                        if (baos == null && bos == null) {
                            tmpFile = new File(uploadPath + fName);
                            baos = new FileOutputStream(tmpFile);
                            bos = new BufferedOutputStream(baos);
                        }
                        bos.write(buffs, 0, rtnPos);
                        baos.flush();
                    } else {
                        System.out.println("test :" + value + "--" + strBuff);
                        value = value + strBuff;
                    }
                }
            }
        } catch (IOException e) {
            status = 4;
        }
        return status;
    }
    private int preCheck() {
        int errCode = 0;
        if ( request == null )
            return 1;
        if ( uploadPath == null || uploadPath.trim().length() == 0 )
            return 2;
        else{
            File tmpF = new File(uploadPath);
            if (!tmpF.exists())
                return 2;
        }
        String contentType = request.getContentType();
        if ( contentType.indexOf("multipart/form-data") == -1 )
            return 3;
        return errCode;
    }
    public String getParameter(String name){
        String value = "";
        if ( name == null || name.trim().length() == 0 )
            return value;
        value = (paramHt.get(name) == null)?"":(String)((ArrayList)paramHt.get(name)).get(0);
        return value;
    }
    public String[] getParameters(String name){
        if ( name == null || name.trim().length() == 0 )
            return null;
        if ( paramHt.get(name) == null )
            return null;
        ArrayList al = (ArrayList)paramHt.get(name);
        String[] strArr = new String[al.size()];
        for ( int i=0;i<al.size();i++ )
            strArr[i] = (String)al.get(i);
        return strArr;
    }
    
    public int getUpdFileSize(){
        return updFileArr.size();
    }
    
    public String[] getUpdFileNames(){
        String[] strArr = new String[updFileArr.size()];
        for ( int i=0;i<updFileArr.size();i++ )
            strArr[i] = (String)updFileArr.get(i);
        return strArr;
    }
    private String getFileName(String input){
        int fIndex = input.lastIndexOf("\\");
        if (fIndex == -1) {
            fIndex = input.lastIndexOf("/");
            if (fIndex == -1) {
                return input;
            }
        }
        input = input.substring(fIndex + 1);
        return input;
    }
    private String getFileNameByTime(String input){
        int index = input.indexOf(".");
        Date dt = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
        return input.substring(0,index) + sdf.format(dt) + input.substring(index);
    }
}
说明:
复制内容到剪贴板
代码:
这个类基本解决了上一贴的上一贴说的存在的bug和不足。主要做了如下修正。
1.用户可以设定文件上传的路径,这里没有用request对象的getRealPath方法来取得相对路径,而是用了绝对路径。是一个小败笔。因为有时候用户只是得到服务器的一个应用,而不知道整个服务器的路径。但是既然getRealPath自己可以得到,用户自己取得也可以。
2.在文件上传处理的时候,预先进行了check,把一些可能出现的造成上传失败的情况拍查掉。避免该类出现不该出现的异常。
3.捕获了IO异常,避免文件上传的时候出现异常时程序的不友好表现。
4.提供了方法返回form表单中其他参数的取得,模拟了HttpServletRequest对象的getParameter和getParameters方法(后面这个方法是叫这个名字么-_-b),取得Parameter的名称的方法没有提供,是个小缺陷。
5.提供了方法返回上传的文件的件数和上传的文件名,方便用户作其他操作。
现在介绍下JSP页面中如何用这个类实现上传。
首先,要把这个类编译后的class文件拷贝到WEB-INF/classes/目录下。注意保持package的结构。
在jsp页面中引用这个类
<%@page import="com.vogoal.util.JspFileUpload"%>

<%
    //初始化
    JspFileUpload jfu = new JspFileUpload();
    //设定request对象
    jfu.setRequest(request);
    //设定上传的文件路径
    jfu.setUploadPath("C:\\");
    //上传处理
    int rtn = jfu.process();
    //取得form中其他input控件参数的值
    String username = jfu.getParameter("username");
    //如果对应同一个参数有多个input控件,返回数组
    String[] usernameArr = jfu.getParameters("username");
    //取得上传的文件的名字
    String[] fileArr = jfu.getUpdFileNames();
    //取得上传文件的个数,这个方法有点鸡肋
    int fileNumber = getUpdFileSize();
//下面的是测试输出的代码。
//       out.println("parameter:" + username);
//       out.println("parameter size:" + usernameArr.length);
//       out.println("fileArr size:" + fileArr.length);
//       if (fileArr.length > 0)
//              out.println("fileArr 0:" + fileArr[0]);
%>

使用的时候的注意事项。
复制内容到剪贴板
代码:
1.一定要设定request对象。
2.一定要设定正确的上传路径。
3.执行完了之后才可以得到其他参数,因为执行了之后这些参数才被分析。
1,2两点如果没有做到的话,process方法执行的时候汇报错。
各个用户可用的方法及说明。
设定requet对象。
public void setRequest(HttpServletRequest request)

设定文件上传的路径。
public void setUploadPath(String path)

文件上传处理主程序。
@return int 操作结果 0 文件操作成功;1 request对象不存在。 2 没有设定文件保存路径或者文件保存路径不正确;3
         没有设定正确的enctype;4 文件操作异常。
public int process()

根据name取得form表单中其他传递的参数的值(多个的话返回其中一个)
public String getParameter(String name)

根据name取得form表单中其他传递的参数的值(返回数组,可有多个)
public String[] getParameters(String name)

取得上传成功文件的个数
public int getUpdFileSize()

取得上传的文件名对应的数组。
public String[] getUpdFileNames()

注意process方法地返回值,在不是0的情况下操作失败。


一下提供测试类以及测试页面。附件。
HelloPostFile.html
HelloPostFile.jsp
写在jsp中的代码的测试文件。
HelloPostFileWithClass.html
HelloPostFileWithClass.jsp
抽出class后的测试文件。
src在
WEB-INF/src/
class在
WEB-INF/classes/

如果有问题请回帖或者mail联系我。转载应用等请不要删除作者信息。

另,由于这个文件被我在中文日文系统下编辑过,注释出现乱码,所以大部分都删掉了,见谅。
附件: 您所在的用户组无法下载或查看附件,您需要注册/登录后才能查看!
ForgotteN

TOP

用TOMCAT作简单的Java web开发-Application,session变量

今天粗浅的谈一下java中application,session,request级别的变量的保存,使用方法以及用途的问题。其中理解的不正确的地方还请指正。
Web编程中,application,session,request对象是很常用的对象。可以在这些对象中存储变量,由于各个对象的生命周期的范围不同,所以,相应的,在存储数据的时候,可能会存储在不同的对象中。正确的使用这些对象,可以完成许多功能,掌握了这几个对象的使用方法,对程序的实现有很大的帮助。
而request对象在前面介绍jsp页面之间传值的时候,主要用了它的getParameter方法,并且在文件上传的时候,模拟了这个方法,这里,主要介绍它的getAttribute方法和setAttribute方法。

先说下这几个对象在jsp中都是通过什么类来实现的。这点,其实在前面的jsp页面的例子中,已经说到这几个对象都是怎么得到的了。这里就不再说了,只分别介绍下对象名称以及常用的方法。Request对象在前面虽然介绍了怎么利用它在页面间怎么传值,但是没有具体介绍各个常用的方法,这里补上来。(注,这里的常用的方法只是针对设定和取得值来说)
Application对象。
类:javax.servlet.ServletContext
实例取得方法(jsp页面中):
ServletContext application = pageContext.getServletContext();
常用的方法:
getAttribute(String name)
从ServletContext对象中取得名称为指定字符串的参数的值。
setAttribute(String name,Object value)
设定参数名为name的参数的值为value(可以是对象)

Session对象。
类:javax.servlet.http.HttpSession
实例取得方法(jsp页面中):
HttpSession session = pageContext.getSession();
常用的方法:
getAttribute(String name)
从ServletContext对象中取得名称为指定字符串的参数的值。
setAttribute(String name,Object value)
设定参数名为name的参数的值为value(可以是对象)

Reqpest对象。
类:javax.servlet.http.HttpServletRequest;
常用的方法:
getAttribute(String name)
从ServletContext对象中取得名称为指定字符串的参数的值。
setAttribute(String name,Object value)
设定参数名为name的参数的值为value(可以是对象)

以上所说的常用的方法只是针对设定取得参数而言,还有一些方法,也是比较重要的,有许多可以用到,暂时先不说了。比如request对象还有很多有用的方法,但是目前我们还不会用到它们。等到所学的知识进一步加深的时候,就要全面的,完整的了解一下这个类以及这个类提供的方法。

几个对象存储变量的生命周期以及用途比较。
Application:
从服务器启动开始,web服务被加载后,application对象就被初始化了,而且在服务器运行的整个过程中,application对象始终存在,在整个web服务中,只存在一个application对象。所以说,所用的用户,在网站运行自始至终,公用同一个application对象。Application对象的这个特点,使它可以用来存储一些全局的静态变量,静态数据等,用来给整个web服务用。比如网站的数据库连接的连接串,网站操作出错的错误信息,网站的一些基本的配置信息等。

Session:
从一个客户端(比如一个IE浏览器)打起并向web服务器请求后,web服务器就为这个客户端建立了一个session对象,至此,在这个客户端请求服务器的过程中,这个session对象一直存在,直到这个客户端关闭后,这个session对象才关闭,释放资源。可以这样理解,每一个客户端用户都对应一个session对象,session对象是用户级别的,这样,这个用户的数据,在不同的页面请求中,可以被传递。这样,session对象可以用来存储一些用户的信息,从而来区分不同的用户。比如,用户A登陆后,记录下用户A的用户名,用户权限等,每次操作的时候,都可以通过session对象中的用户名,用户权限来判断该用户是否可执行此类操作。

Request:
Request对象是页面级别的,对于每一次请求,都会重新创建request对象。Request对象常见的用法是用于页面请求之间的传值。

下面针对几个对象,分别举例,来看看他们的用法。
对象的用法举例:
Application对象举例:
为了测试application对象,我们做一个如下功能,计算某测试页面的访问量(这个好像前几天经典有人发贴问过类似的页面)。在application对象中定义一个变量,每当用户访问的时候,就对其加1,这样,每次用户访问的时候,就知道该页面的访问量。
文件名,ApplicationSample.jsp
复制内容到剪贴板
代码:
<%@ page     contentType="text/html; charset=GBK" %>
<%@ page     import="java.lang.Integer" %>
<html>
<head>
<title>Application test</title>
</head>
<body>
<%
    Object obj = application.getAttribute("count");
    int count = 0;
if ( obj != null ){
        count = Integer.parseInt((String)obj);
}
count ++;
application.setAttribute("count","" + count);
out.print( "now ,this page is visited " + count + " times" );
%>
</body>
</html>
打开一个IE,访问这个页面,会发现页面打印出了当前页面的访问次数。再打开一个IE,访问这个页面,发现访问次数在刚才页面的访问次数上递增,说明2个页面共用同一个变量count。
application.setAttribute("count","" + count);这句后面之所以用了"" + count是一个小伎俩。因为setAttribute(String name,Object value)方法要求value是一个对象的,而这里我们的count变量是一个基本的数据类型int。通过和一个长度为0的字符串相加,得到一个字符串对象。Count被转化为字符串。但是这个字符串相加毫无别的意义的。编程过程中,这样的小细节经常会遇到,相对来说,还是规矩的把它转化为Integer对象更容易让人看明白。
正常的,
application.setAttribute("count","" + count);
应该写为
application.setAttribute("count",new Integer(count));

相应的
count = Integer.parseInt((String)obj);
也应该改写为
count = ((Integer)obj).intValue();

这里注意下为什么要这样修改。
对于setArrtibute(String name,Object value)方法中,设定进去了什么对象,取出的时候将取出的对象强制转化为对应的对象就可以了。但是如果设定进去的是String对象,取出来时却强制转化为Integer对象,这样,与强制将一个字符串对象转化为Integer对象的道理是一样的,会抛出异常的。

注意:
复制内容到剪贴板
代码:
1.    parseInt(String s)方法会抛出NumberFormatException的异常的,但是这里并没有捕捉这个异常,因为这里的count都是我们的程序控制为其赋值的,出现异常的概率很小。但是并不是说不捕获异常是对的,对可能出现的异常进行捕获是良好的习惯。
2.这里,在application中存储了一个可变的变量,实际应用中,这样的用法比较少。习惯的是在application中存储一些全局的,在程序运行始终都不会改变的静态变量。
Session对象举例:
为了和application对象形成对比,我们采用的代码和上面的结构一样,只是把变量存储在session中来进行测试。
文件名,SessionSample.jsp
复制内容到剪贴板
代码:
<%@ page     contentType="text/html; charset=GBK" %>
<%@ page     import="java.lang.Integer" %>
<html>
<head>
<title>Session test</title>
</head>
<body>
<%
    Object obj = session.getAttribute("count");
    int count = 0;
if ( obj != null ){
        count = Integer.parseInt((String)obj);
}
count ++;
session.setAttribute("count","" + count);
out.print( "now ,this page is visited " + count + " times" );
%>
</body>
</html>
打开一个IE,访问这个页面,发现访问量仍然可以被显示出来,但是新开一个IE打开这个页面,发现这个页面显示的访问量跟另一个页面显示的访问量是不同的,各自互不干扰。这就是session对象存储变量的特点,我们可以用session变量存储跟用户有关的数据,这样用户在访问我们的网站中,需要时时访问的信息,就可以随时得到了。

Request对象。Request对象在这里就不再介绍了。它的setAttribute方法和getAttribute方法和application和session对象的使用方法相同。在纯jsp页面中,它的应用不是很明显,如果有服务器端编码的时候,比如servlet,如果在服务器端的编码中向request对象存储了一个变量,在jsp页面中就可以取得这个变量。

以上,简单的介绍了application,session等对象的特点和用途。细心的读者,会发现它们的setAttribute(String name,Object value)方法中,value可以存储的是一个对象。而java中,对象无处不在,一个数据库连接,一个数据结构等,都是一个对象。所以,在application,session,request对象中,我们可以设定各种各样的对象,不仅仅是一个数据。这样,它们的功能变得灵活而强大了。有许多技术和功能会利用到这一点。但是,向application对象,session对象中存储数据的时候,是要占用一定的内存的。而且在它们的生命周期内,如果没有手动清空它们的话,它们将一直占用内存。只是频繁的向application对象,session对象中存入不同的对象,并不一定是最好的方法,可能会影响到网站的效率和执行速度。所以,在编程过程中,要考虑到所需要的数据的特点,使用的频度,选择最好的方式来取得存储这些数据,并不一定存储在application中,session中就是好的解决方法。
ForgotteN

TOP

用TOMCAT作简单的Java web开发-request,response对象简介

前面几个部分的介绍过程中,在jsp页面间传值的时候,已经介绍了request对象的getParameter方法,在application,session对象介绍的时候,也捎带介绍了request对象的getAttribute,setAttribute方法。但是reuqest对象,以及response对象中还有许多方法。通过这些方法,我们能够得到页面请求跳转时传递的各种信息,也能够执行许多操作,实现许多功能。所以,全面的了解request对象,response对象的各个方法的功能,还是很有必要的。当然,查阅JDK文档,或者搜索,就可以找到许多类似的介绍。最权威的还要算JDK文档帮助文件了。在这里,也只是简单的把它们罗列出来。

Request对象
Request用于客户端向服务器端发送请求。对象中存储了从客户端(比如IE浏览器)向服务器请求时传递的各种信息。
之前的getParameter方法可以得到客户端传递的参数的值,以下还有其他的重要的方法。
复制内容到剪贴板
代码:
public java.lang.Object getAttribute(java.lang.String name)
取得name属性对应的值。
public java.util.Enumeration getAttributeNames()
取得request对象所有属性名字。注意返回的是java.util.Enumeration对象。这里不做介绍,不了解的查阅一下JDK帮助文档。
public Cookie[] getCookies()
取得客户端的cookies对象。返回的是Cookie数组。
public java.lang.String getHeader(java.lang.String name)
取得HTTP协议定义的头文件信息.
public java.util.Enumeration getHeaders(java.lang.String name)
取得指定名字的request Header的所有值。
public java.lang.String getMethod()
取得客户端向服务器端传送数据的方法。GET/POST或者PUT
public java.lang.String getParameter(java.lang.String name)
取得客户端传送给服务器端的参数值。如果对应name参数的值是一个数组,将会返回第一个参数值。
public java.util.Enumeration getParameterNames()
取得客户端传送给服务器端的所有参数的名字。
public java.lang.String[] getParameterValues(java.lang.String name)
取得客户端传送给服务器端的指定参数的所有值。
public java.lang.String getProtocol()
取得客户端向服务器端传送数据所依据的协议名称。
public java.lang.String getQueryString()
取得查询字符串。
public java.lang.String getRequestURI()
取得发出请求字符串的客户端地址。
public java.lang.String getRemoteAddr()
取得发出请求字符串的客户端的IP地址。
public java.lang.String getRemoteHost()
取得发出请求字符串的客户端的名字。
public java.lang.String getServerName()
取得服务器的名字。
public java.lang.String getServletPath()
取得客户端所请求的脚本文件的文件路径。
public int getServerPort()
取得服务器的端口号。
public ServletInputStream getInputStream()
取得客户端向服务器端传送的数据的流。这个方法在文件上传的例子中用过。由于流的特点。在一般的情况下,不建议使用这个方法。
Response对象
Response对象用于向客户端浏览器发送数据,用户可以使用该对象将服务器的数据发送到用户端的浏览器。

Response对象常用的方法。
复制内容到剪贴板
代码:
public void addCookie(Cookie cookie)
添加一个cookie对象,用来保存客户端的用户信息。这个方法可被调用多次,来添加多个Cookie。
public void addHeader(java.lang.String name, java.lang.String value)
添加HTTP头文件信息。
public boolean containsHeader(java.lang.String name)
判断指定名字的HTTP头文件是否已经存在。
public void sendError(int sc)
向客户端发送错误的信息。
public void setHeader(java.lang.String name,java.lang.String value)
设置指定名字的HTTP文件头的值。
public ServletOutputStream getOutputStream()
取得一个用于向response对象输出的流。
public void setContentType(java.lang.String type)
设定返回客户端的内容类型。
public void sendRedirect(java.lang.String location)
设定重定向的URL。
Request,response对象中还有一些方法,这里没有罗列。利用这些方法,我们可以实现许多强大的功能。所以,对这些方法有一定的了解,对一些重要方法的使用的掌握是有必要的,在实现一些功能时,如果不知道解决方法,也可以查找JDK文档,看看有没有支持的函数。
下面写一个JSP页面来看一下这些方法的返回值。然后使用request,response对象其中的几个方法来实现一些功能。
1.Request对象的各个方法的输出。
为了查看request各个方法的输出。我们写一个请求页面,页面中输入各种参数,然后写一个接受请求的jsp页面,在接受请求的页面中打印出各种函数的返回值。
请求页面。
HelloRequest.html

 提示:您可以先修改部分代码再运行
接受请求的jsp页面, HelloRequest.jsp
复制内容到剪贴板
代码:
<%
String strGetHeader = request.getHeader("username");
//取得HTTP协议定义的头文件信息.
if (strGetHeader != null ){
    out.print("getHeader:"+strGetHeader);
    out.print("<br>");
}
java.util.Enumeration en = request.getHeaders("username");
//取得指定名字的request Header的所有值。
if (en != null){
    out.print("getHeaders");
    out.print("<br>");
    while(en.hasMoreElements()){
        String tmpString = en.nextElement().toString();
        out.print(tmpString);
        out.print("<br>");
    }
}
String strMethod = request.getMethod();
//取得客户端向服务器端传送数据的方法。GET/POST或者PUT
if ( strMethod != null ){
    out.print("getMethod:" + strMethod);
    out.print("<br>");
}
String strParam = request.getParameter("username");
//取得客户端传送给服务器端的参数值。如果对应name参数的值是一个数组,将会返回第一个参数值。
if ( strParam != null ){
    out.print("getParam :" + strParam);
}
java.util.Enumeration en1 = request.getParameterNames();
//取得客户端传送给服务器端的所有参数的名字。
if (en1 != null){
    out.print("getParameterNames");
    out.print("<br>");
    while(en1.hasMoreElements()){
        String tmpString = en1.nextElement().toString();
        out.print(tmpString);
        out.print("<br>");
    }
}
String[] strArr = request.getParameterValues("username");
//取得客户端传送给服务器端的指定参数的所有值。
if (strArr != null){
    out.print("getParameterValues");
    out.print("<br>");
    for ( int i=0;i<strArr.length;i++ ){
        out.print(strArr[i]);
        out.print("<br>");    
    }
}
String strPro = request.getProtocol();
//取得客户端向服务器端传送数据所依据的协议名称。
if ( strPro != null ){
    out.print("getProtocol:" + strPro);
    out.print("<br>");
}
String qs = request.getQueryString();
//取得查询字符串。
if ( qs != null ){
    out.print("getQueryString:" + qs);
    out.print("<br>");
}
String ruri = request.getRequestURI();
//取得发出请求字符串的客户端地址。
    out.print("getRequestURI:" + ruri);
    out.print("<br>");
String rAdd = request.getRemoteAddr();
//取得发出请求字符串的客户端的IP地址。
    out.print("getRemoteAddr:" + rAdd);
    out.print("<br>");
String rHost = request.getRemoteHost();
//取得发出请求字符串的客户端的名字。
    out.print("getRemoteHost:" + rHost);
    out.print("<br>");
String sName = request.getServerName();
//取得服务器的名字。
    out.print("getServerName:" + sName);
    out.print("<br>");
String sPath = request.getServletPath();
//取得客户端所请求的脚本文件的文件路径。
    out.print("getServletPath:" + sPath);
    out.print("<br>");
int port = request.getServerPort();
//取得服务器的端口号。
    out.print("getServerPort:" + port);
    out.print("<br>");
%>
运行后,会打印出request对象中的各种信息。而这些信息,有可能就是我们在程序中要用到的。比如,我们要知道一个应用所部署的服务器的URL。就可以用getRemoteHost和getServerPort取得HOST和端口,再取得连接协议,就知道这个应用所在的主机的URL了。

Response对象打印出各个方法返回的信息的方法类似,这里就省略了。

另,上面的方法中有一个Enumeration对象,以及对Enumeration对象的遍历,是以前没有接触到的,遍历方法如下。
复制内容到剪贴板
代码:
java.util.Enumeration en1 = [取得Enumeration对象];
while(en1.hasMoreElements()){
    en1.nextElement().toString();
}
2.Cookie操作。
注意到request对象中有public Cookie[] getCookies()方法,response对象中有public void addCookie(Cookie cookie)方法。我们自然想到了Cookie操作。
Java中,实现Cookie对象的类是javax.servlet.http.Cookie。
Cookie对象有一个构造函数,以及一些常用的方法,这里只用几个基本的方法。想进一步了解Cookie,可以查看J2EE的JDK帮助的Cookie类。
复制内容到剪贴板
代码:
public Cookie(java.lang.String name, java.lang.String value)
public java.lang.String getName()
public java.lang.String getValue()
public void setValue(java.lang.String newValue)
我们做如下的一个功能。在第一个jsp页面新建一个Cookie,并加入到response对象中,再第一个jsp页面跳转的页面取得Cookie并打印Cookie的值。
第一个jsp页面。
TestAddCookie.jsp
复制内容到剪贴板
代码:
<%@page import = "javax.servlet.http.Cookie"%>
<%
    Cookie ck = new Cookie("SinNeR","HelloCookie");
Cookie ck1 = new Cookie("SinNeR1","HelloBlueidea");
    response.addCookie(ck);
response.addCookie(ck1);
%>
<a href = "TestGetCookie.jsp">Go, test Get Cookie</a>
取得Cookie的页面如下。
TestGetCookie.jsp
复制内容到剪贴板
代码:
<%@page import = "javax.servlet.http.Cookie"%>
<%
    Cookie[] ckArr = request.getCookies();
    for (int i=0;i<ckArr.length;i++){
    out.print(ckArr[i].getName());
    out.print(":");
    out.print(ckArr[i].getValue());
    out.print("<br>");
}
%>
运行后,发现打印的信息如下的
复制内容到剪贴板
代码:
SinNeR:HelloCookie
SinNeR1:HelloBlueidea
JSESSIONID:CD9044E663BF0F6F6FF9FAE9210BE976
JSESSIONID:ac47e021a3e686d2c5e39380f573
form:tree-hi:form:tree:configuration
可以看到我们设置的Cookie的信息。至此,我们可以简单的操作Cookie了。
在这里可以看到Cookie中还有JSESSIONID这个值,而JSESSIONID这个值的用途是什么呢?可以在baidu中搜索JSESSIONID,Cookie来看到有人对其进行的详尽的解释。这里就不说了。
Request对象,response对象就暂时介绍到这里,下一帖简单介绍下怎么利用response对象实现文件下载。
包括
复制内容到剪贴板
代码:
为什么要实现文件下载
简单的实现文件下载的方法
如何实现文件的打包下载
文件下载类的抽出和细节处理
ForgotteN

TOP

用TOMCAT作简单的Java web开发--通过response对象实现文件下载

为什么要实现文件下载
文件下载很简单,只要用html的a tag,做一个<a href= "要下载的文件的URL">download</a>就可以了。但是这样有一些缺点存在。
1,用户可以了解你的服务器的一些结构,存在安全性问题。
2,无法对下载的用户进行限制,任何人都可以下载你的文件。
如果你不想让用户了解你的服务器的结构,而且想要特定的用户才能够实现下载功能,这样的下载方法是不可行的,简单的,可以利用response对象来实现文件的下载。
另外,有的网站提供的下载的文件并不存在,而是从数据库或者别的容器里里取得数据,生成文件,让用户下载,这个时候,只能在服务器端中通过程序来实现文件下载了。

简单的实现文件下载的方法
实现文件下载的方法有很多,下面简单介绍2种,并分析它们的优缺点。
1,利用RequestDispatcher对象实现文件下载。
RequestDispatcher对象有一个能够实现页面重定向的功能的方法,Forward方法,通过这个方法,可以将Web浏览器(用户)重定向到一个新的URL。在这里,我们就是利用这个重定向功能,将web请求转移到一个需要下载的文件上。
RequestDispatcher对象可以从ServletContent对象中取得,取得的方法是getRequestDispatcher(String str),其中的参数str是相对于当前应用的路径的文件地址。而ServletContent对象在哪里呢?我们观察以前看到的tomcat/work目录下jsp文件生成的java文件,发现application对象的类型就是ServletContent。
至此,比较清晰了,我们要做的无非就是取得RequestDispatcher对象,然后实现重定向。
复制内容到剪贴板
代码:
RequestDispatcher dis = application. getRequestDispatcher(url);
dis.forward(request,response);
但是这里,我们传递给客户端的一些信息还是没有改变,我们要修改一下,更改ContentType和request对象的头文件信息。
复制内容到剪贴板
代码:
response.setContentType("APPLICATION/OCTET-STREAM");
response.addHeader("Content-Disposition","attachment;filename=" + filedisplay);
关于ContentType,见到有人设置为application/x-download,虽然这样也可以实现,但是在MIME-type中并没有这样的值。还是用MIME-type中存在的值为好。至于MIME-type,暂时只需要知道它和ContentType有关即可。
至此,代码主体部分已经基本完成,可以写出文件下载的jsp程序如下。
Download.jsp
复制内容到剪贴板
代码:
<%@ page import = "java.net.URLEncoder,javax.servlet.RequestDispatcher"%>
         <%
    response.setContentType("APPLICATION/OCTET-STREAM");
    String realFile = "/200621232875.jpg";
    String fileName = "asasas.jpg";
    response.addHeader("Content-Disposition","attachment;filename=" + fileName);
   
    try
    {
        RequestDispatcher dis = application.getRequestDispatcher(realFile);
        if(dis!= null)
        {
            dis.forward(request,response);
        }
    response.flushBuffer();
    }
    catch(Exception e)
    {
        e.printStackTrace();
    }
    finally
    {        
    }
%>
这里realFile定义的是你的应用下的文件路径。
fileName是文件弹出下载框的时候要显示的文件名。写一个测试页面,将链接指向这个页面,或者作一个按钮,提交到这个页面。访问这个测试页面,点击链接或者按钮,会发现弹出一个对话框,让你保存文件。点击保存,可以正常保存文件。
这样,简单的文件下载已经实现了。实际应用时,realFile,fileName可以根据程序的功能要求来取得。比如,从数据库中取得下载文件的地址,或者从请求页面传递过来下载文件的地址。
在测试这个程序的时候,如果在下载对话框弹出的时候点击取消,程序正常,但是打开TOMCAT的输出窗口界面,会发现抛出了Socket异常。为什么呢?因为URL重定向后,用户点击链接弹出下载界面的时候,服务器端和客户端已经建立了链接,当用户点击取消的时候,服务器端的输出流没有正常关闭,所以抛出异常。而我们的程序中,在捕获异常的时候,调用了e.printStackTrace();方法来打印异常,所以就会在TOMCAT的输出窗口打印出了一堆错误信息。这样是不友好的,把打印异常信息的代码注释掉,再次测试页面,发现取消下载的时候不在打印错误信息。但是这只是无视异常,并没有解决这个异常问题。

PS.如果注释掉response.addHeader("Content-Disposition","attachment;filename=" + fileName);这句,再次执行程序,会发现点击下载的按钮的时候,并没有弹出下载对话框,而是在IE中打开了这个文件。设定了头文件信息,这样客户端读取头文件信息的时候,可以确定服务器端返回的流的类型,从而采用合适的方法来处理和现实服务器传输过来的流。这说明,设定头文件信息是有必要的。

这个方法实现下载文件的一个缺点:由于是基于重定向功能实现的文件下载,不能实现将一些数据生成文件流,实现下载的功能,这是这个方法的缺点,但是对于普通的下载要求,已经可以胜任了。

2,利用向response对象中输出文件流来实现文件下载。
在文件上传的例子中,我们取得了request对象的ServletInputStream流,对其进行分析,实现文件的上传。同样,在文件下载的过程中,生成一个文件流,将文件流的数据写入到response对象的ServletOutputStream流中,就可以实现文件的下载。
具体代码如下:
Download1.jsp
复制内容到剪贴板
代码:
<%@ page contentType="text/html;charset=GBK"%>  
<%
    String   filename   ="text.txt";//  
    String   filepath   =   "E:\\tomcatGroup\\jakarta-tomcat-4.0.6\\webapps\\examples\\text.txt";//   
    response.setContentType("APPLICATION/OCTET-STREAM");   
    response.setHeader("Content-Disposition","attachment;   filename=\""   +   filename   +   "\"");   
    java.io.FileInputStream   inStream   =new   java.io.FileInputStream(filepath);   
    byte[]   b   =   new   byte[1024];     
    int   len;     
    while((len=inStream.read(b))   >0)   
    response.getOutputStream().write(b,0,len);   
    response.getOutputStream().close();   
    inStream.close();
%>
这里filename为要显示的文件的名字,filepath为文件的绝对路径。
仍然用刚才的测试页面进行测试,发现也可以正常实现文件的下载。
这个方法的特点是,通过将文件流写入response对象的ServletOutputSream流中,实现文件的下载功能,那么,我们直接把一个字符串写入这个流中,是否可以实现文件下载功能呢?现在我们写一个方法进行测试。
Download2.jsp
复制内容到剪贴板
代码:
<%@ page contentType="text/html;charset=GBK"%>  
<%
    String   filename   ="text.txt";//     
    response.setContentType("APPLICATION/OCTET-STREAM");   
    response.setHeader("Content-Disposition","attachment;   filename=\""   +   filename   +   "\"");   
    String tmpStr = "Hello Stream";
    byte[]   b   =  tmpStr.getBytes();     
    int   len = b.length;
    response.getOutputStream().write(b,0,len);   
    response.getOutputStream().close();
%>
这个程序将Hello Stream这个字符串写入了response对象的输出流。
测试这个程序,保存文件后,发现文件里的内容为Hello Stream。这个简单的程序却可以实现强大的功能。比如说,从数据库中得到的数据,要生成Excel文件(大部分是csv格式的)提供给用户下载,我们就可以把数据库中得到的数据拼成一个流,然后写入response对象的ServletOutputStream流中,这样,就实现了生成Excel文件下载的功能。利用了这个方法后,提供给用户下载的文件并不一定必须是存在的文件了,我们可以自己生成符合要求的文件,提供给用户下载。
在知道某种基础的实现方法后,不要局限在一个应用上,要考虑这个方法都可以实现什么样的功能,可以根据需要,实现我们自己的web系统特有的功能,达到举一反三的效果。当然,这种能力需要我们对程序的实现方法的深刻理解,以及开发过程中的经验积累,多思考,多尝试,把某种方法的用途发挥的淋漓尽致,这样,以后的开发中,会发现自己的能力在潜移默化中逐渐的提高。

如何实现文件的打包下载
前几天看到经典有朋友问怎么实现把选取的文件压缩成一个zip包来提供下载。这里就多说一句。看了第二种文件下载的方法后,我们会想到,如果有一个zip文件,把这个zip文件以流的形式输入到response对象的ServletOutputStream流中,这个功能不就实现了么?那么我们现在面临的问题是什么?现在面临的问题只剩下如果将几个指定的文件生成zip文件了!!!查找JDK帮助,发现JDK中提供了zip文件操作的类,OK,问题已经到了解决的边缘了,现在我们要做的就是了解zip文件操作类的使用方法,如果用这些类来将几个文件生成一个zip文件。思路是不是很简单?
Zip文件操作类以及使用方法。
Zip操作要用到的文件主要在java.util.zip这个包下,压缩的时候常用的类如下。
复制内容到剪贴板
代码:
ZipOutputStream
用于创建zip文件输出流的类。
ZipEntry
用于设定zip中文件属性的类。
Zip文件的操作基本流程。
1.创建zip输出流。ZipOutputStream
2.循环将要压缩的文件输入到zip输出流中。
   读取要压缩的文件的文件名以及其他信息,做成ZipEntry对象,设定到zip输出流中。
   读取要压缩的文件,将其输入到zip输出流中。
操作很简单吧,一下是一段实例的代码。将一个文件生成zip文件。
复制内容到剪贴板
代码:
//压缩文件名
ZipOutputStream zos=new ZipOutputStream(new FileOutputStream("zip文件路径"));
ZipEntry ze=null;
byte[] buf=new byte[1024];
int readLen=0;
File f= new File("要压缩的文件路径");
//创建一个ZipEntry,并设置Name和其它的一些属性
ze=new ZipEntry(f.getName());
ze.setSize(f.length());
ze.setTime(f.lastModified());
//将ZipEntry加到zos中,再写入实际的文件内容
zos.putNextEntry(ze);
InputStream is=new BufferedInputStream(new FileInputStream(f));
while ((readLen=is.read(buf, 0, 1024))!=-1) {
zos.write(buf, 0, readLen);
}
is.close();
zos.close();
这是zip文件操作的一个基本例子,实际实现多文件压缩下载的时候,只要根据文件下载和zip压缩的程序,将它们合并到一起就可以了。
以下为合并后的代码:
DownloadZip.jsp
复制内容到剪贴板
代码:
<%@ page contentType="text/html;charset=GBK"%>
<%@ page import="java.util.zip.*"%>
<%@ page import="java.io.*"%>
<%
    String   filename   ="text.zip";//     
    response.setContentType("APPLICATION/OCTET-STREAM");   
    response.setHeader("Content-Disposition","attachment;   filename=\""   +   filename   +   "\"");
    String[] fileToZip = {
    "E:\\tomcatGroup\\jakarta-tomcat-4.0.6\\webapps\\examples\\text.txt","E:\\tomcatGroup\\jakarta-tomcat-4.0.6\\webapps\\examples\\200621232875.jpg","E:\\tomcatGroup\\jakarta-tomcat-4.0.6\\webapps\\examples\\ddd.xls"
};
//压缩文件名
ZipOutputStream zos=new ZipOutputStream(new java.io.FileOutputStream("E:\\tomcatGroup\\jakarta-tomcat-4.0.6\\webapps\\examples\\" + filename));
ZipEntry ze=null;
byte[] buf=new byte[1024];
int readLen=0;
for (int i= 0;i<fileToZip.length;i++){
File f= new File(fileToZip[i]);
//创建一个ZipEntry,并设置Name和其它的一些属性
ze=new ZipEntry(f.getName());
ze.setSize(f.length());
ze.setTime(f.lastModified());
//将ZipEntry加到zos中,再写入实际的文件内容
zos.putNextEntry(ze);
InputStream is=new BufferedInputStream(new FileInputStream(f));
while ((readLen=is.read(buf, 0, 1024))!=-1) {
zos.write(buf, 0, readLen);
}
is.close();
}
zos.close();
java.io.FileInputStream   inStream   =new   java.io.FileInputStream("E:\\tomcatGroup\\jakarta-tomcat-4.0.6\\webapps\\examples\\" + filename);   
    byte[]   b   =   new   byte[1024];     
    int   len;     
    while((len=inStream.read(b))   >0)   
    response.getOutputStream().write(b,0,len);   
    response.getOutputStream().close();
    inStream.close();
%>
同样,用前面的测试文件下载的方法测试这个文件下载,发现可以正常下载到zip文件。里面的文件解压后也正常。但是这里有个问题,仔细观察代码会发现,现在的实现文件打包下载都是先在服务器端生成一个zip文件,然后再读取这个文件,写入到response对象的输出流中,这样操作的效率很慢,而且生成了服务器端并不需要的zip文件。有没有办法解决这个问题呢,目前想到的解决办法就是在zip文件的流向客户端传输完毕后,删除这个zip文件。至于效率上,暂时没有想到好的方法,如果有朋友有好的方法,指点一下。

至此,文件下载的几个例子已经介绍完毕。接下来的工作跟文件上传的问题一样,将这些代码抽出source,整理成类文件。同时对细节方面进行修改,尽量减少程序的bug。
大体上,要做如下的工作。
复制内容到剪贴板
代码:
1.确认抽出class的接口和大体功能。抽出的class主要是实现文件下载或者文件打包下载的功能。所以需要用户传入以下参数。
    文件下载的类别:普通文件,压缩文件,流文件
    要下载的文件的路径(如果是流文件要提供下载成文件的流)
    要下载的文件显示的文件名
2.文件下载前的check,确定要下载的文件是否存在,不存在的话报错,不进行文件下载操作。
3.文件下载过程中捕获异常,避免IOException等异常出现。
4.代码的优化,逻辑的优化。
5.测试。
上面的几个文件下载的例子已经明白的朋友不妨作一下抽出class的工作,看看自己做成一个class,以及对一个类的驾驭能力如何。到现在,在介绍文件上传方法以及文件下载方法抽出class的时候,已经要求有一定的java的编程基础和经验了。抽出的class中,并没有介绍为什么有的变量是public的,为什么有的变量是私有的(private),也没有介绍静态变量和其他变量的区别。捕获异常的知识也没有介绍。毕竟,我们现在在讨论的是jsp开发。侧重点还是在讨论jsp中各种功能的实现方法以及特点。上面所说的欠缺的知识,都属于java的基础知识。如果要做java的web 开发的话,这些知识的了解是有必要,有帮助的。并不一定要把JDK所有的类全部了解,但是要知道一些基本的类的使用方法,一些基本的编程方法,然后,带着JDK帮助文档,不断的积累经验,才能写出更好的代码。
大家一起努力。
下一帖介绍抽出的jsp文件下载类。

[ 本帖最后由 SinNeR 于 2007-4-5 13:30 编辑 ]
ForgotteN

TOP

用TOMCAT作简单的Java web开发—JSP中实现文件下载

JSP文件下载类整理完成。可以在JSP页面中实现简单的下载,支持文件打包下载功能。支持将字符串或者流生成文件提供下载的功能。但是也有一些缺点。现把这个类的基本介绍总结如下。
名称:jsp页面下载类
作者:SinNeR
Mail:vogoals@hotmail.com
特点及功能介绍:
复制内容到剪贴板
代码:
1.支持单文件下载。支持多文件zip压缩下载。多文件zip压缩可在服务器保留或者删除副本。支持将特定的字符串或者byte数组以指定的文件名提供下载。
2.在下载过程中需要提供response对象。并且选择下载形式。
3.下载处理前先进行check,避免出现错误。
4.下载处理过程中出错时,会报告错误信息。
5.日文系统下文件名是中文时出现乱码问题。
6.处理前需要进行相对繁琐的设定。
现介绍下使用者可见的方法的功能。
复制内容到剪贴板
代码:
设定实现下载必须的response对象。
public void setResponse(HttpServletResponse response)
设定下载方式:0 为普通单文件下载。 1 为多文件压缩成zip包下载。 2 为将指定的字符串等拼接成文件内容提供给用户下载。
public void setDownType(int fileType)
设定下载时显示给用的文件名。
public void setDisFileName(String fileName)
    
压缩文件下载时,设定压缩文件暂时保存的路径(路径为绝对路径)
public void setZipFilePath( String path )
    
压缩文件下载时,设定服务器端生成的压缩文件是否删除。True 删除;false 保留。
public void setZipDelFlag(boolean b)
    
压缩文件下载时,设定要压缩的文件的文件路径(路径为绝对路径)
public void setZipFileNames(String[] fileNames)
单文件下载时,设定下载文件的路径(绝对路径)
public void setDownFileName(String fileName)
    
将字符串生成文件内容模拟下载时,设定文件的内容。参数为字符串(可多次调用)
public int setFileContent(String fileContent)
    
将字符串生成文件内容模拟下载时,设定文件的内容。参数为byte数组(可多次调用)
返回值:0 操作正常; 9 出现IO异常。
public int setFileContent(byte[] fileContent)
    
将字符串生成文件内容模拟下载时,调用此方法结束文件内容设定。
返回值:0 操作正常; 9 出现IO异常。
public int setFileContentEnd()
    
主处理函数。
返回值:    0   处理正常;1  未设定response对象。 2  未设定文件下载方式。  3   未设定要显示的文件名。  4  未设定要下载的文件路径,或者设定的下载的文件路径不存在。  9  IO异常。
public int process()
现在介绍不同下载模式下的简单流程。
单文件下载流程
复制内容到剪贴板
代码:
//实例初始化
JspFileDownload jfd = new JspFileDownload();
//设定response对象    
jfd.setResponse(response);
//设定文件下载模式 0 单文件下载。
jfd.setDownType(0);
//设定显示的文件名 xxxx.xxx
jfd.setDisFileName(filename);
//设定要下载的文件的路径,绝对路径
jfd.setDownFileName(filePath);
//主处理函数。注意处理返回值。
int result = jfd.process();
多文件压缩成ZIP文件下载
复制内容到剪贴板
代码:
//实例初始化
JspFileDownload jfd = new JspFileDownload();
//设定response对象。
jfd.setResponse(response);
//设定下载模式 1 多文件压缩成ZIP文件下载。
jfd.setDownType(1);
//设定显示的文件名
jfd.setDisFileName(filename);
//设定要下载的文件的路径(数组,绝对路径)
jfd.setZipFileNames(fileNames);
//设定服务器端生成的zip文件是否保留。 true 删除  false 保留,默认为false
jfd.setZipDelFlag(true);
//设定zip文件暂时保存的路径 (是文件夹)
jfd.setZipFilePath(zipfolder);
//主处理函数 注意返回值
Int result = jfd.process();
将字符串生成为文件内容,模拟文件下载
复制内容到剪贴板
代码:
//实例初始化
JspFileDownload jfd = new JspFileDownload();
//设定response对象。
jfd.setResponse(response);
//设定下载模式 2 将字符串作为文件内容,实现文件下载。
jfd.setDownType(2);
//设定文件显示的名称。
jfd.setDisFileName(request.getParameter("filename"));
//主处理函数,下载前check,注意返回值
out.print(jfd.process());
//设定要写入文件的内容,参数可为字符串或者byte数组。可多次调用。
jfd.setFileContent(request.getParameter("name"));
//文件内容设定完了,调用函数。    
jfd.setFileContentEnd();
以上就是简单的使用介绍。下面贴出主处理类的代码。由于在日文系统下编辑的文件。注释只能写英文,英文太差-_-b。多包涵。
复制内容到剪贴板
代码:
package com.vogoal.util;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.servlet.http.HttpServletResponse;
/*
* vogoalAPI 1.0
* Auther SinNeR@blueidea.com
* by vogoal.com
* mail: vogoals@hotmail.com
*/
/**
* JSP FILE DOWNLOAD SUPPORT
*
* @author SinNeR
* @version 1.0
*/
public class JspFileDownload {
    /** request object */
    private HttpServletResponse response = null;
    /** file type: -1 un-setting; 0 normal file; 1 zip file ;2 stream*/
    private int fileType = -1;
    
    /** file name to be displayed */
    private String disFileName = null;
    
    /** zip file path */
    private String zipFilePath = null;
    
    /** file to be zipped */
    private String[] zipFileNames = null;
    
    private boolean zipDelFlag = false;
    
    /** file to be downloaded */
    private String downFileName = null;
    
    /** error code 0 */
    private static final int PROCESS_OK = 0;
    
    /** error code 1 */
    private static final int RESPONSE_IS_NULL = 1;
    
    /** error code 2 */
    private static final int UNSET_DOWNLOADTYPE = 2;
    
    /** error code 3 */
    private static final int UNSET_DIS_FILE_NAME = 3;
    
    /** error code 4 */
    private static final int UNSET_DOWN_FILE_NAME = 4;
    
    /** error code 9 */
    private static final int IO_EXCEPTION = 9;
    
    /**
     * set response object
     * @param response response Object
     */
    public void setResponse(HttpServletResponse response){
        this.response = response;
    }
    
    /**
     * set file type 0 normal file; 1 zip file ;2 stream
     * @param fileType
     */
    public void setDownType(int fileType){
        this.fileType = fileType;
    }
    
    /**
     * set display file name
     * @param fileName
     */
    public void setDisFileName(String fileName){
        this.disFileName = fileName;
    }
    
    /**
     * set zip file path
     * @param fileNames
     */
    public void setZipFilePath( String path ){
        this.zipFilePath = path;
    }
    
    public void setZipDelFlag(boolean b){
        this.zipDelFlag = b;
    }
    
    /**
     * set zip file names
     * @param fileNames
     */
    public void setZipFileNames(String[] fileNames){
        this.zipFileNames = fileNames;
    }
    
    /**
     * set download file name
     * @param fileName
     */
    public void setDownFileName(String fileName){
        this.downFileName = fileName;
    }
    
    /**
     * set file content
     * @param fileContent
     */
    public int setFileContent(String fileContent){    
        try{    
            byte[] buffs = fileContent.getBytes("UTF-8");
            response.getOutputStream().write(buffs);
        }catch(IOException e){
            return IO_EXCEPTION;
        }
        return PROCESS_OK;
    }
    
    /**
     * set file content
     * @param fileContent
     */
    public int setFileContent(byte[] fileContent){
        try{
            response.getOutputStream().write(fileContent);
        }catch(IOException e){
            return IO_EXCEPTION;
        }
        return PROCESS_OK;
    }
    
    /**
     * set file content end
     *
     */
    public int setFileContentEnd(){
        try{
            response.getOutputStream().close();
        }catch(IOException e){
            return IO_EXCEPTION;
        }
        return PROCESS_OK;
    }
    
    /**
     * main process
     * @return
     */
    public int process(){
        int status = PROCESS_OK;
        
        status = preCheck();
        if ( status != PROCESS_OK )
            return status;
            
        String fileName = disFileName;
        
        response.setContentType("APPLICATION/OCTET-STREAM");   
        response.setHeader("Content-Disposition","attachment;filename=\"" + fileName + "\"");
        int BUFSIZE = 1024 * 8;        
        int rtnPos = 0;
        byte[] buffs = new byte[ BUFSIZE ];
        FileInputStream inStream = null;
        ZipOutputStream zos = null;
        InputStream is = null;
        String filepath = null;
        try{
          
            if ( fileType == 0 || fileType == 1){
                if ( fileType == 0 ){
                    filepath = downFileName;   
                }else{
                    filepath = zipFilePath + fileName;
                    String[] fileToZip = zipFileNames;
                                    
                    zos=new ZipOutputStream(new FileOutputStream(filepath));
                    ZipEntry ze=null;
                    byte[] buf=new byte[BUFSIZE];
                    int readLen=0;
                    for (int i= 0;i<fileToZip.length;i++){
                        File f= new File(fileToZip[i]);
        
                        ze=new ZipEntry(f.getName());
                        ze.setSize(f.length());
                        ze.setTime(f.lastModified());
        
                        zos.putNextEntry(ze);
                        is=new BufferedInputStream(new FileInputStream(f));
                        while ((readLen=is.read(buf, 0, BUFSIZE))!=-1) {
                            zos.write(buf, 0, readLen);
                        }
                        is.close();
                    }
                    zos.close();
                }
           
                inStream =new FileInputStream(filepath);   
   
                while((rtnPos=inStream.read(buffs)) >0)   
                    response.getOutputStream().write(buffs,0,rtnPos);   
                response.getOutputStream().close();   
                inStream.close();
            }
            if ( zipDelFlag ){
                File fToDel = new File(filepath);
                fToDel.delete();
            }
        }catch(IOException e){
            return IO_EXCEPTION;
        }finally{
            try{
                if ( inStream != null ){
                    inStream.close();
                    inStream = null;
                }
                if ( zos != null ){
                    zos.close();
                    zos = null;
                }
                if ( is != null ){
                    is.close();
                    is = null;
                }
            }catch (IOException e){
            }
        }
        return status;
    }
    
    /**
     * pre check.
     * @return
     */
    private int preCheck(){
        
        if ( response == null )
            return RESPONSE_IS_NULL;
            
        if ( disFileName == null || disFileName.trim().length() == 0 )
            return UNSET_DIS_FILE_NAME;
        if ( fileType == -1 )
            return UNSET_DOWNLOADTYPE;
        else if ( fileType == 0 ){
            if ( downFileName == null || downFileName.trim().length() == 0 )
                return UNSET_DOWN_FILE_NAME;
            else{
                if ( !isFile( downFileName ) )
                    return UNSET_DOWN_FILE_NAME;
            }
            
        }else if ( fileType == 1 ){
            if ( zipFilePath == null || zipFilePath.length() == 0 )
                return UNSET_DOWN_FILE_NAME;
            else{
                if ( !isDirect(zipFilePath) )
                    return UNSET_DOWN_FILE_NAME;
            }
            if ( zipFileNames == null || zipFileNames.length == 0 )
                return UNSET_DOWN_FILE_NAME;
            else{
                for ( int i=0;i<zipFileNames.length;i++ ){
                    if ( zipFileNames[i] == null || zipFileNames[i].trim().length() == 0 )
                        return UNSET_DOWN_FILE_NAME;
                    else{
                        if ( !isFile( zipFileNames[i] ) )
                            return UNSET_DOWN_FILE_NAME;
                    }
                }
            }
        }else if ( fileType == 2 ){
            //doing nothing
        }else{
            return UNSET_DOWNLOADTYPE;
        }
        return PROCESS_OK;
    }
    
    private boolean isFile(String fileName){
        File f = new File(fileName);
        if (!f.exists() || !f.isFile())
            return false;
        return true;
    }
    
    private boolean isDirect(String filePath){
        File f = new File(filePath);
        if (!f.exists() || !f.isDirectory())
            return false;
        return true;
    }
}
至此,jsp页面文件下载介绍完成。
使用的时候,把这个类生成的class文件拷贝到WEB-INF/classes下。(注意保持包的路径)
然后在使用的页面import进这个class即可。
<%@ page contentType="text/html;charset=GBK"%>

注意:
可能存在编码方式的问题,如果出现或者有别的bug请联系我,我来debug。

附件为这个类的source以及测试程序。
附件说明。
复制内容到剪贴板
代码:
1.    单文件下载测试程序
请求页面:downloadSimpleFile.html  下载功能实现页面:downloadSimpleFile.jsp
1.    多文件压缩成zip文件下载的测试程序
请求页面:downloadZipFile.html  下载功能实现页面:downloadZipFile.jsp
3.字符串生成文件内容模拟文件下载的测试程序。
请求页面:downloadStreamFile.html  下载功能实现页面:downloadStreamFile.jsp
4.    Source及class文件。
Source WEB-INF/src/com/vogoal/util/ JspFileDownload.java
Class文件WEB-INF/classes/com/vogoal/util/ JspFileDownload.class
以上文件为SinNeR劳动成果,归blueidea所有,转载请注明出处。
附件: 您所在的用户组无法下载或查看附件,您需要注册/登录后才能查看!
ForgotteN

TOP

用TOMCAT作简单的Java web开发—数据库连接

基本上,每个web应用都会有自己的数据库,选择什么样的数据库,根据web对数据操作的性能要求有不同的选择。从Access,SQLServer,MySQL,Oracle到DB2 …
数据库的连接是一个重要的环节。今天就简单的介绍下jsp中的数据库连接。
一个完整的数据库操作过程大体如下。
1.建立数据库连接。
2.取得Statement对象。
3.执行数据库操作。
4.关闭数据库连接,Statement对象以及其他打开的资源。
以下按照各个环节,依次介绍其简单的程序和注意点。
[建立数据库连接]
JDK中提供了DriverManager类来帮助用户实现数据库的连接。基本的用法大体如下。
复制内容到剪贴板
代码:
Class.forName("Driver class");
Connection conn = DriverManager.getConnection("各种参数");
Class.forName("");
中的字符串参数指定了数据库的驱动class的路径。这句代码加载了这个数据库类,同时初始化这个类的静态初始化部分。这样,DriverManager在取得数据库连接的时候,就可以找到对应的数据库驱动。
不同的数据库有其不同的驱动类,而这些驱动类,往往都在其提供的jar包里。所以在进行数据库连接的时候,要在classpath中加入这个包含该驱动类的jar包。不然在程序运行过程中无法加载这个类,会抛出ClassNotFoundException。
同时,数据库的连接还要告诉DriverManager一些数据库连接的基本信息,比如,数据库的URL,数据库连接的用户名和密码。不同的数据库连接要求的信息不同,在实现数据库连接的时候,要了解自己的数据库需要什么样的连接信息。
比如,Oracle9.2的数据库,其所用的驱动在classes12.jar这个包里,驱动的名称叫做oracle.jdbc.driver.OracleDriver。连接需要URL,用户名和密码。可以这样来写数据库连接。
复制内容到剪贴板
代码:
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@10.169.183.175:1521:ORCL1","test","test");
以下拿几个常见的数据库为例,介绍下他们的数据库连接。
Oracle数据库,THIN连接。
复制内容到剪贴板
代码:
Class.forName("oracle.jdbc.driver.OracleDriver");
String url="jdbc:oracle:thin:@localhost:1521:orcl";  
String user="test";
String password="test";
Connection conn= DriverManager.getConnection(url,user,password);
Oracle数据库的连接需要的参数如下:
驱动程序:oracle.jdbc.driver.OracleDriver
URL:jdbc:oracle:thin:@localhost:1521:orcl
其中localhost为数据库所在的服务器的HOST,也可以写IP。
1521为默认的Oracle数据库连接的端口。
Orcl为数据库的SID。
用户名;
密码。

MySQL数据库连接:
复制内容到剪贴板
代码:
Class.forName("com.mysql.jdbc.Driver");
String url ="jdbc:mysql://localhost/myDB?user=soft&password=soft1234&useUnicode=true&characterEncoding=8859_1"  
Connection conn= DriverManager.getConnection(url);
MySQL数据库连接需要的参数如下:
驱动程序:com.mysql.jdbc.Driver
URL:jdbc:mysql://localhost/myDB?user=soft&password=soft1234&useUnicode=true&characterEncoding=8859_1
其中,localhost为数据库服务器的地址。MyDB为数据库名。后面的参数分别为用户名,密码,useUnicode和编码方式。
编码方式一般用UTF-8和GBK。

SQLServer的数据库连接:
复制内容到剪贴板
代码:
Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver");
String url="jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=mydb";
String user="sa";
String password="";
Connection conn= DriverManager.getConnection(url,user,password);
SQLServer数据库连接需要的参数如下:
驱动程序:com.microsoft.jdbc.sqlserver.SQLServerDriver
URL:jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=mydb
其中localhost为数据库的地址。1433为默认端口。Mydb为数据库的名称。
用户名;
密码。

Access的数据库连接:
复制内容到剪贴板
代码:
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver") ;
String url="jdbc:odbc:Driver={MicroSoft Access Driver (*.mdb)};DBQ="+application.getRealPath("/Data/ReportDemo.mdb");
Connection conn = DriverManager.getConnection(url,"","");
Access数据库连接需要的参数如下:
驱动程序:sun.jdbc.odbc.JdbcOdbcDriver
URL:"jdbc:odbc:Driver={MicroSoft Access Driver (*.mdb)};DBQ="+application.getRealPath("/Data/ReportDemo.mdb")
其中application.getRealPath("/Data/ReportDemo.mdb")为取得数据库的绝对路径。在这里为默认数据库是存放在对应的web应用的Data文件夹下的。也可以不这么写,直接写出数据库的绝对路径也可以。
注:这里的access数据库连接采用的是JDBC,ODBC桥的方式进行连接。

取得Statement对象
Statement对象分几种,主要分为普通的Statement对象,PreparedStatement,CallabledStatement。都是从数据库连接中取得的。
1.普通的Statement对象
常见的取得方法如下:
复制内容到剪贴板
代码:
conn. createStatement();
使用它,可以完成大部分的数据库的操作。
2.PreparedStatement对象。
常见取得方法如下:
复制内容到剪贴板
代码:
Conn. prepareStatement(String sql);
PreparedStatement对象的特点是将SQL文中的变量提前用?的形式预先设定入数据库。然后依次设定各个参数的值来进行数据库检索。

3.CallabledStatement对象。
CallabledStatement对象的取得方法:
复制内容到剪贴板
代码:
conn.prepareCall(String sql);
这个对象的特点是,可以调用存储过程,在调用前设定其输入输出参数,这样在执行数据库操作后,输出参数会返回结果。

关闭数据库打开的资源
上面介绍了数据库的连接和Statement的取得方法。这些对象,以及后面将要遇到的ResultSet对象在使用完后,要调用close方法关闭对象。这是良好的编程习惯,而且是有必要的。也许有人会说java不是有垃圾回收机制么?不使用的对象或者过了生命周期的对象,java虚拟机不是会自动回收么?是这样的,但是建立数据库连接的时候,数据库服务器也打开了一个连接,在用户没有关闭连接的时候,这个连接将一直打开。而java虚拟机的垃圾回收机制只会释放掉不适用的类占用的内存,而不会去管这个类占用的外部的资源。这样,在程序不断的执行过程中,数据库建立的连接会越来越多,最终有可能造成数据库服务的崩溃。

前几天公司接到一个客户反映的一个java的BS系统出的问题就是这个问题。由于有一个程序的逻辑在中途有退出操作的可能,而退出操作的时候并没有关闭Statement对象,最终导致Oracle数据库打开的Cursor过多而不提供服务。这种潜在的问题是危险的而且不容易发现的。当时的解决方法是打开程序,让它运行一遍,把程序所有的逻辑都跑一遍,然后打开Oracle数据库,察看未关闭的Cursor,语句如下:
复制内容到剪贴板
代码:
select user_name, status, osuser, machine, a.sql_text from v$session b, v$open_cursor a where a.sid = b.sid
执行后,发现了许多处于INACTIVE状态的未关闭的Cursor,这个就是程序中Statement对象没有关闭的地方。根据其SQL_TEXT,在程序中找到这个SQL文被执行的部分,从而修正对应。
之所以说这句题外话是提供一个解决这种问题的一个可以尝试的方法。毕竟,拿来一个自己没有接触的系统,要找出某个部分Statement对象没有关闭的问题,形同大海捞针,而这个方法,可以快速准确地定位错误的地点。

执行数据库操作
以上已经简单的介绍了数据库连接的大体流程,下面介绍数据库执行操作部分。由于执行数据库操作可以看到对数据库的影响。所以这里我们建立一个数据库连接,执行操作,看得到的结果。数据库采用大家可能都有的Access数据库。
首先,在我们tomcat下的一个应用下建立一个数据库dbtest.mdb。然后在里边新建一个名为m_usr的表。
表的结构如下
ID                    自动增长
NAME                   text型(50)
BIRTHDAY               日期型
AGE                     数值型
CONTENT                 text型(255)
然后在里边随便插入几条数据测试用。

以下为数据库连接的基本代码。
复制内容到剪贴板
代码:
<%@page import = "java.sql.*"%>
<%@page import = "java.lang.*"%>
<%
Connection conn = null;
Statement st = null;
try{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver") ;
String url="jdbc:odbc:Driver={MicroSoft Access Driver (*.mdb)};DBQ="+application.getRealPath("/dbtest.mdb");
conn = DriverManager.getConnection(url,"","");
st = conn.createStatement();
//数据操作测试部分开始
//数据操作测试部分结束
}catch( ClassNotFoundException e ){
    out.print(e.getMessage());
}catch( SQLException e ){
    out.print(e.getMessage());
}finally{
    try{
if ( st != null ){
        st.close();
        st = null;
}
if ( conn != null ){
        conn.close();
        conn = null;
}
}catch( SQLException e ){}
}
%>
这里注意一下try.. catch.. finay的用法。
Try catch是捕获可能出现的异常的用法。当try中的语句出现异常的时候,就会在catch部分捕获这个异常,然后执行catch里面的语句。而finally语句是让程序无论执行到哪里,最终都会执行finally中的语句。而在这里,close()方法也可能出现异常,所以也要捕获异常。捕获异常是让程序变得友好的一种方法,不进行捕获的话,出现异常的时候,页面就会打出所有的错误信息。
捕获什么异常并不是凭空出现的,这个是要察看JDK的帮助文档来了解的。每个方法如果可能出现异常。JDK中会明确的指出来的。

Statement对象的使用
用Statement对象来进行SELECT,INSERT,DELETE,UPDATE操作。
复制内容到剪贴板
代码:
//检索数据库
out.print("SELECT TEST <br>");
String sql = "SELECT * FROM m_usr";
ResultSet rs = st.executeQuery(sql);
String name = null;
String birthday = null;
String age = null;
String content = null;
while(rs.next()){
    name = rs.getString("name");
    birthday = rs.getString("birthday");
    age = rs.getString("age");
    content = rs.getString("content");
    out.print("name:" + name + ";birthday:" + birthday + ";age :" + age + ";content:" + content + "<br>");
}
out.print("SELECT TEST OK!!!<br>");
//插入一条记录。
name = "vogoal";
birthday = "2006-06-06";
age = "1";
content = "a poor website";
sql = "insert into m_usr (name,birthday,age,content) values('" + name + "','" + birthday + "'," + age + ",'" + content + "')";
st.executeUpdate(sql);
out.print("INSERT TEST OK!!! <br>");
//更新一条记录。
name = "unforgivabled SinNeR";
sql = "update m_usr set name = '"+ name + "' where ID = 1";
st.executeUpdate(sql);
out.print("UPDATE TEST OK!!! <br>");
//删除一条记录。
sql = "delete from m_usr where ID = 2";
st.executeUpdate(sql);
out.print("DELETE TEST OK!!! <br>");
把这些代码放到刚才的数据库连接的代码里,存成jsp文件,放在自己的应用下,测试一下。
执行操作后,会打印出类似如下的信息。
复制内容到剪贴板
代码:
SELECT TEST 
name:unforgivabled SinNeR;birthday:1918-07-01 00:00:00;age :89;content:Somewhere I Belong
name:LinkinPark;birthday:1989-09-09 00:00:00;age :18;content:I also like this ID.
SELECT TEST OK!!!
INSERT TEST OK!!!
UPDATE TEST OK!!!
DELETE TEST OK!!!
表明数据库操作成功。察看数据库,可以看到数据库里的数据被更新。
注,这里的打印的信息是多次刷新页面的结果,跟第一次运行的不同。

PreparedStatement对象的使用
PreparedStatement对象的原理还是和Statement对象类似的,毕竟最终的实现的功能是一样的。这里就拿检索数据库为例简单介绍一下吧。
复制内容到剪贴板
代码:
String sql = "SELECT * FROM m_usr WHERE name = ?";
PreparedStatement pst = conn.prepareStatement(sql);
pst.setString(1,"LinkinPark");
ResultSet rs = pst.executeQuery();
String name = null;
String birthday = null;
String age = null;
String content = null;
while(rs.next()){
    name = rs.getString("name");
    birthday = rs.getString("birthday");
    age = rs.getString("age");
    content = rs.getString("content");
    out.print("name:" + name + ";birthday:" + birthday + ";age :" + age + ";content:" + content + "<br>");
}
out.print("SELECT TEST OK!!!<br>");
把这部分代码拷贝到上面的数据库连接代码中。存为jsp文件,放在web应用下,然后进行访问。
得到类似如下的输出:
复制内容到剪贴板
代码:
name:LinkinPark;birthday:1989-09-09 00:00:00;age :18;content:I also like this ID.
SELECT TEST OK!!!
表明检索数据库成功。

由于对mdb的Access数据库中没有存储过程,CallableStatement的使用在这里就不介绍了。
上面的代码仍然存在着问题,就是ResultSet没有关闭,PreparedStatement对象也没有关闭。这是不可取的,一次两次操作不会出现问题,但是多人同时访问的时候,时间长了,问题就积累出现了。所以说,关闭掉不用的数据库对象,不仅是编程习惯的问题,如果不注意,系统有可能有严重的隐患。

上面的介绍实际上只是介绍了数据库连接类的冰山一角。要能够熟练的使用它们,要认真的阅读JDK帮助文档,把每个方法都了解一下。同时要不断的尝试,比较不同的方法的异同。这样才能够真正的掌握他们的用法。写过类似的一个介绍,http://bbs.blueidea.com/thread-2674405-1-20.html,比较粗鄙,可能有错,无事的时候不妨一看。

附件:
ACCESS数据库连接测试用文件。注:由于数据库里的数据已有变动,测试的时候请修改测试代码。
附件: 您所在的用户组无法下载或查看附件,您需要注册/登录后才能查看!
ForgotteN

TOP

原来四对曰开发呀

TOP

用TOMCAT作简单的Java web开发—连接池的概念与简单实现

本节介绍连接池的概念与JSP中的简单实现。连接池,顾名思义,就是存放了一堆数据库连接对象的容器。在web开发中,一些用户量小,数据库访问频率小的网站,可以不考虑使用连接池。但是如果用户量比较庞大,访问数据库的频率比较大的时候,用一般的方法进行数据库访问,频繁的打开关闭数据库,可能给数据库造成很大的压力。这个时候,数据库的访问就是整个网站的效率的一个瓶颈问题了,需要考虑怎么样能解决这方面的压力。
对应的解决方法,数据库端,程序端都有对应的解决方案。程序端一种比较常见的方法,就是采用连接池。
连接池的工作原理基本如下,在用户要访问数据库取得数据库连接的时候,并不是直接建立连接,而是到连接池中取出可用的数据库连接,当用户访问完数据库后,把这个数据库连接对象返回给连接池进行管理。在web运行的整个过程中,数据库的连接不会被关闭。这时候我们会想,前面不是说了每次使用完数据库,就要关闭连接么?为什么这里不关闭,这不是容易产生问题么?在这里,数据库的连接被连接池所管理,连接池定义了最大的连接数目,连接池中存放着当前空闲的连接,同时记录着当前和数据库的连接数,当当前没有空闲连接,而数据库的连接数已经达到了连接池允许的最大连接数的时候,连接池不会再次申请数据库连接,而是做相应的对策,比如警告用户,提示用户过一段时间再访问。
以下用图表来大体的表示下1.没有使用连接池,没有正确关闭数据库连接 2.没有使用连接池,正确的关闭数据库连接  3.使用连接池  3种情况下的数据库连接情况。通过图表,应该看得清晰些。
如下,在不同的时间内,web依次进行了A,B,C,D,E,F,G,H这几个对数据库的查询。其中纵向是表示时间轴。



由上面可以看出3者的区别。1图中,没有正确的关闭数据库连接,这样长时间访问后,数据库部分提供的资源很快就被耗尽。2图中,没有使用连接池,正确的关闭数据库,可以看出同一时间最大的连接数是3,和使用连接池的一样,但是进行了8次的数据库连接关闭。而图3,利用了连接池后,和数据库的连接只进行了3次。
如果这样的过程在实际的web中测试的时候,效率的差别就更明显了。由此可见,使用连接池可以解决对于数据库频繁访问造成的效率低下的问题。
现在,我们开始在jsp中简单的实现连接池,我们只求能够实现它,并不考虑细节方面的问题。
首先,要选择存放连接池对象的地方。可以看出,这个连接池在web运行的整个过程中都是可见的,而且所有的用户都可以访问,跟用户无关。很容易想到application级别的变量和这个要求完全符合。
接着,我们要找到一个合适的容器来存储连接池,常用的容器有很多,Vector,Hashtable,ArrayList,数组。。。我们的连接池,需要可以容易的放入数据库连接对象,取出数据库连接对象,用数组似乎可以,但是仔细比较一下,用ArrayList更好,因为ArrayList的大小是不固定的,它可以随时告诉我们当前可用的空闲的连接池的数目。最后,我们要决定连接池允许的最大连接数目,这个不是凭空想象出来的,这个是根据系统的用户多少,访问多少来科学的找出的一个合适的数字。要经受得起压力测试的,当然,如果有一定的经验后,可以根据经验判断需要的连接数。以上都分析完了,似乎我们可以着手了。
但是还有一个小细节问题,那就是连接池中的连接的打开和关闭的时机。很明显,关闭连接要在整个网站的服务关闭的时候关闭。而建立连接,我们并没有必要在一开始就按照最大数目建立所有的连接,用户要访问数据库的时候,我们先察看是否有空闲的连接,如果没有,并且当前已经建立的连接数小于最大连接数的时候,再新建一个连接。
经过以上的分析,我们可以开始编码实现了。
首先,我们需要一个在application变量中初始化连接池的jsp程序,在web应用搭载起来的时候,就先运行这个程序,把连接池建立好。以下是基本的代码。Application变量的使用前面已经介绍了,这里,我们只要专心怎么建立这个连接池就好。
我们需要作如下的动作:
设定允许的最大连接数
设定参数用来记录当前已经建立的连接数
初始化用来存储空闲连接的容器(这里是ArrayList)
StartConnectionPool.jsp
复制内容到剪贴板
代码:
<%
    int DBCONN_MAX_COUNT = 10;
    int CURRENT_CONN_COUNT = 0;
    ArrayList al = new ArrayList();
    Application.setAttribute("DBCONN_MAX_COUNT",String.valueOf(DBCONN_MAX_COUNT));
    Application.setAttribute("CURRENT_CONN_COUNT ",String.valueOf(CURRENT_CONN_COUNT));
    Application.setAttribute("CONN_POOL", al);
%>
那么在进行数据库访问的时候,怎么申请数据库连接呢?按照刚才的逻辑,可以整理基本的思路如下。
1.判断当前的连接数是否小于允许的最大连接数。
2.如果否,表明已经超过当前最大连接,报错。
3.如果是,判断存储数据库连接对象的ArrayList的大小是否大于0
4.如果大于0,从ArrayList中取得数据库连接,同时将ArrayList中的这个数据库连接删除。
5.如果不大于0,表明当前没有空闲的数据库连接,新建一个数据库连接。
逻辑很清晰吧。那么我们写出数据库连接的伪代码(指不严谨的代码)
复制内容到剪贴板
代码:
<%
Connection conn = null;
int DBCONN_MAXCOUNT = application.getAttribute("DBCONN_MAX_COUNT").toString().intValue();
int CURRENT_CONN_COUNT = application.getAttribute("CURRENT_CONN_COUNT ").toString().intValue();
ArrayList al = (ArrayList)application.getAttribute("CONN_POOL");
if (DBCONN_MAXCOUNT = CURRENT_CONN_COUNT ){
    out.print("The connection pool is full");
}else{
    if ( al.size() > 0 ){
    conn = (Connection)al.get(0);
    al.remove(0);
}else{
    //新建一个数据库连接
    Conn = getConnection();
    CURRENT_CONN_COUNT++;
}
}
%>
数据库释放连接的逻辑相对简单,只要把连接放到存放连接的ArrayList中即可。但是注意,仍然要调用close()方法来释放数据库的连接指针。
复制内容到剪贴板
代码:
    ArrayList al = (ArrayList)application.getAttribute("CONN_POOL");
    al.add(conn);
    application.setAttribute("CONN_POOL", al);
连接池释放。释放的时候,也要专门调用一个方法,释放所有的连接。
releaseDBConnectionPool.jsp
复制内容到剪贴板
代码:
Connection conn = null;
ArrayList al = (ArrayList)application.getAttribute("CONN_POOL");
for ( int i=0;i<al.size();i++ ){
    conn.close();
    conn = null;
}
//由于这个时候web应用已经关闭,application对象中的变量的生命周期也跟着结束,没有必要将其他Attribute置空。
以上就是连接池的简单伪代码。之所以叫伪代码是因为它们无法编译通过,因为没有import对象,没有捕获异常,取得数据库连接的方法也没有写。在这里只是提供了连接池实现的简单思路,真正要实现的话,还是要把连接池以及数据库连接抽出class为好。

至此,已经把jsp页面中常用的简单的技术介绍完了。当然,还有很多知识没有涉及。但是,现在如果从头走到这里的话,应该已经可以自己利用jsp开发简单的web应用了。比如,留言本。许多学习asp开发的朋友喜欢开始做一个简单的可以运行的程序出来。那样应该会给自己以成就感,能够鼓励自己进一步学习。出于这个角度考虑。下一帖也开始,利用前面介绍的知识,开发出一个简单的留言本。从而走上我们真正的jsp建站之路。至于纵深学习下去的话,只会if else的我已经提供不了太多经验了,但是仍然希望能够和大家一起探讨学习。
以下的几个帖子主要做如下打算:
1.       利用当前学到的知识开发一个jsp留言本。
2. 补充一些其他的相关的jsp知识介绍。
3. 补充一些jsp中常用的插件的实现方法探讨以及实现好的可用的类。比如csv文件操作的类。如果有朋友需要某种文件操作的类,而且比较通用的话,可以提出来,我们一起分析实现方法,最终做成。

综合种种原因考虑,接下来的一小段时间将主要集中在jsp留言本的开发的介绍。
由于留言本相对来说访问量,对数据库的压力都不是很大。所以在这里数据库使用Access,编码部分以jsp为主。开发后期后可能会修改数据库的接口,提供支持其他数据库的不同版本。但是整个开发过程中是学习为主要目的。所以,后续的操作就有点画蛇添足了。

在这里感觉赛扬,老农的鼓励,写的东西并不好,看得人估计也不是很多,但是我想既然开始了,就让它走下去。

[ 本帖最后由 SinNeR 于 2007-4-6 18:12 编辑 ]
附件: 您所在的用户组无法下载或查看附件,您需要注册/登录后才能查看!
ForgotteN

TOP

写的东西并不好,看得人估计也不是很多,但是我想既然开始了,就让它走下去。
====================================================
至少我在看~
认为是对就做下去~
等待中~

TOP

写得好!完全支持!
SinNeR 斑竹应该更加贯彻由浅入深的方法
让我这样的初学者能跟着你的步伐一起前进!
辛苦了!期待下文!

TOP

楼主你太伟大了.在此鞠躬感谢!
一只小毛毛虫,希望有一天不再啃菜叶了,化为美丽的蝴蝶.

TOP

用TOMCAT作简单的Java web开发—从最简单的留言本开始-1

感谢楼上的几位的支持,这几天太忙了,周末也出去了,所以一直没有更新。另一点疑惑的是总感觉有很多东西,有时候又不知道从何处说起。

今天我们开始尝试作一个简单的web应用-留言本。开始做一个网站的时候,是要做许多准备工作的,这是开发出来的网站的健壮性,可移植性,可二次开发性的保障。简单来说,首先,我们要考虑开发的网站将会有多大的用户访问量,从而根据用户的访问量来估计我们需要什么样的web服务器,什么样的数据库以及采用什么样的框架来满足这样的用户的要求。一般情况下,简单的web应用是不太需要考虑这些问题的,但是如果你做的web应用有比较高的效率要求或者比较高的安全或者其他方面的要求。那么,选择合适的web服务器,数据库服务器以及合适的框架技术将是很重要的一个环节。比如,银行所用的系统,对安全性,稳定性要求很高,而且可能要承载很大的访问量。数据库,应该会选择DB2这样的大型数据库,不会选择相对安全性,稳定性等方面相对差些的access数据库。所以,在开发前,要考虑好整个系统的架构的问题。
但是我们今天要实现的只是一个简单的jsp留言本。目的只是用前面介绍的一些知识和技术点来实现一套可以运行的程序,而且,本身,留言本对安全性,稳定性以及访问量的要求不是那么高。所以,关于系统架构方面我们不需要考虑太多。至于程序的可移植性,可二次开发性,这里也暂不考虑。只要能实现简单的留言本的功能即可。
项目名:jsp留言本
使用数据库:Access数据库
Web服务器:TOMCAT
使用的框架,技术:无,简单的jsp技术。

确定了架构后,需要简单分析下留言本的功能和业务逻辑。这里主要根据用户级别以及该用户对应的需要什么样的功能来划分。最简单的留言本,只需要一个管理员级别的用户,可以修改,回复,删除留言,同时进行作为一个用户所拥有的基本操作(更改密码等)。而其他的用户,全部以匿名身份登录,只可以进行留言。留言本的功能比较简单,我们也应该非常清楚。就不再多说了。以下为留言本对应不同的用户的画面跳转基本流程。

对于留言本这种功能简单清晰的web程序来说,用户可能不需要这些流程图或者能够体现流程的设计书。但是如果系统比较庞大,而且是多人开发的话,这样的流程图,甚至是设计书是有必要的。

功能大略分析完成后,我们需要着手数据库的设计。数据库的设计也是一个重要的环节。如果数据库设计的比较清晰,比较合理,那么程序处理起来就会相对轻松,而且数据访问的效率也会提高。相反,如果数据库设计的拖沓,冗杂。程序处理起来可能相应的变得复杂,也有可能影响到数据访问的效率。而留言本由于结构比较简单,所以数据库的设计也相对比较简单。经过思考后,我们首先需要存放留言的表,这个表里主要存储了该条留言对应的留言者的基本信息,留言的内容等信息。由于有管理员这个用户,我们需要一个用户表,这个表主要存储了管理员这个用户的用户名,密码等信息。而考虑到留言本的移植性,上面我们设置了属性设置栏。属性设置主要用来设置留言本的基本信息,比如留言本的名称,留言本每页显示的条数等信息。所以针对它,我们也要给它设计一个表。
现在,根据我们的功能要求,依次设计出3个表来。
留言表:
表名:topic
ID              主键
u_name              留言用户名              text型 20位
u_ip              留言者IP              text型 20位
u_mail              留言者邮箱              text型 50位
u_face              留言者头像编码              text型 2位
u_qq              留言者QQ              text型 11位
u_host              留言者主页              text型 100位
u_sexy              留言者性别              数值型 0:男 1:女
title              标题                     text型 40位
content              内容                     备注型
ad_date              提交日期              日期型
up_date              修改日期              日期型
up_u_name       修改者用户名              text型 20位
content_rep       回复内容              备注型
re_date              回复日期              日期型
re_u_name       回复者用户名              text型 20位
del_flg              是否删除              数值型 0:未删除 1:已删除

用户表:
表名:user
ID              主键
username       管理员用户名              text型 20位
upass              管理员密码              text型 20位

系统属性设置表:
表名:profile
ID              主键
linkurl              网站链接              text型 50位
pagecount       每页显示留言数              数值型
webtitle       留言本名称              text型 50位

数据库建立完毕,把它名字存为data.mdb,存放到自己现在可用的应用的data/目录下备用。

现在讨论一下jsp页面程序结构的问题,虽然在这里我们打算只用jsp页面来实现留言本。但是,把程序和html混杂在一起,让业务逻辑看起来乱糟糟的程序,还是不容易维护和更新的。所以,我们还是需要稍微思考一下页面的结构,尝试将表现层和处理层稍微分开,那样程序看起来会更清晰,也方便以后修改更新。在这里,我们尝试一下用这样一个方法来实现,每个页面有跳转请求的时候,我们让它们都跳转向一个固定的页面,这个页面根据请求的不同来调用不同的程序来处理,然后再返回到相应的页面。这样,所有的处理程序都被集中到这个固定的接受请求的页面进行处理。程序似乎清晰了些。并且,对数据库的访问操作,也全部集中在主处理页面这里,已经初步的把显示层和处理层分开了。那么,每个提交页面在跳转向这个页面的时候,都要提交一个参数,告诉这个固定页面,请求是从哪里来的,要做什么样的操作。
由上面的分析,我们可以画出网站页面跳转的基本流程如下:


至此,整体上的分析已经完成,现在考虑单页面结构的问题了。很明显,留言本的头部和底部是一样的,而中间的主体部分,则根据功能的不同,对应不同的页面。所以,我们的页面可以划分为3个部分。头部,中间的主体部分,底部。而在开发阶段,可以先不管头部与底部的部分,先开发出我们的程序部分,最后再用jsp的include 标签把头部尾部文件包含进去。
其它的准备工作。
1.       注意到数据库里有留言者头像这一栏,所以我们要为留言者选择一套头像图标。
2.开发过程中没有用到CSS,并且打算用表格布局,如果对这个留言本有兴趣,要标准化,可以在开发后修改相应的页面。

[ 本帖最后由 SinNeR 于 2007-4-10 21:36 编辑 ]
附件: 您所在的用户组无法下载或查看附件,您需要注册/登录后才能查看!
ForgotteN

TOP

正在认真的看,多谢楼主!

TOP

顶一个 学习中·~~~~~
本人也是初学jsp 呵呵 这些问题都在逐渐遇见 呵呵
坛子里全是asp php很少看到jsp的身影~
人善天不欺

TOP

学习中!支持!
www.sjweb.cn

TOP

真是一个及时的好贴

早就想有这样的一个教程了,我会认真看认真做的,非常感谢。

TOP