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.beans.Introspector; 020import java.lang.Thread.UncaughtExceptionHandler; 021import java.lang.reflect.Field; 022import java.net.URL; 023import java.util.Arrays; 024import java.util.Collection; 025import java.util.HashMap; 026import java.util.LinkedHashSet; 027import java.util.LinkedList; 028import java.util.List; 029import java.util.Map; 030import java.util.Set; 031import java.util.concurrent.BlockingDeque; 032import java.util.concurrent.Callable; 033import java.util.concurrent.CopyOnWriteArrayList; 034import java.util.concurrent.LinkedBlockingDeque; 035import java.util.concurrent.ThreadFactory; 036import java.util.concurrent.locks.Lock; 037import java.util.concurrent.locks.ReentrantLock; 038 039import org.apache.commons.logging.Log; 040import org.apache.commons.logging.LogFactory; 041 042import org.springframework.beans.CachedIntrospectionResults; 043import org.springframework.beans.factory.ObjectFactory; 044import org.springframework.boot.SpringApplication; 045import org.springframework.boot.devtools.restart.FailureHandler.Outcome; 046import org.springframework.boot.devtools.restart.classloader.ClassLoaderFiles; 047import org.springframework.boot.devtools.restart.classloader.RestartClassLoader; 048import org.springframework.boot.logging.DeferredLog; 049import org.springframework.boot.system.JavaVersion; 050import org.springframework.cglib.core.ClassNameReader; 051import org.springframework.context.ConfigurableApplicationContext; 052import org.springframework.context.support.GenericApplicationContext; 053import org.springframework.core.ResolvableType; 054import org.springframework.core.annotation.AnnotationUtils; 055import org.springframework.core.io.ResourceLoader; 056import org.springframework.util.Assert; 057import org.springframework.util.ReflectionUtils; 058 059/** 060 * Allows a running application to be restarted with an updated classpath. The restarter 061 * works by creating a new application ClassLoader that is split into two parts. The top 062 * part contains static URLs that don't change (for example 3rd party libraries and Spring 063 * Boot itself) and the bottom part contains URLs where classes and resources might be 064 * updated. 065 * <p> 066 * The Restarter should be {@link #initialize(String[]) initialized} early to ensure that 067 * classes are loaded multiple times. Mostly the {@link RestartApplicationListener} can be 068 * relied upon to perform initialization, however, you may need to call 069 * {@link #initialize(String[])} directly if your SpringApplication arguments are not 070 * identical to your main method arguments. 071 * <p> 072 * By default, applications running in an IDE (i.e. those not packaged as "fat jars") will 073 * automatically detect URLs that can change. It's also possible to manually configure 074 * URLs or class file updates for remote restart scenarios. 075 * 076 * @author Phillip Webb 077 * @author Andy Wilkinson 078 * @since 1.3.0 079 * @see RestartApplicationListener 080 * @see #initialize(String[]) 081 * @see #getInstance() 082 * @see #restart() 083 */ 084public class Restarter { 085 086 private static final Object INSTANCE_MONITOR = new Object(); 087 088 private static final String[] NO_ARGS = {}; 089 090 private static Restarter instance; 091 092 private final Set<URL> urls = new LinkedHashSet<>(); 093 094 private final ClassLoaderFiles classLoaderFiles = new ClassLoaderFiles(); 095 096 private final Map<String, Object> attributes = new HashMap<>(); 097 098 private final BlockingDeque<LeakSafeThread> leakSafeThreads = new LinkedBlockingDeque<>(); 099 100 private final Lock stopLock = new ReentrantLock(); 101 102 private final Object monitor = new Object(); 103 104 private Log logger = new DeferredLog(); 105 106 private final boolean forceReferenceCleanup; 107 108 private boolean enabled = true; 109 110 private URL[] initialUrls; 111 112 private final String mainClassName; 113 114 private final ClassLoader applicationClassLoader; 115 116 private final String[] args; 117 118 private final UncaughtExceptionHandler exceptionHandler; 119 120 private boolean finished = false; 121 122 private final List<ConfigurableApplicationContext> rootContexts = new CopyOnWriteArrayList<>(); 123 124 /** 125 * Internal constructor to create a new {@link Restarter} instance. 126 * @param thread the source thread 127 * @param args the application arguments 128 * @param forceReferenceCleanup if soft/weak reference cleanup should be forced 129 * @param initializer the restart initializer 130 * @see #initialize(String[]) 131 */ 132 protected Restarter(Thread thread, String[] args, boolean forceReferenceCleanup, 133 RestartInitializer initializer) { 134 Assert.notNull(thread, "Thread must not be null"); 135 Assert.notNull(args, "Args must not be null"); 136 Assert.notNull(initializer, "Initializer must not be null"); 137 if (this.logger.isDebugEnabled()) { 138 this.logger.debug("Creating new Restarter for thread " + thread); 139 } 140 SilentExitExceptionHandler.setup(thread); 141 this.forceReferenceCleanup = forceReferenceCleanup; 142 this.initialUrls = initializer.getInitialUrls(thread); 143 this.mainClassName = getMainClassName(thread); 144 this.applicationClassLoader = thread.getContextClassLoader(); 145 this.args = args; 146 this.exceptionHandler = thread.getUncaughtExceptionHandler(); 147 this.leakSafeThreads.add(new LeakSafeThread()); 148 } 149 150 private String getMainClassName(Thread thread) { 151 try { 152 return new MainMethod(thread).getDeclaringClassName(); 153 } 154 catch (Exception ex) { 155 return null; 156 } 157 } 158 159 protected void initialize(boolean restartOnInitialize) { 160 preInitializeLeakyClasses(); 161 if (this.initialUrls != null) { 162 this.urls.addAll(Arrays.asList(this.initialUrls)); 163 if (restartOnInitialize) { 164 this.logger.debug("Immediately restarting application"); 165 immediateRestart(); 166 } 167 } 168 } 169 170 private void immediateRestart() { 171 try { 172 getLeakSafeThread().callAndWait(() -> { 173 start(FailureHandler.NONE); 174 cleanupCaches(); 175 return null; 176 }); 177 } 178 catch (Exception ex) { 179 this.logger.warn("Unable to initialize restarter", ex); 180 } 181 SilentExitExceptionHandler.exitCurrentThread(); 182 } 183 184 /** 185 * CGLIB has a private exception field which needs to initialized early to ensure that 186 * the stacktrace doesn't retain a reference to the RestartClassLoader. 187 */ 188 private void preInitializeLeakyClasses() { 189 try { 190 Class<?> readerClass = ClassNameReader.class; 191 Field field = readerClass.getDeclaredField("EARLY_EXIT"); 192 field.setAccessible(true); 193 ((Throwable) field.get(null)).fillInStackTrace(); 194 } 195 catch (Exception ex) { 196 this.logger.warn("Unable to pre-initialize classes", ex); 197 } 198 } 199 200 /** 201 * Set if restart support is enabled. 202 * @param enabled if restart support is enabled 203 */ 204 private void setEnabled(boolean enabled) { 205 this.enabled = enabled; 206 } 207 208 /** 209 * Add additional URLs to be includes in the next restart. 210 * @param urls the urls to add 211 */ 212 public void addUrls(Collection<URL> urls) { 213 Assert.notNull(urls, "Urls must not be null"); 214 this.urls.addAll(urls); 215 } 216 217 /** 218 * Add additional {@link ClassLoaderFiles} to be included in the next restart. 219 * @param classLoaderFiles the files to add 220 */ 221 public void addClassLoaderFiles(ClassLoaderFiles classLoaderFiles) { 222 Assert.notNull(classLoaderFiles, "ClassLoaderFiles must not be null"); 223 this.classLoaderFiles.addAll(classLoaderFiles); 224 } 225 226 /** 227 * Return a {@link ThreadFactory} that can be used to create leak safe threads. 228 * @return a leak safe thread factory 229 */ 230 public ThreadFactory getThreadFactory() { 231 return new LeakSafeThreadFactory(); 232 } 233 234 /** 235 * Restart the running application. 236 */ 237 public void restart() { 238 restart(FailureHandler.NONE); 239 } 240 241 /** 242 * Restart the running application. 243 * @param failureHandler a failure handler to deal with application that doesn't start 244 */ 245 public void restart(FailureHandler failureHandler) { 246 if (!this.enabled) { 247 this.logger.debug("Application restart is disabled"); 248 return; 249 } 250 this.logger.debug("Restarting application"); 251 getLeakSafeThread().call(() -> { 252 Restarter.this.stop(); 253 Restarter.this.start(failureHandler); 254 return null; 255 }); 256 } 257 258 /** 259 * Start the application. 260 * @param failureHandler a failure handler for application that won't start 261 * @throws Exception in case of errors 262 */ 263 protected void start(FailureHandler failureHandler) throws Exception { 264 do { 265 Throwable error = doStart(); 266 if (error == null) { 267 return; 268 } 269 if (failureHandler.handle(error) == Outcome.ABORT) { 270 return; 271 } 272 } 273 while (true); 274 } 275 276 private Throwable doStart() throws Exception { 277 Assert.notNull(this.mainClassName, "Unable to find the main class to restart"); 278 URL[] urls = this.urls.toArray(new URL[0]); 279 ClassLoaderFiles updatedFiles = new ClassLoaderFiles(this.classLoaderFiles); 280 ClassLoader classLoader = new RestartClassLoader(this.applicationClassLoader, 281 urls, updatedFiles, this.logger); 282 if (this.logger.isDebugEnabled()) { 283 this.logger.debug("Starting application " + this.mainClassName + " with URLs " 284 + Arrays.asList(urls)); 285 } 286 return relaunch(classLoader); 287 } 288 289 /** 290 * Relaunch the application using the specified classloader. 291 * @param classLoader the classloader to use 292 * @return any exception that caused the launch to fail or {@code null} 293 * @throws Exception in case of errors 294 */ 295 protected Throwable relaunch(ClassLoader classLoader) throws Exception { 296 RestartLauncher launcher = new RestartLauncher(classLoader, this.mainClassName, 297 this.args, this.exceptionHandler); 298 launcher.start(); 299 launcher.join(); 300 return launcher.getError(); 301 } 302 303 /** 304 * Stop the application. 305 * @throws Exception in case of errors 306 */ 307 protected void stop() throws Exception { 308 this.logger.debug("Stopping application"); 309 this.stopLock.lock(); 310 try { 311 for (ConfigurableApplicationContext context : this.rootContexts) { 312 context.close(); 313 this.rootContexts.remove(context); 314 } 315 cleanupCaches(); 316 if (this.forceReferenceCleanup) { 317 forceReferenceCleanup(); 318 } 319 } 320 finally { 321 this.stopLock.unlock(); 322 } 323 System.gc(); 324 System.runFinalization(); 325 } 326 327 private void cleanupCaches() throws Exception { 328 Introspector.flushCaches(); 329 cleanupKnownCaches(); 330 } 331 332 private void cleanupKnownCaches() throws Exception { 333 // Whilst not strictly necessary it helps to cleanup soft reference caches 334 // early rather than waiting for memory limits to be reached 335 ResolvableType.clearCache(); 336 cleanCachedIntrospectionResultsCache(); 337 ReflectionUtils.clearCache(); 338 clearAnnotationUtilsCache(); 339 if (!JavaVersion.getJavaVersion().isEqualOrNewerThan(JavaVersion.NINE)) { 340 clear("com.sun.naming.internal.ResourceManager", "propertiesCache"); 341 } 342 } 343 344 private void cleanCachedIntrospectionResultsCache() throws Exception { 345 clear(CachedIntrospectionResults.class, "acceptedClassLoaders"); 346 clear(CachedIntrospectionResults.class, "strongClassCache"); 347 clear(CachedIntrospectionResults.class, "softClassCache"); 348 } 349 350 private void clearAnnotationUtilsCache() throws Exception { 351 try { 352 AnnotationUtils.clearCache(); 353 } 354 catch (Throwable ex) { 355 clear(AnnotationUtils.class, "findAnnotationCache"); 356 clear(AnnotationUtils.class, "annotatedInterfaceCache"); 357 } 358 } 359 360 private void clear(String className, String fieldName) { 361 try { 362 clear(Class.forName(className), fieldName); 363 } 364 catch (Exception ex) { 365 if (this.logger.isDebugEnabled()) { 366 this.logger.debug("Unable to clear field " + className + " " + fieldName, 367 ex); 368 } 369 } 370 } 371 372 private void clear(Class<?> type, String fieldName) throws Exception { 373 try { 374 Field field = type.getDeclaredField(fieldName); 375 field.setAccessible(true); 376 Object instance = field.get(null); 377 if (instance instanceof Set) { 378 ((Set<?>) instance).clear(); 379 } 380 if (instance instanceof Map) { 381 ((Map<?, ?>) instance).keySet().removeIf(this::isFromRestartClassLoader); 382 } 383 } 384 catch (Exception ex) { 385 if (this.logger.isDebugEnabled()) { 386 this.logger.debug("Unable to clear field " + type + " " + fieldName, ex); 387 } 388 } 389 } 390 391 private boolean isFromRestartClassLoader(Object object) { 392 return (object instanceof Class 393 && ((Class<?>) object).getClassLoader() instanceof RestartClassLoader); 394 } 395 396 /** 397 * Cleanup any soft/weak references by forcing an {@link OutOfMemoryError} error. 398 */ 399 private void forceReferenceCleanup() { 400 try { 401 final List<long[]> memory = new LinkedList<>(); 402 while (true) { 403 memory.add(new long[102400]); 404 } 405 } 406 catch (OutOfMemoryError ex) { 407 // Expected 408 } 409 } 410 411 /** 412 * Called to finish {@link Restarter} initialization when application logging is 413 * available. 414 */ 415 void finish() { 416 synchronized (this.monitor) { 417 if (!isFinished()) { 418 this.logger = DeferredLog.replay(this.logger, 419 LogFactory.getLog(getClass())); 420 this.finished = true; 421 } 422 } 423 } 424 425 boolean isFinished() { 426 synchronized (this.monitor) { 427 return this.finished; 428 } 429 } 430 431 void prepare(ConfigurableApplicationContext applicationContext) { 432 if (applicationContext != null && applicationContext.getParent() != null) { 433 return; 434 } 435 if (applicationContext instanceof GenericApplicationContext) { 436 prepare((GenericApplicationContext) applicationContext); 437 } 438 this.rootContexts.add(applicationContext); 439 } 440 441 void remove(ConfigurableApplicationContext applicationContext) { 442 if (applicationContext != null) { 443 this.rootContexts.remove(applicationContext); 444 } 445 } 446 447 private void prepare(GenericApplicationContext applicationContext) { 448 ResourceLoader resourceLoader = new ClassLoaderFilesResourcePatternResolver( 449 applicationContext, this.classLoaderFiles); 450 applicationContext.setResourceLoader(resourceLoader); 451 } 452 453 private LeakSafeThread getLeakSafeThread() { 454 try { 455 return this.leakSafeThreads.takeFirst(); 456 } 457 catch (InterruptedException ex) { 458 Thread.currentThread().interrupt(); 459 throw new IllegalStateException(ex); 460 } 461 } 462 463 public Object getOrAddAttribute(String name, final ObjectFactory<?> objectFactory) { 464 synchronized (this.attributes) { 465 if (!this.attributes.containsKey(name)) { 466 this.attributes.put(name, objectFactory.getObject()); 467 } 468 return this.attributes.get(name); 469 } 470 } 471 472 public Object removeAttribute(String name) { 473 synchronized (this.attributes) { 474 return this.attributes.remove(name); 475 } 476 } 477 478 /** 479 * Return the initial set of URLs as configured by the {@link RestartInitializer}. 480 * @return the initial URLs or {@code null} 481 */ 482 public URL[] getInitialUrls() { 483 return this.initialUrls; 484 } 485 486 /** 487 * Initialize and disable restart support. 488 */ 489 public static void disable() { 490 initialize(NO_ARGS, false, RestartInitializer.NONE); 491 getInstance().setEnabled(false); 492 } 493 494 /** 495 * Initialize restart support. See 496 * {@link #initialize(String[], boolean, RestartInitializer)} for details. 497 * @param args main application arguments 498 * @see #initialize(String[], boolean, RestartInitializer) 499 */ 500 public static void initialize(String[] args) { 501 initialize(args, false, new DefaultRestartInitializer()); 502 } 503 504 /** 505 * Initialize restart support. See 506 * {@link #initialize(String[], boolean, RestartInitializer)} for details. 507 * @param args main application arguments 508 * @param initializer the restart initializer 509 * @see #initialize(String[], boolean, RestartInitializer) 510 */ 511 public static void initialize(String[] args, RestartInitializer initializer) { 512 initialize(args, false, initializer, true); 513 } 514 515 /** 516 * Initialize restart support. See 517 * {@link #initialize(String[], boolean, RestartInitializer)} for details. 518 * @param args main application arguments 519 * @param forceReferenceCleanup if forcing of soft/weak reference should happen on 520 * @see #initialize(String[], boolean, RestartInitializer) 521 */ 522 public static void initialize(String[] args, boolean forceReferenceCleanup) { 523 initialize(args, forceReferenceCleanup, new DefaultRestartInitializer()); 524 } 525 526 /** 527 * Initialize restart support. See 528 * {@link #initialize(String[], boolean, RestartInitializer, boolean)} for details. 529 * @param args main application arguments 530 * @param forceReferenceCleanup if forcing of soft/weak reference should happen on 531 * @param initializer the restart initializer 532 * @see #initialize(String[], boolean, RestartInitializer) 533 */ 534 public static void initialize(String[] args, boolean forceReferenceCleanup, 535 RestartInitializer initializer) { 536 initialize(args, forceReferenceCleanup, initializer, true); 537 } 538 539 /** 540 * Initialize restart support for the current application. Called automatically by 541 * {@link RestartApplicationListener} but can also be called directly if main 542 * application arguments are not the same as those passed to the 543 * {@link SpringApplication}. 544 * @param args main application arguments 545 * @param forceReferenceCleanup if forcing of soft/weak reference should happen on 546 * each restart. This will slow down restarts and is intended primarily for testing 547 * @param initializer the restart initializer 548 * @param restartOnInitialize if the restarter should be restarted immediately when 549 * the {@link RestartInitializer} returns non {@code null} results 550 */ 551 public static void initialize(String[] args, boolean forceReferenceCleanup, 552 RestartInitializer initializer, boolean restartOnInitialize) { 553 Restarter localInstance = null; 554 synchronized (INSTANCE_MONITOR) { 555 if (instance == null) { 556 localInstance = new Restarter(Thread.currentThread(), args, 557 forceReferenceCleanup, initializer); 558 instance = localInstance; 559 } 560 } 561 if (localInstance != null) { 562 localInstance.initialize(restartOnInitialize); 563 } 564 } 565 566 /** 567 * Return the active {@link Restarter} instance. Cannot be called before 568 * {@link #initialize(String[]) initialization}. 569 * @return the restarter 570 */ 571 public static Restarter getInstance() { 572 synchronized (INSTANCE_MONITOR) { 573 Assert.state(instance != null, "Restarter has not been initialized"); 574 return instance; 575 } 576 } 577 578 /** 579 * Set the restarter instance (useful for testing). 580 * @param instance the instance to set 581 */ 582 static void setInstance(Restarter instance) { 583 synchronized (INSTANCE_MONITOR) { 584 Restarter.instance = instance; 585 } 586 } 587 588 /** 589 * Clear the instance. Primarily provided for tests and not usually used in 590 * application code. 591 */ 592 public static void clearInstance() { 593 synchronized (INSTANCE_MONITOR) { 594 instance = null; 595 } 596 } 597 598 /** 599 * Thread that is created early so not to retain the {@link RestartClassLoader}. 600 */ 601 private class LeakSafeThread extends Thread { 602 603 private Callable<?> callable; 604 605 private Object result; 606 607 LeakSafeThread() { 608 setDaemon(false); 609 } 610 611 public void call(Callable<?> callable) { 612 this.callable = callable; 613 start(); 614 } 615 616 @SuppressWarnings("unchecked") 617 public <V> V callAndWait(Callable<V> callable) { 618 this.callable = callable; 619 start(); 620 try { 621 join(); 622 return (V) this.result; 623 } 624 catch (InterruptedException ex) { 625 Thread.currentThread().interrupt(); 626 throw new IllegalStateException(ex); 627 } 628 } 629 630 @Override 631 public void run() { 632 // We are safe to refresh the ActionThread (and indirectly call 633 // AccessController.getContext()) since our stack doesn't include the 634 // RestartClassLoader 635 try { 636 Restarter.this.leakSafeThreads.put(new LeakSafeThread()); 637 this.result = this.callable.call(); 638 } 639 catch (Exception ex) { 640 ex.printStackTrace(); 641 System.exit(1); 642 } 643 } 644 645 } 646 647 /** 648 * {@link ThreadFactory} that creates a leak safe thread. 649 */ 650 private class LeakSafeThreadFactory implements ThreadFactory { 651 652 @Override 653 public Thread newThread(Runnable runnable) { 654 return getLeakSafeThread().callAndWait(() -> { 655 Thread thread = new Thread(runnable); 656 thread.setContextClassLoader(Restarter.this.applicationClassLoader); 657 return thread; 658 }); 659 } 660 661 } 662 663}