classpath and packaging process in SpringBoot

1. The classpath in SpringBoot

Recently, I want to summarize the default path of the classpath in Java and the processing of related resource files, not only considering that it was not clear enough in the process of use before, but also found in the class ResourceProperties when looking at spring-boot-autoconfigure. A string of codes:

private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{
    "classpath:/META-INF/resources/", 
    "classpath:/resources/", 
    "classpath:/static/", 
    "classpath:/public/"};

This code represents Spring's default environment configuration when reading resources.

There may be some doubts about why it looks different from the project, which is about the value of classpath

  • classpath: Represents the resources under the target/classes folder in the current project
  • classpath*: represents the current project and depends on the resources under the target/classes folder in the jar

PS: Using classpath* needs to traverse all classpaths, so the loading speed is relatively slow. Therefore, it is necessary to plan the path of resource files as much as possible, and avoid using global searches such as classpath*

The default path of the above code corresponds to the project is:

  • /META-INF/resources/: src/main/resources/META-INF/resources (configuration file)
  • /resources/: src/main/resources/resources (configuration file)
  • /static/: src/main/resources/static (static resource file)
  • /public/: src/main/resources/public (page file)

Of course, these default addresses are not unchanged. When the Spring version is updated or you manually modify the default Sources folder, the default path will change accordingly.

In a web project like SpringMvc, you will also have a folder: src/main/webapp. This directory at the same level as java and resource is specially used to develop the front end, that is, to store static resource files (JPG, JS, CSS, etc.), front-end page files (JSP, etc.)

2. SpringBoot packaging process

Configuration process and knowledge points of SpringBoot packaging

Configuration process: Introduce maven packaging dependencies in the build layer

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

After running the maven clean->compile->package command, two files appear under the project target folder 

The one without the original suffix is ​​the executable jar package, and the one with the original suffix is ​​the file generated by maven's first packaging project. Maven first packages the project into a normal jar, and then performs secondary packaging through repackage to generate an executable jar.

Knowledge points:

Why use spring-boot-maven-plugin?

The role of the Spring Boot Maven plugin: Provide Spring Boot support in Apache Maven. It allows the project to be packaged as an executable jar or war, run the Spring Boot application, generate build information (MANIFEST.MF content) and start the Spring Boot application before running the integration tests.

The specification of the executable jar

  • In the jar file, we must put the class of the mainclass entry in the root directory of the jar package, otherwise the jar file cannot be started

Springboot can run the running process of jar

First introduce the internal structure of the executable jar. After opening the executable jar file, you can find that the directory is as follows: 

Red box: applications and third-party dependencies

Basket: Basic information of related jar packages, mianclass information, startclass information, version information of building and packaging projects, maven related information

Black box: springboot can run the starter of the jar package

  • We can know the jar startup class org.springframework.boot.loader.JarLauncher through the MANIFEST.MF file

This piece is the runnable class entry provided by the spring-boot-maven-plugin plugin

  • Entering the JarLauncher class, you can find that the launch function in the superclass Launcher is called in the main function

  • Since the isExploded() function returns false by default, JarFile.registerUrlProtocolHandler() must be executed to register the url protocol handler. Load springboot's custom protocol handler (org.springframework.boot.loader.jar.Handler) into the environment variable java.protocol.handler.pkgs.
JarFile.registerUrlProtocolHandler(); 
=====================================
public static void registerUrlProtocolHandler() {
    String handlers = System.getProperty("java.protocol.handler.pkgs", "");
    System.setProperty("java.protocol.handler.pkgs", 
        "".equals(handlers) ? "org.springframework.boot.loader" : (handlers + "|" + "org.springframework.boot.loader"));
    resetCachedUrlHandlers();
}
private static void resetCachedUrlHandlers() {
    try {
      URL.setURLStreamHandlerFactory(null);
    } catch (Error error) {}
}

