Java Command
编译
1 | javac Main.java -cp jar1.jar:jar2.jar |
Main.java
放在前面还是放在后面都可以编译成功
- -d: 指定编译之后类保存的目录,目录必须提前创建好
在引用 maven repository
库的时候,必须明确指定 jar
文件的名称,因为 Maven 在下载的时候可能会同时下载 source.jar
和 lib.jar
,会造成引入错误,所以必须明确指定:
1 | javac PrimitiveServlet.java -cp ~/.m2/repository/javax/servlet/javax.servlet-api/3.1.0/javax.servlet-api-3.1.0.jar:./ |
运行
运行的时候必须站在包的顶级目录执行:
1 | java -cp .:jar1.jar:jar2.jar Main |
运行 jar
官方示例:
1 | java -jar mybatis-generator-core-1.3.6-SNAPSHOT.jar -configfile generatorConfig.xml -overwrite |
可是 mybatis-generator-core-1.3.6-SNAPSHOT.jar
要依赖 mysql-connector
,所以应该添加 classpath
,而 -cp
和 -jar
这两个命令是不能同时使用的,所以唯有指定 main class
这种办法了:
1 | java -cp mybatis-generator-core-1.3.6-SNAPSHOT.jar:mysql-connector-java-6.0.6.jar org.mybatis.generator.api.ShellRunner -configfile generatorConfig.xml -overwrite |
其中 main
方法所在的类,是通过搜过 mybatis-generator
的源代码找到的:
1 | zk@zk-pc:~/Documents/github/generator$ grep "main(String" -Rn . |
反编译
javap
: Disassembles one or more class files
-c
: Prints disassembled code, for example, the instructions that comprise the Java bytecodes, for each of the methods in the class.
1 | javap -c DocFooter.class |
jar 包
命令 | 操作 |
---|---|
jar cf jar-file input-file(s) |
创建一个 JAR 文件 |
jar tf jar-file |
查看 JAR 内容 |
jar xf jar-file |
提取内容 |
java -jar app.jar |
运行 JAR 文件 |
- -c: 创建
- -f: 你想要输出到文件,而不是命令行
- -m: 你想要从一个已经存在的文件合并消息到 manifest 文件中
- -t: 列举包的 table
1 | jar cfm app.jar manifest.txt com/zk/HelloWorld.class |
manifest.txt 文件内容如下所示:
1 | Main-Class: com.zk.HelloWorld |
创建出来的 MANIFEST.MF 文件如下所示:
1 | Manifest-Version: 1.0 |
添加 Main-Class
:
- -e: entrypoint
1 | jar cfe app.jar com.zk.HelloWorld com/zk/HelloWorld.class |
打包 resources:
打包的过程是严格按照路径结构来的:
1 | jar cf log4jtest.jar Log4jTest.java resources/* |
生成的 log4jtest.jar
的目录结构如下所示:
1 | META-INF/ |
jps - 列举运行的 JVM 信息
- -m: 列举传递给
main
方法的参数 - -l: 列举完整的包名或完整的 JAR 文件的路径
- -v: 列举传递给
JVM
的参数
Java 资源加载
目录:
a.properties
和 ResourcePositionA.class
位于同一级目录(同一个包)下,a.properties
挪到其它地方就找不到了:
1 | java.net.URL url = this.getClass().getResource("a.properties") |
目录:
system.properties
位于顶级目录,system.properties
挪到其它地方就找不到了,在 ResourcePositionA.java
中有如下代码:
1 | java.net.URL systemResource = ClassLoader.getSystemResource("system.properties"); |
从顶级目录获取资源也可以使用 this.getClass().getResource()
方法:
1 | this.getClass().getResource("/system.properties") |
还有一种 getResource
的方法:
1 | Thread.currentThread().getContextClassLoader().getResource(resourceName) |
ClassLoader
can be passed (shared) when creating a new thread using Thread.setContextClassLoader, so that different thread contexts can load each other classes/resources. If not set, the default is the ClassLoader
context of the parent Thread. This method is not appropriate if we want to load resources inside the packages unless we use complete paths starting from root.
Log4j
查找 log4j.properties
的逻辑:
1 | public static URL getResource(String resource) { |
编译:
1 | mkdir output |
无论将 log4j.properties
放到编译后的类文件旁边,还是直接打包进 jar
包中,都可以实现加载与查找。站在 output
文件夹执行命令:
1 | jar cfe log4j.jar org.apache.log4j.helpers.Loader log4j.properties org/apache/log4j/helpers/Loader* |
执行命令如下所示:
1 | java -jar log4j.jar |
执行结果:
1 | Trying to find [log4j.properties] using context classloader sun.misc.Launcher$AppClassLoader@42a57993. |
具体源代码可以参考 log4j
的 github
的两个源文件:
org.apache.log4j.helpers.Loader.java
org.apache.log4j.LogManager.java
使用 Maven
或者 Gradle
帮助我们编译项目的时候,资源文件需要放到 resources
目录下面:
编译之后,aa.txt
会拷贝到 target/classes/
目录下面:
当尝试从 Jar
文件中读取资源的时候,遇到如下问题:
1 | Exception in thread "main" java.lang.IllegalArgumentException: URI is not hierarchical |
以下是 stackoverflow 给出的解释:
You cannot do this
1 | File src = new File(resourceUrLol.toURI()); //ERROR HERE |
与此相对应,我就是使用了这样的错误获取资源的方法:
1 | return WorkbookFactory.create(new File(getURL(excelName).toURI())); |
it is not a file! When you run from the ide you don’t have any error, because you don’t run a jar
file. In the IDE classes and resources are extracted on the file system.
But you can open an InputStream
in this way:
1 | InputStream in = Model.class.getClassLoader().getResourceAsStream("/data.sav"); |
Remove “/resource”. Generally the IDEs separates on file system classes and resources. But when the jar is created they are put all together. So the folder level “/resource” is used only for classes and resources separation.
When you get a resource from classloader you have to specify the path that the resource has inside the jar, that is the real package hierarchy.
或者通过 URL
自身携带的方法 java.net.URL#openStream()
) 转为 InputStream
来操作
文件转为 URL
的方法:
1 | new File(path).toURI().toURL(); |
注意文件转为 URL
意味着什么,路径 /home/abc - def.jar
将会被转化为:
1 | /home/abc/abc%20-%20def.jar |
1 | // /home/abc - def.jar |
想要转为正常路径:
1 | URLDecoder.decode("/home/abc/abc%20-%20def.jar", "UTF-8"); |
另外千万不要傻傻的自己来拼接 URL:
1 | new URL("file://" + filePath); |
在 UNIX
系统上可能还发现没问题,但是到 Windows
系统上直接 GG:
Extensions
installed extensions: 放在 lib/ext
目录下面的 jar
包
生成 jar
包:
1 | mkdir build |
编译:
1 | javac -classpath ./build/area.jar AreaApp.java |
运行:
1 | java -classpath .:./build/area.jar AreaApp |
Download Extensions: 每次都需要下载,略过
Understanding Extension Class Loading
class
寻找路径:
1 | Bootstrap classes ( rt.jar、i18n.jar ) |
JVM 的各提供商使用本地代码来实现 Bootstrap
类加载器。
类加载特性:
- 延迟加载
- 当一个类被加载时,它的所有父类都必须被加载
- 类缓存: 当一个类已经被请求,被类加载器加载之后,就会在 JVM 运行期间一直驻留在内存。当再次被请求时,就不需要再次加载,直接调用就可以了。
- 独立的命名空间:不同的类加载器有自己独立的命名空间。例如
Bootstrap
类加载器加载了com.test.ClassA
,System
类加载器加载了com.test.ClassB
, 那么这两个类将被当作不同包中的类,它们之间的私有成员不能互相访问。
自定义类加载器:
在 J2SE 中提供了两个默认的实现类, SecureClassLoader
和 URLClassLoader
。SecureClassLoader
是对 ClassLoader
类的包装, 它使用了 Java 安全模型机制。 URLClassLoader
是 SecureClassLoader
的子类, 用于定位磁盘和网络上的 Jar 和类文件。Extension
类加载器和 System
类加载器均继承自 URL
类加载器。
得到当前程序的所有 classpath
:
1 | System.getProperty("java.class.path") |
创建一个 Extensible 应用
- Extensible application: 你可以不用修改它原来的 code base,就能 extend 的应用,可理解为插件或者模块话
- Service: 一系列提供某些功能的接口
- Service provider interface (SPI): 一个 Service 所定义的开放接口或抽象类
- Service Provider: 实现了 SPI
SPI 的全名为 Service Provider Interface.大多数开发人员可能不熟悉,因为这个是针对厂商或者插件的。在 java.util.ServiceLoader
的文档里有比较详细的介绍。简单的总结下 java spi 机制的思想。我们系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块的方案,xml 解析模块、jdbc 模块的方案等。面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。 java spi 就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似 IOC 的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。
java spi 的具体约定为:当服务的提供者,提供了服务接口的一种实现之后,在 jar 包的 META-INF/services/
目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该 jar 包 META-INF/services/
里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。 基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。jdk 提供服务实现查找的一个工具类:java.util.ServiceLoader
用法示例:
1 | private static ServiceLoader<CodecSet> codecSetLoader |
注册:
把一个文件放在 META-INF/services
目录下,文件名: 类的全名,文件必须 UTF-8 编码,在开头可以使用 # 来注释
配置文件为什么要放在 META-INF/services
下面:
ServiceLoader
源码已经写死了
1 | private static final String PREFIX = "META-INF/services/"; |
ServiceLoader
读取实现类是什么时候实例化的:
ServiceLoader.LazyIterator.nextService
中实例化,即 load
的结果迭代时才会被实例化。
ServiceLoader
类: 帮助你查找、加载、使用 service providers,他会搜索你的 class path 来查找 service provider
,ServiceLoader
被 final
声明,意味着你无法覆盖它的算法
1 | java -Djava.ext.dirs=../DictionaryServiceProvider/dist:../ExtendedDictionary/dist -cp dist/DictionaryDemo.jar dictionary.DictionaryDemo |
- java.ext.dirs: 指定从哪里加载 extension mechanism 所需要的类,通常用来给 JRE 或其他库添加功能