Better support for split packages.

- if you force an inclusion via 'inclusions=foo..*' then
  allow for everything in that package to be made reloadable
  regardless of it coming from jar or disk.
- when inclusion patterns don't match fall back on regular
  acceptance mechanisms (like package cache, etc) rather
  than just dismissing it.
- the results of decision making around making things
  reloadable are now enums/objects rather than plain booleans
  so that later we can do further analysis on those
  decisions.

Issue: https://github.com/grails/grails-core/issues/8817
This commit is contained in:
Andy Clement
2015-07-15 08:44:10 -07:00
parent 51cce3c0d7
commit d2fc21dc1d
15 changed files with 279 additions and 66 deletions

View File

@@ -450,6 +450,8 @@ public class GlobalConfiguration {
public final static boolean isJava18orHigher;
public static boolean InTestMode = false;
static {
String version = System.getProperty("java.version");
if (version.startsWith("1.8")) {

View File

@@ -601,6 +601,33 @@ public class TypeRegistry {
private List<String> packagesNotFound = new ArrayList<String>();
public static enum CouldBeReloadableDecision {
No_BuiltIn(false, false, "built in rejection"), //
No_FixedPackageList(false, false, "on hard coded list of those to reject"), //
Yes_CGLIB(true, false, "cglib related type"), //
No_JSP(false, false, "jsp"), //
No_GroovyScript(false, false, "groovy script"), //
Yes_PackageCache(true, false, "package cache"), //
No_PackageCache(false, false, "package cache"), //
Yes_FoundInJar(true, true, "in jar"), //
Yes_FoundOnDisk(true, true, "on disk"), //
No_Array(false, false, "array"), //
No_DiskCheck(false, true, "disk checked");
public final boolean couldBeReloadable;
public final boolean diskChecked;
public String reason;
CouldBeReloadableDecision(boolean couldBeReloadable, boolean diskChecked, String reason) {
this.couldBeReloadable = couldBeReloadable;
this.diskChecked = diskChecked;
this.reason = reason;
}
}
/**
* Determine if the named type could be reloadable. This method is invoked if the user has not setup any inclusions.
* With no inclusions specified, something is considered reloadable if it is accessible by the classloader for this
@@ -609,12 +636,12 @@ public class TypeRegistry {
* @param slashedName the typename of interest (e.g. com/foo/Bar)
* @return true if the type should be considered reloadable
*/
private boolean couldBeReloadable(String slashedName) {
private CouldBeReloadableDecision couldBeReloadable(String slashedName) {
if (slashedName == null) {
return false;
return CouldBeReloadableDecision.No_BuiltIn;
}
if (slashedName.startsWith("java/")) {
return false;
return CouldBeReloadableDecision.No_BuiltIn;
}
char ch = slashedName.charAt(0);
int index = ch - 'a';
@@ -630,27 +657,27 @@ public class TypeRegistry {
+ ignorablePackagePrefix
+ "' which is considered infrastructure and types within it are not made reloadable");
}
return false;
return CouldBeReloadableDecision.No_FixedPackageList;
}
}
}
}
if (slashedName.indexOf("$Proxy") != -1 || slashedName.indexOf("$$EnhancerBy") != -1
|| slashedName.indexOf("$$FastClassBy") != -1) {
return true;
return CouldBeReloadableDecision.Yes_CGLIB;
}
// TODO review all these... are these four only loaded by jasperloader?
int underscorePos = slashedName.indexOf("_");
if (underscorePos != -1) {
if (slashedName.endsWith("_jspx") || slashedName.endsWith("_tagx")) {
return false;
return CouldBeReloadableDecision.No_JSP;
}
if (slashedName.endsWith("_jspx$Helper") || slashedName.endsWith("_tagx$Helper")) {
return false;
return CouldBeReloadableDecision.No_JSP;
}
// skip grails scripts like "_PackagePlugins_groovy$_run_closure1_closure7"
if (ch == '_' && slashedName.indexOf("_groovy") != -1) {
return false;
return CouldBeReloadableDecision.No_GroovyScript;
}
}
int lastSlashPos = slashedName.lastIndexOf('/');
@@ -660,18 +687,18 @@ public class TypeRegistry {
for (String foundPackageName : packagesFound) {
if (packageName.equals(foundPackageName)) {
// System.out.println("fast accept " + slashedName);
return true;
return CouldBeReloadableDecision.Yes_PackageCache;
}
}
for (String notfoundPackageName : packagesNotFound) {
if (packageName.equals(notfoundPackageName)) {
// System.out.println("fast reject " + slashedName);
return false;
return CouldBeReloadableDecision.No_PackageCache;
}
}
}
if (ch == '[') {
return false;
return CouldBeReloadableDecision.No_Array;
}
try {
if (getResourceMethod == null) {
@@ -685,6 +712,7 @@ public class TypeRegistry {
getResourceMethod.setAccessible(true);
URL url = (URL) getResourceMethod.invoke(classLoader.get(), slashedName + ".class");
boolean reloadable = false;
boolean jarEntry = false;
if (url != null) {
String protocol = url.getProtocol();
// ignore 'jar' - what others?
@@ -708,6 +736,7 @@ public class TypeRegistry {
for (String jarToWatch : GlobalConfiguration.jarsToWatch) {
if (jarname.equals(jarToWatch)) {
reloadable = true;
jarEntry = true;
}
}
}
@@ -725,7 +754,17 @@ public class TypeRegistry {
// System.out.println("expensive, no package name and URL checked: " + slashedName + " : " + url + " loader="
// + classLoader);
}
return reloadable;
if (reloadable) {
if (jarEntry) {
return CouldBeReloadableDecision.Yes_FoundInJar;
}
else {
return CouldBeReloadableDecision.Yes_FoundOnDisk;
}
}
else {
return CouldBeReloadableDecision.No_DiskCheck;
}
}
catch (Exception e) {
throw new ReloadException("Unexpected problem locating the bytecode for " + slashedName + ".class", e);
@@ -733,7 +772,36 @@ public class TypeRegistry {
}
public boolean isReloadableTypeName(String slashedName) {
return isReloadableTypeName(slashedName, null, null);
return isReloadableTypeName(slashedName, null, null).isReloadable;
}
public static class ReloadableTypeNameDecision {
public final boolean isReloadable;
public final boolean extraInfo;
public final CouldBeReloadableDecision cbrd;
public final String reason;
public final boolean explicitlyIncluded;
ReloadableTypeNameDecision(boolean reloadable, CouldBeReloadableDecision cbrd, String reason, boolean extraInfo,
boolean explicitlyIncluded) {
this.isReloadable = reloadable;
this.cbrd = cbrd;
this.reason = reason;
this.extraInfo = extraInfo;
this.explicitlyIncluded = explicitlyIncluded;
}
public static final ReloadableTypeNameDecision No = new ReloadableTypeNameDecision(false, null,
null, false, false);
public static final ReloadableTypeNameDecision Yes = new ReloadableTypeNameDecision(true, null,
null, false, false);
}
/**
@@ -745,7 +813,8 @@ public class TypeRegistry {
* @param bytes the class bytes for the class being loaded
* @return true if the type is reloadable, false otherwise
*/
public boolean isReloadableTypeName(String slashedName, ProtectionDomain protectionDomain, byte[] bytes) {
public ReloadableTypeNameDecision isReloadableTypeName(String slashedName, ProtectionDomain protectionDomain,
byte[] bytes) {
if (GlobalConfiguration.verboseMode && log.isLoggable(Level.FINER)) {
log.finer("entering TypeRegistry.isReloadableTypeName(" + slashedName + ")");
}
@@ -758,7 +827,7 @@ public class TypeRegistry {
log.finer("[explanation] The type " + slashedName
+ " is considered part of yourkit and is not being made reloadable");
}
return false;
return ReloadableTypeNameDecision.No;
}
}
// Proxy types that implement a reloadable interface should themselves be made reloadable ... to be fleshed out
@@ -788,34 +857,35 @@ public class TypeRegistry {
log.finer("[explanation] The plugin " + plugin.getClass().getName() + " determined type "
+ slashedName + " is reloadable");
}
return true;
return ReloadableTypeNameDecision.Yes;
}
else if (decision == ReloadDecision.NO) {
if (GlobalConfiguration.explainMode && log.isLoggable(Level.FINER)) {
log.finer("[explanation] The plugin " + plugin.getClass().getName() + " determined type "
+ slashedName + " is not reloadable");
}
return false;
return ReloadableTypeNameDecision.No;
}
}
if (inclusionPatterns.isEmpty()) {
// No inclusions, so unless it matches an exclusion, it will be included
if (exclusionPatterns.isEmpty()) {
if (couldBeReloadable(slashedName)) {
CouldBeReloadableDecision cbrd = couldBeReloadable(slashedName);
if (cbrd.couldBeReloadable) {
if (GlobalConfiguration.explainMode && log.isLoggable(Level.FINER)) {
log.finer("[explanation] The class "
+ slashedName
+ " is currently considered reloadable. It matches no exclusions, is accessible from this classloader and is not in a jar/zip.");
}
return true;
return new ReloadableTypeNameDecision(true, cbrd, null, true, false);
}
else {
if (GlobalConfiguration.explainMode && log.isLoggable(Level.FINER)) {
log.finer("[explanation] The class " + slashedName
+ " is not going to be treated as reloadable.");
}
return false;
return new ReloadableTypeNameDecision(false, cbrd, null, true, false);
}
}
else {
@@ -829,14 +899,15 @@ public class TypeRegistry {
}
}
if (isExcluded) {
return false;
return new ReloadableTypeNameDecision(false, null, null, false, false);
}
}
if (couldBeReloadable(slashedName)) {
return true;
CouldBeReloadableDecision cbrd = couldBeReloadable(slashedName);
if (cbrd.couldBeReloadable) {
return new ReloadableTypeNameDecision(true, cbrd, null, false, false);
}
else {
return false;
return new ReloadableTypeNameDecision(false, cbrd, null, false, false);
}
}
}
@@ -853,12 +924,23 @@ public class TypeRegistry {
}
}
if (!isIncluded) {
return false;
// Not on the inclusion list
// In test mode there are various hierarchies of test data classes all on disk
// but inclusions are used to specify exactly what we want to consider reloadable. By
// making this check we avoid making types we discover on disk (or in the package cache)
// being made reloadable. In a real setup this wouldn't be what we want (hence the check)
if (!GlobalConfiguration.InTestMode) {
CouldBeReloadableDecision cbrd = couldBeReloadable(slashedName);
if (cbrd.couldBeReloadable) {
return new ReloadableTypeNameDecision(true, cbrd, null, true, false);
}
}
return ReloadableTypeNameDecision.No;
}
}
// Ok it matched an inclusion, but it must not match any exclusions
if (exclusionPatterns.isEmpty()) {
return true;
return new ReloadableTypeNameDecision(true, null, null, true, true);
}
else {
boolean isExcluded = false;
@@ -870,7 +952,7 @@ public class TypeRegistry {
}
}
}
return !isExcluded;
return new ReloadableTypeNameDecision(!isExcluded, null, null, true, true);
}
}
}
@@ -1058,7 +1140,8 @@ public class TypeRegistry {
}
}
if (GlobalConfiguration.logging && log.isLoggable(Level.INFO)) {
log.log(Level.INFO, "ReloadableType.addType(): Type '" + dottedname + "' is now reloadable! id=" + typeId);
log.log(Level.INFO,
"ReloadableType.addType(): Type '" + dottedname + "' is now reloadable! id=" + typeId);
}
return rtype;
}
@@ -1207,7 +1290,8 @@ public class TypeRegistry {
defineClassMethod.setAccessible(true);
ClassLoader loaderToUse = null;
loaderToUse = classLoader.get();
clazz = (Class<?>) defineClassMethod.invoke(loaderToUse, new Object[] { name, bytes, 0, bytes.length });
clazz = (Class<?>) defineClassMethod.invoke(loaderToUse,
new Object[] { name, bytes, 0, bytes.length });
}
else {
clazz = ccl.defineClass(name, bytes);
@@ -1344,8 +1428,9 @@ public class TypeRegistry {
}
else if (IncrementalTypeDescriptor.hasChanged(method)) {
if (IncrementalTypeDescriptor.isNowNonStatic(method)) {
throw new IncompatibleClassChangeError("SpringLoaded: Target of static call is no longer static '"
+ reloadableType.getBaseName() + "." + nameAndDescriptor + "'");
throw new IncompatibleClassChangeError(
"SpringLoaded: Target of static call is no longer static '"
+ reloadableType.getBaseName() + "." + nameAndDescriptor + "'");
}
// TODO need a check in here for a visibility change? Something like this:
// if (IncrementalTypeDescriptor.hasVisibilityChanged(method)) {
@@ -1474,7 +1559,8 @@ public class TypeRegistry {
*/
private static ReloadableType searchForReloadableType(int typeId, TypeRegistry typeRegistry) {
ReloadableType reloadableType;
reloadableType = typeRegistry.getReloadableTypeInTypeRegistryHierarchy(NameRegistry.getTypenameById(typeId));
reloadableType = typeRegistry.getReloadableTypeInTypeRegistryHierarchy(
NameRegistry.getTypenameById(typeId));
typeRegistry.rememberReloadableType(typeId, reloadableType);
return reloadableType;
}
@@ -1802,7 +1888,8 @@ public class TypeRegistry {
@UsedByGeneratedCode
public static ReloadableType getReloadableType(int typeRegistryId, int typeId) {
if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) {
log.info(">TypeRegistry.getReloadableType(typeRegistryId=" + typeRegistryId + ",typeId=" + typeId + ")");
log.info(
">TypeRegistry.getReloadableType(typeRegistryId=" + typeRegistryId + ",typeId=" + typeId + ")");
}
TypeRegistry typeRegistry = registryInstances[typeRegistryId].get();
if (typeRegistry == null) {
@@ -1919,14 +2006,16 @@ public class TypeRegistry {
// 'local' plugins
for (Plugin plugin : localPlugins) {
if (plugin instanceof ReloadEventProcessorPlugin) {
((ReloadEventProcessorPlugin) plugin).reloadEvent(reloadableType.getName(), reloadableType.getClazz(),
((ReloadEventProcessorPlugin) plugin).reloadEvent(reloadableType.getName(),
reloadableType.getClazz(),
versionsuffix);
}
}
// 'global' plugins
for (Plugin plugin : SpringLoadedPreProcessor.getGlobalPlugins()) {
if (plugin instanceof ReloadEventProcessorPlugin) {
((ReloadEventProcessorPlugin) plugin).reloadEvent(reloadableType.getName(), reloadableType.getClazz(),
((ReloadEventProcessorPlugin) plugin).reloadEvent(reloadableType.getName(),
reloadableType.getClazz(),
versionsuffix);
}
}

View File

@@ -42,6 +42,7 @@ import org.springsource.loaded.SystemClassReflectionInvestigator;
import org.springsource.loaded.SystemClassReflectionRewriter;
import org.springsource.loaded.SystemClassReflectionRewriter.RewriteResult;
import org.springsource.loaded.TypeRegistry;
import org.springsource.loaded.TypeRegistry.ReloadableTypeNameDecision;
import org.springsource.loaded.Utils;
import org.springsource.loaded.ri.ReflectiveInterceptor;
import org.springsource.loaded.support.Java8;
@@ -188,9 +189,10 @@ public class SpringLoadedPreProcessor implements Constants {
// 2. If NO, and nothing in this classloader might be, return the original bytes.
// 3. If YES, make the type reloadable (including rewriting call sites)
boolean isReloadableTypeName = typeRegistry.isReloadableTypeName(slashedClassName, protectionDomain, bytes);
ReloadableTypeNameDecision isReloadableTypeName = typeRegistry.isReloadableTypeName(slashedClassName,
protectionDomain, bytes);
if (isReloadableTypeName && GlobalConfiguration.explainMode && log.isLoggable(Level.INFO)) {
if (isReloadableTypeName.isReloadable && GlobalConfiguration.explainMode && log.isLoggable(Level.INFO)) {
log.info("[explanation] Based on the name, type " + slashedClassName + " is considered to be reloadable");
}
@@ -199,7 +201,7 @@ public class SpringLoadedPreProcessor implements Constants {
// if (GlobalConfiguration.verboseMode && isReloadableTypeName) {
// Log.log("Type '"+slashedClassName+"' is preliminarily being considered a reloadable type");
// }
if (isReloadableTypeName) {
if (isReloadableTypeName.isReloadable) {
if (!firstReloadableTypeHit) {
firstReloadableTypeHit = true;
// TODO move into the ctor for ReloadableType so that it can't block loading
@@ -305,13 +307,29 @@ public class SpringLoadedPreProcessor implements Constants {
if (!makeReloadableAnyway) {
// can't watch it for updates (it comes from a jar perhaps) so just rewrite call sites and return
if (GlobalConfiguration.verboseMode) {
Log.log("Cannot watch " + slashedClassName + ": not making it reloadable");
// Not planning to watch this class so ordinarily do not make it reloadable. UNLESS the user
// is specifying that it needs to be. This may happen with split packages - some classes in a jar
// and some on disk. During type rewriting the top most reloadable types get fields inserted -
// when split across jars we get confused by split packages. If we go by name (as the code does
// right now) then we think we aren't the top most reloadable type but we don't realize that the
// type above us comes from a jar. Hence this condition below. If the user did explicitly
// specify types with this kind of name should be made reloadable we even make the ones from
// the jar reloadable. (TODO: optimization, make a smarter isTopMostReloadableType test that
// allows us to keep the jar loaded types as non reloadable).
if (isReloadableTypeName.extraInfo && isReloadableTypeName.explicitlyIncluded
&& !GlobalConfiguration.InTestMode) {
}
if (needsClientSideRewriting(slashedClassName)) {
bytes = typeRegistry.methodCallRewriteUseCacheIfAvailable(slashedClassName, bytes);
else {
if (GlobalConfiguration.verboseMode) {
Log.log("Cannot watch " + slashedClassName + ": not making it reloadable");
}
if (needsClientSideRewriting(slashedClassName)) {
bytes = typeRegistry.methodCallRewriteUseCacheIfAvailable(slashedClassName, bytes);
}
return bytes;
}
return bytes;
}
}
ReloadableType rtype = typeRegistry.addType(dottedClassName, bytes);

View File

@@ -32,15 +32,13 @@ import org.springsource.loaded.GlobalConfiguration;
import org.springsource.loaded.ReloadableType;
import org.springsource.loaded.SpringLoaded;
import org.springsource.loaded.TypeRegistry;
import org.springsource.loaded.Utils;
import org.springsource.loaded.Utils.ReturnType;
import org.springsource.loaded.test.infra.ClassPrinter;
import org.springsource.loaded.test.infra.Result;
/**
* Tests for the TypeRegistry that exercise it in the same way it will actively be used when managing ReloadableType
* instances.
*
*
* @author Andy Clement
* @since 1.0
*/
@@ -136,7 +134,8 @@ public class ReloadableTypeTests extends SpringLoadedTests {
assertEquals("[1,2,3]",
toString(runOnInstance(rtype.getClazz(), instance, "getProtectedArrayOfInts").returnValue));
runOnInstance(rtype.getClazz(), instance, "setProtectedArrayOfStrings", (Object) new String[] { "a", "b", "c" });
runOnInstance(rtype.getClazz(), instance, "setProtectedArrayOfStrings",
(Object) new String[] { "a", "b", "c" });
assertEquals("[a,b,c]",
toString(runOnInstance(rtype.getClazz(), instance, "getProtectedArrayOfStrings").returnValue));
@@ -182,7 +181,8 @@ public class ReloadableTypeTests extends SpringLoadedTests {
assertEquals("[1,2,3]",
toString(runOnInstance(rtype.getClazz(), instance, "getProtectedArrayOfInts").returnValue));
runOnInstance(rtype.getClazz(), instance, "setProtectedArrayOfStrings", (Object) new String[] { "a", "b", "c" });
runOnInstance(rtype.getClazz(), instance, "setProtectedArrayOfStrings",
(Object) new String[] { "a", "b", "c" });
assertEquals("[a,b,c]",
toString(runOnInstance(rtype.getClazz(), instance, "getProtectedArrayOfStrings").returnValue));
@@ -206,13 +206,13 @@ public class ReloadableTypeTests extends SpringLoadedTests {
ReloadableType B = tr.addType("invokestatic.issue4.B", loadBytesForClass("invokestatic.issue4.B"));
Result r = runUnguarded(B.getClazz(), "getMessage");
assertEquals("String1", (String) r.returnValue);
assertEquals("String1", r.returnValue);
B.loadNewVersion(B.bytesInitial);
r = runUnguarded(B.getClazz(), "getMessage");
assertEquals("String1", (String) r.returnValue);
assertEquals("String1", r.returnValue);
}
@Test
@@ -222,12 +222,12 @@ public class ReloadableTypeTests extends SpringLoadedTests {
ReloadableType BB = tr.addType("invokestatic.issue4.BB", loadBytesForClass("invokestatic.issue4.BB"));
Result r = runUnguarded(BB.getClazz(), "getMessage");
assertEquals("String1", (String) r.returnValue);
assertEquals("String1", r.returnValue);
BB.loadNewVersion(BB.bytesInitial);
r = runUnguarded(BB.getClazz(), "getMessage");
assertEquals("String1", (String) r.returnValue);
assertEquals("String1", r.returnValue);
}
@Test
@@ -237,12 +237,12 @@ public class ReloadableTypeTests extends SpringLoadedTests {
ReloadableType BBB = tr.addType("invokestatic.issue4.BBB", loadBytesForClass("invokestatic.issue4.BBB"));
Result r = runUnguarded(BBB.getClazz(), "getMessage");
assertEquals("String1", (String) r.returnValue);
assertEquals("String1", r.returnValue);
AAA.loadNewVersion(AAA.bytesInitial);
r = runUnguarded(BBB.getClazz(), "getMessage");
assertEquals("String1", (String) r.returnValue);
assertEquals("String1", r.returnValue);
}
@Test
@@ -252,13 +252,13 @@ public class ReloadableTypeTests extends SpringLoadedTests {
ReloadableType B = tr.addType("invokestatic.issue4.B", loadBytesForClass("invokestatic.issue4.B"));
Result r = runUnguarded(B.getClazz(), "getMessage");
assertEquals("String1", (String) r.returnValue);
assertEquals("String1", r.returnValue);
A.loadNewVersion(A.bytesInitial);
B.loadNewVersion(B.bytesInitial);
r = runUnguarded(B.getClazz(), "getMessage");
assertEquals("String1", (String) r.returnValue);
assertEquals("String1", r.returnValue);
}
// The supertype is not reloadable,it is in a jar
@@ -268,7 +268,7 @@ public class ReloadableTypeTests extends SpringLoadedTests {
ReloadableType B = tr.addType("invokestatic.issue4.BBBB", loadBytesForClass("invokestatic.issue4.BBBB"));
Result r = runUnguarded(B.getClazz(), "getMessage");
assertEquals("Hello", (String) r.returnValue);
assertEquals("Hello", r.returnValue);
ReloadableType thesuper = B.getSuperRtype();
assertNull(thesuper);
@@ -278,7 +278,7 @@ public class ReloadableTypeTests extends SpringLoadedTests {
B.loadNewVersion(B.bytesInitial);
r = runUnguarded(B.getClazz(), "getMessage");
assertEquals("Hello", (String) r.returnValue);
assertEquals("Hello", r.returnValue);
}
// Basic write/read then reload then write/read again
@@ -366,7 +366,7 @@ public class ReloadableTypeTests extends SpringLoadedTests {
// There is a test that will work in the SpringLoadedTestsInSeparateJVM
public void serialization4() throws Exception {
TypeRegistry tr = getTypeRegistry("remote..*");
// ReloadableType person =
// ReloadableType person =
tr.addType("remote.Person", loadBytesForClass("remote.Person"));
// When the Serialize class is run directly, we see: byteinfo:len=98:crc=c1047cf6
@@ -386,7 +386,7 @@ public class ReloadableTypeTests extends SpringLoadedTests {
ReloadableType B = tr.addType("invokestatic.issue4.BBBBB", loadBytesForClass("invokestatic.issue4.BBBBB"));
Result r = runUnguarded(B.getClazz(), "getMessage");
assertEquals("Hello", (String) r.returnValue);
assertEquals("Hello", r.returnValue);
ReloadableType thesuper = B.getSuperRtype();
thesuper = tr.getReloadableType("invokestatic/issue4/subpkg/AAAA");
@@ -395,7 +395,7 @@ public class ReloadableTypeTests extends SpringLoadedTests {
B.loadNewVersion(B.bytesInitial);
r = runUnguarded(B.getClazz(), "getMessage");
assertEquals("Hello", (String) r.returnValue);
assertEquals("Hello", r.returnValue);
}
@Test
@@ -405,7 +405,7 @@ public class ReloadableTypeTests extends SpringLoadedTests {
ReloadableType B = tr.addType("invokestatic.issue4.BBBBB", loadBytesForClass("invokestatic.issue4.BBBBB"));
Result r = runUnguarded(B.getClazz(), "getMessage");
assertEquals("Hello", (String) r.returnValue);
assertEquals("Hello", r.returnValue);
ReloadableType thesuper = B.getSuperRtype();
thesuper = tr.getReloadableType("invokestatic/issue4/subpkg/AAAA");
@@ -414,7 +414,7 @@ public class ReloadableTypeTests extends SpringLoadedTests {
B.loadNewVersion(B.bytesInitial);
r = runUnguarded(B.getClazz(), "getMessage");
assertEquals("Hello", (String) r.returnValue);
assertEquals("Hello", r.returnValue);
assertAssociateSubtypes(mid, "invokestatic.issue4.BBBBB");
assertTrue(B.isAffectedByReload());
@@ -425,7 +425,7 @@ public class ReloadableTypeTests extends SpringLoadedTests {
/**
* Verify that the reloadable type knows about the expected list of subtypes. Subtype names are dotted (e.g.
* invokestatic.foo.Bar")
*
*
* @param rtype the reloadabletype to check
* @param expectedAssociateSubtypes the subtypes it should know about
*/
@@ -485,12 +485,12 @@ public class ReloadableTypeTests extends SpringLoadedTests {
/**
* In this test a protected field has the same name as another field being referenced from the reloadable type.
* Check only the right one is redirect to the accessor.
*
*
*/
@Test
public void protectedFieldAccessors3() throws Exception {
TypeRegistry tr = getTypeRegistry("prot.SubThree,prot.PeerThree");
// ReloadableType rtypePeer =
// ReloadableType rtypePeer =
tr.addType("prot.PeerThree", loadBytesForClass("prot.PeerThree"));
ReloadableType rtype = tr.addType("prot.SubThree", loadBytesForClass("prot.SubThree"));
@@ -653,4 +653,5 @@ public class ReloadableTypeTests extends SpringLoadedTests {
assertTrue(Modifier.isPublic((Integer) runUnguarded(simpleClass, "getModifiers").returnValue));
assertTrue(Modifier.isStatic((Integer) runUnguarded(simpleClass, "getModifiers").returnValue));
}
}

View File

@@ -378,6 +378,21 @@ public class ReloadingJVM {
}
}
public boolean isReloadableType(String classname) {
JVMOutput jo = sendAndReceive("isreloadabletype " + classname);
System.out.println(jo);
if (jo.stdout.contains(classname + " is reloadable type")) {
return true;
}
else if (jo.stdout.contains(classname + " is not reloadable type")) {
return false;
}
else {
System.err.println(jo);
throw new IllegalStateException();
}
}
public JVMOutput newInstance(String instanceName, String classname) {
return newInstance(instanceName, classname, true);
}

View File

@@ -98,6 +98,9 @@ public class ReloadingJVMCommandProcess {
else if (commandName.equals("reload")) {
reloadCommand(arguments.get(0), arguments.size() == 1 ? null : arguments.get(1));
}
else if (commandName.equals("isreloadabletype")) {
isReloadableTypeCheck(arguments.get(0));
}
else if (commandName.equals("extendcp")) {
extendClasspath(arguments.get(0));
}
@@ -190,6 +193,22 @@ public class ReloadingJVMCommandProcess {
System.err.println("!!");
}
private static void isReloadableTypeCheck(String classname) {
try {
Class<?> clazz = Class.forName(classname);
TypeRegistry tr = TypeRegistry.getTypeRegistryFor(clazz.getClassLoader());
if (tr.getReloadableType(clazz) != null) {
System.out.println(classname + " is reloadable type");
}
else {
System.out.println(classname + " is not reloadable type");
}
}
catch (Exception e) {
e.printStackTrace(System.out);
}
}
private static void reloadCommand(String classname, String data) {
try {
Class<?> clazz = Class.forName(classname);

View File

@@ -57,6 +57,7 @@ import org.objectweb.asm.tree.LocalVariableNode;
import org.objectweb.asm.tree.MethodNode;
import org.springsource.loaded.ClassRenamer;
import org.springsource.loaded.Constants;
import org.springsource.loaded.GlobalConfiguration;
import org.springsource.loaded.ISMgr;
import org.springsource.loaded.MethodMember;
import org.springsource.loaded.NameRegistry;
@@ -1009,6 +1010,8 @@ public abstract class SpringLoadedTests implements Constants {
protected TypeRegistry getTypeRegistry(String includePatterns) {
TypeRegistry.reinitialize();
TypeRegistry tr = TypeRegistry.getTypeRegistryFor(binLoader);
GlobalConfiguration.InTestMode = true;
GlobalConfiguration.allowSplitPackages = true;
Properties p = new Properties();
if (includePatterns != null) {
p.setProperty(TypeRegistry.Key_Inclusions, includePatterns);

View File

@@ -17,6 +17,7 @@
package org.springsource.loaded.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.After;
import org.junit.AfterClass;
@@ -376,6 +377,43 @@ public class SpringLoadedTestsInSeparateJVM extends SpringLoadedTests {
assertStdoutContains("TopB.foo() running\nControllerB.foo() running again!\n", jvm.call("a", "foo"));
}
/**
* GRAILS-9061.
*
* To build the jar see testdata/jars/grailsplugins
*/
@Test
public void splitPackages() throws Exception {
String supertypeInJar = "grails.plugins.A"; // from the grailsPlugins.jar
String subtypeNotInJar = "grails.plugins.B";
jvm.shutdown();
jvm = ReloadingJVM.launch("inclusions=grails.plugins..*");
String path = jvm.copyJarToTestdataDirectory("grailsplugins/grailsPlugins.jar", "grailsplugins.jar");
jvm.extendCp(path);
jvm.copyToTestdataDirectory(subtypeNotInJar);
JVMOutput jo = null;
// Load the one from the jar
jvm.newInstance("a", supertypeInJar);
jo = jvm.call("a", "foo");
assertContains("A.foo() running", jo.stdout);
// Load the one not from the jar, which extends the jar one
jo = jvm.newInstance("b", subtypeNotInJar);
jo = jvm.call("b", "foo");
System.out.println(jo);
assertContains("B.foo() running", jo.stdout);
assertTrue(jvm.isReloadableType(supertypeInJar));
assertTrue(jvm.isReloadableType(subtypeNotInJar));
jvm.updateClass(subtypeNotInJar, retrieveRename(subtypeNotInJar, subtypeNotInJar + "2"));
waitForReloadToOccur();
jo = jvm.call("b", "foo");
assertContains("B2.foo() running", jo.stdout);
}
// ---
private void waitForReloadToOccur() {

BIN
testdata/jars/grailsplugins/A.class vendored Normal file

Binary file not shown.

4
testdata/jars/grailsplugins/build.sh vendored Executable file
View File

@@ -0,0 +1,4 @@
mkdir -p grails/plugins
cp ../../bin/grails/plugins/A.class ./grails/plugins/.
jar -cvMf grailsplugins.jar grails/plugins/A.class

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,8 @@
package grails.plugins;
public class A {
public void foo() {
System.out.println("A.foo() running");
}
}

View File

@@ -0,0 +1,8 @@
package grails.plugins;
public class B extends A {
public void foo() {
System.out.println("B.foo() running");
}
}

View File

@@ -0,0 +1,8 @@
package grails.plugins;
public class B2 extends A {
public void foo() {
System.out.println("B2.foo() running");
}
}