📜 ⬆️ ⬇️

How do we find the inaccessible model

About sad

I sat yesterday at the next interview, I was sad that javax rushes at me and listened to the sad story of the applicant about how he suffered trying to screw the serialization in JSON to the Java model without having its source code. From the look of his attempts, my mood has not improved.
We will try better because, unlike him, we know about Groovy.
This post is more or less a continuation of yesterday . They would like to kill a bag of hares:
  1. Show a real-life Groovy meta-programming example.
  2. Show a slightly more sophisticated way to work with meta-classes.
  3. Show work with json-lib in Groovy
  4. Tell about dependency management for the poor


What do we have with goose java


We have a model from JavaBeans written in Java, and for example we assume that we do not have sorts. Also for the sake of interest, suppose that this model does not have a common interface and / or superclass, and all that this model unites is a package, so forget about any polymorphism. For example, let's assume that the model already has 2 classes - model.Clock and model.Radio, both extend Object, and they have a couple of fields - strings and primitives.

What do you need?


Here is a test that will take place by the end of this article:
import model.Radio import service.Service Radio radio = new Radio() radio.frequency = 91.2 radio.volume = 20 def json = radio.toJson() assert json == '{"frequency":91.2,"volume":20}' Radio radioClone = Radio.parseJson(json) assert radioClone == radio try { new Service().toJson() assert false } catch (MissingMethodException e){} 


Naturally, neither the toJson () method nor the parseJson () method in the Radio class exists, and therefore our test falls from “groovy.lang.MissingMethodException: No signature of the method: Radio.toJson () is () values: [] ".
Check with try at the end - the Service class, which is not related to the model’s package, does not have toJson and parseJson methods as a result of our shamanism.
')

Who is to blame What to do?


As you, of course, already know (at least from yesterday) that with the help of MetaClass of any class you can add and replace methods. So, we need to add the toJson () and parseJson () methods to the MetaClasses of all classes from the package model. But as? In Java, this is all bad - you cannot get a list of classes from a package. Well, that is possible, but not quite, not always, only from jar-s, and a lot of restrictions and a sheet of code. This is not our method. We are Groovy.
Groovy absolutely wedges in the classloading. For example, to create MetaClass. And since we are still going to mess with them, we can break into the process of creating them!
In the groovy.lang.MetaClassRegistry class, we look at the creation code of the Handler, which is engaged in the creation of MetaClasses (I simplified the code a little, but the point is this):
  try { Class customHandle = Class.forName("groovy.runtime.metaclass.CustomMetaClassCreationHandle"); this.handle = customHandle.newInstance(); } catch (ClassNotFoundException e) { // custom handler,    this.handle = new MetaClassCreationHandle(); } 

- Sherlock, but what is it?
- It's elementary, Watson. This is one of the extension points that allows us to write our own MetaClassCreationHandle, in which we will create our own MetaClasses, with new methods and jason.
Everything is clear, Groovy is looking for a class called “groovy.runtime.metaclass.CustomMetaClassCreationHandle”, and we will give it to him:
 public class CustomMetaClassCreationHandle extends MetaClassRegistry.MetaClassCreationHandle { protected MetaClass createNormalMetaClass(Class theClass, MetaClassRegistry registry) { Package thePackage = theClass.getPackage(); if (thePackage != null && thePackage.getName().equals("model")) { return new Jsonizer(theClass); } else { return super.createNormalMetaClass(theClass, registry); } } } 

If the package is ours - we give our MetaClass, if not - our own. I think - credit.
Notice the silly semicolons. That's right, CustomMetaClassCreationHandle must be a Java class, not Groovy, because here is the chicken and the egg - to create a Groovy class you need the already created MetaClassCreationHandle.

All we have to do is to write our own MetaClass, in which we add one ordinary (instance) and one static method.
Here, you probably need to sketch a small work plan:
  1. Inherit from ExpandoMetaClass (this is the one to which you can add methods)
  2. Add a toJson method in which Json-lib will serialize this into json string
  3. Add a static parseJson method in which Json-lib will deserialize json string into an object

And here is a list of questions and answers on the work plan (campaign, I love lists) :

We look:
(Newly supported “doggie” for marking users breaks the annotation announcement code, so there is an “a” instead of you know what. Well, you understand.)
 groovy.lang.Grapes([ GrabResolver(name = 'rjo', root = 'http://repo.jfrog.org/artifactory/libs-releases'), Grab(group = 'net.sf.json-lib', module = 'json-lib', version = '2.4', classifier = 'jdk15')]) class Jsonizer extends ExpandoMetaClass { Jsonizer(Class theClass) { super(theClass) toJson << {-> fromObject(delegate).toString() } static.parseJson << {String json -> toBean(fromObject(json), theClass); } } } 

Well, in general, profit. The test passes, Clock and Radio have grown methods, Service does not, and you, I hope, enjoyed the process.

Conclusions are also necessary, and then.


  1. If you think “how would I screw up here, without touching what is already there, and / or to work EVERYWHERE,” most likely you are thinking about meta-programming.
  2. Meta programming in Java is best done in Groovy.
  3. Groovy has a lot of extension points, look for it, and most likely you will find it (and if you do not find it, open the feature in JIRA, and in the next version you will definitely find it).

Source: https://habr.com/ru/post/144407/


All Articles