-
Notifications
You must be signed in to change notification settings - Fork 807
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
Potential expression cache enhancement for 2.6 series #528
Potential expression cache enhancement for 2.6 series #528
Conversation
- Add support for an optional basic LRU cache for OGNL expressions and OGNL BeanInfo. - Add support for cache limits applying to both normal and LRU caches. For a normal cache the entire cache will flush when the limit is reached. - Add flags to allow switching between normal and LRU caches, and setting the maximum sizes.
Hello Apache Struts Team. This PR contains a potential enhancement for the expression and BeanInfo cache implementation in the 2.6 branch. Others may have other ideas, or more efficient implementations to consider, but it may still provide a starting point for an implementation. If the idea seems reasonable, then a new JIRA ticket can be opened and associated with the proposed change. If not, then the PR can be closed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clever idea, yet I would do it a bit different. It would be better to extract existing cache layer into dedicated classes and also extract the common interfaces. Then add new LRU implementation to the stack. And then depending on the flags create a proper instance.
Such approach will eliminate in-method logic to select a proper reference, instead this:
final Map<String, Object> expressionsCacheRef = (useLRUExpressionCache.get() ? expressionsCacheLRU.backingMapReference() : expressionsCache);
tree = expressionsCacheRef.get(expression);
you will just use already defined reference
tree = expressionsCacheRef.get(expression);
wdyt?
Hi @lukaszlenart . Thanks for the feedback and suggestions. Moving the existing cache layer into dedicated interfaces/classes is probably cleaner in the long run, as you pointed out. I can attempt to do that in a revision to the PR, it just might take me a little while. Because the cache instance(s) are final and part of the intializer block, the instances get set before the configuration flags can be applied (when the framework injects the flag values a little later). A dedicated cache implementation could probably help address that (or at least hide some of the details), in a cleaner way than what I have done so far. I will try a refactor and see how it works out. |
You can inject them via constructor if you want to or provide a factory which will be injected into public OgnlUtil(
@Inject(StrutsConstants.STRUTS_EXPRESSION_CACHE_FACTORY, required = false) ExpressionCacheFactory cacheFactory
) {
if (cacheFactory == null) {
// fallback to old mechanism
} else {
this.cache = cacheFactory.createCacheInstance();
}
} and the factory: class ExpressionCacheFactory {
@Inject(value = StrutsConstants.STRUTS_OGNL_EXPRESSION_CACHE_MAXSIZE, required = false)
protected void setExpressionsCacheMaxSize(String maxSize) {
expressionsCacheMaxSize.set(Integer.parseInt(maxSize));
expressionsCacheLRU.setEvictionLimit(expressionsCacheMaxSize.get());
}
@Inject(value = StrutsConstants.STRUTS_OGNL_BEANINFO_CACHE_MAXSIZE, required = false)
protected void setBeanInfoCacheMaxSize(String maxSize) {
beanInfoCacheMaxSize.set(Integer.parseInt(maxSize));
beanInfoCacheLRU.setEvictionLimit(beanInfoCacheMaxSize.get());
}
@Inject(value = StrutsConstants.STRUTS_OGNL_EXPRESSION_CACHE_LRU_MODE, required = false)
protected void setUseLRUExpressionCache(String useLRUMode) {
useLRUExpressionCache.set(BooleanUtils.toBoolean(useLRUMode));
}
@Inject(value = StrutsConstants.STRUTS_OGNL_BEANINFO_CACHE_LRU_MODE, required = false)
protected void setUseLRUBeanInfoCache(String useLRUMode) {
useLRUBeanInfoCache.set(BooleanUtils.toBoolean(useLRUMode));
}
public CacheInstance createCacheInstance() {
....
}
} Then you just need to define a bean in Take your time and if you need my help let me know. Or if you want to, I can refactor your code once we merge this PR - working with DI in Struts can be frustrating on the beginning ;-) |
…ruts into localS2_26_OgnlUtilOptionalCache1 # Conflicts: # core/src/main/java/com/opensymphony/xwork2/config/providers/StrutsDefaultConfigurationProvider.java # core/src/main/resources/struts-default.xml Manually resolved conflicts.
Hi @lukaszlenart . The PR has been updated after refactoring to something more along the lines of what you suggested, and then resolving a merge conflict. There are likely more improvements to be made, but it is probably closer to something workable now. |
Brilliant 💯 👏 [1] https://struts.apache.org/tag-developers/ |
Hi @lukaszlenart. Thanks for the additional feedback. I had not considered the documentation side of things in relation to the potential change. The existing documentation section for OGNL expression in the Tag Developer's Guide seems to be OK where it is, since most of the interaction and examples are via tags. If this PR (or something similar) goes forward, then maybe a new section in the Core Developer's Guide that addresses configuration of the default OGNL expression and BeanInfo caches would make sense. I could try drafting something for that, in that case. Do you (or anyone else) see any weaknesses, design issues, or any other concerns with the current refactored code ? If so, please advise, and I can attempt to re-work things as needed. Should we create a JIRA for tracking for the potential change as well ? |
if (ognlExpressionCacheFactory != null) { | ||
this.expressionCache = ognlExpressionCacheFactory.buildOgnlCache(ognlExpressionCacheFactory.getCacheMaxSize(), 16, 0.75f, ognlExpressionCacheFactory.getUseLRUCache()); | ||
} else { | ||
this.expressionCache = new OgnlDefaultCache<>(25000, 16, 0.75f); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think about moving this code into OgnlExpressionCacheFactory#createDefaultCache()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The latest commit introduces a zero-parameter build method, which is a little cleaner. Since the factory parameter could be null, there is still a need to check for that possibility somewhere.
A static generator method could be introduced, something like DefaultOgnlExpressionCacheFactory#createDefaultCache(expressionCacheFactoryInstance)
, but it would not seem to improve things much ?
Did I misinterpret what you were suggesting ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, ognlExpressionCacheFactory
represents interface, I missed that. And I thought about wrapping new OgnlDefaultCache<>(25000, 16, 0.75f);
into a factory method within the factory:
public OgnlCache<> buildDefaultCache() {
return new OgnlDefaultCache<>(25000, 16, 0.75f);
}
if (ognlBeanInfoCacheFactory != null) { | ||
this.beanInfoCache = ognlBeanInfoCacheFactory.buildOgnlCache(ognlBeanInfoCacheFactory.getCacheMaxSize(), 16, 0.75f, ognlBeanInfoCacheFactory.getUseLRUCache()); | ||
} else { | ||
this.beanInfoCache = new OgnlDefaultCache<>(25000, 16, 0.75f); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, OgnlExpressionCacheFactory#createDefaultCache()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please see the comment for the expression cache item above.
Up to you, it allows users to monitor what has changed plus you can link the new documentation to the ticket as well. |
Hi @lukaszlenart . |
Yeah... I'm planning to write a guideline how to use Struts DI mechanism, it can be hard to understand on the first glimpse ;-) You can try to follow my changes in this PR - I introduced an injectable date formatter with two default implementations, but users can provide they own if needed. So the whole trick is that you must define an extension point:
(your PR is missing this step, sorry that I didn't spot it ealy :( ) Having that set, either you can make this a required dependency and provide at least one implementation of the interface/class or make it optional and handle nulls in your code. In my case I made it required and provided two implementations: Now you must register the beans in Struts DI:
^^ this ties implementation with the name. And now it's time to tell Struts DI which implementation to use for the extension point:
and done! :) With such approach users can define they own implementations and used them with the extension point. If something is still unclear please ask, I will try to prepare the guideline. |
I've started preparing the guideline apache/struts-site#160 |
- Added some IDE-recommended annotations and cleanup to some of the modified files. - Applied easier-to-read/differentiate names "ognlExpressionCacheFactory" and "ognlBeanInfoCacheFactory" for the cache factory configuration extension points. - Reorded default configuration factory init for the cache factories (did not help extension override). - Cleanup of parameterized OgnlUtil constructor. - Added extension point aliases to StrutsBeanSelectionProvider. - Added beaninfo for cache factories to ConstantConfig (did not help extension override). - Added cache factory references to default.properties, struts-default.xml.
Hello @lukaszlenart . Thank you for the explanation concerning the DI mechanism flow, the reference example PR, and putting together the guideline in the site documentation. 😄 The PR has been updated, but still has some issues. I did my best to try and get the extension points to work for customization, but the custom implementation failed to be used every time. 😞 No matter how I tweaked things (including adding elements that some other extension points seemed to also define), only the default implementation ended up initialized/used (either that or the configuration prevented the framework from initializing). Debugging a sample app (I tried using a slightly modified showcase for convenience) it also seems that depending on what stage initialization is at, the DI configuration values (for cache size and mode) are sometimes available and sometimes not (almost as if order or timing related). Given the issues encountered so far, I am concerned that this proposed PR implementation is somehow fundamentally broken (due to something being wrong with its DI implementation, or an ordering/timing limitation). What do you (and others) think ? |
Let me work a bit on you PR, maybe something obvious is missing (which happens to me each time when I need to work with Struts DI ;) ) |
PR fixing the problem is ready JCgH4164838Gh792C124B5#1 |
Ties cache extension points with implementation
# Conflicts: # core/src/test/java/com/opensymphony/xwork2/ognl/OgnlUtilTest.java
All set, LGTM 👍 thanks @JCgH4164838Gh792C124B5 👏 |
Update: