SpringBoot开发入门

1、如何创建SpringBoot应用

一般开发Java应用都会使用Maven或Gradle管理依赖,SpringBoot应用也一样,创建的SpringBoot应用本身就是Maven或Gradle工程,只是引入了SpringBoot的相关依赖。

1.1、创建SpringBoot应用的工具

创建SpringBoot应用的途径或工具大概有三种,大部分时候会使用IDE(含STS)创建SpringBoot应用。

  • 第一种是命令行

比如Maven命令行,因为SpringBoot应用本身就是Maven工程或Gradle工程。相较于后面两种创建方式,需要手动创建和编写pom文件。

  • 第二种是IDE

基本上大部分IDE都有创建SpringBoot应用的插件可以下载安装,下载后,创建的过程种可以选择应用需要使用的依赖,然后生成工程并已经正确引入需要的依赖,不要人工再另外编辑。如果没有安装插件,也可以创建一般的工程(如Maven工程),然后手动引入SpringBoot或第三方库即可。

  • 第三种是Spring官方提供的工具

包括Spring官方的STS(Spring Tools Suite,基于Eclipse开发的IDE)、以及Spring Initialzr网站在线创建。

1.2、创建SpringBoot应用的步骤

如果是使用安装了创建SpringBoot应用的插件或Spring官方提供的工具创建,只需要按照提示操作即可。

如果不使用插件的IDE创建SpringBoot应用,大概可以分为三步

  • 创建Maven/Gradle工程

基本上所有的IDE都支持创建Maven或Gradle工程,按照步骤提示操作

  • 引入需要的SpringBoot或第三方库依赖

创建完工程后,引入应用所需的SpringBoot模块或第三方库。比如创建一个Web应用,为了减少Spring模块及其第三方库的版本管理,引入spring-boot-starter-parent作为parent;其次是引入spring-boot-starter-web模块,这是开发WEB应用所需的模块;接着,要将应用构建打包成为独立的Spring应用部署,需要引入spring-boot-maven-plugin插件;然后,如果要使用Spring的测试组件,可以引入spring-boot-starter-test模块;最后,按照应用需要引入其它第三方库。

如果创建的SpringBoot应用不能或无法使用spring-boot-starter-parent作为parent,则可以引入spring-boot-dependencies作为替代的依赖管理,此时spring-boot-maven-plugin插件需要明确指定版本信息且与spring-boot-dependencies保持一致,并且指定的执行目标goal为repackage;如果要打成war包,还需要另外引入apache的maven-war-plugin且版本需要符合或匹配SpringBoot所要求的版本。

  • 创建引导类

引导类是启动SpringBoot应用的入口。如果是使用了插件或Spring官方工具创建的工程,会自动生成引导类。否则,需要手动创建。可以为引导类添加@SpringBootApplication做为引导启动SpringApplication的主要配置源。

接下来的,就是业务逻辑开发,和一般的Spring应用没有差别了。

2、Hello,SpringBoot!

下面,根据以上的创建步骤,演示如何使用IDEA社区版创建一个Hello,SpringBoot应用。首先它是一个Maven工程,属于Web应用,实现简单的业务逻辑,用户通过浏览器访问时,返回欢迎语,比如Hello,SpringBoot!。

2.1、创建Maven工程

使用IDEA创建一个Maven工程hello-spring-boot。初始的pom.xml如下



    4.0.0
​
    com.fandou.coffee.learning
    hello-spring-boot
    1.0-SNAPSHOT

2.2、引入SpringBoot相关模块

修改pom.xml文件,引入spring-boot-starter-parent作为parent;因为创建的式Web应用需要使用SpringMVC模块,需要引入spring-boot-starter-web模块依赖;最后,添加spring-boot-maven-plugin插件用来打包部署;暂时不编写测试代码,不添加测试依赖。最终的pom.xml文件如下



    4.0.0
​
    
    
        org.springframework.boot
        spring-boot-starter-parent
        2.3.3.RELEASE
        
    
​
    com.fandou.coffee.learning
    hello-spring-boot
    1.0-SNAPSHOT
    Hello,SpringBoot
    SpringBoot开发入门示例
​
    
    
        1.8
    
​
    
    
        
            org.springframework.boot
            spring-boot-starter-web
        
    
​
    
    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    

2.3、创建引导类

接着创建hello-spring-boot应用的引导类。创建应用的包com.fandou.coffee.learning.springboot.hello,然后包下创建引导类HelloSpringBootBootstrap。

/**
 * HelloSpringBoot应用引导类
 *
 * 使用@SpringBootApplication开启自动装配
 */
