一篇在我开始jsp之路的时候看到的文章(1)

入门 专栏收录该内容
6 篇文章 0 订阅

这篇文章是在05、06年分两次完成的(内容含盖了学习JSP初期经常会遇见的问题,包括配置服务器、操作数据库、javabean、servlet、超长文本的保存、保存图片文件到数据库、文件的上传、分页的实现、Model2、甚至awt画图等),之前在CSND发过,现在我把它贴到这里,作为以后工作、学习的参考资料,备查。

一个Jsp初学者的学习过程


前言

从现在开始我要把我学习Jsp的过程写出来。这些东西都是我从书本、网络上搜集整理的,我把它们据为己有后写这样一篇总结供如我一样的初学者参考。

请容许我在这里说一些和读者无关的话,对此不感兴趣的请直接跳到第一章或第二章。
我在2003年七月大学毕业,这之前学了四年计算机专业,由于基本是属于文科学校的计算机专业,学校女生少男生多学习的风气极受影响,所以可以想象我毕业时候的水平。在学校时学习的大多是枯燥的计算机理论知识,我虽知其有用,但实在不感兴趣,我感兴趣的是编码,于是学了一个学期的pascal,又编了一个学期的C(学C的时候是热情高涨的,甚至教课的老师允许或者说支持我逃课回去编码),遇指针、链表,不成,弃之,转而学C++,不半月,遇“对象”、“类”、“继承”,不成,复弃之,终不成,无奈之下,日夜游戏,不思进取,不数载,毕业。
这以后直至现在一直在一省直机关的信息中心工作,由于是“事业单位”,所以工作很闲,头半年学了两个月的html,后又学一个月的Asp,由于缺少压力,最终都放弃了。直到我们单位开始了一个项目——电子政务,我的境况有了转变。
我们找来了两家公司给我们开发软件,领导说让我跟一跟,学写东西,由于当年C给我的打击实在太大了,我已认定自己不是那块料,是不具有学程序的天赋的,所以有些不情愿,但是终于由于太闲而且又不甘心自己“一点技术含量都没有”,最终选择了学习Jsp。我找给我们开发软件的一个程序员朋友,向他要来了Tomcat、jsdk和盗版的Oracle,编写平台就先暂时用我比较熟悉的Dreamweaver,英文也差,就没用Jbuilder、Jcreator什么的,至于资料,没有,只有网络,我就这样开始了我的Jsp之旅。
我没想到的是当年学的那一点点C、html、Asp让我不怎么费力的就走进(近)了Jsp的大门,这速度至少出乎了我的意料。

值得说明的一点是:我学Jsp完全是从实例入手,这之前我的相关理论知识基本为零,所以我在对代码的理解上(很大程度表现在注释上)是使用自己的“土语”的,而不是“术语”。我的原则是这篇文字能够让和我一样的初学者能够看懂。
对于一个毫无基础的人来说,你对他说“类”、“对象”、“接口”、“继承”这些名词,他是无法理解的,因为它们太抽象,所以我必须通过实例和不怎么规范的语言使它们具体化一些。
由于我的水平实在有限,可能会出现很多错误,尤其在对某些“代码”的理解上,但是我勇于拿出板砖,希望有热心的高手使劲扔玉^_^这是我的邮箱:。
最后,感谢互联网及在其上分享自己经验的程序员、我的单位的领导、同事、程序员朋友孙罡、大学的朋友彭涛等。

第一章 配置服务器环境

Jsp全称是Java Server Pages(而不是JavaScript的缩写,JavaScript是使用Java语言的一种脚本语言),用我的话说,它就是一堆使用于网页浏览器上的代码(或者说代码规范),从这个角度讲和Asp类似。它并不是一种编程语言,但是它需要一种编程语言来编写其中的程序,正如Asp使用VB作为编程语言一样,Jsp使用的是Java语言。

安装软件:
既然Jsp使用Java,那么我们的服务器操作系统里当然要有相应的Java环境,否则我们怎么使用Java提供的“库函数”呢?JDK就是这个东西,我们可以从SUN公司的网站上下载它,先把它安装到操作系统(我的操作系统是Windows XP Professional SP2)中,我的安装目录是:D:\j2sdk1.4.2_07,装完之后我们还需要进行配置,这一步在下面再说。
我们还需要安装Web服务器,我们初学者一般可以选用Tomcat作为Jsp的Web服务器。Web服务器是什么东西?假如你对Asp有一点了解的话,你就会知道IIS这东西——IIS就是Asp的Web服务器,那么Tomcat对于Jsp来说,它就相当于Asp的IIS。我使用的是Tomcat5.0,你也可以使用更高的版本,注意在安装它之前你得先安装JDK,我的Tomcat的安装目录是:D:\Tomcat 5.0。

配置:
右键“我的电脑”-“属性”-“高级”-“环境变量”,这里面有用户变量和系统变量之分,更改系统变量就可以了:
我的相关的系统变量是:
CATALINA_HOME——d:\Tomcat 5.0
JAVA_HOME——d:\j2sdk1.4.2_07
classpath——.;%JAVA_HOME%\lib\tools.jar;%JAVA_HOME%\lib\dt.jar;(dt.jar是关于运行环境的类库,tools.jar是关于一些工具的类库)
Path——%JAVA_HOME%\bin;(把这句添加进去,而不是最终的值)
TOMCAT_HOME——d:\Tomcat 5.0
(安装完后可以查看:运行-cmd,打开模拟DOS窗口,echo %JAVA_HOME%,echo %classpath%和echo %path%可以分别查看其中的内容;javac可以查看该命令的用法;java -version可以查看系统中的JDK的版本)
Tomcat在安装完之后在Windows系统的“服务”中会多一项“Apache Tomcat”的服务,它是自动的,你可以给它改成手动,这样在不使用的时候可以节省一些系统资源了。既然改成手动了,那么怎么启动它呢?找这两个文件:D:\Tomcat 5.0\bin\startup.bat和D:\Tomcat 5.0\bin\shutdown.bat,前一个就是启动Tomcat的批处理文件了,而后一个就是关闭的。

