乐者为王

Do one thing, and do it well.

Ant系列之用JDepend生成包依赖性度量的问题

首先获取JDepend的最新jar文件jdepend-2.9.jar,把它添加到ANT_HOME/lib目录里。

在build.xml中添加:

1
2
3
4
5
6
7
<target name="jdepend" description="Generate dependency metrics for each package">
    <jdepend format="xml" outputfile="${jdepend.dir}/jdepend-report.xml">
        <classpath>
            <pathelement path="classes" />
        </classpath>
    </jdepend>
</target>

运行任务后出现如下的错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
BUILD FAILED
build.xml:59: Missing classespath required argument
    at org.apache.tools.ant.taskdefs.optional.jdepend.JDependTask.execute(JDependTask.java:397)
    at org.apache.tools.ant.UnknownElement.execute(UnknownElement.java:275)
    at org.apache.tools.ant.Task.perform(Task.java:364)
    at org.apache.tools.ant.Target.execute(Target.java:341)
    at org.apache.tools.ant.Target.performTasks(Target.java:369)
    at org.apache.tools.ant.Project.executeSortedTargets(Project.java:1216)
    at org.apache.tools.ant.Project.executeTarget(Project.java:1185)
    at org.apache.tools.ant.helper.DefaultExecutor.executeTargets(DefaultExecutor.java:40)
    at org.eclipse.ant.internal.ui.antsupport.EclipseDefaultExecutor.executeTargets(EclipseDefaultExecutor.java:32)
    at org.apache.tools.ant.Project.executeTargets(Project.java:1068)
    at org.eclipse.ant.internal.ui.antsupport.InternalAntRunner.run(InternalAntRunner.java:423)
    at org.eclipse.ant.internal.ui.antsupport.InternalAntRunner.main(InternalAntRunner.java:137)

查看JDependTask.java文件的397行:

1
2
3
4
5
6
if (getSourcespath() == null && getClassespath() == null) {
    throw new BuildException("Missing classespath required argument");
} else if (getClassespath() == null) {
    String msg = "sourcespath is deprecated in JDepend >= 2.5 - please convert to classespath";
    log(msg);
}

发现是getClassespath() == null了,跳到221行:

1
2
3
public Path getClassespath() {
    return classesPath;
}

不是写了classpath吗?怎么classesPath的值会等于null呢?结果发现在247行有:

1
2
3
4
5
6
7
public void setClasspath(Path classpath) {
    if (compileClasspath == null) {
        compileClasspath = classpath;
    } else {
        compileClasspath.append(classpath);
    }
}

原来是在build.xml中把classespath错写成classpath了,那样当然有问题啦。改过来,再运行就可以了。

Ant系列之用Checkstyle检查代码规范的问题

首先将checkstyle-all-4.1.jar文件拷贝到项目的lib目录下,并建立CheckStyle配置文件。

然后在build.xml文件中声明CheckStyle任务:

1
<taskdef resource="checkstyletask.properties" classpath="${lib.dir}/checkstyle-all-4.1.jar" />

接着是建立CheckStyle任务:

1
2
3
4
5
6
7
8
9
10
<target name="checkstyle" description="Generates a report of code convention violations.">
    <checkstyle config="${docs.dir}/checkstyle_checks.xml" failOnViolation="false">
        <fileset dir="${src.dir}" includes="**/*.java" />
        <formatter type="xml" toFile="${checkstyle.dir}/checkstyle_report.xml" />
    </checkstyle>
    <xslt basedir="${checkstyle.dir}" destdir="${checkstyle.dir}/html" extension=".html"
            style="${docs.dir}/checkstyle-frames.xsl">
        <param name="output.dir" expression="${checkstyle.dir}/html" />
    </xslt>
</target>

其中output.dir是checkstyle-frames.xsl中的参数:

1
<xsl:param name="output.dir" />

在运行任务时可能会出现如下异常:

1
javax.xml.transform.TransformerException: java.lang.RuntimeException: Unrecognized XSLTC extension 'org.apache.xalan.xslt.extensions.Redirect:write'

可以将checkstyle-frames.xsl中的:

1
xmlns:redirect="org.apache.xalan.xslt.extensions.Redirect"

改成下面的内容:

1
xmlns:redirect="http://xml.apache.org/xalan/redirect"

如果出现这样的异常:

1
Got an exception - java.lang.RuntimeException: Unable to get class information for

可以修改对抛出异常的限制:允许抛出非检查型异常异常和抛出另一个已声明异常的子类。

1
2
3
4
<module name="RedundantThrows">
    <property name="allowUnchecked" value="true" />
    <property name="allowSubclasses" value="true" />
</module>

去除退出中国游戏中心时弹出的IE窗口

这次破解的版本是0.8.011.4。

中国游戏中心退出时弹出的IE窗口很烦人,决定做个补丁把它去除掉。

省略无数次尝试……确定退出中国游戏中心后打开的网页地址是 http://www.chinagames.net/PlazaJump/open 。用W32Dasm反汇编iGame.exe,通过String Data References找到网址字符串,双击该字符串,看到以下程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
:0043397A 90                nop
:0043397B 90                nop
:0043397C 90                nop
:0043397D 90                nop
:0043397E 90                nop
:0043397F 90                nop
:00433980 A1A2034800        mov eax, dword ptr [004803A2]
:00433985 56                push esi
:00433986 85C0              test eax, eax
:00433988 8BF1              mov esi, ecx
:0043398A 7411              je 0043399D               -> 判断是否要打开IE窗口
:0043398C 6A00              push 00000000

* Possible StringData Ref from Data Obj ->"http://www.chinagames.net/PlazaJump/open/"
|
:0043398E 6860D84700        push 0047D860
:00433993 B9D0FC4700        mov ecx, 0047FCD0
:00433998 E8B3D2FFFF        call 00430C50             -> 打开IE窗口

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0043398A(C)
|
:0043399D 8BCE              mov ecx, esi

从上面可以知道,只要将0043398A处的je指令改成jmp指令就可以避免退出时弹出的IE窗口了。

现在使用CodeFusion来制作一个文件补丁。它有三种制作补丁的方案,这里使用Find&Replace方法。将0043397A到00433998段的数据作为查找匹配数据,并将0043398A处的74(je指令)改成eb(jmp指令),然后按照步骤生成补丁文件就可以了。

2008/3/19更新

应ls的要求制作了一个针对最新版本(0.8.011.10)的补丁,和原来的补丁放在一起提供下载。

补丁文件

POSIX Conventions for Command Line Arguments

  1. An option is a hyphen followed by a single alphanumeric character, like this: -o.
  2. An option may require an argument (which must appear immediately after the option); for example, -oargument or -o argument.
  3. Options that do not require arguments can be grouped after a hyphen, so, for example, -lst is equivalent to -t -l -s.
  4. Options can appear in any order; thus -lst is equivalent to -tls.
  5. Options can appear multiple times.
  6. Options precede other nonoption arguments: -lst nonoption.
  7. The -- argument terminates options.
  8. The - option is typically used to represent one of the standard input streams.

异常处理最佳实践

英文原文:http://www.onjava.com/pub/a/onjava/2003/11/19/exceptions.html

异常处理的问题之一是知道何时以及如何使用它。在这篇文章中,我将讨论一些异常处理的最佳实践。我也会总结最近关于使用检查型异常的争论。

作为程序员,我们想要编写解决问题的高质量的代码。不幸的是,异常作为我们代码的意外结果出现。没有人喜欢意外结果,因此我们很快找到我们自己的方法去解决它们。我曾经见过一些聪明的程序员以下列方式处理异常:

1
2
3
4
5
6
7
public void consumeAndForgetAllExceptions() {
    try {
        // some code that throws exceptions
    } catch (Exception ex) {
        ex.printStacktrace();
    }
}

上面的代码有什么问题?

一旦异常被抛出,正常的程序执行被挂起,并且控制权转移到catch块。catch块捕获异常后什么也没做。在catch块后面的程序继续执行,就好象什么也没发生。

下面的代码怎么样?

1
2
public void someMethod() throws Exception {
}

这是一个空方法,它里面没有任何代码。空方法如何能抛出异常呢?Java不能阻止你这么做。最近我正好遇见过类似的代码:方法声明抛出异常,但方法里没有实际产生那个异常的代码。当我问那个程序员时,他回复说:“我知道,它会让API变得糟糕,但我一直都是这么做的,而且这样做很管用。”

C++社区花了好几年去确定如何使用异常。这种争论在Java社区刚刚开始。我曾经见过几个Java程序员与异常的使用作斗争。如果没有正确使用,异常会导致你的程序变慢,因为它需要内存和CPU去创建、抛出和捕获异常。如果过度使用,会使代码难以阅读,让使用API的程序员感到挫折。我们都知道挫折感(frustrations)会导致特殊技巧( hacks)和代码味道。客户端代码只要忽略异常或者抛出它们就可以规避这个问题,就像前面的两个示例。

异常的本质

大体上说,有3种不同的情况可以导致异常被抛出:

  • 由于编程错误的异常: 这种异常是由于编程错误产生的(比如,NullPointerException和IllegalArgumentException)。客户端代码通常不能做关于编程错误的任何事情。
  • 由于客户端代码错误的异常: 客户端代码尝试API不允许的东西,从而违反它的契约。如果异常有提供有用的信息,客户端可以采取一些替代的做法。例如:当分析一个非格式良好的XML文档时有异常被抛出。异常包含XML文档中引起问题的位置信息。客户端可以使用该信息去采取恢复措施。
  • 由于资源失败的异常: 当资源失败时生成的异常。例如:系统内存不足或者网络连接失败。资源失败的客户端响应是上下文驱动的。客户端可以过段时间重试操作或者仅仅记录资源失败和停止应用程序。

Java中的异常类型

Java定义了两类异常:

  • 检查型异常: 继承自Exception类的异常是检查型异常。客户端代码必须处理被API抛出的检查型异常,不是在catch子句中就是通过throw子句向外转发。
  • 非检查型异常: RuntimeException也是扩展自Exception。不过,所有从RuntimeException继承的异常享有特殊待遇。对客户端代码是否处理它们没有要求,因此被称为非检查型异常。

举例来说,下图显示了NullPointerException的层次结构。

nullpointer-exception-hierarchy

在这个图中,NullPointerException扩展自RuntimeException,因此是非检查型异常。

我曾经见过检查型异常的重度使用和非检查型异常的最小使用。最近,在Java社区有关于检查型异常和其真正价值的激烈争论。这场争论源于Java似乎是第一个带有检查型异常的主流OO语言这个事实。C++和C#根本没有检查型异常,在这些语言中所有的异常都是非检查型的。

由较低层抛出的检查型异常是强制性的,要求调用层必须捕获或者抛出它。如果客户端代码无法有效地处理异常,在API和它的客户端之间的检查型异常契约很快就会转变为不必要的负担。客户端代码的程序员可能会走捷径,通过使用一个空的catch块抑制异常或者仅仅抛出它。实际上,它只是把负担放到客户端的调用者身上。

检查型异常也被指责破坏封装。考虑以下代码:

1
2
public List getAllAccounts() throws FileNotFoundException, SQLException {
}

方法getAllAccounts()抛出两个检查型异常。该方法的客户端必须显式地处理这些特定实现的异常,即使它也不知道在getAllAccounts()中什么文件或数据库调用失败,或者没有业务提供文件系统或数据库逻辑。因此,异常处理在方法和它的调用者之间强制造成了一个不适当的紧密耦合。

设计API的最佳实践

说了这么多,现在让我们谈谈如何设计一个正确抛出异常的API。

1. 当确定使用检查型异常还是非检查型异常时,问问你自己:“当异常发生时客户端代码可以采取什么措施?”

如果客户端可以采取一些替代措施来从异常中恢复,把它设置为检查型异常。如果客户端不能做任何有用的事情,那么就把它设置为非检查型异常。这里的“有用的”的意思是采取措施从异常中恢复而不是仅仅记录异常。总的来说:

当异常发生时客户端的反应 异常类型
客户端代码将基于异常中的信息采取一些有用的恢复操作 把它设置为检查型异常
客户端代码不能做任何事情 把它设置为非检查型异常

此外,对于所有的编程错误尽量使用非检查型异常。非检查型异常有个好处就是不会强制客户端代码显式地处理它们。它们传播到你想捕获它们的地方,或者直接就是报告异常。Java的API有许多非检查型异常,像NullPointerException、IllegalArgumentException和IllegalStateException。我更倾向于使用Java提供的标准异常而不是创建自己的。这会让我的代码容易理解和避免增加代码的内存占用。

2. 保持封装性。

不要让特定实现的检查型异常逐步上升到更高的层。例如,不要把来自数据访问代码中的SQLException传播到业务对象层。业务对象层不需要了解SQLException。你有两个选择:

  • 如果客户端代码期望从异常中恢复,把SQLException转换成别的检查型异常。
  • 如果客户端代码不能做关于它的任何事情,把SQLException转换成一个非检查型异常。

