-
Notifications
You must be signed in to change notification settings - Fork 2.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Panache: add support for interceptors in static methods #9487
Comments
WDYT @mkouba, should this work? |
Well, static methods are not intercepted in CDI. In fact, the interceptors spec explicitly forbids this scenario: "An around-invoke method must not be declared as abstract, final or static." (2.5 Business Method Interceptors). So no, it should not work. |
Well, in the general case, sure, you don't want to run interceptors on bean static methods, because you can't without instrumentation. But here we're talking about entities, they're not beans, and we most certainly do want to allow people to run interceptors for those cases. I'm not sure the CDI spec constraint applies here. |
Ok, my point was that interceptors are in general not intended to work with static methods. I think that we could make it work but there are few challenges:
|
I can try to prepare a POC to see how difficult it would be... |
@mkouba +1 for the PoC. I am split brained on that one. An entity is not a bean therefore should not intercept. But we do have a specific use case here which makes sense since Panache entities are half entities, half component that would be a CDI bean in another life. So maybe that facility is dedicated to Panache Entities only and not a generally exposed feature to ArC beans. |
I think that if we decide to implement this it should be possible to use it anywhere. On the other hand, it seems that the implementation would require bytecode transformation of call sites (due to the nature of From the CDI POV - only method-level interceptor bindings should be considered to retain backwards compatibility. For panache entities we could use an annotation transformer to add bindings declared on the class to all static methods. BTW CDI observers and producers could be static. So I believe that the original restriction from the interceptor spec originates from a technical limitation. |
Ah, call side bytecode enhancement might be a problem. On the other hand that's what we do for entity access already @FroMage |
Yes, we do replace field access with getter/setter invocations. The performance drop could be a problem since we have to produce a lot of So this is the first working version of POC: It's not optimized, only CC @manovotn |
Hmm, so the current approach would not work with panache entities because it does not analyze the inherited static methods :-(. I need to think it over... |
So I've got a first version that also works for panache entities annotated with Now I'll try to polish/optimize the code and then I'll send a DRAFT PR... |
Thanks a lot @mkouba I need to look at this. My idea was that the existing Panache transformer would generate the interception bytecode itself in the static methods, the same way we generate mocking interception bytecode for the same static methods. But I'll check your code first. |
We can probably return a bogus instance for all the use-cases that want to access the class. Is there no way to access the target class without going through the instance? |
Oh, I see, it looks like you're switching the callers of the static methods to use an intercepted static method. My idea was more to inject the interception code into the static method itself, similar to the public static java.util.List findOrderedTransactional(){
Object[] objectArray = new Object[0]; // probably parameters?
if(metadata == null)
return findOrderedTransactional$$real();
Function<InvocationContext> target = ctx -> {
ctx.getParameters(); //ignored
return findOrderedTransactional$$real();
};
Object v1 = metadata.get("m1");
Method targetMethod = ((SubclassMethodMetadata)v1).method;
List chain = ((SubclassMethodMetadata)v1).chain;
Set bindings = ((SubclassMethodMetadata)v1).bindings;
return (List)InvocationContexts.performAroundInvoke(this, targetMethod, target, objectArray, chain, bindings);
}
public static List findOrderedTransactional$$real(){
// original method code goes here
return find("ORDER BY name").list();
} That would allow us to not scan static method call use-sites. OTOH, now that @stuartwdouglas has added support for limiting the scanning to jars that depend on Panache, perhaps it's cheap enough. |
FTR, this is how we inject mock interceptors for each static methods in Panache: https://github.com/quarkusio/quarkus/blob/master/extensions/panache/panache-mock/src/main/java/io/quarkus/panache/mock/impl/PanacheMockMethodCustomizer.java#L21 But this sort of interceptor can only either handle the entire call, or delegate to the original method: there's no "around invoke" concept like in CDI, which pretty much requires moving the original code into a second method like I showed. I could make an extension point like this that would allow moving stuff to another method. OTOH this would be specific to Panache and would end up in user stack traces because we're moving the real code around, whereas your solution changes the call sites by adding one layer, so stack traces are more correct when something happens in the user code. So, probably your solution is more user-friendly. |
Well, we could try to return a "fake" instance but that could bring more problems than benefits. Interceptors could inspect
Yes.
Yes, that's the other possible solution. I chose the transformation of call sites because this would allow us to leave the static methods untouched which is easier for debugging. Anyway, the snippet in your comment would require call site transformation as well because the client calls |
Well, I generate the bridge method for |
I see. So panache adds a synthetic method to the entity class to override some static methods declared on |
I am very much -100 on call site transformation, this should be done by modifying the method itself. I only just finished optimising the call site modification we do for panache but this is not generally applicable, so that work would likely go to waste. |
So I'm -1 for modifying the original static methods. IIUIC this would break the debugger, i.e. users would not be able to debug the static methods they create. Also due to the nature of @stuartwdouglas Is your only concern the performance? |
If you enhance the call sites then users won't be able to debug the calling method. Except that really they still can, you just make sure the LineNumberTableAttribute is still pointing at the correct code and breakpoints will work fine. There may be some weirdness if you step into the very first like (as in the debugger will start to run the interceptors), but you get this whenever you step into an intercepted class. Even if you have to copy the method the debugger will still work fine, as long as the line numbers match up. |
Well, they will be lost for a while (if they step into) but they will get to the original method eventually (in few steps)...
Modyfing line number attributes? That sounds cumbersome... Maybe we should get some numbers to estimate the cost of the call site transformation. Anyway, your |
The big problem is not even the requireConstPoolEntry optimisation. The big problem is that it would negate the annotation processor based marker file approach I used for panache. The idea is that this annotation processor creates a marker files, that can be used to tell which jars on the class path have panache as a dependency. We know that only modules with panache as a dependency can access panache entities, so we can limit the number of jars we transform. If potentially any static method can want call site transformation the we have to parse the whole class path on every dev mode reload. Basically one option involves modify a single class, the other option involves scanning every single class on the classpath, so for me it is an obvious choice. In terms of the LineNumberTableAttribute if you rename the method then you don't have to do anything, ASM will just copy it for you and it will not need modification. If you insert bytecode into the start of the method then you just need to increment every entry by the number of bytes you have inserted. |
OK, so @stuartwdouglas essentially says this should not be general-case, but only for Panache static methods. Now, about the debug issues:
|
Yeah, I do understand this optimization. So you're basically saying that the call site transformation is fordbidden and we only keep it for panache. Shoudn't we try to optimize our transformation approach then so that it's usable for other extensions as well?
We would not insert something at the start of the method but replace the whole body... |
No, general case is fine, but not for call site instrumentation. Just modify the class being called so you don't have to potentially modify the whole class path. |
But you would copy the original method right (at least that is what you mentioned above). If so you don't need to do anything, as long as you leave the method attributes alone, the line number table will be copied across and debug will work as normal. |
Yes, |
Oh, you want ArC to modify the original classes rather than deal with generating new classes (which is the only thing it currently does). Interesting. Since I don't think it currently does that, I wonder how much work that would involve, and I wonder how it would integrate with Panache also doing other enhancements. We'd have Hibernate, Panache and ArC modifying the same class, so we have to make sure transformers happen in the right order.
Yes but the method name would be different once the user steps into it. Probably not a big deal. |
In that case then debugging will just work. It will look pretty much exactly the same debugging any other intercepted window, the only difference is that the stack trace will have the _original prefix in it. |
That's exactly what I'm afraid of... |
No we won't, because the methods that the user can apply an interceptor to are just normal static methods. The user can't modify the generated static methods, as there is nothing to add annotations to. |
I don't understand? What are we not going to do? |
In the case of Panache, that's not strictly true, though it depends on the semantics we ascribe to static methods "inheritance". If the user adds We certainly do inject the mock-interception bytecode for both user-defined static methods and the ones that are "inherited" where we generate the bridge methods in Panache. So there is going to be an interaction. Now, I certainly would not advise users make the entire entity But even for those we do transform them to inject the mock-interceptor. I don't think Hibernate transforms those, though. So perhaps interaction is not that complex. |
So if a user wants a static method from the |
That sounds reasonable, no? At least, we can start with this approach, and extend support for class-declared interceptors later if people come up with a good use-case? Or do you think it's too restrictive? |
ATM we do define an ordering by using markers. They are applied in reverse order I think, so I think Panache declares its transformer after Hibernate declares them, which means Panache transformers run first. That's from memory, though, perhaps I'm wrong. |
I've updated my POC branch to use the other approach discussed above. It seems to work well for "regular" static methods. I still need to verify the Panache use case... afterwards I'll send a draft PR with more detailed description. |
OK great, make a PR indeed because I have comments to add there, but it's looking really good! |
FYI: #9657 UPDATE: This PR got merged. |
Yeah, why is this not closed? |
ATM interceptors are only applied to beans, and not static methods.
Similar to what I've done for panache-mocking, we should be able to inject interceptor code in the static methods.
If you have a bean called
PersonRepository
ArC will generate a class calledPersonRepository_Subclass
which contains all intercepted methods:I should be able to inject similar code in the static methods to get them intercepted.
The text was updated successfully, but these errors were encountered: