Support Spring Expression Language in Spring AOP

User Case
We want to create @Loggable so developers can use it to specify log level and what to log before or after the method being called. developers can use #p0, #p1 to log param values, use #result to log response or specify any valid spring expression.

Learning How to Do it from Spring Code
We know that spring cache annotations supports spring expression language, we can use #p0, #p1 in @Cacheable, use #result in @CachePut. So we can debug spring cache code to figure it how it works.

Relate code in Spring CacheAspectSupport.execute
CacheAspectSupport.generateKey(CacheOperationContext, Object)

CacheOperationExpressionEvaluator.createEvaluationContext
if (result != NO_RESULT) evaluationContext.setVariable(RESULT_VARIABLE, result);
- To support #result, we just put method return result value into the varaible: result.

The Implementation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Loggable {
    Level level() default Level.INFO;
    /**
     * This value has to be a valid spring expression<br>
     * Example: "'-begin'" or use #p0, #p1 to refer method params.
     * 
     * @return
     */
    String beginMessage() default "";
    /**
     * This value has to be a valid spring expression<br>
     * Example: "#result", "'-end'"
     * 
     * @return
     */
    String endMessage() default "";
}
@Aspect
@Component
public class LoggableAspect {
    @Value("${throw.exception.if.invalid.expression:true}")
    private boolean throwExceptionIfInvalidExpression;
    public static final String RESULT_VARIABLE = "result";
    /**
     * It is recommended to reuse ParameterNameDiscoverer instances as far as possible.
     */
    private static final ParameterNameDiscoverer parameterNameDiscoverer =
            new LocalVariableTableParameterNameDiscoverer();
    /**
     * SpEL parser. Instances are reusable and thread-safe.
     */
    private static final ExpressionParser parser = new SpelExpressionParser();

    @Around("@annotation(com.sony.sie.kamaji.vue.metadata.aop.Loggable)")
    public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        final Class<?> declaringClass = method.getDeclaringClass();
        final Logger logger = LoggerFactory.getLogger(declaringClass);
        final Loggable loggable = method.getAnnotation(Loggable.class);
        Object result = null;
        if (loggable != null) {
            final Level logLevel = loggable.level();
            if (whetherParseExpression(loggable.beginMessage(), logger, logLevel)) {
                final EvaluationContext beginContext = new MethodBasedEvaluationContext(invocation.getThis(), method,
                        invocation.getArguments(), parameterNameDiscoverer);
                parseExpressionValue(logger, logLevel, loggable.beginMessage(), beginContext);
            }
            try {
                result = invocation.proceed();
                if (whetherParseExpression(loggable.endMessage(), logger, logLevel)) {
                    final EvaluationContext context = new MethodBasedEvaluationContext(invocation.getThis(), method,
                            invocation.getArguments(), parameterNameDiscoverer);
                    context.setVariable(RESULT_VARIABLE, result);
                    parseExpressionValue(logger, logLevel, loggable.endMessage(), context);
                }
                return result;
            } catch (final RuntimeException e) {
                logValues(logger, "Failed with exception: " + e.getMessage(), logLevel);
                throw e;
            }
        }
        return invocation.proceed();
    }

    private void parseExpressionValue(final Logger logger, final Level logLevel, final String expression,
            final EvaluationContext context) {
        if (StringUtils.isBlank(expression)) {
            return;
        }
        Object value = null;
        try {
            value = parser.parseExpression(expression).getValue(context);
        } catch (final Exception e) {
            if (throwExceptionIfInvalidExpression) {
                throw new VMSBusinessException(ErrorCode.internal_error, e,
                        "Failed to parse expression: " + expression);
            }
        }
        logValues(logger, value != null ? value.toString() : expression, logLevel);
    }

    /**
     * If the log level is not enabled, no need to do anything at all.
     */
    private boolean whetherParseExpression(String expression, final Logger logger, final Level logLevel) {
        if (StringUtils.isBlank(expression)) {
            return false;
        }
        switch (logLevel) {
            case INFO:
                return logger.isInfoEnabled();
            case ERROR:
                return logger.isErrorEnabled();
            case WARN:
                return logger.isWarnEnabled();
            case DEBUG:
                return logger.isDebugEnabled();
            case TRACE:
                return logger.isTraceEnabled();
            default:
                return false;
        }
    }
}

Post a Comment

Labels

Java (159) Lucene-Solr (110) All (60) Interview (59) J2SE (53) Algorithm (37) Eclipse (35) Soft Skills (35) Code Example (31) Linux (26) JavaScript (23) Spring (22) Windows (22) Web Development (20) Tools (19) Nutch2 (18) Bugs (17) Debug (15) Defects (14) Text Mining (14) J2EE (13) Network (13) PowerShell (11) Chrome (9) Continuous Integration (9) How to (9) Learning code (9) Performance (9) UIMA (9) html (9) Design (8) Dynamic Languages (8) Http Client (8) Maven (8) Security (8) Trouble Shooting (8) bat (8) blogger (8) Big Data (7) Google (7) Guava (7) JSON (7) Problem Solving (7) ANT (6) Coding Skills (6) Database (6) Scala (6) Shell (6) css (6) Algorithm Series (5) Cache (5) IDE (5) Lesson Learned (5) Miscs (5) Programmer Skills (5) System Design (5) Tips (5) adsense (5) xml (5) AIX (4) Code Quality (4) GAE (4) Git (4) Good Programming Practices (4) Jackson (4) Memory Usage (4) OpenNLP (4) Project Managment (4) Python (4) Spark (4) Testing (4) ads (4) regular-expression (4) Android (3) Apache Spark (3) Become a Better You (3) Concurrency (3) Eclipse RCP (3) English (3) Firefox (3) Happy Hacking (3) IBM (3) J2SE Knowledge Series (3) JAX-RS (3) Jetty (3) Restful Web Service (3) Script (3) regex (3) seo (3) .Net (2) Android Studio (2) Apache (2) Apache Procrun (2) Architecture (2) Batch (2) Build (2) Building Scalable Web Sites (2) C# (2) C/C++ (2) CSV (2) Career (2) Cassandra (2) Distributed (2) Fiddler (2) Google Drive (2) Gson (2) Html Parser (2) Http (2) Image Tools (2) JQuery (2) Jersey (2) LDAP (2) Life (2) Logging (2) Software Issues (2) Storage (2) Text Search (2) xml parser (2) AOP (1) Application Design (1) AspectJ (1) Bit Operation (1) Chrome DevTools (1) Cloud (1) Codility (1) Data Mining (1) Data Structure (1) ExceptionUtils (1) Exif (1) Feature Request (1) FindBugs (1) Greasemonkey (1) HTML5 (1) Httpd (1) I18N (1) IBM Java Thread Dump Analyzer (1) JDK Source Code (1) JDK8 (1) JMX (1) Lazy Developer (1) Mac (1) Machine Learning (1) Mobile (1) My Plan for 2010 (1) Netbeans (1) Notes (1) Operating System (1) Perl (1) Problems (1) Product Architecture (1) Programming Life (1) Quality (1) Redhat (1) Redis (1) Review (1) RxJava (1) Solutions logs (1) Team Management (1) Thread Dump Analyzer (1) Troubleshooting (1) Visualization (1) boilerpipe (1) htm (1) ongoing (1) procrun (1) rss (1)

Popular Posts