Java基础20-从IDE的实现原理聊起,谈谈那些年我们用过的Java命令(一)

简介: Java基础20-从IDE的实现原理聊起,谈谈那些年我们用过的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本质)

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复制代码

-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


Java基础20-从IDE的实现原理聊起,谈谈那些年我们用过的Java命令(二):https://developer.aliyun.com/article/1535740

目录
相关文章
|
1月前
|
存储 算法 Java
滚雪球学Java(65):深入理解Java中的Map接口:实现原理剖析
【6月更文挑战第19天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
20 3
滚雪球学Java(65):深入理解Java中的Map接口:实现原理剖析
|
1月前
|
存储 缓存 算法
滚雪球学Java(62):HashSet的底层实现原理解析
【6月更文挑战第16天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
19 3
滚雪球学Java(62):HashSet的底层实现原理解析
|
24天前
|
安全 Java 编译器
杭州 【Java基础知识 11】java泛型方法的定义和使用(学习+改进+自己理解,想法) (借鉴-侵-删)
杭州 【Java基础知识 11】java泛型方法的定义和使用(学习+改进+自己理解,想法) (借鉴-侵-删)
16 1
|
27天前
|
Java Linux
Java执行Linux命令
Java执行Linux命令
29 2
|
1月前
|
存储 设计模式 Java
java实习生面试题_java基础面试_java面试题2018及答案_java面试题库
java实习生面试题_java基础面试_java面试题2018及答案_java面试题库
|
1月前
|
SQL 算法 安全
java面试宝典_java基础面试_2018java面试题_2019java最新面试题
java面试宝典_java基础面试_2018java面试题_2019java最新面试题
|
17天前
|
存储 Java
Redis08命令-Hash类型,也叫散列,其中value是一个无序字典,类似于java的HashMap结构,Hash结构可以将对象中的每个字段独立存储,可以针对每字段做CRUD
Redis08命令-Hash类型,也叫散列,其中value是一个无序字典,类似于java的HashMap结构,Hash结构可以将对象中的每个字段独立存储,可以针对每字段做CRUD
|
20天前
|
Java
Java中多态的实现原理解析
Java中多态的实现原理解析
|
23天前
|
存储 安全 Java
Java基础系列1:Java面向对象
Java基础系列1:Java面向对象
|
25天前
|
存储 Java 调度
深入探索Java并发编程:ConcurrentSkipListSet的高效使用与实现原理
深入探索Java并发编程:ConcurrentSkipListSet的高效使用与实现原理