夯實Java基礎系列20:從IDE的實現原理聊起,談談那些年咱們用過的Java命令

本系列文章將整理到我在GitHub上的《Java面試指南》倉庫,更多精彩內容請到個人倉庫裏查看html

https://github.com/h2pl/Java-Tutorialjava

喜歡的話麻煩點下Star哈git

文章首發於個人我的博客:程序員

www.how2playlife.comgithub

聊聊IDE的實現原理

IDE是把雙刃劍,它能夠什麼都幫你作了,你只要敲幾行代碼,點幾下鼠標,程序就跑起來了,用起來至關方便。web

你不用去關心它後面作了些什麼,執行了哪些命令,基於什麼原理。然而也是這種過度的依賴每每讓人散失了最基本的技能,當到了一個沒有IDE的地方,你便以爲無從下手,給你個代碼都不知道怎麼去跑。比如給你瓶水,你不知道怎麼打開去喝,而後活活給渴死。面試

以前用慣了idea,Java文件編譯運行的命令基本忘得一乾二淨。算法

那好,不如我們先來了解一下IDE的實現原理,這樣一來,即便離開IDE,咱們仍是知道如何運行Java程序了。shell

像Eclipse等java IDE是怎麼編譯和查找java源代碼的呢?apache

源代碼保存

這個無需多說,在編譯器寫入代碼,並保存到文件。這個利用流來實現。

編譯爲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/ 是資源目錄, 客戶端不能直接訪問。

二、WEB-INF/classes目錄存放src目錄java文件編譯以後的class文件,xml、properties等資源配置文件,這是一個定位資源的入口。

三、引用classpath路徑下的文件,只需在文件名前加classpath:

classpath:applicationContext-*.xml

classpath:context/conf/controller.xml

四、lib和classes同屬classpath,二者的訪問優先級爲: lib>classes。

五、classpath 和 classpath* 區別:

classpath:只會到你的class路徑中查找找文件;

classpath*:不只包含class路徑,還包括jar文件中(class路徑)進行查找。

總結:

(1).什麼時候須要使用-classpath:當你要編譯或執行的類引用了其它的類,但被引用類的.class文件不在當前目錄下時,就須要經過-classpath來引入類

(2).什麼時候須要指定路徑:當你要編譯的類所在的目錄和你執行javac命令的目錄不是同一個目錄時,就須要指定源文件的路徑(CLASSPATH是用來指定.class路徑的,不是用來指定.java文件的路徑的)

Java項目和Java web項目的本質區別

(看清IDE及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目錄的命名有何關係呢?只要讓服務器找到不就好了。

-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平臺的類):Javajdk1.7.0_25jrelibrt.jar等,用-bootclasspath設置。

•擴展類:Javajdk1.7.0_25jrelibext目錄中的文件,用-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和 buildsrc/com/yp/test/HelloWorld.javabuild/

├─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:codeplacen_learnjavajavacmd> java com/yp/test/HelloWorld.class

錯誤: 找不到或沒法加載主類 build.com.yp.test.HelloWorld.class

運行時要指定main

E:codeplacen_learnjavajavacmdbuild> java com.yp.test.HelloWorld

helloWorld

若是引用到多個其餘的類,應該怎麼作呢 ?

編譯

E:codeplacen_learnjavajavacmd>javac src/com/yp/test/HelloWorld.java -sourcepath src -d build -g

1

-sourcepath 表示 從指定的源文件目錄中找到須要的.java文件並進行編譯。

也能夠用-cp指定編譯好的class的路徑

運行,注意:運行在build目錄下

E:codeplacen_learnjavajavacmdbuild>java com.yp.test.HelloWorld

怎麼打成jar包?

生成:

E:codeplacen_learnjavajavacmdbuild>jar cvf h.jar *

運行:

E:codeplacen_learnjavajavacmdbuild>java h.jar

錯誤: 找不到或沒法加載主類 h.jar

這個錯誤是沒有指定main類,因此相似這樣來指定:

E:codeplacen_learnjavajavacmdbuild>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 $BINPATH/ -cp $jarfile @$SRCFILELISTPATH

在要編譯的文件不少時候,一個個敲命令會顯得很長,也不方便修改,

能夠把要編譯的源文件列在文件中,在文件名前加@,這樣就能夠對多個文件進行編譯,

以上就是吧java文件放到 $SRCFILELIST_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技術學習資源: 一些程序員學習技術的資源大禮包,關注公衆號後,後臺回覆關鍵字 「資料」 便可免費無套路獲取。

相關文章
相關標籤/搜索