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 thenodelet
engine. It managesNodelet
s andEndNodelet
s 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 aNodelet
for the start tag of the current path..endNodelet(...)
: Registers anEndNodelet
for the end tag of the current path..with(NodeletAdder)
: Adds a reusable set of rules to the current group..mount(NodeletGroup)
: Dynamically “mounts” anotherNodeletGroup
at 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 theNodelet
using 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
mount
effectively 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 aNodeletParser
and registersAspectranNodeletGroup
, the masterNodeletGroup
where all of Aspectran’s XML rules are defined, with the parser. It also sets upAspectranDtdResolver
for DTD validation.AspectranNodeletGroup
and*NodeletAdder
:AspectranNodeletGroup
is 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
*NodeletAdder
uses the Fluent API ofNodeletGroup
to define in detail how to parse the XML element it is responsible for and its child elements. For example,TransletNodeletAdder
defines 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
ActionInnerNodeletAdder
orArgumentNodeletAdder
, 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:
AspectranNodeParsingContext
andObjectStack
:- 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 aThreadLocal
variable. - How it works:
- When the
Nodelet
for an element’s start tag is called (e.g.,<translet>
), it creates aTransletRule
object to hold that rule and pushes it onto the stack usingAspectranNodeParsingContext.pushObject()
. - In the
Nodelet
for 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
EndNodelet
for 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:NodeletParser
encounters the<translet>
start tag.- The
nodelet
defined inTransletNodeletAdder
is executed. - The
nodelet
reads thename
attribute value (“/example/hello”) and creates aTransletRule
object. - The created
transletRule
object is pushed onto the Object Stack viaAspectranNodeParsingContext.pushObject()
. - Object Stack State:
[ TransletRule(...) ]
<action>
Start:- The parser encounters the child node
<action>
start tag. - The
nodelet
ofActionInnerNodeletAdder
, which was added byTransletNodeletAdder
via.with(ActionInnerNodeletAdder.instance())
, is executed. - The
nodelet
reads thebean
andmethod
attributes and creates anInvokeActionRule
object. - The created
invokeActionRule
object 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
endNodelet
ofActionInnerNodeletAdder
is executed. InvokeActionRule invokeActionRule = AspectranNodeParsingContext.popObject()
: TheInvokeActionRule
is popped from the stack.HasActionRules hasActionRules = AspectranNodeParsingContext.peekObject()
: The top object of the stack, theTransletRule
, is checked. (TransletRule
implements theHasActionRules
interface.)hasActionRules.putActionRule(invokeActionRule)
: TheInvokeActionRule
is added as a child to theTransletRule
.- Object Stack State:
[ TransletRule(...) ]
- The parser encounters the
</translet>
End:- The parser encounters the
</translet>
end tag. - The
endNodelet
ofTransletNodeletAdder
is executed. TransletRule transletRule = AspectranNodeParsingContext.popObject()
: The fully configuredTransletRule
is popped from the stack.AspectranNodeParsingContext.assistant().addTransletRule(transletRule)
: The completed rule is finally registered withActivityRuleAssistant
.- 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>
TransletNodeletAdder
adds the<choose>
rule via.with(ChooseNodeletAdder.instance())
.ChooseNodeletAdder
, when defining the rules for the<when>
and<otherwise>
elements, calls.mount(ChooseNodeletGroup.instance())
.ChooseNodeletGroup
is 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 themount
edChooseNodeletGroup
are 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
nodelet
ofAspectNodeletAdder
is executed. - It reads the
id
andorder
attributes, creates anAspectRule
object, and pushes it onto the stack. - Object Stack:
[ AspectRule(...) ]
- The
<joinpoint>
Start and End:- The
nodelet
forjoinpoint
defined insideAspectNodeletAdder
is executed, but it does no special work when using APON format. - The
endNodelet
of</joinpoint>
is executed. - The
endNodelet
receives the entire APON-formatted text inside the<joinpoint>
element as thetext
parameter. - Internal logic like
JoinpointRule.updateJoinpoint(joinpointRule, text)
parses the APON text to createPointcutRule
, etc. - It gets the
AspectRule
from the stack withpeek()
and sets the completedjoinpointRule
. - Object Stack:
[ AspectRule(...) ]
- The
<advice>
Start:- The
nodelet
foradvice
defined insideAspectNodeletAdder
is executed. - It reads the
bean
attribute (“loggingAdvice”). - It gets the
AspectRule
withpeek()
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
advice
are handled byAdviceInnerNodeletAdder
. Thenodelet
forbefore
in this Adder is executed. - It gets the
AspectRule
withpeek()
and calls thenewBeforeAdviceRule()
method to create anAdviceRule
object. - The created
adviceRule
is pushed onto the stack. - Object Stack:
[ AspectRule(...), AdviceRule(type=BEFORE, ...) ]
- The child node rules of
<action>
Start and End (in<before>
):- The
before
rule reuses the rules ofActionInnerNodeletAdder
(with(...)
). - The
nodelet
of<action>
is executed, creates anInvokeActionRule
(“profiler.start”), and pushes it onto the stack. - Object Stack:
[ AspectRule(...), AdviceRule(...), InvokeActionRule(...) ]
- The
endNodelet
of</action>
is executed. - It pops the
InvokeActionRule
, gets theAdviceRule
withpeek()
, and adds it as a child viaputActionRule()
. - Object Stack:
[ AspectRule(...), AdviceRule(...) ]
- The
</before>
End:- The
endNodelet
ofbefore
is executed. - It pops the
AdviceRule
. ThisAdviceRule
is already connected to its parentAspectRule
. - Object Stack:
[ AspectRule(...) ]
- The
</advice>
End:- No
endNodelet
is specifically defined for theadvice
element, so no action is taken.
- No
</aspect>
End:- The
endNodelet
ofAspectNodeletAdder
is executed. - It pops the
AspectRule
, which is now filled with all information (joinpoint, advice, etc.), from the stack. - The completed
aspectRule
is finally registered withActivityRuleAssistant
. - 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
NodeletGroup
and themount
feature allow for very flexible responses to complex and variable XML structures. - Reusability:
NodeletAdder
perfectly 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
mount
feature 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.