Supercharge your Kotlin with Java Dynamic Proxies

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hello it's Duncan we left the last episode with the realization that we can't automatically delegate from an interface of suspend functions to an interface of non- suspend functions that seems like quite an Arcane problem but the solution is generally very useful it turns out we can Implement interfaces at runtime running Dynamic code to respond to Method calls this can save us from writing lots of boiler plate code and is used inde dependence injection object relational mapping and mopping and mocking unfortunately this technique is only available on the JVS so if you're a cotlin purist You may wish to look away now for the rest of us let's have fun with Dynamic proxies is that good it certainly is this is where we ended up at the end of the last episode we'd extracted this customers repository type and an implementation of it in terms of mutable list now this in memory customers is very much not thread safe which is something we might look at later but we have another problem which is that we want a co-routines version of this customers this co- customers here that is composed of suspend functions so that we don't have to block a thread if we're going to talk to an actual database now unfortunately we can't merge these two interfaces because the way that we call a suspend function the non- suspend function is very different the compiler actually has to build the state machine around the call of a suspend function that I think is just the truth but another truth that's more irritating is that we can't implement this co- customers in terms of inmemory customers well I mean we can but we have to do it by hand we either have to build a new version of inmemory customers or I suppose we could say that class Co in memory customers is a co- customers yes and in memory customers we'll call it delegate so it's going to delegate to that and then if we ask it to implement all of these then each one of these things is going to have to say return delegate. list and so on and so on now obviously an actual implementation of customers talking to a database will be different than a co-c customers talking to a database because this one will be able to use blocking apis and this one will use suspend apis but for our inmemory case it's a shame that we can't just delegate one to the other that delegation is the challenge that I posed at the end of the last episode and I think that I have got a way to do it but we'll take a time to get there so first of all let's just delete this and before we go on we're going to have a look at our ther tests and imagine how to make these work if we didn't already have in memory customers these tests only list the customers and only expect an empty list back so of all the customers interface we're only calling one method so what we could do would be to say not in memory customers here but I just want an object that implements customers and then ask intellig to fill those in for me and it will helpfully put in two L for everything but we could now say well actually this list here let's just return an empty list and if we run that then everything is good because we're returning empty list for the only one method of this customers is actually called let's stop that and I suppose you can think of this as a very specific mock object built without a framework still a bit of a pain though because we've got these methods here and if we add anything to customers as an interface then we'll have to go and change our test and so on but there is a way that we can do better at least on the jvm so let's get rid of this and we'll go to proxy Java Lang reflect proxy just going to have a look at that see how long it's been around and the answer is since Java 1.3 so that's pretty much the beginning of time and what proxy has is a static method called new proxy instance and what new proxy instance will do is build an implementation of an interface at runtime now in order to do so it needs a class loader an array of classes and an invocation Handler so let's see how we can get there the class loader we can use pretty much anything I think we'll use the one from this test for now so that would be this Java class class loader the next parameter is the interfaces we want to implement and in our case we know that's customers and we need to provide it as an array so we can say interfaces is not that but array of customers class Java and this Java is because class here returns a cotlin reflection object on a Java Reflection object but we can convert that and then finally we need an invocation Handler and you can see that's been completed for us let's have a look at what an invocation Handler is it's an interface that has a single method on it which is invoke that takes the proxy that the method is being invoked on the method itself and whatever arguments and we return an object that we want to be the result of calling this method does that make sense well let's have a look let's say we just return banana in here which is allowable because this invocation Handler invoke just returns object and we're nearly there but new proxy instance returns an object we need to cast that to the thing we know we want which is as customers and some issue where it's not compiling here none can be provided with the arguments supplied well oh it turns out the names of those parameters were completely wrong so let's just get rid of them all together now if we run that it fails and it fails because we get an internal server error why is that well in the case of Kor it seems to be buried let's have a look at HTP for K now again we've lost it but I can tell you the issue here is that we are returning a string rather than a list of customers which is is what the one method that we are calling expects so I think if we change this to empty list of customer and run then we start passing our tests brilliant so maybe a better more General solution to this problem would be to say that if the method we're being called with we can answer its name is the one we think we want which is this list then we want to return an empty list to customer else well I think at this say we could say too fix all that to make it compile and this would be no implementation for method dollar method reformat that and now if we were to just try for example in here before we go on saying customers do find by ID frad and run that test then we see we'd get our not implemented error no implementation for method but public abstract customer find by ID so for an interface like customers I'll put it over here with quite a few methods this is almost as short as the object implementing with a lot of to-dos but there the advantage that we could add methods into customers and it would continue to work it's a bit of a shame though that we have to put this code in here can we do better and the answer is we can because cotlin already has a way of delegating interfaces so what I'm going to do is I'm going to say let's just call this our proxy and now we'll set up another private V customers and this is going to be an object that implements customers by our proxy that would work it turns out if we'd put a body on there and weren't doing that okay good but now this object here will only delegate to this proxy for things that it doesn't Define itself so in fact in here we can say override fun list to return our empty list and that means we can take out this body here except for the to-do so anything we explicitly Define as an override inside this body will be called here but everything else will be passed out to this new proxy instance which will just say I can't find that so we're still good because we're only calling list and we have an implementation of list and now we can do the usual thing which is to take this here and generalize all the things so let's put it out into a thing and we'll call it fake now we don't want this to be tied to customers. class. Java so let's pull that for now at least as a parameter instead of using the class loer from the test we might as well use the class loer from this class so we'll use class which is a Java class already class loader and now our invocation Handler is telling us we can remove the Constructor the Sam Constructor and then we can pull that out of the parenthesis and we can make that and underscore still good still good and now we can take this out of this test I'm going to put it up above for the moment and now we don't really want this to be tied to customers so what we can do is we can make this an inline fun of a rarified type and now this is not a parameter but we can use the T in here so we can say t class. java. class loader here we can say t class. Java and here we can say as T and then to call it we can just use the type in here so say fake customers is that good it certainly is and now we can do without this proxy Al together so we can inline that so we have our customers is an object which implements customers by fake customers but overrides the one method we're interested in returning an empty list so fake is pretty cool and normally ends up in a cotl code V if I've been there for long enough so let's move it out of here into its own place I think we'll say that can go in Main and we'll say util and quite sure why we're not allowed to say that do have to create one choose destination package oh I don't know I suppose we're sing it to a package let's say we want it in com example util SL proxying KT and refactor that no come on in tell don't embarrass me what's going on there because there's no no I don't know you know what put it in fake. KT an example because that's all you are going to allow me to do we'll add that to get and then we're going to find it and we will say let's make this com example util we will move it to there and we will rename this file to be proing marvelous how does that break oh dear oh dear let's try rebuilding the project and now that's broken but that's all right now maybe we'll find it by import not the most edifying refact of that but we're back in business good there's another hole that Java proxies have dug me out of and that's if I want to pretend that something I don't control has an interface that I do control what do I mean by that well let's say there was a version of this inmemory customers that was somebody else's code and they haven't implemented our customer interface so I'm going to just fake that by putting it in here and say uh we'll call it third party customers but that doesn't Implement customers now that's perfectly fine it just can't override customers that's all good but now let's say we want to use this where we have a customers so instead of customers here we'd want to say I've got my third party customers which is fine but third party customers doesn't implement our customers object now we could say as customers but the fact is that it isn't a customers and as the compiler is telling us here this cost can never succeed note that it has the same interface here it just doesn't say that it implements customers so this can't succeed but the structure is the same what we actually want is structural typing and we can do that with proxies as well so let's just pretend that we have a method I don't know let's call it structur L typed as and let's make that a type parameter now this would be easy to get wrong so I think I'm going to start writing some tests let's generate our structurally typed as so we'll create an extension function on anything for now and then I'm going to move this into our proxying like that and does this now build no but it will by import okay and I think I might call this structural typing tests okay one of the things I've used this for in real life is to narrow a big interface into something smaller so that we know that we're already calling a small number of methods in our code so sort of thing I'm thinking of is if we had for example an interface of my collection now if this is to implement collection then collection has size is empty contains iterator contains all but maybe I wanted a version that only returned an iterator and had size so what I'd want in here is not the whole of collection but we'd have Val size is an INT and fun iterator and we'll pull the definition of that is an iterator of T well e so my collection for example would be over e and this would be e so the sort of thing we might want to do is take an existing cotton collection and have a view of it as just my collection so what would that look like we would want to take something that's wrapped say a list of banana and kumquat and then my wrapper would be wrapped dot structurally typed as my collection now this is not compiling because thirdparty customers here we need two types really we'll say we've got a T1 and a T2 so this would be t one structurally typed as T2 and that would allow this to compile uh no because you'd have to specify both so I think we'll say this is any and we'll just stick with one t are we good are we would be if we said we want this my collection of string okay then let's just check that these tests actually run which they do and what do we have an implementation is not permitted because we have a to do in here right okay what's the behavior we want to see well we'd like to say assert equals two is the thing we get if we ask for raer size and also assert equals what do we want we want that the wrapped effectively that list would be what we got if we took the wrapper called iterator on it and ask for the list of that to list uh that doesn't appear to be a thing we can do that sort of surprises me but I suppose we can bdge it by saying as sequence to list some issue ah I can't spell assert okay that will continue to fail now then let's use this as our pattern for how to implement a proxy so we'll take the inline fun rifi t as always being useful and the new proxy instance like that now if we run our test then we will fail with this too but now instead of this to do what we can do is find a method on this receiver here that matches the method that's just been called which is to say find a method on this wrapped that matches the method on the wrapper so what would that look like well we will go to this that's this receiver here and we'll get its Java class and at its simplest we can call get method given the same name as the method that just been called us so we can say that is Method with methodname and we also need to pass the parameter types and our method has parameter types as well so there's a chance that this is Val matching method and now we can say matching method invoke and we want to invoke it on the wrapped thing which is this any so that's this and we want to give it the arguments that we were given it's not that it is this arguments here so let's put that in there and I think as this is an array and this takes vogs we'll need to Splat the array let's see how that does okay so that's a bit irritating what's happened is we've called this without any arguments so that's been null not an empty array so let's just get ourselves working I think by passing no VAR ars no still no good let's just see what matching method we got out so we'll say printland matching method run oh that's very strange it passes if I take that out well I don't know quite what happened before but you can see that now at least for two methods that take no parameters we've called size on array list through size on my collection now that we know that the number of parameters is an issue maybe we should change this from iterator to something else on list see what we can call we could say wrapped get at an index that would be a good one so we'll change this to get index which is int this is going to return an e and now I think we should be able to say that kumquat is rapper get one we run that we don't expect it to work because we don't have the right number of arguments here but now we think that we might say if ARS equals null invoke without any else matching method invoke with the arguments have we go splendid okay I've just laid that out a bit better and I think we might also say here that we could say here this is ARS and Elvis empty array if so we get rid of that that and one more there and still good so to make structural typing work all we've had to do is build a proxy that finds a method with the same name and parameter types as the call we've just made and then invoke it I say all we have to do because if you are Eagle eyed you may have noticed a little slight of hand there and that's that at some point I added this array list here now why is that well the answer is that if we do this and go straight to list of and run that this fails we fail with an Undeclared throwball exception and that's caused by an illegal access exception which happens when we try and call this invoke now I think we can solve that problem but it'll take quite a time and unless you're already an expert in Java Reflection I suspect this has been pretty heavy going so let's leave that until next week if you'd like to see that episode then please do subscribe to the channel if you press the notification Bell YouTube will tell you as soon as I published and if you could like this video as well I'd really appreciate it as it helps other people find the channel and finally if you haven't already please consider buying the book that are open that price called jav deot line refactoring guide book details of which are in the show notes below thanks for watching
Info
Channel: Refactoring to Kotlin
Views: 663
Rating: undefined out of 5
Keywords:
Id: 5DHDOn3LttA
Channel Id: undefined
Length: 22min 5sec (1325 seconds)
Published: Fri Jul 05 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.