001/* 002 * Copyright 2002-2020 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.io.Serializable; 020import java.lang.annotation.Annotation; 021import java.lang.reflect.Field; 022import java.lang.reflect.Method; 023import java.util.ArrayList; 024import java.util.Comparator; 025import java.util.List; 026 027import org.aopalliance.aop.Advice; 028import org.aspectj.lang.annotation.After; 029import org.aspectj.lang.annotation.AfterReturning; 030import org.aspectj.lang.annotation.AfterThrowing; 031import org.aspectj.lang.annotation.Around; 032import org.aspectj.lang.annotation.Before; 033import org.aspectj.lang.annotation.DeclareParents; 034import org.aspectj.lang.annotation.Pointcut; 035 036import org.springframework.aop.Advisor; 037import org.springframework.aop.MethodBeforeAdvice; 038import org.springframework.aop.aspectj.AbstractAspectJAdvice; 039import org.springframework.aop.aspectj.AspectJAfterAdvice; 040import org.springframework.aop.aspectj.AspectJAfterReturningAdvice; 041import org.springframework.aop.aspectj.AspectJAfterThrowingAdvice; 042import org.springframework.aop.aspectj.AspectJAroundAdvice; 043import org.springframework.aop.aspectj.AspectJExpressionPointcut; 044import org.springframework.aop.aspectj.AspectJMethodBeforeAdvice; 045import org.springframework.aop.aspectj.DeclareParentsAdvisor; 046import org.springframework.aop.framework.AopConfigException; 047import org.springframework.aop.support.DefaultPointcutAdvisor; 048import org.springframework.beans.factory.BeanFactory; 049import org.springframework.core.annotation.AnnotationUtils; 050import org.springframework.core.convert.converter.Converter; 051import org.springframework.core.convert.converter.ConvertingComparator; 052import org.springframework.lang.Nullable; 053import org.springframework.util.ReflectionUtils; 054import org.springframework.util.StringUtils; 055import org.springframework.util.comparator.InstanceComparator; 056 057/** 058 * Factory that can create Spring AOP Advisors given AspectJ classes from 059 * classes honoring AspectJ's annotation syntax, using reflection to invoke the 060 * corresponding advice methods. 061 * 062 * @author Rod Johnson 063 * @author Adrian Colyer 064 * @author Juergen Hoeller 065 * @author Ramnivas Laddad 066 * @author Phillip Webb 067 * @author Sam Brannen 068 * @since 2.0 069 */ 070@SuppressWarnings("serial") 071public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFactory implements Serializable { 072 073 private static final Comparator<Method> METHOD_COMPARATOR; 074 075 static { 076 // Note: although @After is ordered before @AfterReturning and @AfterThrowing, 077 // an @After advice method will actually be invoked after @AfterReturning and 078 // @AfterThrowing methods due to the fact that AspectJAfterAdvice.invoke(MethodInvocation) 079 // invokes proceed() in a `try` block and only invokes the @After advice method 080 // in a corresponding `finally` block. 081 Comparator<Method> adviceKindComparator = new ConvertingComparator<>( 082 new InstanceComparator<>( 083 Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class), 084 (Converter<Method, Annotation>) method -> { 085 AspectJAnnotation<?> ann = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(method); 086 return (ann != null ? ann.getAnnotation() : null); 087 }); 088 Comparator<Method> methodNameComparator = new ConvertingComparator<>(Method::getName); 089 METHOD_COMPARATOR = adviceKindComparator.thenComparing(methodNameComparator); 090 } 091 092 093 @Nullable 094 private final BeanFactory beanFactory; 095 096 097 /** 098 * Create a new {@code ReflectiveAspectJAdvisorFactory}. 099 */ 100 public ReflectiveAspectJAdvisorFactory() { 101 this(null); 102 } 103 104 /** 105 * Create a new {@code ReflectiveAspectJAdvisorFactory}, propagating the given 106 * {@link BeanFactory} to the created {@link AspectJExpressionPointcut} instances, 107 * for bean pointcut handling as well as consistent {@link ClassLoader} resolution. 108 * @param beanFactory the BeanFactory to propagate (may be {@code null}} 109 * @since 4.3.6 110 * @see AspectJExpressionPointcut#setBeanFactory 111 * @see org.springframework.beans.factory.config.ConfigurableBeanFactory#getBeanClassLoader() 112 */ 113 public ReflectiveAspectJAdvisorFactory(@Nullable BeanFactory beanFactory) { 114 this.beanFactory = beanFactory; 115 } 116 117 118 @Override 119 public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) { 120 Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass(); 121 String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName(); 122 validate(aspectClass); 123 124 // We need to wrap the MetadataAwareAspectInstanceFactory with a decorator 125 // so that it will only instantiate once. 126 MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory = 127 new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory); 128 129 List<Advisor> advisors = new ArrayList<>(); 130 for (Method method : getAdvisorMethods(aspectClass)) { 131 // Prior to Spring Framework 5.2.7, advisors.size() was supplied as the declarationOrderInAspect 132 // to getAdvisor(...) to represent the "current position" in the declared methods list. 133 // However, since Java 7 the "current position" is not valid since the JDK no longer 134 // returns declared methods in the order in which they are declared in the source code. 135 // Thus, we now hard code the declarationOrderInAspect to 0 for all advice methods 136 // discovered via reflection in order to support reliable advice ordering across JVM launches. 137 // Specifically, a value of 0 aligns with the default value used in 138 // AspectJPrecedenceComparator.getAspectDeclarationOrder(Advisor). 139 Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, 0, aspectName); 140 if (advisor != null) { 141 advisors.add(advisor); 142 } 143 } 144 145 // If it's a per target aspect, emit the dummy instantiating aspect. 146 if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) { 147 Advisor instantiationAdvisor = new SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory); 148 advisors.add(0, instantiationAdvisor); 149 } 150 151 // Find introduction fields. 152 for (Field field : aspectClass.getDeclaredFields()) { 153 Advisor advisor = getDeclareParentsAdvisor(field); 154 if (advisor != null) { 155 advisors.add(advisor); 156 } 157 } 158 159 return advisors; 160 } 161 162 private List<Method> getAdvisorMethods(Class<?> aspectClass) { 163 final List<Method> methods = new ArrayList<>(); 164 ReflectionUtils.doWithMethods(aspectClass, method -> { 165 // Exclude pointcuts 166 if (AnnotationUtils.getAnnotation(method, Pointcut.class) == null) { 167 methods.add(method); 168 } 169 }, ReflectionUtils.USER_DECLARED_METHODS); 170 if (methods.size() > 1) { 171 methods.sort(METHOD_COMPARATOR); 172 } 173 return methods; 174 } 175 176 /** 177 * Build a {@link org.springframework.aop.aspectj.DeclareParentsAdvisor} 178 * for the given introduction field. 179 * <p>Resulting Advisors will need to be evaluated for targets. 180 * @param introductionField the field to introspect 181 * @return the Advisor instance, or {@code null} if not an Advisor 182 */ 183 @Nullable 184 private Advisor getDeclareParentsAdvisor(Field introductionField) { 185 DeclareParents declareParents = introductionField.getAnnotation(DeclareParents.class); 186 if (declareParents == null) { 187 // Not an introduction field 188 return null; 189 } 190 191 if (DeclareParents.class == declareParents.defaultImpl()) { 192 throw new IllegalStateException("'defaultImpl' attribute must be set on DeclareParents"); 193 } 194 195 return new DeclareParentsAdvisor( 196 introductionField.getType(), declareParents.value(), declareParents.defaultImpl()); 197 } 198 199 200 @Override 201 @Nullable 202 public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory, 203 int declarationOrderInAspect, String aspectName) { 204 205 validate(aspectInstanceFactory.getAspectMetadata().getAspectClass()); 206 207 AspectJExpressionPointcut expressionPointcut = getPointcut( 208 candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass()); 209 if (expressionPointcut == null) { 210 return null; 211 } 212 213 return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod, 214 this, aspectInstanceFactory, declarationOrderInAspect, aspectName); 215 } 216 217 @Nullable 218 private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) { 219 AspectJAnnotation<?> aspectJAnnotation = 220 AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod); 221 if (aspectJAnnotation == null) { 222 return null; 223 } 224 225 AspectJExpressionPointcut ajexp = 226 new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class<?>[0]); 227 ajexp.setExpression(aspectJAnnotation.getPointcutExpression()); 228 if (this.beanFactory != null) { 229 ajexp.setBeanFactory(this.beanFactory); 230 } 231 return ajexp; 232 } 233 234 235 @Override 236 @Nullable 237 public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut, 238 MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) { 239 240 Class<?> candidateAspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass(); 241 validate(candidateAspectClass); 242 243 AspectJAnnotation<?> aspectJAnnotation = 244 AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod); 245 if (aspectJAnnotation == null) { 246 return null; 247 } 248 249 // If we get here, we know we have an AspectJ method. 250 // Check that it's an AspectJ-annotated class 251 if (!isAspect(candidateAspectClass)) { 252 throw new AopConfigException("Advice must be declared inside an aspect type: " + 253 "Offending method '" + candidateAdviceMethod + "' in class [" + 254 candidateAspectClass.getName() + "]"); 255 } 256 257 if (logger.isDebugEnabled()) { 258 logger.debug("Found AspectJ method: " + candidateAdviceMethod); 259 } 260 261 AbstractAspectJAdvice springAdvice; 262 263 switch (aspectJAnnotation.getAnnotationType()) { 264 case AtPointcut: 265 if (logger.isDebugEnabled()) { 266 logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'"); 267 } 268 return null; 269 case AtAround: 270 springAdvice = new AspectJAroundAdvice( 271 candidateAdviceMethod, expressionPointcut, aspectInstanceFactory); 272 break; 273 case AtBefore: 274 springAdvice = new AspectJMethodBeforeAdvice( 275 candidateAdviceMethod, expressionPointcut, aspectInstanceFactory); 276 break; 277 case AtAfter: 278 springAdvice = new AspectJAfterAdvice( 279 candidateAdviceMethod, expressionPointcut, aspectInstanceFactory); 280 break; 281 case AtAfterReturning: 282 springAdvice = new AspectJAfterReturningAdvice( 283 candidateAdviceMethod, expressionPointcut, aspectInstanceFactory); 284 AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation(); 285 if (StringUtils.hasText(afterReturningAnnotation.returning())) { 286 springAdvice.setReturningName(afterReturningAnnotation.returning()); 287 } 288 break; 289 case AtAfterThrowing: 290 springAdvice = new AspectJAfterThrowingAdvice( 291 candidateAdviceMethod, expressionPointcut, aspectInstanceFactory); 292 AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation(); 293 if (StringUtils.hasText(afterThrowingAnnotation.throwing())) { 294 springAdvice.setThrowingName(afterThrowingAnnotation.throwing()); 295 } 296 break; 297 default: 298 throw new UnsupportedOperationException( 299 "Unsupported advice type on method: " + candidateAdviceMethod); 300 } 301 302 // Now to configure the advice... 303 springAdvice.setAspectName(aspectName); 304 springAdvice.setDeclarationOrder(declarationOrder); 305 String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod); 306 if (argNames != null) { 307 springAdvice.setArgumentNamesFromStringArray(argNames); 308 } 309 springAdvice.calculateArgumentBindings(); 310 311 return springAdvice; 312 } 313 314 315 /** 316 * Synthetic advisor that instantiates the aspect. 317 * Triggered by per-clause pointcut on non-singleton aspect. 318 * The advice has no effect. 319 */ 320 @SuppressWarnings("serial") 321 protected static class SyntheticInstantiationAdvisor extends DefaultPointcutAdvisor { 322 323 public SyntheticInstantiationAdvisor(final MetadataAwareAspectInstanceFactory aif) { 324 super(aif.getAspectMetadata().getPerClausePointcut(), (MethodBeforeAdvice) 325 (method, args, target) -> aif.getAspectInstance()); 326 } 327 } 328 329}