本人作为学生时代上过几堂课的java业余小白在不懈的努力下还是掌握了一些java coding的基础知识,只是至今我都无法相信我的当代软件开发技术与实践课考试刚刚及格的事实,难道我跟学校真的八字不合~在接下来的讲解中不会使用到eclipse这个目前最流行的开发工具,而是在纯粹的命令行环境下编译打包和执行java代码,虽然平时修改代码我也用eclipse,但是eclipse真的是太好用了反而隐藏了java的众多概念与机制,而不了解这些概念和机制很难说掌握了java的开发技巧,即使能写出性能优良的code~毕竟我并不是一个软件开发工程师,因此更加关注如何修改并执行java code而非如何开发,所以这篇文章的主要适用人群是像我这样的通信人,相信读完之后就可以摆脱小白的身份并应用到工作实践中,并且不要担心本文不涉及eclipse会导致文章实践性不足,在了解以下知识点之后eclipse的使用真的可以无师自通~
1.java程序的文件结构与类的调用名-package
即使不懂java也应该知道java是完全面向对象的编程语言,因此本质上java程序里的文件都是定义java类的类文件(.java),并且文件名就是文件中定义public类的类名(class name),即类文件对应java 类:
<class_name>.java <--> java class
为了实现相同类名存在于程序中而且不影响调用,java引入的包的概念,即在类文件中使用package关键字定义该类隶属的包,没有使用package关键字定义包名的类隶属于默认包(default package),其实java包的实质就是文件夹,包名(package name)就是文件夹的名字,类似于使用/
定义文件夹的层级结构,java包是用.
来表示层级结构,例如存储在com/xxx/yyy
目录下类的包名就是com.xxx.yyy
,因此文件夹就对应java包:
<package_name>/ <--> java package
我们知道任何文件系统都有一个根目录作为结构顶级,java程序的“根目录”就是所谓的默认包,该“根目录”可以是系统文件系统任意一个文件夹或者是一个jar包,在该“根目录”下或内存储属于默认包的类文件,对应包层级的文件夹结构以后隶属于相应包的类文件,典型的java程序目录树如下所示:
#Java Program Architecture
.<Any_dir or Jar>
├── A.class
├── package_A
│ ├── B.class
│ └── package_C
│ └── C.class
└── package_B
└── D.class
在这种层级结构下程序中每个java类都形成对应其存储路径的调用名(call name),即<package_name>.<class_name>
,例如上述class A的调用名就是A
,class B的调用名是package_A.B
,class C的调用名是package_A.package_C.C
,class D的调用名是package_B.D
。
命令行执行java程序即在java命令后指定执行类的调用名,通过调用名中的包名逐级深入直至定位到类文件,然后java虚拟机加载代码执行,需要注意的是存储在一定层级结构的类文件源码中一定要使用package关键字定义当前层级的包,不然即使调用名能正确的反映类文件的位置,java虚拟机也会报错提示找不到该类。
2.java程序的“根目录”
此外类调用名只是反映了类文件在“根目录”下/内的路径,因此让java命令知晓我们开发的java程序“根目录”就十分重要,只有定位了“根目录”才能逐级找到类代码,java提供了两套方案指定程序“根目录(classpath)”。
a. 环境变量 - CLASSPATH
每次执行程序时Java默认会从该环境变量定义的路径下或者jar包中搜索类,该变量中的“.”
代表当前路径,也就是执行Java命令的路径,这样就无需将当前完整路径添加到该变量中,命令执行较为方便。
export CLASSPATH=.:/usr/jre/lib:/usr/lib/tools.jar:/xyz/testng-6.11.1-SNAPSHOT.jar:/xyz/jcommander-1.7.jar
b. Java VM 参数- classpath/cp
java -classpath/cp /path:/path/file.jar:/path/file.zip org.testng.TestNG
example:java -cp /xyz/mob-rmm-R18A.jar:/xyz/testng-6.11.1-SNAPSHOT.jar org.testng.TestNG
the :
separated list of directories, JAR archives, and ZIP archives to search for class files,需要注意的是上述VM参数会覆盖环境变量CLASSPATH的设置。
3.Java命令 - VM param & program param
java命令的执行格式如下所示:
java [options] class_call_name [arguments]
java [options] -jar file.jar [arguments]
上述范式中options即为VM param,参数值传递给JAVA虚拟机,设置虚拟机相关属性,除去-cp/classpath <list of path, jar and zip>
之外比较重要的的VM参数还有-D<name>=<vaule>
,该-D
参数设置虚拟机的系统属性和其值(set a system property and its value
),如果属性值是包换空格的字符串那必须用双引号包围,使用该参数定义的系统属性值可以使用System
类中的getProperties()
方法得到<System.getProperty("Name"
)>。
范式中的arguments就是所谓的program param,参数之间空格隔开,该参数列存储在main方式中定义的args数组中(String[] args
)。
虚拟机参数-D
和程序参数是最常用的引入外部参数的两种方法,使用以下code加深理解:
//reference from http://blog.youkuaiyun.com/cuidiwhere/article/details/17885675
public class test {
public static void main(String[] args) {
System.out.println("program params:");
if (args == null || args.length == 0) {
System.out.println("\t" +"no input params");
} else {
for (String arg:args) {
System.out.println("\t" + arg);
}
}
System.out.println("system property params:");
String sysprop1 = "sysprop1";
String sysprop2 = "sysprop2";
System.out.println("\tName:" + sysprop1 + ",Value:" + System.getProperty(sysprop1));
System.out.println("\tName:" + sysprop2 + ",Value:" + System.getProperty(sysprop2));
System.out.println("Default VM arguments:");
System.out.println("java_vendor:" + System.getProperty("java.vendor"));
System.out.println("java_vendor_url:"
+ System.getProperty("java.vendor.url"));
System.out.println("java_home:" + System.getProperty("java.home"));
System.out.println("java_class_version:"
+ System.getProperty("java.class.version"));
System.out.println("java_class_path:"
+ System.getProperty("java.class.path"));
}
}
测试结果如下:
xyx@zyx:~/space> java -Dsysprop1=hello -Dsysprop2=world test good day
program params:
good
day
system property params:
Name:sysprop1,Value:hello
Name:sysprop2,Value:world
Default VM arguments:
java_vendor:Oracle Corporation
java_vendor_url:http://java.oracle.com/
java_home:/usr/java/jdk1.7.0_25/jre
java_class_version:51.0
java_class_path:.:/usr/jre/lib:/usr/lib/tools.jar:/xyz/mob-rmm-R18A.jar:/xyz/jcommander-1.7.jar:/xyz/testng-6.11.1-SNAPSHOT.jar
VM参数和program参数的区别在于位置,class和jar包之前指定的参数均为VM参数,class和jar包之后指定的参数均为program参数,VM参数放置在class和jar包之后会被当做program参数传递到args
数组中,而program参数放置到class和jar包之前则被解析为VM参数,一般会导致JAVA报错提示不识别的选项(Unrecognized option: -d
)。
jar包是Java定义的打包文件格式,类似于zip包,用于封装Java程序,可以理解为jar包就是上述Java程序“根目录”的文件封装形式,“根目录”下的文件结构原封不动的封装到jar包里,特别是Java原生支持解析读取jar包内的包与类文件,这样就使Java程序的存储、发布变的十分简单。一般jar包生成的过程中会自动建立META-INF/MANIFEST.MF文件,可以使用该文件里参数指定默认执行的类调用名,这样就可以直接通过指定jar包的方式执行程序,该内容在下一节中进行讲解。
4.jar命令和jar包执行
pack:
//将指定路径的文件结构(从根目录开始)打包到指定jar包中,不常用
jar cvf file.jar /path/
//将当前路径下的文件结构打包到指定jar包中,常用
jar cvf file.jar ./
//-C相当于cd到指定的路径下然后执行不带-C参数的jar命令,常用
jar cvf file.jar -C /path/ ./
//使用指定的MANIFEST.MF文件而不是自动生成的,常用
jar cvfm file.jar MANIFEST.MF -C /path/ ./
unpack:
//将jar包内容解压到当前路径
jar xvf file.jar
如果不指定MANIFEST.MF文件,生成jar包的过程会自动建立META-INF文件夹并在其下生成MF文件,jar包内的目录树如下所示:
.<test.jar>
├── main
│ ├── Connect.class
│ ├── Connect.java
│ ├── Enumer.class
│ └── Enumer.java
└── META-INF
└── MANIFEST.MF
MANIFEST.MF文件的作用比较多,比如之前提过的生成可执行的jar包就要在该文件下使用Main-Class
指定默认的类调用名,该类必须是拥有main函数的main class才能作为jar包的程序入口,简单的MF文件如下所示:
exyz@ldfker:~> cat MANIFEST.MF
Manifest-Version: 1.0
Created-By: 1.6.0_20 (Sun Microsystems Inc.)
Main-Class: main.Enumer
Class-Path: /xyz/mob-rmm-R18A.jar
使用jar包执行程序方便简单,但这种方式带来的弊端是无法使用环境变量CLASSPATH
和VM参数-classpath/cp
引用其他jar包和路径,原因在于上述两种方式指定的classpath(“根目录”)是由AppClassloader加载的,Java命令引入-jar参数后,AppClassloader只解析指定的file.jar内部的class,导致无法引用其他的class。
参考http://www.cnblogs.com/adolfmc/archive/2012/10/07/2713562.html
常用解决方法:
一、MANIFEST.MF文件定义Class-Path
在jar包MANIFEST.MF文件中添加Class-Path: /path /path/file.jar
,多个路径或jar包可以采用添加多个Class-Path:
标签的方式也可以一个Class-Path:
标签后多个item用空格隔开。
Class-Path: /xyz/mob-rmm-R18A.jar /xyz/testng-6.11.1-SNAPSHOT.jar
二、Bootstrap Classloader定义类路径
上面介绍了JAVA VM param中的-D
参数,要使用bootstrap classloader还需要了解-X
参数,该参数形式也是-X<name>
,是Java的non-standard options,所谓的非标准参数就是官方会在不通知的情况下修改或废除这些参数。
The -X options are non-standard and subject to change without notice.
使用java -X
可以显示当前Java支持的非标准VM参数,我们需要的bootstrap参数有以下三个:
xyz@moiyuier:~> java -X
-Xbootclasspath:<directories and zip/jar files separated by :>
set search path for bootstrap classes and resources //完全的取代系统bootclass,较少使用
-Xbootclasspath/a:<directories and zip/jar files separated by :>
append to end of bootstrap class path //在系统bootclass加载之后载入,常用
-Xbootclasspath/p:<directories and zip/jar files separated by :>
prepend in front of bootstrap class path //在系统bootclass加载之前载入,可能会跟系统bootclass冲突,不常用
所以执行jar包命令如下:
java -Xbootclasspath/a:/path:/path/file.jar -jar file.jar
三、自定义classloader实现加载
参考http://cuixiaodong214.blog.163.com/blog/static/951639820099135859761
5.javac命令
之前各节都只是涉及Java类的执行,从文件形式上来看一个Java类就是后缀为.class
的字节码文件,该字节码文件才是Java程序的主体,所以Java跟纯粹的脚本语言区别也体现在这里,Java虚拟机载入的并不是Java源码而是使用后缀名为.java
的源码文间编译生成的字节码,实现编译功能的命令就是javac,实质上就是Java的编译器。
Java命令执行类时需要指定类的调用名,执行的前提条件就是类定义的包结构跟所在的文件结构相同,而使用javac编译源码则无需考虑包结构,仅需考虑类需要import的外部类是否通过环境变量CLASSPATH
和VM参数-classpath/cp
指定。
典型的javac执行命令如下:
javac -cp/classpath /path:/path/file.jar /path/<class_name>.java
如果指定了环境变量CLASSPATH
,则无需指定-cp/classpath
,上述命令就在源码的同级目录生成同名的class
文件,一般手动编译的情况下都是直接cd
到源码目录执行javac
命令,所以/path
就是./
,实际中可以直接省略。
6.Junit/TestNG测试框架
不涉及测试框架的细节分析,大体来说能称为框架的软体都是留有调用接口封装好的软件包,Java框架大都以jar包的形式发布,目前TestNG比Junit要流行,所以只演示TestNGK测试的执行,其实框架的执行方式大同小异。
想要使用TestNG进行测试或者开发程序就要下载TestNG的jar包,现在多数的项目都在使用maven作为构建发布工具,所以我竟然没有在TestNG的官网上找到最新版本jar包的下载链接,不得已只能直接clone TestNG的git repo,repo中提供了编译打包的可执行脚本,最终得到了TestNG的jar包,该jar包执行测试时会报错提示not found Jcommander,于是下载Jcommander jar包,遍寻不见,最后还是去maven的中央仓库download到本地,强大的maven~
testng-6.11.1-SNAPSHOT.jar
jcommander-1.7.jar
将上面的两个jar包路径添加到CLASSPATH变量中,就可以执行TestNG主类org.testng.TestNG
。
xyz@mobrmm:~/space> java org.testng.TestNG
You need to specify at least one testng.xml, one class or one method
Usage: <main class> [options]
The XML suite files to run
Options: -configfailurepolicy Configuration failure policy (skip or continue)
-d Output directory
-dataproviderthreadcount Number of threads to use when running data providers
-excludegroups Comma-separated list of group names to exclude
-groups Comma-separated list of group names to be run
-junit JUnit mode (default: false)
-listener List of .class files or list of class names implementing ITestListener or ISuiteListener
-log, -verbose Level of verbosity
-methods Comma separated of test methods (default: [])
-methodselectors List of .class files or list of class names implementing IMethodSelector
-mixed Mixed mode - autodetect the type of current test and run it with appropriate runner (default: false)
-objectfactory List of .class files or list of class names implementing ITestRunnerFactory
-parallel Parallel mode (methods, tests or classes)
-port The port
-reporter Extended configuration for custom report listener
-suitename Default name of test suite, if not specified in suite definition file or source code
-suitethreadpoolsize Size of the thread pool to use to run suites (default: 1)
-testclass The list of test classes
-testjar A jar file containing the tests
-testname Default name of test, if not specified in suitedefinition file or source code
-testnames The list of test names to run
-testrunfactory, -testRunFactory The factory used to create tests
-threadcount Number of threads to use when running tests in parallel
-usedefaultlisteners Whether to use the default listeners (default: true)
-xmlpathinjar The full path to the xml file inside the jar file (only valid if -testjar was specified) (default: testng.xml)
执行后会得到main class后可以接的program param列表,一般使用-testclass
指定我们定义的测试类,由org.testng.TestNG
主类调用执行,更一般的可以将要执行的测试类定义到xml文件中,xml文件直接作为参数传递给TestNG主类。
java org.testng.TestNG -testclass <test_class_call_name>
java org.testng.TestNG /path/<suite>.xml
另外,Junit的主类是org.junit.runner.JUnitCore
。