After working in software development for a number of years, one tends to build up a repository of useful code examples and utilities. I’m certainly no different in this regard and go one step further by maintaining a hosted Subversion repository to keep such code examples safe and sound (and readily accessible).
One such code example that I often turn to is a logging aspect implemented using Spring AOP and AspectJ. This logging aspect traces method entry and exit which proves very useful if you need to perform root cause analysis in pre-production environments.
In what follows, I’m going to share this code example. I have also made the logging aspect source code available for download.
I have assumed you have a reasonable grasp of Aspect Orientated Programming concepts and terminologies.
The Logger
There are a few options to choose from when considering a logging framework and these have evolved over the years so it is probably best to abstract the logging framework from the aspect. For this purpose, I have implemented a simple logging interface and log level enum.
package net.thoughtforge.logger;
public interface Logger {
boolean isLogLevel(LogLevel logLevel, Class<?> clazz);
void log(LogLevel logLevel, Class<?> clazz, Throwable throwable,
String pattern, Object... arguments);
}
Listing 1 : Logger.java
package net.thoughtforge.logger;
public enum LogLevel {
DEBUG,
ERROR,
FATAL,
INFO,
TRACE,
WARN
}
Listing 2 : LogLevel.java
In the majority of cases, I end up using the Commons Logging framework and so have included this implementation of the Logger in the source download.
You may decide that this level of abstraction is overkill and that you are willing to commit to using a specific logging framework. When making this decision, keep in mind that you will be specifying the log level throughout your code base.
The Logging Aspect
I am going to use the @Aspect approach to implement the logging aspect which is to say I will use annotations to specify the advice. I want the logging aspect to log the method name, argument values, return value and any exception thrown so I will use the @Before, @AfterThrowing and @AfterReturning annotations.
@Before(value = "@annotation(trace)", argNames = "joinPoint, trace")
public void before(JoinPoint joinPoint, Loggable loggable) {
Class<? extends Object> clazz = joinPoint.getTarget().getClass();
String name = joinPoint.getSignature().getName();
if (ArrayUtils.isEmpty(joinPoint.getArgs())) {
logger.log(loggable.value(), clazz, null, BEFORE_STRING, name,
constructArgumentsString(clazz, joinPoint.getArgs()));
} else {
logger.log(loggable.value(), clazz, null, BEFORE_WITH_PARAMS_STRING, name,
constructArgumentsString(clazz, joinPoint.getArgs()));
}
}
Listing 3 : @Before Code Snippet
The ‘Before’ advice simply logs (at the appropriate log level) the method name and the toString value of all arguments (if any). ‘Before’ advice executes when a join point is reached.
@AfterThrowing(value = "@annotation(net.thoughtforge.aspect.Loggable)",
throwing = "throwable", argNames = "joinPoint, throwable")
public void afterThrowing(JoinPoint joinPoint, Throwable throwable) {
Class<? extends Object> clazz = joinPoint.getTarget().getClass();
String name = joinPoint.getSignature().getName();
logger.log(LogLevel.ERROR, clazz, throwable, AFTER_THROWING, name,
throwable.getMessage(), constructArgumentsString(clazz,
joinPoint.getArgs()));
}
Listing 4 : @AfterThrowing Code Snippet
The ‘AfterThrowing’ advice logs the method name, exception message and the toString value of all arguments (if any). ‘AfterThrowing’ advice executes after a method exits by throwing an exception.
@AfterReturning(value = "@annotation(trace)", returning = "returnValue",
argNames = "joinPoint, trace, returnValue")
public void afterReturning(JoinPoint joinPoint, Loggable loggable,
Object returnValue) {
Class<? extends Object> clazz = joinPoint.getTarget().getClass();
String name = joinPoint.getSignature().getName();
if (joinPoint.getSignature() instanceof MethodSignature) {
MethodSignature signature = (MethodSignature) joinPoint
.getSignature();
Class<?> returnType = signature.getReturnType();
if (returnType.getName().compareTo("void") == 0) {
logger.log(loggable.value(), clazz, null, AFTER_RETURNING_VOID,
name, constructArgumentsString(clazz, returnValue));
return;
}
}
logger.log(loggable.value(), clazz, null, AFTER_RETURNING, name,
constructArgumentsString(clazz, returnValue));
}
Listing 5 : @AfterReturning Code Snippet
The ‘AfterReturning’ advice logs the method name and the toString value of the returned value (if any). ‘AfterReturning’ advice executes after a method exits normally.
You will notice that I log the toString value to identify objects. I routinely use the Apache Commons ToStringBuilder to create the toString value. I find this particularly useful when working with persistent entities as it allows me to clearly identify the entity.
Another possible implementation that avoids using the toString method is to use the Apache Commons ReflectionToStringBuilder within the logging aspect to create a string representation of the object being logged.
If you are writing your own toString implementations (the Commons implementation is perfectly adequate) and are implementing an object with complex properties be aware of recursive invocations that may result in a stack overflow exception.
Specifying the Pointcut
A pointcut expression is an expression that specifies where in the code the advice will be applied. With AspectJ, you can create a pointcut by specifying package, class and method attributes among other things. I find the easiest way to specify a pointcut for the logging aspect is by matching methods that have a specific annotation.
package net.thoughtforge.aspect;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import net.thoughtforge.logger.LogLevel;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Loggable {
LogLevel value();
}
Listing 6 : Loggable.java
As you can see, the Loggable annotation has one property that specifies the log level at which the log statement should be output. Using the annotation means that developers never need to alter the pointcut expression to add or remove methods to the pointcut. A developer only has to add the annotation to a method to have the logging aspect applied.
package net.thoughtforge.bean;
import java.util.Date;
import net.thoughtforge.aspect.Loggable;
import net.thoughtforge.logger.LogLevel;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.springframework.stereotype.Component;
@Component(value = "simpleBean")
public class SimpleBean {
private Date dateProperty;
private Integer integerProperty;
private String stringProperty;
@Loggable(value = LogLevel.TRACE)
public Date getDateProperty() {
return dateProperty;
}
@Loggable(value = LogLevel.TRACE)
public void setDateProperty(final Date dateProperty) {
this.dateProperty = dateProperty;
}
@Loggable(value = LogLevel.TRACE)
public Integer getIntegerProperty() {
return integerProperty;
}
@Loggable(value = LogLevel.TRACE)
public void setIntegerProperty(final Integer integerProperty) {
this.integerProperty = integerProperty;
}
@Loggable(value = LogLevel.TRACE)
public String getStringProperty() {
return stringProperty;
}
@Loggable(value = LogLevel.TRACE)
public void setStringProperty(final String stringProperty) {
this.stringProperty = stringProperty;
}
@Override
public String toString() {
return new ToStringBuilder(this).append("dateProperty", dateProperty)
.append("integerProperty", integerProperty).append(
"stringProperty", stringProperty).toString();
}
}
Listing 7 : SimpleBean.java
The SimpleBean and SimpleBeanSubclass are for demonstration purposes. You can see that each method is annotated with the @Loggable annotation and the log level is set to TRACE. You can obviously use different log levels for different methods as required.
package net.thoughtforge.bean;
import java.math.BigDecimal;
import net.thoughtforge.aspect.Loggable;
import net.thoughtforge.logger.LogLevel;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.springframework.stereotype.Component;
@Component(value = "simpleBeanSubclass")
public class SimpleBeanSubclass extends SimpleBean {
private BigDecimal decimalProperty;
@Loggable(value = LogLevel.TRACE)
public BigDecimal getDecimalProperty() {
return decimalProperty;
}
@Loggable(value = LogLevel.TRACE)
public void setDecimalProperty(final BigDecimal decimalProperty) {
this.decimalProperty = decimalProperty;
}
@Override
public String toString() {
return new ToStringBuilder(this).append("decimalProperty",
decimalProperty).appendSuper(super.toString()).toString();
}
}
Listing 8 : SimpleBeanSubclass.java
Also note the use of the ToStringBuilder to create the toString value. You may choose to use the ReflectionToStringBuilder or some other mechanism.
Testing the Logging Aspect
One way to test the logging aspect is to simply have a test invoke instrumented methods and observe the log statements produced. This is a useful exercise but requires manual intervention to determine if the test was successful.
In order to create an automated test for the logging aspect, I needed to create a mock logger that simulates the actions of a logger and allows me to interrogate the log statements produced.
package net.thoughtforge.mock.logger;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.thoughtforge.logger.LogLevel;
import net.thoughtforge.logger.Logger;
import org.springframework.stereotype.Component;
@Component
public class MockLogger implements Logger {
private Map<Class<?>, LogLevel> logLevelMap =
new HashMap<Class<?>, LogLevel>();
private Map<Class<?>, List<LogMessage>> messages = new HashMap<Class<?>, List<LogMessage>>();
public boolean isLogLevel(LogLevel logLevel, Class<?> clazz) {
boolean result = false;
switch (logLevel) {
case DEBUG:
result = isLogLevelEnabled(clazz, LogLevel.DEBUG);
case ERROR:
result = isLogLevelEnabled(clazz, LogLevel.ERROR);
case FATAL:
result = isLogLevelEnabled(clazz, LogLevel.FATAL);
case INFO:
result = isLogLevelEnabled(clazz, LogLevel.INFO);
case TRACE:
result = isLogLevelEnabled(clazz, LogLevel.TRACE);
case WARN:
result = isLogLevelEnabled(clazz, LogLevel.WARN);
default:
result = false;
}
return result;
}
public void log(LogLevel logLevel, Class<?> clazz,
Throwable throwable, String pattern,
Object... arguments) {
switch (logLevel) {
case DEBUG:
debug(clazz, throwable, pattern, arguments);
break;
case ERROR:
error(clazz, throwable, pattern, arguments);
break;
case FATAL:
fatal(clazz, throwable, pattern, arguments);
break;
case INFO:
info(clazz, throwable, pattern, arguments);
break;
case TRACE:
trace(clazz, throwable, pattern, arguments);
break;
case WARN:
warn(clazz, throwable, pattern, arguments);
break;
}
}
private void debug(Class<?> clazz, Throwable throwable,
String pattern, Object... arguments) {
if (throwable != null) {
getMessages(clazz).add(new LogMessage(LogLevel.DEBUG, format(pattern, arguments), throwable));
} else {
getMessages(clazz).add(new LogMessage(LogLevel.DEBUG, format(pattern, arguments)));
}
}
private void error(Class<?> clazz, Throwable throwable,
String pattern, Object... arguments) {
if (throwable != null) {
getMessages(clazz).add(new LogMessage(LogLevel.ERROR, format(pattern, arguments), throwable));
} else {
getMessages(clazz).add(new LogMessage(LogLevel.ERROR, format(pattern, arguments)));
}
}
private void fatal(Class<?> clazz, Throwable throwable,
String pattern, Object... arguments) {
if (throwable != null) {
getMessages(clazz).add(new LogMessage(LogLevel.FATAL, format(pattern, arguments), throwable));
} else {
getMessages(clazz).add(new LogMessage(LogLevel.FATAL, format(pattern, arguments)));
}
}
private void info(Class<?> clazz, Throwable throwable,
String pattern, Object... arguments) {
if (throwable != null) {
getMessages(clazz).add(new LogMessage(LogLevel.INFO, format(pattern, arguments), throwable));
} else {
getMessages(clazz).add(new LogMessage(LogLevel.INFO, format(pattern, arguments)));
}
}
private void trace( Class<?> clazz, Throwable throwable,
String pattern, Object... arguments) {
if (throwable != null) {
getMessages(clazz).add(new LogMessage(LogLevel.TRACE, format(pattern, arguments), throwable));
} else {
getMessages(clazz).add(new LogMessage(LogLevel.TRACE, format(pattern, arguments)));
}
}
private void warn( Class<?> clazz, Throwable throwable,
String pattern, Object... arguments) {
if (throwable != null) {
getMessages(clazz).add(new LogMessage(LogLevel.WARN, format(pattern, arguments), throwable));
} else {
getMessages(clazz).add(new LogMessage(LogLevel.WARN, format(pattern, arguments)));
}
}
private String format( String pattern, Object... arguments) {
return MessageFormat.format(pattern, arguments);
}
public void resetLoggers() {
messages = new HashMap<Class<?>, List<LogMessage>>();
}
public List<LogMessage> getMessages(Class<?> clazz) {
if (messages.get(clazz) == null) {
messages.put(clazz, new ArrayList<LogMessage>());
}
return messages.get(clazz);
}
private boolean isLogLevelEnabled(Class<?> clazz, LogLevel logLevel) {
return logLevelMap.get(clazz) != null && logLevelMap.get(clazz).equals(logLevel);
}
public void setLogLevel(Class<?> clazz, LogLevel logLevel) {
logLevelMap.put(clazz, logLevel);
}
public class LogMessage {
private LogLevel logLevel;
private String message;
private Throwable throwable;
public LogMessage(LogLevel logLevel, String message, Throwable throwable) {
this(logLevel, message);
this.throwable = throwable;
}
public LogMessage(LogLevel logLevel, String message) {
this.logLevel = logLevel;
this.message = message;
}
public LogLevel getLogLevel() {
return logLevel;
}
public String getMessage() {
return message;
}
public Throwable getThrowable() {
return throwable;
}
}
}
Listing 9 : MockLogger.java
The logging aspect test utilises the MockLogger and makes assertions about the log statements produced.
package net.thoughtforge.aspect;
import java.math.BigDecimal;
import junit.framework.Assert;
import net.thoughtforge.bean.SimpleBean;
import net.thoughtforge.bean.SimpleBeanSubclass;
import net.thoughtforge.logger.LogLevel;
import net.thoughtforge.mock.logger.MockLogger;
import net.thoughtforge.mock.logger.MockLogger.LogMessage;
import org.apache.commons.lang.time.DateUtils;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
"/applicationContext/applicationContext-aspect.xml",
"/applicationContext/applicationContext-logger.xml",
"/applicationContext/applicationContext.xml" })
public class LoggingAspectTest {
@Autowired
private MockLogger logger;
@Autowired
@Qualifier(value = "simpleBean")
public SimpleBean simpleBean;
@Autowired
public SimpleBeanSubclass simpleBeanSubclass;
@Before
public void before() {
logger.setLogLevel(SimpleBean.class, LogLevel.TRACE);
logger.setLogLevel(SimpleBeanSubclass.class, LogLevel.TRACE);
logger.resetLoggers();
}
@Test
public void testSimpleBean_SetDateProperty() throws Exception {
simpleBean.setDateProperty(
DateUtils.parseDate("01/01/2010", new String[] {"dd/MM/yyyy"}));
Assert.assertEquals(2, logger.getMessages(SimpleBean.class).size());
assertEquals(logger.getMessages(SimpleBean.class).get(0),
LogLevel.TRACE,
"[ entering < setDateProperty > with params Fri Jan 01 00:00:00 GMT 2010 ]");
assertEquals(logger.getMessages(SimpleBean.class).get(1),
LogLevel.TRACE,
"[ leaving < setDateProperty > ]");
}
@Test
public void testSimpleBean_SetIntegerProperty() {
simpleBean.setIntegerProperty(100);
Assert.assertEquals(2, logger.getMessages(SimpleBean.class).size());
assertEquals(logger.getMessages(SimpleBean.class).get(0),
LogLevel.TRACE,
"[ entering < setIntegerProperty > with params 100 ]");
assertEquals(logger.getMessages(SimpleBean.class).get(1),
LogLevel.TRACE,
"[ leaving < setIntegerProperty > ]");
}
@Test
public void testSimpleBean_SetStringProperty() {
simpleBean.setStringProperty("stringProperty");
Assert.assertEquals(2, logger.getMessages(SimpleBean.class).size());
assertEquals(logger.getMessages(SimpleBean.class).get(0),
LogLevel.TRACE,
"[ entering < setStringProperty > with params stringProperty ]");
assertEquals(logger.getMessages(SimpleBean.class).get(1),
LogLevel.TRACE,
"[ leaving < setStringProperty > ]");
}
@Test
public void testSimpleBean_GetDateProperty() {
simpleBean.getDateProperty();
Assert.assertEquals(2, logger.getMessages(SimpleBean.class).size());
assertEquals(logger.getMessages(SimpleBean.class).get(0),
LogLevel.TRACE,
"[ entering < getDateProperty > ]");
assertEquals(logger.getMessages(SimpleBean.class).get(1),
LogLevel.TRACE,
"[ leaving < getDateProperty > returning Fri Jan 01 00:00:00 GMT 2010 ]");
}
@Test
public void testSimpleBean_GetIntegerProperty() {
simpleBean.getIntegerProperty();
Assert.assertEquals(2, logger.getMessages(SimpleBean.class).size());
assertEquals(logger.getMessages(SimpleBean.class).get(0),
LogLevel.TRACE,
"[ entering < getIntegerProperty > ]");
assertEquals(logger.getMessages(SimpleBean.class).get(1),
LogLevel.TRACE,
"[ leaving < getIntegerProperty > returning 100 ]");
}
@Test
public void testSimpleBean_GetStringProperty() {
simpleBean.getStringProperty();
Assert.assertEquals(2, logger.getMessages(SimpleBean.class).size());
assertEquals(logger.getMessages(SimpleBean.class).get(0),
LogLevel.TRACE,
"[ entering < getStringProperty > ]");
assertEquals(logger.getMessages(SimpleBean.class).get(1),
LogLevel.TRACE,
"[ leaving < getStringProperty > returning stringProperty ]");
}
@Test
public void testSimpleBeanSubclass_SetDateProperty() throws Exception {
simpleBeanSubclass.setDateProperty(
DateUtils.parseDate("01/01/2010", new String[] {"dd/MM/yyyy"}));
Assert.assertEquals(2, logger.getMessages(SimpleBeanSubclass.class).size());
assertEquals(logger.getMessages(SimpleBeanSubclass.class).get(0),
LogLevel.TRACE,
"[ entering < setDateProperty > with params Fri Jan 01 00:00:00 GMT 2010 ]");
assertEquals(logger.getMessages(SimpleBeanSubclass.class).get(1),
LogLevel.TRACE,
"[ leaving < setDateProperty > ]");
}
@Test
public void testSimpleBeanSubclass_SetDecimalProperty() {
simpleBeanSubclass.setDecimalProperty(new BigDecimal("0.25"));
Assert.assertEquals(2, logger.getMessages(SimpleBeanSubclass.class).size());
assertEquals(logger.getMessages(SimpleBeanSubclass.class).get(0),
LogLevel.TRACE,
"[ entering < setDecimalProperty > with params 0.25 ]");
assertEquals(logger.getMessages(SimpleBeanSubclass.class).get(1),
LogLevel.TRACE,
"[ leaving < setDecimalProperty > ]");
}
@Test
public void testSimpleBeanSubclass_SetIntegerProperty() {
simpleBeanSubclass.setIntegerProperty(100);
Assert.assertEquals(2, logger.getMessages(SimpleBeanSubclass.class).size());
assertEquals(logger.getMessages(SimpleBeanSubclass.class).get(0),
LogLevel.TRACE,
"[ entering < setIntegerProperty > with params 100 ]");
assertEquals(logger.getMessages(SimpleBeanSubclass.class).get(1),
LogLevel.TRACE,
"[ leaving < setIntegerProperty > ]");
}
@Test
public void testSimpleBeanSubclass_SetStringProperty() {
simpleBeanSubclass.setStringProperty("stringProperty");
Assert.assertEquals(2, logger.getMessages(SimpleBeanSubclass.class).size());
assertEquals(logger.getMessages(SimpleBeanSubclass.class).get(0),
LogLevel.TRACE,
"[ entering < setStringProperty > with params stringProperty ]");
assertEquals(logger.getMessages(SimpleBeanSubclass.class).get(1),
LogLevel.TRACE,
"[ leaving < setStringProperty > ]");
}
@Test
public void testSimpleBeanSubclass_GetDateProperty() {
simpleBeanSubclass.getDateProperty();
Assert.assertEquals(2, logger.getMessages(SimpleBeanSubclass.class).size());
assertEquals(logger.getMessages(SimpleBeanSubclass.class).get(0),
LogLevel.TRACE,
"[ entering < getDateProperty > ]");
assertEquals(logger.getMessages(SimpleBeanSubclass.class).get(1),
LogLevel.TRACE,
"[ leaving < getDateProperty > returning Fri Jan 01 00:00:00 GMT 2010 ]");
}
@Test
public void testSimpleBeanSubclass_GetDecimalProperty() {
simpleBeanSubclass.getDecimalProperty();
Assert.assertEquals(2, logger.getMessages(SimpleBeanSubclass.class).size());
assertEquals(logger.getMessages(SimpleBeanSubclass.class).get(0),
LogLevel.TRACE,
"[ entering < getDecimalProperty > ]");
assertEquals(logger.getMessages(SimpleBeanSubclass.class).get(1),
LogLevel.TRACE,
"[ leaving < getDecimalProperty > returning 0.25 ]");
}
@Test
public void testSimpleBeanSubclass_GetIntegerProperty() {
simpleBeanSubclass.getIntegerProperty();
Assert.assertEquals(2, logger.getMessages(SimpleBeanSubclass.class).size());
assertEquals(logger.getMessages(SimpleBeanSubclass.class).get(0),
LogLevel.TRACE,
"[ entering < getIntegerProperty > ]");
assertEquals(logger.getMessages(SimpleBeanSubclass.class).get(1),
LogLevel.TRACE,
"[ leaving < getIntegerProperty > returning 100 ]");
}
@Test
public void testSimpleBeanSubclass_GetStringProperty() {
simpleBeanSubclass.getStringProperty();
Assert.assertEquals(2, logger.getMessages(SimpleBeanSubclass.class).size());
assertEquals(logger.getMessages(SimpleBeanSubclass.class).get(0),
LogLevel.TRACE,
"[ entering < getStringProperty > ]");
assertEquals(logger.getMessages(SimpleBeanSubclass.class).get(1),
LogLevel.TRACE,
"[ leaving < getStringProperty > returning stringProperty ]");
}
private void assertEquals(LogMessage logMessage, LogLevel logLevel, String message) {
Assert.assertEquals(logLevel, logMessage.getLogLevel());
Assert.assertEquals(message, logMessage.getMessage());
}
}
Listing 10 : LoggingAspectTest.java
The test is not exhaustive but suffices to test the general operation of the logging aspect.
Final Word
Hopefully you found my example useful but I will finish by saying that there are many choices that I made in deciding on this approach.
A strong argument could be made for the use of XML configuration as opposed to the @Aspect approach which allows you greater control over the execution of the advice without modifying code. This would be particularly relevant in an environment where performance was a major focus. I personally have found that the control provided by logging frameworks to enable and disable particular loggers is sufficient in most environments.



