Spring Boot热部署实践:开发效率提升与配置详解

# Spring Boot热部署实践:开发效率提升与配置详解


在软件开发过程中,频繁的重启应用会严重影响开发效率。Spring Boot通过多种热部署技术,实现了代码变更的实时生效,显著提升了开发体验。


## DevTools基础配置


Spring Boot DevTools提供了开箱即用的热部署能力,支持自动重启和静态资源热加载。


```xml

   

        org.springframework.boot

        spring-boot-starter-parent

        3.1.0

   

    

   

       

       

            org.springframework.boot

            spring-boot-devtools

            runtime

            true

       

        

       

       

            org.springframework.boot

            spring-boot-starter-actuator

       

        

       

       

            org.springframework.boot

            spring-boot-configuration-processor

            true

       

        

       

       

            org.springframework.boot

            spring-boot-starter-thymeleaf

       

        

       

       

            org.webjars

            webjars-locator-core

       

   

    

   

       

           

           

                org.apache.maven.plugins

                maven-resources-plugin

               

                   

                        @

                   

                    false

               

           

            

           

           

                org.springframework.boot

                spring-boot-maven-plugin

               

                    true

                    true

                    false

                   

                   

                        -XX:+UseG1GC

                        -Xmx512m

                        -Xms256m

                        -Dspring.devtools.restart.enabled=true

                        -Dspring.devtools.livereload.enabled=true

                   

               

           

       

   

```


## application.yml热部署配置


