복붙노트

[SPRING] 항아리를 실행할 때 스프링 부팅 DevTools 강제 실행

SPRING

항아리를 실행할 때 스프링 부팅 DevTools 강제 실행

Docker 컨테이너에서 스프링 부트 응용 프로그램을 실행하여 원격 LiveReload를 사용하려고합니다.

스프링 부트 DevTools 문서에는

DevTools를 활성화 할 수있는 방법이 있습니까?

해결법

  1. ==============================

    1.솔루션은 스케치이므로, 그것이 당신에게 좋은지 결정합니다. 최종 해결책은이 게시물의 마지막 부분입니다.

    솔루션은 스케치이므로, 그것이 당신에게 좋은지 결정합니다. 최종 해결책은이 게시물의 마지막 부분입니다.

    솔루션을 포기하는 것이 어렵습니다. 먼저 내가 어떻게 얻었는지 설명해야합니다. 첫째, IDE 외부에서 실행될 때 livereload가 활성화되지 않는 이유는 다음과 같습니다.

    무슨 일이 일어나는지 알기.

    (1) LocalDevToolsAutoConfiguration 구성은 @ ConditionalOnInitializedRestarter / OnInitializedRestarterCondition에서 조건부입니다.

        @Configuration
        @ConditionalOnInitializedRestarter
        @EnableConfigurationProperties(DevToolsProperties.class)
        public class LocalDevToolsAutoConfiguration {
        ...
    

    (2) OnInitializedRestarterCondition은 Restarter 인스턴스를 검색하여 null인지 확인합니다. 그렇지 않으면 restarter.getInitialUrls ()는 null을 반환합니다. 내 경우에는 restarter.getInitialUrls ()가 null을 반환했습니다.

    class OnInitializedRestarterCondition extends SpringBootCondition {
        @Override
        public ConditionOutcome getMatchOutcome(ConditionContext context,
                AnnotatedTypeMetadata metadata) {
            Restarter restarter = getRestarter();
            if (restarter == null) {
                return ConditionOutcome.noMatch("Restarter unavailable");
            }
            if (restarter.getInitialUrls() == null) {
                return ConditionOutcome.noMatch("Restarter initialized without URLs");
            }
            return ConditionOutcome.match("Restarter available and initialized");
        }
    

    (3) initialUrls는 DefaultRestartInitializer.getInitialUrls (..)를 통해 Restarter.class에서 초기화됩니다.

    class Restarter{
        this.initialUrls = initializer.getInitialUrls(thread);
    }
    
    class DefaultRestartInitializer{
        @Override
        public URL[] getInitialUrls(Thread thread) {
            if (!isMain(thread)) {
                return null;
            }
            for (StackTraceElement element : thread.getStackTrace()) {
                if (isSkippedStackElement(element)) {
                    return null;
                }
            }
            return getUrls(thread);
        }
    
        protected boolean isMain(Thread thread) {
        return thread.getName().equals("main") && thread.getContextClassLoader()
                .getClass().getName().contains("AppClassLoader");
        }
    }
    

    이클립스에서 실행될 때만 유효하다 (아마도 IDE? + spring boot-maven-plugin 일까?). 요약하자면:

    해결책 :

    자신의 AppClassLoader 클래스 로더를 생성하여 클래스 로더 이름이 "AppClassLoader"인지 확인하십시오. 스프링 부트 메인의 맨 처음 줄에서 클래스 로더를 다음과 같이 바꿉니다.

    URLClassLoader originalClassLoader = (URLClassLoader)Thread.currentThread().getContextClassLoader();
    Thread.currentThread().setContextClassLoader(new CustomAppClassLoader(originalClassLoader));
    

    우리의 커스텀 클래스 로더 구현은 단순히 원래 클래스로 위임합니다 :

    public class CustomAppClassLoader extends URLClassLoader{
    
    private URLClassLoader contextClassLoader;
    
    public CustomAppClassLoader(URLClassLoader contextClassLoader) {
        super(contextClassLoader.getURLs(), contextClassLoader.getParent());
        this.contextClassLoader = contextClassLoader;
    }
    
    public int hashCode() {
        return contextClassLoader.hashCode();
    }
    
    public boolean equals(Object obj) {
        return contextClassLoader.equals(obj);
    }
    
    public InputStream getResourceAsStream(String name) {
        return contextClassLoader.getResourceAsStream(name);
    }
    
    public String toString() {
        return contextClassLoader.toString();
    }
    
    public void close() throws IOException {
        contextClassLoader.close();
    }
    
    public URL[] getURLs() {
        return contextClassLoader.getURLs();
    }
    
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return contextClassLoader.loadClass(name);
    }
    
    public URL findResource(String name) {
        return contextClassLoader.findResource(name);
    }
    
    public Enumeration<URL> findResources(String name) throws IOException {
        return contextClassLoader.findResources(name);
    }
    
    public URL getResource(String name) {
        return contextClassLoader.getResource(name);
    }
    
    public Enumeration<URL> getResources(String name) throws IOException {
        return contextClassLoader.getResources(name);
    }
    
    public void setDefaultAssertionStatus(boolean enabled) {
        contextClassLoader.setDefaultAssertionStatus(enabled);
    }
    
    public void setPackageAssertionStatus(String packageName, boolean enabled) {
        contextClassLoader.setPackageAssertionStatus(packageName, enabled);
    }
    
    public void setClassAssertionStatus(String className, boolean enabled) {
        contextClassLoader.setClassAssertionStatus(className, enabled);
    }
    
    public void clearAssertionStatus() {
        contextClassLoader.clearAssertionStatus();
    }
    

    }

    CustomAppClassLoader를 최대한 많이 (즉, 원래 클래스 로더에서 'url'과 'parent'를 사용하여) 호출 할 수 있도록 구성했지만 어쨌든 모든 public 메소드를 원래 클래스 로더에 위임합니다.

    그것은 나를 위해 작동합니다. 자, 더 좋은 질문은 내가 정말로 이것을 원한다는 것입니다 :)

    더 나은 옵션

    나는 스프링 클라우드의 RestartEndpoint가 더 좋은 선택이라고 생각한다. 스프링 부트 애플리케이션을 프로그램 적으로 재시작한다. 그러나 RestartEndPoint는 클래스 경로의 변경 사항 감지를 처리하지 않습니다.

  2. ==============================

    2.다음과 같이 devtools가 리 패키징 된 아카이브에 포함되어 있는지 확인하십시오.

    다음과 같이 devtools가 리 패키징 된 아카이브에 포함되어 있는지 확인하십시오.

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludeDevtools>false</excludeDevtools>
                </configuration>
            </plugin>
        </plugins>
    </build>
    
  3. from https://stackoverflow.com/questions/35591423/force-enable-spring-boot-devtools-when-running-jar by cc-by-sa and MIT license