你在安装Tomcat的时候可能会发现安装过程中有一个地方可以改端口号,默认是“8080”,而当你在调试程序时你就要注意这个端口号了。在上面的配置都完成以后,你可以看看是否成功了:先开启Tomcat服务器,然后在网页地址栏里输入:http://localhost:8080/(客户端和服务器是同一台机器),安装成功的话你会看见Tomcat的欢迎界面。你也可以使用你的IP或是机器名,比如我的IP是172.16.20.30,机器名是ringz,那么输入以下两种形式都是可行的:http://172.16.20.30:8080/http://ringz:8080/


第二章 轻度接触server.xml

现在开始编写我们的第一个.jsp文件了,代码如下:
----------------------------------------------------------------------------------
<%@ page contentType="text/html; charset=gb2312" language="java"errorPage="" %>
<%
out.print("日本人应该被彻底消灭");
%>
----------------------------------------------------------------------------------
这个文件是在页面输出一句话,你甚至可以用记事本来编写,但主要的问题是,这个文件(比如叫test.jsp)它放在哪里。Tomcat的默认目录好象是D:\Tomcat 5.0\webapps\jsp-examples,早期4.1版本目录是examples,把这个文件放到jsp-examples下,开启Tomcat服务器,在地址栏输入:http://ringz:8080/jsp-examples/test.jsp

现在要说的问题是:我不想用8080这个端口,直接用http://ringz/jsp-examples/test.jsp多好啊。这首先要取决你的80端口是否被占用,一般来说你的机器里没有安装IIS或者停掉IIS服务,80端口就可以使用。具体的修改方法如下:打开这个文件——D:\Tomcat 5.0\conf\server.xml,你会找到这样一段代码:
<Connector port="8080" 
maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
enableLookups="false" redirectPort="8443" acceptCount="100"
debug="0" connectionTimeout="20000" 
disableUploadTimeout="true" />
把port="8080"改成port="80",保存文件,重启Tomcat服务器,如果能够启动,说明可以使用80端口了;而要是Tomcat启动窗口一闪即关掉,就说明80端口已经被占用了(这个判断的前提是该server.xml文件没有错误,如果有其他错误的话,比如少一个“/>”,也会这样)。按照此方法你可以改成其他闲置的端口,比如说,给我们做软件的公司在测试的时候使用的是7988端口。除了80端口,其他端口都要在地址栏注明,80之所以不用是因为它是http协议的默认端口。
接下来的问题是:我不想把写好的.jsp文件放在D:\Tomcat 5.0\webapps\jsp-examples下怎么办?这同样需要修改server.xml文件:假如你的.jsp文件都放在e:\MyJsp下,则在该文件的<Host></Host>之间加入这样一段代码:<Context path="/MyJsp" docBase="e:\MyJsp" debug="0" reloadable="true" crossContext="true"/>(注意要和文件名的大小写要一致)。这样你可以在地址栏输入http://ringz/MyJsp/test.jsp(同样要注意大小写要一致)来运行这个文件。如果改成:<Context path="" docBase="e:\MyJsp" debug="0" reloadable="true" crossContext="true"/>,这样在地址栏里输入http://ringz/test.jsp就行了。
你感兴趣的话可以看看只输入http://ringz后页面是什么样的。
现在你可能会有这个想法:我的一个系统(或者叫一个站点)有一个固定的入口文件,比如叫login.htm,我希望只在地址栏输入http://ringz就可以打开这个文件,这怎么办?这个我们可以通过修改和server.xml处于同目录下的web.xml文件来实现:一般在这个文件的最下有这样一段代码:
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
你只需要将其中一个改成<welcome-file>login.htm</welcome-file>就行了。对于这个web.xml文件我们以后还会提到,就先不多说了。

记住一点:server.xml或者web.xml文件在修改后一定要重启Tomcat服务器才行。


第三章 连接数据库

