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