Most software applications usually have some secondary—but critical—features, such as security, transaction, and audit-logging, spanned across multiple logical modules. It would be a nice idea not to mix these cross-cutting concerns in your core business logic. Aspect Oriented Programming (AOP) helps you achieve this.
Object Oriented Programming (OOP) is about modularizing complex software programs, with objects as the fundamental units that hold your core business logic and data. AOP complements OOP to add more complex functionality transparently across modules of your application without polluting the original object structure. AOP stitches (weaves) cross-cutting concerns into your program, either at compile time or runtime, without modifying the base code itself. AOP lets the object-oriented program stay clean and just have the core business concerns.
In AOP, the framework weaves the cross-cutting concerns into the main program transparently. This weaving process comes in two different flavors: static and dynamic. In the case of static AOP, as the name implies, Aspects are compiled directly into static files, that is, to the Java bytecode, on compilation. This method performs better, as there is no special interception at runtime. But the drawback is that you need to recompile the entire application every time you change anything in the code. AspectJ, one of the most comprehensive AOP implementations, provides compile-time weaving of Aspects.
In the case of dynamic AOP, the weaving process is performed dynamically at runtime. Different frameworks implement this differently, but the most general way of achieving this is using proxies or wrappers for the advised objects, allowing the Advice to be invoked as required. This is a more flexible method as you can apply AOP with varying behavior at runtime depending on data, which is not possible in the case of static AOP. There is no need for recompiling the main application code if you use XML files for defining your AOP constructs (schema-based approach). The disadvantage of dynamic AOP is a very negligible performance loss due to the extra runtime processing.
Spring AOP is proxy based, that is, it follows the dynamic flavor of AOP. Spring provides the facility to use static AOP by integrating with AspectJ too.
Understanding AOP concepts and terms gives you an excellent starting point for AOP; it helps you visualize how and where AOP can be applied in your application:
before
, after
, and around
advices. Typically, an Aspect has one or more Advices.execution(* com.xyz.service.*.*(..))
.Spring provides a proxy-based dynamic implementation of AOP, developed purely in Java. It neither requires a special compilation process like AspectJ nor controls the class loader hierarchy, hence it can be deployed inside any Servlet container or application server.
Although not a full-blown AOP framework like AspectJ, Spring provides a simple and easy-to-use abstraction of most of the common features of AOP. It supports only method execution join points; field interception is not implemented. Spring provides tight integration with AspectJ, in case you want to advise very fine-grained Aspect orientation that Spring AOP doesn't cover by adding more AspectJ-specific features without breaking the core Spring AOP APIs.
Spring AOP uses standard JDK dynamic proxies for Aspect orientation by default. JDK dynamic proxies allow any interface (or set of interfaces) to be proxied. If you want to proxy classes rather than interfaces, you may switch to CGLIB proxies. Spring automatically switches to use CGLIB if a target object does not implement an interface.
Starting from Spring 2.0, you can follow either a schema-based approach or an @AspectJ
annotation style to write custom Aspects. Both of these styles offer fully typed Advice and use of the AspectJ pointcut language while still using Spring AOP for weaving.
When using schema-based AOP, you need to import aop
namespace tags into your application-context
file, as follows:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- bean definitions here --> </beans>
@AspectJ
refers to a style of declaring Aspects as regular Java classes that are annotated. Spring interprets the same annotations as AspectJ 5, using a library supplied by AspectJ for pointcut parsing and matching. Spring AOP has no dependency on the AspectJ compiler or weaver, though.
When using the @AspectJ
annotation style, you first need to enable @AspectJ
support in your Spring configuration, whether or not it is in the XML or Java configuration. Additionally, you need to make sure you add aspectjweaver.jar
in your classpath. Adding an @EnableAspectJAutoProxy
annotation to your Java @Configuration
annotation will enable @AspectJ
support in your project:
@Configuration @ComponentScan(basePackages = "com.springessentialsbook") @EnableAspectJAutoProxy public class AOPJavaConfigurator { ... }
Alternatively, if you use XML-based configuration, @AspectJ
support can be enabled by adding the <aop:aspectj-autoproxy/>
element in your application-context
file.
Your Aspect is a simple POJO, either annotated with @Aspect
(org.aspectj.lang.annotation.Aspect
) or declared as <aop:aspect/>
under the <aop:config>
section of your application-context
XML file. Remember, the class marked as @Aspect
should be declared as a Spring bean using either an annotation or <bean/>
declaration in your application context XML file.
Here is an annotated Aspect, a Spring component annotated as @Aspect
:
@Component("auditLoggerAspect") @Aspect public class AuditLoggerAspect { ... }
Note that @Aspect
is a Spring bean too. It can be any of the specializations of @Component
.
Now, let's take a look at the XML alternative for Aspect declaration:
<aop:config> <aop:aspect id="audLogAspect" ref="auditLoggerAspect"> </aop:config> <bean id="auditLoggerAspect" class="com...AuditLoggerAspect"/>
Aspects may have methods and fields, just like any other class. They may also contain pointcut, advice, and introduction (inter-type) declarations. Aspects themselves cannot be the target of Advice from other Aspects; they are excluded from auto-proxying.
A pointcut comprises two parts, as shown in the following code snippet: a method signature (an empty method with a void
return type inside the Aspect
class) with any parameters and an expression that matches the exact method executions we are interested in. Remember, Spring AOP only supports method execution join points:
@Pointcut("execution(* com.springessentialsbook.service.TaskService.createTask(..))") //Pointcut expression private void createTaskPointCut() {} //Signature
The pointcut expression follows the standard AspectJ format. You may refer to the AspectJ pointcut expression reference for the detailed syntax. The following section gives you a strong foundation for constructing pointcuts for Spring AOP.
Spring AOP supports just a subset of the original AspectJ pointcut designators (PCDs) for use in pointcut expressions, as given in the following table:
PCD |
Description |
---|---|
|
Method execution join point; the default PCD for Spring AOP |
|
Matches methods in a range of types, packages, and so on |
|
Matches proxy instances of a given type |
|
Matches target object with a given type |
|
Matches methods with the given argument types |
|
Matches methods of classes with the given annotation |
|
Matches methods having argument (s) with the given annotation (s) |
|
Matches methods within types that have a given annotation |
|
Matches methods with the given annotation |
In addition to the preceding table, Spring supports an extra non-AspectJ PCD, bean
, which is useful to directly refer to a Spring bean or a set of beans with a comma-separated list of beans using bean(idsOrNamesOfBean)
.
Note that the pointcuts intercept only public
methods due to the proxy nature of Spring AOP. If you want to intercept protected
and private
methods or even constructors, consider using AspectJ weaving (integrated with Spring itself) instead.
Pointcut expressions can be combined using &&
, ||
, and !
. You can refer to pointcut expressions by name, too. Let's see a few examples:
@Pointcut("execution(* com.taskify.service.*.*(..))") private void allServiceMethods() {} @Pointcut("execution(public * *(..))") private void anyPublicOperation() {} @Pointcut("anyPublicOperation() && allServiceMethods()") private void allPublicServiceMethods() {} @Pointcut("within(com.taskify.service..*)") private void allServiceClasses() {} @Pointcut("execution(* set*(..))") private void allSetMethods() {} @Pointcut("execution(* com.taskify.service.TaskService.*(..))") private void allTaskServiceMethods() {} @Pointcut("target(com.taskify.service.TaskService)") private void allTaskServiceImplMethods() {} @Pointcut("@within(org.springframework.transaction.annotation.Transactional)") private void allTransactionalObjectMethods() {} @Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)") private void allTransactionalAnnotatedMethods() {} @Pointcut("bean(simpleTaskService)") private void allSimpleTaskServiceBeanMethods() {}
An XML version of a pointcut definition goes like this:
<aop:config> ... <aop:pointcut id="allTaskServicePointCut" expression="execution(*com.taskify.service..TaskService.*(..))"/> </aop:config>
An Advice is the action that gets injected before, after, or around the method executions matched by the pointcut expression. The pointcut expression associated with an Advice could be a named or defined pointcut, as listed in the above examples, or a pointcut expression declared in place, that is, advices and pointcuts can be declared together.
Let's see an example for an Advice that refers to a pointcut expression named Pointcut
:
@Pointcut("execution(* com.taskify.service.TaskService.*(..))") private void allTaskServiceMethods() {} @Before("allTaskServiceMethods()") private void logBeforeAllTaskServiceMethods() { logger.info("*** logBeforeAllTaskServiceMethods invoked ! ***"); }
The following code listing combines both a join point and Advice in one go. This is the most common approach:
@After("execution(* com.taskigy.service.TaskService.*(..))") private void logAfterAllTaskServiceMethods() { logger.info("***logAfterAllTaskServiceMethods invoked ! ***"); }
The following table lists the available Advice annotations:
Advice annotation |
Description |
---|---|
|
Runs before method execution. |
|
Runs after method exit (finally). |
|
Runs after the method returns without an exception. You can bind the return value with the Advice as the method argument. |
|
Runs after the method exits by throwing an exception. You can bind the exception with the Advice as the method argument. |
|
The target method actually runs inside this Advice. It allows you to manipulate the method execution inside your Advice method. |
The @Around
Advice gives you more control over method execution, as the intercepted method essentially runs inside your Advice method. The first argument of the Advice must be ProceedingJoinPoint
. You need to invoke the proceed()
method of ProceedingJoinPoint
inside the Advice body in order to execute the target method; else, the method will not get called. After the method execution returns to you with whatever it returns back to your advice, do not forget to return the result in your Advice method. Take a look at a sample @Around
advice:
@Around("execution(* com.taskify.service.**.find*(..))") private Object profileServiceFindAdvice(ProceedingJoinPoint jPoint) throws Throwable { Date startTime = new Date(); Object result = jPoint.proceed(jPoint.getArgs()); Date endTime = new Date(); logger.info("Time taken to execute operation: " + jPoint.getSignature() + " is " + (endTime.getTime() - startTime.getTime()) + " ms"); return result; }
There are two distinct ways of accessing the parameters of the method you are advising in the Advice method:
args
in the pointcut definitionLet's see the first approach:
@Before("execution(* com.taskify.service.TaskService.createTask(..)") private void logBeforeCreateTaskAdvice(JoinPoint joinpoint) { logger.info("***logBeforeCreateTaskAdvice invoked ! ***"); logger.info("args = " + Arrays.asList(joinpoint.getArgs())); }
You can see that joinpoint.getArgs()
returns Object[]
of all the arguments passed to the intercepted method. Now, let's see how to bind named arguments to the Advice method:
@Before("createTaskPointCut() and args(name, priority, createdByuserId, assigneeUserId)") private void logBeforeCreateTaskAdvice(String name, int priority, int createdByuserId, int assigneeUserId) { logger.info("name = " + name + "; priority = " + priority + "; createdByuserId = " + createdByuserId); }
Note that the joinpoint
expression matches the arguments by name. You can have a joinpoint
object as an optional first argument in the method signature without specifying it in the expression: you will have both joinpoint
and arguments, enabling more manipulation.