1 /* 2 * Copyright 2002-2010 the original author or authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package org.springframework.aop.aspectj.annotation; 18 19 import java.lang.annotation.Annotation; 20 import java.lang.reflect.Constructor; 21 import java.lang.reflect.Field; 22 import java.lang.reflect.Method; 23 import java.lang.reflect.Modifier; 24 import java.util.HashMap; 25 import java.util.Map; 26 import java.util.StringTokenizer; 27 28 import org.apache.commons.logging.Log; 29 import org.apache.commons.logging.LogFactory; 30 import org.aspectj.lang.annotation.After; 31 import org.aspectj.lang.annotation.AfterReturning; 32 import org.aspectj.lang.annotation.AfterThrowing; 33 import org.aspectj.lang.annotation.Around; 34 import org.aspectj.lang.annotation.Aspect; 35 import org.aspectj.lang.annotation.Before; 36 import org.aspectj.lang.annotation.Pointcut; 37 import org.aspectj.lang.reflect.AjType; 38 import org.aspectj.lang.reflect.AjTypeSystem; 39 import org.aspectj.lang.reflect.PerClauseKind; 40 41 import org.springframework.aop.aspectj.AspectJExpressionPointcut; 42 import org.springframework.aop.framework.AopConfigException; 43 import org.springframework.core.ParameterNameDiscoverer; 44 import org.springframework.core.PrioritizedParameterNameDiscoverer; 45 import org.springframework.core.annotation.AnnotationUtils; 46 import org.springframework.util.StringUtils; 47 48 /** 49 * Abstract base class for factories that can create Spring AOP Advisors 50 * given AspectJ classes from classes honoring the AspectJ 5 annotation syntax. 51 * 52 * <p>This class handles annotation parsing and validation functionality. 53 * It does not actually generate Spring AOP Advisors, which is deferred to subclasses. 54 * 55 * @author Rod Johnson 56 * @author Adrian Colyer 57 * @author Juergen Hoeller 58 * @since 2.0 59 */ 60 public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFactory { 61 62 protected static final ParameterNameDiscoverer ASPECTJ_ANNOTATION_PARAMETER_NAME_DISCOVERER = 63 new AspectJAnnotationParameterNameDiscoverer(); 64 65 private static final String AJC_MAGIC = "ajc$"; 66 67 68 /** 69 * Find and return the first AspectJ annotation on the given method 70 * (there <i>should</i> only be one anyway...) 71 */ 72 @SuppressWarnings("unchecked") findAspectJAnnotationOnMethod(Method method)73 protected static AspectJAnnotation findAspectJAnnotationOnMethod(Method method) { 74 Class<? extends Annotation>[] classesToLookFor = new Class[] { 75 Before.class, Around.class, After.class, AfterReturning.class, AfterThrowing.class, Pointcut.class}; 76 for (Class<? extends Annotation> c : classesToLookFor) { 77 AspectJAnnotation foundAnnotation = findAnnotation(method, c); 78 if (foundAnnotation != null) { 79 return foundAnnotation; 80 } 81 } 82 return null; 83 } 84 findAnnotation(Method method, Class<A> toLookFor)85 private static <A extends Annotation> AspectJAnnotation<A> findAnnotation(Method method, Class<A> toLookFor) { 86 A result = AnnotationUtils.findAnnotation(method, toLookFor); 87 if (result != null) { 88 return new AspectJAnnotation<A>(result); 89 } 90 else { 91 return null; 92 } 93 } 94 95 96 /** Logger available to subclasses */ 97 protected final Log logger = LogFactory.getLog(getClass()); 98 99 protected final ParameterNameDiscoverer parameterNameDiscoverer; 100 101 AbstractAspectJAdvisorFactory()102 protected AbstractAspectJAdvisorFactory() { 103 PrioritizedParameterNameDiscoverer prioritizedParameterNameDiscoverer = new PrioritizedParameterNameDiscoverer(); 104 prioritizedParameterNameDiscoverer.addDiscoverer(ASPECTJ_ANNOTATION_PARAMETER_NAME_DISCOVERER); 105 this.parameterNameDiscoverer = prioritizedParameterNameDiscoverer; 106 } 107 108 /** 109 * We consider something to be an AspectJ aspect suitable for use by the Spring AOP system 110 * if it has the @Aspect annotation, and was not compiled by ajc. The reason for this latter test 111 * is that aspects written in the code-style (AspectJ language) also have the annotation present 112 * when compiled by ajc with the -1.5 flag, yet they cannot be consumed by Spring AOP. 113 */ isAspect(Class<?> clazz)114 public boolean isAspect(Class<?> clazz) { 115 return (hasAspectAnnotation(clazz) && !compiledByAjc(clazz)); 116 } 117 hasAspectAnnotation(Class<?> clazz)118 private boolean hasAspectAnnotation(Class<?> clazz) { 119 return (AnnotationUtils.findAnnotation(clazz, Aspect.class) != null); 120 } 121 122 /** 123 * We need to detect this as "code-style" AspectJ aspects should not be 124 * interpreted by Spring AOP. 125 */ compiledByAjc(Class<?> clazz)126 private boolean compiledByAjc(Class<?> clazz) { 127 // The AJTypeSystem goes to great lengths to provide a uniform appearance between code-style and 128 // annotation-style aspects. Therefore there is no 'clean' way to tell them apart. Here we rely on 129 // an implementation detail of the AspectJ compiler. 130 for (Field field : clazz.getDeclaredFields()) { 131 if (field.getName().startsWith(AJC_MAGIC)) { 132 return true; 133 } 134 } 135 return false; 136 } 137 validate(Class<?> aspectClass)138 public void validate(Class<?> aspectClass) throws AopConfigException { 139 // If the parent has the annotation and isn't abstract it's an error 140 if (aspectClass.getSuperclass().getAnnotation(Aspect.class) != null && 141 !Modifier.isAbstract(aspectClass.getSuperclass().getModifiers())) { 142 throw new AopConfigException("[" + aspectClass.getName() + "] cannot extend concrete aspect [" + 143 aspectClass.getSuperclass().getName() + "]"); 144 } 145 146 AjType<?> ajType = AjTypeSystem.getAjType(aspectClass); 147 if (!ajType.isAspect()) { 148 throw new NotAnAtAspectException(aspectClass); 149 } 150 if (ajType.getPerClause().getKind() == PerClauseKind.PERCFLOW) { 151 throw new AopConfigException(aspectClass.getName() + " uses percflow instantiation model: " + 152 "This is not supported in Spring AOP."); 153 } 154 if (ajType.getPerClause().getKind() == PerClauseKind.PERCFLOWBELOW) { 155 throw new AopConfigException(aspectClass.getName() + " uses percflowbelow instantiation model: " + 156 "This is not supported in Spring AOP."); 157 } 158 } 159 160 /** 161 * The pointcut and advice annotations both have an "argNames" member which contains a 162 * comma-separated list of the argument names. We use this (if non-empty) to build the 163 * formal parameters for the pointcut. 164 */ createPointcutExpression( Method annotatedMethod, Class declarationScope, String[] pointcutParameterNames)165 protected AspectJExpressionPointcut createPointcutExpression( 166 Method annotatedMethod, Class declarationScope, String[] pointcutParameterNames) { 167 168 Class<?> [] pointcutParameterTypes = new Class<?>[0]; 169 if (pointcutParameterNames != null) { 170 pointcutParameterTypes = extractPointcutParameterTypes(pointcutParameterNames,annotatedMethod); 171 } 172 173 AspectJExpressionPointcut ajexp = 174 new AspectJExpressionPointcut(declarationScope,pointcutParameterNames,pointcutParameterTypes); 175 ajexp.setLocation(annotatedMethod.toString()); 176 return ajexp; 177 } 178 179 /** 180 * Create the pointcut parameters needed by aspectj based on the given argument names 181 * and the argument types that are available from the adviceMethod. Needs to take into 182 * account (ignore) any JoinPoint based arguments as these are not pointcut context but 183 * rather part of the advice execution context (thisJoinPoint, thisJoinPointStaticPart) 184 */ extractPointcutParameterTypes(String[] argNames, Method adviceMethod)185 private Class<?>[] extractPointcutParameterTypes(String[] argNames, Method adviceMethod) { 186 Class<?>[] ret = new Class<?>[argNames.length]; 187 Class<?>[] paramTypes = adviceMethod.getParameterTypes(); 188 if (argNames.length > paramTypes.length) { 189 throw new IllegalStateException("Expecting at least " + argNames.length + 190 " arguments in the advice declaration, but only found " + paramTypes.length); 191 } 192 // Make the simplifying assumption for now that all of the JoinPoint based arguments 193 // come first in the advice declaration. 194 int typeOffset = paramTypes.length - argNames.length; 195 for (int i = 0; i < ret.length; i++) { 196 ret[i] = paramTypes[i + typeOffset]; 197 } 198 return ret; 199 } 200 201 202 protected enum AspectJAnnotationType { 203 AtPointcut, 204 AtBefore, 205 AtAfter, 206 AtAfterReturning, 207 AtAfterThrowing, 208 AtAround 209 } 210 211 212 /** 213 * Class modelling an AspectJ annotation, exposing its type enumeration and 214 * pointcut String. 215 */ 216 protected static class AspectJAnnotation<A extends Annotation> { 217 218 private static final String[] EXPRESSION_PROPERTIES = new String[] {"value", "pointcut"}; 219 220 private static Map<Class, AspectJAnnotationType> annotationTypes = 221 new HashMap<Class, AspectJAnnotationType>(); 222 223 static { annotationTypes.put(Pointcut.class,AspectJAnnotationType.AtPointcut)224 annotationTypes.put(Pointcut.class,AspectJAnnotationType.AtPointcut); annotationTypes.put(After.class,AspectJAnnotationType.AtAfter)225 annotationTypes.put(After.class,AspectJAnnotationType.AtAfter); annotationTypes.put(AfterReturning.class,AspectJAnnotationType.AtAfterReturning)226 annotationTypes.put(AfterReturning.class,AspectJAnnotationType.AtAfterReturning); annotationTypes.put(AfterThrowing.class,AspectJAnnotationType.AtAfterThrowing)227 annotationTypes.put(AfterThrowing.class,AspectJAnnotationType.AtAfterThrowing); annotationTypes.put(Around.class,AspectJAnnotationType.AtAround)228 annotationTypes.put(Around.class,AspectJAnnotationType.AtAround); annotationTypes.put(Before.class,AspectJAnnotationType.AtBefore)229 annotationTypes.put(Before.class,AspectJAnnotationType.AtBefore); 230 } 231 232 private final A annotation; 233 234 private final AspectJAnnotationType annotationType; 235 236 private final String pointcutExpression; 237 238 private final String argumentNames; 239 AspectJAnnotation(A annotation)240 public AspectJAnnotation(A annotation) { 241 this.annotation = annotation; 242 this.annotationType = determineAnnotationType(annotation); 243 // We know these methods exist with the same name on each object, 244 // but need to invoke them reflectively as there isn't a common interface. 245 try { 246 this.pointcutExpression = resolveExpression(annotation); 247 this.argumentNames = (String) annotation.getClass().getMethod("argNames").invoke(annotation); 248 } 249 catch (Exception ex) { 250 throw new IllegalArgumentException(annotation + " cannot be an AspectJ annotation", ex); 251 } 252 } 253 determineAnnotationType(A annotation)254 private AspectJAnnotationType determineAnnotationType(A annotation) { 255 for (Class type : annotationTypes.keySet()) { 256 if (type.isInstance(annotation)) { 257 return annotationTypes.get(type); 258 } 259 } 260 throw new IllegalStateException("Unknown annotation type: " + annotation.toString()); 261 } 262 resolveExpression(A annotation)263 private String resolveExpression(A annotation) throws Exception { 264 String expression = null; 265 for (String methodName : EXPRESSION_PROPERTIES) { 266 Method method; 267 try { 268 method = annotation.getClass().getDeclaredMethod(methodName); 269 } 270 catch (NoSuchMethodException ex) { 271 method = null; 272 } 273 if (method != null) { 274 String candidate = (String) method.invoke(annotation); 275 if (StringUtils.hasText(candidate)) { 276 expression = candidate; 277 } 278 } 279 } 280 return expression; 281 } 282 getAnnotationType()283 public AspectJAnnotationType getAnnotationType() { 284 return this.annotationType; 285 } 286 getAnnotation()287 public A getAnnotation() { 288 return this.annotation; 289 } 290 getPointcutExpression()291 public String getPointcutExpression() { 292 return this.pointcutExpression; 293 } 294 getArgumentNames()295 public String getArgumentNames() { 296 return this.argumentNames; 297 } 298 299 @Override toString()300 public String toString() { 301 return this.annotation.toString(); 302 } 303 } 304 305 306 /** 307 * ParameterNameDiscoverer implementation that analyzes the arg names 308 * specified at the AspectJ annotation level. 309 */ 310 private static class AspectJAnnotationParameterNameDiscoverer implements ParameterNameDiscoverer { 311 getParameterNames(Method method)312 public String[] getParameterNames(Method method) { 313 if (method.getParameterTypes().length == 0) { 314 return new String[0]; 315 } 316 AspectJAnnotation annotation = findAspectJAnnotationOnMethod(method); 317 if (annotation == null) { 318 return null; 319 } 320 StringTokenizer strTok = new StringTokenizer(annotation.getArgumentNames(), ","); 321 if (strTok.countTokens() > 0) { 322 String[] names = new String[strTok.countTokens()]; 323 for (int i = 0; i < names.length; i++) { 324 names[i] = strTok.nextToken(); 325 } 326 return names; 327 } 328 else { 329 return null; 330 } 331 } 332 getParameterNames(Constructor ctor)333 public String[] getParameterNames(Constructor ctor) { 334 throw new UnsupportedOperationException("Spring AOP cannot handle constructor advice"); 335 } 336 } 337 338 } 339