GRAILS-7799: Adjust treatment of CGLIB generated types
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
16
springloaded/src/main/java/org/springsource/loaded/Log.java
Normal file
16
springloaded/src/main/java/org/springsource/loaded/Log.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user