大多数时候,客户端代码不能做关于SQLException的任何事情。不要犹豫去把它们转换成非检查型异常。考虑下面的代码片段:

1
2
3
4
5
6
7
public void dataAccessCode() {
    try {
        // some code that throws SQLException
    } catch (SQLException ex) {
        ex.printStacktrace();
    }
}

这里的catch块只是抑制异常而且什么也不做。理由是对于SQLException我的客户端没有什么能做的。以下列方式处理它怎么样?

1
2
3
4
5
6
7
public void dataAccessCode() {
    try {
        // some code that throws SQLException
    } catch (SQLException ex) {
        throw new RuntimeException(ex);
    }
}

它把SQLException转换成RuntimeException。如果SQLException发生,catch子句会抛出一个新的RuntimeException。执行线程挂起,报告异常。不管怎样,我没有用不必要的异常处理让我的业务对象层变得糟糕,特别是因为它不能做关于SQLException的任何事情。如果我的catch子句需要根异常的原因,我可以利用自JDK 1.4起就在所有异常类中有效的getCause()方法。

当SQLException发生时如果你确信业务层能采取一些恢复动作,你可以把它转换成一个更有意义的检查型异常。但我发现大多数时间仅仅抛出RuntimeException就足够。

3. 如果不能给客户端代码提供有用的信息就不要试图去创建新的定制异常。

下面的代码有什么问题?

1
2
public class DuplicateUsernameException extends Exception {
}

它没有给客户端代码提供任何有用的信息,除了一个指示的异常名字。不要忘记Java的异常类就像其它类,在其中你可以添加你认为客户端代码将会调用以便获得更多信息的方法。

我们可以给DuplicateUsernameException添加有用的方法,像:

1
2
3
4
5
public class DuplicateUsernameException extends Exception {
    public DuplicateUsernameException(String username) {}
    public String requestedUsername() {}
    public String[] availableNames() {}
}

新版本提供两个有用的方法:requestedUsername()返回请求的名字;availableNames()返回与请求的名字相似的一组有效用户名。客户端可以使用这些方法去告知请求的用户名是无效的和其它的用户名是有效的。但如果你不打算添加额外的信息,那么就抛出一个标准的异常:

1
throw new Exception("Username already taken");

如果你认为当用户名已被占用时客户端代码除了日志记录不打算采取任何动作,那么抛出一个非检查型异常就更好:

1
throw new RuntimeException("Username already taken");

或者,你甚至可以提供一个方法检查是否用户名已被占用。

值得重复的是,检查型异常被用于那些客户端API可以基于异常中的信息采取一些富有成效的动作的情况中。对于所有的编程式错误尽量使用非检查型异常。它们让你的代码可读性更强。

4. 用文档说明异常。

你可以使用Javadoc的@throws标签来说明API抛出的检查型和非检查型异常。不过,我倾向于编写单元测试来说明异常。测试允许我看见动作中的异常,因此能被当作可以执行的文档。无论你做什么,都有一些办法让客户端代码可以通过它们了解你的API抛出的异常。这里是一个用于测试IndexOutOfBoundsException的示例单元测试:

1
2
3
4
5
6
7
8
public void testIndexOutOfBoundsException() {
    ArrayList blankList = new ArrayList();
    try {
        blankList.get(10);
        fail("Should raise an IndexOutOfBoundsException");
    } catch (IndexOutOfBoundsException success) {
    }
}

当blankList.get(10)被调用时上面的代码将会抛出IndexOutOfBoundsException。如果没有抛出异常,fail("Should raise an IndexOutOfBoundsException")语句会显式地让测试失败。通过为异常编写单元测试,你不仅说明异常是如何工作的,也通过测试特殊场景让你的代码更加健壮。

使用异常的最佳实践

下一组最佳实践显示客户端代码将如何处理抛出检查型异常的API。

1.总是要做些清理工作

如果你使用像数据库连接或网络连接这样的资源,确保你已经把它们清理干净。如果你调用的API只使用非检查型异常,你仍然应该在使用后用try-finally块清理资源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void dataAccessCode() {
    Connection conn = null;
    try {
        conn = getConnection();
        // some code that throws SQLException
    } catch (SQLException ex) {
        ex.printStacktrace();
    } finally {
        DBUtil.closeConnection(conn);
    }
}

