Aspectran parses configuration files in XML or APON (Aspectran Object Notation) format to create a tree of *Rule objects, which serve as the blueprint for the application’s core components (Bean, Translet, Aspect, etc.). At the heart of this process is the high-performance, event-based parsing engine nodelet, specially designed for Aspectran and implemented in the com.aspectran.utils.nodelet package.
[XML/APON Config File] ---> [NodeletParser] --uses--> [AspectranNodeletGroup] ---> [*Rule Object Tree] ---> [ActivityContext]
1. The Core Parsing Engine: com.aspectran.utils.nodelet
nodelet operates on an event basis, similar to a typical SAX parser, but it is a lightweight parsing framework that allows for much more intuitive and structured processing of complex XML documents by directly mapping callbacks to specific XPath paths.
Key Components:
NodeletParser: The main engine that drives the parsing process. It sequentially reads an XML document, detects the start and end of each node (element), and fires the appropriate event corresponding to the current path.Nodelet/EndNodelet: Event handler (callback) interfaces.Nodelet: Called when the start tag of an XML element is encountered. It processes the attribute information of that element, which is passed as a parameter.EndNodelet: Called when the end tag of an XML element is encountered. It processes the text content (including CDATA) contained within that element.
NodeletGroup: The core design of thenodeletengine. It managesNodelets andEndNodelets in groups along with specific XPath paths. This class provides a Fluent API that acts as a kind of DSL (Domain-Specific Language) for declaratively defining parsing rules..child("elementName"): Starts defining rules for a child element of the current path..parent(): Moves from the current path to the parent path..nodelet(...): Registers aNodeletfor the start tag of the current path..endNodelet(...): Registers anEndNodeletfor the end tag of the current path..with(NodeletAdder): Adds a reusable set of rules to the current group..mount(NodeletGroup): Dynamically “mounts” anotherNodeletGroupat the current path.
NodeletAdder: An interface that adds a reusable set of rules to aNodeletGroup. This allows for the modularization and reuse of parsing logic for common XML structures (e.g.,<item>,<argument>) in multiple places.
The Innovative mount Feature
The mount feature of NodeletGroup is a key element that makes the nodelet engine very powerful and efficient. This feature dynamically activates a pre-defined set of rules from another NodeletGroup when a specific element appears. This provides the following important advantages:
- Memory Efficiency and Performance Improvement: When using
mount, the parser finds theNodeletusing a relative path from the mounted point, instead of the full absolute XPath path (a/b/c/...). This dramatically reduces memory usage and improves parsing performance by avoiding the cost of generating and comparing long XPath strings, even in very deep nested structures. - Maximizing Rule Reusability: Complex structures that can appear repeatedly, such as
<choose>/<when>/<otherwise>, can be defined as a separateNodeletGroup(ChooseNodeletGroup), and this set of parsing rules can be reused anywhere with a single line of.mount(). - Removing Nesting Level Limits: Since
mounteffectively resets the parsing context, there is theoretically no limit to the nesting level of XML elements.
2. Aspectran’s Implementation: com.aspectran.core.context.rule.parser.xml
Aspectran uses the nodelet engine described above to parse its complex XML configuration files (e.g., aspectran-config.xml) and convert their contents into a tree of *Rule objects.
Parsing Flow:
Initialization of
AspectranNodeParser: This is the starting point of the parsing. It internally creates aNodeletParserand registersAspectranNodeletGroup, the masterNodeletGroupwhere all of Aspectran’s XML rules are defined, with the parser. It also sets upAspectranDtdResolverfor DTD validation.AspectranNodeletGroupand*NodeletAdder:AspectranNodeletGroupis the top-level rule group for the<aspectran>root element. This group assembles the entire set of parsing rules by addingBeanNodeletAdder,AspectNodeletAdder,TransletNodeletAdder, etc., which handle each top-level element, through thewith()method.- Each
*NodeletAdderuses the Fluent API ofNodeletGroupto define in detail how to parse the XML element it is responsible for and its child elements. For example,TransletNodeletAdderdefines the rule for the<translet>element, and also defines the rules for child elements that can come inside it, such as<request>,<response>, and<content>. - In particular, Adders with ‘Inner’ in their name, like
ActionInnerNodeletAdderorArgumentNodeletAdder, or those that handle specific elements (argument, property, etc.), are made to parse common XML structures used inside various other rules (e.g.,<translet>,<advice>,<bean>). This maximizes the reusability of parsing logic.
- State Management:
AspectranNodeParsingContextandObjectStack:- XML parsing is a stateful task that needs to handle parent-child relationships. For example, when parsing an
<action>element, you need to know which<translet>this action belongs to. - Aspectran solves this problem through
AspectranNodeParsingContext. This class provides a thread-safeArrayStack(Object Stack) for each thread using aThreadLocalvariable. - How it works:
- When the
Nodeletfor an element’s start tag is called (e.g.,<translet>), it creates aTransletRuleobject to hold that rule and pushes it onto the stack usingAspectranNodeParsingContext.pushObject(). - In the
Nodeletfor a child element (e.g.,<request>), it callsAspectranNodeParsingContext.peekObject()to get the parent object (TransletRule) at the top of the stack and connects itself (RequestRule) to the parent. - When the
EndNodeletfor an element’s end tag is called, the completed object is removed from the stack usingAspectranNodeParsingContext.popObject().
- When the
- XML parsing is a stateful task that needs to handle parent-child relationships. For example, when parsing an
3. Parsing Process with a Real Example
Example 1: Basic <translet> Parsing and the Object Stack
Let’s assume we have the following simple XML configuration:
<translet name="/example/hello">
<action bean="helloAction" method="sayHello" />
</translet>
The process of parsing this XML is as follows:
<translet>Start:NodeletParserencounters the<translet>start tag.- The
nodeletdefined inTransletNodeletAdderis executed. - The
nodeletreads thenameattribute value (“/example/hello”) and creates aTransletRuleobject. - The created
transletRuleobject is pushed onto the Object Stack viaAspectranNodeParsingContext.pushObject(). - Object Stack State:
[ TransletRule(...) ]
<action>Start:- The parser encounters the child node
<action>start tag. - The
nodeletofActionInnerNodeletAdder, which was added byTransletNodeletAddervia.with(ActionInnerNodeletAdder.instance()), is executed. - The
nodeletreads thebeanandmethodattributes and creates anInvokeActionRuleobject. - The created
invokeActionRuleobject is pushed onto the stack. - Object Stack State:
[ TransletRule(...), InvokeActionRule(...) ]
- The parser encounters the child node
</action>End:- The parser encounters the
</action>end tag. - The
endNodeletofActionInnerNodeletAdderis executed. InvokeActionRule invokeActionRule = AspectranNodeParsingContext.popObject(): TheInvokeActionRuleis popped from the stack.HasActionRules hasActionRules = AspectranNodeParsingContext.peekObject(): The top object of the stack, theTransletRule, is checked. (TransletRuleimplements theHasActionRulesinterface.)hasActionRules.putActionRule(invokeActionRule): TheInvokeActionRuleis added as a child to theTransletRule.- Object Stack State:
[ TransletRule(...) ]
- The parser encounters the
</translet>End:- The parser encounters the
</translet>end tag. - The
endNodeletofTransletNodeletAdderis executed. TransletRule transletRule = AspectranNodeParsingContext.popObject(): The fully configuredTransletRuleis popped from the stack.AspectranNodeParsingContext.getCurrentRuleParsingContext().addTransletRule(transletRule): The completed rule is finally registered withRuleParsingContext.- Object Stack State:
[ ](empty)
- The parser encounters the
Example 2: Reusing Complex Structures with the mount Feature
Now let’s look at the power of the mount feature using the nestable <choose> element as an example.
<translet name="/example/conditional">
<choose>
<when test="@{param:type == 'A'}">
<action bean="actionA" />
</when>
<otherwise>
<!-- Another choose can come here -->
<action bean="actionB" />
</otherwise>
</choose>
</translet>
TransletNodeletAdderadds the<choose>rule via.with(ChooseNodeletAdder.instance()).ChooseNodeletAdder, when defining the rules for the<when>and<otherwise>elements, calls.mount(ChooseNodeletGroup.instance()).ChooseNodeletGroupis an independent group that defines the parsing rules for the<choose>element itself.
What this means is:
When the parser enters a
<when>or<otherwise>element, the rules of themountedChooseNodeletGroupare dynamically activated. This means that even if another<choose>element is encountered inside<when>or<otherwise>, the parser can handle the parsing recursively in the same way as the first time.
In this way, the mount feature allows you to define the parsing rules for complex and recursive XML structures only once and reuse them wherever needed, eliminating code duplication and optimizing memory usage.
Example 3: <aspect> Rule and Complex Relationship Settings
Since AOP rules involve complex relationships between multiple components, the parsing process is more multi-layered. Let’s look at it through the following example.
<aspect id="myAspect" order="1">
<joinpoint>
pointcut: {
type: "wildcard",
+: "/example/hello@helloAction^say*"
}
</joinpoint>
<advice bean="loggingAdvice">
<before>
<action bean="profiler" method="start"/>
</before>
</advice>
</aspect>
This XML defines an AOP rule that executes the profiler.start() action of the loggingAdvice bean before any method starting with say (joinpoint) of the helloAction bean is executed in the Translet named /example/hello.
In the pointcut string, the part before the @ delimiter represents the Translet’s name, the part after it represents the Bean’s ID, and the part after the ^ delimiter represents the method name to be called. Wildcards can be used in each part. If you want to target all Translets instead of a specific one, the pattern must start with the @ delimiter (e.g., + "@helloAction^say*").
<aspect>Start:- The
nodeletofAspectNodeletAdderis executed. - It reads the
idandorderattributes, creates anAspectRuleobject, and pushes it onto the stack. - Object Stack:
[ AspectRule(...) ]
- The
<joinpoint>Start and End:- The
nodeletforjoinpointdefined insideAspectNodeletAdderis executed, but it does no special work when using APON format. - The
endNodeletof</joinpoint>is executed. - The
endNodeletreceives the entire APON-formatted text inside the<joinpoint>element as thetextparameter. - Internal logic like
JoinpointRule.updateJoinpoint(joinpointRule, text)parses the APON text to createPointcutRule, etc. - It gets the
AspectRulefrom the stack withpeek()and sets the completedjoinpointRule. - Object Stack:
[ AspectRule(...) ]
- The
<advice>Start:- The
nodeletforadvicedefined insideAspectNodeletAdderis executed. - It reads the
beanattribute (“loggingAdvice”). - It gets the
AspectRulewithpeek()and callssetAdviceBeanId("loggingAdvice")to specify the bean that will perform the Advice. Nothing is pushed onto the stack at this stage. - Object Stack:
[ AspectRule(...) ]
- The
<before>Start:- The child node rules of
adviceare handled byAdviceInnerNodeletAdder. Thenodeletforbeforein this Adder is executed. - It gets the
AspectRulewithpeek()and calls thenewBeforeAdviceRule()method to create anAdviceRuleobject. - The created
adviceRuleis pushed onto the stack. - Object Stack:
[ AspectRule(...), AdviceRule(type=BEFORE, ...) ]
- The child node rules of
<action>Start and End (in<before>):- The
beforerule reuses the rules ofActionInnerNodeletAdder(with(...)). - The
nodeletof<action>is executed, creates anInvokeActionRule(“profiler.start”), and pushes it onto the stack. - Object Stack:
[ AspectRule(...), AdviceRule(...), InvokeActionRule(...) ] - The
endNodeletof</action>is executed. - It pops the
InvokeActionRule, gets theAdviceRulewithpeek(), and adds it as a child viaputActionRule(). - Object Stack:
[ AspectRule(...), AdviceRule(...) ]
- The
</before>End:- The
endNodeletofbeforeis executed. - It pops the
AdviceRule. ThisAdviceRuleis already connected to its parentAspectRule. - Object Stack:
[ AspectRule(...) ]
- The
</advice>End:- No
endNodeletis specifically defined for theadviceelement, so no action is taken.
- No
</aspect>End:- The
endNodeletofAspectNodeletAdderis executed. - It pops the
AspectRule, which is now filled with all information (joinpoint, advice, etc.), from the stack. - The completed
aspectRuleis finally registered withRuleParsingContext. - Object Stack:
[ ](empty)
- The
As you can see, even complex AOP rules are parsed in a very systematic and predictable way through state management using Nodelet and Object Stack, and rule reuse via with().
4. Conclusion
Aspectran’s configuration loading mechanism is the essence of a sophisticated architecture designed on top of its custom nodelet parsing engine. The key advantages of this mechanism can be summarized in three keywords: flexibility, reusability, and performance.
- Flexibility: The Fluent API of
NodeletGroupand themountfeature allow for very flexible responses to complex and variable XML structures. - Reusability:
NodeletAdderperfectly modularizes the parsing logic for common XML structures like<action>and<item>, and minimizes code duplication by easily reusing it wherever needed with thewith()method. - Performance: The
mountfeature dynamically switches the parsing context, so it does not use the full XPath in deeply hierarchical XML documents, thus ensuring efficient parsing performance.
After parsing, the created *Rule objects are a kind of “blueprint”. Based on these blueprints, ActivityContext then creates the actual Bean instances, injects dependencies, and applies AOP proxies to finally build the application’s executable runtime environment. This architecture can be said to be the core foundation of the Aspectran framework’s flexibility and extensibility.