Open Source Repository

Home /spring/spring-aop-3.0.5 | Repository Home



org/springframework/aop/aspectj/annotation/ReflectiveAspectJAdvisorFactory.java
/*
 * Copyright 2002-2007 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.aop.aspectj.annotation;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;

import org.aopalliance.aop.Advice;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.DeclareParents;
import org.aspectj.lang.annotation.Pointcut;

import org.springframework.aop.Advisor;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.aspectj.AbstractAspectJAdvice;
import org.springframework.aop.aspectj.AspectJAfterAdvice;
import org.springframework.aop.aspectj.AspectJAfterReturningAdvice;
import org.springframework.aop.aspectj.AspectJAfterThrowingAdvice;
import org.springframework.aop.aspectj.AspectJAroundAdvice;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.aspectj.AspectJMethodBeforeAdvice;
import org.springframework.aop.aspectj.DeclareParentsAdvisor;
import org.springframework.aop.framework.AopConfigException;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

/**
 * Factory that can create Spring AOP Advisors given AspectJ classes from
 * classes honoring the AspectJ 5 annotation syntax, using reflection to
 * invoke the corresponding advice methods.
 *
 @author Rod Johnson
 @author Adrian Colyer
 @author Juergen Hoeller
 @author Ramnivas Laddad
 @since 2.0
 */