Hi,
Nice post very informative.
I’ve tried the source code but instead of using spring 2.5. I’ve used spring 3.0.3 release version. However I’m having java.lang.NoClassDefFoundError
at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.(AbstractAutoProxyCreator.java:118)
at org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator.(AbstractAdvisorAutoProxyCreator.java:47)
at org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator.(AspectJAwareAdvisorAutoProxyCreator.java:46)
at org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator.(AnnotationAwareAspectJAutoProxyCreator.java:48)
Has anybody also encountered this? Is this a spring 3.0.3 issue?
thanks,
Mich
Hey,
You forgot to add in the applicationContext.xml or maybe …
But I have a problem with a similar annotation I’ve been asked to do. It works well, except when an annoted method calls another annoted in the same object. Only the first annotation reached will be logged
You are probably using proxy based AOP (i.e. aspects are applied via a proxy) and so when you invoke the second method you are invoking it directly, not through the proxy. This is documented in the Spring reference documentation.
The alternatives to Springs proxy based AOP are to use run time or compile time byte code injection.
Hope this helps.
Sorry some symbols and what was inside has been removed from the comment.
So I was saying you forgot to add the tag ” aop:aspectj-autoproxy/ ” or the tag ” bean class=”org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator” “.
These tags are not necessary when configuring the aspect directly in XML.