001/* 002 * Copyright 2012-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.boot.devtools.restart; 018 019import java.net.URL; 020import java.util.Collections; 021import java.util.LinkedHashSet; 022import java.util.Set; 023 024/** 025 * Default {@link RestartInitializer} that only enable initial restart when running a 026 * standard "main" method. Skips initialization when running "fat" jars (included 027 * exploded) or when running from a test. 028 * 029 * @author Phillip Webb 030 * @author Andy Wilkinson 031 * @since 1.3.0 032 */ 033public class DefaultRestartInitializer implements RestartInitializer { 034 035 private static final Set<String> SKIPPED_STACK_ELEMENTS; 036 037 static { 038 Set<String> skipped = new LinkedHashSet<>(); 039 skipped.add("org.junit.runners."); 040 skipped.add("org.junit.platform."); 041 skipped.add("org.springframework.boot.test."); 042 skipped.add("cucumber.runtime."); 043 SKIPPED_STACK_ELEMENTS = Collections.unmodifiableSet(skipped); 044 } 045 046 @Override 047 public URL[] getInitialUrls(Thread thread) { 048 if (!isMain(thread)) { 049 return null; 050 } 051 for (StackTraceElement element : thread.getStackTrace()) { 052 if (isSkippedStackElement(element)) { 053 return null; 054 } 055 } 056 return getUrls(thread); 057 } 058 059 /** 060 * Returns if the thread is for a main invocation. By default checks the name of the 061 * thread and the context classloader. 062 * @param thread the thread to check 063 * @return {@code true} if the thread is a main invocation 064 */ 065 protected boolean isMain(Thread thread) { 066 return thread.getName().equals("main") && thread.getContextClassLoader() 067 .getClass().getName().contains("AppClassLoader"); 068 } 069 070 /** 071 * Checks if a specific {@link StackTraceElement} should cause the initializer to be 072 * skipped. 073 * @param element the stack element to check 074 * @return {@code true} if the stack element means that the initializer should be 075 * skipped 076 */ 077 private boolean isSkippedStackElement(StackTraceElement element) { 078 for (String skipped : SKIPPED_STACK_ELEMENTS) { 079 if (element.getClassName().startsWith(skipped)) { 080 return true; 081 } 082 } 083 return false; 084 } 085 086 /** 087 * Return the URLs that should be used with initialization. 088 * @param thread the source thread 089 * @return the URLs 090 */ 091 protected URL[] getUrls(Thread thread) { 092 return ChangeableUrls.fromClassLoader(thread.getContextClassLoader()).toArray(); 093 } 094 095}