在最初几天我写了几个页面之后,我决定要做一个留言板之类的东西:有登录验证、注册、发表文章、浏览文章、管理文章、管理用户等这些功能。首先的登录验证这个不难,但是有个问题:需要连接数据库了。于是我开始查找资料,并安装了Oracle数据库(对于Oracle数据库的一些最基本的知识我就不在这里说明了,但是需要注意的两点是:一、安装完成后就不要再改变你的机器名,一旦改了再改回来,数据库也用不了;二、安装完后不要使用Windows优化大师的清理注册表垃圾的功能,它会删掉一个有用的注册表信息导致Oracle的监听无法启动),最后写了这个文件:
-----------------------------link.jsp-----------------------------------------
<%@ include file="include.inc"%>
<%@ page contentType="text/html;charset=gb2312"%>
<html>
<body>
<%
Connection con = null;
Statement stmt = null;
ResultSet rs = null;
try
//有try就至少要有一个catch或finally
{
Class.forName(CLASSFORNAME);//载入驱动程式类别
con=DriverManager.getConnection(SERVANDDB);//建立数据库连接
stmt=con.createStatement();
String sql="select * from infom";//infom是表名
rs=stmt.executeQuery(sql);
while(rs.next())
{
out.println(rs.getString(1));//1就是第一个字段,第一个字段的名是username,所以这段也可以写成:rs.getString("username")
out.println(rs.getString(2));
}
out.println("<br>成功!");
}//try结束
catch(Exception e)
//当try里运行出错时,运行catch里的内容
{
out.println(e);//输出错误信息

finally
//不论是否出错、结果怎样,都要运行finally里的内容
//向下为关闭数据库连接
{
if (rs!=null)
rs.close();
if (stmt!=null)
stmt.close();
if (con!=null)
con.close();

%>
</body>
</html>
---------------------------------------------
Class.forName(CLASSFORNAME);
con=DriverManager.getConnection(SERVANDDB);
这两句无疑是很重要的,可是CLASSFORNAME、SERVANDDB这两个变量是什么呢?它们都来自<%@ include file="include.inc"%>这句里的include.inc文件,该文件与link.jsp在同一目录下。
-----------------------------------include.inc------------------------------------
<%@ page import="java.sql.*"%>
<%@ page import="java.util.*"%>
<%@ page import="java.io.*"%>
<%@ page import="oracle.jdbc.driver.OracleDriver"%>
<%@ page import="java.lang.*"%>

<% 
request.setCharacterEncoding("gb2312");
String CLASSFORNAME="oracle.jdbc.driver.OracleDriver";//定义载入驱动程式的字符串
String SERVANDDB="jdbc:oracle:thin:name/password@ringz:1521:rock";//定义建立数据库连接的字符串
//name是数据库的用户名;password是该用户的密码;ringz是我的机器名;rock是SID
%>
----------------------------------------------
假如你用的是Oracle的数据库的话,现在你运行这个文件还是会出错,因为Tomcat服务器找不到Oracle的JDBC驱动,你需要甲骨文(oracle)公司提拱的一个包:classes12.jar,你可以在D:\oracle\ora92\jdbc\lib下找到它,然后把它放到D:\Tomcat 5.0\common\lib下,现在,应该可以了。

本章最后的话:向程序员方向发展有两点无疑是很重要的:1、试着读懂代码;2、亲自动手写,然后亲自调试。

第四章 第一个Javabean

一、先看看如何取当前时间并显示的代码:
------------------------------------------------
<%
java.text.SimpleDateFormat formatter = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 

java.util.Date currentTime = new java.util.Date();//得到当前系统时间

String str_date1 = formatter.format(currentTime); //将日期时间格式化
String str_date2 = currentTime.toString(); //将Date型日期时间转换成字符串形式
%>
格式化成"yyyy-MM-dd HH:mm:ss"格式的日期时间:<%=str_date1%>
未经格式化的String格式的日期时间:<%=str_date2%>
未经格式化的Date格式的日期时间:<%=currentTime%>
-------------------------------------------------
页面的输出内容:
格式化成"yyyy-MM-dd HH:mm:ss"格式的日期时间:2005-03-17 09:55:40 
未经格式化的String格式的日期时间:Thu Mar 17 09:55:40 CST 2005 
未经格式化的Date格式的日期时间:Thu Mar 17 09:55:40 CST 2005

通常我们需要的是这种格式化后的时间:2005-03-17 09:55:40。现在有这样一个问题:“2005-03-17 09:55:40”是一个字符串,有些时候我们需要在这个字符串里提取出年、月、日等的相关信息,怎么办呢?看下面的代码:
----------------------规则的字符串----------------------------------------------
规则的字符串(年4位,月2位,日2位,中间用字符“-”分隔):
<br>原字符串为:
<%
String date="1989-12-30";
out.println(date+"<br>");
String year=date.substring(0,4);//取第0+1位至第4位
String month=date.substring(5,7);//取第5+1位至第7位
String day=date.substring(8,10);//取第8+1位至第10位
out.println("year="+year+",month="+month+",day="+day);
%>
----------------------------------------------------------------------------------
从上面的代码我们可以看出来:这只能针对规则的字符串(年4位,月2位,日2位),要是不规则的呢?年可能2位也可能4位,月和日可能1位也可能2位,怎么办?通过对上面的代码修改,可以得到下面的通用的代码(这个“通用”有一个前提——年月日之间必须以“-”分隔):
----------------------不规则的字符串----------------------------------------------
不规则的字符串(年、月、日长度不一定,中间用字符“-”分隔):
<br>原字符串为:
<%
String date="04-05-6";
out.println(date+"<br>");
int a=date.indexOf("-");//求第一个“-”的位数
int b=date.lastIndexOf("-");//求最后一个“-”的位数
int len=date.length();//求字符串的长度
year=date.substring(0,a);//取第一个“-”前的字符串
month=date.substring(a+1,b);//取两个“-”之间的字符串
day=date.substring(b+1,len);//取最后一个“-”以后的字符串
out.println("year="+year+",month="+month+",day="+day);
%>
----------------------------------------------------------------------------------
现在这个问题解决了。可是我们会想到:每次需要对一个表示年月日的字符串进行分割的时候都需要在.jsp页面里写上这么一段代码,不但麻烦而且使页面显得混乱,能不能以一种看起来更清晰的方法解决这个问题呢?当然可以了,用javabean就行了。

二、第一个javabean
什么是javabean?我在接触到这个问题时苦恼不已,因为我始终无法理解这个概念,直到我相继写了几个之后,我蓦然发现:它不就是一个类吗?!现在,我们先不去管什么是类,先看下面的这个javabean:
--------------------------------DateBean.java-------------------------------------
//该bean能够从jsp文件中得到一个表示年月日(用“-”分隔)的字符串,然后返回给jsp文件分别表示年、月、日的字符串
package ringz.javabeans; //ringz.javabeans是我的包名,我自己写的javabean都在这个包里
public class DateBean //这个class的名字是DateBean,那么这个javabean文件的名就必须叫:DateBean.java
{
private String dateStr;
private String year;
private String month;
private String day;
//
public void setDateStr(String str) //私有变量dateStr的set方法
{
this.dateStr=str;
}
public String getDateStr() //私有变量dateStr的get方法
{
return dateStr;
}
public String getYear()//得到年的字符串
{
int a=dateStr.indexOf("-");//求第一个“-”的位数
year=dateStr.substring(0,a);//取第一个“-”前的字符串
return year;
}
public String getMonth()//得到月的字符串
{
int a=dateStr.indexOf("-");//求第一个“-”的位数
int b=dateStr.lastIndexOf("-");//求最后一个“-”的位数
month=dateStr.substring(a+1,b);//取两个“-”之间的字符串
return month;
}
public String getDay()//得到日的字符串
{
int b=dateStr.lastIndexOf("-");//求最后一个“-”的位数
int len=dateStr.length();//求字符串的长度
day=dateStr.substring(b+1,len);//取最后一个“-”以后的字符串
return day;
}
}
---------------------------------------------------------------------------------
一个javabean里最主要的是set和get方法:set方法用于从.jsp页面向javabean传值;get方法用于从javabean向.jsp页面传值。下面看.jsp页面如何使用这个javabean:
------------------------------ymd_use_bean.jsp----------------------------------
<%@ page contentType="text/html; charset=gb2312" language="java" import="java.sql.*" errorPage="" %>
<jsp:useBean id="ymd" scope="page" class="ringz.javabeans.DateBean">
<jsp:setProperty name="ymd" property="*"/>
</jsp:useBean>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
<title>使用javabean从字符串中取得年月日信息</title>
</head>
<body>
<%
try
{
out.println("您输入的字符串是:"+ymd.getDateStr()+"<br>");
out.println("year="+ymd.getYear()+",month="+ymd.getMonth()+",day="+ymd.getDay());
}
catch(Exception e){}
%>
<br><br><hr><br>请在下边的文本框中输入代表年月日的字符串,格式:XXXX-XX-XX,其中代表年、月、日的位数可以不限定。
<form method="post" action="ymd_use_bean.jsp">
<input type="text" name="dateStr"><!--必须和所使用的javabean:DateBean中的这个“private String dateStr;”所定义的名称完全一致!!!-->
<input type="submit" value="确定"> 
(必须使用两个“-”分开,如:1999-9-29)
</form>
</body>
</html>
--------------------------------------------------------------------------------
你在这段代码里能看到get方法,可是set方法呢?怎么没看见?这里面的玄机在<jsp:useBean></jsp:useBean>之间和form里的这句:<input type="text" name="dateStr">,这里我不想重点说明.jsp文件如何引用javabean,你不清楚的话请查阅其他资料。
另外在这里提一个小技巧你自己试一试:把try和catch的内容去掉,看看结果是什么样的;把catch的内容改成这样:
catch(Exception e)
{out.print(e);}
再看看结果是什么样的。然后自己分析一下为什么会这样。这是一个我经常使用的小技巧,不知道别人是不是也用这种方法来解决这种问题。关于异常的处理学问实在是很多啊。这里说的这个小技巧实际上是我之前的一种自作聪明——就是这样一种情况:有时候一个字符串变量可能得不到值(比如接收一个可以为空的表单文本框),但是下边还需要对这个变量操作,如果变量为空的话下边的这个操作就会抛出异常,这里说的这个小技巧就是指如果出现这种情况的异常,那么对这个异常的处理是NOTHING,什么都不做,也就是视为一切正常。

上面已经说了javabean就是一个类,下面我要用自己的话说一下什么是类,我是怎么理解类这个概念(理解的不一定准确,还请高手们给予指正,免的误人子弟^_^!):
先是想起一个笑话:说有一种机器,从机器的一侧放进一头猪,机器的另一侧就会出香肠。引申一下。这台机器在一个仓库里,这个仓库里还有一台能自动出猪毛和一台出猪皮的机器。现在这个仓库就可比做一个类,而每台机器就是一个方法,因为这个仓库是只针对猪的仓库,所以这个仓库一定在java.pig这个包(java的API——说白了就是类库或者按照C的说法就是函数库——里有很多类,这些类是分类存放的,每一个分类称做一个包,比如,和输入输出有关的类就放在java.io包里,和绘制图形相关的类就放在java.awt包里)里面,我们给这个仓库起名叫PigFactory,这就是类名。现在拿来一头猪:Japanese属于类Pig,先通过和PigFactory同名的构造函数PigFactory()为处理这头猪新建一个实例:
java.pig.PigFactory pf = new java.pig.PigFactory();
现在我们可以通过这个实例对这头猪进行操作了——由于我们只想要它的皮,那就使用出皮的机器就行了,这台机器作为一个方法叫做getSkin(),那么:
Skin pigskin = pf.getSkin(Japanese);
猪皮(pigskin)出来了,并且运送出仓库,它属于类“皮”(Skin)了。
类似的,还有一些专门处置牛、羊的仓库,它们也有自动出皮的机器,所以也是方法:getSkin(),这也就是为什么我们会发现很多不同的类里边有相同名称的方法,如:getString方法。
我们可以发现:这台自动出皮的机器是如何工作的,我们并不关心,我们所关心的只是怎样使用它。我们也可以自己开发一个类(或者javabean),它的工作原理和SUN给我们开发好的API里的类是基本一致的。在使用时我们所关心的仅仅是如何把值传给它和如何从它那里得到值。
以上是我的理解,可能有很多不确切的地方,希望大家指正。

说了这很多废话以后,你可能想问:我们上面写的javabean现在可以用了吗?还不行。刚刚我们写好的是一个.java文件,必须把它编译成.class文件以后Jsp才能调用它。下面说怎么编译,由于我现在没有使用专门的java开发工具(如JBuilder),所以只能使用我们安装的JDK(java 2 sdk)所提供的编译工具:
把DateBean.java这个文件放到C:\下,“开始”-“运行”-“cmd命令”,在开启的窗口中将当前路径改为C:\,然后输入:javac DateBean.java,回车,如果窗口中没有提示出错的话,OK,编译成功了。这时你会发现和DateBean.java处于同目录的地方多了一个DateBean.class文件,就是它了。现在的问题是它应该放在哪了。由于是我自己写的javabean,所以我不打算把他们和Tomcat自带的放在一起:之前我们定义了自己的根目录:e:\MyJsp,在它的下面新建一个WEB-INF文件夹(注意大小写),其下再建一个classes文件夹,放在这里就行了。当然你可以专门为你自己开发的javabean做一个包——就像SUN那样——我自己的包名叫ringz.javabeans,所以我在classes文件夹下建了一个ringz的文件夹,里面再建一个javabeans文件夹,然后把我开发的.class文件放到这里。这时使用的时候要注意包名:<jsp:useBean id="ymd" scope="page" class="ringz.javabeans.DateBean" />。(实际上,类外层的包名是必须的)

本章需要注意的当然是每次改完后的.java文件都要重新编译成.class文件,而且使用.class文件时要重启Tomcat服务器。(后补:实际上,这是不正确的,有时候并不需要重启TC,这大概是和TC的版本有关)

第五章 分页功能的实现

在我逐步把我的留言板的功能完善的同时,我渐渐熟悉了对数据库的操作,这时我发现留言信息的目录越来越长了,我需要实现一个分页功能了,最初我尝试自己解决这个问题:
1、我应该把它的关键部分封装成一个bean,使它尽可能的能够重用;
2、通过资料了解有两种数据库查询方案:一、一次取得所有资料,然后在指定的页显示指定的资料;二、分次查询数据库,每次只获得本页的数据。考虑到数据库中记录数越多,方案一所占的服务器资源就越多(将所有的记录都放到内存中,假如有50万条记录的话@#),所以应该采用方案二;
3、首先要知道目标数据库里共有多少条记录(select count(*) from 表名),然后确定每页显示多少条记录,再根据它计算一共分多少页(最大页数)显示,这部分由bean1(在我的代码里就随便起个名叫PageBean)实现;获取当前要显示第几页的请求,查询本页要显示哪些条记录,将每条记录的内容作为一组数据返回给显示页面,这部分由bean2(CountBean)实现;显示页面(.jsp文件)显示各条记录的内容。
到这里我发现有两个难点:(1)查询从第m条到第n条记录的SQL语句不会写,通过在网上查找资料,这个问题得到解决;(2)bean2返回给显示页面的值是个二维的数组,这个数组怎么传呢?于是不得不上网翻书查找资料,最终发现由Vector(向量)来解决,由于没有工具书,无法针对Vector进一步学习,就只好分析代码,好在最后分析明白了——到目前为止,也只是明白了那段代码,仍然无法做到能够应用。(后补:这些东西事实上也不是难于理解的,有时候我们需要做的是在有了一定理论基础之后查看文档和别人的说明。在别人的指点下,我现在的代码里边基本都不使用Vector了,采用ArrayList来代替它,实际上,还有人建议我使用Hashtable代替ArrayList)
下面是这三个文件的代码:
-----------------------------------------PageBean.java-----------------------------------------------
//该bean用于实现分页功能时得到总的记录数和最大的页数
package ringz.javabeans;
import java.io.*; 
import java.sql.*;
public class PageBean
{
private int maxRowCount;//最大记录数
private int onePageRowCount;//每页显示的记录数
private int maxPageCount;//最大页数
private String classforname;
private String servanddb;
private String sql;
//
//得到关于目标数据库的搜索条件
public void setSql(String s1,String s2,String sql)
{
this.classforname=s1;
this.servanddb=s2;
this.sql=sql;
}

//得到onePageRowCount
public void setOnePageRowCount(int counts)
{
onePageRowCount=counts;
}

//计算maxRowCount并返回
public int getMaxRowCount()throws Exception
{
try
{
Class.forName(classforname);//载入驱动程式类别
Connection con=DriverManager.getConnection(servanddb);//建立数据库连接
Statement stmt=con.createStatement();//建立Statement变量
ResultSet rs=stmt.executeQuery(sql);
if (rs.next())
maxRowCount=rs.getInt(1);
rs.close();
stmt.close();
con.close();
return maxRowCount;
}//try
catch (Exception e)

e.printStackTrace();
throw e;
}
}//getMaxRowCount()

//根据maxRowCount和onePageRowCount计算出maxPageCount并返回
public int getMaxPageCount()
{
if (maxRowCount%onePageRowCount==0)
maxPageCount=maxRowCount/onePageRowCount;
else
maxPageCount=maxRowCount/onePageRowCount+1;
return maxPageCount;
}
}
---------------------------------------------------------------------------------
----------------------------CountBean.java------------------------------------
//该bean用于接收具体页数然后返回该页应显示的记录
package ringz.javabeans;
import java.util.*;
import java.io.*; 
import java.sql.*;
public class CountBean
{
private int pageNum;//当前是第几页
private String classforname;
private String servanddb;
private String sql;
Vector v=new Vector();

//得到关于目标数据库的搜索条件
public void setSql(String s1,String s2,String sqlstr)
{
this.classforname=s1;
this.servanddb=s2;
this.sql=sqlstr;
}

//得到pageNum
public void setPageNum(int pagenum)
{
this.pageNum=pagenum;
}

//返回结果
public Vector getResult(String listname[])throws Exception
{
int num=listname.length;//得到数组的长度
String listName[]=new String[num];//定义一个大小为num的string型数组
for(int i=0;i<num;i++)
listName[i]=listname[i];//将目标数组的内容传给listName数组

try
{
Class.forName(classforname);
Connection con=DriverManager.getConnection(servanddb);
Statement stmt=con.createStatement();
ResultSet rs=stmt.executeQuery(sql);
int i=1;
while(rs.next())
{
Object[] obj=new Object[num];
for(int j=0;j<num;j++)
obj[j]=rs.getString(listName[j]);
v.add(obj);
i++;
}//while
rs.close();
stmt.close();
con.close();
return v;
}//try
catch(Exception e)
{
e.printStackTrace();
throw e;
}//catch
}
}
----------------------------------------------------------------------------------
编译上面这两个文件的时候,发现如果不把“错误”扔掉(throw e),就无法编译成功,我不明白具体原因。
-----------------------------------page.jsp---------------------------------------
<%@ include file="include.inc"%>
<%@ page contentType="text/html;charset=gb2312"%>
<jsp:useBean id="page1" scope="page" class="ringz.javabeans.PageBean"/>
<jsp:useBean id="page2" scope="page" class="ringz.javabeans.CountBean"/>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>分页的实现</title>
<style type="text/css">
<!--
body {
margin-left: 10%;
margin-right: 10%;
}
-->
</style>
<script language="JavaScript" type="text/JavaScript">
<!--
function jumping(jump){ //v3.0 ***用于页面跳转
var pageID=jump.value;
var url=pageID;
window.location.href = url;
}
//-->
</script>
</head>

<body>
<%
int pageNum;
try
{
pageNum=Integer.parseInt(request.getParameter("page"));//得到“要显示第几页”
}
catch(Exception e)
{
pageNum=1;//如果出错说明pageNum没有接收到"page",那么就把pageNum初始为1
}
int onePageRowCount=10;//每页的条数************按需要改变
String s1="oracle.jdbc.driver.OracleDriver";//定义载入驱动程式的字符串
String s2="jdbc:oracle:thin:name/password@ringz:1521:rock";//定义建立数据库连接的字符串************按需要改变
String sql="select count(*) from article";//确定记录总数的查询语句************按需要改变
page1.setSql(s1,s2,sql);
page1.setOnePageRowCount(onePageRowCount);
int maxRowCount=page1.getMaxRowCount();//获得记录总数
int maxPageCount=page1.getMaxPageCount();//获得总的页数
page2.setPageNum(pageNum);
String listname[]={"ID","author","time","title"};//要查询的字段名************按需要改变
int max=pageNum*onePageRowCount;//本页最后一条记录的行号
int min=(pageNum-1)*onePageRowCount+1;//本页第一条记录的行号
String sqlstr="select b.* from (select a.*,rownum row_num from (select * from article order by time desc) a where rownum<='"+max+"') b where row_num>='"+min+"'";//************按需要改变
page2.setSql(s1,s2,sqlstr);
if(maxRowCount>0)
out.println("<div align='left'>共有"+maxRowCount+"条记录,每页显示"+onePageRowCount+"条。</div>");
%>
<table width="100%" border="1" align="center" cellpadding="0" cellspacing="0" bordercolorlight="#000000">
<tr bgcolor="#00CCFF">
<td align="center">标题</td>
<td align="center">作者</td>
<td align="center">日期</td>
</tr>
<%
java.util.Vector v=page2.getResult(listname);
java.util.Enumeration e=v.elements();
while(e.hasMoreElements())
{
Object[] obj=(Object[])e.nextElement();//****************注意修改下面的几行
String id=obj[0].toString();
String name=obj[1].toString();
String time=obj[2].toString();
String title=obj[3].toString();
out.println("<tr>");
out.println("<td bgcolor='#eeeeee'><div align='left'><font color='#eeeeee'><a href=view.jsp?ID="+id+">"+title+"</a></font></div></td>");
out.println("<td width='15%' bgcolor='#ffff99'><div align='center'>"+name+"</div></td>");
out.println("<td width='30%' bgcolor='#9999ff'><div align='center'>"+time+"</div></td>");
out.println("</tr>");
}
%>
</table>
<div align="right">
<%
String fileName="page";//**************************将文件名作为变量***********************
out.print("第<font color=red>"+pageNum+"</font>页 共"+maxPageCount+"页&nbsp;&nbsp;&nbsp;");
if (maxPageCount>1)//不只有一页
{
if (pageNum==1)//当前页是首页
{
out.print("首页 | 上一页 | <a href="+fileName+".jsp?page="+(pageNum+1)+">下一页</a> | ");
out.print("<a href="+fileName+".jsp?page="+maxPageCount+">尾页</a> ");
}//if (pageNum==1)
else
{
if (pageNum==maxPageCount)//当前页是尾页
{
out.print("<a href="+fileName+".jsp?page=1>首页</a> | ");
out.print("<a href="+fileName+".jsp?page="+(pageNum-1)+">上一页</a> | 下一页 | 尾页 ");
}//if (pageNum==maxPageCount)
else//当前页不是上面的2种情况
{
out.print("<a href="+fileName+".jsp?page=1>首页</a> | ");
out.print("<a href="+fileName+".jsp?page="+(pageNum-1)+">上一页</a> | ");
out.print("<a href="+fileName+".jsp?page="+(pageNum+1)+">下一页</a> | ");
out.print("<a href="+fileName+".jsp?page="+maxPageCount+">尾页</a> ");
}
}
%>
跳转到第
<select name="jumps" onChange="jumping(this)">
<%
for (int i=1;i<=maxPageCount;i++)
{
if (i==pageNum)
{%>
<option value="<%=fileName%>.jsp?page=<%=i%>" selected><%=i%></option>
<%} else {%>
<option value="<%=fileName%>.jsp?page=<%=i%>"><%=i%></option>
<%} } %>
</select>

<%
}//if (maxPageCount!=1)
%>
</div>
</body>
</html>
----------------------------------------------------------------------------------

整个的分页功能写完后,我发现还是有很大的不足:显示分页的页面(page.jsp)代码太多,其他页面引用该功能的时候还是要从这里复制大段的代码,而且其中需要根据实际情况改动的地方多达七处(标注很多*的地方),这很容易出错,不利于管理使用,但是至今我仍找不到一个合适的方法解决这个问题——希望高手们扔玉。(后补:这种写法确实很烂,但是不得不承认这是一个学习的过程,当你对自己的代码感到不满意并不断试图想要给自己一个满意的答卷时,你的水平就进步了)

第六章 画柱状统计图

在编码学习的过程中,我发现的问题越来越多了,有Java方面的,SQL方面的,Html方面的,JavaScript方面的等等,对这些看似细小的问题的研究使我积累了实战的经验,起码不只是纸上谈兵了。
这个时候我的领导让我做一个东西,实现局域网内部网上计算机故障报修。这其实就是一个留言板的功能,我正好之前做过练习,所以很轻松的就做好了。之后我想我也许应该做一个统计——统计一年内每个月完成的报修任务量,如果用表格显示的话太简单了,不如做一个动态生成的柱状图吧,我突然有了这个想法。
马上开始动手,先是查资料,知道了Java里和画图有关的是java.awt包,由于我构想的图只是由矩形组成,那么用到的方法也就这么几个:fillRect,drawRect,setColor,setFont,drawString。我很快发现一个问题:如何在页面显示这个图,这是个大问题,于是找例子。
在一个学过研究生Java课程的同事的帮助下知道可以这样:写一个类(Picture.class),这个类只负责画图,没有任何关于如何显示的语句,然后在一个页面文件(.htm文件就行)里<body>里写上这段代码:<applet code="Picture" height="400" width="400"></applet>,运行这个文件就可以了。但是这个方法有这两个弊端:1、它是直接从服务器端下载Picture.class,在客户端生成图片,所以客户端必须装有java环境,比如j2re等;2、现在大部分浏览器都或者迫于无奈或者被强行绑架(这里我严重鄙视一下3721和一个叫“天下搜索”的)安装了阻止小窗口、ActiveX控件的插件——就连XP的SP2也集成了这个功能——而这个功能同样对<applet>有效。
放弃第一种方法后我在网上找到了第二个例子,第二个例子让我很奇怪,代码直接写在一个.jsp文件里,打开文件显示图片,一看这个图片的属性竟然就是这个.jsp文件的名。看了一阵子代码发现不是很理解,我开始看第三个例子。
第三个例子符合我的思维:写一个bean(或者说是一个类),把一个代表路径的字符串和一些数据传给它,它根据数据画图但是不返回(从这一点来说它不能叫做bean),而是生成一个如.jpg文件并按照传进来的路径名进行保存。然后显页面通过<img src="……">显示图片。我通过这种方式实现了工作,下面是这个类的代码:(后补:awt是重量级的,所以推荐使用的是swing,但是这部分内容我一直没有打算试图有swing改写,甚至不知道是否可行,不过我曾经用swing做过一个C/S结构的小东西,但是最终半途而废)
----------------------------------Picture.java------------------------------------
//该bean用于画柱状统计图
package ringz.javabeans;
import java.io.*; 
import java.util.*; 
import com.sun.image.codec.jpeg.*; 
import java.awt.image.*; 
import java.awt.*;

public class PictureBean 

BufferedImage image; 
private String fileLocation;

public void setFileLocation(String fileLocation)//fileLocation是图片的路径,如:“D:\\a\\b\\c.jpg”
{
this.fileLocation=fileLocation;
}

public void createImage(String fileLocation)

try 

FileOutputStream fos = new FileOutputStream(fileLocation);
BufferedOutputStream bos = new BufferedOutputStream(fos);
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(bos);
encoder.encode(image);
bos.close();

catch(Exception e)

e.printStackTrace();

}

public void outGraphic(String titles,String sstr,String str[],int datas[])
{
String Title=titles;
String SStr=sstr;

int imageWidth = 400;//图片的宽度 Line
int imageHeight;//不定长

int frameFirstWidth=imageWidth-10;
int frameFirstHeight=25;

int frameSecondWidth=imageWidth-10;
int frameSecondHeight;//不定长

int frameSpace=10;//两框间隔

int columnHeight=18;//柱的粗 
int columnMaxWidth=frameSecondWidth-20;//柱的最大长度,也是代表数值最大的那个柱的长度

int sp=30;//柱的间隔

int num=datas.length;//数组的长度
int Datas[]=new int[num];//得到数组的数值
String name[]=new String[num];
for (int i=0;i<num;i++)
{
Datas[i]=datas[i];
name[i]=str[i];
}

//得此数组中的最大值
int max=Datas[0];
for (int j=0;j<num;j++)
{
if(Datas[j]>max)
max=Datas[j];
}

//得到代表数值的柱的各自高度,实际数值*columnMaxHeight/max
int columnWidth[]=new int[num];//不定长,柱的长度
for (int k=0;k<num;k++)
columnWidth[k]=(Datas[k]*columnMaxWidth)/max;//取整

frameSecondHeight=(sp+columnHeight)*num+10;//+10为了留出一块底边
imageHeight=frameSecondHeight+frameFirstHeight+frameSpace+10;//多加10为了画阴影

PictureBean chartGraphics = new PictureBean();
chartGraphics.image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_RGB); 
Graphics g = chartGraphics.image.getGraphics();
g.setColor(Color.white);
g.fillRect(0,0,imageWidth,imageHeight);//用白色涂整个图
Color frameFirstColor = new Color(20,50,100);
Color columnColor = new Color(153,19,19);
Color shadowColor = new Color(200,200,200);
g.setColor(shadowColor);
g.fillRect(0+7,0+7,frameFirstWidth,frameFirstHeight);//阴影在原框基础上移7
g.setColor(Color.white);
g.drawRect(0,0,frameFirstWidth,frameFirstHeight);//画第一个框
g.setColor(frameFirstColor);
g.fillRect(0+1,0+1,frameFirstWidth-1,frameFirstHeight-1);
g.setFont(new Font("仿体", 0 , 14));
g.setColor(Color.white);
g.drawString(Title,10,18);//写字
g.drawString(SStr,300,18);

int frameSecondY=1+frameFirstHeight+frameSpace;
g.setColor(shadowColor);
g.fillRect(0+7,frameSecondY+7,frameSecondWidth,frameSecondHeight);//阴影在原框基础上移7
g.setColor(Color.black);
g.drawRect(0,frameSecondY,frameSecondWidth,frameSecondHeight);//画第二个框
g.setColor(Color.yellow);
g.fillRect(0+1,frameSecondY+1,frameSecondWidth-1,frameSecondHeight-1);//填充第二个框

for(int l=0;l<num;l++)
{
g.setColor(Color.black);
int textY=frameSecondY+20+(sp+columnHeight)*l;
g.drawString(name[l]+"("+datas[l]+")",0+10,textY);//写文字
if (columnWidth[l]!=0)
{
g.setColor(columnColor);
g.drawRect(10,textY+5,columnWidth[l],columnHeight);//画柱的外框//框的上边离文字的底边为5
g.fillRect(10+2,textY+5+2,columnWidth[l]-3,columnHeight-3);//画柱
}
}
chartGraphics.createImage(fileLocation);
}
}
--------------------------------------------------------------------------------
但是接下来出现了一个让我难以忍受的事:自做聪明的浏览器缓存使得页面无法在短时间内更新图片——输入2004,显示了2004的图片,马上再输入2005,可是显示的仍然是2004的图片,但这时硬盘目录下的图片已经是2005的图片了,一般来说两次操作时间间隔大约少于3秒,则总是显示缓存里的那张图。这个问题困扰我很长时间,问了很多人试了很多方法都没有解决了。
很显然上面提到的第二个方法不存在此问题,我决定采用这种方法,所以我不得不回头研究它的代码,之后我发现这几句代码是显示图片的关键,而最下面的三句是和显示图有关的:
---------------------------------------------------------
response.setContentType("image/jpeg");
BufferedImage bi = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
Graphics2D biContext = bi.createGraphics();

…… 

OutputStream output = response.getOutputStream();
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(output);
encoder.encode(bi);
---------------------------------------------------------
我手头仅有一本电子版的《java2参考大全》,而令我苦恼的是在里边我竟然找不到BufferedImage、Graphics2D、JPEGImageEncoder这些字样;另外,上一个例子里是Graphics,它和Graphics2D有什么差别呢?这也让我很困惑。但是我终于决定要试一试,把两个例子综合一下,最终得到了下面这个worklord.jsp文件:
-----------------------------------worklord.jsp----------------------------------
<%@ include file="include.inc"%>
<%@ page contentType="text/html;charset=gb2312"%>
<%@ page import="java.io.OutputStream" %>
<%@ page import="java.util.*"%>
<%@ page import="java.awt.image.BufferedImage" %>
<%@ page import="java.awt.*" %>
<%@ page import="com.sun.image.codec.jpeg.*" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>工作量统计</title>
<style type="text/css">
<!--
body {
margin-left: 10%;
margin-right: 10%;
}
.style2 {font-size: 24px}
-->
</style></head>

<body>
<%
//得到当前的年
java.text.SimpleDateFormat formatter = new java.text.SimpleDateFormat("yyyy"); 
java.util.Date currentTime_1 = new java.util.Date();//得到当前系统时间
String yearNow = formatter.format(currentTime_1);

