I am trying to capture a method signature change and throw an error when it happens.
But the declare error is not working as expected
@DeclareError("call(* a.b.C.method(..)) && !call(* a.b.C.method(int))")
public static final String errorMsg= "Signature error";
This is always matching the call to this method.
But if I move this pointcut to @Before, then it will not match unless the method signature has changed.
Any idea on why the different behavior between @DeclareError & @Before concerning the pointcuts ?
Thanks
tkruse
9,9146 gold badges51 silver badges77 bronze badges
asked Jul 3, 2013 at 12:36
strange — it works in my environment. (Eclipse with AspectJ Plugin)
@Aspect
public class GetNameOverrider {
@DeclareError("call(* a.b.C.method(..)) && !call(* a.b.C.method(int))")
static final String errorMsg= "Signature error";
}
gives me an Error at compile time if I do:
a.b.C c = new a.b.C();
c.method(new Integer(2)); <--- no Error
c.method(2); <--- no Error
c.method("test"); <--- Error
=============================
ErrorDescription Resource Path Location Type
"Signature error" Main.java /Stackoverflow/src/test line 12 AspectJ Problem
answered Jul 3, 2013 at 16:59
FredFred
4163 silver badges5 bronze badges
3
-
public interface DeclareErrorOrWarning
AspectJ runtime representation of a declare error or declare warning member
in an aspect.
-
-
Method Summary
All Methods Instance Methods Abstract Methods
Modifier and Type Method and Description AjType
getDeclaringType()
The type that declared this declare warning or declare error member.
java.lang.String
getMessage()
The message associated with the declare warning / declare error
PointcutExpression
getPointcutExpression()
The pointcut expression associated with the warning or error
boolean
isError()
True if this is a declare error member, false if it is declare warning
-
-
-
Method Detail
-
getDeclaringType
AjType getDeclaringType()
The type that declared this declare warning or declare error member.
-
getPointcutExpression
PointcutExpression getPointcutExpression()
The pointcut expression associated with the warning or error
-
getMessage
java.lang.String getMessage()
The message associated with the declare warning / declare error
-
isError
boolean isError()
True if this is a declare error member, false if it is declare warning
-
-
4.1. Intro
In the last sections you saw how to
code aspects and how pointcut expressions are formed. This chapter puts it all together. There are two
forms of bindings for advices, mixins, and introductions. One is XML which will be the focus of this chapter.
The Annotated Bindings chapter discusses how you can replace XML with JDK 5.0 annotations.
4.2. Resolving XML
JBoss AOP resolves pointcut and advice bindings at runtime. So, bindings are a deployment time thing. How
does JBoss AOP find the XML files it needs at runtime? There are a couple of ways.
4.2.1. Standalone XML Resolving
When you are running JBoss AOP outside of the application server there are a few ways that the JBoss AOP
framework can resolve XML files.
- jboss.aop.path This is a system property that is a ‘;’ (Windows) or ‘:’ (Unix)
delimited list of XML files and/or directories. If the item in the list is a directory, JBoss AOP
will load any xml file in those directories with the filename suffix
-aop.xml - META-INF/jboss-aop.xml Any JAR file in your CLASSPATH that has a
jboss-aop.xml
file in the
META-INF/ will be loaded. JBoss AOP does a
ClassLoader.getResources(«META-INF/jboss-aop.xml») to obtain all these files.
4.2.2. Application Server XML Resolving
When you are running JBoss AOP outside of the application server there are a few ways that the JBoss AOP
framework can resolve XML files. One is to place an XML file with the suffix
*-aop.xml
in the deploy directory. The other way is to JAR up your classes and provide a
META-INF/jboss-aop.xml
file in this JAR. This JAR file must be suffixed with
.aop and placed within the deploy/
directory or embedded as a nested archive.
4.3. XML DTD
<?xml version='1.0' encoding='UTF-8' ?> <!ELEMENT aop (interceptor|introduction|metadata-loader|metadata| stack|aspect|pointcut|pluggable-pointcut|bind| prepare|cflow-stack|dynamic-cflow|annotation-introduction|typedef)+> <!ELEMENT interceptor ANY> <!ATTLIST interceptor name CDATA #IMPLIED> <!ATTLIST interceptor class CDATA #IMPLIED> <!ATTLIST interceptor factory CDATA #IMPLIED> <!ATTLIST interceptor scope (PER_VM|PER_CLASS|PER_INSTANCE|PER_JOINPOINT) "PER_VM"> <!ELEMENT aspect ANY> <!ATTLIST aspect name CDATA #IMPLIED> <!ATTLIST aspect class CDATA #IMPLIED> <!ATTLIST aspect factory CDATA #IMPLIED> <!ATTLIST aspect scope (PER_VM|PER_CLASS|PER_INSTANCE|PER_JOINPOINT) "PER_VM"> <!ELEMENT introduction (mixin*,interfaces)> <!ATTLIST introduction class CDATA #IMPLIED> <!ATTLIST introduction expr CDATA #IMPLIED> <!ELEMENT mixin (interfaces, class, construction?)> <!ATTLIST mixin transient (true|false) "true"> <!ELEMENT interfaces (#PCDATA)> <!ELEMENT class (#PCDATA)> <!ELEMENT construction (#PCDATA)> <!ELEMENT metadata-loader EMPTY> <!ATTLIST metadata-loader tag CDATA #REQUIRED> <!ATTLIST metadata-loader class CDATA #REQUIRED> <!ELEMENT metadata ANY> <!ATTLIST metadata tag CDATA #REQUIRED> <!ATTLIST metadata class CDATA #REQUIRED> <!ELEMENT stack (interceptor|interceptor-ref|stack-ref|advice)+> <!ATTLIST stack name CDATA #REQUIRED> <!ELEMENT interceptor-ref EMPTY> <!ATTLIST interceptor-ref name CDATA #REQUIRED> <!ELEMENT stack-ref EMPTY> <!ATTLIST stack-ref name CDATA #REQUIRED> <!ELEMENT advice EMPTY> <!ATTLIST advice name CDATA #REQUIRED> <!ATTLIST advice aspect CDATA #REQUIRED> <!ELEMENT pointcut EMPTY> <!ATTLIST pointcut name CDATA #REQUIRED> <!ATTLIST pointcut expr CDATA #REQUIRED> <!ELEMENT prepare EMPTY> <!ATTLIST prepare expr CDATA #REQUIRED> <!ELEMENT pluggable-pointcut ANY> <!ATTLIST pluggable-pointcut name CDATA #REQUIRED> <!ATTLIST pluggable-pointcut class CDATA #REQUIRED> <!ELEMENT bind (interceptor|interceptor-ref|stack-ref|advice)+> <!ATTLIST bind name CDATA #IMPLIED> <!ATTLIST bind pointcut CDATA #REQUIRED> <!ATTLIST bind cflow CDATA #IMPLIED> <!ELEMENT cflow-stack (called|not-called)+> <!ATTLIST cflow-stack name CDATA #REQUIRED> <!ELEMENT called EMPTY> <!ATTLIST called expr CDATA #REQUIRED> <!ELEMENT not-called EMPTY> <!ATTLIST not-called expr CDATA #REQUIRED> <!ELEMENT dynamic-cflow EMPTY> <!ATTLIST dynamic-cflow name CDATA #REQUIRED> <!ATTLIST dynamic-cflow class CDATA #REQUIRED> <!ELEMENT annotation-introduction (#PCDATA)> <!ATTLIST annotation-introduction expr CDATA #REQUIRED> <!ATTLIST annotation-introduction invisible (true|false) #REQUIRED> <!ELEMENT typedef EMPTY> <!ATTLIST typedef name CDATA #REQUIRED> <!ATTLIST typedef expr CDATA #REQUIRED>
4.4. aspect
The
<aspect> tag specifies to the AOP container to declare an aspect
class. It is also used for configuring aspects as they are created and defining the scope of the
aspects instance.
4.4.1. Basic Definition
<aspect class="org.jboss.MyAspect"/>
In a basic declaration you specify the fully qualified class name of the aspect. If you want to reference
the aspect at runtime through the AspectManager, the name of the aspect is the same name as the class name.
The default Scope of this aspect is
PER_VM. Another important note is that aspect instances are created on demand and NOT
at deployment time.
4.4.2. Scope
<aspect class="org.jboss.MyAspect" scope="PER_VM"/>
The
scope attribute defines when an instance of the aspect should be created. An aspect
can be created per vm, per class, per instance, or per joinpoint.
Table 4.1.
Aspect instance scope
Name | Description |
---|---|
PER_VM | One and only instance of the aspect class is allocated for the entire VM. |
PER_CLASS |
One and only instance of the aspect class is allocated for a particular class. This instance will be created if an advice of that aspect is bound to that particular class. |
PER_INSTANCE |
An instance of an aspect will be created per advised object instance. For instance, if a method has an advice attached to it, whenever an instance of that advised class is allocated, there will also be one created for the aspect. |
PER_JOINPOINT |
An instance of an aspect will be created per joinpoint advised. If the joinpoint is a static member (constructor, static field/method), then there will be one instance of the aspect created per class, per joinpoint. If the joinpoint is a regular non-static member, than an instance of the aspect will be created per object instance, per joinpoint. |
PER_CLASS_JOINPOINT |
An instance of an aspect will be created per advised joinpoint. The aspect instance is shared between all instances of the class (for that joinpoint). |
4.4.3. Configuration
<aspect class="org.jboss.SomeAspect"> <attribute name="SomeIntValue">55</attribute> <advisor-attribute name="MyAdvisor"/> <instance-advisor-attribute name="MyInstanceAdvisor"/> <joinpoint-attribute name="MyJoinpoint"/> </aspect>
Aspects can be configured by default using a Java Beans style convention. The
<attribute>
tag will delegate to a setter method and convert the string value to the type of the setter method.
Table 4.2.
Supported Java Bean types
primitive types (int, float, String, etc…) |
java.lang.Class |
java.lang.Class[] |
java.lang.String[] |
java.math.BigDecimal |
org.w3c.dom.Document |
java.io.File |
java.net.InetAddress |
java.net.URL |
javax.management.ObjectName (if running in JBoss) |
Besides types, you can also inject AOP runtime constructs into the aspect. These types of attributes
are referenced within XML under special tags. See the table below.
Table 4.3.
Injecting AOP runtime constructs
<advisor-attribute> | org.jboss.aop.Advisor |
<instance-advisor-attribute> | org.jboss.aop.InstanceAdvisor |
<joinpoint-attribute> | org.jboss.aop.joinpoint.Joinpoint |
4.4.3.1. Names
If there
is no
name attribute defined, the name of the aspect is the same as the
class or
factory attribute value.
4.4.3.2. Example configuration
<aspect class="org.jboss.SomeAspect"> <attribute name="SomeIntValue">55</attribute> <advisor-attribute name="MyAdvisor"/> <instance-advisor-attribute name="MyInstanceAdvisor"/> <joinpoint-attribute name="MyJoinpoint"/> </aspect>
The above example would would need a class implemented as follows:
public class SomeAspect { public SomeAspect() {} public void setSomeIntValue(int val) {...} public void setMyAdvisor(org.jboss.aop.Advisor advisor) {...} public void setMyInstanceAdvisor(org.jboss.aop.InstanceAdvisor advisor) {...} public void setMyJoinpoint(org.jboss.aop.joinpoint.Joinpoint joinpoin) {...} }
4.4.4. Aspect Factories
<aspect name="MyAspect" factory="org.jboss.AspectConfigFactory" scope="PER_CLASS"> <some-arbitrary-xml>value</some-arbitrary-xml> </aspect>
If you do not like the default Java Bean configuration for aspects, or want to delegate aspect
creation to some other container, you can plug in your own factory class by specifying the
factory attribute rather than the
class attribute. Any arbitrary
XML can be specified in the aspect XML declaration and it will be passed to the factory class.
Factories must implement the
org.jboss.aop.advice.AspectFactory interface.
4.5. interceptor
<interceptor class="org.jboss.MyInterceptor" scope="PER_VM"/> <interceptor class="org.jboss.SomeInterceptor"> <attribute name="SomeIntValue">55</attribute> <advisor-attribute name="MyAdvisor"/> <instance-advisor-attribute name="MyInstanceAdvisor"/> <joinpoint-attribute name="MyJoinpoint"/> </interceptor> <interceptor name="MyAspect" factory="org.jboss.InterceptorConfigFactory" scope="PER_CLASS"> <some-arbitrary-xml>value</some-arbitrary-xml> </interceptor>
Interceptors are defined in XML the same exact way as aspects are. No difference except the tag. If there
is no
name attribute defined, the name of the interceptor is the same as the
class or
factory attribute value.
4.6. bind
<bind pointcut="execution(void Foo->bar())"> <interceptor-ref name="org.jboss.MyInterceptor/> <advice name="trace" aspect="org.jboss.MyAspect"/> </bind>
In the above example, the MyInterceptor interceptor and the trace method of the MyAspect class will be executed
when the Foo.bar method is invoked.
- bind
-
bind tag is used to bind an advice of an aspect, or an interceptor to a specific joinpoint.
The
pointcut attribute is required and at least an advice or interceptor-ref definition. - interceptor-ref
-
The
interceptor-ref tag must reference an already existing
interceptor XML
definition. The name attribute should be the name of the interceptor you are referencing. - advice
-
The
advice tag takes a
name attribute that should map to a method within
the aspect class. The
aspect attribute should be the name of the aspect definition.
4.7. stack
Stacks allow you to define a predefined set of advices/interceptors that you want to reference from within
a
bind element.
<stack name="stuff"> <interceptor class="SimpleInterceptor1" scope="PER_VM"/> <advice name="trace" aspect="org.jboss.TracingAspect"/> <interceptor class="SimpleInterceptor3"> <attribute name="size">55</attribute> </interceptor> </stack>
After defining the stack you can then reference it from within a
bind element.
<bind pointcut="execution(* POJO->*(..))"> <stack-ref name="stuff"/> </bind>
4.8. pointcut
The
pointcut tag allows you to define a pointcut expression, name it and reference it
within any binding you want. It is also useful to publish pointcuts into your applications to that others
have a clear set of named integration points.
<pointcut name="publicMethods" expr="execution(public * *->*(..))"/> <pointcut name="staticMethods" expr="execution(static * *->*(..))"/>
The above define two different pointcuts. One that matches all public methods, the other that matches
the execution of all static methods. These two pointcuts can then be referenced within a
bind
element.
<bind pointcut="publicMethods AND staticMethods"> <interceptor-ref name="tracing"/> </bind>
4.9. introduction
4.9.1. Interface introductions
The
introduction tag allows you to force an existing Java class to implement a particular
defined interface.
<introduction class="org.acme.MyClass"> <interfaces>java.io.Serializable</interfaces> </introduction>
The above declaration says that the org.acme.MyClass class will be forced to implement java.io.Serializable.
The
class attribute can take wildcards but not boolean expressions. If you need more
complex type expressions, you can use the
expr attribute instead.
<introduction expr="has(* *->@test(..)) OR class(org.acme.*)"> <interfaces>java.io.Serializable</interfaces> </introduction>
The
expr can be any type expression allowed in a
typedef expression
4.9.2. Mixins
When introducing an interface you can also define a mixin class which will provide the implementation
of that interface.
<introduction class="org.acme.MyClass"> <mixin> <interfaces> java.io.Externalizable </interfaces> <class>org.acme.ExternalizableMixin</class> <construction>new org.acme.ExternalizableMixin(this)</construction> </mixin> </introduction>
- interfaces
-
defines the list of interfaces you are introduction
- class
-
The type of the mixin class.
- construction
-
The construction statement allows you to specify any Java code to create the mixin class.
This code will be embedded directly in the class you are introducing to so
this
works in the construction statement.
4.10. annotation-introduction
Annotation introductions allow you to embed an annotation within a the class file of the class. You can
introduce an annotation to a class, method, field, or constructor.
<annotation-introduction expr="constructor(POJO->new())"> @org.jboss.complex (ch='a', string="hello world", flt=5.5, dbl=6.6, shrt=5, lng=6, integer=7, bool=true, annotation=@single("hello"), array={"hello", "world"}, clazz=java.lang.String) </annotation-introduction>
The
expr attribute takes method(), constructor(), class(), or field(). Within those you must define
a valid expression for that construct.
The following rules must be followed for the annotation declaration:
- Any annotation, Class or Enum referenced, MUST be fully qualified.
4.11. cflow-stack
Control flow is a runtime construct. It allows you to specify pointcut parameters revolving around the call
stack of a Java program. You can do stuff like, if method A calls method B calls Method C calls Method D from
Constructor A, trigger this advice.
In defining a control flow, you must first paint a picture of what the Java call stack should look like. This
is the responsibility of the cflow-stack.
<cflow-stack name="recursive2"> <called expr="void POJO->recursive(int)"/> <called expr="void POJO->recursive(int)"/> <not-called expr="void POJO->recursive(int)"/> </cflow-stack>
A
cflow-stack has a name and a bunch of
called and
not-called
elements that define individual constructor or method calls with a Java call stack. The
expr
attribute must be a method or constructor expression.
called states that the expr must be in the
call stack.
not-called states that there should not be any more of the expression within the stack.
In the above example, the
cflow-stack will be triggered if there are two and only two calls
to the
recursive method within the stack.
Once the
cflow-stack has been defined, it can then be referenced within a
bind
element through the
cflow attribute. Boolean expressions are allowed here as well.
<bind pointcut="execution(void POJO->recursive(int))" cflow="recursive2 AND !cflow2"> <interceptor class="SimpleInterceptor"/> </bind>
4.12. typedef
<typedef name="jmx" expr="class(@org.jboss.jmx.@MBean) OR has(* *->org.jboss.jmx.@ManagedOperation) OR has(* *->org.jboss.jmx.@ManagedAttribute)"/>
typedefs allow you to define complex type expressions and then use then pointcut expressions.
In the above example, we’re defining a class that is tagged as @Mbean, or has a method tagged as @ManagedOperaion
or @ManagedAttribute.
The above typedef could then be used in a pointcut, introduction, or bind element
<pointcut name="stuff" expr="execution(* $typedef{jmx}->*(..))"/> <introduction expr="class($typedef{jmx})">
4.13. dynamic-cflow
dynamic-cflow allows you to define code that will be executed that must be resolved true to trigger
positive on a cflow test on an advice binding. (See Dynamic CFlow for more information).
The test happens dynamically at runtime and when combined with a pointcut expression allows you to
do runtime checks on whether a advice binding should run or not. Create a dynamic cflow class, then you
must declare it with XML so that it can be used in bind expressions.
<dynamic-cflow name="simple" class="org.jboss.SimpleDynamicCFlow"/>
You can then use it within a
bind
<bind expr="execution(void Foo->bar())" cflow="simple">
4.14. prepare
The
prepare tag allows you to define a pointcut expression. Any joinpoint that matches
the expression will be aspectized and bytecode instrumented. This allows you to hotdeploy and bind
aspects at runtime as well as to work with the per instance API that every aspectized class has.
To prepare something, just define a pointcut expression that matches the joinpoint you want to instrument.
<prepare expr="execution(void Foo-bar())"/>
4.15. metadata
You can attach untyped metadata that is stored in
org.jboss.aop.metadata.SimpleMetaData structures
within the
org.jboss.aop.Advisor class that manages each aspectized class. The XML mapping
has a section for each type of metadata. Class, method, constructor, field, and defaults for the whole shabang.
Here’s an example:
<metadata tag="testdata" class="org.jboss.test.POJO"> <default> <some-data>default value</some-data> </default> <class> <data>class level</data> </class> <constructor expr="POJOConstructorTest()"> <some-data>empty</some-data> </constructor> <method expr="void another(int, int)"> <other-data>half</other-data> </method> <field name="somefield"> <other-data>full</other-data> </field> </metadata>
Any element can be defined under the class, default, method, field, and constructor tags. The name of these elements
are used as attribute names in SimpleMetaData structures.
The
tag attribute is the name used to reference the metadata within the Advisor, or Invocation
lookup mechanisms.
4.16. metadata-loader
<metadata-loader tag="security" class="org.jboss.aspects.security.SecurityClassMetaDataLoader"/>
If you need more complex XML mappings for untyped metadata, you can write your own metadata binding. The tag
attribute is used to trigger the loader. The loader class must implement the
org.jboss.aop.metadata.ClassMetaDataLoader
interface.
public interface ClassMetaDataLoader { public ClassMetaDataBinding importMetaData(Element element, String name, String tag, String classExpr) throws Exception; public void bind(ClassAdvisor advisor, ClassMetaDataBinding data, CtMethod[] methods, CtField[] fields, CtConstructor[] constructors) throws Exception; public void bind(ClassAdvisor advisor, ClassMetaDataBinding data, Method[] methods, Field[] fields, Constructor[] constructors) throws Exception; }
Any arbitrary XML can be in the
metadata element. The ClassMetaDataBinding.importMetaData
method is responsible for parsing the element and building ClassMetaDataBinding structurs which are
used in the precompiler and runtime bind steps. Look at the SecurityClassMetaDataLoader code shown above
for a real concrete example.
4.17. precedence
Precedence allows you to impose an overall relative sorting order of your interceptors and advices.
<precedence> <interceptor-ref name="org.acme.Interceptor"/> <advice aspect="org.acme.Aspect" name="advice1"/> <advice aspect="org.acme.Aspect" name="advice2"/> </precedence>
This says that when a joinpoint has both org.acme.Interceptor and
org.acme.Aspect.advice() bound to it, org.acme.Interceptor
must always be invoked before org.acme.Aspect.advice1() which must in turn be
invoked before org.acme.Aspect.advice2(). The ordering of
interceptors/advices that do not appear in a precedence is defined by their ordering for the
individual bindings or intercerceptor stacks.
4.18. declare
You can declare checks to be enforced at instrumentation time. They take a pointcut and a message.
If the pointcut is matched, the message is printed out.
4.18.1. declare-warning
<declare-warning expr="class($instanceof{VehicleDAO}) AND !has(public void *->save())"> All VehicleDAO subclasses must override the save() method. </declare-warning>
The above declaration says that if any subclass of VehicleDAO does not implement a noargs save() method, a
warning with the supplied message should be logged. Your application will continue to be instrumented/run
(since we are using declare-warning in this case).
4.18.2. declare-error
<declare-error expr="call(* org.acme.businesslayer.*->*(..)) AND within(org.acme.datalayer.*)"> Data layer classes should not call up to the business layer </declare-error>
The above declaration says that if any classes in the datalayer call classes in the business layer of your application,
an error should be thrown. Instumentation/execution of your application will stop.
by Barry Ruzek
07/04/2007
Abstract
An effective exception handling strategy is an architectural concern that transcends the boundaries of individual application components. Effective Java Exceptions outlines the Fault-Contingency exception model, which eliminates much of the confusion over using checked and unchecked exceptions in Java applications. Implementing the model using traditional Java techniques requires all components to follow a set of rules and behaviors. This implicit coupling between otherwise unrelated components leaves room for accidental lapses and failures. Applying aspect-oriented techniques to the Fault-Contingency exception model focuses the handling of this concern in one place, allowing other components to concentrate on their primary jobs.
This article shows how an exception-handling aspect based on the Fault-Contingency exception model is an improvement over the traditional implementation. It offers a complete example of an exception-handling aspect created with AspectJ, an aspect-oriented extension to Java, to illustrate the concepts. The code supplied herein runs on both BEA WebLogic 9.2 and Tomcat 5.0 application servers.
AOP and Architecture
As an application architect, you are responsible for making the decisions that govern how components relate to each other. Architectural decisions influence how components are designed, the patterns they use to collaborate, and the conventions they follow. If the decisions are sound, duly communicated, and followed by the project team, the result is a software system that is easy to understand, maintain, and extend. Everyone likes that, but it can be a challenge to achieve it. Architecture spans components, requiring them to perform certain actions or avoid particular behaviors so that everything harmonizes with an overall encompassing vision.
Development teams are composed of human beings, and humans are not perfect. Even the best development teams have some trouble maintaining the purity of their architectural vision. There are two traditional countermeasures teams use to avoid architectural transgressions. The first is to set up regular reviews of design and code. The second is to build frameworks. Reviews are intended to catch problems as they emerge. Frameworks provide a reusable infrastructure whose constraints are intended to prevent problems from emerging in the first place.
Aspect-oriented design is a third alternative for addressing architectural concerns. Instead of scattering architectural behavior throughout unrelated components, the behavior is encapsulated in an aspect and applied at specific execution points. Although work on aspect-oriented programming (AOP) began in the 1990s, it would be fair to say that widespread adoption is still some distance away. One reason may be a shortage of inspiring examples of how the technology can be of benefit. A compelling AOP example would have these traits:
- Valuable — Solving a well-recognized problem
- Hard to accomplish without AOP
- Easy to accomplish with AOP
The commonly used example of tracing method executions is a good way to illustrate what an aspect can do, but is not very inspiring—certainly not inspiring enough for most to invest in learning the technology or to make a case for using AOP in their next project. There are better examples out there, but you need to sift through all of the method-logging examples to find them.
Exception handling in Java applications is a well-recognized concern in many software projects. Poorly managed exception discipline leads to fragile code that’s difficult to understand and maintain. A consistent exception handling approach is a real benefit to most applications. Even when a team adopts an architectural model for exceptions, ensuring that every component adheres to the model requires effort and oversight. It seems like an exception handling model is a good candidate for an AOP exploration. Only you can judge whether or not it makes an inspiring example.
The Fault-Contingency Exception Model
An exception handling aspect starts with a model, or set of behaviors, that you want to apply across your application. The Fault-Contingency exception model provides a practical way of thinking about the exceptional conditions encountered by executing software. The model characterizes an anomalous outcome as either a Contingency or a Fault. A Contingency is an alternate outcome that can be described using the vocabulary of a component’s intended purpose. Callers of a method or constructor have a strategy for handling its Contingency outcomes. A Fault, on the other hand, is a failure that cannot be described in terms of a semantic contract but only in terms of implementation details. For example, consider an Account Service with a
getAccount()
method that returns an Account object when supplied with an Account ID. You could easily imagine possible contingencies such as «No such account,» or «Invalid Account ID,» expressed in terms of the method’s intended purpose. To imagine the possible faults, you would first have to know how the
getAccount()
method is implemented. Did it receive an
SQLException
because it could not connect to a database? Maybe there was a timeout waiting for a Web service that was down for maintenance. Or a missing file (that really ought to be there) caused a
FileNotFoundException
. The point here is that the caller of
getAccount()
should not know anything about the implementation and should not be forced to catch checked exceptions for any of its projected faults.
A simple Java implementation of the Fault-Contingency exception model has three basic concepts: Contingency Exception, Fault Exception, and Fault Barrier. Methods and constructors use Contingency Exceptions to signal alternate outcomes that are part of their contracts. Contingency Exceptions are checked exceptions, so the compiler helps ensure that callers consider all contracted outcomes. Fault Exceptions are used to signal implementation-specific failures. Fault Exceptions are unchecked exceptions, and working code generally avoids catching them, leaving that responsibility to the class acting as Fault Barrier. The Fault Barrier’s main responsibility is to craft a graceful exit to the processing activity when a Fault Exception reaches it. The graceful exit usually includes an indication of processing failure such as an apologetic display on the user interface (if there is one), or some other gesture indicating failure to the world «outside.»
A traditional implementation uses a subclass of
RuntimeException
(say,
FaultException
) to represent Fault Exceptions. Being an unchecked exception,
FaultException
can be thrown without being explicitly caught or declared in method and constructor signatures. Therefore, it can remain under the radar until it is caught and handled by the Fault Barrier. Contingency Exceptions are based on a subclass of
Exception
(say,
ContingencyException
) that makes them subject to checking by the Java compiler. Since a
ContingencyException
is an integral part of a semantic contract, it makes sense to enlist the compiler’s help to ensure that a caller has a strategy for handling it.
Components that participate in the model need to follow a set of conventions that make everything work. First, components must not throw exceptions other than
FaultException
or
ContingencyException
subclasses. Second, components must avoid catching
FaultException
, leaving that responsibility to the Fault Barrier. Components are responsible for handling exceptions thrown from external methods they invoke, performing translations to
FaultException
or
ContingencyException
, if needed. Any uncaught
RuntimeException
is considered to be a fault, and the Fault Barrier needs to be aware of this. These rules are simple and they go a long way toward eliminating messy, confusing exception sequences in application code. By cleanly separating faults and making them the responsibility of the Fault Barrier, the temptation for low-level code to handle fault conditions is greatly diminished. That leaves the field clear for Contingency Exceptions, which are expressly intended to convey meaningful information between components.
Where the Traditional Implementation Falls Short
The traditional implementation of the Fault-Contingency exception model is a nice improvement over
ad hoc exception handling but is still some distance away from ideal. All components must adhere to the conventions, even if they have no other relationship to each other. The only way to ensure that they do is to review the code. A component may inadvertently catch Fault Exceptions, preventing them from reaching the Fault Barrier. If that happens, you can kiss your graceful exit goodbye and be left without a way to diagnose the fault.
The traditional implementation places two obligations on the Fault Barrier. Its natural responsibility is to gracefully terminate the processing sequence. By virtue of its position near the top of the call stack, the Fault Barrier knows about the surrounding context and what constitutes an appropriate outward response. Its other obligation is to record analytic information associated with the fault so that people can figure out what happened. The only reason it has this job is that there is no other good place to do it. If a system requires more than one Fault Barrier (some do), then each must contain similar logic to capture the available information.
The ability to fix a problem depends on the quality of the information available. Practically speaking, the information that traditional implementation can supply is limited to what a
RuntimeException
can support: stack traces and error messages. Every Java programmer has experienced the joy of staring at a stack trace without a clue as to what really happened. Stack traces show what happened and where it happened but not
why it happened. Ideally, you want to know which methods were called and
how they were called—the types and values of arguments passed to each method leading up to the fault. Scattering code across every method to record its arguments upon entry is unpleasant, impractical, error prone, and a waste of effort unless a fault actually occurs.
Aspects, Pointcuts, and Advice
Aspect programming was invented to address problems like this. In our case, all the components in the application have to be concerned about the rules for faults and contingencies. If there is a lapse in a single class, its effect can ripple across any number of unrelated classes, causing the larger exception model to fail. Also, we have the Fault Barrier doing the job of recording analytic information when its natural role is simply to know how to generate a generic response to the external world and perform cleanup operations.
The AOP idea is to encapsulate the required behaviors in a single entity: an aspect. An aspect contains logic that runs at certain well-defined points across the application. The logic that runs is called
advice. The points at which advice is applied are called
join points. You specify the sets of join points advice applies to by defining pointcuts. A
pointcut is basically an expression that filters through all of the potential join points in your application, choosing some based on criteria such as the kind of join point, and various types of pattern matching. If you craft the aspect properly, it performs actions that would otherwise be scattered around the application. With everything in one place, the components in the rest of the application are free to concentrate on their primary jobs. The result is greater component cohesion, and that is a good thing.
The sample application containing our exception-handling aspect is built using AspectJ, a superset of the Java language. The language supports the diversity of join points that are significant to exception handling across any application. Exceptions can be generated and caught in lots of ways during the execution of a Java application. Method executions are just one of them. An exception handling aspect also needs to worry about constructor execution, object initialization, and class initialization, which can all result in exceptions. It also needs to worry about places where exceptions are explicitly thrown and caught. AspectJ’s pointcut language supports everything needed to implement the model we have in mind.
The ExceptionHandling Aspect
The ExceptionHandling aspect is designed to be as flexible as possible so it has only two compile-time dependencies. They are the two Java classes that represent faults and contingencies:
-
FaultException
— A subclass of
RuntimeException
that represents fault conditions. -
ContingencyException
— A subclass of
Exception
that represents contingency outcomes. Subclasses represent specific contingency conditions.
The aspect assumes (and enforces) that the rest of the application uses these classes according to model rules. A nice feature of the AspectJ system is the ability to «program the compiler» to enforce a policy that transcends standard Java language rules. In our case, we want to encourage developers to think in terms of faults and contingencies and clearly distinguish between them. Since our architecture provides a
ContingencyException
base class, we want to ensure that developers use its subclasses exclusively to represent contingency conditions. By doing so, we will prevent the temptation to declare that a method throws
SQLException
(for example), when the method should be treating any unexpected JDBC problem as a fault.
The ExceptionHandling aspect uses pointcuts to detect methods and constructors that declare checked exceptions other than those based on
ContingencyException
. Violations are flagged as compile errors, a sure way to draw attention to the problem.
package exception;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.Stack;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.CodeSignature;
public abstract aspect ExceptionHandling {
...
pointcut methodViolation():
execution(* *(..) throws (Exception+
&& !ContingencyException+
&& !RuntimeException+));
declare error:
methodViolation():
"Method throws a checked exception that is not
a ContingencyException";
pointcut constructorViolation():
execution(*.new(..) throws (Exception+
&& !ContingencyException+
&& !RuntimeException+));
declare error:
constructorViolation():
"Constructor throws a checked exception that is not
a ContingencyException";
...
}
Listing 1. Compile-time exception policy enforcement
In the example below, the
commit()
method of the
Transaction
class violates the exception policy by declaring that it throws
SQLException
, which should be considered to be a fault in its context. The compiler flags the violation as a compile error, based on the declaration in the
ExceptionHandling
aspect. The
rollback()
method conforms to the model by treating an unexpected
SQLException
as a fault, so no flag appears. The examples developed for this article were developed using Eclipse 3.2.1 with the AspectJ Development Tools (AJDT) 1.4.1 plug-in installed.
Figure 1. Exception policy violation generates an error flag
Exception Advice Join Points
The
ExceptionHandling
aspect uses the
exceptionAdvicePoints()
pointcut to apply advice to any execution sequence capable of throwing an exception. The aspect uses this pointcut several times to inject processing when exceptions might be thrown. The pointcut includes these join points:
- All Method executions
- All Constructor executions
- All Object initializations
- All Object preinitializations
- All Class initializations
Since the
ExceptionHandling
aspect has its own methods, has its own constructor, and undergoes class and object initialization, some of the join points selected above fall within the aspect itself. That’s generally a bad thing, causing recursions when the aspect tries to advise its own advice. To avoid this possibility, the
exceptionAdvicePoints()
pointcut specifically excludes these join points from those selected above:
- Any join point within the aspect’s own lexical scope
- Any join point within the lexical scope of its subaspects
- Any join point in the control flow of advice execution
public abstract aspect ExceptionHandling {
...
pointcut exceptionAdvicePoints():
(execution (* *.*(..))
|| execution (*.new(..))
|| initialization(*.new(..))
|| preinitialization(*.new(..))
|| staticinitialization(*))
&& !within(ExceptionHandling+)
&& !cflow(adviceexecution());
...
}
Listing 2. Exception advice points
Now, the
ExceptionHandling
aspect is able to apply exception-related advice to all application components outside of itself. It won’t try to apply its advice to methods that may run as a result of executing its own advice.
Runtime Exception Translation
Throwable
Throwable
FaultException
Throwable
FaultException
Throwable
ExceptionHandling
The «after throwing» advice runs if an execution sequence throws any sort of
Throwable
. If the exception is a
FaultException
or
ContingencyException
, the advice takes no action. Otherwise, it replaces the offending exception with a new instance of
FaultException
, citing the uncaught exception as the cause. Note that the aspect’s compile-time exception policy enforcement simplifies the check that the advice has to make.
public abstract aspect ExceptionHandling {
...
after() throwing(Throwable throwable):exceptionAdvicePoints(){
if (!(throwable instanceof FaultException ||
throwable instanceof ContingencyException)) {
throw new FaultException("Unhandled exception: ",
throwable);
}
}
...
}
Listing 3. Runtime exception translation
- 1
- 2
Немножко магии от AspectJ
Наверно, вы уже сталкивались с таким понятием, как AOП — аспектно-ориентированное программирование.
Обычно, про него вспоминают, когда говорят про декларативное использование транзакций, про проверку прав доступа, либо про реализацию журналирования.
Но это не единственные области применения АОП.
Я хочу показать ещё пару областей применения из реальных проектов:
- Модификация исходного кода для реализации дополнительных возможностей.
- Принудительная проверка контракта между модулями.
Модификация исходного кода для реализации дополнительных возможностей
Предположим, что у нас есть модуль в приложении, который предоставляет нужную нам функциональность. С модулем всё в порядке, кроме одного — все его методы могут выбрасывать проверяемые исключения, что ведёт к ухудшению читаемости кода, так как вместо простого вызова метода:
наш вызов превращается в следующую конструкцию
try {
service.doUsefulThing();
} catch ( FirstServiceException e) {
processException(e);
} catch ( SecondServiceException e) {
processException(e);
}
Дополнительная проблема в том, что у модуля количество модулей 10+, количество методов также велико, что приводит к тому, что блоки try/catch
замусоривают код. Решение с использованием паттерна Callback
также приведёт к замусориванию кода.
Вариант решения проблемы с использованием AOP
Решение данной проблемы было таким — а что, если используя возможности AOP трансформировать проверяемое исключение в непроверяемое? Таким образом, мы сможем избавиться от скучной проверки на исключения в нашем коде, а для обработки исключения (ведь мы всегда его будем обрабатывать одинаково) достаточно будет использовать обработку исключения на верхнем уровне абстракции.
Для более элегантного решения проблемы было решено добавить собственную аннотацию, которой нужно помечать метод, который использует сервис из «плохого» модуля.
package com.blogger.atamanenko;
import java.lang.annotation.Documented;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
@Inherited
public @interface SuppressExceptions {
}
А также аспект, который бы делал всю нужную нам функциональность:
public aspect ExceptionSupressingAspect {
declare soft :ServiceException: execution(@com.blogger.atamanenko.annotation.SuppressExceptions * *.*(..));
}
Данный аспект делает в точности следующее: “Смягчает” исключение ServiceException
для метода, который помечен аннотацией @SuppressExceptions
.
Пример использования:
@SuppressExceptions
protected Entity findEntity(final Identifiable id) {
return entityService.findById(id);
}
Принудительная проверка контракта между модулями
Часто нам необходимо принудительно требовать выполнения каких-то архитектурных ограничений, например, контроллеры должны работать только с сервисами и им запрещено напрямую обращаться к базе данных.
Для реализации таких проверок можно также использовать возможности AOP.
Предположим, что в нашем приложении модуль сервиса выставляет наружу DTO для работы, скрывая при этом классы модели. Для того, чтобы явно запретить доступ к классам и методам модели нам необходимо создать аспект, который бы вызывал ошибку компиляции при нарушении ограничения.
aspect ForbidAccessToModelAspect {
// Full prohibition of access to model:
pointcut accessModel(): call(* com.blogger.atamanenko.app.model..*.*(..));
declare error: accessModel() : "Illegal call to model";
}
После объявления такого аспекта, мы получим ошибку компиляции, что, очевидным образом, приведёт к выполнению архитектурного ограничения.
Если же нам необходимо разрешить доступ к одному пакету только из какого-то определённого другого, то мы можем модифицировать наш аспект следующим образом:
aspect ForbidAccessToModelAspect2 {
pointcut accessModel(): call(* com.blogger.atamanenko.app.model.**.*(..));
// Allow access to model from specific package for methods and constructors
pointcut allowAccessModelFromSpecificPackage(): withincode(* com.blogger.atamanenko.app.allowedpackage..*.*(..));
pointcut allowAccessModelFromSpecificPackage2(): withincode(com.blogger.atamanenko.app.allowedpackage..*.new(..));
// forbid usage from any other methods.
declare error: accessModel() && !(allowAccessModelFromSpecificPackage() || allowAccessModelFromSpecificPackage()):"Illegal call to Model from forbidden package";
}
Такой аспект, созданный в нашем модуле запретит нам использовать классы модели из всех пакетов, кроме com.blogger.atamanenko.app.allowedpackage
Сборка приложения
Файл аспекта нужно положить в каталог src/main/aspect
, а для сборки приложения необходимо использовать не стандартный Oracle Java Compiler, а AspectJ compiler.
Пример конфигурации для Apache Maven:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>${aspectj-maven-plugin.version}</version>
<configuration>
<complianceLevel>1.6</complianceLevel>
<aspectLibraries>
<aspectLibrary>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</aspectLibrary>
</aspectLibraries>
<verbose>true</verbose>
</configuration>
<executions>
<execution>
<phase>process-sources</phase>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
Заключение
Вот в общем-то и всё. Я сознательно не стал описывать языковые конструкции аспектов, так как они подробно описаны в руководстве AspectJ
Recipe 16.6. Extending Compilation
Problem
You want to extend the capabilities of the compiler to enforce application specific rules.
Solution
Use the declare error or declare warning statement to specify conditions that should raise a compiler error or warning respectively.
Discussion
AspectJ supports advising most of the Java system but perhaps the most interesting and odd construct is the Java compiler. Example 16-10 shows how to declare a new error and warning that will be raised by the compiler if the specified condition is found within the application being compiled.
Example 16-10. Using aspects to declare new warnings and errors that can be raised by the compiler
public aspect CompilationAdviceRecipe { declare error : call(void ProtectedAccessClass.setValue(int)) && !this(MyClass) : "Must only set the ProtectedAccessClass.value from a MyClass object"; declare warning : call(int ProtectedAccessClass.getValue( )) && !this(MyClass) : "Should only be reading ProtectedAccessClass.value from a MyClass object"; }
The capability to enforce new rules regarding what Java constructs are allowed and how they should be used is useful when you are potentially creating an application that may have changing needs depending on its target environment and as to what functionality it is allowed to expose. This allows you to define system policy warnings and to guide developers in their implementations.
The error and warning messages must be simple, declared string literals. You cannot reference any other part of the software architecture, even static pieces of the structure, from within these messages. |
See Also
Recipe 17.5 shows how this form of warning is used to indicate to developers that they are breaking the rules of Builder design pattern; for real-world examples of using declare error and declare warning to enforce design constraints and general developer guidance, take a look at the aTrack bug-tracking project available at https://atrack.java.net.