本系列文章將整理到我在GitHub上的《Java面試指南》倉庫,更多精彩內容請到我的倉庫裏查看
https://github.com/h2pl/Java-Tutorial
喜歡的話麻煩點下Star、Fork、Watch三連哈,感謝你的支持。
文章首發於我的個人博客:
本文是微信公衆號【Java技術江湖】的《夯實Java基礎系列博文》其中一篇,本文部分內容來源於網絡,爲了把本文主題講得清晰透徹,也整合了很多我認爲不錯的技術博客內容,引用其中了一些比較好的博客文章,如有侵權,請聯繫作者。
該系列博文會告訴你如何從入門到進階,一步步地學習Java基礎知識,並上手進行實戰,接着瞭解每個Java知識點背後的實現原理,更完整地瞭解整個Java技術體系,形成自己的知識框架。爲了更好地總結和檢驗你的學習成果,本系列文章也會提供每個知識點對應的面試題以及參考答案。
文章目錄
如果對本系列文章有什麼建議,或者是有什麼疑問的話,也可以關注公衆號【Java技術江湖】聯繫作者,歡迎你參與本系列博文的創作和修訂。
聊聊IDE的實現原理
IDE是把雙刃劍,它可以什麼都幫你做了,你只要敲幾行代碼,點幾下鼠標,程序就跑起來了,用起來相當方便。
你不用去關心它後面做了些什麼,執行了哪些命令,基於什麼原理。然而也是這種過分的依賴往往讓人散失了最基本的技能,當到了一個沒有IDE的地方,你便覺得無從下手,給你個代碼都不知道怎麼去跑。好比給你瓶水,你不知道怎麼打開去喝,然後活活給渴死。
之前用慣了idea,Java文件編譯運行的命令基本忘得一乾二淨。
那好,不如咱們先來了解一下IDE的實現原理,這樣一來,即使離開IDE,我們還是知道如何運行Java程序了。
像Eclipse等java IDE是怎麼編譯和查找java源代碼的呢?
源代碼保存
這個無需多說,在編譯器寫入代碼,並保存到文件。這個利用流來實現。
編譯爲class文件
java提供了JavaCompiler,我們可以通過它來編譯java源文件爲class文件。
查找class
可以通過Class.forName(fullClassPath)或自定義類加載器來實現。
生成對象,並調用對象方法
通過上面一個查找class,得到Class對象後,可以通過newInstance()或構造器的newInstance()得到對象。然後得到Method,最後調用方法,傳入相關參數即可。
示例代碼:
public class MyIDE {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 定義java代碼,並保存到文件(Test.java)
StringBuilder sb = new StringBuilder();
sb.append("package com.tommy.core.test.reflect;\n");
sb.append("public class Test {\n");
sb.append(" private String name;\n");
sb.append(" public Test(String name){\n");
sb.append(" this.name = name;\n");
sb.append(" System.out.println(\"hello,my name is \" + name);\n");
sb.append(" }\n");
sb.append(" public String sayHello(String name) {\n");
sb.append(" return \"hello,\" + name;\n");
sb.append(" }\n");
sb.append("}\n");
System.out.println(sb.toString());
String baseOutputDir = "F:\\output\\classes\\";
String baseDir = baseOutputDir + "com\\tommy\\core\\test\\reflect\\";
String targetJavaOutputPath = baseDir + "Test.java";
// 保存爲java文件
FileWriter fileWriter = new FileWriter(targetJavaOutputPath);
fileWriter.write(sb.toString());
fileWriter.flush();
fileWriter.close();
// 編譯爲class文件
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager manager = compiler.getStandardFileManager(null,null,null);
List<File> files = new ArrayList<>();
files.add(new File(targetJavaOutputPath));
Iterable compilationUnits = manager.getJavaFileObjectsFromFiles(files);
// 編譯
// 設置編譯選項,配置class文件輸出路徑
Iterable<String> options = Arrays.asList("-d",baseOutputDir);
JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, options, null, compilationUnits);
// 執行編譯任務
task.call();
// 通過反射得到對象
// Class clazz = Class.forName(“com.tommy.core.test.reflect.Test”);
// 使用自定義的類加載器加載class
Class clazz = new MyClassLoader(baseOutputDir).loadClass(“com.tommy.core.test.reflect.Test”);
// 得到構造器
Constructor constructor = clazz.getConstructor(String.class);
// 通過構造器new一個對象
Object test = constructor.newInstance(“jack.tsing”);
// 得到sayHello方法
Method method = clazz.getMethod(“sayHello”, String.class);
// 調用sayHello方法
String result = (String) method.invoke(test, “jack.ma”);
System.out.println(result);
}
}
自定義類加載器代碼:
public class MyClassLoader extends ClassLoader {
private String baseDir;
public MyClassLoader(String baseDir) {
this.baseDir = baseDir;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String fullClassFilePath = this.baseDir + name.replace("\.","/") + “.class”;
File classFilePath = new File(fullClassFilePath);
if (classFilePath.exists()) {
FileInputStream fileInputStream = null;
ByteArrayOutputStream byteArrayOutputStream = null;
try {
fileInputStream = new FileInputStream(classFilePath);
byte[] data = new byte[1024];
int len = -1;
byteArrayOutputStream = new ByteArrayOutputStream();
while ((len = fileInputStream.read(data)) != -1) {
byteArrayOutputStream.write(data,0,len);
}
return defineClass(name,byteArrayOutputStream.toByteArray(),0,byteArrayOutputStream.size());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != fileInputStream) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != byteArrayOutputStream) {
try {
byteArrayOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
return super.findClass(name);
}
}
javac命令初窺
注:以下紅色標記的參數在下文中有所講解。
本部分參考https://www.cnblogs.com/xiazdong/p/3216220.html
用法: javac
其中, 可能的選項包括:
-g 生成所有調試信息
-g:none 不生成任何調試信息
-g:{lines,vars,source} 只生成某些調試信息
-nowarn 不生成任何警告
-verbose 輸出有關編譯器正在執行的操作的消息
-deprecation 輸出使用已過時的 API 的源位置
-classpath <路徑> 指定查找用戶類文件和註釋處理程序的位置
-cp <路徑> 指定查找用戶類文件和註釋處理程序的位置
-sourcepath <路徑> 指定查找輸入源文件的位置
-bootclasspath <路徑> 覆蓋引導類文件的位置
-extdirs <目錄> 覆蓋所安裝擴展的位置
-endorseddirs <目錄> 覆蓋簽名的標準路徑的位置
-proc:{none,only} 控制是否執行註釋處理和/或編譯。
-processor [,,…] 要運行的註釋處理程序的名稱; 繞過默認的搜索進程
-processorpath <路徑> 指定查找註釋處理程序的位置
-d <目錄> 指定放置生成的類文件的位置
-s <目錄> 指定放置生成的源文件的位置
-implicit:{none,class} 指定是否爲隱式引用文件生成類文件
-encoding <編碼> 指定源文件使用的字符編碼
-source <發行版> 提供與指定發行版的源兼容性
-target <發行版> 生成特定 VM 版本的類文件
-version 版本信息
-help 輸出標準選項的提要
-A關鍵字[=值] 傳遞給註釋處理程序的選項
-X 輸出非標準選項的提要
-J<標記> 直接將 <標記> 傳遞給運行時系統
-Werror 出現警告時終止編譯
@<文件名> 從文件讀取選項和文件名
在詳細介紹javac命令之前,先看看這個classpath是什麼
classpath是什麼
在dos下編譯java程序,就要用到classpath這個概念,尤其是在沒有設置環境變量的時候。classpath就是存放.class等編譯後文件的路徑。
javac:如果當前你要編譯的java文件中引用了其它的類(比如說:繼承),但該引用類的.class文件不在當前目錄下,這種情況下就需要在javac命令後面加上-classpath參數,通過使用以下三種類型的方法 來指導編譯器在編譯的時候去指定的路徑下查找引用類。
(1).絕對路徑:javac -classpath c:/junit3.8.1/junit.jar Xxx.java
(2).相對路徑:javac -classpath …/junit3.8.1/Junit.javr Xxx.java
(3).系統變量:javac -classpath %CLASSPATH% Xxx.java (注意:%CLASSPATH%表示使用系統變量CLASSPATH的值進行查找,這裏假設Junit.jar的路徑就包含在CLASSPATH系統變量中)
IDE中的classpath
對於一個普通的Javaweb項目,一般有這樣的配置:
1 WEB-INF/classes,lib纔是classpath,WEB-INF/ 是資源目錄, 客戶端不能直接訪問。
2、WEB-INF/classes目錄存放src目錄java文件編譯之後的class文件,xml、properties等資源配置文件,這是一個定位資源的入口。
3、引用classpath路徑下的文件,只需在文件名前加classpath:
classpath:applicationContext-*.xml
classpath:context/conf/controller.xml
4、lib和classes同屬classpath,兩者的訪問優先級爲: lib>classes。
5、classpath 和 classpath* 區別:
classpath:只會到你的class路徑中查找找文件;
classpath*:不僅包含class路徑,還包括jar文件中(class路徑)進行查找。
總結:
(1).何時需要使用-classpath:當你要編譯或執行的類引用了其它的類,但被引用類的.class文件不在當前目錄下時,就需要通過-classpath來引入類
(2).何時需要指定路徑:當你要編譯的類所在的目錄和你執行javac命令的目錄不是同一個目錄時,就需要指定源文件的路徑(CLASSPATH是用來指定.class路徑的,不是用來指定.java文件的路徑的)
Java項目和Java web項目的本質區別
(看清IDE及classpath本質)
現在只是說說Java Project和Web Project,那麼二者有區別麼?回答:沒有!都是Java語言的應用,只是應用場合不同罷了,那麼他們的本質到底是什麼?
回答:編譯後路徑!虛擬機執行的是class文件而不是java文件,那麼我們不管是何種項目都是寫的java文件,怎麼就不一樣了呢?分成java和web兩種了呢?
從.classpath文件入手來看,這個文件在每個項目目錄下都是存在的,很少有人打開看吧,那麼我們就來一起看吧。這是一個XML文件,使用文本編輯器打開即可。
這裏展示一個web項目的.classpath
Xml代碼
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="resources"/>
<classpathentry kind="src" path="test"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="lib" path="lib/servlet-api.jar"/>
<classpathentry kind="lib" path="webapp/WEB-INF/lib/struts2-core-2.1.8.1.jar"/>
……
<classpathentry kind="output" path="webapp/WEB-INF/classes"/>
</classpath>
XML文檔包含一個根元素,就是classpath,類路徑,那麼這裏麪包含了什麼信息呢?子元素是classpathentry,kind屬性區別了種 類信息,src源碼,con你看看後面的path就知道是JRE容器的信息。lib是項目依賴的第三方類庫,output是src編譯後的位置。
既然是web項目,那麼就是WEB-INF/classes目錄,可能用MyEclipse的同學會說他們那裏是WebRoot或者是WebContext而不是webapp,有區別麼?回答:完全沒有!
既然看到了編譯路徑的本來面目後,還區分什麼java項目和web項目麼?回答:不區分!普通的java 項目你這樣寫就行了:,看看Eclipse是不是這樣生成的?這個問題解決了吧。
再說說webapp目錄命名的問題,這個無所謂啊,web項目是要發佈到服務器上的對吧,那麼服務器讀取的是類文件和頁面文件吧,它不管源文件,它也無法去理解源文件。那麼webapp目錄的命名有何關係呢?只要讓服務器找到不就行了。
javac命令後綴
-g、-g:none、-g:{lines,vars,source}
•-g:在生成的class文件中包含所有調試信息(行號、變量、源文件)
•-g:none :在生成的class文件中不包含任何調試信息。這個參數在javac編譯中是看不到什麼作用的,因爲調試信息都在class文件中,而我們看不懂這個class文件。
爲了看出這個參數的作用,我們在eclipse中進行實驗。在eclipse中,我們經常做的事就是“debug”,而在debug的時候,我們會
•加入“斷點”,這個是靠-g:lines起作用,如果不記錄行號,則不能加斷點。
•在“variables”窗口中查看當前的變量,如下圖所示,這是靠-g:vars起作用,否則不能查看變量信息。
•在多個文件之間來回調用,比如 A.java的main()方法中調用了B.java的fun()函數,而我想看看程序進入fun()後的狀態,這是靠-g:source,如果沒有這個參數,則不能查看B.java的源代碼。
-bootclasspath、-extdirs
-bootclasspath和-extdirs 幾乎不需要用的,因爲他是用來改變 “引導類”和“擴展類”。
•引導類(組成Java平臺的類):Java\jdk1.7.0_25\jre\lib\rt.jar等,用-bootclasspath設置。
•擴展類:Java\jdk1.7.0_25\jre\lib\ext目錄中的文件,用-extdirs設置。
•用戶自定義類:用-classpath設置。我們用-verbose編譯後出現的“類文件的搜索路徑”,就是由上面三個路徑組成,如下:
[類文件的搜索路徑: C:\Java\jdk1.7.0_25\jre\lib\resources.jar,C:\Java\jdk1.7.0_25
\jre\lib\rt.jar,C:\Java\jdk1.7.0_25\jre\lib\sunrsasign.jar,C:\Java\jdk1.7.0_25\j
re\lib\jsse.jar,C:\Java\jdk1.7.0_25\jre\lib\jce.jar,C:\Java\jdk1.7.0_25\jre\lib\
charsets.jar,C:\Java\jdk1.7.0_25\jre\lib\jfr.jar,C:\Java\jdk1.7.0_25\jre\classes
,C:\Java\jdk1.7.0_25\jre\lib\ext\access-bridge-32.jar,C:\Java\jdk1.7.0_25\jre\li
b\ext\dnsns.jar,C:\Java\jdk1.7.0_25\jre\lib\ext\jaccess.jar,C:\Java\jdk1.7.0_25\
jre\lib\ext\localedata.jar,C:\Java\jdk1.7.0_25\jre\lib\ext\sunec.jar,C:\Java\jdk
1.7.0_25\jre\lib\ext\sunjce_provider.jar,C:\Java\jdk1.7.0_25\jre\lib\ext\sunmsca
pi.jar,C:\Java\jdk1.7.0_25\jre\lib\ext\sunpkcs11.jar,C:\Java\jdk1.7.0_25\jre\lib
\ext\zipfs.jar,..\bin]
如果利用 -bootclasspath 重新定義: javac -bootclasspath src Xxx.java,則會出現下面錯誤:
致命錯誤: 在類路徑或引導類路徑中找不到程序包 java.lang
-sourcepath和-classpath(-cp)
•-classpath(-cp)指定你依賴的類的class文件的查找位置。在Linux中,用“:”分隔classpath,而在windows中,用“;”分隔。
•-sourcepath指定你依賴的類的java文件的查找位置。
舉個例子,
public class A
{
public static void main(String[] args) {
B b = new B();
b.print();
}
}
public class B
{
public void print()
{
System.out.println(“old”);
}
}
目錄結構如下:
sourcepath //此處爲當前目錄
|-src
|-com
|- B.java
|- A.java
|-bin
|- B.class //是 B.java
編譯後的類文件
如果要編譯 A.java,則必須要讓編譯器找到類B的位置,你可以指定B.class的位置,也可以是B.java的位置,也可以同時都存在。
javac -classpath bin src/A.java //查找到B.class
javac -sourcepath src/com src/A.java //查找到B.java
javac -sourcepath src/com -classpath bin src/A.java //同時查找到B.class和B.java
如果同時找到了B.class和B.java,則:
•如果B.class和B.java內容一致,則遵循B.class。
•如果B.class和B.java內容不一致,則遵循B.java,並編譯B.java。
以上規則可以通過 -verbose選項看出。
-d
•d就是 destination,用於指定.class文件的生成目錄,在eclipse中,源文件都在src中,編譯的class文件都是在bin目錄中。
這裏我用來實現一下這個功能,假設項目名稱爲project,此目錄爲當前目錄,且在src/com目錄中有一個Main.java文件。‘
package com;
public class Main
{
public static void main(String[] args) {
System.out.println(“Hello”);
}
}
javac -d bin src/com/Main.java
上面的語句將Main.class生成在bin/com目錄下。
-implicit:{none,class}
•如果有文件爲A.java(其中有類A),且在類A中使用了類B,類B在B.java中,則編譯A.java時,默認會自動編譯B.java,且生成B.class。
•implicit:none:不自動生成隱式引用的類文件。
•implicit:class(默認):自動生成隱式引用的類文件。
public class A
{
public static void main(String[] args) {
B b = new B();
}
}
public class B
{
}
如果使用:
javac -implicit:none A.java
則不會生成 B.class。
-source和-target
•-source:使用指定版本的JDK編譯,比如:-source 1.4表示用JDK1.4的標準編譯,如果在源文件中使用了泛型,則用JDK1.4是不能編譯通過的。
•-target:指定生成的class文件要運行在哪個JVM版本,以後實際運行的JVM版本必須要高於這個指定的版本。
javac -source 1.4 Xxx.java
javac -target 1.4 Xxx.java
-encoding
默認會使用系統環境的編碼,比如我們一般用的中文windows就是GBK編碼,所以直接javac時會用GBK編碼,而Java文件一般要使用utf-8,如果用GBK就會出現亂碼。
•指定源文件的編碼格式,如果源文件是UTF-8編碼的,而-encoding GBK,則源文件就變成了亂碼(特別是有中文時)。
javac -encoding UTF-8 Xxx.java
-verbose
輸出詳細的編譯信息,包括:classpath、加載的類文件信息。
比如,我寫了一個最簡單的HelloWorld程序,在命令行中輸入:
D:\Java>javac -verbose -encoding UTF-8 HelloWorld01.java
輸出:
[語法分析開始時間 RegularFileObject[HelloWorld01.java]]
[語法分析已完成, 用時 21 毫秒]
[源文件的搜索路徑: .,D:\大三下\編譯原理\cup\java-cup-11a.jar,E:\java\jflex\lib\J //-sourcepath
Flex.jar]
[類文件的搜索路徑: C:\Java\jdk1.7.0_25\jre\lib\resources.jar,C:\Java\jdk1.7.0_25 //-classpath、-bootclasspath、-extdirs
省略............................................
[正在加載ZipFileIndexFileObject[C:\Java\jdk1.7.0_25\lib\ct.sym(META-INF/sym/rt.j
ar/java/lang/Object.class)]]
[正在加載ZipFileIndexFileObject[C:\Java\jdk1.7.0_25\lib\ct.sym(META-INF/sym/rt.j
ar/java/lang/String.class)]]
[正在檢查Demo]
省略............................................
[已寫入RegularFileObject[Demo.class]]
[共 447 毫秒]
編寫一個程序時,比如寫了一句:System.out.println(“hello”),實際上還需要加載:Object、PrintStream、String等類文件,而上面就顯示了加載的全部類文件。
其他命令
-J <標記>
•傳遞一些信息給 Java Launcher.
javac -J-Xms48m Xxx.java //set the startup memory to 48M.
-@<文件名>
如果同時需要編譯數量較多的源文件(比如1000個),一個一個編譯是不現實的(當然你可以直接 javac *.java ),比較好的方法是:將你想要編譯的源文件名都寫在一個文件中(比如sourcefiles.txt),其中每行寫一個文件名,如下所示:
HelloWorld01.java
HelloWorld02.java
HelloWorld03.java
則使用下面的命令:
javac @sourcefiles.txt
編譯這三個源文件。
使用javac構建項目
這部分參考:
https://blog.csdn.net/mingover/article/details/57083176
一個簡單的javac編譯
新建兩個文件夾,src和 build
src/com/yp/test/HelloWorld.java
build/
├─build
└─src
└─com
└─yp
└─test
HelloWorld.java
java文件非常簡單
package com.yp.test;
public class HelloWorld {
public static void main(String[] args) {
System.out.println("helloWorld");
}
}
編譯:
javac src/com/yp/test/HelloWorld.java -d build
-d 表示編譯到 build文件夾下
查看build文件夾
├─build
│ └─com
│ └─yp
│ └─test
│ HelloWorld.class
│
└─src
└─com
└─yp
└─test
HelloWorld.java
運行文件
E:\codeplace\n_learn\java\javacmd> java com/yp/test/HelloWorld.class
錯誤: 找不到或無法加載主類 build.com.yp.test.HelloWorld.class運行時要指定main
E:\codeplace\n_learn\java\javacmd\build> java com.yp.test.HelloWorld
helloWorld
如果引用到多個其他的類,應該怎麼做呢 ?
編譯
E:\codeplace\n_learn\java\javacmd>javac src/com/yp/test/HelloWorld.java -sourcepath src -d build -g
1
-sourcepath 表示 從指定的源文件目錄中找到需要的.java文件並進行編譯。
也可以用-cp指定編譯好的class的路徑
運行,注意:運行在build目錄下E:\codeplace\n_learn\java\javacmd\build>java com.yp.test.HelloWorld
怎麼打成jar包?
生成:
E:\codeplace\n_learn\java\javacmd\build>jar cvf h.jar *
運行:
E:\codeplace\n_learn\java\javacmd\build>java h.jar
錯誤: 找不到或無法加載主類 h.jar
這個錯誤是沒有指定main類,所以類似這樣來指定:
E:\codeplace\n_learn\java\javacmd\build>java -cp h.jar com.yp.test.HelloWorld
生成可以運行的jar包
需要指定jar包的應用程序入口點,用-e選項:
E:\codeplace\n_learn\java\javacmd\build> jar cvfe h.jar com.yp.test.HelloWorld *
已添加清單
正在添加: com/(輸入 = 0) (輸出 = 0)(存儲了 0%)
正在添加: com/yp/(輸入 = 0) (輸出 = 0)(存儲了 0%)
正在添加: com/yp/test/(輸入 = 0) (輸出 = 0)(存儲了 0%)
正在添加: com/yp/test/entity/(輸入 = 0) (輸出 = 0)(存儲了 0%)
正在添加: com/yp/test/entity/Cat.class(輸入 = 545) (輸出 = 319)(壓縮了 41%)
正在添加: com/yp/test/HelloWorld.class(輸入 = 844) (輸出 = 487)(壓縮了 42%)
直接運行
java -jar h.jar
額外發現
指定了Main類後,jar包裏面的 META-INF/MANIFEST.MF 是這樣的, 比原來多了一行Main-Class….
Manifest-Version: 1.0
Created-By: 1.8.0 (Oracle Corporation)
Main-Class: com.yp.test.HelloWorld
如果類裏有引用jar包呢?
先下一個jar包 這裏直接下 log4j
* main函數改成
import com.yp.test.entity.Cat;
import org.apache.log4j.Logger;
public class HelloWorld {
static Logger log = Logger.getLogger(HelloWorld.class);
public static void main(String[] args) {
Cat c = new Cat("keyboard");
log.info("這是log4j");
System.out.println("hello," + c.getName());
}
}
現的文件是這樣的
├─build
├─lib
│ log4j-1.2.17.jar
│
└─src
└─com
└─yp
└─test
│ HelloWorld.java
│
└─entity
Cat.java
這個時候 javac命令要接上 -cp ./lib/*.jar
E:\codeplace\n_learn\java\javacmd>javac -encoding "utf8" src/com/yp/test/HelloWorld.java -sourcepath src -d build -g -cp ./lib/*.jar
運行要加上-cp, -cp 選項貌似會把工作目錄給換了, 所以要加上 ;../build
E:\codeplace\n_learn\java\javacmd\build>java -cp ../lib/log4j-1.2.17.jar;../build com.yp.test.HelloWorld
結果:
log4j:WARN No appenders could be found for logger(com.yp.test.HelloWorld).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
hello,keyboard
由於沒有 log4j的配置文件,所以提示上面的問題,往 build 裏面加上 log4j.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'>
<appender name="stdout" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{ABSOLUTE} %-5p [%c{1}] %m%n" />
</layout>
</appender>
<root>
<level value="info" />
<appender-ref ref="stdout" />
</root>
</log4j:configuration>
再運行
E:\codeplace\n_learn\java\javacmd>java -cp lib/log4j-1.2.17.jar;build com.yp.tes t.HelloWorld
15:19:57,359 INFO [HelloWorld] 這是log4j
hello,keyboard
說明:
這個log4j配置文件,習慣的做法是放在src目錄下, 在編譯過程中 copy到build中的,但根據ant的做法,不是用javac的,而是用來處理,我猜測javac是不能copy的,如果想在命令行直接 使用,應該是用cp命令主動去執行 copy操作
ok 一個簡單的java 工程就運行完了
但是 貌似有些繁瑣, 需要手動鍵入 java文件 以及相應的jar包 很是麻煩,
so 可以用 shell 來腳本來簡化相關操作
shell 文件整理如下:
#!/bin/bash
echo "build start"
JAR_PATH=libs
BIN_PATH=bin
SRC_PATH=src
# java文件列表目錄
SRC_FILE_LIST_PATH=src/sources.list
#生所有的java文件列表 放入列表文件中
rm -f $SRC_PATH/sources
find $SRC_PATH/ -name *.java > $SRC_FILE_LIST_PATH
#刪除舊的編譯文件 生成bin目錄
rm -rf $BIN_PATH/
mkdir $BIN_PATH/
#生成依賴jar包 列表
for file in ${JAR_PATH}/*.jar;
do
jarfile=${jarfile}:${file}
done
echo "jarfile = "$jarfile
#編譯 通過-cp指定所有的引用jar包,將src下的所有java文件進行編譯
javac -d $BIN_PATH/ -cp $jarfile @$SRC_FILE_LIST_PATH
#運行 通過-cp指定所有的引用jar包,指定入口函數運行
java -cp $BIN_PATH$jarfile com.zuiapps.danmaku.server.Main
有一點需要注意的是, javac -d $BIN_PATH/ -cp SRC_FILE_LIST_PATH
在要編譯的文件很多時候,一個個敲命令會顯得很長,也不方便修改,
可以把要編譯的源文件列在文件中,在文件名前加@,這樣就可以對多個文件進行編譯,
以上就是吧java文件放到 $SRC_FILE_LIST_PATH 中去了
編譯 :
1. 需要編譯所有的java文件
2. 依賴的java 包都需要加入到 classpath 中去
3. 最後設置 編譯後的 class 文件存放目錄 即 -d bin/
4. java文件過多是可以使用 @$SRC_FILE_LIST_PATH 把他們放到一個文件中去
運行:
1.需要吧 編譯時設置的bin目錄和 所有jar包加入到 classpath 中去
javap 的使用
javap是jdk自帶的一個工具,可以對代碼反編譯,也可以查看java編譯器生成的字節碼。
情況下,很少有人使用javap對class文件進行反編譯,因爲有很多成熟的反編譯工具可以使用,比如jad。但是,javap還可以查看java編譯器爲我們生成的字節碼。通過它,可以對照源代碼和字節碼,從而瞭解很多編譯器內部的工作。
javap命令分解一個class文件,它根據options來決定到底輸出什麼。如果沒有使用options,那麼javap將會輸出包,類裏的protected和public域以及類裏的所有方法。javap將會把它們輸出在標準輸出上。來看這個例子,先編譯(javac)下面這個類。
import java.awt.*;
import java.applet.*;
public class DocFooter extends Applet {
String date;
String email;
public void init() {
resize(500,100);
date = getParameter("LAST_UPDATED");
email = getParameter("EMAIL");
}
}
在命令行上鍵入javap DocFooter後,輸出結果如下
Compiled from “DocFooter.java”
public class DocFooter extends java.applet.Applet {
java.lang.String date;
java.lang.String email;
public DocFooter();
public void init();
}
如果加入了-c,即javap -c DocFooter,那麼輸出結果如下
Compiled from “DocFooter.java”
public class DocFooter extends java.applet.Applet {
java.lang.String date;
java.lang.String email;
public DocFooter();
Code:
0: aload_0
1: invokespecial #1 // Method java/applet/Applet."<init>":()V
4: return
public void init();
Code:
0: aload_0
1: sipush 500
4: bipush 100
6: invokevirtual #2 // Method resize:(II)V
9: aload_0
10: aload_0
11: ldc #3 // String LAST_UPDATED
13: invokevirtual #4 // Method getParameter:(Ljava/lang/String;)Ljava/lang/String;
16: putfield #5 // Field date:Ljava/lang/String;
19: aload_0
20: aload_0
21: ldc #6 // String EMAIL
23: invokevirtual #4 // Method getParameter:(Ljava/lang/String;)Ljava/lang/String;
26: putfield #7 // Field email:Ljava/lang/String;
29: return
}
上面輸出的內容就是字節碼。
用法摘要
-help 幫助
-l 輸出行和變量的表
-public 只輸出public方法和域
-protected 只輸出public和protected類和成員
-package 只輸出包,public和protected類和成員,這是默認的
-p -private 輸出所有類和成員
-s 輸出內部類型簽名
-c 輸出分解後的代碼,例如,類中每一個方法內,包含java字節碼的指令,
-verbose 輸出棧大小,方法參數的個數
-constants 輸出靜態final常量
總結
javap可以用於反編譯和查看編譯器編譯後的字節碼。平時一般用javap -c比較多,該命令用於列出每個方法所執行的JVM指令,並顯示每個方法的字節碼的實際作用。可以通過字節碼和源代碼的對比,深入分析java的編譯原理,瞭解和解決各種Java原理級別的問題。
參考文章
https://blog.csdn.net/Anbernet/article/details/81449390
https://www.cnblogs.com/luobiao320/p/7975442.html
https://www.jianshu.com/p/f7330dbdc051
https://www.jianshu.com/p/6a8997560b05
https://blog.csdn.net/w372426096/article/details/81664431
https://blog.csdn.net/qincidong/article/details/82492140
微信公衆號
Java技術江湖
如果大家想要實時關注我更新的文章以及分享的乾貨的話,可以關注我的公衆號【Java技術江湖】一位阿里 Java 工程師的技術小站,作者黃小斜,專注 Java 相關技術:SSM、SpringBoot、MySQL、分佈式、中間件、集羣、Linux、網絡、多線程,偶爾講點Docker、ELK,同時也分享技術乾貨和學習經驗,致力於Java全棧開發!
Java工程師必備學習資源: 一些Java工程師常用學習資源,關注公衆號後,後臺回覆關鍵字 “Java” 即可免費無套路獲取。
個人公衆號:黃小斜
作者是 985 碩士,螞蟻金服 JAVA 工程師,專注於 JAVA 後端技術棧:SpringBoot、MySQL、分佈式、中間件、微服務,同時也懂點投資理財,偶爾講點算法和計算機理論基礎,堅持學習和寫作,相信終身學習的力量!
程序員3T技術學習資源: 一些程序員學習技術的資源大禮包,關注公衆號後,後臺回覆關鍵字 “資料” 即可免費無套路獲取。