Using AspectJ with Infra Applications
Everything we have covered so far in this chapter is pure Infra AOP. In this section, we look at how you can use the AspectJ compiler or weaver instead of or in addition to Infra AOP if your needs go beyond the facilities offered by Infra AOP alone.
Infra ships with a small AspectJ aspect library, which is available stand-alone in your
distribution as today-aspects.jar
. You need to add this to your classpath in order
to use the aspects in it. Using AspectJ to Dependency Inject Domain Objects with Infra and Other Infra aspects for AspectJ discuss the
content of this library and how you can use it. Configuring AspectJ Aspects by Using Infra IoC discusses how to
dependency inject AspectJ aspects that are woven using the AspectJ compiler. Finally,
Load-time Weaving with AspectJ in the TODAY Framework provides an introduction to load-time weaving for Infra applications
that use AspectJ.
Using AspectJ to Dependency Inject Domain Objects with Infra
The Infra container instantiates and configures beans defined in your application
context. It is also possible to ask a bean factory to configure a pre-existing
object, given the name of a bean definition that contains the configuration to be applied.
today-aspects.jar
contains an annotation-driven aspect that exploits this
capability to allow dependency injection of any object. The support is intended to
be used for objects created outside of the control of any container. Domain objects
often fall into this category because they are often created programmatically with the
new
operator or by an ORM tool as a result of a database query.
The @Configurable
annotation marks a class as being eligible for Infra-driven
configuration. In the simplest case, you can use purely it as a marker annotation, as the
following example shows:
-
Java
package com.xyz.domain;
import cn.taketoday.beans.factory.annotation.Configurable;
@Configurable
public class Account {
// ...
}
When used as a marker interface in this way, Infra configures new instances of the
annotated type (Account
, in this case) by using a bean definition (typically
prototype-scoped) with the same name as the fully-qualified type name
(com.xyz.domain.Account
). Since the default name for a bean defined via XML is the
fully-qualified name of its type, a convenient way to declare the prototype definition
is to omit the id
attribute, as the following example shows:
<bean class="com.xyz.domain.Account" scope="prototype">
<property name="fundsTransferService" ref="fundsTransferService"/>
</bean>
If you want to explicitly specify the name of the prototype bean definition to use, you can do so directly in the annotation, as the following example shows:
-
Java
package com.xyz.domain;
import cn.taketoday.beans.factory.annotation.Configurable;
@Configurable("account")
public class Account {
// ...
}
Infra now looks for a bean definition named account
and uses that as the
definition to configure new Account
instances.
You can also use autowiring to avoid having to specify a dedicated bean definition at
all. To have Infra apply autowiring, use the autowire
property of the @Configurable
annotation. You can specify either @Configurable(autowire=Autowire.BY_TYPE)
or
@Configurable(autowire=Autowire.BY_NAME)
for autowiring by type or by name,
respectively. As an alternative, it is preferable to specify explicit, annotation-driven
dependency injection for your @Configurable
beans through @Autowired
or @Inject
at the field or method level (see Annotation-based Container Configuration for further details).
Finally, you can enable Infra dependency checking for the object references in the newly
created and configured object by using the dependencyCheck
attribute (for example,
@Configurable(autowire=Autowire.BY_NAME,dependencyCheck=true)
). If this attribute is
set to true
, Infra validates after configuration that all properties (which
are not primitives or collections) have been set.
Note that using the annotation on its own does nothing. It is the
AnnotationBeanConfigurerAspect
in today-aspects.jar
that acts on the presence of
the annotation. In essence, the aspect says, "after returning from the initialization of
a new object of a type annotated with @Configurable
, configure the newly created object
using Infra in accordance with the properties of the annotation". In this context,
"initialization" refers to newly instantiated objects (for example, objects instantiated
with the new
operator) as well as to Serializable
objects that are undergoing
deserialization (for example, through
readResolve()).
One of the key phrases in the above paragraph is "in essence". For most cases, the
exact semantics of "after returning from the initialization of a new object" are
fine. In this context, "after initialization" means that the dependencies are
injected after the object has been constructed. This means that the dependencies
are not available for use in the constructor bodies of the class. If you want the
dependencies to be injected before the constructor bodies run and thus be
available for use in the body of the constructors, you need to define this on the
You can find more information about the language semantics of the various pointcut types in AspectJ in this appendix of the AspectJ Programming Guide. |
For this to work, the annotated types must be woven with the AspectJ weaver. You can
either use a build-time Ant or Maven task to do this (see, for example, the
AspectJ Development
Environment Guide) or load-time weaving (see Load-time Weaving with AspectJ in the TODAY Framework). The
AnnotationBeanConfigurerAspect
itself needs to be configured by Infra (in order to obtain
a reference to the bean factory that is to be used to configure new objects). You can define
the related configuration as follows:
include-code::./ApplicationConfiguration[tag=snippet,indent=0]
Instances of @Configurable
objects created before the aspect has been configured
result in a message being issued to the debug log and no configuration of the
object taking place. An example might be a bean in the Infra configuration that creates
domain objects when it is initialized by Infra. In this case, you can use the
depends-on
bean attribute to manually specify that the bean depends on the
configuration aspect. The following example shows how to use the depends-on
attribute:
<bean id="myService"
class="com.xyz.service.MyService"
depends-on="cn.taketoday.beans.factory.aspectj.AnnotationBeanConfigurerAspect">
<!-- ... -->
</bean>
Do not activate @Configurable processing through the bean configurer aspect unless you
really mean to rely on its semantics at runtime. In particular, make sure that you do
not use @Configurable on bean classes that are registered as regular Infra beans
with the container. Doing so results in double initialization, once through the
container and once through the aspect.
|
Unit Testing @Configurable
Objects
One of the goals of the @Configurable
support is to enable independent unit testing
of domain objects without the difficulties associated with hard-coded lookups.
If @Configurable
types have not been woven by AspectJ, the annotation has no affect
during unit testing. You can set mock or stub property references in the object under
test and proceed as normal. If @Configurable
types have been woven by AspectJ,
you can still unit test outside of the container as normal, but you see a warning
message each time that you construct a @Configurable
object indicating that it has
not been configured by Infra.
Other Infra aspects for AspectJ
In addition to the @Configurable
aspect, today-aspects.jar
contains an AspectJ
aspect that you can use to drive Infra transaction management for types and methods
annotated with the @Transactional
annotation. This is primarily intended for users who
want to use the TODAY Framework’s transaction support outside of the Infra container.
The aspect that interprets @Transactional
annotations is the
AnnotationTransactionAspect
. When you use this aspect, you must annotate the
implementation class (or methods within that class or both), not the interface (if
any) that the class implements. AspectJ follows Java’s rule that annotations on
interfaces are not inherited.
A @Transactional
annotation on a class specifies the default transaction semantics for
the execution of any public operation in the class.
A @Transactional
annotation on a method within the class overrides the default
transaction semantics given by the class annotation (if present). Methods of any
visibility may be annotated, including private methods. Annotating non-public methods
directly is the only way to get transaction demarcation for the execution of such methods.
today-aspects provides a similar aspect that offers the
exact same features for the standard jakarta.transaction.Transactional annotation. Check
JtaAnnotationTransactionAspect for more details.
|
For AspectJ programmers who want to use the Infra configuration and transaction
management support but do not want to (or cannot) use annotations, today-aspects.jar
also contains abstract
aspects you can extend to provide your own pointcut
definitions. See the sources for the AbstractBeanConfigurerAspect
and
AbstractTransactionAspect
aspects for more information. As an example, the following
excerpt shows how you could write an aspect to configure all instances of objects
defined in the domain model by using prototype bean definitions that match the
fully qualified class names:
public aspect DomainObjectConfiguration extends AbstractBeanConfigurerAspect {
public DomainObjectConfiguration() {
setBeanWiringInfoResolver(new ClassNameBeanWiringInfoResolver());
}
// the creation of a new bean (any object in the domain model)
protected pointcut beanCreation(Object beanInstance) :
initialization(new(..)) &&
CommonPointcuts.inDomainModel() &&
this(beanInstance);
}
Configuring AspectJ Aspects by Using Infra IoC
When you use AspectJ aspects with Infra applications, it is natural to both want and
expect to be able to configure such aspects with Infra. The AspectJ runtime itself is
responsible for aspect creation, and the means of configuring the AspectJ-created
aspects through Infra depends on the AspectJ instantiation model (the per-xxx
clause)
used by the aspect.
The majority of AspectJ aspects are singleton aspects. Configuration of these
aspects is easy. You can create a bean definition that references the aspect type as
normal and include the factory-method="aspectOf"
bean attribute. This ensures that
Infra obtains the aspect instance by asking AspectJ for it rather than trying to create
an instance itself. The following example shows how to use the factory-method="aspectOf"
attribute:
<bean id="profiler" class="com.xyz.profiler.Profiler"
factory-method="aspectOf"> (1)
<property name="profilingStrategy" ref="jamonProfilingStrategy"/>
</bean>
1 | Note the factory-method="aspectOf" attribute |
Non-singleton aspects are harder to configure. However, it is possible to do so by
creating prototype bean definitions and using the @Configurable
support from
today-aspects.jar
to configure the aspect instances once they have bean created by
the AspectJ runtime.
If you have some @AspectJ aspects that you want to weave with AspectJ (for example,
using load-time weaving for domain model types) and other @AspectJ aspects that you want
to use with Infra AOP, and these aspects are all configured in Infra, you
need to tell the Infra AOP @AspectJ auto-proxying support which exact subset of the
@AspectJ aspects defined in the configuration should be used for auto-proxying. You can
do this by using one or more <include/>
elements inside the <aop:aspectj-autoproxy/>
declaration. Each <include/>
element specifies a name pattern, and only beans with
names matched by at least one of the patterns are used for Infra AOP auto-proxy
configuration. The following example shows how to use <include/>
elements:
<aop:aspectj-autoproxy>
<aop:include name="thisBean"/>
<aop:include name="thatBean"/>
</aop:aspectj-autoproxy>
Do not be misled by the name of the <aop:aspectj-autoproxy/> element. Using it
results in the creation of Infra AOP proxies. The @AspectJ style of aspect
declaration is being used here, but the AspectJ runtime is not involved.
|
Load-time Weaving with AspectJ in the TODAY Framework
Load-time weaving (LTW) refers to the process of weaving AspectJ aspects into an application’s class files as they are being loaded into the Java virtual machine (JVM). The focus of this section is on configuring and using LTW in the specific context of the TODAY Framework. This section is not a general introduction to LTW. For full details on the specifics of LTW and configuring LTW with only AspectJ (with Infra not being involved at all), see the LTW section of the AspectJ Development Environment Guide.
The value that the TODAY Framework brings to AspectJ LTW is in enabling much
finer-grained control over the weaving process. 'Vanilla' AspectJ LTW is effected by using
a Java (5+) agent, which is switched on by specifying a VM argument when starting up a
JVM. It is, thus, a JVM-wide setting, which may be fine in some situations but is often a
little too coarse. Infra-enabled LTW lets you switch on LTW on a
per-ClassLoader
basis, which is more fine-grained and which can make more
sense in a 'single-JVM-multiple-application' environment (such as is found in a typical
application server environment).
Further, in certain environments, this support enables
load-time weaving without making any modifications to the application server’s launch
script that is needed to add -javaagent:path/to/aspectjweaver.jar
or (as we describe
later in this section) -javaagent:path/to/today-instrument.jar
. Developers configure
the application context to enable load-time weaving instead of relying on administrators
who typically are in charge of the deployment configuration, such as the launch script.
Now that the sales pitch is over, let us first walk through a quick example of AspectJ LTW that uses Infra, followed by detailed specifics about elements introduced in the example. For a complete example, see the Petclinic sample application.
A First Example
Assume that you are an application developer who has been tasked with diagnosing the cause of some performance problems in a system. Rather than break out a profiling tool, we are going to switch on a simple profiling aspect that lets us quickly get some performance metrics. We can then apply a finer-grained profiling tool to that specific area immediately afterwards.
The example presented here uses XML configuration. You can also configure and
use @AspectJ with Java configuration. Specifically, you can use the
@EnableLoadTimeWeaving annotation as an alternative to <context:load-time-weaver/>
(see below for details).
|
The following example shows the profiling aspect, which is not fancy. It is a time-based profiler that uses the @AspectJ-style of aspect declaration:
-
Java
package com.xyz;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Pointcut;
import cn.taketoday.util.StopWatch;
import cn.taketoday.core.annotation.Order;
@Aspect
public class ProfilingAspect {
@Around("methodsToBeProfiled()")
public Object profile(ProceedingJoinPoint pjp) throws Throwable {
StopWatch sw = new StopWatch(getClass().getSimpleName());
try {
sw.start(pjp.getSignature().getName());
return pjp.proceed();
} finally {
sw.stop();
System.out.println(sw.prettyPrint());
}
}
@Pointcut("execution(public * com.xyz..*.*(..))")
public void methodsToBeProfiled(){}
}
We also need to create an META-INF/aop.xml
file, to inform the AspectJ weaver that
we want to weave our ProfilingAspect
into our classes. This file convention, namely
the presence of a file (or files) on the Java classpath called META-INF/aop.xml
is
standard AspectJ. The following example shows the aop.xml
file:
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "https://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver>
<!-- only weave classes in our application-specific packages and sub-packages -->
<include within="com.xyz..*"/>
</weaver>
<aspects>
<!-- weave in just this aspect -->
<aspect name="com.xyz.ProfilingAspect"/>
</aspects>
</aspectj>
It is recommended to only weave specific classes (typically those in the
application packages, as shown in the aop.xml example above) in order
to avoid side effects such as AspectJ dump files and warnings.
This is also a best practice from an efficiency perspective.
|
Now we can move on to the Infra-specific portion of the configuration. We need
to configure a LoadTimeWeaver
(explained later). This load-time weaver is the
essential component responsible for weaving the aspect configuration in one or
more META-INF/aop.xml
files into the classes in your application. The good
thing is that it does not require a lot of configuration (there are some more
options that you can specify, but these are detailed later), as can be seen in
the following example:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- a service object; we will be profiling its methods -->
<bean id="entitlementCalculationService"
class="com.xyz.StubEntitlementCalculationService"/>
<!-- this switches on the load-time weaving -->
<context:load-time-weaver/>
</beans>
Now that all the required artifacts (the aspect, the META-INF/aop.xml
file, and the Infra configuration) are in place, we can create the following
driver class with a main(..)
method to demonstrate the LTW in action:
-
Java
package com.xyz;
// imports
public class Main {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
EntitlementCalculationService service =
ctx.getBean(EntitlementCalculationService.class);
// the profiling aspect is 'woven' around this method execution
service.calculateEntitlement();
}
}
We have one last thing to do. The introduction to this section did say that one could
switch on LTW selectively on a per-ClassLoader
basis with Infra, and this is true.
However, for this example, we use a Java agent (supplied with Infra) to switch on LTW.
We use the following command to run the Main
class shown earlier:
java -javaagent:C:/projects/xyz/lib/today-instrument.jar com.xyz.Main
The -javaagent
is a flag for specifying and enabling
agents
to instrument programs that run on the JVM. The TODAY Framework ships with such an
agent, the InstrumentationSavingAgent
, which is packaged in the
today-instrument.jar
that was supplied as the value of the -javaagent
argument in
the preceding example.
The output from the execution of the Main
program looks something like the next example.
(I have introduced a Thread.sleep(..)
statement into the calculateEntitlement()
implementation so that the profiler actually captures something other than 0
milliseconds (the 01234
milliseconds is not an overhead introduced by the AOP).
The following listing shows the output we got when we ran our profiler:
Calculating entitlement StopWatch 'ProfilingAspect': running time (millis) = 1234 ------ ----- ---------------------------- ms % Task name ------ ----- ---------------------------- 01234 100% calculateEntitlement
Since this LTW is effected by using full-blown AspectJ, we are not limited only to advising
Infra beans. The following slight variation on the Main
program yields the same
result:
-
Java
package com.xyz;
// imports
public class Main {
public static void main(String[] args) {
new ClassPathXmlApplicationContext("beans.xml");
EntitlementCalculationService service =
new StubEntitlementCalculationService();
// the profiling aspect will be 'woven' around this method execution
service.calculateEntitlement();
}
}
Notice how, in the preceding program, we bootstrap the Infra container and
then create a new instance of the StubEntitlementCalculationService
totally outside
the context of Infra. The profiling advice still gets woven in.
Admittedly, the example is simplistic. However, the basics of the LTW support in Infra have all been introduced in the earlier example, and the rest of this section explains the "why" behind each bit of configuration and usage in detail.
The ProfilingAspect used in this example may be basic, but it is quite useful. It is a
nice example of a development-time aspect that developers can use during development
and then easily exclude from builds of the application being deployed
into UAT or production.
|
Aspects
The aspects that you use in LTW have to be AspectJ aspects. You can write them in either the AspectJ language itself, or you can write your aspects in the @AspectJ-style. Your aspects are then both valid AspectJ and Infra AOP aspects. Furthermore, the compiled aspect classes need to be available on the classpath.
META-INF/aop.xml
The AspectJ LTW infrastructure is configured by using one or more META-INF/aop.xml
files that are on the Java classpath (either directly or, more typically, in jar files).
For example:
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "https://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver>
<!-- only weave classes in our application-specific packages and sub-packages -->
<include within="com.xyz..*"/>
</weaver>
</aspectj>
It is recommended to only weave specific classes (typically those in the
application packages, as shown in the aop.xml example above) in order
to avoid side effects such as AspectJ dump files and warnings.
This is also a best practice from an efficiency perspective.
|
The structure and contents of this file is detailed in the LTW part of the
AspectJ reference
documentation. Because the aop.xml
file is 100% AspectJ, we do not describe it further here.
Required libraries (JARS)
At minimum, you need the following libraries to use the TODAY Framework’s support for AspectJ LTW:
-
today-aop.jar
-
aspectjweaver.jar
If you use the Infra-provided agent to enable instrumentation , you also need:
-
today-instrument.jar
Infra Configuration
The key component in Infra’s LTW support is the LoadTimeWeaver
interface (in the
cn.taketoday.instrument.classloading
package), and the numerous implementations
of it that ship with the Infra distribution. A LoadTimeWeaver
is responsible for
adding one or more java.lang.instrument.ClassFileTransformers
to a ClassLoader
at
runtime, which opens the door to all manner of interesting applications, one of which
happens to be the LTW of aspects.
If you are unfamiliar with the idea of runtime class file transformation, see the
javadoc API documentation for the java.lang.instrument package before continuing.
While that documentation is not comprehensive, at least you can see the key interfaces
and classes (for reference as you read through this section).
|
Configuring a LoadTimeWeaver
for a particular ApplicationContext
can be as easy as
adding one line. (Note that you almost certainly need to use an
ApplicationContext
as your Infra container — typically, a BeanFactory
is not
enough because the LTW support uses BeanFactoryPostProcessors
.)
To enable the TODAY Framework’s LTW support, you need to configure a LoadTimeWeaver
as follows:
@Configuration
@EnableLoadTimeWeaving
public class ApplicationConfiguration {
}
The preceding configuration automatically defines and registers a number of LTW-specific
infrastructure beans, such as a LoadTimeWeaver
and an AspectJWeavingEnabler
, for you.
The default LoadTimeWeaver
is the DefaultContextLoadTimeWeaver
class, which attempts
to decorate an automatically detected LoadTimeWeaver
. The exact type of LoadTimeWeaver
that is "automatically detected" is dependent upon your runtime environment.
The following table summarizes various LoadTimeWeaver
implementations:
Runtime Environment | LoadTimeWeaver implementation |
---|---|
Running in Apache Tomcat |
|
Running in GlassFish (limited to EAR deployments) |
|
|
|
JVM started with Infra |
|
Fallback, expecting the underlying ClassLoader to follow common conventions
(namely |
|
Note that the table lists only the LoadTimeWeavers
that are autodetected when you
use the DefaultContextLoadTimeWeaver
. You can specify exactly which LoadTimeWeaver
implementation to use.
To configure a specific LoadTimeWeaver
, implement the
LoadTimeWeavingConfigurer
interface and override the getLoadTimeWeaver()
method
(or use the XML equivalent).
The following example specifies a ReflectiveLoadTimeWeaver
:
@Configuration
@EnableLoadTimeWeaving
public class CustomWeaverConfiguration implements LoadTimeWeavingConfigurer {
@Override
public LoadTimeWeaver getLoadTimeWeaver() {
return new ReflectiveLoadTimeWeaver();
}
}
The LoadTimeWeaver
that is defined and registered by the configuration can be later
retrieved from the Infra container by using the well known name, loadTimeWeaver
.
Remember that the LoadTimeWeaver
exists only as a mechanism for Infra LTW
infrastructure to add one or more ClassFileTransformers
. The actual
ClassFileTransformer
that does the LTW is the ClassPreProcessorAgentAdapter
(from
the org.aspectj.weaver.loadtime
package) class. See the class-level javadoc of the
ClassPreProcessorAgentAdapter
class for further details, because the specifics of how
the weaving is actually effected is beyond the scope of this document.
There is one final attribute of the configuration left to discuss: the aspectjWeaving
attribute (or aspectj-weaving
if you use XML). This attribute controls whether LTW
is enabled or not. It accepts one of three possible values, with the default value being
autodetect
if the attribute is not present. The following table summarizes the three
possible values:
Annotation Value | XML Value | Explanation |
---|---|---|
|
|
AspectJ weaving is on, and aspects are woven at load-time as appropriate. |
|
|
LTW is off. No aspect is woven at load-time. |
|
|
If the Infra LTW infrastructure can find at least one |
Environment-specific Configuration
This last section contains any additional settings and configuration that you need when you use Infra LTW support in environments such as application servers and web containers.
Tomcat, JBoss, WildFly
Tomcat and JBoss/WildFly provide a general app ClassLoader
that is capable of local
instrumentation. Infra native LTW may leverage those ClassLoader implementations
to provide AspectJ weaving.
You can simply enable load-time weaving, as described earlier.
Specifically, you do not need to modify the JVM launch script to add
-javaagent:path/to/today-instrument.jar
.
Note that on JBoss, you may need to disable the app server scanning to prevent it from
loading the classes before the application actually starts. A quick workaround is to add
to your artifact a file named WEB-INF/jboss-scanning.xml
with the following content:
<scanning xmlns="urn:jboss:scanning:1.0"/>
Generic Java Applications
When class instrumentation is required in environments that are not supported by
specific LoadTimeWeaver
implementations, a JVM agent is the general solution.
For such cases, Infra provides InstrumentationLoadTimeWeaver
which requires a
Infra-specific (but very general) JVM agent, today-instrument.jar
, autodetected
by common @EnableLoadTimeWeaving
and <context:load-time-weaver/>
setups.
To use it, you must start the virtual machine with the Infra agent by supplying the following JVM options:
-javaagent:/path/to/today-instrument.jar
Note that this requires modification of the JVM launch script, which may prevent you from using this in application server environments (depending on your server and your operation policies). That said, for one-app-per-JVM deployments such as standalone Infra applications, you typically control the entire JVM setup in any case.