```yaml

# application-dev.yml - 开发环境热部署配置

spring:

  # DevTools配置

  devtools:

    restart:

      enabled: true

      # 触发重启的轮询间隔

      poll-interval: 1000ms

      # 静默期,避免频繁重启

      quiet-period: 400ms

      # 触发重启的额外路径

      additional-paths:

        - src/main/java

        - src/main/resources

        - src/main/templates

      # 排除路径

      exclude:

        - static/**

        - public/**

        - resources/**

        - META-INF/maven/**

        - target/**

        - build/**

      # 触发器文件

      trigger-file: .trigger

      # 日志级别

      log-condition-evaluation-delta: false

      # 重启策略

      additional-exclude: META-INF/spring-configuration-metadata.json

    

    # 实时重载配置

    livereload:

      enabled: true

      port: 35729

      

    # 远程开发支持

    remote:

      secret: development-secret-key-change-in-production

      debug:

        enabled: true

        local-port: 8000

    

    # 静态资源缓存

    add-properties: false

    

  # Thymeleaf模板引擎热部署配置

  thymeleaf:

    cache: false

    prefix: classpath:/templates/

    suffix: .html

    mode: HTML

    encoding: UTF-8

    servlet:

      content-type: text/html

    # 开发模式配置

    check-template-location: true

    template-resolver-order: 0

    # 启用模板解析器缓存

    enable-spring-el-compiler: true

    

  # Freemarker模板引擎配置

  freemarker:

    cache: false

    charset: UTF-8

    check-template-location: true

    content-type: text/html

    expose-request-attributes: true

    expose-session-attributes: true

    expose-spring-macro-helpers: true

    suffix: .ftl

    template-loader-path: classpath:/templates/

    

  # Groovy模板配置

  groovy:

    template:

      cache: false

      

  # Mustache模板配置

  mustache:

    cache: false

    suffix: .mustache

    

  # 静态资源处理

  web:

    resources:

      # 静态资源缓存时间(秒)

      cache:

        period: 0

      chain:

        cache: false

        compressed: false

        enabled: true

        html-application-cache: false

      static-locations:

        - classpath:/static/

        - classpath:/public/

        - classpath:/resources/

        - classpath:/META-INF/resources/

      

  # MVC配置

  mvc:

    log-request-details: true

    throw-exception-if-no-handler-found: true

    

  # 消息转换

  jackson:

    default-property-inclusion: non_null

    serialization:

      indent-output: true

      write-dates-as-timestamps: false

    deserialization:

      fail-on-unknown-properties: false

    parser:

      allow-unquoted-control-chars: true

      allow-single-quotes: true

      

  # JPA配置

  jpa:

    hibernate:

      ddl-auto: update

    show-sql: true

    properties:

      hibernate:

        format_sql: true

        use_sql_comments: true

        # 启用统计信息

        generate_statistics: true

        jdbc:

          batch_size: 20

        order_inserts: true

        order_updates: true

        

  # 缓存配置

  cache:

    type: simple

    cache-names:

      - users

      - products

      - categories

    caffeine:

      spec: maximumSize=500,expireAfterAccess=600s

      

  # 会话配置

  session:

    store-type: none

    

  # 邮件配置(开发环境使用假发送)

  mail:

    host: localhost

    port: 1025

    properties:

      mail:

        smtp:

          auth: false

          starttls:

            enable: false

            

  # 日志配置

  output:

    ansi:

      enabled: always


# 开发工具自定义配置

app:

  dev:

    # 热部署监控端点

    monitoring:

      enabled: true

      endpoints:

        - /actuator/restart

        - /actuator/refresh

      # 自动刷新配置

      auto-refresh: true

      refresh-interval: 5000ms

    # 快速失败配置

    fail-fast: true

    # 代码覆盖率收集

    code-coverage:

      enabled: true

      format: html

      excludes:

        - "**/test/**"

        - "**/Test*.java"

        - "**/*Test.java"

        - "**/*Tests.java"


# 日志详细配置

logging:

  level:

    root: INFO

    # 开发工具日志

    org.springframework.boot.devtools: DEBUG

    # 重启相关日志

    org.springframework.boot.devtools.restart: DEBUG

    # 类加载器日志

    org.springframework.boot.devtools.classloader: DEBUG

    # 模板引擎日志

    org.thymeleaf: DEBUG

    org.freemarker: DEBUG

    # 数据库日志

    org.hibernate.SQL: DEBUG

    org.hibernate.type.descriptor.sql.BasicBinder: TRACE

    org.hibernate.stat: DEBUG

    # Spring相关日志

    org.springframework.web: DEBUG

    org.springframework.transaction: DEBUG

    org.springframework.jdbc: DEBUG

    # 应用包日志

    com.example: DEBUG

  pattern:

    console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{36}) - %msg%n"

    file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"

  file:

    name: logs/dev-app.log

    max-history: 7

    max-size: 10MB

    total-size-cap: 100MB


# Actuator端点配置

management:

  endpoints:

    web:

      exposure:

        include: restart,refresh,health,info,metrics,loggers

      base-path: /dev

    # JMX端点

    jmx:

      exposure:

        include: "*"

  endpoint:

    restart:

      enabled: true

    refresh:

      enabled: true

    health:

      show-details: always

    loggers:

      enabled: true

  info:

    env:

      enabled: true

    java:

      enabled: true

    os:

      enabled: true

    build:

      enabled: true

    git:

      mode: full


# 开发服务器配置

server:

  port: 8080

  # 开发环境使用HTTP

  ssl:

    enabled: false

  # 连接配置

  connection-timeout: 30000ms

  tomcat:

    # 线程池配置

    threads:

      max: 50

      min-spare: 5

    # 连接器配置

    max-connections: 10000

    accept-count: 100

    # 访问日志

    accesslog:

      enabled: true

      pattern: "%t %a %r %s %b %D"

      directory: logs

      prefix: access_log

      suffix: .log

      rotate: true

    # URI编码

    uri-encoding: UTF-8

    # 静态资源缓存

    static-resource-cache-ttl: 0s

```


## 高级热部署配置类


