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}