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.autoconfigure; 018 019import java.io.File; 020import java.net.URL; 021import java.util.List; 022 023import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 024import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 025import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 026import org.springframework.boot.context.properties.EnableConfigurationProperties; 027import org.springframework.boot.devtools.autoconfigure.DevToolsProperties.Restart; 028import org.springframework.boot.devtools.classpath.ClassPathChangedEvent; 029import org.springframework.boot.devtools.classpath.ClassPathFileSystemWatcher; 030import org.springframework.boot.devtools.classpath.ClassPathRestartStrategy; 031import org.springframework.boot.devtools.classpath.PatternClassPathRestartStrategy; 032import org.springframework.boot.devtools.filewatch.FileSystemWatcher; 033import org.springframework.boot.devtools.filewatch.FileSystemWatcherFactory; 034import org.springframework.boot.devtools.livereload.LiveReloadServer; 035import org.springframework.boot.devtools.restart.ConditionalOnInitializedRestarter; 036import org.springframework.boot.devtools.restart.RestartScope; 037import org.springframework.boot.devtools.restart.Restarter; 038import org.springframework.context.ApplicationEvent; 039import org.springframework.context.ApplicationListener; 040import org.springframework.context.annotation.Bean; 041import org.springframework.context.annotation.Configuration; 042import org.springframework.context.event.ContextRefreshedEvent; 043import org.springframework.context.event.GenericApplicationListener; 044import org.springframework.core.ResolvableType; 045import org.springframework.lang.Nullable; 046import org.springframework.util.StringUtils; 047 048/** 049 * {@link EnableAutoConfiguration Auto-configuration} for local development support. 050 * 051 * @author Phillip Webb 052 * @author Andy Wilkinson 053 * @author Vladimir Tsanev 054 * @since 1.3.0 055 */ 056@Configuration 057@ConditionalOnInitializedRestarter 058@EnableConfigurationProperties(DevToolsProperties.class) 059public class LocalDevToolsAutoConfiguration { 060 061 /** 062 * Local LiveReload configuration. 063 */ 064 @Configuration 065 @ConditionalOnProperty(prefix = "spring.devtools.livereload", name = "enabled", matchIfMissing = true) 066 static class LiveReloadConfiguration { 067 068 private final DevToolsProperties properties; 069 070 LiveReloadConfiguration(DevToolsProperties properties) { 071 this.properties = properties; 072 } 073 074 @Bean 075 @RestartScope 076 @ConditionalOnMissingBean 077 public LiveReloadServer liveReloadServer() { 078 return new LiveReloadServer(this.properties.getLivereload().getPort(), 079 Restarter.getInstance().getThreadFactory()); 080 } 081 082 @Bean 083 public OptionalLiveReloadServer optionalLiveReloadServer( 084 LiveReloadServer liveReloadServer) { 085 return new OptionalLiveReloadServer(liveReloadServer); 086 } 087 088 @Bean 089 public LiveReloadServerEventListener liveReloadServerEventListener( 090 OptionalLiveReloadServer liveReloadServer) { 091 return new LiveReloadServerEventListener(liveReloadServer); 092 } 093 094 } 095 096 /** 097 * Local Restart Configuration. 098 */ 099 @Configuration 100 @ConditionalOnProperty(prefix = "spring.devtools.restart", name = "enabled", matchIfMissing = true) 101 static class RestartConfiguration 102 implements ApplicationListener<ClassPathChangedEvent> { 103 104 private final DevToolsProperties properties; 105 106 RestartConfiguration(DevToolsProperties properties) { 107 this.properties = properties; 108 } 109 110 @Override 111 public void onApplicationEvent(ClassPathChangedEvent event) { 112 if (event.isRestartRequired()) { 113 Restarter.getInstance().restart( 114 new FileWatchingFailureHandler(fileSystemWatcherFactory())); 115 } 116 } 117 118 @Bean 119 @ConditionalOnMissingBean 120 public ClassPathFileSystemWatcher classPathFileSystemWatcher() { 121 URL[] urls = Restarter.getInstance().getInitialUrls(); 122 ClassPathFileSystemWatcher watcher = new ClassPathFileSystemWatcher( 123 fileSystemWatcherFactory(), classPathRestartStrategy(), urls); 124 watcher.setStopWatcherOnRestart(true); 125 return watcher; 126 } 127 128 @Bean 129 @ConditionalOnMissingBean 130 public ClassPathRestartStrategy classPathRestartStrategy() { 131 return new PatternClassPathRestartStrategy( 132 this.properties.getRestart().getAllExclude()); 133 } 134 135 @Bean 136 public HateoasObjenesisCacheDisabler hateoasObjenesisCacheDisabler() { 137 return new HateoasObjenesisCacheDisabler(); 138 } 139 140 @Bean 141 public FileSystemWatcherFactory fileSystemWatcherFactory() { 142 return this::newFileSystemWatcher; 143 } 144 145 @Bean 146 @ConditionalOnProperty(prefix = "spring.devtools.restart", name = "log-condition-evaluation-delta", matchIfMissing = true) 147 public ConditionEvaluationDeltaLoggingListener conditionEvaluationDeltaLoggingListener() { 148 return new ConditionEvaluationDeltaLoggingListener(); 149 } 150 151 private FileSystemWatcher newFileSystemWatcher() { 152 Restart restartProperties = this.properties.getRestart(); 153 FileSystemWatcher watcher = new FileSystemWatcher(true, 154 restartProperties.getPollInterval(), 155 restartProperties.getQuietPeriod()); 156 String triggerFile = restartProperties.getTriggerFile(); 157 if (StringUtils.hasLength(triggerFile)) { 158 watcher.setTriggerFilter(new TriggerFileFilter(triggerFile)); 159 } 160 List<File> additionalPaths = restartProperties.getAdditionalPaths(); 161 for (File path : additionalPaths) { 162 watcher.addSourceFolder(path.getAbsoluteFile()); 163 } 164 return watcher; 165 } 166 167 } 168 169 static class LiveReloadServerEventListener implements GenericApplicationListener { 170 171 private final OptionalLiveReloadServer liveReloadServer; 172 173 LiveReloadServerEventListener(OptionalLiveReloadServer liveReloadServer) { 174 this.liveReloadServer = liveReloadServer; 175 } 176 177 @Override 178 public boolean supportsEventType(ResolvableType eventType) { 179 Class<?> type = eventType.getRawClass(); 180 if (type == null) { 181 return false; 182 } 183 return ContextRefreshedEvent.class.isAssignableFrom(type) 184 || ClassPathChangedEvent.class.isAssignableFrom(type); 185 } 186 187 @Override 188 public boolean supportsSourceType(@Nullable Class<?> sourceType) { 189 return true; 190 } 191 192 @Override 193 public void onApplicationEvent(ApplicationEvent event) { 194 if (event instanceof ContextRefreshedEvent 195 || (event instanceof ClassPathChangedEvent 196 && !((ClassPathChangedEvent) event).isRestartRequired())) { 197 this.liveReloadServer.triggerReload(); 198 } 199 } 200 201 @Override 202 public int getOrder() { 203 return 0; 204 } 205 206 } 207 208}