class DBUtil {
    public static void closeConnection(Connection conn) {
        try {
            conn.close();
        } catch (SQLException ex) {
            logger.error("Cannot close connection");
            throw new RuntimeException(ex);
        }
    }
}

DBUtil是一个可以关闭连接的实用工具类。重点是finally块的使用,无论异常是否被捕获它都会执行。在这个例子中,finally关闭连接,如果关闭连接有问题就抛出一个RuntimeException。

2. 不要使用异常控制流程

生成栈跟踪是昂贵的,栈跟踪的价值是在调试。在一个流程控制的情况中,栈跟踪将被忽略,因为客户端仅仅想知道如何处理。

在下面的代码中,一个定制的异常MaximumCountReachedException被用来控制流程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void useExceptionsForFlowControl() {
    try {
        while (true) {
            increaseCount();
        }
    } catch (MaximumCountReachedException ex) {
    }
    // continue execution
}

public void increaseCount()
    throws MaximumCountReachedException {
    if (count >= 5000)
        throw new MaximumCountReachedException();
}

useExceptionsForFlowControl()使用一个无限循环增加计数直到异常被抛出。这不仅使代码难以阅读,而且让代码变得更慢。仅在特殊情况下使用异常处理。

3. 不要抑制或忽略异常

当来自API的一个方法抛出检查型异常时,它试图告诉你应该采取一些反向动作。如果检查型异常对你毫无意义,不要犹豫去把它转换成一个非检查型异常并且再次抛出它,但是不要通过用{}捕获异常来忽略它,然后继续,好像什么也没发生过。

4. 不要捕获顶级异常

非检查型异常继承自RuntimeException类,RuntimeException又继承自Exception。通过捕获Exception类,你也捕获了RuntimeException,如以下代码所示:

1
2
3
try {
} catch (Exception ex) {
}

上面的代码同样忽略了非检查型异常。

5. 只记录异常一次

多次记录相同的异常栈跟踪可能会让程序员在检查关于异常原始源的栈跟踪时被迷惑。因此,只记录它一次。

总结

这些都是异常处理最佳实践的一些建议。我无意开始一场检查型异常与非检查型异常之间的宗教战争。你必须根据你的需求自定义设计和用法。我相信,假以时日,我们会找到更好的用异常编码的方法。

我要感谢Bruce Eckel、Joshua Kerievsky和Somik Raha他们对我在写这篇文章时的支持。

相关资源

贫血领域模型

英文原文:https://martinfowler.com/bliki/AnemicDomainModel.html

贫血领域模型是那些已近存在了相当长时间的反模式中的一份子,然而目前似乎有一个特定的高潮。我和Eric Evans聊过这个,并且我们都注意到它们似乎越来越流行。作为真正的领域模型支持者,这不是一件好事。

贫血领域模型的基本症状是乍一看它就像真的领域模型一样。这些对象大多以领域空间中的名词命名,并且它们被真正的领域模型拥有的丰富的关系和结构连接。当你观察行为时问题来了,你意识到这些对象几乎没有任何行为,仅仅只是封装了getter和setter方法。这些模型的设计规则往往说不要把任何领域逻辑放到领域对象里。取而代之的是一组服务对象捕获了所有的领域逻辑。这些在领域对象上层的服务把领域对象当数据使用。

这种反模式最根本的恐怖在于它和面向对象设计的基本理念完全相反,它只是把数据和处理结合在一起。贫血领域模型其实只是一种过程式风格的设计,正是那种像我(和Eric)自Smalltalk早期以来一直在与之作斗争的顽固者。更糟糕的是,许多人认为贫血对象是真正的对象,因此完全没有抓住面向对象设计是怎么回事的要领。

现在面向对象的纯粹主义当然很好,但我意识到我需要更多的基本论据来反对这种贫血症。本质上贫血领域模型的问题是它们承担了领域模型所有的成本,没有产生任何的好处。主要成本是映射到数据库的笨拙,它通常会导致一整层的O/R映射。在而且只有在你使用强大的面向对象技术去组织复杂的逻辑时这才值得。通过把所有的行为拉取到服务中,不管用什么方法,基本上你最后得到的都是事务脚本,从而失去领域模型能带来的优势。正如我在《企业应用架构模式》中讨论过的,领域模型并不总是最好的工具。