```java

// DevToolsAdvancedConfig.java - 高级热部署配置

package com.example.config;


import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;

import org.springframework.boot.devtools.restart.ConditionalOnInitializedRestarter;

import org.springframework.boot.devtools.restart.RestartScope;

import org.springframework.boot.devtools.restart.Restarter;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.context.annotation.Profile;


import java.io.File;

import java.nio.file.*;

import java.util.HashSet;

import java.util.Set;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;


@Configuration

@Profile("dev")

@ConditionalOnProperty(name = "spring.devtools.restart.enabled", havingValue = "true")

public class DevToolsAdvancedConfig {

    

    /**

     * 自定义文件变更监听器

     * 监控特定目录的文件变更,触发自定义重启逻辑

     */

    @Bean

    @ConditionalOnInitializedRestarter

    public FileChangeWatcher fileChangeWatcher() {

        return new FileChangeWatcher();

    }

    

    /**

     * 类重载器配置

     * 支持部分类重载而不需要完全重启

     */

    @Bean

    @RestartScope

    public ClassReloader classReloader() {

        return new ClassReloader();

    }

    

    /**

     * 热部署管理器

     * 控制重启频率和策略

     */

    @Bean

    public HotDeployManager hotDeployManager() {

        return new HotDeployManager();

    }

    

    /**

     * 静态资源监视器

     * 实时更新静态资源而不重启

     */

    @Bean

    public StaticResourceWatcher staticResourceWatcher() {

        return new StaticResourceWatcher();

    }

    

    /**

     * 配置刷新器

     * 监控配置文件的变更

     */

    @Bean

    public ConfigRefresher configRefresher() {

        return new ConfigRefresher();

    }

}


// 文件变更监视器

@Component

@Slf4j

class FileChangeWatcher {

    

    private final Set watchServices = new HashSet<>();

    private final ExecutorService executor = Executors.newSingleThreadExecutor();

    private volatile boolean running = true;

    

    @PostConstruct

    public void init() {

        log.info("初始化文件变更监视器");

        

        // 监视的目录

        String[] watchDirs = {

            "src/main/java",

            "src/main/resources",

            "src/main/templates"

        };

        

        for (String dir : watchDirs) {

            startWatching(dir);

        }

        

        // 启动监控线程

        executor.submit(this::monitorChanges);

    }

    

    private void startWatching(String directory) {

        try {

            Path path = Paths.get(directory).toAbsolutePath();

            if (!Files.exists(path)) {

                log.warn("监视目录不存在: {}", path);

                return;

            }

            

            WatchService watchService = FileSystems.getDefault().newWatchService();

            path.register(watchService,

                StandardWatchEventKinds.ENTRY_CREATE,

                StandardWatchEventKinds.ENTRY_MODIFY,

                StandardWatchEventKinds.ENTRY_DELETE);

            

            watchServices.add(watchService);

            log.info("开始监视目录: {}", path);

            

        } catch (Exception e) {

            log.error("初始化文件监视失败: {}", directory, e);

        }

    }

    

    private void monitorChanges() {

        while (running && !Thread.currentThread().isInterrupted()) {

            for (WatchService watchService : watchServices) {

                try {

                    Watch Key key = watchService.poll(500, java.util.concurrent.TimeUnit.MILLISECONDS);

                    if (key != null) {

                        processWatch Key(key);

                        key.reset();

                    }

                } catch (InterruptedException e) {

                    Thread.currentThread().interrupt();

                    break;

                } catch (Exception e) {

                    log.error("处理文件变更事件失败", e);

                }

            }

        }

    }

    

    private void processWatch Key(Watch Key key) {

        for (WatchEvent event : key.pollEvents()) {

            WatchEvent.Kind kind = event.kind();

            

            if (kind == StandardWatchEventKinds.OVERFLOW) {

                continue;

            }

            <"i6.p5k3.org.cn"><"o3.p5k3.org.cn"><"l7.p5k3.org.cn">

            @SuppressWarnings("unchecked")

            WatchEvent ev = (WatchEvent) event;

            Path filename = ev.context();

            Path dir = (Path) key.watchable();

            Path fullPath = dir.resolve(filename);

            

            log.debug("检测到文件变更 - 类型: {}, 文件: {}", kind, fullPath);

            

            // 根据文件类型处理

            if (shouldTriggerRestart(fullPath)) {

                scheduleRestart();

            } else if (shouldTriggerLiveReload(fullPath)) {

                triggerLiveReload();

            }

        }

    }

    

    private boolean shouldTriggerRestart(Path filePath) {

        String fileName = filePath.toString();

        // Java文件变更需要重启

        return fileName.endsWith(".java") || 

               fileName.endsWith(".kt") || 

               fileName.endsWith(".groovy");

    }

    

    private boolean shouldTriggerLiveReload(Path filePath) {

        String fileName = filePath.toString();

        // 静态资源文件只需实时重载

        return fileName.endsWith(".html") || 

               fileName.endsWith(".css") || 

               fileName.endsWith(".js") ||

               fileName.endsWith(".properties") ||

               fileName.endsWith(".yml") ||

               fileName.endsWith(".yaml");

    }

    

    private void scheduleRestart() {

        log.info("检测到代码变更,计划重启应用");

        

        // 使用Spring Boot的Restarter

        Restarter restarter = Restarter.getInstance();

        if (restarter != null) {

            // 延迟重启,避免频繁触发

            new Thread(() -> {

                try {

                    Thread.sleep(1000); // 1秒延迟

                    restarter.restart();

                } catch (Exception e) {

                    log.error("重启应用失败", e);

                }

            }).start();

        }

    }

    

    private void triggerLiveReload() {

        log.debug("检测到资源变更,触发实时重载");

        // 可以集成LiveReload服务器

    }

    

    @PreDestroy

    public void destroy() {

        running = false;

        executor.shutdownNow();

        

        for (WatchService watchService : watchServices) {

            try {

                watchService.close();

            } catch (Exception e) {

                log.error("关闭WatchService失败", e);

            }

        }

    }

}


// 类重载器实现

@Component

@Slf4j

class ClassReloader {

    

    private final ClassLoader classLoader;

    private final Set reloadableClasses = new HashSet<>();

    

    public ClassReloader() {

        this.classLoader = Thread.currentThread().getContextClassLoader();

        

        // 配置可热重载的类

        reloadableClasses.add("com.example.controller.");

        reloadableClasses.add("com.example.service.");

        reloadableClasses.add("com.example.component.");

    }

    

    /**

     * 尝试热重载单个类

     */

    public boolean reloadClass(String className) {

        if (!isReloadable(className)) {

            return false;

        }

        

        try {

            String classFile = className.replace('.', '/') + ".class";

            URL url = classLoader.getResource(classFile);

            

            if (url != null) {

                File file = new File(url.toURI());

                if (file.exists()) {

                    byte[] bytes = Files.readAllBytes(file.toPath());

                    defineClass(className, bytes);

                    log.info("热重载类: {}", className);

                    return true;

                }

            }

        } catch (Exception e) {

            log.error("热重载类失败: {}", className, e);

        }

        

        return false;

    }

    

    /**

     * 批量重载类

     */

    public int reloadClasses(Set classNames) {

        int successCount = 0;

        for (String className : classNames) {

            if (reloadClass(className)) {

                successCount++;

            }

        }

        return successCount;

    }

    

    private boolean isReloadable(String className) {

        return reloadableClasses.stream()

            .anyMatch(className::startsWith);

    }

    

    private Class defineClass(String name, byte[] b) {

        try {

            // 使用自定义ClassLoader定义类

            Method defineClass = ClassLoader.class.getDeclaredMethod(

                "defineClass", String.class, byte[].class, int.class, int.class);

            defineClass.setAccessible(true);

            return (Class) defineClass.invoke(classLoader, name, b, 0, b.length);

        } catch (Exception e) {

            throw new RuntimeException("定义类失败: " + name, e);

        }

    }

}


// 热部署管理器

@Component

@Slf4j

class HotDeployManager {

    

    private final Restarter restarter;

    private final AtomicInteger restartCount = new AtomicInteger(0);

    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

    private volatile ScheduledFuture restartTask;

    

    public HotDeployManager() {

        this.restarter = Restarter.getInstance();

    }

    

    /**

     * 延迟重启

     */

    public void scheduleDelayedRestart(long delay, TimeUnit unit) {

        cancelPendingRestart();

        

        restartTask = scheduler.schedule(() -> {

            try {

                log.info("执行延迟重启 (#{})", restartCount.incrementAndGet());

                restarter.restart();

            } catch (Exception e) {

                log.error("延迟重启失败", e);

            }

        }, delay, unit);

        

        log.info("计划在{} {}后重启应用", delay, unit);

    }

    

    /**

     * 取消待处理的重启

     */

    public void cancelPendingRestart() {

        if (restartTask != null && !restartTask.isDone()) {

            restartTask.cancel(false);

            log.info("已取消待处理的重启");

        }

    }

    

    /**

     * 立即重启

     */

    public void restartNow() {

        cancelPendingRestart();

        try {

            log.info("立即重启应用 (#{})", restartCount.incrementAndGet());

            restarter.restart();

        } catch (Exception e) {

            log.error("立即重启失败", e);

        }

    }

    

    /**

     * 获取重启统计

     */

    public RestartStats getRestartStats() {

        return new RestartStats(

            restartCount.get(),

            System.currentTimeMillis()

        );

    }

    

    @PreDestroy

    public void destroy() {

        cancelPendingRestart();

        scheduler.shutdownNow();

    }

    

    public static class RestartStats {

        private final int totalRestarts;

        private final long lastRestartTime;

        

        public RestartStats(int totalRestarts, long lastRestartTime) {

            this.totalRestarts = totalRestarts;

            this.lastRestartTime = lastRestartTime;

        }

        

        // getters...

    }

}


// 静态资源监视器

@Component

@Slf4j

class StaticResourceWatcher {

    

    private final Map fileTimestamps = new ConcurrentHashMap<>();

    private final ScheduledExecutorService watcher = Executors.newScheduledThreadPool(1);

    private final List listeners = new CopyOnWriteArrayList<>();

    

    @PostConstruct

    public void init() {

        // 监控静态资源目录

        String[] resourceDirs = {

            "src/main/resources/static",

            "src/main/resources/public",

            "src/main/resources/templates"

        };

        

        // 初始扫描

        for (String dir : resourceDirs) {

            scanDirectory(Paths.get(dir));

        }

        

        // 定期扫描变更

        watcher.scheduleAtFixedRate(this::checkForChanges, 1, 1, TimeUnit.SECONDS);

        

        log.info("静态资源监视器已启动");

    }

    

    private void scanDirectory(Path directory) {

        if (!Files.exists(directory)) {

            return;

        }

        

        try (Stream paths = Files.walk(directory)) {

            paths.filter(Files::isRegularFile)

                .forEach(path -> {

                    try {

                        long lastModified = Files.getLastModifiedTime(path).toMillis();

                        fileTimestamps.put(path, lastModified);

                    } catch (IOException e) {

                        log.warn("无法获取文件时间戳: {}", path, e);

                    }

                });

        } catch (IOException e) {

            log.error("扫描目录失败: {}", directory, e);

        }

    }

    

    private void checkForChanges() {

        for (Map.Entry entry : new HashSet<>(fileTimestamps.entrySet())) {

            Path path = entry.getKey();

            Long oldTimestamp = entry.getValue();

            

            try {

                if (Files.exists(path)) {

                    long newTimestamp = Files.getLastModifiedTime(path).toMillis();

                    if (newTimestamp != oldTimestamp) {

                        fileTimestamps.put(path, newTimestamp);

                        notifyListeners(path, oldTimestamp, newTimestamp);

                    }

                } else {

                    fileTimestamps.remove(path);

                    notifyListeners(path, oldTimestamp, null);

                }

            } catch (IOException e) {

                log.warn("检查文件变更失败: {}", path, e);

            }

        }

    }

    

    private void notifyListeners(Path path, Long oldTimestamp, Long newTimestamp) {

        String relativePath = getRelativePath(path);

        ResourceChangeEvent event = new ResourceChangeEvent(

            relativePath,

            oldTimestamp,

            newTimestamp,

            newTimestamp == null ? ChangeType.DELETED : ChangeType.MODIFIED

        );

        

        for (ResourceChangeListener listener : listeners) {

            try {

                listener.onResourceChanged(event);

            } catch (Exception e) {

                log.error("监听器处理资源变更事件失败", e);

            }

        }

    }

    

    private String getRelativePath(Path path) {

        Path projectRoot = Paths.get("").toAbsolutePath();

        return projectRoot.relativize(path).toString();

    }

    

    public void addListener(ResourceChangeListener listener) {

        listeners.add(listener);

    }

    

    public void removeListener(ResourceChangeListener listener) {

        listeners.remove(listener);

    }

    

    @PreDestroy

    public void destroy() {

        watcher.shutdownNow();

    }

    

    public interface ResourceChangeListener {

        void onResourceChanged(ResourceChangeEvent event);

    }

    

    public static class ResourceChangeEvent {

        private final String resourcePath;

        private final Long oldTimestamp;

        private final Long newTimestamp;

        private final ChangeType changeType;

        

        // constructor and getters...

        

        public enum ChangeType {

            MODIFIED, DELETED, CREATED

        }

    }

}


// 配置刷新器

@Component

@Slf4j

class ConfigRefresher {

    

    private final ConfigurableEnvironment environment;

    private final Map configFileTimestamps = new HashMap<>();

    

    public ConfigRefresher(ConfigurableEnvironment environment) {

        this.environment = environment;

        initialize();

    }

    

    private void initialize() {

        // 监控的配置文件

        String[] configFiles = {

            "application.yml",

            "application-dev.yml",

            "bootstrap.yml"

        };

        

        for (String configFile : configFiles) {

            monitorConfigFile(configFile);

        }

        

        log.info("配置刷新器已初始化");

    }

    

    private void monitorConfigFile(String fileName) {

        Path configPath = Paths.get("src/main/resources", fileName);

        if (Files.exists(configPath)) {

            try {

                long timestamp = Files.getLastModifiedTime(configPath).toMillis();

                configFileTimestamps.put(fileName, timestamp);

                log.debug("开始监控配置文件: {}", fileName);

            } catch (IOException e) {

                log.warn("无法监控配置文件: {}", fileName, e);

            }

        }

    }

    <"c9.p5k3.org.cn"><"y2.p5k3.org.cn"><"e5.p5k3.org.cn">

    @Scheduled(fixedDelay = 5000)

    public void checkConfigChanges() {

        for (Map.Entry entry : new HashSet<>(configFileTimestamps.entrySet())) {

            String fileName = entry.getKey();

            Long oldTimestamp = entry.getValue();

            

            Path configPath = Paths.get("src/main/resources", fileName);

            if (Files.exists(configPath)) {

                try {

                    long newTimestamp = Files.getLastModifiedTime(configPath).toMillis();

                    if (newTimestamp > oldTimestamp) {

                        log.info("检测到配置文件变更: {}", fileName);

                        configFileTimestamps.put(fileName, newTimestamp);

                        refreshConfiguration(fileName);

                    }

                } catch (IOException e) {

                    log.warn("检查配置文件变更失败: {}", fileName, e);

                }

            }

        }

    }

    

    private void refreshConfiguration(String fileName) {

        try {

            log.info("刷新配置文件: {}", fileName);

            

            // 重新加载配置文件

            Resource resource = new ClassPathResource(fileName);

            YamlPropertySourceLoader loader = new YamlPropertySourceLoader();

            List> propertySources = loader.load(fileName, resource);

            

            if (!propertySources.isEmpty()) {

                PropertySource newSource = propertySources.get(0);

                

                // 更新环境中的属性源

                MutablePropertySources propertySources = environment.getPropertySources();

                String sourceName = fileName.replace(".yml", "");

                

                if (propertySources.contains(sourceName)) {

                    propertySources.replace(sourceName, newSource);

                } else {

                    propertySources.addFirst(newSource);

                }

                

                // 发布配置变更事件

                environment.publishEvent(new EnvironmentChangeEvent(environment, Collections.singleton(fileName)));

                

                log.info("配置文件 {} 刷新完成", fileName);

            }

        } catch (Exception e) {

            log.error("刷新配置文件失败: {}", fileName, e);

        }

    }

}

```


