This article is participating in “Java Theme Month – Java Development Practice”, for details: juejin.cn/post/696826…

This is the third day of my participation in Gwen Challenge

Daily sentence

Different life, have different happiness. Find the luck you have, complain less about the injustice of god, grasp their own happiness. You, me, all of us can have a happy life.

background

I often get confused about how a JAR package is made, including how Maven packages are packaged, how SpringBoot jars work, and how jar packages will not work at all if they are slightly wrong.

Compressed jar package

As we all know, Java application projects in accordance with the JSR standard protocol, generally using JAR packages to run, Java programs can be packaged into a JAR. In addition, of course, you must specify a Main Class with a Main method as the entry point to your Java program, which is also the entry point to start the JVM process

Make a JAR package that contains only bytecode files

Let’s first look at how to make a JAR that contains only bytecode files, i.e. only class files, which is the simplest form

The simplest JAR package – just print Hello

The resulting JAR package structure

META-INF
Hello.class
Copy the code
steps
  1. Use Notepad to write a hello.java file
class Hello{
     public static void main(String[] agrs){
         System.out.println("hello"); }}Copy the code
  1. Go to the directory using the command line and compile the file
javac Hello.java 
Copy the code
  1. Jar the compiled hello. class file
jar -cvf hello.jar Hello.class 
Copy the code
  • C (create) : to create a new JAR package,
  • V (View) : displays information about the creation process on the console during creation.
  • F (file) indicates the name of the generated JAR package
  1. Run the jar package
java -jar hello.jar
Copy the code

Jar does not have the main list attribute.

  1. Add the main-class attribute
  • If you open hello.jar with a zip file, you’ll find a meta-INF folder containing a manifest.mf file. Open it with Notepad.

    • Write main-class: Hello on the third line (note that there is a space after the colon and a blank line at the end of the file) and save
  • Run Java -jar hello.jar again and see Hello on the console successfully

Jar package with two classes — output Hello via the call

The resulting JAR package structure

META-INF
Tom.class
Hello.class
Copy the code

steps

  1. Use Notepad to write a hello. Java and a Tom. Java file to have Hello call Tom’s speak method
class Hello{ public static void main(String[] agrs){ Tom.speak(); } } class Tom{ public static void speak(){ System.out.println("hello"); }}Copy the code
  1. Compile: javac hello.java

Both Hello. Java and Tom. Java are compiled at the same time, because Tom is called in Hello

  1. Jar package, this time let’s change the way to define main-class directly.
Manifest-Version: 1.0
Created-By: 1.8.0_121 (Oracle Corporation)
Main-Class: Hello
Copy the code

Prepare the manifest. MF file and save it in the meta-INF folder. In this case, run the following command to open the JAR package

jar -cvfm hello.jar META-INF\MANIFEST.MF Hello.class Tom.class 
Copy the code
  • This command uses the first file as the manifest.mf file, hello.jar as the name, and jars hello. class and Tom.class.

  • There is an extra parameter, m, to define the MANIFEST file

  1. Run Java -jar hello.jar and see Hello on the console successfully
Jar package with directory structure – output Hello by quoting package and calling

The resulting JAR package structure

Meta-inf com Tom. Class Hello. ClassCopy the code

We will change the last one slightly, put the Class Tom under the COM package, the source file directory structure becomes

com
  Tom.java
Hello.java
Copy the code

Also, Tom.java needs to declare its own package name in the first line

package com;

Hello. Java needs to import the Tom class, again with import on the first line

import com.Tom;

steps
  1. Hello. Java

  2. Open the JAR package and also prepare the MANIFEST file

 jar -cvfm hello.jar META-INF\MANIFEST.MF Hello.class com
Copy the code

Note that the last com indicates that all files in the com folder are entered into the JAR package

  1. Run Java -jar hello.jar and see Hello on the console successfully

  2. The optimization process

The com package contains the tom.java source file, which is also inserted into the JAR package. This is not good. Can we optimize the javac command so that all compiled files are compiled in a separate place?

When compiling Hello.java, create a new target folder. Then we use the following command:

javac Hello.java -d target
Copy the code
  • Put all compiled files in the target folder.

  • Copy the meta-INF folder to the target directory and enter the following command

jar -cvfm hello.jar META-INF\MENIFEST.MF *
Copy the code

Note that the last position is changed to *, indicating that all files in the current directory are placed in the JAR package

At this point, we can conclude that to make a JAR package containing only class bytecode files, the following command is sufficient

Javac file to compile -d target location

Jar-cvfm name the MANIFEST file Class file 1 Class file 2

Make jar packages containing JAR files

Let’s make the scenario a little more complicated by looking at the scenarios where other JARS need to be introduced into the JAR package

  1. Two JAR packages call each other — calling the jar outside the jar prints Hello

The resulting JAR package structure

hello.jar
tom.jar
Copy the code
steps

Prepare: Copy the tom.jar without the package from above (to call the speak method inside).

  1. Write a hello. Java file and compile it into hello. class
 javac -cp tom.jar Hello.class
Copy the code

-cp stands for -classpath, which means adding tom.jar to the classpath path

  1. Create hello.class as a jar package, skip the steps

  2. Error: ClassNotFoundException: Tom

The reason is simple: introducing jar packages requires a new property to be configured in the manifest.mf file: class-path. The Path points to all jar packages you need. Now the manifest.mf file should become

