|
From: Colin S. <col...@ex...> - 2005-01-27 15:09:23
|
(I am starting to hate SourceForge intensely. I got 3 replies to Drew's
message, but not Drew's message Add to the the ongoing CVS issue for
the better part of 2 years now (which seem to have gotten worse, I
simply can't do a proper update without trying dozens of times) or the
last week's disk space problems. I guess we get what we pay for).
This code looks pretty interesting. I agree about it probably making
sense to include in Spring. Drew, do you have any feel for performance,
i.e. how big an impact there would be to using a lot of these
expressions in a config file? I'm also trying to get my head around how
this affects lifecycles and when initialization happens.
PropertyPlaceholderConfigurer is obviously pretty simplistic, all it's
going to do is replace a placehold value in a text property value with
another. It can't trigger the initialization of another bean. Now in
this OGNL variant, if the OGNL script references the context, then it
can trigger the initialization of other beans, similar in fashion to how
a <ref bean="xxx"> will trigger the init of that other bean first. Now
the difference here is that while the <ref bean=""> will trigger that
init only if used, since this approach is as a bean factory
postprocessor and all expressions are evaluated at init time, all
references beans will be immediately initialized at init time. For this
reason, while I think this is a valid approach, and has the advantage
that it doesn't require changes to the guts of Spring, I think there's
still added value in having the idea of expressions known to Spring
itself, which are only evaluated on demand... Possibly the 2nd step...
Colin
Rob Harrop wrote:
> Drew,
> This sounds excellent - I would certainly like to include it in the
> main Spring codebase. If everyone else is in agreement feel free to
> either post this to JIRA or send it to me and I'll put it in the
> sandbox. I think it would be really nice to get this up and running
> for the 1.2 release.
>
> Alternatively, I wonder if it is about time we started a separate
> project for Spring add-ons. Still underneath the Spring umberella,
> just a separate project away from the core. This way we can keep the
> core compact and free from clutter. Thoughts everyone?
>
> Rob
>
> Drew Davidson wrote:
>
>> I wrote an OGNL-based property configurer (BeanFactoryPostProcessor
>> implementor) that evaluates OGNL expressions in <value>, <map> and
>> <list> entries, etc.
>>
>> It is similar in spirit to the PropertyPlaceholderConfigurer in that
>> is substitutes values during the post processing phase. It looks for
>> values delimitted by :[ and ] and processes them as OGNL expressions,
>> which return a value.
>>
>> Advantages of OGNL expressions in Spring configurations:
>> * Reference static members of classes directly (great for setting
>> values from "static final int" constants):
>>
>> <property name="aConstValue"><value>:[
>> @org.ognl.SomeClass@CONSTANT_VALUE ]</value>
>>
>> * Access Java data structures (and Collections) more easily than
>> in Spring. Here are some examples, but I can't do justice to the
>> amount of data structure manipulation possibilities in OGNL in this
>> small space:
>>
>> - Create a Map:
>> #{
>> "name": "MyName",
>> "description:" "This is a description of myName"
>> }
>>
>> - Create a List of Strings:
>>
>> { "one", "two", "three" }
>>
>> - Create a List, filtered for certain elements:
>>
>> #someObjectInContext.items.{? name.startsWith("foo") }
>>
>> * Call methods on any object reachable by the bean factory
>>
>> * Reference and navigate other objects in the factory by name
>>
>>
>> Example from my commerce library of configuring a factory that
>> produces OrderQueryCriteria objects. These objects have setup
>> requiring bindings to helper objects (OrderStatus is a custom
>> enumerated type, SortOrdering is similar to Hibernate's Order class
>> but slightly different usage pattern). One way to do this is to
>> change the objects to take primitive-based setters and getters to
>> construct these, or to complexify the Spring configuration file to
>> provide these objects through factories, etc. I tried that and the
>> config file was a bit heavy with "noise" due to the number of custom
>> classes I needed just to get to static members, constructors, etc.
>>
>> public class OrderQueryCriteria extends AbstractQueryCriteria
>> {
>> ...
>>
>> public List getOrderStatus()
>> {
>> return orderStatus;
>> }
>>
>> public void setOrderStatus(List value)
>> {
>> orderStatus = value;
>> }
>>
>> public SortOrdering getPrimaryOrdering()
>> {
>> return primaryOrdering;
>> }
>>
>> public void setPrimaryOrdering(SortOrdering value)
>> {
>> primaryOrdering = value;
>> }
>>
>> public int getPageSize()
>> {
>> return pageSize;
>> }
>>
>> public void setPageSize(int value)
>> {
>> pageSize = value;
>> }
>> }
>>
>> applicationContext-dao.xml:
>>
>> <bean id="orderQueryCriteria"
>> class="org.ognl.dao.OrderQueryCriteria" singleton="false"
>> autowire="byName">
>> <property name="orderStatus"><value>:[
>> @EnumeratedType@getFactory(@OrderStatus@class).instances.{ #this }
>> ]</value></property>
>> <property name="primaryOrdering"><value>:[ new
>> SortOrdering('orderDate', @SortOrdering@DESCENDING) ]</value></property>
>> <property name="pageSize"><value>10</value></property>
>> </bean>
>>
>> EnumeratedType is an abstraction for managing enumerated type
>> objects. It allows you to get lists of instances and manages
>> indexing the enumerations by other properties as well. Access is
>> through static factory that implements the getInstances() method.
>>
>> orderStatus is a List of OrderStatus objects. To construct this we
>> need to get the List from the EnumeratedType's factory and get a copy
>> of the list that is returned (the copy is done by "projecting" the
>> instances list via { #this }, which creates a new List as a result).
>>
>> primaryOrdering is a SortOrdering object that has an Object "target"
>> and an ordering value (ASCENDING, DESCENDING or NONE). Note here
>> that we are constructing this object using it's own static constant
>> fields.
>>
>> The above code is accessing SortOrdering, EnumeratedType, and
>> OrderStatus without any package specifications, you may note. This
>> works here because the processor is looking for beans of type
>> ognl.ClassResolver to use to resolve class names to actual Class
>> objects. I've written an ImportClassResolver that is configured thus:
>>
>> <!-- id does not matter; only the fact that it exists in the
>> context -->
>> <bean id="imports"
>> class="org.ognl.spring.config.ImportClassResolver">
>> <property name="imports">
>> <list>
>> <value>org.ognl.util.*</value>
>> <value>org.ognl.model.shop.*</value>
>> <value>org.ognl.pager.*</value>
>> </list>
>> </property>
>> </bean>
>> You can define as many of these as you like and they do inherit.
>>
>> Another feature is that the OGNL "context" object allows access to
>> the rest of the Spring applicationContext through OGNL's "context
>> variable" syntax.
>>
>> In the above code you saw reference to "#this" - this is the implicit
>> value of the current object of the navigation; also available is
>> #root (the original root object of the expression). This syntax also
>> is used to access other elements in the context:
>>
>> <!-- a ListFactory produces, as the result of the factory, a List
>> object -->
>> <bean id="itemSortProperties"
>> class="org.ognl.spring.config.ListFactory">
>> <property name="list">
>> <value>:[
>> {
>> #{ "name": "Item #",
>> "property": "itemNumber",
>> "advanced": false
>> },
>> #{ "name": "Name",
>> "property": "name",
>> "advanced": false
>> },
>> #{ "name": "Categorization",
>> "property": { "product.category.name",
>> "product.name" },
>> "advanced": false
>> },
>> }
>> ]</value>
>> </property>
>> </bean>
>>
>>
>> <bean id="itemQueryCriteria"
>> class="org.ognl.dao.ItemQueryCriteria" singleton="false"
>> autowire="byName">
>> ...
>> <property name="primaryOrdering"><value>:[
>> new SortOrdering(#itemSortProperties.{? name ==
>> "Categorization" }[^].property, @SortOrdering@ASCENDING)
>> ]</value></property>
>>
>> <property name="secondaryOrdering"><value>:[
>> new SortOrdering(#itemSortProperties.{? name == "Item #"
>> }[^].property, @SortOrdering@ASCENDING)
>> ]</value></property>
>>
>> <property name="tertiaryOrdering"><value>:[
>> new SortOrdering(#itemSortProperties.{? name == "Name"
>> }[^].property, @SortOrdering@ASCENDING)
>> ]</value></property>
>>
>> <property name="pageSize"><value>10</value></property>
>>
>> </bean>
>>
>> The above configures a List of Map objects, each of which has a
>> "name", "property" and "advanced" key/value pair. The
>> itemQueryCriteria object is configured from this list by referencing
>> the other bean via "#itemSortProperties". Each of these uses the
>> selection syntax to get a specific item out of the list, then the
>> dynamic subscript "[^]" to return the first item of the list.
>>
>> You can also navigate through other objects in the context for other
>> purposes such as mirroring another configuration value:
>>
>> <bean id="myObject" class="...">
>> <property name="foo"><value>1254</value></property>
>> ...
>> </bean>
>>
>> <bean id="otherObject" class="...">
>> <property name="bar"><value>:[ #myObject.foo ]</value></property>
>> </bean>
>>
>> This can help cut down on error where properties are used repetitively.
>>
>> I'm not sure how I should distribute the source to this or if the
>> project takes contributions. The code has a dependency on OGNL 2.6.3
>> and above (1 jar file of ~185k). Is there a contribution mechanism
>> that I can use to put instructions for use, the jar and the source up
>> somewhere?
>>
>> - Drew
>
|