-
-
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
Propertiesinherits fromHashtable, theputandputAllmethods can be applied to aPropertiesobject. Their use is strongly discouraged as they allow the caller to insert entries whose keys or values are notStrings. ThesetPropertymethod should be used instead. If thestoreorsavemethod is called on a "compromised"Propertiesobject that contains a non-Stringkey or value, the call will fail. Similarly, the call to thepropertyNamesorlistmethod will fail if it is called on a "compromised"Propertiesobject that contains a non-Stringkey.
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.putandjava.lang.System.getProperties.getto 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.getKeysinstead ofProperties.stringPropertyNames, and then casting the value toStringwithout checking its type. I'm looking at you ivy. For me, this broke ivy, so unfortunately that wasn't an option.