Manifest-Version: 1.0
Created-By: 1.8.0_121 (Oracle Corporation)
Main-Class: Hello
Class-Path: Tom.jar
Copy the code
  1. Modify the file, run it again, and find success in printing Hello on the console
  • Jar -d target location of the file to be compiled

  • Jar-cvfm names the MANIFEST file to package 1 The file to package 2

Jar package contains jar package — call jar inside jar to print Hello

The resulting JAR package structure

META-INF
Hello.class
tom.jar
Copy the code
  • When we add the required third party JAR package to our own jar package, we will not find the Class exception if we continue to do so. The reason is that the JAR does not reference the JAR package that is placed inside itself.
Make a JAR package containing the resource files
Resource files inside jar packages — read files inside jars

The resulting JAR package structure

META-INF
Hello.class
text.txt
Copy the code

steps

  import java.io.InputStream;
  import java.io.BufferedReader;
  import java.io.InputStreamReader;
  class Hello{
      public static void main(String[] args) throws Exception{
          Hello hello = new Hello();
          InputStream is = hello.getClass().getResourceAsStream("text.txt");
          print(is);
     }

     /** * read the file, output the contents of the general method */
     public static void print(InputStream inputStream) throws Exception {
         InputStreamReader reader = new InputStreamReader(inputStream, "utf-8");
         BufferedReader br = new BufferedReader(reader);
         String s = "";
         while((s = br.readLine()) ! =null) System.out.println(s); inputStream.close(); }}Copy the code
Resource files are inside another JAR package — read files inside another JAR

The resulting JAR package structure

Hello. The jar resource. Jar text. TXTCopy the code

steps

Same as 1, but add resource. Jar to the classpath in the MANIFEST file

  import java.io.InputStream;
  import java.io.BufferedReader;
  import java.io.InputStreamReader;
  
  class Hello{
      public static void main(String[] args) throws Exception{
          Hello hello = new Hello();
          InputStream is = hello.getClass().getResourceAsStream("text.txt");
          print(is);
     } 
    /** * read the file, output the contents of the general method */
     public static void print(InputStream inputStream) throws Exception {
         InputStreamReader reader = new InputStreamReader(inputStream, "utf-8");
         BufferedReader br = new BufferedReader(reader);
         String s = "";
         while((s = br.readLine()) ! =null) System.out.println(s); inputStream.close(); }}Copy the code
Resource files outside the JAR package — read files outside the JAR

The resulting JAR package structure

hello.jar
text.txt
Copy the code

steps

  import java.io.InputStream;
  import java.io.BufferedReader;
  import java.io.InputStreamReader;
  import java.io.FileInputStream;
  class Hello{
      public static void main(String[] args) throws Exception{
          Hello hello = new Hello();
          InputStream is = new FileInputStream("text.txt");
         print(is);
     }
    /** * read the file, output the contents of the general method */
     public static void print(InputStream inputStream) throws Exception {
         InputStreamReader reader = new InputStreamReader(inputStream, "utf-8");
         BufferedReader br = new BufferedReader(reader);
         String s = "";
         while((s = br.readLine()) ! =null) System.out.println(s); inputStream.close(); }}Copy the code

Run the jar package

  • To do this, modify the manifest.mf file in the meta-INF directory inside the JAR package.

  • For example, there is a JAR package called demo.jar that contains a main class with a main method: demo.mainclassName

  • In the manifest.mf file, add the following sentence: main-class: demo.mainclassName

  • We can then run the jar by typing java-jar test.jar in the console.

If the project needs to reference some third-party jar packages, the IDE refers to this package called some.jar as a project jar package in the project’s lib subdirectory. The Class exception is not found when the java-jar is used to execute the test.jar, because the jar does not reference its own internal JAR package.

Classpath Parameter setting mode

Is there a way to add it to the classpath at runtime? Add the CLASspath parameter while running the JAR.

java -classpath some.jar -jar test.jar
Copy the code
  • This method does not work because the jar specified by classpath is loaded by the AppClassloader (system class loader). The AppClassloader only focuses on classes in the test.jar range after the -jar parameter is added to the Java command. The CLASspath parameter is invalid.
How do you refer to other JAR packages?
Methods a

Bootstrap Classloader loads these classes

As we mentioned earlier when talking about classloaders, you can use the following parameters at runtime:

  • -Xbootclasspath: completely replaces the System Java classpath. Better not.

  • -xbootCLASSPath /a: : Loads after the system class is loaded. This is usually used.

  • -xbootclasspath /p: loads the system class before it is loaded.

    • win32 java -Xbootclasspath/a: some.jar; some2.jar; -jar test.jar

    • unix java -Xbootclasspath/a: some.jar:some2.jar: -jar test.jar

In Win32, each JAR is separated by a semicolon. In Unix, each JAR is separated by a colon

Method 2

Use the Extension Classloader to load

You can dump all the jars that need to be loaded under %JRE_HOME%/lib/ext. The jar packages in this directory will be loaded by Extension Classloader after Bootstrap Classloader is finished. Very convenient, very easy.

Methods three

To load it using AppClassloader, but without the classpath parameter, we add the following code to manifest.mf:

Class-Path: lib/some.jar

Lib is a subdirectory of the same directory as test.jar, which contains the some.jar package that test.jar references. If more than one JAR package needs to be referenced:

Class-Path: lib/some.jar lib/some2.jar

Each individual JAR should be separated by a space. Note the use of relative paths.

If the meta-INF file contains the index. LIST file, the class-path configuration may be invalid. Index. LIST is an INDEX file generated when the Jar packaging tool is used to package the Jar.