- 论坛徽章:
- 0
|
6.4. Choosing which AOP declaration style to use
Once you have decided that an aspect is the best approach for implementing a
given requirement, how do you decide between using Spring AOP or AspectJ,
and between the Aspect language (code) style, @AspectJ annotation style, and
the XML style? These decisions are influenced by a number of factors including
application requirements, development tools, and team familiarity with AOP.
6.4.1. Spring AOP or full AspectJ?
Use the simplest thing that can work. Spring AOP is simpler than using
full AspectJ as there is no requirement to introduce the AspectJ
compiler / weaver into your development and build processes.
If you only need to advise the execution of operations on Spring beans,
then Spring AOP is the right choice. If you need to advise domain objects,
or any other object not managed by the Spring container, then you will
need to use AspectJ. You will also need to use AspectJ if you wish to
advise join points other than simple method executions (for example, call
join points, field get or set join points, and so on).
When using AspectJ, you have the choice of the AspectJ language syntax
(also known as the "code style") or the @AspectJ annotation style.
If aspects play a large role in your design, and you are able to use the
AspectJ Development Tools (AJDT)
in Eclipse, then the AspectJ language syntax is the preferred option: it is cleaner
and simpler because the language was purposefully designed for writing aspects.
If you are not using Eclipse, or have only a few aspects that do not play a
major role in your application, then you may want to consider using the @AspectJ
style and sticking with a regular Java compilation in your IDE, and adding an aspect
weaving (linking) phase to your build scripts.
6.4.2. @AspectJ or XML for Spring AOP?
The XML style will be most familiar to existing Spring users. It can be
used with any JDK level (referring to named pointcuts from within
pointcut expressions does still require Java 5 though) and is backed
by genuine POJOs. When using AOP as a tool to configure enterprise
services (a good test is whether you consider the pointcut expression
to be a part of your configuration you might want to change independently)
then XML can be a good choice. With the XML style it is arguably
clearer from your configuration what aspects are present in the system.
The XML style has two disadvantages. Firstly it does not fully encapsulate
the implementation of the requirement it addresses in a single place.
The DRY principle says that there should be a single, unambiguous, authoritative
representation of any piece of knowledge within a system. When using the
XML style, the knowledge of how a requirement is implemented is split across
the declaration of the backing bean class, and the XML in the configuration
file. When using the @AspectJ style there is a single module - the aspect - in
which this information is encapsulated. Secondly, the XML style is more
limited in what in can express than the @AspectJ style: only the "singleton"
aspect instantiation model is supported, and it is not possible to combine
named pointcuts declared in XML. For example, in the @AspectJ style we
can write something like:
@Pointcut(execution(* get*()))
public void propertyAccess() {}
@Pointcut(execution(org.xyz.Account+ *(..))
public void operationReturningAnAccount() {}
@Pointcut(propertyAccess() && operationReturningAnAccount())
public void accountPropertyAccess() {}
In the XML style I certainly can declare the first two pointcuts:
The downside of the XML approach becomes evident in this case because I
cannot define the 'accountPropertyAccess' pointcut by
combining these definitions.
The @AspectJ style supports additional instantiation models, and richer
pointcut composition. It has the advantage of keeping the aspect as a
modular unit. It also has the advantage the @AspectJ aspects can be
understood both by Spring AOP and by AspectJ - so if you later decide
you need the capabilities of AspectJ to implement additional requirements
then it is very easy to migrate to an AspectJ based approach.
So much for the pros and cons of each style then: which is best? If you are not using Java5
(or above) then clearly the XML-style is the best because it is the only option available to you.
If you are using Java5+, then you really will have to come to your own decision as to which style suits
you best. In the experience of the Spring team, we advocate the use of the @AspectJ style whenever
there are aspects that do more than simple "configuration" of enterprise services. If you are writing,
have written, or have access to an aspect that is not part of the business contract of a particular
class (such as a tracing aspect), then the XML-style is better.
6.5. Mixing aspect types
It is perfectly possible to mix @AspectJ style aspects using the autoproxying
support, schema-defined aspects,
declared advisors and even proxies and
interceptors defined using the Spring 1.2 style in the same configuration. All of
these are implemented using the same underlying support mechanism and will co-exist
without any difficulty.
6.6. Proxying mechanisms
Spring AOP uses either JDK dynamic proxies or CGLIB to create the proxy for a given
target object. (JDK dynamic proxies are preferred whenever you have a choice).
If the target object to be proxied implements at least one interface then a JDK
dynamic proxy will be used. All of the interfaces implemented by the target type will be
proxied. If the target object does not implement any interfaces then a CGLIB proxy
will be created.
If you want to force the use of CGLIB proxying (for example, to proxy every
method defined for the target object, not just those implemented by its interfaces)
you can do so. However, there are some issues to consider:
final methods cannot be advised, as they cannot
be overriden.
You will need the CGLIB 2 binaries on your classpath, whereas
dynamic proxies are available with the JDK. Spring will automatically
warn you when it needs CGLIB but it isn't available on the classpath.
The
constructor of your proxied object will be called twice. This is a
natural consequence of the CGLIB proxy model whereby a subclass is
generated for each proxied object. For each proxied instance, two
objects are created: the actual proxied object and an instance of the
subclass that implements the advice. This behavior does not show when
using JDK proxies. Usually, calling the constructor of the proxied type
twice, is not a huge problem, as there are usually only assignments
taking place and no real logic is (and probably should be) implemented
in the constructor.
To force the use of CGLIB proxies set the value of the proxy-target-class
attribute of the element to true:
proxy-target-class="true">
To force CGLIB proxying when using the @AspectJ autoproxy support, set the
'proxy-target-class' attribute of the
true:
element to
proxy-target-class="true"/>6.6.1. Understanding AOP proxies
Spring AOP is proxy-based. It is vitally important that you grasp
the semantics of what that last statement actually means before you write your own aspects or
use any of the Spring AOP-based aspects supplied with the Spring Framework.
Consider first the scenario where you have a plain-vanilla, un-proxied,
nothing-special-about-it, straight object reference, as illustrated by the following code snippet.
public class SimplePojo implements Pojo {
public void foo() {
// this is a direct method call on the 'this' reference
this.bar();
}
public void bar() {
// some logic...
}
}
If you invoke a method on an object reference, the method is invoked
directly on that object reference, as can be seen below.
![]()
public class Main {
public static void main(String[] args) {
Pojo pojo = new SimplePojo();
// this is a direct method call on the 'pojo' reference
pojo.foo();
}
}
Things change slightly when the reference that client code has is a proxy. Consider the
following diagram and code snippet.
![]()
public class Main {
public static void main(String[] args) {
ProxyFactory factory = new ProxyFactory(new SimplePojo());
factory.addInterface(Pojo.class);
factory.addAdvice(new RetryAdvice());
Pojo pojo = (Pojo) factory.getProxy();
// this is a method call on the proxy!
pojo.foo();
}
}
The key thing to understand here is that the client code inside the
main(..) of the Main class
has a reference to the proxy. This means that method calls on that object
reference will be calls on the proxy, and as such the proxy will be able to delegate to
all of the interceptors (advice) that are relevant to that particular method call.
However, once the call has finally reached the target object, the SimplePojothis.bar() or this.foo(), are going to be
invoked against the thisnot
the proxy. This has important implications. It means that self-invocation is not
going to result in the advice associated with a method invocation getting a chance to execute.
reference in this case, any method calls that it may make on itself, such as
reference, and
Okay, so what is to be done about this? The best approach (the term best is used loosely here)
is to refactor your code such that the self-invocation does not happen. For sure, this does entail
some work on your part, but it is the best, least-invasive approach. The next approach is
absolutely horrendous, and I am almost reticent to point it out precisely because it is so
horrendous. You can (choke!) totally tie the logic within your class to Spring AOP by doing
this:
public class SimplePojo implements Pojo {
public void foo() {
// this works, but... gah!
((Pojo) AopContext.currentProxy()).bar();
}
public void bar() {
// some logic...
}
}
This totally couples your code to Spring AOP, and it makes the class
itself aware of the fact that it is being used in an AOP context, which flies in the face of AOP.
It also requires some additional configuration when the proxy is being created:
public class Main {
public static void main(String[] args) {
ProxyFactory factory = new ProxyFactory(new SimplePojo());
factory.adddInterface(Pojo.class);
factory.addAdvice(new RetryAdvice());
factory.setExposeProxy(true);
Pojo pojo = (Pojo) factory.getProxy();
// this is a method call on the proxy!
pojo.foo();
}
}
Finally, it must be noted that AspectJ does not have this self-invocation issue because it is
not a proxy-based AOP framework.
6.7. Programmatic creation of @AspectJ Proxies
In addition to declaring aspects in your configuration using either
or ,
it is also possible programmatically to create proxies that advise target objects. For the
full details of Spring's AOP API, see the next chapter. Here we want to focus on the ability
to automatically create proxies using @AspectJ aspects.
The class org.springframework.aop.aspectj.annotation.AspectJProxyFactory
can be used to create a proxy for a target object that is advised by one or more
@AspectJ aspects. Basic usage for this class is very simple, as illustrated below. See
the Javadocs for full information.
// create a factory that can generate a proxy for the given target object
AspectJProxyFactory factory = new AspectJProxyFactory(targetObject);
// add an aspect, the class must be an @AspectJ aspect
// you can call this as many times as you need with different aspects
factory.addAspect(SecurityManager.class);
// you can also add existing aspect instances, the type of the object supplied must be an @AspectJ aspect
factory.addAspect(usageTracker);
// now get the proxy object...
MyInterfaceType proxy = factory.getProxy();6.8. Using AspectJ with Spring applications
Everything we've covered so far in this chapter is pure Spring AOP. In this section,
we're going to look at how you can use the AspectJ compiler/weaver instead of or in
addition to Spring AOP if your needs go beyond the facilities offered by Spring AOP
alone.
Spring ships with a small AspectJ aspect library (it's available standalone in
your distribution as spring-aspects.jar, you'll need to add
this to your classpath to use the aspects in it).
Section 6.8.1, “Using AspectJ to dependency inject domain objects with Spring”
and
Section 6.8.2, “Other Spring aspects for AspectJ”
discuss the content of this library and how you
can use it.
Section 6.8.3, “Configuring AspectJ aspects using Spring IoC”
discusses how to dependency inject AspectJ
aspects that are woven using the AspectJ compiler. Finally,
Section 6.8.4, “Using AspectJ Load-time weaving (LTW) with Spring applications”
provides an introduction to load-time weaving for Spring applications
using AspectJ.
6.8.1. Using AspectJ to dependency inject domain objects with Spring
The Spring 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
containing the configuration to be applied. The spring-aspects.jaroutside of the control of any container. Domain objects
often fall into this category: they may be created programmatically using the
new operator, or by an ORM tool as a result of a database query.
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
The @Configurable annotation marks a class as eligible for
Spring-driven configuration. In the simplest case it can be used just as a
marker annotation:
package com.xyz.myapp.domain;
import org.springframework.beans.factory.annotation.Configurable;
@Configurable
public class Account {
...
}
When used as a marker interface in this way, Spring will configure
new instances of the annotated type (Account in this case) using a
prototypical bean definition with the same name as the fully-qualified
type name (com.xyz.myapp.domain.Account). Since the
default name for a bean is the fully-qualified name of its type, a
convenient way to declare the prototype definition is simply to omit the
id attribute:
...
If you want to explicitly specify the name of the prototype bean
definition to use, you can do so directly in the annotation:
package com.xyz.myapp.domain;
import org.springframework.beans.factory.annotation.Configurable;
@Configurable("account")
public class Account {
...
}
Spring will now look for a bean definition named "account" and
use that as a prototypical definition to configure new Account
instances.
You can also use autowiring to avoid having to specify a prototypical
bean definition at all. To have Spring apply autowiring use the
autowire property of the @Configurable annotation: specify either
@Configurable(autowire=Autowire.BY_TYPE) or
@Configurable(autowire=Autowire.BY_NAME for autowiring
by type or by name respectively.
Finally you can enable Spring 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, then Spring will validate after configuration that all properties
(that are not primitives or collections) have been set.
Using the annotation on its own does nothing of course. It's the
AnnotationBeanConfigurerAspect in spring-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 with the
@Configurable annotation, configure the newly created object using Spring
in accordance with the properties of the annotation". 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
Section 6.8.4, “Using AspectJ Load-time weaving (LTW) with Spring applications”
).
The AnnotationBeanConfigurerAspect itself needs
configuring by Spring (in order to obtain a reference to the bean factory that
is to be used to configure new objects). The Spring AOP namespace defines a
convenient tag for doing this. Simply include the following in your
application context configuration:
If you are using the DTD instead of schema, the equivalent definition is:
Instances of @Configurable objects created before
the aspect has been configured will result in a warning being issued to the
log and no configuration of the object taking place. An example might be
a bean in the Spring configuration that creates domain objects when it is
initialized by Spring. In this case you can use the "depends-on" bean
attribute to manually specify that the bean depends on the
configuration aspect.
...
6.8.1.1. 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
then the annotation has no affect during unit testing, and you can simply
set mock or stub property references in the object under test and proceed
as normal. If @Configurable types have been woven by
AspectJ then you can still unit test outside of the container as normal,
but you will see a warning message each time that you construct an
@Configurable object indicating that it has not been configured by Spring.
6.8.1.2. Working with multiple application contexts
The AnnotationBeanConfigurerAspect used to implement
the @Configurable support is an AspectJ singleton aspect. The scope of a
singleton aspect is the same as the scope of static members, that is to say there is
one aspect instance per classloader that defines the type. This means that if
you define multiple application contexts within the same classloader hierarchy
you need to consider where to define the bean and where to place
spring-aspects.jaron the classpath.
Consider a typical Spring web-app configuration with a shared parent application
context defining common business services and everything needed to support
them, and one child application context per servlet containing definitions
particular to that servlet. All of these contexts will co-exist within the
same classloader hierarchy, and so the AnnotationBeanConfigurerAspect
can only hold a reference to one of them. In this case we recommend
defining the bean in the
shared (parent) application context: this defines the services that you are
likely to want to inject into domain objects. A consequence is that you cannot
configure domain objects with references to beans defined in the child
(servlet-specific) contexts using the @Configurable mechanism
(probably not something you want to do anyway!).
When deploying multiple web-apps within the same container, ensure that each
web-application loads the types in spring-aspects.jar using its own classloader
(for example, by placing spring-aspects.jar
in 'WEB-INF/lib'). If spring-aspects.jar
is only added to the container wide classpath (and hence loaded by the shared
parent classloader), all web applications will share the same aspect instance
which is probably not what you want.
6.8.2. Other Spring aspects for AspectJ
In addition to the @Configurable support,
spring-aspects.jar contains an
AspectJ aspect that can be used to drive Spring's transaction management for
types and methods annotated with the @Transactional annotation.
This is primarily intended for users who want to use Spring's transaction support outside of the Spring container.
The aspect that interprets @Transactional annotations is the
AnnotationTransactionAspect. When using this
aspect, you must annotate the implementation class
(and/or methods within that class), notnot inherited. the interface
(if any) that the class implements. AspectJ follows Java's rule that annotations on
interfaces are
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 with public, protected, and default visibility may all be annotated.
Annotating protected and default visibility methods directly is the only way
to get transaction demarcation for the execution of such operations.
For AspectJ programmers that want to use the Spring configuration and
transaction management support but don't want to (or can't) use annotations,
spring-aspects.jar also contains abstract aspects you can extend to provide
your own pointcut definitions. See the Javadocs for
AbstractBeanConfigurerAspect and
AbstractTransactionAspect 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
using prototypical 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(..)) &&
SystemArchitecture.inDomainModel() &&
this(beanInstance);
}6.8.3. Configuring AspectJ aspects using Spring IoC
When using AspectJ aspects with Spring applications, it's natural to want
to configure such aspects using Spring. The AspectJ runtime itself is responsible
for aspect creation, and the means of configuring the AspectJ created aspects via
Spring depends on the AspectJ instantiation model (per-clause) used by the aspect.
The majority of AspectJ aspects are
singleton aspects. Configuration of these aspects is very
easy, simply create a bean definition referencing the aspect type as normal, and
include the bean attribute 'factory-method="aspectOf"'. This
ensures that Spring obtains the aspect instance by asking AspectJ for it rather
than trying to create an instance itself. For example:
For non-singleton aspects, the easiest way to configure them is to create
prototypical bean definitions and annotate use the @Configurable support from
spring-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 Spring AOP, and these aspects are all configured
using Spring, then you'll need to tell the Spring AOP @AspectJ autoproxying support
which subset of the @AspectJ aspects defined in the configuration should be used
for autoproxying. You can do this by using one or more
elements inside the declaration.
Each include element specifies a name pattern, and only beans with names matched
by at least one of the patterns will be used for Spring AOP autoproxy configuration:
6.8.4. Using AspectJ Load-time weaving (LTW) with Spring applications
Load-time weaving (or LTW) refers to the process of weaving AspectJ aspects
with an application's class files as they are loaded into the VM. For full details
on configuring load-time weaving with AspectJ, see the
LTW section of the AspectJ Development Environment Guide
. We will focus here on the essentials of configuring load-time weaving
for Spring applications running on Java 5.
Load-time weaving is controlled by defining a file 'aop.xml' in
the META-INF directory. AspectJ automatically looks for all 'META-INF/aop.xml' files
visible on the classpath and configures itself based on the aggregation of their
content.
A basic META-INF/aop.xml for your application should look like this:
The element tells AspectJ which set of types
should be included in the weaving process. Use the package prefix for your
application followed by "..*" (meaning '... and any type defined in a subpackage
of this') as a good default. Using the include element is important as otherwise
AspectJ will look at every type loaded in support of your application (including
all the Spring library classes and many more besides). Normally you don't want
to weave these types and don't want to pay the overhead of AspectJ attempting
to match against them.
To get informational messages in your log file regarding the activity of the
load-time weaver, add the following options to the weaver element:
Finally, to control exactly which aspects are used, you can use the
aspects element. By default all defined aspects are used for
weaving (spring-aspects.jar contains a META-INF/aop.xml file that defines the
configuration and transaction aspects). If you were using spring-aspects.jar, but
only want the configuration support and not the transaction support you could
specify this as follows:
On the Java 5 platform, load-time weaving is enabled by specifying the following
VM argument when launching the Java virtual machine:
-javaagent:/aspectjweaver.jar6.9. Further Resources
More information on AspectJ can be found at the
AspectJ home page.
The book Eclipse AspectJ by Adrian Colyer et. al. (Addison-Wesley,
2005) provides a comprehensive introduction and reference for the AspectJ language.
The excellent AspectJ in Action by Ramnivas Laddad (Manning, 2003)
comes highly recommended as an introduction to AOP; the focus of the book is on
AspectJ, but a lot of general AOP themes are explored in some depth.
本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u/24141/showart_324462.html |
|