@@ -44,15 +44,15 @@ import org.springsource.loaded.ri.JavaMethodCache;
|
||||
|
||||
/**
|
||||
* Represents a type that has been processed such that it can be reloaded at runtime.
|
||||
*
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class ReloadableType {
|
||||
|
||||
// TODO when a field is shadowed or renamed and the old one never accessed again, it may be holding onto something and prevent it from GC.
|
||||
// Thinking about a solution that involves a tag in the FieldAccessor object so that we can
|
||||
// check whether a 'repair' is needed on a field accessor (because the type has been reloaded and
|
||||
// Thinking about a solution that involves a tag in the FieldAccessor object so that we can
|
||||
// check whether a 'repair' is needed on a field accessor (because the type has been reloaded and
|
||||
// the map in the accessor hasnt been repaired yet)
|
||||
private static Logger log = Logger.getLogger(ReloadableType.class.getName());
|
||||
|
||||
@@ -140,13 +140,14 @@ public class ReloadableType {
|
||||
return clazz;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return dottedtypename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new ReloadableType with the specified name and the specified initial bytecode.
|
||||
*
|
||||
*
|
||||
* @param dottedtypename the dotted name
|
||||
* @param initialBytes the bytecode for the initial version
|
||||
* @param id for this reloadable type, allocated by the registry
|
||||
@@ -190,7 +191,7 @@ public class ReloadableType {
|
||||
/**
|
||||
* Gets the 'orignal' method corresponding to given name and method descriptor. This only considers methods that
|
||||
* exist in the first (non-reloaded) version of the type.
|
||||
*
|
||||
*
|
||||
* @param name method name
|
||||
* @param descriptor method descriptor (e.g (Ljava/lang/String;)I)
|
||||
* @return the MethodMember or an exception if not found
|
||||
@@ -221,7 +222,7 @@ public class ReloadableType {
|
||||
* This method will attempt to apply any pre-existing transforms to the provided bytecode, if it is thought to be
|
||||
* necessary. Currently 'necessary' is determined by finding ourselves running under tcServer and Spring Insight
|
||||
* being turned on.
|
||||
*
|
||||
*
|
||||
* @param bytes the new bytes to be possibly transformed.
|
||||
* @return either the original bytes or a transformed set of bytes
|
||||
*/
|
||||
@@ -300,7 +301,7 @@ public class ReloadableType {
|
||||
|
||||
/**
|
||||
* Load a new version of this type, using the specified suffix to tag the newly generated artifact class names.
|
||||
*
|
||||
*
|
||||
* @param versionsuffix the String suffix to append to classnames being created for the reloaded class
|
||||
* @param newbytedata the class bytes for the new version of this class
|
||||
* @return true if the reload succeeded
|
||||
@@ -328,16 +329,17 @@ public class ReloadableType {
|
||||
// Not allowed to change the type
|
||||
reload = false;
|
||||
|
||||
s = new StringBuilder("Spring Loaded: Cannot reload new version of ").append(this.dottedtypename).append(
|
||||
"\n");
|
||||
s = new StringBuilder("Spring Loaded: Cannot reload new version of ").append(
|
||||
this.dottedtypename).append(
|
||||
"\n");
|
||||
if (td.hasTypeAccessChanged()) {
|
||||
s.append(" Reason: Type modifiers changed\n");
|
||||
s.append(" Reason: Type modifiers changed from=0x" + Integer.toHexString(td.oAccess) + " to=0x"
|
||||
+ Integer.toHexString(td.nAccess) + "\n");
|
||||
cantReload = true;
|
||||
}
|
||||
if (td.hasTypeSupertypeChanged()) {
|
||||
s.append(" Reason: Supertype changed from ").append(td.oSuperName).append(" to ").append(
|
||||
td.nSuperName)
|
||||
.append("\n");
|
||||
td.nSuperName).append("\n");
|
||||
cantReload = true;
|
||||
}
|
||||
if (td.hasTypeInterfacesChanged()) {
|
||||
@@ -370,8 +372,8 @@ public class ReloadableType {
|
||||
}
|
||||
}
|
||||
if (!justGroovyObjectMoved) {
|
||||
s.append(" Reason: Interfaces changed from ").append(td.oInterfaces).append(" to ")
|
||||
.append(td.nInterfaces).append("\n");
|
||||
s.append(" Reason: Interfaces changed from ").append(td.oInterfaces).append(" to ").append(
|
||||
td.nInterfaces).append("\n");
|
||||
cantReload = true;
|
||||
}
|
||||
}
|
||||
@@ -535,17 +537,19 @@ public class ReloadableType {
|
||||
/**
|
||||
* Go through proxies we know about in this registry and see if any of them are for the type we have just reloaded.
|
||||
* If they are, regenerate them and reload them.
|
||||
*
|
||||
*
|
||||
* @param versionsuffix the suffix to use when reloading the proxies (it matches what is being used to reload the
|
||||
* type)
|
||||
*/
|
||||
private void reloadProxiesIfNecessary(String versionsuffix) {
|
||||
ReloadableType proxy = typeRegistry.cglibProxies.get(this.slashedtypename);
|
||||
if (proxy != null) {
|
||||
Object[] strategyAndGeneratorPair = CglibPluginCapturing.clazzToGeneratorStrategyAndClassGeneratorMap.get(getClazz());
|
||||
Object[] strategyAndGeneratorPair = CglibPluginCapturing.clazzToGeneratorStrategyAndClassGeneratorMap.get(
|
||||
getClazz());
|
||||
if (strategyAndGeneratorPair == null) {
|
||||
if (log.isLoggable(Level.SEVERE)) {
|
||||
log.severe("Unable to find regeneration methods for cglib proxies - proxies will be out of date for this type");
|
||||
log.severe(
|
||||
"Unable to find regeneration methods for cglib proxies - proxies will be out of date for this type");
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -572,9 +576,10 @@ public class ReloadableType {
|
||||
|
||||
proxy = typeRegistry.cglibProxiesFastClass.get(this.slashedtypename);
|
||||
if (proxy != null) {
|
||||
Object[] strategyAndFCGeneratorPair = CglibPluginCapturing.clazzToGeneratorStrategyAndFastClassGeneratorMap
|
||||
.get(getClazz());
|
||||
strategyAndFCGeneratorPair = CglibPluginCapturing.clazzToGeneratorStrategyAndFastClassGeneratorMap.get(getClazz());
|
||||
Object[] strategyAndFCGeneratorPair = CglibPluginCapturing.clazzToGeneratorStrategyAndFastClassGeneratorMap.get(
|
||||
getClazz());
|
||||
strategyAndFCGeneratorPair = CglibPluginCapturing.clazzToGeneratorStrategyAndFastClassGeneratorMap.get(
|
||||
getClazz());
|
||||
// System.out.println("need to reload fastclass " + proxy + " os=" + os);
|
||||
if (strategyAndFCGeneratorPair != null) {
|
||||
Object a = strategyAndFCGeneratorPair[0];
|
||||
@@ -617,8 +622,8 @@ public class ReloadableType {
|
||||
}
|
||||
}
|
||||
catch (Throwable t) {
|
||||
new RuntimeException("Unexpected problem trying to reload proxy for interface " + this.dottedtypename, t)
|
||||
.printStackTrace();
|
||||
new RuntimeException("Unexpected problem trying to reload proxy for interface " + this.dottedtypename,
|
||||
t).printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -744,7 +749,7 @@ public class ReloadableType {
|
||||
Class<?> class_ClassInfo = typeRegistry.getClass_ClassInfo();
|
||||
Field field_globalClassSet = class_ClassInfo.getDeclaredField("globalClassSet");
|
||||
field_globalClassSet.setAccessible(true);
|
||||
Object/*ClassInfoSet*/instance_classInfoSet = field_globalClassSet.get(null);
|
||||
Object/*ClassInfoSet*/ instance_classInfoSet = field_globalClassSet.get(null);
|
||||
Method method_ClassInfoSetRemove = instance_classInfoSet.getClass().getMethod("remove", Object.class);
|
||||
Object retval = method_ClassInfoSetRemove.invoke(instance_classInfoSet, this.clazz);
|
||||
|
||||
@@ -853,7 +858,7 @@ public class ReloadableType {
|
||||
/**
|
||||
* Gets the method corresponding to given name and descriptor, taking into consideration changes that have happened
|
||||
* by reloading.
|
||||
*
|
||||
*
|
||||
* @param name the member name
|
||||
* @param descriptor the member descriptor (e.g. (Ljava/lang/String;)I)
|
||||
* @return the MethodMember for that name and descriptor. Null if not found on a live version, or an exception if
|
||||
@@ -870,7 +875,7 @@ public class ReloadableType {
|
||||
|
||||
/**
|
||||
* Gets the method corresponding to given name and descriptor, from the original type descriptor.
|
||||
*
|
||||
*
|
||||
* @param nameAndDescriptor the method name and descriptor (e.g. foo(Ljava/lang/String;)I)
|
||||
* @return the MethodMember for the name and descriptor if it exists, otherwise null
|
||||
*/
|
||||
@@ -973,7 +978,7 @@ public class ReloadableType {
|
||||
RewriteClassAdaptor rca = (RewriteClassAdaptor) cv;
|
||||
if (rca.isEnum && rca.fieldcount > GlobalConfiguration.enumLimit) {
|
||||
// that is too many fields, marking this as not reloadable
|
||||
// TODO ...
|
||||
// TODO ...
|
||||
}
|
||||
TypeRewriter.RewriteClassAdaptor a = (TypeRewriter.RewriteClassAdaptor) rca.getClassVisitor();
|
||||
return ((ClassWriter) a.getClassVisitor()).toByteArray();
|
||||
@@ -1028,7 +1033,7 @@ public class ReloadableType {
|
||||
|
||||
/**
|
||||
* Check if the specified method is different to the original form from the type as loaded.
|
||||
*
|
||||
*
|
||||
* @param methodId the ID of the method currently executing
|
||||
* @return 0 if the method cannot have changed. 1 if the method has changed. 2 if the method has been deleted in a
|
||||
* new version.
|
||||
@@ -1116,7 +1121,7 @@ public class ReloadableType {
|
||||
/**
|
||||
* Intended to handle dynamic dispatch. This will determine the right type to handle the specified method and return
|
||||
* a dispatcher that can handle it.
|
||||
*
|
||||
*
|
||||
* @param instance the target instance for the invocation
|
||||
* @param nameAndDescriptor an encoded method name and descriptor, e.g. foo(Ljava/langString;)V
|
||||
* @return a dispatcher that can handle the method indicated
|
||||
@@ -1255,7 +1260,7 @@ public class ReloadableType {
|
||||
* the non-reloadable types. This method also avoids interfaces because it is looking for instance fields. This is
|
||||
* slightly naughty but if we assume the code we are reloading is valid code, it should never be referring to
|
||||
* interface fields.
|
||||
*
|
||||
*
|
||||
* @param name the name of the field to locate
|
||||
* @return the FieldMember or null if the field is not found
|
||||
*/
|
||||
@@ -1283,7 +1288,7 @@ public class ReloadableType {
|
||||
* Search for a static field from this type upwards, as far as the topmost reloadable types. This is searching for a
|
||||
* field, it is not checking the result. It is up to the caller to check they have not ended up with an instance
|
||||
* field and throw the appropriate exception.
|
||||
*
|
||||
*
|
||||
* @param name the name of the field to look for
|
||||
* @return a FieldMember for the named field or null if not found
|
||||
*/
|
||||
@@ -1320,7 +1325,7 @@ public class ReloadableType {
|
||||
|
||||
/**
|
||||
* Attempt to set the value of a field on an instance to the specified value.
|
||||
*
|
||||
*
|
||||
* @param instance the object upon which to set the field (maybe null for static fields)
|
||||
* @param fieldname the name of the field
|
||||
* @param isStatic whether the field is static
|
||||
@@ -1349,7 +1354,8 @@ public class ReloadableType {
|
||||
}
|
||||
}
|
||||
|
||||
private Set<WeakReference<Object>> liveInstances = Collections.synchronizedSet(new HashSet<WeakReference<Object>>());
|
||||
private Set<WeakReference<Object>> liveInstances = Collections.synchronizedSet(
|
||||
new HashSet<WeakReference<Object>>());
|
||||
|
||||
private ReferenceQueue<Object> liveInstancesRQ = new ReferenceQueue<Object>();
|
||||
|
||||
@@ -1361,7 +1367,7 @@ public class ReloadableType {
|
||||
/**
|
||||
* Attempt to set the value of a field on an instance to the specified value. Simply locate the field, which returns
|
||||
* an object capable of reading/writing it, then use that to retrieve the value.
|
||||
*
|
||||
*
|
||||
* @param instance the object upon which to set the field (maybe null for static fields)
|
||||
* @param fieldname the name of the field
|
||||
* @param isStatic whether the field is static or not
|
||||
@@ -1607,7 +1613,7 @@ public class ReloadableType {
|
||||
* Return the ReloadableType representing the superclass of this type. If the supertype is not reloadable, this
|
||||
* method will return null. The ReloadableType that is returned may not be within the same type registry, if the
|
||||
* supertype was loaded by a different classloader.
|
||||
*
|
||||
*
|
||||
* @return the ReloadableType for the supertype or null if it is not reloadable
|
||||
*/
|
||||
public ReloadableType getSuperRtype() {
|
||||
|
||||
@@ -26,11 +26,14 @@ import java.lang.reflect.Method;
|
||||
import org.objectweb.asm.Handle;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
import org.springsource.loaded.CurrentLiveVersion;
|
||||
import org.springsource.loaded.MethodMember;
|
||||
import org.springsource.loaded.ReloadableType;
|
||||
import org.springsource.loaded.TypeRegistry;
|
||||
|
||||
/**
|
||||
* This class encapsulates dependencies on Java 8 APIs (e.g. LambdaMetafactory).
|
||||
*
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 1.2
|
||||
*/
|
||||
@@ -38,30 +41,30 @@ public class Java8 {
|
||||
|
||||
/**
|
||||
* Notes:
|
||||
*
|
||||
*
|
||||
* Useful to have an example of how this code behaves. Here is a bit of code:
|
||||
*
|
||||
*
|
||||
* class basic.LambdaA { interface Foo { int m(); } static int run() { Foo f = null; f = () -> 77; return f.m(); } }
|
||||
*
|
||||
*
|
||||
* Here is a bootstrap method entry in the constant pool:
|
||||
*
|
||||
*
|
||||
* 0: #31 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:
|
||||
* (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/
|
||||
* lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;
|
||||
* Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; Method arguments: #32
|
||||
* ()I #33 invokestatic basic/LambdaA.lambda$run$0:()I #32 ()I
|
||||
*
|
||||
*
|
||||
* At the invokedynamic site: bsmId = 0 nameAndDescriptor = m()Lbasic/LambdaA$Foo;
|
||||
*
|
||||
*
|
||||
* When invoking the metafactory bootstrap method the first two parameters are stacked by the VM automatically,
|
||||
* namely the MethodHandles$Lookup instance (caller) and the first String (invokedName). What the VM actually sees
|
||||
* is this:
|
||||
*
|
||||
*
|
||||
* metaFactory parameters: 0:MethodHandles$Lookup caller = basic.LambdaA 1:String invokedName = "m" 2:MethodType
|
||||
* invokedType = "()Foo" 3:MethodType samMethodType = "()int" 4:MethodHandle implMethod = (actually a
|
||||
* DirectMethodHandle where memberName is "basic.LambdaA.lambda$run$0()int/invokeStatic") 5:MethodType
|
||||
* instantiatedMethodType = "()int"
|
||||
*
|
||||
*
|
||||
* With all that information then the calls in this case are relatively straightforward: CallSite callsite =
|
||||
* LambdaMetafactory.metafactory(caller, invokedName, invokedType, samMethodType, implMethod,
|
||||
* instantiatedMethodType); callsite.dynamicInvoker().invokeWithArguments((Object[])null);
|
||||
@@ -70,7 +73,7 @@ public class Java8 {
|
||||
/**
|
||||
* Programmatic emulation of INVOKEDYNAMIC so initialize the callsite via use of the bootstrap method then invoke
|
||||
* the result.
|
||||
*
|
||||
*
|
||||
* @param executorClass the executor that will contain the lambda function, null if not yet reloaded
|
||||
* @param handle bootstrap method handle
|
||||
* @param bsmArgs bootstrap method arguments
|
||||
@@ -104,9 +107,9 @@ public class Java8 {
|
||||
MethodType invokedType = MethodType.fromMethodDescriptorString(
|
||||
indyNameAndDescriptor.substring(descriptorStart), callerLoader);
|
||||
|
||||
// Use bsmArgs to build the parameters
|
||||
// Use bsmArgs to build the parameters
|
||||
MethodType samMethodType = MethodType.fromMethodDescriptorString(
|
||||
(String) (((Type) bsmArgs[0]).getDescriptor()), callerLoader);
|
||||
(((Type) bsmArgs[0]).getDescriptor()), callerLoader);
|
||||
|
||||
Handle bsmArgsHandle = (Handle) bsmArgs[1];
|
||||
String owner = bsmArgsHandle.getOwner();
|
||||
@@ -121,7 +124,7 @@ public class Java8 {
|
||||
implMethod = caller.findStatic(caller.lookupClass(), name, implMethodType);
|
||||
break;
|
||||
case Opcodes.H_INVOKESPECIAL:
|
||||
// If there is an executor, the lambda function is actually modified from 'private instance' to 'public static' so adjust lookup. The method
|
||||
// If there is an executor, the lambda function is actually modified from 'private instance' to 'public static' so adjust lookup. The method
|
||||
// will be static with a new leading parameter.
|
||||
if (executorClass == null) {
|
||||
// TODO is final parameter here correct?
|
||||
@@ -133,15 +136,56 @@ public class Java8 {
|
||||
}
|
||||
break;
|
||||
case Opcodes.H_INVOKEVIRTUAL:
|
||||
// If there is an executor, the lambda function is actually modified from 'private instance' to 'public static' so adjust lookup. The method
|
||||
// will be static with a new leading parameter.
|
||||
if (executorClass == null) {
|
||||
// TODO when can this scenario occur? Aren't we only here if reloading has happened?
|
||||
implMethod = caller.findVirtual(caller.lookupClass(), name, implMethodType);
|
||||
// There is a possibility to 'shortcut' here. Basically we are trying to resolve a callsite reference
|
||||
// to the method that satisfies it. The easiest option is to just find the method on the originally
|
||||
// loaded version of the target class and return that. A more optimal shortcut could return the
|
||||
// method on the executor class if the target has been reloaded (effectively bypassing the method
|
||||
// on the originally loaded version since we know that it will be acting as a pass through). But this
|
||||
// opens up a can of worms related to visibility. The executor is loaded into the child classloader,
|
||||
// and if the caller has not been reloaded it will not be able to 'see' the executor (since it is in
|
||||
// a child classloader). So, basically keep this dumb (but reliable) for now.
|
||||
|
||||
TypeRegistry typeRegistry = rtype.getTypeRegistry();
|
||||
ReloadableType ownerRType = typeRegistry.getReloadableType(owner);
|
||||
if (null == ownerRType || !ownerRType.hasBeenReloaded()) {
|
||||
// target containing the reference/lambdaMethod has not been reloaded, no need to get over
|
||||
// complicated.
|
||||
Class<?> ownerClazz = ownerRType.getClazz();
|
||||
implMethod = caller.findVirtual(ownerClazz, name, implMethodType);
|
||||
}
|
||||
else {
|
||||
implMethod = caller.findStatic(caller.lookupClass(), name, MethodType.fromMethodDescriptorString(
|
||||
"(L" + owner + ";" + descriptor.substring(1), callerLoader));
|
||||
MethodMember targetReferenceMethodMember = ownerRType.getCurrentMethod(name, descriptor);
|
||||
String targetReferenceDescriptor = targetReferenceMethodMember.getDescriptor();
|
||||
MethodType targetReferenceMethodType = MethodType.fromMethodDescriptorString(
|
||||
targetReferenceDescriptor, callerLoader);
|
||||
Class<?> targetReferenceClass = ownerRType.getClazz();
|
||||
MethodMember currentMethod = ownerRType.getCurrentMethod(name, descriptor);
|
||||
|
||||
if (currentMethod.original == null) {
|
||||
// null means this method did not exist on the original version of the target.
|
||||
// Assert that the caller must have been reloaded, otherwise how would it
|
||||
// have a reference to something that did not exist on the first version of the type. In that
|
||||
// case we know we can return the method on the executor class because both the reloaded
|
||||
// caller and reloaded target are in the same child classloader (no visibility problem).
|
||||
if (!rtype.hasBeenReloaded()) {
|
||||
throw new IllegalStateException(
|
||||
"Assertion violated: When a method added on reload is being referenced"
|
||||
+ "in target type '" + ownerRType.getName()
|
||||
+ "', expected the caller to also have been reloaded: '"
|
||||
+ rtype.getName() + "'");
|
||||
}
|
||||
CurrentLiveVersion ownerLiveVersion = ownerRType.getLiveVersion();
|
||||
Class<?> ownerExecutorClass = ownerLiveVersion.getExecutorClass();
|
||||
Method executorMethod = ownerLiveVersion.getExecutorMethod(currentMethod);
|
||||
String methodDescriptor = Type.getType(executorMethod).getDescriptor();
|
||||
MethodType type = MethodType.fromMethodDescriptorString(methodDescriptor, callerLoader);
|
||||
implMethod = caller.findStatic(ownerExecutorClass, name, type);
|
||||
}
|
||||
else {
|
||||
// This finds the reference method on the originally loaded class. It will pass through
|
||||
// to the actual code on the reloaded version.
|
||||
implMethod = caller.findVirtual(targetReferenceClass, name, targetReferenceMethodType);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Opcodes.H_INVOKEINTERFACE:
|
||||
@@ -150,7 +194,7 @@ public class Java8 {
|
||||
// TODO Should there not be a more direct way to this than classloading?
|
||||
// TODO What about when this is a method added to the interface on a reload? It won't really exist, should we point
|
||||
// to the executor? or something else? (maybe just directly the real method that will satisfy the interface - if it can be worked out)
|
||||
Class<?> interfaceClass = callerLoader.loadClass(interfaceOwner.replace('/', '.')); // interface type, eg StreamB$Foo
|
||||
Class<?> interfaceClass = callerLoader.loadClass(interfaceOwner.replace('/', '.')); // interface type, eg StreamB$Foo
|
||||
implMethod = caller.findVirtual(interfaceClass, name, implMethodType);
|
||||
break;
|
||||
default:
|
||||
@@ -158,16 +202,17 @@ public class Java8 {
|
||||
}
|
||||
|
||||
MethodType instantiatedMethodType = MethodType.fromMethodDescriptorString(
|
||||
(String) (((Type) bsmArgs[2]).getDescriptor()), callerLoader);
|
||||
(((Type) bsmArgs[2]).getDescriptor()), callerLoader);
|
||||
|
||||
return LambdaMetafactory.metafactory(caller, invokedName, invokedType, samMethodType, implMethod,
|
||||
instantiatedMethodType);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The metafactory we are enhancing is responsible for generating the anonymous classes that will call the lambda
|
||||
* methods in our type
|
||||
*
|
||||
*
|
||||
* @param bytes the class bytes for the InnerClassLambdaMetaFactory that is going to be modified
|
||||
* @return the class bytes for the modified InnerClassLambdaMetaFactory
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user