值得强调的是,把行为放进领域对象不应违背利用分层将领域逻辑从诸如持久和表现责任中分离的坚实方法。在领域对象中的逻辑应该是领域逻辑——验证、计算、业务规则——无论你喜欢叫它什么。(某些情况下,你会产生把数据源和表现逻辑放到领域对象里的争论,但它和我对贫血症的看法无关。)

这一切混乱的来源是许多OO专家强烈推荐在领域模型顶部放一层过程式服务,来形成一个服务层。但这不是让领域模型完全没有行为的一个论据,其实服务层主张服务层要和行为丰富的领域模型一起使用。

Eric Evans的精彩书籍《领域驱动设计》有如下的文字谈及这些层:

应用层【他对服务层的命名】:定义软件可以完成的工作,并且指挥具有丰富含义的领域对象来解决问题。这个层所负责的任务对业务影响深远,对跟其它系统的应用层进行交互非常必要。这个层要保持简练。它不包括处理业务规则或知识,只是给下一层中相互协作的领域对象协调任务、委托工作。在这个层次中不反映业务情况的状态,但反映用户或程序的任务进度的状态。

领域层(或者模型层):负责表示业务概念、业务状况的信息以及业务规则。并且保持这些内容的技术细节由基础结构层来完成,反映业务状况的状态在该层中被控制和使用。这一层是业务软件的核心。

这里的关键点是服务层是瘦的——所有的关键逻辑存在于领域层。他在服务模式中重申了这个观点:

现在,最常犯的错误就是太轻易地放弃把这种行为配置到合适的对象上,逐渐地滑向过程式编程。

我不知道为什么这个反模式如此常见。我猜想它是因为大多数人还没有真正地和正确的领域模型工作过,特别是如果他们有数据背景。有些技术鼓励正确的领域模型,例如J2EE的实体Bean,它是我偏爱POJO领域模型的理由之一。

总之,你在服务中发现越多的行为,你就越难体会到领域模型的好处。如果你所有的逻辑都在服务中,那你将得不到任何好处。

忘记Linux的root密码后

一不小心忘记了Linux的root密码,只能以普通用户zer0ne的身份进入系统。

使用Linux的安装光盘启动电脑,出现boot:引导符后输入以下内容:

1
linux single root=/dev/hda3 initrd=  # /dev/hda3是Linux系统所在的分区

进入系统,把/etc目录下的shadow和passwd文件复制到/home/zer0ne目录下,修改shadow文件的属性。再到John the Ripper password cracker网站下载John the Ripper,编译安装好后输入:

1
./unshadow passwd shadow > passwd.1

接着运行破解:

1
john passwd.1

这样,过半个多小时我的root密码就回来了。

Linux下JDK 1.5.0的安装和配置

先从网上下载jdk-1_5_0-linux-i586-rpm.bin,这是个自解压的文件。然后在终端输入以下指令进行安装:

1
2
3
chmod +x jdk-1_5_0-linux-i586-rpm.bin
./jdk-1_5_0-linux-i586-rpm.bin
rpm –ivh jdk-1_5_0-linux-i586-rpm.bin

安装好后,用文本编辑器打开.bashrc文件,在末尾添加以下内容:

1
2
3
4
5
6
set JAVA_HOME=/usr/java/jdk1.5.0
export JAVA_HOME
set PATH=$PATH:$JAVA_HOME/bin
export PATH
set CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
export CLASSPATH

修改后的文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# .bashrc
# User specific aliases and functions

# Source global definitions
if [ -f /etc/bashrc ]; then
    . /etc/bashrc
fi

set JAVA_HOME=/usr/java/jdk1.5.0
export JAVA_HOME
set PATH=$PATH:$JAVA_HOME/bin
export PATH
set CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
export CLASSPATH

然后重新登陆,用echo $JAVA_HOME查看环境变量。咦!怎么是空的呢?肯定有什么地方出错了,是不是不要set啊,试试把它去掉看看,现在的文件内容是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# .bashrc
# User specific aliases and functions

# Source global definitions
if [ -f /etc/bashrc ]; then
    . /etc/bashrc
fi

JAVA_HOME=/usr/java/jdk1.5.0
export JAVA_HOME
PATH=$PATH:$JAVA_HOME/bin
export PATH
CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
export CLASSPATH

再次执行echo $JAVA_HOME命令:

1
/usr/java/jdk1.5.0/

果然,去掉set后环境变量就OK了。