GRAILS-7799: Adjust treatment of CGLIB generated types

This commit is contained in:
Andy Clement
2014-01-23 15:16:45 -08:00
parent b253399eac
commit db5447e060
6 changed files with 121 additions and 32 deletions

View File

@@ -27,8 +27,8 @@ import org.springsource.loaded.agent.SpringPlugin;
/**
* Captures configurable elements - these are set (to values other than the defaults) in TypeRegistry when the system property
* springloaded.configuration is processed. It is possible to tweak them during testcases to simplify what is being tested - the
* Encapsulates configurable elements - these are set (to values other than the defaults) in TypeRegistry when the system property
* springloaded configuration is processed. It is possible to tweak them during testcases to simplify what is being tested - the
* test should reset them to their original values on completion.
*
* @author Andy Clement
@@ -135,21 +135,37 @@ public class GlobalConfiguration {
public final static boolean debugplugins;
private static void printUsage() {
System.out.println("SpringLoaded");
System.out.println("============");
System.out.println();
System.out.println("Usage: java -noverify -javaagent:<pathto>/springloaded.jar");
System.out.println("Optionally specify configuration through -Dspringloaded=<options>");
System.out.println("<options> is a ';' separated list of directives or name=value options");
System.out.println("Example: -Dspringloaded=verbose;cacheDir=/tmp");
System.out.println();
System.out.println("Directives:");
System.out.println(" ? - print this usage text");
System.out.println(" verbose - the reloader will log important lifecycle events");
System.out.println("Options:");
System.exit(0);
}
/**
* Look for a springloaded system property and initialize the 'default system wide' configuration based upon it.
* Support configuration options:
* <ul>
* <li><tt>info</tt> - print usage information on the options
* <li><tt>verbose</tt> - this directive causes SpringLoaded to report on decisions it is making.
* </ul>
*/
static {
// classesToDump = new ArrayList<String>();
// classesToDump.add("Demo");
globalConfigurationProperties = new Properties();
// Load global configuration
boolean debugPlugins = false;
try {
boolean specifiedCaching = false;
String value = System.getProperty("springloaded");
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINEST)) {
log.finest("GlobalConfiguration: being configured from '" + value + "'");
}
// value is a ';' separated list of configuration options which either may be name=value settings or directives (just a name)
if (value != null) {
StringTokenizer st = new StringTokenizer(value, ";");
@@ -224,8 +240,8 @@ public class GlobalConfiguration {
System.out.println("Spring-Loaded logging = (" + GlobalConfiguration.isRuntimeLogging + ","
+ GlobalConfiguration.logging + ")");
} else if (key.equals("verbose")) {
GlobalConfiguration.verboseMode = kv.substring(equals + 1).equalsIgnoreCase("true");
GlobalConfiguration.reloadMessages = verboseMode;
verboseMode = kv.substring(equals + 1).equalsIgnoreCase("true");
reloadMessages = verboseMode;
} else if (key.equals("rebasePaths")) {
// value is a series of "a=b,c=d,e=f" indicating from and to
globalConfigurationProperties.put("rebasePaths", kv.substring(equals + 1));
@@ -243,7 +259,13 @@ public class GlobalConfiguration {
}
}
} else {
// directive
if (kv.equals("?")) {
printUsage();
}
else if (kv.equals("verbose")) {
Log.log("verbose mode on, configuration is:"+value);
verboseMode = true;
}
}
}
}

View File

@@ -29,10 +29,11 @@ import org.springsource.loaded.agent.ReloadDecision;
public interface IsReloadableTypePlugin extends Plugin {
/**
* @param typeRegistry
* @param typename slashed type name (e.g. java/lang/String)
* @param protectionDomain
* @param bytes the classfile data
*/
ReloadDecision shouldBeMadeReloadable(String typename, ProtectionDomain protectionDomain, byte[] bytes);
ReloadDecision shouldBeMadeReloadable(TypeRegistry typeRegistry, String typename, ProtectionDomain protectionDomain, byte[] bytes);
}

View File