String year=null;
try
{
year=request.getParameter("select");
}
catch(Exception e){}

if (year==null)
year=yearNow;

//String y=Integer.toString(year);
int sum=0;
String mon[]=new String[12];
mon[0]=year+"-01";
mon[1]=year+"-02";
mon[2]=year+"-03";
mon[3]=year+"-04";
mon[4]=year+"-05";
mon[5]=year+"-06";
mon[6]=year+"-07";
mon[7]=year+"-08";
mon[8]=year+"-09";
mon[9]=year+"-10";
mon[10]=year+"-11";
mon[11]=year+"-12";

int Datas[]=new int[12];

Connection con = null;
Statement stmt = null;
ResultSet rs = null;
try
{
Class.forName(CLASSFORNAME);//载入驱动程式类别
con=DriverManager.getConnection(SERVANDDB);//建立数据库连接
stmt=con.createStatement();
String sql="select count(*) from record where com_time like '"+year+"%"+"'";
rs=stmt.executeQuery(sql);
if (rs.next())
sum=rs.getInt("count(*)");
for (int i=0;i<12;i++)
{
sql="select count(*) from record where com_time like '"+mon[i]+"%"+"'";
rs=stmt.executeQuery(sql);
if (rs.next())
Datas[i]=rs.getInt("count(*)");
else
Datas[i]=0; 
}
rs.close();
stmt.close();
con.close(); 
}
catch(Exception e)
{
out.print(e);
}
if (sum!=0)
{
String Title=year+"年度工作量统计图";
String SStr="总和:"+sum;
String name[]={"一月份","二月份","三月份","四月份","五月份","六月份","七月份","八月份","九月份","十月份","十一月份","十二月份"};

int num=Datas.length;//数组的长度
//得此数组中的最大值
int max=Datas[0];
for (int j=0;j<num;j++)
{
if(Datas[j]>max)
max=Datas[j];
}

int imageWidth = 400;//图片的宽度 Line
int imageHeight;//不定长

int frameFirstWidth=imageWidth-10;
int frameFirstHeight=25;

int frameSecondWidth=imageWidth-10;
int frameSecondHeight;//不定长

int frameSpace=10;//两框间隔

int columnHeight=18;//柱的粗 
int columnMaxWidth=frameSecondWidth-20;//柱的最大长度,也是代表数值最大的那个柱的长度

int sp=30;//柱的间隔

//得到代表数值的柱的各自高度,实际数值*columnMaxHeight/max
int columnWidth[]=new int[num];//不定长,柱的长度
for (int k=0;k<num;k++)
columnWidth[k]=(Datas[k]*columnMaxWidth)/max;//取整

frameSecondHeight=(sp+columnHeight)*num+10;//+10为了留出一块底边
imageHeight=frameSecondHeight+frameFirstHeight+frameSpace+10;//多加10为了画阴影

//开始画图
response.setContentType("image/jpeg");
BufferedImage image = new BufferedImage(imageWidth,imageHeight,BufferedImage.TYPE_INT_RGB); 
Graphics g = image.createGraphics();

g.setColor(Color.white);
g.fillRect(0,0,imageWidth,imageHeight);//用白色涂整个图
Color frameFirstColor = new Color(20,50,100);
Color columnColor = new Color(153,19,19);
Color shadowColor = new Color(200,200,200);
g.setColor(shadowColor);
g.fillRect(0+7,0+7,frameFirstWidth,frameFirstHeight);//阴影在原框基础上移7
g.setColor(Color.white);
g.drawRect(0,0,frameFirstWidth,frameFirstHeight);//画第一个框
g.setColor(frameFirstColor);
g.fillRect(0+1,0+1,frameFirstWidth-1,frameFirstHeight-1);
g.setFont(new Font("仿体", 0 , 14));
g.setColor(Color.white);
g.drawString(Title,10,18);//写字
g.drawString(SStr,300,18);

int frameSecondY=1+frameFirstHeight+frameSpace;
g.setColor(shadowColor);
g.fillRect(0+7,frameSecondY+7,frameSecondWidth,frameSecondHeight);//阴影在原框基础上移7
g.setColor(Color.black);
g.drawRect(0,frameSecondY,frameSecondWidth,frameSecondHeight);//画第二个框
g.setColor(Color.yellow);
g.fillRect(0+1,frameSecondY+1,frameSecondWidth-1,frameSecondHeight-1);//填充第二个框

for(int l=0;l<num;l++)
{
g.setColor(Color.black);
int textY=frameSecondY+20+(sp+columnHeight)*l;
g.drawString(name[l]+"("+Datas[l]+")",0+10,textY);//写文字
if (columnWidth[l]!=0)
{
g.setColor(columnColor);
g.drawRect(10,textY+5,columnWidth[l],columnHeight);//画柱的外框//框的上边离文字的底边为5
g.fillRect(10+2,textY+5+2,columnWidth[l]-3,columnHeight-3);//画柱
}
}
try
{
//输出图
OutputStream output = response.getOutputStream();
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(output);
encoder.encode(image);
output.close();
}
catch(Exception e)

e.printStackTrace();

}//if
else
{%>
<table width="100%">
<tr>
<td width="407">
<span class="style2"><font color="#FF0000">没有<%=year%>年的记录!</font></span>
</td>
</tr>
</table>
<%
}
%> 
</body>
</html>
----------------------------------------------------------------------------------
现在任务是完成了,其中的最关键的部分代码是实现什么功能的也大概知道了,可是还是没有掌握其中的知识,不能不说是遗憾。

另外,worklord.jsp这样的页面很特殊,就是因为这部分代码引起的:
OutputStream output = response.getOutputStream();
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(output);
encoder.encode(image);
这导致了这个页面没法再干别的了,也就是说,假如你要在<body>里干点别的,像写几个字,放一个<form>什么的,页面显示不出来。

本章最后,感谢那两个例子的作者,而且图的风格都是抄袭第二个例子那位仁兄的:),但是没有记住你们的名字很遗憾。

  • 1
    点赞
  • 0
    评论
  • 0
    收藏
  • 扫一扫,分享海报

©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值