Note: org.springframework.boot.loader.jar.Handler is used to provide a solution to the jar in jar problem. This is because the ClassLoader provided by JDK can only recognize the class files in the jar and load the classes in other jar packages under the classpath Files cannot be processed for JAR files that contain dependencies, and a classnotfound exception will pop up.

  • Create a class loader ClassLoader
ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator());
=============================================================================
使用ExecutableArchiveLauncher子类中的方法,超类中方法已被@Deprecated修饰
protected Iterator<Archive> getClassPathArchivesIterator() throws Exception {
    Archive.EntryFilter searchFilter = this::isSearchCandidate;
    Iterator<Archive> archives = this.archive.getNestedArchives(searchFilter, entry -> 
        (isNestedArchive(entry) && !isEntryIndexed(entry)));
    if (isPostProcessingClassPathArchives())
      archives = applyClassPathArchivePostProcessing(archives); 
    return archives;
}
=============================================================================
Launcher类:
@Deprecated
protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {
    return createClassLoader(archives.iterator());
}
protected ClassLoader createClassLoader(Iterator<Archive> archives) throws Exception {
    List<URL> urls = new ArrayList<>(50);
    while (archives.hasNext()) {
      Archive archive = archives.next();
      urls.add(archive.getUrl());
      archive.close();
    } 
    return createClassLoader(urls.<URL>toArray(new URL[0]));
}
  • Call the original startup class of the project
String launchClass = (jarMode != null && !jarMode.isEmpty()) ? "org.springframework.boot.loader.jarmode.JarModeLauncher" : getMainClass();
launch(args, launchClass, classLoader);
====================================================================
ExecutableArchiveLauncher:获取Start-Class
protected String getMainClass() throws Exception {
    Manifest manifest = this.archive.getManifest();
    String mainClass = null;
    if (manifest != null)
      mainClass = manifest.getMainAttributes().getValue("Start-Class"); 
    if (mainClass == null)
      throw new IllegalStateException("No 'Start-Class' manifest entry specified in " + this); 
    return mainClass;
}
====================================================================
Launcher:运行项目启动类
protected void launch(String[] args, String launchClass, ClassLoader classLoader) throws Exception {
    Thread.currentThread().setContextClassLoader(classLoader);
    createMainMethodRunner(launchClass, args, classLoader).run();
}
====================================================================
通过反射的方式执行项目启动类的main函数
public class MainMethodRunner {
  private final String mainClassName;
  
  private final String[] args;
  
  public MainMethodRunner(String mainClass, String[] args) {
    this.mainClassName = mainClass;
    this.args = (args != null) ? (String[])args.clone() : null;
  }
  
  public void run() throws Exception {
    Class<?> mainClass = Class.forName(this.mainClassName, false, Thread.currentThread().getContextClassLoader());
    Method mainMethod = mainClass.getDeclaredMethod("main", new Class[] { String[].class });
    mainMethod.setAccessible(true);
    mainMethod.invoke(null, new Object[] { this.args });
  }
}

Since then, the jar package has been running, given that this article contains knowledge points related to resource files. Here also talk about the PropertiesLauncher in the executable jar:

Look at the PropertiesLauncher code under org/springframework/boot/loader, and you can also find that the jar package obtains the dependency classpath and the configuration file under the current project classpath:boot-inf path by default when running and initializing

private void initializeProperties() throws Exception {
    List<String> configs = new ArrayList<>();
    if (getProperty("loader.config.location") != null) {
      configs.add(getProperty("loader.config.location"));
    } else {
      String[] names = getPropertyWithDefault("loader.config.name", "loader").split(",");
      for (String name : names) {
        configs.add("file:" + getHomeDirectory() + "/" + name + ".properties");
        configs.add("classpath:" + name + ".properties");
        configs.add("classpath:BOOT-INF/classes/" + name + ".properties");
      } 
    } 
    for (String config : configs) {
      try (InputStream resource = getResource(config)) {
        if (resource != null) {
          debug("Found: " + config);
          loadResource(resource);
          return;
        } 
        debug("Not found: " + config);
      } 
    } 
  }

​​​​​​​

Guess you like

Origin blog.csdn.net/weixin_42505381/article/details/128763629
Recommended