@@ -0,0 +1,16 @@
package org.springsource.loaded;
/**
* Minimal support for logging messages, avoids all dependencies it can because it will be loaded very early by the VM and
* we don't want to introduce unnecessarily complex classloading.
*
* @author Andy Clement
* @since 1.1.5
*/
public class Log {
public static void log(String message) {
System.out.println("SL: "+message);
}
}

View File

@@ -61,7 +61,7 @@ public class SystemPropertyConfiguredIsReloadableTypePlugin implements IsReloada
private String mostRecentReloadableDirs = null;
// TODO need try/catch protection when calling plugins, in case of bad ones
public ReloadDecision shouldBeMadeReloadable(String typename, ProtectionDomain protectionDomain, byte[] bytes) {
public ReloadDecision shouldBeMadeReloadable(TypeRegistry typeRegistry, String typename, ProtectionDomain protectionDomain, byte[] bytes) {
if (debug) {
System.out.println("SystemPropertyConfiguredIsReloadableTypePlugin: entered, for typename " + typename);
}
@@ -106,6 +106,21 @@ public class SystemPropertyConfiguredIsReloadableTypePlugin implements IsReloada
// System.out.println("SystemPropertyConfiguredIsReloadableTypePlugin: " + typename + " does not have a codeSource");
// }
} else {
// May have to do something special for CGLIB types
// These will have a type name of something like: grails/plugin/springsecurity/SpringSecurityService$$EnhancerByCGLIB$$8f956be2
// But a codesource location of file:/Users/aclement/.m2/repository/org/springframework/spring-core/3.2.5.RELEASE/spring-core-3.2.5.RELEASE.jar
int cglibIndex = typename.indexOf("ByCGLIB$$"); // catches fastclass too
if (cglibIndex != -1) {
String originalType = typename.substring(0, typename.indexOf("$$")); // assuming first $$ is good enough
while (typeRegistry != null) {
ReloadableType originalReloadable = typeRegistry.getReloadableType(originalType);
if (originalReloadable != null) {
return ReloadDecision.YES;
}
typeRegistry = typeRegistry.getParentRegistry();
}
}
if (debug) {
System.out.println("SystemPropertyConfiguredIsReloadableTypePlugin: " + typename + " codeSource.getLocation() is "
+ codeSource.getLocation());

View File

@@ -696,8 +696,8 @@ public class TypeRegistry {
// }
for (IsReloadableTypePlugin plugin : SpringLoadedPreProcessor.getIsReloadableTypePlugins()) {
ReloadDecision decision = plugin.shouldBeMadeReloadable(slashedName, protectionDomain, bytes);
if (decision == ReloadDecision.YES) {
ReloadDecision decision = plugin.shouldBeMadeReloadable(this,slashedName, protectionDomain, bytes);
if (decision == ReloadDecision.YES) {
return true;
} else if (decision == ReloadDecision.NO) {
return false;

View File

@@ -33,6 +33,7 @@ import org.springsource.loaded.Constants;
import org.springsource.loaded.GlobalConfiguration;
import org.springsource.loaded.IsReloadableTypePlugin;
import org.springsource.loaded.LoadtimeInstrumentationPlugin;
import org.springsource.loaded.Log;
import org.springsource.loaded.Plugin;
import org.springsource.loaded.ReloadableType;
import org.springsource.loaded.SystemClassReflectionRewriter;
@@ -158,7 +159,14 @@ public class SpringLoadedPreProcessor implements Constants {
// 3. If NO, and nothing in this classloader might be, return the original bytes
// 4. If YES, make the type reloadable (including rewriting call sites)
if (typeRegistry.isReloadableTypeName(slashedClassName, protectionDomain, bytes)) {
boolean isReloadableTypeName = typeRegistry.isReloadableTypeName(slashedClassName, protectionDomain, bytes);
// logging causes a ClassCircularity problem when reporting on:
// SL: Type 'org/codehaus/groovy/grails/cli/logging/GrailsConsolePrintStream' is not being made reloadable
// if (GlobalConfiguration.verboseMode && isReloadableTypeName) {
// Log.log("Type '"+slashedClassName+"' is preliminarily being considered a reloadable type");
// }
if (isReloadableTypeName) {
if (!firstReloadableTypeHit) {
firstReloadableTypeHit = true;
// TODO move into the ctor for ReloadableType so that it can't block loading
@@ -186,39 +194,66 @@ public class SpringLoadedPreProcessor implements Constants {
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
log.info("Appears to be a CGLIB type, checking if type " + originalType + " is reloadable");
}
if (typeRegistry.isReloadableTypeName(originalType)) {
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
log.info("Type " + originalType + " is reloadable, so making CGLIB type " + slashedClassName
+ " reloadable");
TypeRegistry currentRegistry = typeRegistry;
while (currentRegistry != null) {
ReloadableType originalReloadable = currentRegistry.getReloadableType(originalType);
if (originalReloadable != null) {
makeReloadableAnyway = true;
break;
}
makeReloadableAnyway = true;
currentRegistry = currentRegistry.getParentRegistry();
}
// if (typeRegistry.isReloadableTypeName(originalType)) {
// if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
// log.info("Type " + originalType + " is reloadable, so making CGLIB type " + slashedClassName
// + " reloadable");
// }
// makeReloadableAnyway = true;
// }
}
int cglibIndex2 = slashedClassName.indexOf("$$FastClassByCGLIB");
int cglibIndex2 = makeReloadableAnyway?-1:slashedClassName.indexOf("$$FastClassByCGLIB");
if (cglibIndex2 != -1) {
String originalType = slashedClassName.substring(0, cglibIndex2);
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
log.info("Appears to be a CGLIB FastClass type, checking if type " + originalType + " is reloadable");
}
if (typeRegistry.isReloadableTypeName(originalType)) {
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
log.info("Type " + originalType + " is reloadable, so making CGLIB type " + slashedClassName
+ " reloadable");
TypeRegistry currentRegistry = typeRegistry;
while (currentRegistry != null) {
ReloadableType originalReloadable = currentRegistry.getReloadableType(originalType);
if (originalReloadable != null) {
makeReloadableAnyway = true;
break;
}
makeReloadableAnyway = true;
currentRegistry = currentRegistry.getParentRegistry();
}
// if (typeRegistry.isReloadableTypeName(originalType)) {
// if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
// log.info("Type " + originalType + " is reloadable, so making CGLIB type " + slashedClassName
// + " reloadable");
// }
// makeReloadableAnyway = true;
// }
}
int proxyIndex = slashedClassName.indexOf("$Proxy");
int proxyIndex = makeReloadableAnyway?-1:slashedClassName.indexOf("$Proxy");
if (proxyIndex == 0 || (proxyIndex > 0 && slashedClassName.charAt(proxyIndex - 1) == '/')) {
// Determine if the interfaces being implemented are reloadable
String[] interfacesImplemented = Utils.discoverInterfaces(bytes);
if (interfacesImplemented != null) {
for (int i = 0; i < interfacesImplemented.length; i++) {
if (typeRegistry.isReloadableTypeName(interfacesImplemented[i])) {
makeReloadableAnyway = true;
TypeRegistry currentRegistry = typeRegistry;
while (currentRegistry != null) {
ReloadableType originalReloadable = currentRegistry.getReloadableType(interfacesImplemented[i]);
if (originalReloadable != null) {
makeReloadableAnyway = true;
break;
}
currentRegistry = currentRegistry.getParentRegistry();
}
// if (typeRegistry.isReloadableTypeName(interfacesImplemented[i])) {
// makeReloadableAnyway = true;
// }
}
}
}
@@ -234,8 +269,8 @@ 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.isRuntimeLogging && log.isLoggable(Level.INFO)) {
log.info("can't watch " + slashedClassName + ": not making it reloadable");
if (GlobalConfiguration.verboseMode) {
Log.log("Cannot watch "+slashedClassName+": not making it reloadable");
}
if (needsClientSideRewriting(slashedClassName)) {
bytes = typeRegistry.methodCallRewriteUseCacheIfAvailable(slashedClassName, bytes);