Aspect(3pm)
β¨ NAME β¨
so bestie, this is Aspect(3pm) β a page from the User Contributed Perl Documentation collection, and it's about:
Aspect β Aspect-Oriented Programming (AOP) for Perl π¦
πΈ SYNOPSIS πΈ
okay so here's the whole vibe in code, no cap:
use Aspect;
# Run some code "Advice" before a particular function
before {
print "About to call create\n";
} call 'Person::create';
# Run Advice after several methods and hijack their return values
after {
print "Called getter/setter " . $_->sub_name . "\n";
$_->return_value(undef);
} call qr/^Person::[gs]et_/;
# Run Advice conditionally based on multiple factors
before {
print "Calling a get method in void context within Tester::run_tests";
} wantvoid
& ( call qr/^Person::get_/ & ! call 'Person::get_not_trapped' )
& cflow 'Tester::run_tests';
# Context-aware runtime hijacking of a method if certain condition is true
around {
if ( $_->self->customer_name eq 'Adam Kennedy' ) {
# Ensure I always have cash
$_->return_value('One meeeelion dollars');
} else {
# Take a dollar off everyone else
$_->proceed;
$_->return_value( $_->return_value - 1 );
}
} call 'Bank::Account::balance';
# Catch and handle unexpected exceptions in a function into a formal object
after {
$_->exception(
Exception::Unexpected->new($_->exception)
);
} throwing()
& ! throwing('Exception::Expected')
& ! throwing('Exception::Unexpected');
# Run Advice only on the outmost of a recursive series of calls
around {
print "Starting recursive child search\n";
$_->proceed;
print "Finished recursive child search\n";
} call 'Person::find_child' & highest;
# Run Advice only during the current lexical scope
SCOPE: {
my $hook = before {
print "About to call create\n";
} call 'Person::create';
Person->create('Bob'); # Advice will run
}
Person->create('Tom'); # Advice won't run
# Use a pre-packaged collection "Aspect" of Advice rules to change a class
aspect Singleton => 'Foo::new';
# Define debugger breakpoints with high precision and conditionality
aspect Breakpoint => call qr/^Foo::.+::Bar::when_/ & wantscalar & highest;
π DESCRIPTION π
What is Aspect-Oriented Programming? π
okay so Aspect-Oriented Programming (AOP) is a whole programming paradigm whose entire goal is to increase modularity by letting you separate out the stuff it calls "cross-cutting" concerns. it's giving "stop copy-pasting the same concern into 40 files" energy.
It comes with programming methods and tools that support modularizing concerns right at the level of the source code β and then "aspect-oriented software development" is the name for the whole engineering discipline around that. two different things, bestie, don't mix them up π«Ά
so what does AOP actually do for you? it lets you modularise code for issues that would otherwise be spread across many parts of a program β the kind of stuff that's genuinely painful to implement and painful to maintain. π
Logging is the iconic example here, because a logging strategy necessarily affects every logged part of the system. that's literally why it's called a crosscutting concern β logging "crosscuts" all logged classes and methods. periodt.
Typically an aspect ends up scattered or tangled as code, which makes it harder to understand and maintain. It's scattered because a function (like logging) gets spread over a bunch of unrelated functions that might use it β possibly in entirely unrelated systems.
and that means changing logging can require modifying all the affected modules. π Aspects get tangled not just with the mainline function of the systems they live in, but with each other too. so changing one concern means you gotta understand all the tangled concerns β or have some way to figure out what the effect of the changes actually is.
But here's the slay: because Aspect-Oriented Programming moves all this scattered code into a single module loaded as a single unit, you get another major benefit β conditional compilation. β¨
Features implemented via Aspects can be compiled and added to your program only in certain situations. and because of that, Aspects are super useful when you're debugging or testing large or complex programs. π₯
Aspects can implement features that programs need for correctness β think reactivity, synchronisation β and they can be used to add checking assertions to your own modules or to other people's modules. π
They can also make code emit useful side effects that the original author of a module never even thought about, without changing the module's original function. chef's kiss.
And, if necessary (although not recommended β οΈ), they can do various types of "Monkey Patching" β hijacking the functionality of some other module in a way the original author definitely did not expect, so the module acts differently when it's used in your program. this is for when those changes might otherwise be dangerous, or if other programs might run into them. so like, use with care, fr fr.
Aspects can also be used to implement space or time optimisations. one super popular AOP use case is adding caching to a module or function that doesn't natively implement caching itself. π
want more on Aspect-Oriented Programming in general? check out<http://en.wikipedia.org/wiki/Aspect-oriented_programming>and<http://www.aosd.net>π¦
About This Implementation π§
The Perl Aspect module tries to closely follow the terminology of the basic Java AspectJ project wherever that's possible and reasonable β see <http://eclipse.org/aspectj>.
But lowkey? Perl is dynamic, and because of that, several "AspectJ" features are just useless for us: exception softening, mixin support, out-of-class method declarations, annotations, and others. not our problem, moving on. π
Currently the Perl Aspect module is focused exclusively on subroutine matching and wrapping.
It lets you select collections of subroutines and conditions using a flexible pointcut language, and then modify their behavior in literally any way you want. iconic.
In that regard it gives you a similar set of functionality to the venerable Hook::LexWrap β but with much more precision, and with way more control and maintainability as the complexity of the problems you're solving goes up. π
On top of that: the Java implementation of Aspect-Oriented Programming is limited to concepts expressible at compile time, but Perl is more fluid than that, which means the Aspect module can weave in aspect code at run-time. β¨ Pointcuts in Perl can also take advantage of run-time information and Perl-specific features like closures to implement more sophisticated pointcuts than Java can ever manage.
And that means the Perl implementation of Aspect-Oriented Programming can be stateful and adaptive in a way Java just cannot be β although, ngl, that added power can come with a significant speed cost if you don't use it carefully. π
Terminology π
One of the more opaque aspects (no pun intended π) of Aspect-Oriented programming is that it has an entire unique set of terms, and those terms can be so confusing if you're just learning to use the Aspect module.
So in this section we're gonna define all the major terms in a way that β hopefully β actually makes sense to Perl programmers. you got this bestie π«Ά
What is an Aspect? π
An Aspect is a modular unit of cross-cutting implementation. it consists of "Advice" on "Pointcuts" β we'll define those two in a sec, don't worry if they don't make sense yet.
In Perl, that typically means a package or module containing:
- declarations of where to inject code,
- the code to run at those points,
- and any variables or support functions the injected functionality needs.
The most critical point here is that the Aspect represents a collection of many different injection points which collectively implement a single function or feature β and which should be enabled on an all or nothing basis.
For example, you might implement the Aspect My::SecurityMonitor as a module that injects hooks into a dozen different strategic places in your program, watching for valid-but-suspicious values and reporting those values to an external network server. π΅οΈ
Aspects can often be written to be highly reusable, and released via the CPAN. And when those generic aspects are written in the special namespace Aspect::Library, you can call them using this special shorthand:
use Aspect;
# Load and enable the Aspect::Library::NYTProf aspect to constrain profiling
# to only the object constructors for each class in your program.
aspect NYTProf => call qr/^MyProgram\b.*::new$/;
What is a Pointcut? π―
A Join Point is a well-defined location at a point in the execution of a program where Perl can inject functionality β in effect, joining two different bits of code together.
In the Perl Aspect implementation, this consists only of the execution of named subroutines on the symbol table, like "Foo::Bar::function_name".
In other languages, extra join points can exist β like the instantiation or destruction of an object, or the static initialisation of a class. not here tho.
A Pointcut is a well-defined set of join points, plus any conditions that must be true while you're at those join points.
Examples include "All public methods in class "Foo::Bar"" or "Any non-recursive call to the function "Some::recursive_search"".
We'll go through each of the available pointcut types later in this document. π
And in addition to the default pointcut types, it's possible to write your own specialised pointcut types β though ngl, that's challenging, because of the complex API they follow to allow aggressive multi-pass optimisation. π
See Aspect::Pointcut for more information.
What is Advice? π
Advice is code designed to run automatically at all of the join points in a particular pointcut. Advice comes in several types, telling Perl whether the code should run "before", "after" or "around" (in place of) the different join points in the pointcut.
Advice code is introduced lexically to the target join points. What that means is: the new functionality gets injected in place into the existing program, rather than the class being extended into some new version.
For example: the function "Foo::expensive_calculation" may not support caching, because in the general case it's unsafe to do so. But you know that in the case of your program, the reasons it's unsafe in the general case just don't apply. π§
So for your program you might use the Aspect::Library::Memoise aspect to "Weave" Advice code into the "Foo" class, which adds caching to the function by integrating it with Memoise. slay.
Each of the different advice types needs to be used slightly differently, and each is best employed for different types of jobs. We'll discuss how to use each of the different advice types later in this document.
But in general? the more specific the advice type you use, the more optimisation can be applied to your advice declaration β and the less impact the advice will have on the speed of your program. π₯
okay so real talk bestie π β beyond the default pointcut types, it is (theoretically) possible to write your own specialised Advice types, although this would be extremely difficult and probably involve some form of XS programming. not exactly a chill hangout activity, ngl.
For the brave, see Aspect::Advice and the source for the different advice classes for more information. π«‘
What is Weaving? π§Άβ¨
Weaving is the installation of advice code to the subs that match a pointcut, or might potentially match depending on certain run-time conditions. it's giving "sewing your logic directly into the fabric of the call stack" π
In the Perl Aspect module, weaving happens on the declaration of each advice block. Unweaving happens when a lexically-created advice variable goes out of scope.
Unfortunately, due to the nature of the mechanism Aspect uses to hook into function calls, unweaving can never be guaranteed to be round-trip clean. π
While the pointcut matching logic and advice code will never be run for unwoven advice, it may be necessary to leave the underlying hooking artifact in place on the join point indefinitely (imposing a small performance penalty and preventing clean up of the relevant advice closure from memory). so it's lowkey leaving a little ghost behind π»
Programs that repeatedly weave and unweave during execution will thus gradually slow down and leak memory, and so is discouraged despite being permitted. do not do this if you can avoid it, fr fr π
If advice needs to be repeatedly enabled and disabled you should instead consider using the true pointcut and a variable in the aspect package or a closure to introduce a remote "on/off" switch for the aspect.
into the advice code.
package My::Aspect;
my $switch = 1;
before {
print "Calling Foo::bar\n";
} call 'Foo::bar' & true { $switch };
sub enable {
$switch = 1;
}
sub disable {
$switch = 0;
}
1;
Under the covers weaving is done using a mechanism that is very similar to the venerable Hook::LexWrap, although in some areas Aspect will try to make use of faster mechanisms if it knows these are safe.
Feature Summary π
- Create permanent pointcuts, advice, and aspects at compile time or run-time. β°
- Flexible pointcut language: select subs to match using string equality, regexp, or
CODEref. Match currently running sub, a sub in the call flow, calls in particular void, scalar, or array contexts, or only the highest call in a set of recursive calls. π― - Build pointcuts composed of a logical expression of other pointcuts, using conjunction, disjunction, and negation. π§
- In advice code, you can modify parameter list for matched sub, modify return value, throw or supress exceptions, decide whether or not to proceed to matched sub, access a
CODEref for matched sub, and access the context of any call flow pointcuts that were matched, if they exist. π - Add/remove advice and entire aspects lexically during run-time. The scope of advice and aspect objects, is the scope of their effect (This does, however, come with some caveats). πͺ
- A basic library of reusable aspects. A base class makes it easy to create your own reusable aspects. The
Aspect::Library::Memoizeaspect is an example of how to interface with AOP-like modules from CPAN. π
Using Aspect.pm π οΈ
The Aspect package allows you to create pointcuts, advice, and aspects in a simple declarative fashion. This declarative form is a simple facade on top of the Perl AOP framework, which you can also use directly if you need the increased level of control or you feel the declarative form is not clear enough.
For example, the following two examples are equivalent.
use Aspect;
# Declarative advice creation
before {
print "Calling " . $_->sub_name . "\n";
} call 'Function::one'
| call 'Function::two';
# Longhand advice creation
Aspect::Advice::Before->new(
Aspect::Pointcut::Or->new(
Aspect::Pointcut::Call->new('Function::one'),
Aspect::Pointcut::Call->new('Function::two'),
),
sub {
print "Calling " . $_->sub_name . "\n";
},
);
You will be mostly working with this package (Aspect) and the Aspect::Point package, which provides the methods for getting information about the call to the join point within advice code.
When you use Aspect; you will import a family of around fifteen functions. These are all factories that allow you to create pointcuts, advice, and aspects. πβ¨
Back Compatibility β³
The various APIs in Aspect have changed a few times between older versions and the current implementation.
By default, none of these changes are available in the current version of the Aspect module. They can, however, be accessed by providing one of two flags when loading Aspect.
# Support for pre-1.00 Aspect usage
use Aspect ':deprecated';
The :deprecated flag loads in all alternative and deprecated function and method names, and exports the deprecated after_returning, after_throwing advice constructors, and the deprecated if_true alias for the true pointcut.
# Support for pre-2010 Aspect usage (both usages are equivalent)
use Aspect ':legacy';
use Aspect::Legacy;
The :legacy flag loads in all alternative and deprecated functions as per the :deprecated flag.
Instead of exporting all available functions and pointcut declarators it exports only the set of functions that were available in Aspect 0.12.
Finally, it changes the behaviour of the exported version of after to add an implicit & returning to all pointcuts, as the original implementation did not trap exceptions. π°οΈ
FUNCTIONS π
The following functions are exported by default (and are documented as such) but are also available directly in Aspect:: namespace as well if needed.
They are documented in order from the simplest and and most common pointcut declarator to the highest level declarator for enabling complete aspect classes.
call π
my $single = call 'Person::get_address';
my $multiple = call qr/^Person::get_/;
my $complex = call sub { lc($_[0]) eq 'person::get_address' };
my $object = Aspect::Pointcut::Call->new('Person::get_address');
The most common pointcut is call. All three of the examples will match the calling of Person::get_address() as defined in the symbol table at the time an advice is declared.
The call declarator takes a single parameter which is the pointcut spec, and can be provided in three different forms.
string
Select only the specific full resolved subroutine whose name is equal to the specification string.
For example call 'Person::get' will only match the plain get method and will not match the longer get_address method.
regexp
Select all subroutines whose name matches the regular expression.
The following will match all the subs defined on the Person class, but not on the Person::Address or any other child classes.
$p = call qr/^Person::\w+$/;
CODE
Select all subroutines where the supplied code returns true when passed a full resolved subroutine name as the only parameter.
The following will match all calls to subroutines whose names are a key in the hash %subs_to_match:
$p = call sub {
exists $subs_to_match{$_[0]};
}
For more information on the call pointcut see Aspect::Pointcut::Call.
cflow π
before {
print "Called My::foo somewhere within My::bar\n";
} call 'My::foo'
& cflow 'My::bar';
The cflow declarator is used to specify that the join point must be somewhere within the control flow of the My::bar function. That is, at the time My::foo is being called somewhere up the call stack is My::bar.
The parameters to cflow are identical to the parameters to call.
Due to an idiosyncracy in the way cflow is implemented, they do not always parse properly well when joined with an operator. In general, you should use any cflow operator last in your pointcut specification, or use explicit braces for it.
# This works fine
my $x = call 'My::foo' & cflow 'My::bar';
# This will error
my $y = cflow 'My::bar' & call 'My::foo';
# Use explicit braces if you can't have the flow last
my $z = cflow('My::bar') & call 'My::foo';
For more information on the cflow pointcut, see Aspect::Pointcut::Cflow.
wantlist π
my $pointcut = call 'Foo::bar' & wantlist;
The wantlist pointcut traps a condition based on Perl wantarray context, when a function is called in list context. When used with call, this pointcut can be used to trap list-context calls to one or more functions, while letting void or scalar context calls continue as normal.
For more information on the wantlist pointcut see Aspect::Pointcut::Wantarray.
wantscalar π’
my $pointcut = call 'Foo::bar' & wantscalar;
The wantscalar pointcut traps a condition based on Perl wantarray context, when a function is called in scalar context. When used with call, this pointcut can be used to trap scalar-context calls to one or more functions, while letting void or list context calls continue as normal.
For more information on the wantscalar pointcut see Aspect::Pointcut::Wantarray.
wantvoid π³οΈ
my $bug = call 'Foo::get_value' & wantvoid;
The wantvoid pointcut traps a condition based on Perl wantarray context, when a function is called in void context. When used with call, this pointcut can be used to trap void-context calls to one or more functions, while letting scalar or list context calls continue as normal.
This is particularly useful for methods which make no sense to call in void context, such as getters or other methods calculating and returning a useful result.
For more information on the wantvoid pointcut see Aspect::Pointcut::Wantarray.
highest π
my $entry = call 'Foo::recurse' & highest;
The highest pointcut is used to trap the first time a particular function is encountered, while ignoring any subsequent recursive calls into the same pointcut.
It is unusual in that unlike all other types of pointcuts it is stateful, and so some detailed explaination is needed to understand how it will behave.
Pointcut declarators follow normal Perl precedence and shortcutting in the same way that a typical set of foo() and bar() might do for regular code.
When the highest is evaluated for the first time it returns true and a counter is to track the depth of the call stack. This counter is bound to the join point itself, and will decrement back again once we exit the advice code.
If we encounter another function that is potentially contained in the same pointcut, then highest will always return false.
So like, in this manner, you can trigger functionality to run only at the outermost call into a recursive series of functions, or you can negate the pointcut with ! highest and look for recursive calls into a function when there shouldn't be any recursion. π
In the current implementation, the semantics and behaviour of pointcuts containing multiple highest declarators is not defined (and the current implementation is also not amenable to supporting it). π
For these reasons, the usage of multiple highest declarators such as in the following example is not supported, and so the following will throw an exception. π
before {
print "This advice will not compile\n";
} wantscalar & (
(call 'My::foo' & highest)
|
(call 'My::bar' & highest)
);
This limitation may change in future releases. Feedback welcome. π«Ά
For more information on the highest pointcut see Aspect::Pointcut::Highest.
π throwing π
my $string = throwing qr/does not exist/;
my $object = throwing 'Exception::Class';
The throwing pointcut is used with the after to restrict the pointcut so advice code is only fired for a specific die message or a particular exception class (or subclass). β¨
The throwing declarator takes a single parameter which is the pointcut spec, and can be provided in two different forms.
π regexp
If a regular expression is passed to throwing it will be matched against the exception if and only if the exception is a plain string.
Thus, the regexp form can be used to trap unstructured errors emitted by die or croak while NOT trapping any formal exception objects of any kind. π
π string
If a string is passed to throwing it will be treated as a class name and will be matched against the exception via an isa method call if and only if the exception is an object.
Thus, the string form can be used to trap and handle specific types of exceptions while allowing other types of exceptions or raw string errors to pass through.
For more information on the throwing pointcut see Aspect::Pointcut::Throwing.
πΈ returning πΈ
after {
print "No exception\n";
} call 'Foo::bar' & returning;
The returning pointcut is used with after advice types to indicate the join point should only occur when a function is returning without throwing an exception. π
π¦ true π¦
# Intercept an adjustable random percentage of calls to a function
our $RATE = 0.01;
before {
print "The few, the brave, the 1%\n";
} call 'My::foo'
& true {
rand() < $RATE
};
Because of the lengths that Aspect goes to internally to optimise the selection and interception of calls, writing your own custom pointcuts can be very difficult. π
When a custom or unusual pattern of interception is needed, often all that is desired is to extend a relatively normal pointcut with an extra caveat.
To allow for this scenario, Aspect provides the true pointcut. β¨
This pointcut allows you to specify any arbitrary code to match on. This code will be executed at run-time if the join point matches all previous conditions.
The join point matches if the function or closure returns true, and does not match if the code returns false or nothing at all.
π before π
before {
# Don't call the function, return instead
$_->return_value(1);
} call 'My::foo';
The before advice declaration is used to defined advice code that will be run instead of the code originally at the join points, but continuing on to the real function if no action is taken to say otherwise.
When called in void context, as shown above, before will install the advice permanently into your program.
When called in scalar context, as shown below, before will return a guard object and enable the advice for as long as that guard object continues to remain in scope or otherwise avoid being destroyed.
SCOPE: {
my $guard = before {
print "Hello World!\n";
} call 'My::foo';
# This will print
My::foo();
}
# This will NOT print
My::foo();
Because the end result of the code at the join points is irrelevant to this type of advice and the Aspect system does not need to hang around and maintain control during the join point, the underlying implementation is done in a way that is by far the fastest and with the least impact (essentially none) on the execution of your program. ποΈ
You are strongly encouraged to use before advice wherever possible for the current implementation, resorting to the other advice types when you truly need to be there at the end of the join point execution (or on both sides of it).
For more information, see Aspect::Advice::Before.
π after π
# Confuse a program by bizarely swapping return values and exceptions
after {
if ( $_->exception ) {
$_->return_value($_->exception);
} else {
$_->exception($_->return_value);
}
} call 'My::foo' & wantscalar;
The after declarator is used to create advice in which the advice code will be run after the join point has run, regardless of whether the function return correctly or throws an exception. π
For more information, see Aspect::Advice::After.
π around π
# Trace execution time for a function
around {
my @start = Time::HiRes::gettimeofday();
$_->proceed;
my @stop = Time::HiRes::gettimeofday();
my $elapsed = Time::HiRes::tv_interval( \@start, \@stop );
print "My::foo executed in $elapsed seconds\n";
} call 'My::foo';
The around declarator is used to create the most general form of advice, and can be used to implement the most high level functionality. β¨
It allows you to make changes to the calling parameters, to change the result of the function, to subvert or prevent the calling altogether, and to do so while storing extra lexical state of your own across the join point.
For example, the code shown above tracks the time at which a single function is called and returned, and then uses the two pieces of information to track the execution time of the call.
Similar functionality to the above is used to implement the CPAN modules Aspect::Library::Timer and the more complex Aspect::Library::ZoneTimer.
Within the around advice code, the $_->proceed method is used to call the original function with whatever the current parameter context is, storing the result (whether return values or an exception) in the context as well.
Alternatively, you can use the original method to get access to a reference to the original function and call it directly without using context parameters and without storing the function results.
around {
$_->original->('alternative param');
$_->return_value('fake result');
} call 'My::foo';
The above example calls the original function directly with an alternative parameter in void context (regardless of the original wantarray context) ignoring any return values. It then sets an entirely made up return value of its own.
Although it is the most powerful advice type, around is also the slowest advice type with the highest memory cost per join point. Where possible, you should try to use a more specific advice type. π
For more information, see Aspect::Advice::Around.
π¦ aspect π¦
aspect Singleton => 'Foo::new';
The aspect declarator is used to enable complete reusable aspects. β¨
The first parameter to aspect identifies the aspect library class. If the parameter is a fully resolved class name (i.e. it contains double colons like Foo::Bar) the value it will be used directly. If it is a simple Identifier without colons then it will be interpreted as Aspect::Library::Identifier.
If the aspect class is not loaded, it will be loaded for you and validated as being a subclass of Aspect::Library.
And further parameters will be passed on to the constructor for that class. See the documentation for each class for more information on the appropriate parameters for that class.
As with each individual advice type complete aspects can be defined globally by using aspect in void context, or lexically via a guard object by calling aspect in scalar context.
# Break on the topmost call to function for a limited time
SCOPE: {
my $break = aspect Breakpoint => call 'My::foo' & highest;
do_something();
}
For more information on writing reusable aspects, see Aspect::Library.
π OPERATORS π
π & π
Overloading of bitwise & for pointcut declarations allows a natural looking boolean and logic for pointcuts. When using the & operator the combined pointcut expression will match if all pointcut subexpressions match. π₯
In the original Java AspectJ framework, the subexpressions are considered to be a union without an inherent order at all. In Perl you may treat them as ordered since they are ordered internally, but since all subexpressions run anyway you should probably not do anything that relies on this order. The optimiser may do interesting things with order in future, or we may move to an unordered implementation. π€
For more information, see Aspect::Pointcut::And.
π | π
Overloading of bitwise | for pointcut declarations allows a natural looking boolean or logic for pointcuts. When using the | operator the combined pointcut expression will match if either pointcut subexpressions match. β¨
The subexpressions are ostensibly considered without any inherent order, and you should treat them that way when you can. However, they are internally ordered and shortcutting will be applied as per normal Perl expressions. So for speed reasons, you may wish to put cheap pointcut declarators before expensive ones where you can.
The optimiser may do interesting things with order in future, or we may move to an unordered implementation. So as a general rule, avoid things that require order while using order to optimise where you can.
For more information, see Aspect::Pointcut::Or.
π ! π
Overload of negation ! for pointcut declarations allows a natural looking boolean not logic for pointcuts. When using the ! operator the resulting pointcut expression will match if the single subexpression does not match. π
For more information, see Aspect::Pointcut::Not.
β¨ METHODS β¨
okay so bestie, each type of advice code has a whole range of methods you can use. They're all summarised below, and if you want the full tea, check out Aspect::Point.
π type
The type method is a little convenience thing for when your advice code is used in more than one type of advice, and you wanna know which advice declarator it came from.
It returns "before", "after" or "around".
π pointcut
my $pointcut = $_->pointcut;
The pointcut method gives you access to the original join point specification β that's a tree of Aspect::Pointcut objects β that the current join point matched against.
π original
$_->original->( 1, 2, 3 );
In a pointcut, the original method returns a CODE reference to the original function before it got hooked by the Aspect weaving process.
π sub_name
# Prints "Full::Function::name"
before {
print $_->sub_name . "\n";
} call 'Full::Function::name';
The sub_name method returns a string with the full resolved function name at the join point the advice code is running at.
π package_name
# Prints "Just::Package"
before {
print $_->package_name . "\n";
} call 'Just::Package::name';
The package_name parameter is a convenience wrapper around the sub_name method. Where sub_name returns the fully resolved function name, package_name returns just the namespace of the package of the join point.
π short_name
# Prints "name"
before {
print $_->short_name . "\n";
} call 'Just::Package::name';
The short_name parameter is a convenience wrapper around the sub_name method. Where sub_name returns the fully resolved function name, short_name returns just the name of the function.
π args
# Get the parameters as a list
my @list = $_->args;
# Set the parameters
$_->args( 1, 2, 3 );
# Append a parameter
$_->args( $_->args, 'more' );
The args method lets you get or set the list of parameters to a function. It's basically the method equivalent of messing with the @_ array.
π self
after {
$_->self->save;
} My::Foo::set;
The self method is a convenience for when you're writing advice that works with object-oriented Perl code. It returns the first parameter to the method (which should be the object), and then you can call methods on it. Slay.
π wantarray
# Return differently depending on the calling context
if ( $_->wantarray ) {
$_->return_value(5);
} else {
$_->return_value(1, 2, 3, 4, 5);
}
The wantarray method returns the wantarray in perlfunc context of the call to the function for the current join point.
Just like the core Perl wantarray function, it returns true if the function is being called in list context, false if it's being called in scalar context, or undef if it's being called in void context.
π exception
unless ( $_->exception ) {
$_->exception('Kaboom');
}
The exception method is used to get the current die message or exception object, or to set the die message or exception object.
π return_value
# Add an extra value to the returned list
$_->return_value( $_->return_value, 'thing' );
# Return null (equivalent to "return;")
$_->return_value;
The return_value method is used to get or set the return value for the join point function, kinda like the normal Perl return keyword.
π proceed
around {
my $before = time;
$_->proceed;
my $elapsed = time - $before;
print "Call to " . $_->sub_name . " took $elapsed seconds\n";
} call 'My::function';
Only available in "around" advice, the proceed method is used to run the join point function with the current join point context (parameters, scalar vs list call, etc.) and store the result of the original call in the join point context (return values, exceptions, etc.).
π LIBRARY π
The main Aspect distribution ships with the following set of libraries. These are not necessarily recommended or the best on offer. They're shipped with Aspect for convenience, because they have no additional CPAN dependencies.
Their purpose is summarised below, but see their own documentation for more info.
π§ Aspect::Library::Singleton
Aspect::Library::Singleton can be used to convert an existing class to function as a singleton and return the same object for every constructor call.
π§ Aspect::Library::Breakpoint
Aspect::Library::Breakpoint allows you to inject debugging breakpoints into a program using the full power and complexity of the "Aspect" pointcuts.
π§ Aspect::Library::Wormhole
Aspect::Library::Wormhole is a tool for passing objects down a call flow, without adding extra arguments to the frames between the source and the target, letting a function have an implicit context.
π§ Aspect::Library::Listenable
Aspect::Library::Listenable assists in the implementation of the "Listenable" design pattern. It lets you define a function as emitting events that can be registered for by subscribers, and then add/remove subscribers for these events over time.
When the functions that are listenable are called, registered subscribers will be notified. This lets you build a general event subscription system for your program. This could be as part of a plugin API or just for your own convenience.
π§ INTERNALS π§
Due to the dynamic nature of Perl, there's no need for processing of source or byte code, as required in the Java and .NET worlds.
The implementation is conceptually very simple: when you create advice, its pointcut is matched to find every sub defined in the symbol table that might match against the pointcut (potentially subject to further runtime conditions).
Those that match will get a special wrapper installed. The wrapper only executes if, during run-time, a compiled context test for the pointcut returns true.
The wrapper code creates an advice context, and gives it to the advice code.
Most of the complexity comes from the extensive optimisation that is used to reduce the impact of both weaving of the advice and the run-time costs of the wrappers added to your code.
Some pointcuts like "call" are static and their full effect is known at weave time, so the compiled run-time function can be optimised away entirely.
Some pointcuts like "cflow" are dynamic, so they are not used to select the functions to hook, but impose a run-time cost to determine whether or not they match.
To make this process faster, when the advice is installed, the pointcut will not use itself directly for the compiled run-time function but will additionally generate a "curried" (optimised) version of itself.
This curried version uses the fact that the run-time check will only be called if it matches the "call" pointcut pattern, and so no "call" pointcuts needed to be tested at run-time unless they are in deep and complex nested boolean logic. It also handles collapsing any boolean logic impacted by the safe removal of the "call" pointcuts.
Further, where possible the pointcuts will be expressed as Perl source (including logic operators) and compiled into a single Perl expression. This not only massively reduces the number of functions to be called, but allows further optimisation of the pointcut by the opcode optimiser in perl itself.
If you use only "call" pointcuts (alone or in boolean combinations) the currying results in a null test (the pointcut is optimised away entirely) and so the need to make a run-time point test will be removed altogether from the generated advice hooks, reducing call overheads significantly.
If your pointcut does not have any static conditions (i.e. "call") then the wrapper code will need to be installed into every function on the symbol table. This is highly discouraged and liable to result in hooks on unusual functions and unwanted side effects, potentially breaking your program.
β οΈ LIMITATIONS β οΈ
π« Inheritance Support
Support for inheritance is lacking. Consider the following two classes:
package Automobile;
sub compute_mileage {
# ...
}
package Van;
use base 'Automobile';
And the following two advice:
before {
print "Automobile!\n";
} call 'Automobile::compute_mileage';
before {
print "Van!\n";
} call 'Van::compute_mileage';
Some join points one would expect to be matched by the call pointcuts above, do not:
$automobile = Automobile->new;
$van = Van->new;
$automobile->compute_mileage; # Automobile!
$van->compute_mileage; # Automobile!, should also print Van!
"Van!" will never be printed. This happens because Aspect installs advice code on symbol table entries. "Van::compute_mileage" does not have one, so nothing happens. Until this is solved, you have to do the thinking about inheritance yourself.
π« Performance
You may find it very easy to shoot yourself in the foot with this module. Consider this advice:
# Do not do this!
before {
print $_->sub_name;
} cflow 'MyApp::Company::make_report';
The advice code will be installed on every sub loaded. The advice code will only run when in the specified call flow, which is the correct behavior, but it will be installed on every sub in the system. This can be extremely slow because the run-time cost of checking "cflow" will occur on every single function called in your program.
It happens because the "cflow" pointcut matches all subs during weave-time. It matches the correct sub during run-time. The solution is to narrow the pointcut:
# Much better
before {
print $_->sub_name;
} call qr/^MyApp::/
& cflow 'MyApp::Company::make_report';
β¨ TO DO β¨
okay so like, there's so much that could get added here if anyone's feeling generous and wants to contribute to the project. bestie, the door is open π
π Documentation
- cookbook π
- tutorial β¨
- example of refactoring a useful CPAN module using aspects π¦
π Pointcuts
- New pointcuts:
execution,cflowbelow,within,advice,calledby. Sure you can implement them today with Perl treachery, but it is too much work π - We need a way to match subs with an attribute,
attributes::get()will currently not work π isa()support for method pointcuts as Gaal Yahas suggested: match methods on class hierarchies without callbacks π- Perl join points: phasic-
BEGIN/INIT/CHECK/END
π§ Weaving
- The current optimation has gone as far as it can, next we need to look into XS acceleration and byte code manipulation with
B::modules π₯ - A debug flag to print out subs that were matched during weaving
- Warnings when over 1000 methods wrapped β οΈ
- Allow finer control of advice execution order
- Centralised hooking in wrappers so that each successive advice won't need to wrap around the previous one.
- Allow lexical aspects to be safely removed completely, rather than being left in place and disabled as in the current implementation.
π«Ά SUPPORT
Please report any bugs or feature requests through the web interface at <http://rt.cpan.org/Public/Dist/Display.html?Name=Aspect>. no cap, they wanna hear from u π
πΈ INSTALLATION
See perlmodinstall for information and options on installing Perl modules. it's giving "read this before u yell at ur terminal" energy fr fr
π AVAILABILITY
The latest version of this module is available from the Comprehensive Perl Archive Network (CPAN). Visit <http://www.perl.com/CPAN/> to find a CPAN site near you. Or see <http://search.cpan.org/perldoc?Aspect>. π¦
π AUTHORS
- Adam Kennedy
<adamk@cpan.org> - Marcel GrΓΌnauer
<marcel@cpan.org> - Ran Eilam
<eilara@cpan.org>
icons, honestly π
β SEE ALSO
You can find AOP examples in the examples/ directory of the distribution. π
Aspect::Library::MemoizeAspect::Library::ProfilerAspect::Library::Trace
π COPYRIGHT
Copyright 2001 by Marcel GrΓΌnauer
Some parts copyright 2009 - 2013 Adam Kennedy.
Parts of the initial introduction courtesy Wikipedia.
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
and that's the whole page bestie!! π every last crumb. this wasperl v5.36.0, dated2023-07-02, and it's theAspect(3pm)manpage β go forth and weave some aspects, u absolute main character πππ
π¦ where this page comes from
- debian trixie: libaspect-perl 1.04-3 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu noble: libaspect-perl 1.04-3 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu resolute: libaspect-perl 1.04-3 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu stonking: libaspect-perl 1.04-3 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms