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.util.StringUtils; 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 protected static AspectJAnnotation<?> findAspectJAnnotationOnMethod(Method method) { 132 for (Class<?> clazz : ASPECTJ_ANNOTATION_CLASSES) { 133 AspectJAnnotation<?> foundAnnotation = findAnnotation(method, (Class<Annotation>) clazz); 134 if (foundAnnotation != null) { 135 return foundAnnotation; 136 } 137 } 138 return null; 139 } 140 141 private static <A extends Annotation> AspectJAnnotation<A> findAnnotation(Method method, Class<A> toLookFor) { 142 A result = AnnotationUtils.findAnnotation(method, toLookFor); 143 if (result != null) { 144 return new AspectJAnnotation<A>(result); 145 } 146 else { 147 return null; 148 } 149 } 150 151 152 /** 153 * Enum for AspectJ annotation types. 154 * @see AspectJAnnotation#getAnnotationType() 155 */ 156 protected enum AspectJAnnotationType { 157 158 AtPointcut, AtAround, AtBefore, AtAfter, AtAfterReturning, AtAfterThrowing 159 } 160 161 162 /** 163 * Class modelling an AspectJ annotation, exposing its type enumeration and 164 * pointcut String. 165 */ 166 protected static class AspectJAnnotation<A extends Annotation> { 167 168 private static final String[] EXPRESSION_ATTRIBUTES = new String[] {"pointcut", "value"}; 169 170 private static Map<Class<?>, AspectJAnnotationType> annotationTypeMap = 171 new HashMap<Class<?>, AspectJAnnotationType>(8); 172 173 static { 174 annotationTypeMap.put(Pointcut.class, AspectJAnnotationType.AtPointcut); 175 annotationTypeMap.put(Around.class, AspectJAnnotationType.AtAround); 176 annotationTypeMap.put(Before.class, AspectJAnnotationType.AtBefore); 177 annotationTypeMap.put(After.class, AspectJAnnotationType.AtAfter); 178 annotationTypeMap.put(AfterReturning.class, AspectJAnnotationType.AtAfterReturning); 179 annotationTypeMap.put(AfterThrowing.class, AspectJAnnotationType.AtAfterThrowing); 180 } 181 182 private final A annotation; 183 184 private final AspectJAnnotationType annotationType; 185 186 private final String pointcutExpression; 187 188 private final String argumentNames; 189 190 public AspectJAnnotation(A annotation) { 191 this.annotation = annotation; 192 this.annotationType = determineAnnotationType(annotation); 193 try { 194 this.pointcutExpression = resolveExpression(annotation); 195 this.argumentNames = (String) AnnotationUtils.getValue(annotation, "argNames"); 196 } 197 catch (Exception ex) { 198 throw new IllegalArgumentException(annotation + " is not a valid AspectJ annotation", ex); 199 } 200 } 201 202 private AspectJAnnotationType determineAnnotationType(A annotation) { 203 AspectJAnnotationType type = annotationTypeMap.get(annotation.annotationType()); 204 if (type != null) { 205 return type; 206 } 207 throw new IllegalStateException("Unknown annotation type: " + annotation); 208 } 209 210 private String resolveExpression(A annotation) { 211 for (String attributeName : EXPRESSION_ATTRIBUTES) { 212 String candidate = (String) AnnotationUtils.getValue(annotation, attributeName); 213 if (StringUtils.hasText(candidate)) { 214 return candidate; 215 } 216 } 217 throw new IllegalStateException("Failed to resolve expression: " + annotation); 218 } 219 220 public AspectJAnnotationType getAnnotationType() { 221 return this.annotationType; 222 } 223 224 public A getAnnotation() { 225 return this.annotation; 226 } 227 228 public String getPointcutExpression() { 229 return this.pointcutExpression; 230 } 231 232 public String getArgumentNames() { 233 return this.argumentNames; 234 } 235 236 @Override 237 public String toString() { 238 return this.annotation.toString(); 239 } 240 } 241 242 243 /** 244 * ParameterNameDiscoverer implementation that analyzes the arg names 245 * specified at the AspectJ annotation level. 246 */ 247 private static class AspectJAnnotationParameterNameDiscoverer implements ParameterNameDiscoverer { 248 249 @Override 250 public String[] getParameterNames(Method method) { 251 if (method.getParameterTypes().length == 0) { 252 return new String[0]; 253 } 254 AspectJAnnotation<?> annotation = findAspectJAnnotationOnMethod(method); 255 if (annotation == null) { 256 return null; 257 } 258 StringTokenizer nameTokens = new StringTokenizer(annotation.getArgumentNames(), ","); 259 if (nameTokens.countTokens() > 0) { 260 String[] names = new String[nameTokens.countTokens()]; 261 for (int i = 0; i < names.length; i++) { 262 names[i] = nameTokens.nextToken(); 263 } 264 return names; 265 } 266 else { 267 return null; 268 } 269 } 270 271 @Override 272 public String[] getParameterNames(Constructor<?> ctor) { 273 throw new UnsupportedOperationException("Spring AOP cannot handle constructor advice"); 274 } 275 } 276 277}