## IDE集成与插件配置


```xml

 

   

     

       

       

       

       

     

   

   

     

   

    

   

   

     

     

     

     

   

    

   

   

     

     

   

    

   

   

     

     

     

     

   

    

   

   

     

     

     

   

 

  

 

 

   

     

       

         

         

         

       

       

     

     

       

         

         

         

         

         

       

       

     

   

 


{

  "java.compile.nullAnalysis.mode": "automatic",

  "java.configuration.updateBuildConfiguration": "automatic",

  "java.autobuild.enabled": true,

  "java.debug.settings.onBuildFailureProceed": true,

  "java.saveActions.organizeImports": true,

  "editor.formatOnSave": true,

  "editor.codeActionsOnSave": {

    "source.organizeImports": "explicit"

  },

  "files.watcherExclude": {

    "**/target/**": true,

    "**/build/**": true,

    "**/node_modules/**": true

  },

  "files.exclude": {

    "**/.git": true,

    "**/.svn": true,

    "**/.hg": true,

    "**/CVS": true,

    "**/.DS_Store": true,

    "**/Thumbs.db": true,

    "**/target": true,

    "**/build": true

  },

  "spring-boot.ls.java.home": "/path/to/jdk17",

  "spring.initializr.serviceUrl": "https://start.spring.io",

  "java.import.gradle.enabled": true,

  "java.import.maven.enabled": true,

  "maven.executable.path": "mvn",

  "java.server.launchMode": "Standard",

  "java.debug.settings.console": "integratedTerminal",

  "java.debug.settings.jdwp.requestTimeout": 30000,

  "java.debug.settings.hotCodeReplace": "auto",

  "java.debug.settings.enableRunDebugCodeLens": true,

  "java.debug.settings.forceBuildBeforeLaunch": true

}


{

  "version": "0.2.0",

  "configurations": [

    {

      "type": "java",

      "name": "Debug Spring Boot (DevTools)",

      "request": "launch",

      "mainClass": "com.example.ProductServiceApplication",

      "projectName": "product-service",

      "args": [

        "--spring.profiles.active=dev",

        "--spring.devtools.restart.enabled=true",

        "--spring.devtools.livereload.enabled=true"

      ],

      "vmArgs": [

        "-Xmx512m",

        "-Xms256m",

        "-Dspring.output.ansi.enabled=ALWAYS",

        "-Dspring.devtools.restart.poll-interval=1000",

        "-Dspring.devtools.restart.quiet-period=400",

        "-Dspring.devtools.restart.trigger-file=.trigger"

      ],

      "env": {

        "SPRING_PROFILES_ACTIVE": "dev",

        "SPRING_DEVTOOLS_RESTART_ENABLED": "true",

        "SPRING_DEVTOOLS_LIVERELOAD_ENABLED": "true"

      },

      "preLaunchTask": "build",

      "postDebugTask": "cleanup",

      "console": "integratedTerminal",

      "internalConsoleOptions": "neverOpen",

      "hotCodeReplace": "auto",

      "sourcePaths": [

        "${workspaceFolder}/src/main/java",

        "${workspaceFolder}/src/main/resources"

      ]

    },

    {

      "type": "java",

      "name": "Hot Reload Debug",

      "request": "attach",

      "hostName": "localhost",

      "port": 8000,

      "projectName": "product-service",

      "sourcePaths": [

        "${workspaceFolder}/src/main/java"

      ],

      "hotCodeReplace": "auto",

      "restart": true

    }

  ],

  "compounds": [

    {

      "name": "Spring Boot Full Debug",

      "configurations": ["Debug Spring Boot (DevTools)", "Hot Reload Debug"]

    }

  ]

}

```


基于Spring Boot的热部署技术,通过DevTools、自定义监视器和IDE集成,实现了高效的开发体验。合理的配置可以平衡重启速度和系统稳定性,结合模板引擎缓存控制、静态资源实时更新和配置热刷新,构建了完整的开发效率提升方案。在实际开发中,应根据项目特点和团队习惯调整配置参数,优化监控策略,确保热部署功能的稳定可靠。


请使用浏览器的分享功能分享到微信等