@SpringBootApplication
public class HelloSpringBootBootstrap {
    public static void main(String[] args) {
        // HelloSpringBootBootstrap作为只要配置源
        SpringApplication.run(HelloSpringBootBootstrap.class,args);
    }
}

使用@SpringBootApplication开启自动装配,通过SpringApplication的静态run方法引导应用启动,引导类HelloSpringBootBootstrap作为主要配置源。

2.4、实现业务逻辑

实现简单的打招呼功能,对每个来访客户,返回“Hello,{visitor}!Welcome to SpringBoot world!”问候语。先创建controller子包,然后创建HelloController类。

/**
 * 打招呼业务
 *
 * 欢迎来访客户来到SpringBoot的世界
 */
@RestController
public class HelloController {
​
    /**
     * 欢迎来访的客户来到SpringBoot的世界
     *
     * @param visitor 来访客户名称
     * @return 问候语
     */
    @GetMapping("/hello/{visitor}")
    public String hello(@PathVariable("visitor") String visitor){
        return "Hello," + visitor + "! Welcome to SpringBoot world!";
    }
}

2.5、本地运行测试

在IDEA直接中运行引导类HelloSpringBootBootstrap,看到下面的输出表示成功启动

......
 :: Spring Boot ::        (v2.3.3.RELEASE)
​
......
2020-09-14 22:15:21.642  INFO 15340 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2020-09-14 22:15:21.642  INFO 15340 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2020-09-14 22:15:21.689  INFO 15340 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 47 ms

在浏览器访问地址http://localhost:8080/hello/Kim,看到浏览器返回的结果显示如下,说明应用成功运行,并正常提供服务了。

Hello,Kim! Welcome to SpringBoot world!

2.6、打包部署

在IDEA中使用Maven工具的package打包hello-spring-boot。

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----        2020/9/14     22:14                classes
d-----        2020/9/14     22:14                generated-sources
d-----        2020/9/15      0:00                generated-test-sources
d-----        2020/9/15      0:00                maven-archiver
d-----        2020/9/15      0:00                maven-status
d-----        2020/9/15      0:00                test-classes
-a----        2020/9/15      0:00       16520975 hello-spring-boot-1.0-SNAPSHOT.jar
-a----        2020/9/15      0:00           4366 hello-spring-boot-1.0-SNAPSHOT.jar.original

package打包完成,在项目的target目录下生成了两个文件,一个是hello-spring-boot-1.0-SNAPSHOT.jar,一个是hello-spring-boot-1.0-SNAPSHOT.jar.original,前者大约16M,是接下来要用来单独部署的jar包;后者只有5K,不包含运行所需依赖的类包。

一般情况下,将可独立部署的jar包部署到服务器后,可以在控制台或shell脚本使用java -jar直接运行jar包

java -jar hello-spring-boot-1.0-SNAPSHOT.jar

执行命令后,可以看到和本地运行测试时一样的输出,然后在浏览器输入访问地址http://服务器IP:8080/hello/Kim,可以得到相同的结果,说明部署成功。

在Linux服务器上,也可以使用以下命令,&表示后台运行(不阻塞),nohub表示关闭终端也可以继续运行。

nohub java -jar hello-spring-boot-1.0-SNAPSHOT.jar &

以上的示例当中,没有任何配置文件,只要引入SpringBoot和相关的模块,启动时,自动会加载所需要的资源并提供缺省设置,应用便可以轻松的运行起来。

2.7、使用spring-boot-dependencies

实际开发过程中,项目都会有自己的parent,可能无法使用spring-boot-starter-parent做为parent,此时可以使用spring-boot-dependencies代替。下面改造hello-spring-boot应用,使之更符合实际开发的中的场景。

主要的改造涉及三个部分,第一是添加项目代码的编译版本和项目文件编码,第二个就是添加spring-boot-dependencies依赖管理,第三个是配置spring-boot-maven-plugin打包插件的执行目标repackage。以下是改造后的pom.xml文件内容



    4.0.0
​
    
​
    com.fandou.coffee.learning
    hello-spring-boot
    1.0-SNAPSHOT
    Hello,SpringBoot
    SpringBoot开发入门示例
​
    
    
        1.8
        
        1.8
        1.8
        UTF-8
        UTF-8
    
​
    
    
        
            org.springframework.boot
            spring-boot-starter-web
        
    
​
    
    
        
            
                org.springframework.boot
                spring-boot-dependencies
                2.3.3.RELEASE
                pom
                import
            
        
    
​
    
    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
                
                2.3.3.RELEASE
                
                
                    
                        
                            repackage
                        
                    
                
                
            
        
    

