protectedObjectreadResolve(){if(gridBuilder==null){if(希利ctedJob!=null){gridBuilder=newDownstreamProjectGridBuilder(希利ctedJob);}}returnthis;}
Persistence is XML-based, and when the data is read back from disk and the XML does not contain data for a particular field, the existing field value is left as-is. "As-is" normally means the JVM default value (0,false
,null
, etc.), because the persistence logic does not invoke your constructor to create your object as Java serialization would.
There are a few exceptions to this, where objects first create themselves and then load XML onto themselves. These include必威国际有限公司
andItem
, but these exceptions are limited to the root object of persistence. In these exceptional cases, the values set in the constructor will survive.
If you want to fill in your field with a non-trivial default value, you can write areadResolve
method, which gets invoked right after your object is resurrected from persistence.readResolve
is called by XStream, but it is not part of the Jenkins class hierarchy, so there is no@Override
annotation. Just put it right in your class alongside your fields. For example:
protectedObjectreadResolve(){if(gridBuilder==null){if(希利ctedJob!=null){gridBuilder=newDownstreamProjectGridBuilder(希利ctedJob);}}returnthis;}
If you need to force theManage Old Datascreen to list jobs, builds, etc. using your data in the old format so that it can be saved in the new format in bulk, you cannot usereadResolve
, since it will not notify this system of the problem. Instead you must create a static nested class calledConverterImpl
extendingXStream2.PassthruConverter
, which should clean up the storage of your instance and finally callOldDataMonitor#report
to record the conversion.
Removing a field requires that you leave the field with thetransient
keyword. When Jenkins reads the old XML, XStream will set the value for this field, but it will no longer be written back to XML when data is saved.
Renaming a field is a combination of the above: mark your old field astransient
, declare your new field, and then migrate the data from the old format to the new in yourreadResolve
method:
protectedtransientLongmyObjectId;protectedList<Long>myObjectIds;protectedObjectreadResolve(){if(myObjectId!=null){myObjectIds=Arrays.asList(myObjectId)}returnthis;}
You decide to extend a class and create new choosable classes; for example, adding additional browsers to an SCM plugin.
The old data structure looked like this when you had only one classSCMBrowser
:
http://example.com/
Now you decide to add a newNewSCMBrowser
. All yourSCMBrowsers
extendSCMBrowserBase
and your XML suddenly looks like this:
class="org.jenkinsci.plugins.foo.NewSCMBrowser"> http://example.com/
or
class="org.jenkinsci.plugins.foo.SCMBrowser"> http://example.com/
With new jobs, no problem. Old jobs however will probably break.
In yourSCMBrowserBase
class add areadResolve
method (see theXStream FAQ):
// compatibility with earlier pluginspublicObjectreadResolve(){if(this.getClass()!=SCMBrowserBase.class){returnthis;}// make sure to return the default SCMBrowser only if we no class is given in config.try{returnnewSCMBrowser(url.toExternalForm());}catch(MalformedURLExceptione){thrownewRuntimeException(e);}}
Sometimes, you need to rename packages or class names. If your serialization data includes a fully qualified class name (which happens, for example, if you have a collection of them), then measures must be taken to maintain backward compatibility.
To do this, useXSTREAM2#addCompatibilityAlias(String, Class)
to register aliases. You need to do this against the right XStream instance, as a few different instances are used to persist different parts of data.
Items#XSTREAM2
is used for serializing project configuration, andRun#XSTREAM2
is used for serializing builds and their associatedAction
s.
For example, to aliasFoo
in the "old" package to the "updated" one, you can use this method call:
Items.XSTREAM2.addCompatibilityAlias("org.acme.old.Foo",org.acme.updated.Foo.class);
To ensure your alias is registered early in the Jenkins boot sequence, you can use theInitializer
annotation on a static method, e.g. in yourDescriptorImpl
:
@Initializer(before=InitMilestone.PLUGINS_STARTED)publicstaticvoidaddAliases(){Items.XSTREAM2.addCompatibilityAlias("org.acme.old.Foo",Foo.class);}
Since 1.507Descriptor#getConfigFile
is overridable andXmlFile
can be instantiated with any XStream instance.