Skip to content

Instantly share code, notes, and snippets.

@jroper
Last active September 25, 2017 21:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jroper/c1ab6a9842ee489f55de to your computer and use it in GitHub Desktop.
Save jroper/c1ab6a9842ee489f55de to your computer and use it in GitHub Desktop.
Taking global static variables to a new level on the JVM...
/**
* Provides a global (cross classloader) static var.
*
* Where might you use this? Anywhere where you want to be evil ;)
*
* But, my use case was in a dynamic classloading environment where you want to load a library that
* depends on a native library. Only one classloader can ever load and link the classes associated
* with the native library, so if a second classloader (for example if a dynamic reload was done)
* wanted to use it to, it couldn't. This provided a cross classloader mechanism for storing the
* classloader that loaded the native library.
*
* Of course, it's not thread safe.
*
* This does not leak classloaders (unless the value passed to it references a classloader that
* shouldn't be leaked). It uses an MBeanServer to store an AtomicReference as an mbean, exposing
* the get method of the AtomicReference as an mbean operation, so that the value can be retrieved.
*/
object GlobalStaticVar {
import javax.management._
import javax.management.modelmbean._
import java.lang.management._
import java.util.concurrent.atomic.AtomicReference
import scala.reflect.ClassTag
private def objectName(name: String) = {
new ObjectName(":type=GlobalStaticVar,name=" + name)
}
/**
* Set a global static variable with the given name.
*/
def set(name: String, value: AnyRef): Unit = {
val reference = new AtomicReference[AnyRef](value)
// Now we construct a MBean that exposes the AtomicReference.get method
val getMethod = classOf[AtomicReference[_]].getMethod("get")
val getInfo = new ModelMBeanOperationInfo("The value", getMethod)
val mmbi = new ModelMBeanInfoSupport("GlobalStaticVar",
"A global static variable",
null, // no attributes
null, // no constructors
Array(getInfo), // the operation
null); // no notifications
val mmb = new RequiredModelMBean(mmbi)
mmb.setManagedResource(reference, "ObjectReference")
// Register the Model MBean in the MBean Server
ManagementFactory.getPlatformMBeanServer.registerMBean(mmb, objectName(name))
}
/**
* Get a global static variable by the given name.
*/
def get[T](name: String)(implicit ct: ClassTag[T]): Option[T] = {
try {
val value = ManagementFactory.getPlatformMBeanServer.invoke(objectName(name), "get", Array.empty, Array.empty)
if (ct.runtimeClass.isInstance(value)) {
Some(value.asInstanceOf[T])
} else {
throw new ClassCastException(s"Global static var $name is not an instance of ${ct.runtimeClass}, but is actually a ${Option(value).fold("null")(_.getClass.getName)}")
}
} catch {
case e: InstanceNotFoundException =>
None
}
}
/**
* Clear a global static variable with the given name.
*/
def remove(name: String): Unit = {
try {
ManagementFactory.getPlatformMBeanServer.unregisterMBean(objectName(name))
} catch {
case e: InstanceNotFoundException =>
}
}
}
@jroper
Copy link
Author

jroper commented Jul 30, 2014

Before anyone mentions it - yes, I know you can use java.lang.System.getProperties.put and java.lang.System.getProperties.get to achieve the same thing. But there is a major downside to this: some libraries are very bad, and treat the system Properties object as if every value in there is a String, using Properties.getKeys instead of Properties.stringPropertyNames, and then casting the value to String without checking its type. I'm looking at you ivy. For me, this broke ivy, so unfortunately that wasn't an option.

@hho
Copy link

hho commented Jul 30, 2014

I always thought that Properties are supposed to be treated as only Strings, as the Properties JavaDoc clearly states:

Because Properties inherits from Hashtable, the put and putAll methods can be applied to a Properties object. Their use is strongly discouraged as they allow the caller to insert entries whose keys or values are not Strings. The setProperty method should be used instead. If the store or save method is called on a "compromised" Properties object that contains a non-String key or value, the call will fail. Similarly, the call to the propertyNames or list method will fail if it is called on a "compromised" Properties object that contains a non-String key.

That said, the MBean idea looks devilishly awesome 👍

@viktorklang
Copy link

If you instead of making it an "object GlobalStaticVar" make it a "trait GlobalStaticVar[T] { self: Singleton =>" then you can remove the need for casting.

// Declaration
object GlobalFoo extends GlobalStaticVar[Foo]
// Usage:
GlobalFoo.set(new Foo("pigdog"))
GlobalFoo.get()
GlobalFoo.clear()

@jroper
Copy link
Author

jroper commented Jul 30, 2014

@hho Yes, you shouldn't put non Strings into Properties. But you also shouldn't be using global variables. Two wrongs make a right!

@nayanSingh
Copy link

@jroper good

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment