Skip to content

Commit 2ca8735

Browse files
Johannes Erikssonmshabarov
andauthored
Minor class path scanning optimization (#642) (#647)
Extend the default class scanning exclusion list to JDK and IDE classes. Use prefix tree for faster lookups in long exclusion/inclusion lists. Co-authored-by: Mikhail Shabarov <[email protected]>
1 parent d2ee194 commit 2ca8735

File tree

3 files changed

+182
-54
lines changed

3 files changed

+182
-54
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright 2000-2020 Vaadin Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
package com.vaadin.flow.spring;
17+
18+
import java.io.Serializable;
19+
import java.util.Collection;
20+
import java.util.HashMap;
21+
import java.util.Map;
22+
23+
/**
24+
* Quick prefix lookup for package inclusion and exclusion lists.
25+
*/
26+
class PrefixTree implements Serializable {
27+
28+
private final Node root;
29+
30+
PrefixTree(Collection<String> prefixes) {
31+
root = new Node();
32+
root.terminal = false;
33+
prefixes.forEach(this::addPrefix);
34+
}
35+
36+
void addPrefix(String prefix) {
37+
if (prefix.isEmpty()) {
38+
throw new IllegalArgumentException("empty prefix");
39+
}
40+
root.addPrefix(prefix);
41+
}
42+
43+
boolean hasPrefix(String s) {
44+
Node node = root;
45+
final int slen = s.length();
46+
int sidx = 0;
47+
while (node != null) {
48+
if (node.terminal) {
49+
return true;
50+
} else if (sidx < slen) {
51+
node = node.children.get(s.charAt(sidx++));
52+
} else {
53+
return false;
54+
}
55+
}
56+
return false;
57+
}
58+
59+
static class Node implements Serializable {
60+
private final Map<Character, Node> children = new HashMap<>();
61+
private boolean terminal = true;
62+
63+
void addPrefix(String prefix) {
64+
terminal = false;
65+
char ch = prefix.charAt(0);
66+
children.putIfAbsent(ch, new Node());
67+
if (prefix.length() > 1) {
68+
children.get(ch).addPrefix(prefix.substring(1));
69+
}
70+
}
71+
}
72+
}

vaadin-spring/src/main/java/com/vaadin/flow/spring/VaadinServletContextInitializer.java

Lines changed: 59 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -105,18 +105,38 @@ public class VaadinServletContextInitializer
105105
private ResourceLoader customLoader;
106106

107107
/**
108-
* packages that are white-listed (should be scanned) by default and can't
109-
* be overriden by <code>addedWhiteListed</code>.
108+
* Packages that should be excluded when scanning all packages.
110109
*/
111-
private static final List<String> DEFAULT_WHITE_LISTED = Stream
110+
private static final List<String> DEFAULT_SCAN_NEVER = Stream.of("antlr",
111+
"cglib", "ch/quos/logback", "commons-codec", "commons-fileupload",
112+
"commons-io", "commons-logging", "com/fasterxml", "com/google",
113+
"com/h2database", "com/helger", "com/vaadin/external/atmosphere",
114+
"com/vaadin/webjar", "junit", "net/bytebuddy", "org/apache",
115+
"org/aspectj", "org/bouncycastle", "org/dom4j", "org/easymock",
116+
"org/hamcrest", "org/hibernate", "org/javassist", "org/jboss",
117+
"org/jsoup", "org/seleniumhq", "org/slf4j", "org/atmosphere",
118+
"org/springframework", "org/webjars/bowergithub", "org/yaml",
119+
120+
"java/", "javax/", "javafx/", "com/sun/", "oracle/deploy",
121+
"oracle/javafx", "oracle/jrockit", "oracle/jvm", "oracle/net",
122+
"oracle/nio", "oracle/tools", "oracle/util", "oracle/webservices",
123+
"oracle/xmlns",
124+
125+
"com/intellij/", "org/jetbrains").collect(Collectors.toList());
126+
127+
/**
128+
* Packages that should be scanned by default and can't be overriden by
129+
* a custom list.
130+
*/
131+
private static final List<String> DEFAULT_SCAN_ONLY = Stream
112132
.of(Component.class.getPackage().getName(),
113133
Theme.class.getPackage().getName(), "com.vaadin.shrinkwrap")
114134
.collect(Collectors.toList());
115135

116136
/**
117-
* Packages whitelisted by the user
137+
* Packages marked by the user to be scanned exclusively.
118138
*/
119-
private List<String> customWhitelist;
139+
private final List<String> customScanOnly;
120140

121141
/**
122142
* Class path scanner that reuses infrastructure from Spring while also
@@ -324,8 +344,8 @@ public void contextInitialized(ServletContextEvent event) {
324344
}
325345

326346
Set<String> basePackages;
327-
if (isWhitelistSet()) {
328-
basePackages = new HashSet<>(getWhiteListPackages());
347+
if (isScanOnlySet()) {
348+
basePackages = new HashSet<>(getScanOnlyPackages());
329349
} else {
330350
basePackages = Collections.singleton("");
331351
}
@@ -361,11 +381,11 @@ public void contextDestroyed(ServletContextEvent event) {
361381
// NO-OP
362382
}
363383

364-
private Collection<String> getWhiteListPackages() {
384+
private Collection<String> getScanOnlyPackages() {
365385
HashSet<String> npmPackages = new HashSet<>(getDefaultPackages());
366-
npmPackages.addAll(DEFAULT_WHITE_LISTED);
367-
if (customWhitelist != null) {
368-
npmPackages.addAll(customWhitelist);
386+
npmPackages.addAll(DEFAULT_SCAN_ONLY);
387+
if (customScanOnly != null) {
388+
npmPackages.addAll(customScanOnly);
369389
}
370390
return npmPackages;
371391
}
@@ -386,8 +406,9 @@ private void collectDevModeTypes(
386406
}
387407
}
388408

389-
private boolean isWhitelistSet() {
390-
return customWhitelist != null && !customWhitelist.isEmpty();
409+
private boolean isScanOnlySet() {
410+
return customScanOnly != null
411+
&& !customScanOnly.isEmpty();
391412
}
392413
}
393414

@@ -440,31 +461,30 @@ public void contextDestroyed(ServletContextEvent sce) {
440461
*/
441462
public VaadinServletContextInitializer(ApplicationContext context) {
442463
appContext = context;
443-
String blacklistProperty = appContext.getEnvironment()
464+
String neverScanProperty = appContext.getEnvironment()
444465
.getProperty("vaadin.blacklisted-packages");
445-
List<String> blacklist;
446-
if (blacklistProperty == null) {
447-
blacklist = Collections.emptyList();
466+
List<String> neverScan;
467+
if (neverScanProperty == null) {
468+
neverScan = Collections.emptyList();
448469
} else {
449-
blacklist = Arrays.stream(blacklistProperty.split(","))
470+
neverScan = Arrays.stream(neverScanProperty.split(","))
450471
.map(String::trim).collect(Collectors.toList());
451472
}
452473

453-
String whitelistProperty = appContext.getEnvironment()
474+
String onlyScanProperty = appContext.getEnvironment()
454475
.getProperty("vaadin.whitelisted-packages");
455-
if (whitelistProperty == null) {
456-
customWhitelist = Collections.emptyList();
457-
customLoader = new CustomResourceLoader(appContext, blacklist);
476+
if (onlyScanProperty == null) {
477+
customScanOnly = Collections.emptyList();
478+
customLoader = new CustomResourceLoader(appContext, neverScan);
458479

459480
} else {
460-
customWhitelist = Arrays.stream(whitelistProperty.split(","))
461-
.map(whitelistedPackage -> whitelistedPackage
462-
.replace('/', '.').trim())
481+
customScanOnly = Arrays.stream(onlyScanProperty.split(","))
482+
.map(onlyPackage -> onlyPackage.replace('/', '.').trim())
463483
.collect(Collectors.toList());
464484
customLoader = appContext;
465485
}
466486

467-
if (!customWhitelist.isEmpty() && !blacklist.isEmpty()) {
487+
if (!customScanOnly.isEmpty() && !neverScan.isEmpty()) {
468488
getLogger().warn(
469489
"vaadin.blacklisted-packages is ignored because both vaadin.whitelisted-packages and vaadin.blacklisted-packages have been set.");
470490
}
@@ -604,34 +624,22 @@ private List<String> getDefaultPackages() {
604624
*/
605625
private static class CustomResourceLoader
606626
extends PathMatchingResourcePatternResolver {
607-
/**
608-
* Blacklisted packages that shouldn't be scanned for when scanning all
609-
* packages.
610-
*/
611-
private List<String> blackListed = Stream.of("antlr", "cglib",
612-
"ch/quos/logback", "commons-codec", "commons-fileupload",
613-
"commons-io", "commons-logging", "com/fasterxml", "com/google",
614-
"com/h2database", "com/helger",
615-
"com/vaadin/external/atmosphere", "com/vaadin/webjar", "javax/",
616-
"junit", "net/bytebuddy", "org/apache", "org/aspectj",
617-
"org/bouncycastle", "org/dom4j", "org/easymock", "org/hamcrest",
618-
"org/hibernate", "org/javassist", "org/jboss", "org/jsoup",
619-
"org/seleniumhq", "org/slf4j", "org/atmosphere",
620-
"org/springframework", "org/webjars/bowergithub", "org/yaml")
621-
.collect(Collectors.toList());
622-
623-
private static List<String> defaultWhiteList = DEFAULT_WHITE_LISTED
624-
.stream().map(packageName -> packageName.replace('.', '/'))
625-
.collect(Collectors.toList());
627+
628+
private final PrefixTree scanNever = new PrefixTree(DEFAULT_SCAN_NEVER);
629+
630+
private final PrefixTree scanAlways = new PrefixTree(
631+
DEFAULT_SCAN_ONLY.stream()
632+
.map(packageName -> packageName.replace('.', '/'))
633+
.collect(Collectors.toList()));
626634

627635
public CustomResourceLoader(ResourceLoader resourceLoader,
628-
List<String> addedBlacklist) {
636+
List<String> addedScanNever) {
629637
super(resourceLoader);
630638

631-
Objects.requireNonNull(addedBlacklist,
632-
"addedBlacklist shouldn't be null!");
639+
Objects.requireNonNull(addedScanNever,
640+
"addedScanNever shouldn't be null!");
633641

634-
blackListed.addAll(addedBlacklist);
642+
addedScanNever.forEach(scanNever::addPrefix);
635643
}
636644

637645
/**
@@ -699,11 +707,8 @@ private Resource[] collectResources(String locationPattern)
699707
}
700708

701709
private boolean shouldPathBeScanned(String path) {
702-
if (defaultWhiteList.stream().anyMatch(path::startsWith)) {
703-
return true;
704-
}
705-
706-
return !blackListed.stream().anyMatch(path::startsWith);
710+
return scanAlways.hasPrefix(path)
711+
|| !scanNever.hasPrefix(path);
707712
}
708713
}
709714

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright 2000-2020 Vaadin Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
package com.vaadin.flow.spring;
17+
18+
import java.util.Arrays;
19+
import java.util.Collections;
20+
21+
import org.junit.Assert;
22+
import org.junit.Test;
23+
24+
public class PrefixTreeTest {
25+
26+
@Test
27+
public void hasPrefix_containsPrefix_returnsTrue() {
28+
PrefixTree prefixTree = new PrefixTree(
29+
Arrays.asList("com/sun", "antlr", "ch/quos/logback"));
30+
Assert.assertTrue(prefixTree.hasPrefix("antlr"));
31+
Assert.assertTrue(prefixTree.hasPrefix("com/sun/test"));
32+
Assert.assertTrue(prefixTree.hasPrefix("com/sun"));
33+
}
34+
35+
@Test
36+
public void hasPrefix_doesNotContainPrefix_returnsFalse() {
37+
PrefixTree prefixTree = new PrefixTree(
38+
Arrays.asList("com/sun", "antlr", "ch/quos/logback"));
39+
Assert.assertFalse(prefixTree.hasPrefix(""));
40+
Assert.assertFalse(prefixTree.hasPrefix("a"));
41+
Assert.assertFalse(prefixTree.hasPrefix("test"));
42+
Assert.assertFalse(prefixTree.hasPrefix("com/su"));
43+
}
44+
45+
@Test
46+
public void hasPrefix_emptyTree_returnsFalse() {
47+
PrefixTree prefixTree = new PrefixTree(Collections.emptyList());
48+
Assert.assertFalse(prefixTree.hasPrefix("a"));
49+
}
50+
51+
}

0 commit comments

Comments
 (0)