public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFactory {

  public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory maaif) {
    final Class<?> aspectClass = maaif.getAspectMetadata().getAspectClass();
    final String aspectName = maaif.getAspectMetadata().getAspectName();
    validate(aspectClass);

    // We need to wrap the MetadataAwareAspectInstanceFactory with a decorator
    // so that it will only instantiate once.
    final MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory =
        new LazySingletonAspectInstanceFactoryDecorator(maaif);

    final List<Advisor> advisors = new LinkedList<Advisor>();
    ReflectionUtils.doWithMethods(aspectClass, new ReflectionUtils.MethodCallback() {
      public void doWith(Method methodthrows IllegalArgumentException {
        // Exclude pointcuts
        if (AnnotationUtils.getAnnotation(method, Pointcut.class== null) {
          Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName);
          if (advisor != null) {
            advisors.add(advisor);
          }
        }
      }
    });

    // If it's a per target aspect, emit the dummy instantiating aspect.
    if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
      Advisor instantiationAdvisor = new SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory);
      advisors.add(0, instantiationAdvisor);
    }

    // Find introduction fields.
    for (Field field : aspectClass.getDeclaredFields()) {
      Advisor advisor = getDeclareParentsAdvisor(field);
      if (advisor != null) {
        advisors.add(advisor);
      }
    }

    return advisors;
  }

  /**
   * Build a {@link org.springframework.aop.aspectj.DeclareParentsAdvisor}
   * for the given introduction field.
   <p>Resulting Advisors will need to be evaluated for targets.
   @param introductionField the field to introspect
   @return <code>null</code> if not an Advisor
   */
  private Advisor getDeclareParentsAdvisor(Field introductionField) {
    DeclareParents declareParents = (DeclareParentsintroductionField.getAnnotation(DeclareParents.class);
    if (declareParents == null) {
      // Not an introduction field
      return null;
    }

    if (DeclareParents.class.equals(declareParents.defaultImpl())) {
      // This is what comes back if it wasn't set. This seems bizarre...
      // TODO this restriction possibly should be relaxed
      throw new IllegalStateException("defaultImpl must be set on DeclareParents");
    }

    return new DeclareParentsAdvisor(
        introductionField.getType(), declareParents.value(), declareParents.defaultImpl());
  }


  public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aif,
      int declarationOrderInAspect, String aspectName) {

    validate(aif.getAspectMetadata().getAspectClass());

    AspectJExpressionPointcut ajexp =
        getPointcut(candidateAdviceMethod, aif.getAspectMetadata().getAspectClass());
    if (ajexp == null) {
      return null;
    }
    return new InstantiationModelAwarePointcutAdvisorImpl(
        this, ajexp, aif, candidateAdviceMethod, declarationOrderInAspect, aspectName);
  }

  private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) {
    AspectJAnnotation<?> aspectJAnnotation =
        AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
    if (aspectJAnnotation == null) {
      return null;
    }
    AspectJExpressionPointcut ajexp =
        new AspectJExpressionPointcut(candidateAspectClass, new String[0]new Class[0]);
    ajexp.setExpression(aspectJAnnotation.getPointcutExpression());
    return ajexp;
  }


  public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut ajexp,
      MetadataAwareAspectInstanceFactory aif, int declarationOrderInAspect, String aspectName) {

    Class<?> candidateAspectClass = aif.getAspectMetadata().getAspectClass();
    validate(candidateAspectClass);

    AspectJAnnotation<?> aspectJAnnotation =
        AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
    if (aspectJAnnotation == null) {
      return null;
    }

    // If we get here, we know we have an AspectJ method. 
    // Check that it's an AspectJ-annotated class
    if (!isAspect(candidateAspectClass)) {
      throw new AopConfigException("Advice must be declared inside an aspect type: " +
          "Offending method '" + candidateAdviceMethod + "' in class [" +
          candidateAspectClass.getName() "]");
    }

    if (logger.isDebugEnabled()) {
      logger.debug("Found AspectJ method: " + candidateAdviceMethod);
    }

    AbstractAspectJAdvice springAdvice;

    switch (aspectJAnnotation.getAnnotationType()) {
      case AtBefore:
        springAdvice = new AspectJMethodBeforeAdvice(candidateAdviceMethod, ajexp, aif);
        break;
      case AtAfter:
        springAdvice = new AspectJAfterAdvice(candidateAdviceMethod, ajexp, aif);
        break;
      case AtAfterReturning:
        springAdvice = new AspectJAfterReturningAdvice(candidateAdviceMethod, ajexp, aif);
        AfterReturning afterReturningAnnotation = (AfterReturningaspectJAnnotation.getAnnotation();
        if (StringUtils.hasText(afterReturningAnnotation.returning())) {
          springAdvice.setReturningName(afterReturningAnnotation.returning());
        }
        break;
      case AtAfterThrowing:
        springAdvice = new AspectJAfterThrowingAdvice(candidateAdviceMethod, ajexp, aif);
        AfterThrowing afterThrowingAnnotation = (AfterThrowingaspectJAnnotation.getAnnotation();
        if (StringUtils.hasText(afterThrowingAnnotation.throwing())) {
          springAdvice.setThrowingName(afterThrowingAnnotation.throwing());
        }
        break;
      case AtAround:
        springAdvice = new AspectJAroundAdvice(candidateAdviceMethod, ajexp, aif);
        break;
      case AtPointcut:
        if (logger.isDebugEnabled()) {
          logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() "'");
        }
        return null;
      default:
        throw new UnsupportedOperationException(
            "Unsupported advice type on method " + candidateAdviceMethod);
    }

    // Now to configure the advice...
    springAdvice.setAspectName(aspectName);
    springAdvice.setDeclarationOrder(declarationOrderInAspect);
    String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod);
    if (argNames != null) {
      springAdvice.setArgumentNamesFromStringArray(argNames);
    }
    springAdvice.calculateArgumentBindings();
    return springAdvice;
  }

  /**
   * Synthetic advisor that instantiates the aspect.
   * Triggered by per-clause pointcut on non-singleton aspect.
   * The advice has no effect.
   */
  protected static class SyntheticInstantiationAdvisor extends DefaultPointcutAdvisor {

    public SyntheticInstantiationAdvisor(final MetadataAwareAspectInstanceFactory aif) {
      super(aif.getAspectMetadata().getPerClausePointcut()new MethodBeforeAdvice() {
        public void before(Method method, Object[] args, Object target) {
          // Simply instantiate the aspect
          aif.getAspectInstance();
        }
      });
    }
  }

}