001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.aop.aspectj.annotation; 018 019import java.lang.annotation.Annotation; 020import java.lang.reflect.Constructor; 021import java.lang.reflect.Field; 022import java.lang.reflect.Method; 023import java.lang.reflect.Modifier; 024import java.util.HashMap; 025import java.util.Map; 026import java.util.StringTokenizer; 027 028import org.apache.commons.logging.Log; 029import org.apache.commons.logging.LogFactory; 030import org.aspectj.lang.annotation.After; 031import org.aspectj.lang.annotation.AfterReturning; 032import org.aspectj.lang.annotation.AfterThrowing; 033import org.aspectj.lang.annotation.Around; 034import org.aspectj.lang.annotation.Aspect; 035import org.aspectj.lang.annotation.Before; 036import org.aspectj.lang.annotation.Pointcut; 037import org.aspectj.lang.reflect.AjType; 038import org.aspectj.lang.reflect.AjTypeSystem; 039import org.aspectj.lang.reflect.PerClauseKind; 040 041import org.springframework.aop.framework.AopConfigException; 042import org.springframework.core.ParameterNameDiscoverer; 043import org.springframework.core.annotation.AnnotationUtils; 044import org.springframework.lang.Nullable; 045 046/** 047 * Abstract base class for factories that can create Spring AOP Advisors 048 * given AspectJ classes from classes honoring the AspectJ 5 annotation syntax. 049 * 050 * <p>This class handles annotation parsing and validation functionality. 051 * It does not actually generate Spring AOP Advisors, which is deferred to subclasses. 052 * 053 * @author Rod Johnson 054 * @author Adrian Colyer 055 * @author Juergen Hoeller 056 * @since 2.0 057 */ 058public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFactory { 059 060 private static final String AJC_MAGIC = "ajc$"; 061 062 private static final Class<?>[] ASPECTJ_ANNOTATION_CLASSES = new Class<?>[] { 063 Pointcut.class, Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class}; 064 065 066 /** Logger available to subclasses. */ 067 protected final Log logger = LogFactory.getLog(getClass()); 068 069 protected final ParameterNameDiscoverer parameterNameDiscoverer = new AspectJAnnotationParameterNameDiscoverer(); 070 071 072 /** 073 * We consider something to be an AspectJ aspect suitable for use by the Spring AOP system 074 * if it has the @Aspect annotation, and was not compiled by ajc. The reason for this latter test 075 * is that aspects written in the code-style (AspectJ language) also have the annotation present 076 * when compiled by ajc with the -1.5 flag, yet they cannot be consumed by Spring AOP. 077 */ 078 @Override 079 public boolean isAspect(Class<?> clazz) { 080 return (hasAspectAnnotation(clazz) && !compiledByAjc(clazz)); 081 } 082 083 private boolean hasAspectAnnotation(Class<?> clazz) { 084 return (AnnotationUtils.findAnnotation(clazz, Aspect.class) != null); 085 } 086 087 /** 088 * We need to detect this as "code-style" AspectJ aspects should not be 089 * interpreted by Spring AOP. 090 */ 091 private boolean compiledByAjc(Class<?> clazz) { 092 // The AJTypeSystem goes to great lengths to provide a uniform appearance between code-style and 093 // annotation-style aspects. Therefore there is no 'clean' way to tell them apart. Here we rely on 094 // an implementation detail of the AspectJ compiler. 095 for (Field field : clazz.getDeclaredFields()) { 096 if (field.getName().startsWith(AJC_MAGIC)) { 097 return true; 098 } 099 } 100 return false; 101 } 102 103 @Override 104 public void validate(Class<?> aspectClass) throws AopConfigException { 105 // If the parent has the annotation and isn't abstract it's an error 106 if (aspectClass.getSuperclass().getAnnotation(Aspect.class) != null && 107 !Modifier.isAbstract(aspectClass.getSuperclass().getModifiers())) { 108 throw new AopConfigException("[" + aspectClass.getName() + "] cannot extend concrete aspect [" + 109 aspectClass.getSuperclass().getName() + "]"); 110 } 111 112 AjType<?> ajType = AjTypeSystem.getAjType(aspectClass); 113 if (!ajType.isAspect()) { 114 throw new NotAnAtAspectException(aspectClass); 115 } 116 if (ajType.getPerClause().getKind() == PerClauseKind.PERCFLOW) { 117 throw new AopConfigException(aspectClass.getName() + " uses percflow instantiation model: " + 118 "This is not supported in Spring AOP."); 119 } 120 if (ajType.getPerClause().getKind() == PerClauseKind.PERCFLOWBELOW) { 121 throw new AopConfigException(aspectClass.getName() + " uses percflowbelow instantiation model: " + 122 "This is not supported in Spring AOP."); 123 } 124 } 125 126 /** 127 * Find and return the first AspectJ annotation on the given method 128 * (there <i>should</i> only be one anyway...). 129 */ 130 @SuppressWarnings("unchecked") 131 @Nullable 132 protected static AspectJAnnotation<?> findAspectJAnnotationOnMethod(Method method) { 133 for (Class<?> clazz : ASPECTJ_ANNOTATION_CLASSES) { 134 AspectJAnnotation<?> foundAnnotation = findAnnotation(method, (Class<Annotation>) clazz); 135 if (foundAnnotation != null) { 136 return foundAnnotation; 137 } 138 } 139 return null; 140 } 141 142 @Nullable 143 private static <A extends Annotation> AspectJAnnotation<A> findAnnotation(Method method, Class<A> toLookFor) { 144 A result = AnnotationUtils.findAnnotation(method, toLookFor); 145 if (result != null) { 146 return new AspectJAnnotation<>(result); 147 } 148 else { 149 return null; 150 } 151 } 152 153 154 /** 155 * Enum for AspectJ annotation types. 156 * @see AspectJAnnotation#getAnnotationType() 157 */ 158 protected enum AspectJAnnotationType { 159 160 AtPointcut, AtAround, AtBefore, AtAfter, AtAfterReturning, AtAfterThrowing 161 } 162 163 164 /** 165 * Class modelling an AspectJ annotation, exposing its type enumeration and 166 * pointcut String. 167 * @param <A> the annotation type 168 */ 169 protected static class AspectJAnnotation<A extends Annotation> { 170 171 private static final String[] EXPRESSION_ATTRIBUTES = new String[] {"pointcut", "value"}; 172 173 private static Map<Class<?>, AspectJAnnotationType> annotationTypeMap = new HashMap<>(8); 174 175 static { 176 annotationTypeMap.put(Pointcut.class, AspectJAnnotationType.AtPointcut); 177 annotationTypeMap.put(Around.class, AspectJAnnotationType.AtAround); 178 annotationTypeMap.put(Before.class, AspectJAnnotationType.AtBefore); 179 annotationTypeMap.put(After.class, AspectJAnnotationType.AtAfter); 180 annotationTypeMap.put(AfterReturning.class, AspectJAnnotationType.AtAfterReturning); 181 annotationTypeMap.put(AfterThrowing.class, AspectJAnnotationType.AtAfterThrowing); 182 } 183 184 private final A annotation; 185 186 private final AspectJAnnotationType annotationType; 187 188 private final String pointcutExpression; 189 190 private final String argumentNames; 191 192 public AspectJAnnotation(A annotation) { 193 this.annotation = annotation; 194 this.annotationType = determineAnnotationType(annotation); 195 try { 196 this.pointcutExpression = resolveExpression(annotation); 197 Object argNames = AnnotationUtils.getValue(annotation, "argNames"); 198 this.argumentNames = (argNames instanceof String ? (String) argNames : ""); 199 } 200 catch (Exception ex) { 201 throw new IllegalArgumentException(annotation + " is not a valid AspectJ annotation", ex); 202 } 203 } 204 205 private AspectJAnnotationType determineAnnotationType(A annotation) { 206 AspectJAnnotationType type = annotationTypeMap.get(annotation.annotationType()); 207 if (type != null) { 208 return type; 209 } 210 throw new IllegalStateException("Unknown annotation type: " + annotation); 211 } 212 213 private String resolveExpression(A annotation) { 214 for (String attributeName : EXPRESSION_ATTRIBUTES) { 215 Object val = AnnotationUtils.getValue(annotation, attributeName); 216 if (val instanceof String) { 217 String str = (String) val; 218 if (!str.isEmpty()) { 219 return str; 220 } 221 } 222 } 223 throw new IllegalStateException("Failed to resolve expression: " + annotation); 224 } 225 226 public AspectJAnnotationType getAnnotationType() { 227 return this.annotationType; 228 } 229 230 public A getAnnotation() { 231 return this.annotation; 232 } 233 234 public String getPointcutExpression() { 235 return this.pointcutExpression; 236 } 237 238 public String getArgumentNames() { 239 return this.argumentNames; 240 } 241 242 @Override 243 public String toString() { 244 return this.annotation.toString(); 245 } 246 } 247 248 249 /** 250 * ParameterNameDiscoverer implementation that analyzes the arg names 251 * specified at the AspectJ annotation level. 252 */ 253 private static class AspectJAnnotationParameterNameDiscoverer implements ParameterNameDiscoverer { 254 255 @Override 256 @Nullable 257 public String[] getParameterNames(Method method) { 258 if (method.getParameterCount() == 0) { 259 return new String[0]; 260 } 261 AspectJAnnotation<?> annotation = findAspectJAnnotationOnMethod(method); 262 if (annotation == null) { 263 return null; 264 } 265 StringTokenizer nameTokens = new StringTokenizer(annotation.getArgumentNames(), ","); 266 if (nameTokens.countTokens() > 0) { 267 String[] names = new String[nameTokens.countTokens()]; 268 for (int i = 0; i < names.length; i++) { 269 names[i] = nameTokens.nextToken(); 270 } 271 return names; 272 } 273 else { 274 return null; 275 } 276 } 277 278 @Override 279 @Nullable 280 public String[] getParameterNames(Constructor<?> ctor) { 281 throw new UnsupportedOperationException("Spring AOP cannot handle constructor advice"); 282 } 283 } 284 285}