下面我要给大家讲解的是《JSP实用教程(第三版)耿祥义 张跃平编著》的第4章(JSP与Javabean)中例题4_9,这个例子所实现的功能是编写一个类和一个jsp文件,动态获取项目中一个文件夹下面所有的图片文件并将路径存储到对象中,当我们点击jsp页面中的上一张和下一张按钮时,重新向服务器提交请求,服务器返回新的html页面从而达到切换图片的效果。
(注意:这里的上面的按钮是“上一张”,下面的按钮是下一张,哈哈,没想到我自己居然打错字了,而且是等文章快写好的时候才发现写错字的,不过问题应该不大,大家明白这个意思就好了,我就不重新截图了,麻烦地很!!)
bean的本意是豆子,而在java ee的开发中,一般我们把一些可重用的组件称为一个javabean,简单的来说,javabean就是一个类,当有一些信息需要我们进行存储或者在不同的程序之间传递时,封装成一个javabean会是一个很好的选择,例如:
package tom.jiafei;
import java.util.Date;
public class UserInfo {
//这里的用户的信息存储在UserInfo类的成员变量里,类UserInfo就是一个javabean
public String id;
private String password;
public String name;
public int sex;//0未知 1男 2女
public String phone;
public String email;
public Date registerTime;
public boolean vip;
//一般我们会为javabean中的每个属性编写一个对应的set方法和get方法,不过时候后也会故意不写,例如:
public String getId() {return id;}
public void setId(String i) {id=i;}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
//jsp页面调用 和来获取和设置javabean中的成员时,bean中必须存在对应的is方法或get方法或set方法,否则会出错
public boolean isVip() {
return vip;
}
public void setVip(boolean vip) {
this.vip = vip;
}
//其他的东西我就不写了,大家随便看看就好了
}
File类是java.io包中一个操作磁盘上的文件或目录的类,这里我简单介绍一下File类的使用以及背后的原理。直接上代码,在代码中解释吧。我在我电脑的D盘中新建了一个test.java。内容如下:
package com;
import java.io.File;
public class test{
public static void main(String args[]){
//File类使用方法1.使用绝对路径,路径分隔符可以是\\或/
File f1 = new File("d:/test1.txt");//1.1文件
File f2 = new File("c:\\test");//1.2目录
//注意,上面的这两个路径都是不存在的,File中只是存储了路径信息,
//如果需要判断文件是否存在或者创建文件的话,需要调用File类中的方法。
if(f1.exists()){
System.out.println("文件存在");
}else{
try{
f1.createNewFile();//创建新文件
}catch(Exception e){System.out.println(e);}
}
//File类使用方法2.使用相对路径
File f3 = new File("");//这个目录就是当前目录,具体是啥我后面再解释
File f4 = new File("test3.txt");
System.out.println("f3 "+f3.getAbsolutePath());
System.out.println("f4 "+f4.getAbsolutePath());
}
}
下面简单看一下运行结果:
上面的File类的示例还是比较简单的,唯一容易产生疑问的就是File类中相对路径的参照到底是怎么来的。从上面的图我们可以看到,java中File类获取到的相对路径和我们执行java命令时的工作目录是一致的,下面我再给大家详细讲讲这里。
根据JDK的官方文档,java.io包中的所有类都将相对路径名解释为起始于用户的当前工作目录,可以通过调用System.getProperty(“user.dir”) 来获得,这个user.dir属性,简单来说就是执行java命令的工作目录的路径,下面我用代码给大家验证一下,将上个例子中的代码精简如下:
package com;
import java.io.File;
public class test{
public static void main(String args[]){
System.out.println(new File("").getAbsolutePath());
System.out.println(System.getProperty("user.dir"));
}
}
File类中提供的列举目录中的文件的方法有2个分别是list()和listFiles();在源码中它们的定义如下:
下面我简单示范一下:
package com;
import java.io.File;
import java.util.Arrays;
public class test{
public static void main(String args[]){
//用法1.list方法列举出所有文件和目录,获取到的是字符串
File f = new File("d:\\cordova");//注意这里的必须传入一个目录的值才能使用list方法
String [] list = f.list();
System.out.println(Arrays.toString(list));
//用法2.listFiles方法列举出所有文件和目录,打印出绝对路径
File[] fs = f.listFiles();
System.out.println(Arrays.toString(fs));
//错误用法1.往File中传入空字符串后,使用list方法将返回null,因为文件或目录不存在
//这里传入file的值是空字符串,虽然getAbsolutePath()方法依然能获取到相对路径本身,但是无法使用list方法
System.out.println(Arrays.toString(new File("").list()));
}
}
书上例题4_9的代码就是调用list方法获取一个目录下的所有图片文件,所以大家只要知道list方法的用法就好了。
com.ob.Play.java
package com.ob;
import java.io.*;
import java.net.URISyntaxException;
public class Play {
int imageNumber = 0,max;
String pictureName[],playImage;
String webDir = "";
String tomcatDir;
public Play() {
File f = new File("");
String path = f.getAbsolutePath();
int index = path.indexOf("bin");
tomcatDir = path.substring(0,index);
}
public void setWebDir(String s) {
webDir = s;
if(pictureName == null) {
File dirImage = new File(tomcatDir+"/wtpwebapps/"+webDir+"/image");
pictureName = dirImage.list();
}
if(pictureName!=null) max = pictureName.length;
}
public void setImageNumber(int n) {
if(n<0) n = max-1;
if(n==max) n=0;
imageNumber = n;
}
public int getImageNumber() {
return imageNumber;
}
public String getPlayImage() {
if(pictureName!=null) playImage = pictureName[imageNumber];
return playImage;
}
}
example4_9.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ page import="java.io.File" %>
example4_9.jsp
<%String webDir = request.getContextPath();
webDir = webDir.substring(1);
%>
" style="width:120px;height:90px;" alt="这是当前显示的图像">
单击“上一张”或“下一张”按钮浏览图像
相信很多同学在把书上的例题4_9的代码在eclispe中运行的时候都会遇到图片无法显示或者无法切换的错误,错误情况如图:(这是直接在eclipse的项目中点击Run之后的结果)
然后就出现了,图片无法显示,并且点击切换也没有效果,这是为什么呢?
这里看到的错误是有点抽象的,我们可以用chrome浏览打开这个页面然后用开发者工具查看一下到底是哪里出了问题。(右键浏览器点击检查或按F12键,得到信息如下:)
这里img标签的src中文件名为null,说明后台没有获取到图片的文件名。为什么呢?错误的原因其实就是我们在2.3.1提到的File类相对路径的问题。
例题的代码中,获取图片所在的目录的关键代码为这几句.
其中File f = new File("");String path = f.getAbsolutePath();这两句是为了得到tomcat安装路径的bin目录的绝对路径。什么意思呢?
这里我有必要给大家简单说一下tomcat,一个解压后的tomcat目录结构如下:(注:tomcat8.5和6.0的目录结构稍有区别,一切以实际为准)
如果我们要在把编译好web项目放进tomcat里运行,那么我们只需要把项目放进wtpwebapps里面,然后双击bin目录下的startup.bat就可以运行了。如图:
所以,刚才我们说的File file = new File("");String path = f.getAbsolutePath();实际上我们期望得到的path的值是"D:\work\apache-tomcat-8.5.37\bin"(因为我把tomcat放在D:\work里面了)。
而且,只要我们是按照我图片上的操作去运行web项目的,那么我们就可以得到正确的tomcat路径,接下来,例题代码中用int index = path.indexOf(“bin”);tomcatDir = path.substring(0,index);这两行代码得到tomcatDir为"D:\work\apache-tomcat-8.5.37",然后通过字符串连接最终得到D:\work\apache-tomcat-8.5.37\wtpwebapps\section4\image这个路径(分析省略,自己看代码去吧),最后用这个绝对路径new一个File对象,使用List方法得到文件名。
image内的图片如下:
正确运行时,chrome开发者模式看到的信息如下:
那么为什么直接在eclipse中点击Run运行这个例题的代码会出错呢?eclipse中File类相对路径的基路径又定位到哪里去了呢?我们在String path = f.getAbsolutePath();后面加一句System.out.println(“path”);并且把tomcatDir = path.substring(0,index);注释掉,然后来看一下:
我们可以看到,运行在eclipse中的java程序,File类获取到的相对路径基路径是eclispe的启动路径,因为我直接从桌面快捷方式打开了eclipse,所以识别到的路径是我桌面的路径C:\Users\weibin\Desktop,假如我们关掉eclipse,然后进入eclipse的安装路径再打开eclispe再测试的话,那么识别到的路径也会跟着改变,如图:
获取到的路径不同的原因在于,我打开时eclipse的用户路径不一样,即直接桌面快捷方式打开,和手动进入安装路径打开。所以,你把快捷方式移动到哪个目录打开,识别到的路径就是哪个。
所以,使用File类的相对路径去获取项目目录的绝对路径这种方法是存在极大的漏洞的,这里先教大家怎么解决,然后再教一下更好的解决方案。
为了解决上面这个问题,我给大家提供2种解决办法,不过在讲之前,其实还有些知识点我们需要补充一下。
1.eclipse安装tomcat之后,我们点击run运行项目时,默认会把项目发布到eclipse的内部数据文件夹.metadata的子文件夹里面,而不会直接发布到tomcat的安装路径。
这里,我们双击server视图中当前已经创建好的server可以查看发布项目时的路径设置:
默认是长这样的:
不知道怎么开启server视图的请参考百度经验-eclipse中的Servers面板不见了怎么办
metadata其实就是这里,我随便发布一个项目给大家看一下。我电脑上的路径是这样的,不同的电脑可能会有差别。(C:\Users\weibin\eclipse-workspace.metadata.plugins\org.eclipse.wst.server.core\tmp2\wtpwebapps)
注意:metadata中是没有bin目录或者其他只存在于tomcat安装路径的东西的,有的只是存放项目的wtpwebapps和几个xml配置文件。所以,为了兼容例题中的代码,我们必须把eclipse中的server location改成第二个,即tomcat installation。
修改server location这里我就先不说了,大家记得把旧的server删掉,新建一个server才能设置server location。(当server中已经发布了项目则只能查看,不能修改server locations,想要修改的话需要再tomcat中删除重建一个tomcat server选项,然后再双击就可以修改了,这里已经把server location改成tomcat的安装路径了)
设置好了之后,我们再次右键项目run到这个新建的server上,run成功再点击stop关掉。
这样,tomcat安装路径中就会有编译好的项目文件了。
如图:
接下来,我们只要去tomcat的bin目录下,启动tomcat,然后我们的例题4_9就能正常运行了。
结合我们在3.2中讲的两种运行tomcat的方式,这里我们可以有两种启动tomcat的方案。
3.4.1 关掉eclipse,把eclipse的快捷方式复制一份到tomcat的bin目录后再打开,此时打开的eclipse,相对路径的基目录是tomcat的bin目录,所以例题4_9能正常运行,如图:
3.4.1 关掉eclipse,然后双击bin目录中的startup.bat,再去浏览器输入网址打开例题4_9,结果也是正常的。因为运行bat的目录就是bin目录,所以以此为基础运行的java程序识别到的相对路径基目录就是bin目录,(同理,如果你把startup.bat发送到桌面快捷方式再去桌面打开的话,那么识别到的相对路径就又变成了桌面),这里我就不示范了,上面已经讲过了。
因为使用File类或者其他基于System.getProperty(“user.dir”)的方式获取到的路径经常都是变化的,所以在实际开发中,我们建议大家不要使用这种方式去存取文件,而可以使用另外一个方式,即使用类的getResource()方法去读取文件,getResource方法的使用大家可以参考: 关于Class.getResource和ClassLoader.getResource的路径问题
这里我用代码简单的给大家做一个示范:
package com;
import java.io.File;
import java.net.URISyntaxException;
public class Test {
public static void main(String args[]) {
// class.getResource接收一个字符串类型的变量,返回一个java.net.URL类型的对象
// 用法1.使用 类名.class.getResource("文件路径")获取与当前类字节码文件同目录的文件
// Test.class.getResource("1.txt");//这里是获取不到1.txt的,只是做一个用法示范
// 用法2.使用 类名.class.getResource("/文件路径")获取与当前类字节码文件最上层包(即与com文件夹)同目录的文件
System.out.println(Test.class.getResource("/1.txt"));
// 上面能获取到1.txt,不过要注意被读取的1.txt并不是src目录下的原件,而是编译后的classes目录下的1.txt的副本
// 用法3.使用 对象名.getClass().getResource()代替 类名.class.getResource();
A a = new A();
System.out.println(a.getClass().getResource("/1.txt"));
// 用法4. 将getResource方法和File类结合获取目录的绝对路径
// java中URL类、File类和URI类可以进行便捷的转换
try {
// 这里调用URL类的toURI方法的getPath来获取绝对路径
System.out.println(new File(Test.class.getResource("/").toURI()
.getPath()).getAbsolutePath());
// 调用toURI方法时可能发生异常,需要用try catch捕捉
} catch (URISyntaxException e) {
System.out.println(e);
}
}
}
class A {
}
运行结果如图:
(注意:这篇教程写的比较久,所以后面的东西我都是在另外一台电脑上截图的,上面那个截图的路径是另外一台电脑上myeclipse的项目发布路径,总之原理是一样的,后面的代码也是能正确运行的,希望大家不要被绕晕)
所以上面的例题4_9的代码,获取图片文件的方式可以修改成这样:
com.ob.Play.java
package com.ob;
import java.io.File;
import java.net.URISyntaxException;
public class Play {
int imageNumber = 0,max;
String pictureName[],playImage;
public Play() {
try {
String dir = new File(this.getClass().getResource("/").toURI().getPath()).getAbsolutePath();
pictureName = new File(dir.substring(0, dir.indexOf("WEB-INF"))+"image").list();
if(pictureName!=null) max = pictureName.length;
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
public void setImageNumber(int n) {
if(n<0) n = max-1;
if(n==max) n=0;
imageNumber = n;
}
public int getImageNumber() {
return imageNumber;
}
public String getPlayImage() {
if(pictureName!=null) playImage = pictureName[imageNumber];
return playImage;
}
}
example4_9.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ page import="java.io.File" %>
example4_9.jsp
" style="width:120px;height:90px;" alt="这是当前显示的图像">
单击“上一张”或“下一张”按钮浏览图像
上面移除了setWebDir方法,结合class.getResource来直接列出文件的名字,运行效果我这里就不贴图了,测试了一下是正常的。
在上面的例子中,当我们点击上一张和下一张按钮时,实际上每一次点击的时候我们都服务器提交了一个请求,服务器返回了一个新的html页面才实现了图片切换,所以,这样的实现方式存在这两个问题:
解决的方案:
服务器将所有图片的相对路径存储在一个数组中,jsp页面中采用js监听点击事件执行图片切换。修改后的代码如下:
com.ob.Play.java
package com.ob;
import java.io.File;
import java.net.URISyntaxException;
public class Play {
public String pictureName[];
String id = "image";
public Play() {
try {
String dir = new File(this.getClass().getResource("/").toURI().getPath()).getAbsolutePath();
pictureName = new File(dir.substring(0, dir.indexOf("WEB-INF"))+id).list();
for(int i=0;i
example4_9.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ page import="java.util.Arrays" %>
example4_9.jsp
单击“上一张”或“下一张”按钮浏览图像
这里的代码我也已经测试过啦,是可以的,但是运行结果还是跟之前一模一样,只是运行效率提升了而已,就不贴图了。
到这里,这个例题所涉及到的知识点和改良的方案我已经讲的差不多了,虽然界面还是很丑,不过界面的设计这个并不是我们学习jsp主要考虑的问题,我就不去改界面了。今天就写到这里吧,本文作者郑伟斌,写于2019/4/25,转载请注明出处,另外走过路过不要忘记点个赞哦。