[ 
https://issues.apache.org/jira/browse/WICKET-7202?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18110849#comment-18110849
 ] 

Korbinian Bachl edited comment on WICKET-7202 at 9/2/26 8:07 PM:
-----------------------------------------------------------------

Hi,

You need to be careful about the Weld cache. The point is that the Weld cache 
remembers the injected class. It won't remember classes that have no CDI, but 
this is the problem with Wicket.

Every component gets injected, and if it's already known to Weld, it's fast. If 
not, it slows us down because we need the BeanManager for that. So we need to 
know whether we have an injected member before we even call inject, otherwise 
Weld will drag us down.

I found this during performance testing when I wondered why my system wouldn't 
get any faster even though I had optimized critical parts. It turned out that 
we have a special page type with 36 items consisting of hundreds of 
subcomponents. There, we experienced a significant slowdown because Weld scans 
every non-CDI component over and over again.

According to Java Flight Recorder, Weld was the cause. So I created this class, 
and a similar one for behaviors, and suddenly the system was as fast as 
expected.

The problem with Weld/CDI is that it slows things down before it even considers 
its own caching mechanism. Wicket creates a large number of classes at runtime, 
since every small part of it is effectively an instance of a class. So we try 
to remember the class here, which makes this extremely fast in the end.

One optimization we took even further was to have CDI react only to our own 
class structure, so we could bypass classes that aren't from our own packages. 
I don't know how we could make this global for all users, but even without this 
optimization, it was enough to remove the bottleneck.

PS: i also had a look at 7126: the idea is good, yet IMHO it wont hit the main 
culprit: the scanning of non CDI classes that way (at least how I understood 
the code); For a maximum of performance we need to make sure every costly call 
is made not more than once (e.g.: JNDI or bean manager lookup for non CDI 
class) and then cached. Caching inside a Hashmap ist blazing fast and with 
using a ConcurrentHashMap also nealry lock free. For maximum performance one 
could even divide the hashmaps by submaps based on the package prefixes, or 
even basic reject based on pure java package strings for wicket internals (this 
then means that it breaks when wicket would use CDI inside its internal one 
day? - not sure if this is something to happen any time)


was (Author: kbachl):
Hi,

You need to be careful about the Weld cache. The point is that the Weld cache 
remembers the injected class. It won't remember classes that have no CDI, but 
this is the problem with Wicket.

Every component gets injected, and if it's already known to Weld, it's fast. If 
not, it slows us down because we need the BeanManager for that. So we need to 
know whether we have an injected member before we even call inject, otherwise 
Weld will drag us down.

I found this during performance testing when I wondered why my system wouldn't 
get any faster even though I had optimized critical parts. It turned out that 
we have a special page type with 36 items consisting of hundreds of 
subcomponents. There, we experienced a significant slowdown because Weld scans 
every non-CDI component over and over again.

According to Java Flight Recorder, Weld was the cause. So I created this class, 
and a similar one for behaviors, and suddenly the system was as fast as 
expected.

The problem with Weld/CDI is that it slows things down before it even considers 
its own caching mechanism. Wicket creates a large number of classes at runtime, 
since every small part of it is effectively an instance of a class. So we try 
to remember the class here, which makes this extremely fast in the end.

One optimization we took even further was to have CDI react only to our own 
class structure, so we could bypass classes that aren't from our own packages. 
I don't know how we could make this global for all users, but even without this 
optimization, it was enough to remove the bottleneck.

