-
-
Save jroper/c1ab6a9842ee489f55de to your computer and use it in GitHub Desktop.
/** | |
* 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 => | |
} | |
} | |
} |
I always thought that Properties
are supposed to be treated as only Strings, as the Properties JavaDoc clearly states:
Because
Properties
inherits fromHashtable
, theput
andputAll
methods can be applied to aProperties
object. Their use is strongly discouraged as they allow the caller to insert entries whose keys or values are notStrings
. ThesetProperty
method should be used instead. If thestore
orsave
method is called on a "compromised"Properties
object that contains a non-String
key or value, the call will fail. Similarly, the call to thepropertyNames
orlist
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 👍
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()
@hho Yes, you shouldn't put non Strings into Properties. But you also shouldn't be using global variables. Two wrongs make a right!
@jroper good
Before anyone mentions it - yes, I know you can use
java.lang.System.getProperties.put
andjava.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, usingProperties.getKeys
instead ofProperties.stringPropertyNames
, and then casting the value toString
without checking its type. I'm looking at you ivy. For me, this broke ivy, so unfortunately that wasn't an option.