Skip to content

Commit 1e09ff0

Browse files
committed
Optimize message bundling scanning
Searching for message bundles *.properties was scanning the whole classpath which took 2-3 seconds of startup time. This change optimises the scanning to only find bundles relative to the application class. A "searchClasspath" property has been added to the "messageSource" bean to restore the previous behaviour but in 95% of cases this will not be necessary.
1 parent 4853b6f commit 1e09ff0

File tree

6 files changed

+175
-7
lines changed

6 files changed

+175
-7
lines changed

grails-core/src/main/groovy/grails/boot/config/GrailsApplicationPostProcessor.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ class GrailsApplicationPostProcessor implements BeanDefinitionRegistryPostProces
7171
this.applicationClass = null
7272
}
7373
this.classes = classes != null ? classes : [] as Class[]
74-
grailsApplication = new DefaultGrailsApplication()
74+
grailsApplication = applicationClass != null ? new DefaultGrailsApplication(applicationClass) : new DefaultGrailsApplication()
7575
pluginManager = new DefaultGrailsPluginManager(grailsApplication)
7676
if(applicationContext != null) {
7777
setApplicationContext(applicationContext)

grails-core/src/main/groovy/grails/core/DefaultGrailsApplication.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,22 @@ public class DefaultGrailsApplication extends AbstractGrailsApplication implemen
9191
protected Class<?>[] allArtefactClassesArray;
9292
protected Resource[] resources;
9393
protected boolean initialised = false;
94+
protected GrailsApplicationClass applicationClass;
9495

9596
/**
9697
* Creates a new empty Grails application.
9798
*/
9899
public DefaultGrailsApplication() {
99100
this(new GroovyClassLoader());
100101
}
102+
103+
/**
104+
* Creates a new empty Grails application.
105+
*/
106+
public DefaultGrailsApplication(GrailsApplicationClass applicationClass) {
107+
this(new GroovyClassLoader());
108+
this.applicationClass = applicationClass;
109+
}
101110

102111
public DefaultGrailsApplication(ClassLoader classLoader) {
103112
super();
@@ -168,6 +177,13 @@ public DefaultGrailsApplication(org.grails.io.support.Resource[] resources) {
168177
}
169178
}
170179

180+
/**
181+
* @return The application class
182+
*/
183+
public GrailsApplicationClass getApplicationClass() {
184+
return applicationClass;
185+
}
186+
171187
/**
172188
* Initialises the default set of ArtefactHandler instances.
173189
*
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package org.grails.core.support.internal.tools;
2+
3+
import grails.io.IOUtils;
4+
import grails.util.BuildSettings;
5+
import org.springframework.core.io.FileSystemResource;
6+
import org.springframework.core.io.Resource;
7+
import org.springframework.core.io.UrlResource;
8+
9+
import java.io.IOException;
10+
import java.net.URL;
11+
import java.net.URLClassLoader;
12+
import java.util.Enumeration;
13+
14+
/**
15+
* A classloader that only finds resources and classes that are in the same jar as the given class
16+
*
17+
* For internal use only
18+
*
19+
* @author Graeme Rocher
20+
* @since 3.1.13
21+
*/
22+
class ClassRelativeClassLoader extends URLClassLoader {
23+
public ClassRelativeClassLoader(Class targetClass) {
24+
super(createClassLoaderUrls(targetClass), ClassLoader.getSystemClassLoader());
25+
}
26+
27+
private static URL[] createClassLoaderUrls(Class targetClass) {
28+
URL root = IOUtils.findRootResource(targetClass);
29+
if(BuildSettings.RESOURCES_DIR != null && BuildSettings.RESOURCES_DIR.exists()) {
30+
try {
31+
return new URL[] {root, new FileSystemResource(BuildSettings.RESOURCES_DIR.getCanonicalFile()).getURL() };
32+
} catch (IOException e) {
33+
return new URL[]{root};
34+
}
35+
}
36+
else {
37+
return new URL[]{root};
38+
}
39+
}
40+
41+
@Override
42+
public URL getResource(String name) {
43+
return findResource(name);
44+
}
45+
46+
@Override
47+
public Enumeration<URL> getResources(String name) throws IOException {
48+
if("".equals(name)) {
49+
final URL[] urls = getURLs();
50+
final int l = urls.length;
51+
return new Enumeration<URL>() {
52+
int i = 0;
53+
@Override
54+
public boolean hasMoreElements() {
55+
return i < l;
56+
}
57+
58+
@Override
59+
public URL nextElement() {
60+
return urls[i++];
61+
}
62+
};
63+
}
64+
else {
65+
return findResources(name);
66+
}
67+
}
68+
69+
70+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package org.grails.core.support.internal.tools;
2+
3+
import org.springframework.core.io.Resource;
4+
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
5+
6+
import java.util.Set;
7+
8+
/**
9+
* Attempts to limit classpath searching to only locations relative to the given class
10+
*
11+
* @author Graeme Rocher
12+
* @since 3.1.13
13+
*/
14+
public class ClassRelativeResourcePatternResolver extends PathMatchingResourcePatternResolver {
15+
16+
public ClassRelativeResourcePatternResolver(Class clazz) {
17+
super(new ClassRelativeClassLoader(clazz));
18+
}
19+
20+
@Override
21+
protected void addAllClassLoaderJarRoots(ClassLoader classLoader, Set<Resource> result) {
22+
// no-op - don't search jar roots
23+
}
24+
}

grails-core/src/main/groovy/org/grails/spring/context/support/PluginAwareResourceBundleMessageSource.java

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,25 +15,29 @@
1515
*/
1616
package org.grails.spring.context.support;
1717

18+
import grails.core.DefaultGrailsApplication;
1819
import grails.core.GrailsApplication;
20+
import grails.core.GrailsApplicationClass;
1921
import grails.core.support.GrailsApplicationAware;
2022
import grails.plugins.GrailsPlugin;
2123
import grails.plugins.GrailsPluginManager;
2224
import grails.plugins.PluginManagerAware;
23-
import grails.util.*;
24-
import org.apache.commons.logging.Log;
25-
import org.apache.commons.logging.LogFactory;
25+
import grails.util.BuildSettings;
26+
import grails.util.CacheEntry;
27+
import grails.util.Environment;
28+
import grails.util.GrailsStringUtils;
2629
import org.grails.core.io.CachingPathMatchingResourcePatternResolver;
30+
import org.grails.core.support.internal.tools.ClassRelativeResourcePatternResolver;
2731
import org.grails.plugins.BinaryGrailsPlugin;
2832
import org.springframework.beans.factory.InitializingBean;
2933
import org.springframework.core.io.FileSystemResource;
3034
import org.springframework.core.io.Resource;
3135
import org.springframework.core.io.ResourceLoader;
3236
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
37+
import org.springframework.core.io.support.ResourcePatternResolver;
3338

3439
import java.io.File;
3540
import java.io.FilenameFilter;
36-
import java.io.IOException;
3741
import java.text.MessageFormat;
3842
import java.util.ArrayList;
3943
import java.util.List;
@@ -59,6 +63,8 @@ public class PluginAwareResourceBundleMessageSource extends ReloadableResourceBu
5963
private ConcurrentMap<Locale, CacheEntry<PropertiesHolder>> cachedMergedPluginProperties = new ConcurrentHashMap<Locale, CacheEntry<PropertiesHolder>>();
6064
private ConcurrentMap<Locale, CacheEntry<PropertiesHolder>> cachedMergedBinaryPluginProperties = new ConcurrentHashMap<Locale, CacheEntry<PropertiesHolder>>();
6165
private long pluginCacheMillis = Long.MIN_VALUE;
66+
private boolean searchClasspath = false;
67+
private String messageBundleLocationPattern = "classpath*:*.properties";
6268

6369
@Deprecated
6470
public List<String> getPluginBaseNames() {
@@ -111,7 +117,20 @@ public boolean accept(File dir, String name) {
111117
}
112118
}
113119
else {
114-
resources = resourceResolver.getResources("classpath*:**/*.properties");
120+
if(searchClasspath) {
121+
resources = resourceResolver.getResources(messageBundleLocationPattern);
122+
}
123+
else {
124+
DefaultGrailsApplication defaultGrailsApplication = (DefaultGrailsApplication) application;
125+
GrailsApplicationClass applicationClass = defaultGrailsApplication.getApplicationClass();
126+
if(applicationClass != null) {
127+
ResourcePatternResolver resourcePatternResolver = new ClassRelativeResourcePatternResolver(applicationClass.getClass());
128+
resources = resourcePatternResolver.getResources(messageBundleLocationPattern);
129+
}
130+
else {
131+
resources = resourceResolver.getResources(messageBundleLocationPattern);
132+
}
133+
}
115134
}
116135

117136
List<String> basenames = new ArrayList<String>();
@@ -261,5 +280,24 @@ public void setResourceLoader(ResourceLoader resourceLoader) {
261280
*/
262281
public void setPluginCacheSeconds(int pluginCacheSeconds) {
263282
this.pluginCacheMillis = (pluginCacheSeconds * 1000);
264-
}
283+
}
284+
285+
/**
286+
* Whether to search the full classpath for message bundles. Enabling this will degrade startup performance.
287+
* The default is to only search for message bundles relative to the application classes directory.
288+
*
289+
* @param searchClasspath True if the entire classpath should be searched
290+
*/
291+
public void setSearchClasspath(boolean searchClasspath) {
292+
this.searchClasspath = searchClasspath;
293+
}
294+
295+
/**
296+
* The location pattern for message bundles
297+
*
298+
* @param messageBundleLocationPattern The message bundle location pattern
299+
*/
300+
public void setMessageBundleLocationPattern(String messageBundleLocationPattern) {
301+
this.messageBundleLocationPattern = messageBundleLocationPattern;
302+
}
265303
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package org.grails.core.support.internal.tools
2+
3+
import spock.lang.Specification
4+
5+
/**
6+
* Created by graemerocher on 30/09/2016.
7+
*/
8+
class ClassRelativeClassLoaderSpec extends Specification {
9+
10+
void "test class relative classloader"() {
11+
when:"A classloader is created for only resources relative to this class"
12+
def classLoader = new ClassRelativeClassLoader(ClassRelativeClassLoaderSpec)
13+
14+
then:"The resources are found"
15+
classLoader.getResource('org/grails/core/support/internal/tools/ClassRelativeClassLoaderSpec.class')
16+
17+
and:"other classpath resources are not found"
18+
!classLoader.getResource('springloaded.properties')
19+
}
20+
}

0 commit comments

Comments
 (0)