> wicket-cdi performs non-contextual CDI injection for every Component and 
> Behavior even when no injection points exist
> ---------------------------------------------------------------------------------------------------------------------
>
>                 Key: WICKET-7202
>                 URL: https://issues.apache.org/jira/browse/WICKET-7202
>             Project: Wicket
>          Issue Type: Improvement
>          Components: wicket-cdi
>    Affects Versions: 10.10.0, 10.11.0
>            Reporter: Korbinian Bachl
>            Priority: Major
>         Attachments: FastCdiComponentInjector.java
>
>
> *Abstract:*
> Wicket’s default CDI integration unconditionally processes every newly 
> created component and behavior, even classes without any @Inject members. 
> This caused significant CDI overhead and contention in Open Liberty.
> We replaced Wicket’s ComponentInjector and BehaviorInjector with optimized 
> listeners. A per-class ClassValue cache determines whether the class or one 
> of its superclasses contains injectable members.
> CDI is invoked only when injection is actually required. The BeanManager and 
> InjectionTarget instances are also cached and invalidated on CDI or 
> redeployment failures.
> As a result, ordinary Wicket components and behaviors bypass CDI entirely, 
> while injection remains available for application classes that actually 
> require it.
> *Example:*
> enclosed is our FastCdiComponentInjector class only cleaned up with out 
> company name that we use; beside this we also use a FastCdiBehaviorInjector 
> and AbstractCdiWebApplication but this should give an impression how we 
> solved this that CDI inject is no longer a burdon on the app server itself
> *Long Description:*
> CdiConfiguration installs ComponentInjector and BehaviorInjector as global
> instantiation listeners.
> Both listeners unconditionally call AbstractInjector.inject(instance) for 
> every
> new Wicket Component and Behavior:
> ComponentInjector / BehaviorInjector
> -> AbstractInjector.inject()
> -> NonContextual.of(instance)
> -> NonContextual.inject(instance)
> This path is also executed for classes such as Label, WebMarkupContainer,
> AttributeModifier and application components which do not declare any CDI
> injection points.
> NonContextual caches the InjectionTarget per BeanManager and class, but it 
> does
> not cache or use the negative result "this class has no injection points".
> Therefore every instance still causes:
> * BeanManagerLookup.lookup() while resolving the NonContextual cache;
> * another BeanManagerLookup.lookup() in NonContextual.inject();
> * creation of a CreationalContext;
> * invocation of InjectionTarget.inject().
> BeanManagerLookup caches the last successful lookup strategy, but not the
> BeanManager itself. Thus the successful JNDI or CDI.current() lookup strategy 
> is
> still invoked repeatedly.
> In a component-heavy application this creates a very hot CDI path even though
> the overwhelming majority of Wicket framework components and behaviors do not
> contain injectable members.
> We observed severe CDI overhead and container contention with Open Liberty. 
> The
> CDI resolution/injection path could not keep up with the rate at which Wicket
> created small Components and Behaviors.
> Environment where the problem was first addressed:
> * Apache Wicket 10.8.0
> * Open Liberty 26.0.0.2
> * Jakarta EE 10
> * Java 25
> The same code path is still present with:
> * Apache Wicket 10.10.0
> * Open Liberty 26.0.0.8
> * Jakarta EE 11
> * Java 25
> Workaround used in the application
> After calling CdiConfiguration.configure(application), we remove Wicket's
> ComponentInjector and BehaviorInjector and replace them with application
> listeners.
> The replacement listeners use the following fast path:
> 1. Exclude known framework/non-application packages.
> 2. Use ClassValue<Boolean> to cache whether the class hierarchy declares
>  jakarta.inject.Inject or javax.inject.Inject fields/methods.
> 3. Return without entering CDI when no injectable member exists.
> 4. Cache the BeanManager.
> 5. Cache InjectionTarget instances by target class.
> 6. Create a CreationalContext and invoke InjectionTarget.inject() only for
>  classes which actually require injection.
> 7. Invalidate the BeanManager and InjectionTarget caches after container
>  failures so that redeployment can recover.
> The same mechanism is used for Components and Behaviors.
> This removes CDI completely from the instantiation path of ordinary Wicket
> framework objects and application objects without injection points.
> Proposed upstream solution
> Please add a class-level injection candidate cache to wicket-cdi and avoid
> calling NonContextual.of(...)/InjectionTarget.inject(...) for Components and
> Behaviors which cannot contain CDI injection points.
> A possible implementation is:
> * Introduce a ClassValue<Boolean> in AbstractInjector or in a small shared
> injection metadata helper.
> * On first use, inspect the complete class hierarchy for injectable fields and
> initializer methods.
> * If the cached result is false, return immediately before BeanManager lookup,
> CreationalContext creation and InjectionTarget invocation.
> * Use the optimized method from both ComponentInjector and BehaviorInjector.
> * Keep SessionInjector/application postConstruct handling unchanged, because
> these objects may have lifecycle callbacks even without injection fields.
> * Cache the resolved BeanManager for the application lifecycle, or retain the
> BeanManager in NonContextual together with its InjectionTarget.
> * Clear container-specific caches during 
> CdiShutdownCleaner/NonContextual.undeploy.
> An application package whitelist is a useful local optimization, but should 
> not
> be hard-coded upstream. Wicket could instead offer an optional
> Predicate<Class<?>>/injection candidate filter on CdiConfiguration. The 
> default
> implementation should at least cheaply reject Wicket framework classes and
> classes without supported injection annotations.
> Compatibility must be considered if wicket-cdi intends to support Java EE
> resource injection annotations other than @Inject. In that case, either those
> annotations must be included in the candidate check, or the fast filter should
> be configurable/opt-in. The application workaround intentionally targets
> @Inject-based CDI injection.
> Acceptance criteria
> * Instantiating a Component without injection points does not create a
> CreationalContext and does not call InjectionTarget.inject().
> * The same applies to Behavior instances without injection points.
> * Field and initializer-method injection still works.
> * Inherited injection points still work.
> * Qualifiers and normal CDI scopes remain unaffected.
> * Session and application @PostConstruct behavior remains unchanged.
> * Metadata caching is safe across concurrent requests and application
> undeployment/redeployment.
> * Tests verify CDI call counts for many instances of the same non-injectable
> Component and Behavior.
> Suggested reproducer
> Create a Wicket quickstart with CdiConfiguration and an instrumented/fake
> BeanManager. Instantiate a page containing several thousand Labels,
> WebMarkupContainers and AttributeModifiers.
> After application initialization, count:
> * BeanManager lookups;
> * createCreationalContext() calls;
> * InjectionTarget.inject() calls.
> Repeat with one Component and one Behavior containing an inherited @Inject
> field to prove that real injection continues to work.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to