改造后,可以正常打包运行。在真实项目中,也可以把spring-boot-dependencies依赖管理等添加到parent项目中,这样就不需要所有子模块都另外配置。

3、独立运行原理

3.1、如何运行jar包程序

在Java语言规范中,程序运行的入口是main方法,是程序运行的起点,运行jar文件时,实际上运行的入口也是main方法。

public static void main(String args[]) { ... }

main方法需要符合4个规则:修饰符为public和static、返回值为void、方法名为main、方法参数为字符串数组String[]。

java命令要运行一个jar文件形式的程序,只需在jar包中创建META-INF/MANIFEST.MF文件,并在MANIFEST.MF文件中设置Main-Class属性,指定程序入口main方法所在的类,告诉java命令(JVM)要运行含有main方法的具体类。这是Java对运行jar包的规范,只要jar包符合这样的规范,即java命令就可以运行该jar包程序。

下面以一个Hello,world!程序为例,查看打包后可运行的jar包是否符合以上规范。

用IDEA创建一个普通的Maven工程,不需要引入任何依赖,然后创建HellojarBootstrap类

package com.fandou.coffee.learning.hellojar.hello;
​
/**
 * Hello,world!
 */
public class HellojarBootstrap {
    /**
     * 程序入口方法
     *
     * @param args 参数
     */
    public static void main(String[] args) {
        // 打印Hello,world!
        System.out.println("Hello,world!");
    }
}

接着在pom.xml文件中添加maven-jar-plugin插件,配置打包所需的MANIFEST.MF所需的信息,只需要配置mainClass即可


    org.apache.maven.plugins
    maven-jar-plugin
    3.0.2
    
        
            
            false
            
                
                com.fandou.coffee.learning.hellojar.hello.HellojarBootstrap
            
        
    

package打包后,进入target目录,使用命令行运行java -jar hello-jar-1.0-SNAPSHOT.jar,可以看到正常输出

Hello,world!

解压hello-jar-1.0-SNAPSHOT.jar,看到只有两个文件,一个是HellojarBootstrap.class,一个是MANIFEST.MF

├─com
│  └─fandou
│      └─coffee
│          └─learning
│              └─hellojar
│                  └─hello
│                          HellojarBootstrap.class
│
└─META-INF
        MANIFEST.MF

查看MANIFEST.MF文件

Manifest-Version: 1.0
Built-By: Coffee
Created-By: Apache Maven 3.6.2
Build-Jdk: 1.8.0_221
Main-Class: com.fandou.coffee.learning.hellojar.hello.HellojarBootstrap

可以看到,确实包含了Main-Class属性,当执行java命令-jar选项的时候,JVM加载jar包,然后运行Main-Class类的main方法,从而启动程序,打印输出了Hello,world!。这就是jar包的运行原理。

3.2、SpringBoot应用的jar文件

同样,java命令运行SpringBoot应用的jar包,也需要符合运行普通jar包的基本规范,就是通过运行jar包中的META-INF/MANIFEST.MF文件的Main-Class属性指定的入口类的(main方法)来实现的。解压hello-spring-boot-1.0-SNAPSHOT.jar文件后,其内部文件结构如下

