Aspectran parses configuration files in XML or APON (Aspectran Parameters Object Notation) format to create a tree of *Rule objects—the blueprints for the application’s core components (Beans, Translets, Aspects, etc.). At the heart of this process lies nodelet, a high-performance event-driven parsing engine specially designed for Aspectran, implemented in the com.aspectran.utils.nodelet package.
[XML/APON Configuration Files] ---> [NodeletParser] --uses--> [AspectranNodeletGroup] ---> [*Rule Object Tree] ---> [ActivityContext]
1. Core Parsing Engine: com.aspectran.utils.nodelet
nodelet operates on an event-driven basis similar to a standard SAX parser, but is a lightweight parsing framework that maps callbacks directly to specific XPath paths, allowing complex XML documents to be processed much more intuitively and structurally.
Key Components:
NodeletParser: The main engine that drives the parsing process. It sequentially reads XML documents, detects the start and end of each node (element), and triggers the appropriate event corresponding to the current path.Nodelet/EndNodelet: Event handler (callback) interfaces.Nodelet: Called when an XML element’s start tag is encountered. It receives and processes the attribute information of that element as parameters.EndNodelet: Called when an XML element’s end tag is encountered. It processes the text content (including CDATA) contained within that element.
NodeletGroup: The core design of thenodeletengine. ManagesNodeletandEndNodeletbundled together with specific XPath paths in a group. This class provides a Fluent API acting as a kind of DSL (Domain-Specific Language) to declaratively define parsing rules:.child("elementName"): Begins defining rules for a child element of the current path..parent(): Moves from the current path back 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 rule set to the current group..mount(NodeletGroup): Dynamically “mounts” anotherNodeletGrouponto the current path.
NodeletAdder: An interface responsible for adding reusable rule sets to aNodeletGroup. Through this, parsing logic for common XML structures (e.g.,<item>,<argument>) can be modularized and reused across multiple places.
Innovative mount Capability
The mount feature of NodeletGroup is a key element that makes the nodelet engine exceptionally powerful and efficient. When a specific element appears, this feature dynamically activates the rule set of another pre-defined NodeletGroup. This provides the following significant advantages:
- Memory Efficiency and Performance Enhancement: When using
mount, the parser searches forNodelets using relative paths scoped from the mount point rather than full absolute XPath paths (a/b/c/...). Consequently, even in deeply nested structures, the overhead of creating and comparing long XPath strings is eliminated, drastically reducing memory usage and boosting parsing performance. - Maximizing Rule Reusability: Complex structures that can recur repeatedly, such as
<choose>/<when>/<otherwise>, can be defined as an independentNodeletGroup(ChooseNodeletGroup) and reused anywhere needed with a single line of.mount(). - Removing Nesting Depth Limitations: Because
mounteffectively resets the parsing context, there is theoretically no limit to the nesting depth of XML elements.
2. Aspectran Implementation: com.aspectran.core.context.rule.parser.xml
Aspectran uses the nodelet engine described above to parse its complex XML configuration files (such as aspectran-config.xml) and transforms their contents into a tree of *Rule objects.
Parsing Flow:
Initialization of
AspectranNodeParser: The starting point of parsing. Internally, it creates aNodeletParserand registersAspectranNodeletGroup—the master rule group containing all Aspectran XML rules—with the parser. It also configuresAspectranDtdResolverfor DTD validation.AspectranNodeletGroupand*NodeletAdder:AspectranNodeletGroupis the top-level rule group for the<aspectran>root element. This group assembles the complete parsing rules by addingBeanNodeletAdder,AspectNodeletAdder,TransletNodeletAdder, etc., via thewith()method to process each top-level element such as<bean>,<aspect>, and<translet>.- Each
*NodeletAdderdefines parsing methods in detail for its designated XML element and its child elements using the Fluent API ofNodeletGroup. For example,TransletNodeletAdderdefines rules for the<translet>element while simultaneously defining rules for child elements that can appear inside it, such as<request>,<response>, and<content>. - In particular, Adders whose names contain ‘Inner’ or those handling specific elements (argument, property, etc.), such as
ActionInnerNodeletAdderorArgumentNodeletAdder, are created to parse XML structures commonly shared within multiple different rules (e.g.,<translet>,<advice>,<bean>). This maximizes the reusability of parsing logic.
- State Management:
AspectranNodeParsingContextandObjectStack:- XML parsing is inherently a stateful operation that must handle parent-child relationships. For example, when parsing an
<action>element, the parser must know which<translet>that action belongs to. - Aspectran solves this problem through
AspectranNodeParsingContext. This class uses aThreadLocalvariable to provide a thread-safeArrayStack(Object Stack). - Operating Mechanism:
- When an element’s start tag
Nodeletis called (e.g.,<translet>), it instantiates aTransletRuleobject to hold that rule and pushes it onto the stack viaAspectranNodeParsingContext.pushObject(). - In the
Nodeletof a child element (e.g.,<request>), it callsAspectranNodeParsingContext.peekObject()to retrieve the parent object (TransletRule) at the top of the stack and links itself (RequestRule) to the parent. - When the element’s end tag
EndNodeletis called, it removes the completed object from the stack viaAspectranNodeParsingContext.popObject().
- When an element’s start tag
- XML parsing is inherently a stateful operation that must handle parent-child relationships. For example, when parsing an
3. Parsing Process Illustrated with Concrete Examples
Example 1: Basic <translet> Parsing and Object Stack
Suppose we have the following simple XML configuration:
<translet name="/example/hello">
<action bean="helloAction" method="sayHello" />
</translet>
The process of parsing this XML proceeds as follows:
<translet>Start Tag: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 Status:
[ TransletRule(...) ]
<action>Start Tag:- The parser encounters the child node
<action>start tag. - The
nodeletofActionInnerNodeletAdder(added byTransletNodeletAddervia.with(ActionInnerNodeletAdder.instance())) is executed. - The
nodeletreads thebeanandmethodattributes and creates anInvokeActionRuleobject. - The created
invokeActionRuleobject is pushed onto the stack. - Object Stack Status:
[ TransletRule(...), InvokeActionRule(...) ]
- The parser encounters the child node
</action>End Tag:- The parser encounters the
</action>end tag. - The
endNodeletofActionInnerNodeletAdderis executed. InvokeActionRule invokeActionRule = AspectranNodeParsingContext.popObject(): PopsInvokeActionRulefrom the stack.HasActionRules hasActionRules = AspectranNodeParsingContext.peekObject(): Inspects the top object of the stack, namelyTransletRule. (TransletRuleimplements theHasActionRulesinterface.)hasActionRules.putActionRule(invokeActionRule): AddsInvokeActionRuleas a child toTransletRule.- Object Stack Status:
[ TransletRule(...) ]
- The parser encounters the
</translet>End Tag:- The parser encounters the
</translet>end tag. - The
endNodeletofTransletNodeletAdderis executed. TransletRule transletRule = AspectranNodeParsingContext.popObject(): Pops the fully configuredTransletRulefrom the stack.AspectranNodeParsingContext.getCurrentRuleParsingContext().addTransletRule(transletRule): Finally registers the completed rule intoRuleParsingContext.- Object Stack Status:
[ ](empty)
- The parser encounters the
Example 2: Reusing Complex Hierarchies with the mount Capability
Now let’s examine 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 appear here -->
<action bean="actionB" />
</otherwise>
</choose>
</translet>
TransletNodeletAdderadds the<choose>rule via.with(ChooseNodeletAdder.instance()).ChooseNodeletAddercalls.mount(ChooseNodeletGroup.instance())when defining rules for<when>and<otherwise>elements.ChooseNodeletGroupis an independent group that defines parsing rules for the<choose>element itself.
What this means is:
- When the parser enters inside a
<when>or<otherwise>element, the rules of the mountedChooseNodeletGroupare dynamically activated. - In other words, even if another
<choose>element is encountered inside<when>and<otherwise>, the parser can process the parsing recursively in the exact same manner as the first time.
In this way, the mount capability allows parsing rules for complex and recursive XML structures to be defined only once and reused wherever needed, eliminating code duplication and optimizing memory usage.
Example 3: <aspect> Rules and Complex Relationship Setup
Because AOP rules establish complex relationships among multiple components, the parsing process is more multi-layered. Let’s look at the following example:
<aspect id="myAspect" order="1">
<joinpoint>
pointcut: {
type: wildcard
+: /example/hello@helloAction^say*
}
</joinpoint>
<advice bean="loggingAdvice">
<before>
<invoke method="start"/>
</before>
</advice>
</aspect>
This XML defines an AOP rule to execute the start() method of the loggingAdvice bean before (before) any method starting with say in the helloAction bean (joinpoint) is executed within the Translet named /example/hello.
In the pointcut string, the part before the @ delimiter represents the name of the Translet, the part after it indicates the ID of the Bean, and the part after the ^ delimiter denotes the method name to invoke. Wildcards can be used in each part. If you want to target all Translets without specifying a particular Translet, you must start the pattern with the @ delimiter (e.g., + "@helloAction^say*").
<aspect>Start Tag:- The
nodeletofAspectNodeletAdderis executed. - Reads
idandorderattributes, creates anAspectRuleobject, and pushes it onto the stack. - Object Stack:
[ AspectRule(...) ]
- The
<joinpoint>Start and End Tags:- The
nodeletforjoinpointdefined insideAspectNodeletAdderis executed, but when APON format is used, thenodeletitself does not perform any special action. - The
endNodeletof</joinpoint>is executed. endNodeletreceives the entire APON format text inside the<joinpoint>element via thetextparameter.- Through internal logic such as
JoinpointRule.updateJoinpoint(joinpointRule, text), it parses the APON text to createPointcutRule, etc. - Retrieves
AspectRulefrom the stack viapeek()and sets the completedjoinpointRule. - Object Stack:
[ AspectRule(...) ]
- The
<advice>Start Tag:- The
nodeletforadvicedefined insideAspectNodeletAdderis executed. - Reads the
beanattribute (“loggingAdvice”). - Retrieves
AspectRuleviapeek()and callssetAdviceBeanId("loggingAdvice")to designate the bean that will execute the Advice. Nothing is pushed onto the stack at this stage. - Object Stack:
[ AspectRule(...) ]
- The
<before>Start Tag:- Rules for child nodes of
adviceare handled byAdviceInnerNodeletAdder. Thenodeletforbeforein this Adder is executed. - Retrieves
AspectRuleviapeek()and callsnewBeforeAdviceRule()to create anAdviceRuleobject. - Pushes the created
adviceRuleonto the stack. - Object Stack:
[ AspectRule(...), AdviceRule(type=BEFORE, ...) ]
- Rules for child nodes of
<action>Start and End Tags (inside<before>):- The
beforerule reuses the rule ofActionInnerNodeletAdder(with(...)). - The
nodeletof<action>is executed to createInvokeActionRule(“profiler.start”) and pushes it onto the stack. - Object Stack:
[ AspectRule(...), AdviceRule(...), InvokeActionRule(...) ] - The
endNodeletof</action>is executed. - Pops
InvokeActionRule, retrievesAdviceRuleviapeek(), and adds it as a child viaputActionRule(). - Object Stack:
[ AspectRule(...), AdviceRule(...) ]
- The
</before>End Tag:- The
endNodeletofbeforeis executed. - Pops
AdviceRule. ThisAdviceRuleis already linked to the parentAspectRule. - Object Stack:
[ AspectRule(...) ]
- The
</advice>End Tag:- Because no specific
endNodeletis defined on theadviceelement itself, no action is performed.
- Because no specific
</aspect>End Tag:- The
endNodeletofAspectNodeletAdderis executed. - Pops
AspectRule, now populated with all information (joinpoint, advice, etc.), from the stack. - Finally registers the completed
aspectRuleintoRuleParsingContext. - Object Stack:
[ ](empty)
- The
As shown above, even complex AOP rules are parsed in a highly systematic and predictable manner through state management using Nodelet and Object Stack, and rule reuse via with().
4. Conclusion
Aspectran’s configuration loading mechanism is the quintessential architectural design built on top of its proprietary nodelet parsing engine. The core advantages of this mechanism can be summarized by three keywords: flexibility, reusability, and performance.
- Flexibility: The Fluent API and
mountfeature ofNodeletGroupallow it to adapt extremely flexibly to complex and variable XML structures. - Reusability: Through
NodeletAdder, parsing logic for common XML structures like<action>and<item>is completely modularized and easily reused anywhere via thewith()method, minimizing code duplication. - Performance: The
mountfeature dynamically transitions the parsing context, ensuring efficient parsing performance without evaluating full XPath paths even in deeply nested XML documents.
The *Rule objects generated through this parsing process serve as “blueprints”. After parsing is complete, ActivityContext creates actual Bean instances based on these blueprints, injects dependencies, applies AOP proxies, and ultimately constructs the application’s executable runtime environment. This architecture serves as the core foundation for the flexibility and extensibility of the Aspectran framework.