├─BOOT-INF
│  │  classpath.idx
│  │
│  ├─classes
│  │  └─com
│  │      └─fandou
│  │          └─coffee
│  │              └─learning
│  │                  └─springboot
│  │                      └─hello
│  │                          │  HelloSpringBootBootstrap.class
│  │                          │
│  │                          └─controller
│  │                                  HelloController.class
│  │
│  └─lib
│          jackson-annotations-2.11.2.jar
│          jackson-core-2.11.2.jar
│          jackson-databind-2.11.2.jar
│          jackson-datatype-jdk8-2.11.2.jar
│          jackson-datatype-jsr310-2.11.2.jar
│          jackson-module-parameter-names-2.11.2.jar
│          jakarta.annotation-api-1.3.5.jar
│          jakarta.el-3.0.3.jar
│          jul-to-slf4j-1.7.30.jar
│          log4j-api-2.13.3.jar
│          log4j-to-slf4j-2.13.3.jar
│          logback-classic-1.2.3.jar
│          logback-core-1.2.3.jar
│          slf4j-api-1.7.30.jar
│          snakeyaml-1.26.jar
│          spring-aop-5.2.8.RELEASE.jar
│          spring-beans-5.2.8.RELEASE.jar
│          spring-boot-2.3.3.RELEASE.jar
│          spring-boot-autoconfigure-2.3.3.RELEASE.jar
│          spring-boot-starter-2.3.3.RELEASE.jar
│          spring-boot-starter-json-2.3.3.RELEASE.jar
│          spring-boot-starter-logging-2.3.3.RELEASE.jar
│          spring-boot-starter-tomcat-2.3.3.RELEASE.jar
│          spring-boot-starter-web-2.3.3.RELEASE.jar
│          spring-context-5.2.8.RELEASE.jar
│          spring-core-5.2.8.RELEASE.jar
│          spring-expression-5.2.8.RELEASE.jar
│          spring-jcl-5.2.8.RELEASE.jar
│          spring-web-5.2.8.RELEASE.jar
│          spring-webmvc-5.2.8.RELEASE.jar
│          tomcat-embed-core-9.0.37.jar
│          tomcat-embed-websocket-9.0.37.jar
│
├─META-INF
│  │  MANIFEST.MF
│  │
│  └─maven
│      └─com.fandou.coffee.learning
│          └─hello-spring-boot
│                  pom.properties
│                  pom.xml
│
└─org
    └─springframework
        └─boot
            └─loader
                │  ClassPathIndexFile.class
                │  ExecutableArchiveLauncher.class
                │  JarLauncher.class
                │  LaunchedURLClassLoader$DefinePackageCallType.class
                │  LaunchedURLClassLoader$UseFastConnectionExceptionsEnumeration.class
                │  LaunchedURLClassLoader.class
                │  Launcher.class
                │  MainMethodRunner.class
                │  PropertiesLauncher$1.class
                │  PropertiesLauncher$ArchiveEntryFilter.class
                │  PropertiesLauncher$ClassPathArchives.class
                │  PropertiesLauncher$PrefixMatchingArchiveFilter.class
                │  PropertiesLauncher.class
                │  WarLauncher.class
                │
                ├─archive
                │      Archive$Entry.class
                │      Archive$EntryFilter.class
                │      Archive.class
                │      ExplodedArchive$AbstractIterator.class
                │      ExplodedArchive$ArchiveIterator.class
                │      ExplodedArchive$EntryIterator.class
                │      ExplodedArchive$FileEntry.class
                │      ExplodedArchive$SimpleJarFileArchive.class
                │      ExplodedArchive.class
                │      JarFileArchive$AbstractIterator.class
                │      JarFileArchive$EntryIterator.class
                │      JarFileArchive$JarFileEntry.class
                │      JarFileArchive$NestedArchiveIterator.class
                │      JarFileArchive.class
                │
                ├─data
                │      RandomAccessData.class
                │      RandomAccessDataFile$1.class
                │      RandomAccessDataFile$DataInputStream.class
                │      RandomAccessDataFile$FileAccess.class
                │      RandomAccessDataFile.class
                │
                ├─jar
                │      AsciiBytes.class
                │      Bytes.class
                │      CentralDirectoryEndRecord$1.class
                │      CentralDirectoryEndRecord$Zip64End.class
                │      CentralDirectoryEndRecord$Zip64Locator.class
                │      CentralDirectoryEndRecord.class
                │      CentralDirectoryFileHeader.class
                │      CentralDirectoryParser.class
                │      CentralDirectoryVisitor.class
                │      FileHeader.class
                │      Handler.class
                │      JarEntry.class
                │      JarEntryFilter.class
                │      JarFile$1.class
                │      JarFile$JarEntryEnumeration.class
                │      JarFile$JarFileType.class
                │      JarFile.class
                │      JarFileEntries$1.class
                │      JarFileEntries$EntryIterator.class
                │      JarFileEntries.class
                │      JarURLConnection$1.class
                │      JarURLConnection$JarEntryName.class
                │      JarURLConnection.class
                │      StringSequence.class
                │      ZipInflaterInputStream.class
                │
                ├─jarmode
                │      JarMode.class
                │      JarModeLauncher.class
                │      TestJarMode.class
                │
                └─util
                        SystemPropertyUtils.class

与平常的jar包相比,SpringBoot应用jar包的一级目录下多了一个BOOT-INF目录,该目录下存放的应用的类文件以及依赖库;同时原本预期根目录下的应用中的类包(com.*)变成了SpringBoot的loader模块的类包目录(org.*)。

查看META-INF/MANIFEST.MF文件,其Main-Class指向的也并不是应用中的引导类HelloSpringBootBootstrap,而是SpringBoot的loader模块下的org.springframework.boot.loader.JarLauncher类,同时发现另一个属性Start-Class指向了应用的引导类。

Manifest-Version: 1.0
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Archiver-Version: Plexus Archiver
Built-By: Coffee
Start-Class: com.fandou.coffee.learning.springboot.hello.HelloSpringBo
 otBootstrap
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Spring-Boot-Version: 2.3.3.RELEASE
Created-By: Apache Maven 3.6.2
Build-Jdk: 1.8.0_221
Main-Class: org.springframework.boot.loader.JarLauncher

在项目中引入spring-boot-loader模块


    org.springframework.boot
    spring-boot-loader
    provided

查看JarLauncher类的源码

public class JarLauncher extends ExecutableArchiveLauncher {
​
    // SpringBoot应用编译后的类在当前jar包中的目录
    static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";
    // SpringBoot应用依赖库在当前jar中的目录
    static final String BOOT_INF_LIB = "BOOT-INF/lib/";
​
    public JarLauncher() {
    }
​
    protected JarLauncher(Archive archive) {
        super(archive);
    }
​
    // 判断给定的归档文件是否为BOOT-INF/classes/目录,或BOOT-INF/lib/下的jar文件
    @Override
    protected boolean isNestedArchive(Archive.Entry entry) {
        // 如果是目录
        if (entry.isDirectory()) {
            return entry.getName().equals(BOOT_INF_CLASSES);
        }
        // 如果不是目录
        return entry.getName().startsWith(BOOT_INF_LIB);
    }
​
    // java -jar 命令执行的入口
    public static void main(String[] args) throws Exception {
        // 调用了父类的launch方法
        // 当new方式创建JarLauncher对象时,父类构造方法同时被调用,会加载当前jar包
        new JarLauncher().launch(args);
    }
​
}

查看父类Launcher的launch方法

public abstract class Launcher {
​
   /**
    * 加载SpringBoot应用。此方法由子类即JarLauncher或WarLauncher的main方法调用
    * @param args 命令行参数
    * @throws 应用加载失败抛出异常
    */
   protected void launch(String[] args) throws Exception {
      // 注册jar文件的url协议处理器,用于读取加载SpringBoot应用依赖的类
      JarFile.registerUrlProtocolHandler();
       
      // 创建SpringBoot应用类加载器:指定了需要加载的类文件列表
      // 返回的是SpringBoot自定义的一个类加载器LaunchedURLClassLoader的实例,
      // LaunchedURLClassLoader重写了loadClass等方法以实现加载SpringBoot应用的类或依赖,
      // 即BOOT-INF/classes/和BOOT-INF/lib/中的类和依赖包
      ClassLoader classLoader = createClassLoader(getClassPathArchives());
      
      // 传递命令行参数,以及真正的入口程序类即Start-Class属性指定的HelloSpringBootBootstrap,
      // 并设置当前线程使用自定义的类加载器加载类和依赖库,
      // 然后通过反射,运行HelloSpringBootBootstrap类的main方法,从而启动SpringBoot应用
      launch(args, getMainClass(), classLoader);
   }
   ...
}

下面是运行引导类main方法的代码,其最终是通过运行MainMethodRunner实例的run方法完成。

/**
 * main方法运行器,通过反射调用指定类的main方法
 */
public class MainMethodRunner {
​
   // 即上面通过getMainClass()方法获取的引导类即HelloSpringBootBootstrap的名称
   private final String mainClassName;
   
   // 命令行参数
   private final String[] args;
​
   /**
    * 创建一个main方法运行器实例对象
    * @param mainClass 含有main方法的类
    * @param args 命令行参数
    */
   public MainMethodRunner(String mainClass, String[] args) {
      this.mainClassName = mainClass;
      this.args = (args != null) ? args.clone() : null;
   }
​
   /**
    * 通过反射调用指定类的main方法
    */
   public void run() throws Exception {
      // 获取当前线程中的自定义类加载器实例加载指定的类
      Class mainClass = Thread.currentThread().getContextClassLoader()
            .loadClass(this.mainClassName);
      // 通过反射获取main方法并调用
      Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
      // 即调用了引导类HelloSpringBootBootstrap的main方法,从而启动SpringBoot应用
      mainMethod.invoke(null, new Object[] { this.args });
   }
}

到此,基本上可以了解SpringBoot应用独立运行部署的核心原理,本质上它依然遵循jar包的规范,然后通过自定义类加载器修改了加载规则来启动SpringBoot应用。

首先,通过自定义打包插件,修改jar包中的Main-Class属性,在真正启动SpringBoot应用前先启动自定义的加载类JarLauncher或WarLauncher类;

接着创建自定义类加载器实例,用来加载SpringBoot应用的引导类以及依赖的类库;

然后运行SpringBoot应用的引导类的main方法,从而启动SpringBoot应用的。

了解SpringBoot应用的生命周期和事件,可参考SpringBoot生命周期

你可能感兴趣的:(Java开发,spring,spring,boot,java)