前言

Aspectj是面向切面编程中的java实现类库,Spring可以更方便的集成aspectj. SpringAOP的底层是基于代理模式来实现的。默认情况下有两种实现策略: 一是基于JDK的动态代理,二是基于CGLIB字节码生产库来实现。

Spring AOP从类库层面集成了Cglib与AOP联盟(org.aopalliance),如果要使用aspectj.需要引入aspectjweaver.jar,maven依赖如下:

  1. <dependency>
  2. <groupId>org.aspectj</groupId>
  3. <artifactId>aspectjweaver</artifactId>
  4. <version>1.9.5</version>
  5. </dependency>

Spring的AOP设计

开启AspectJ定义切面

一是采用@Aspectj的注解,二是基于XML的配置方式。推荐使用注解风格来定义切面。

如通过注解开启aspectj

  1. @Configuration
  2. @EnableAspectJAutoProxy
  3. public class AppConfig {
  4. }

通过xml配置开启:

aop:aspectj-autoproxy/

添加@EnableAspectJAutoProxy<aop:aspectj-autoproxy/>后会隐式注册如下BeanPostProcessor到容器中:

AnnotationAwareAspectJAutoProxyCreator

该类在Spring中的类图如下:
AnnotationAwareAspectJAutoProxyCreator.png

可以看到该类的作用主要与创建代理有关,也就是说启用aspect后,都会作用到相应的bean上,并给对应的bean生成对应的代理,如果启用了proxyTargetClass则强制生成cglib代理。

AnnotationAwareAspectJAutoProxyCreatorpostProcessBeforeInstantiation方法wrapIfNecessary方法在容器的Refresh阶段会被调用,因此可以容器启动阶段对满足条件的bean调用createProxy方法,最后返回的bean实例即为被代理后的bean.

AspectJ使用

Aspect中的常用概念

  • Aspect(切面):一个模块化的关注点,并可用于横切多个类,例如日志记录,事物处理,异常捕获等。
  • JoinPoint(连接点):执行程序的切入点,表示一次方法执行。
  • Pointcut(横切点): 表示匹配连接点的前置条件,并关联到相应的Advice上。
  • Advice(通知): 英文直意为通知,建议等,我理解为在调用目标方法时,需要做某些相关的操作,可以是前置操作,后置操作,这些操作需要告知目标对象。有的翻译为增强处理,也可行,重在理解即可。

支持的横切点语法

Spring AOP supports the following AspectJ pointcut designators (PCD) for use in pointcut expressions:

execution: For matching method execution join points. This is the primary pointcut designator to use when working with Spring AOP.

within: Limits matching to join points within certain types (the execution of a method declared within a matching type when using Spring AOP).

this: Limits matching to join points (the execution of methods when using Spring AOP) where the bean reference (Spring AOP proxy) is an instance of the given type.

target: Limits matching to join points (the execution of methods when using Spring AOP) where the target object (application object being proxied) is an instance of the given type.

args: Limits matching to join points (the execution of methods when using Spring AOP) where the arguments are instances of the given types.

@target: Limits matching to join points (the execution of methods when using Spring AOP) where the class of the executing object has an annotation of the given type.

@args: Limits matching to join points (the execution of methods when using Spring AOP) where the runtime type of the actual arguments passed have annotations of the given types.

@within: Limits matching to join points within types that have the given annotation (the execution of methods declared in types with the given annotation when using Spring AOP).

@annotation: Limits matching to join points where the subject of the join point (the method being executed in Spring AOP) has the given annotation.

常见示例

Combining Pointcut Expressions

  1. @Pointcut("execution(public * *(..))")
  2. private void anyPublicOperation() {}
  3. @Pointcut("within(com.xyz.someapp.trading..*)")
  4. private void inTrading() {}
  5. @Pointcut("anyPublicOperation() && inTrading()")
  6. private void tradingOperation() {}

Combining Pointcut Expressions
  1. @Aspect
  2. public class SystemArchitecture {
  3. /**
  4. * A join point is in the web layer if the method is defined
  5. * in a type in the com.xyz.someapp.web package or any sub-package
  6. * under that.
  7. */
  8. @Pointcut("within(com.xyz.someapp.web..*)")
  9. public void inWebLayer() {}
  10. /**
  11. * A join point is in the service layer if the method is defined
  12. * in a type in the com.xyz.someapp.service package or any sub-package
  13. * under that.
  14. */
  15. @Pointcut("within(com.xyz.someapp.service..*)")
  16. public void inServiceLayer() {}
  17. /**
  18. * A join point is in the data access layer if the method is defined
  19. * in a type in the com.xyz.someapp.dao package or any sub-package
  20. * under that.
  21. */
  22. @Pointcut("within(com.xyz.someapp.dao..*)")
  23. public void inDataAccessLayer() {}
  24. /**
  25. * A business service is the execution of any method defined on a service
  26. * interface. This definition assumes that interfaces are placed in the
  27. * "service" package, and that implementation types are in sub-packages.
  28. *
  29. * If you group service interfaces by functional area (for example,
  30. * in packages com.xyz.someapp.abc.service and com.xyz.someapp.def.service) then
  31. * the pointcut expression "execution(* com.xyz.someapp..service.*.*(..))"
  32. * could be used instead.
  33. *
  34. * Alternatively, you can write the expression using the 'bean'
  35. * PCD, like so "bean(*Service)". (This assumes that you have
  36. * named your Spring service beans in a consistent fashion.)
  37. */
  38. @Pointcut("execution(* com.xyz.someapp..service.*.*(..))")
  39. public void businessService() {}
  40. /**
  41. * A data access operation is the execution of any method defined on a
  42. * dao interface. This definition assumes that interfaces are placed in the
  43. * "dao" package, and that implementation types are in sub-packages.
  44. */
  45. @Pointcut("execution(* com.xyz.someapp.dao.*.*(..))")
  46. public void dataAccessOperation() {}
  47. }

exection 表达式

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)

throws-pattern?)

Declaring Advice

  1. @Aspect
  2. public class BeforeExample {
  3. @Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
  4. public void doAccessCheck() {
  5. // ...
  6. }
  7. @Before("execution(* com.xyz.myapp.dao.*.*(..))")
  8. public void doAccessCheck() {
  9. // ...
  10. }
  11. @AfterReturning("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
  12. public void doAccessCheck() {
  13. // ...
  14. }
  15. @Around("com.xyz.myapp.SystemArchitecture.businessService()")
  16. public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
  17. // start stopwatch
  18. Object retVal = pjp.proceed();
  19. // stop stopwatch
  20. return retVal;
  21. }
  22. }

编程API使用AspectJ

  1. @Test
  2. public void testAspect() {
  3. HelloSpring hs = new HelloSpring();
  4. // create a factory that can generate a proxy for the given target object
  5. AspectJProxyFactory factory = new AspectJProxyFactory(hs);
  6. // add an aspect, the class must be an @AspectJ aspect
  7. // you can call this as many times as you need with different aspects
  8. // you can also add existing aspect instances, the type of the object supplied must be an @AspectJ aspect
  9. factory.addAspect(BeforeAspect.class);
  10. factory.addAspect(AfterAspect.class);
  11. factory.addAspect(AroundAspect.class);
  12. // now get the proxy object...
  13. HelloSpring proxy = factory.getProxy();
  14. proxy.show();
  15. }

获取被代理后的实例

getBean() -> doCreateBean() -> beanPostProcessor -> AbstractAutoProxyCreator#getEarlyBeanReference -> ProxyFactory.getProxy

-> createAopProxy().getProxy(classLoader)

ProxyFactory的类图如下:(以AspectJ为例)

分析如下:

传入到构造方法中的bean或target对象会通过ProxyCreatorSupport记录对象的所有interface并把bean或target包装为SingletonTargetSource.

factory.addAspect的切面会记录到Creator的List<Advisor> advisors属性里。

调用getProxy时,则执行getAopProxyFactory().createAopProxy(ProxyCreatorSupport)

最后通过返回的AopProxy.getProxy()拿到被代理后的对象。
AspectJProxyFactory.png

获取代理Bean背后的原始Bean

  1. Class<?> targetClass = AopUtils.getTargetClass(bean);

查找@Aspect

  1. AnnotationAwareAspectJAutoProxyCreator#findCandidateAdvisors
  2. protected List<Advisor> findCandidateAdvisors() {
  3. // Add all the Spring advisors found according to superclass rules.
  4. List<Advisor> advisors = super.findCandidateAdvisors();
  5. // Build Advisors for all AspectJ aspects in the bean factory.
  6. if (this.aspectJAdvisorsBuilder != null) {
  7. advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
  8. }
  9. return advisors;
  10. }
  11. BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors
  12. AbstractAspectJAdvisorFactory#hasAspectAnnotation
  13. /**
  14. * We consider something to be an AspectJ aspect suitable for use by the Spring AOP system
  15. * if it has the @Aspect annotation, and was not compiled by ajc. The reason for this latter test
  16. * is that aspects written in the code-style (AspectJ language) also have the annotation present
  17. * when compiled by ajc with the -1.5 flag, yet they cannot be consumed by Spring AOP.
  18. */
  19. @Override
  20. public boolean isAspect(Class<?> clazz) {
  21. return (hasAspectAnnotation(clazz) && !compiledByAjc(clazz));
  22. }
  23. private boolean hasAspectAnnotation(Class<?> clazz) {
  24. return (AnnotationUtils.findAnnotation(clazz, Aspect.class) != null);
  25. }
  26. for (String beanName : beanNames) {
  27. if (!isEligibleBean(beanName)) {
  28. continue;
  29. }
  30. // We must be careful not to instantiate beans eagerly as in this case they
  31. // would be cached by the Spring container but would not have been weaved.
  32. Class<?> beanType = this.beanFactory.getType(beanName);
  33. if (beanType == null) {
  34. continue;
  35. }
  36. if (this.advisorFactory.isAspect(beanType)) {
  37. aspectNames.add(beanName);
  38. AspectMetadata amd = new AspectMetadata(beanType, beanName);
  39. if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
  40. MetadataAwareAspectInstanceFactory factory =
  41. new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
  42. List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
  43. if (this.beanFactory.isSingleton(beanName)) {
  44. this.advisorsCache.put(beanName, classAdvisors);
  45. }
  46. else {
  47. this.aspectFactoryCache.put(beanName, factory);
  48. }
  49. advisors.addAll(classAdvisors);
  50. }
  51. }

AOP联盟与代理

如果配置了增强Advisor(切面),Spring AOP采用AOP联盟提供的API(如MethodInterceptor)来封装代理的调用流程,主要是把切面封装为一个调用链,方法调用链最终执行的还是底层的代理逻辑。

DefaultAopProxyFactory -> createAopProxy -> ObjenesisCglibAopProxy or JdkDynamicAopProxy -> getProxy -> invoke -> MethodInterceptor -> MethodInvocation -> Method.invoke

如果没有任何切面,则执行JDK动态代理或CGLIB代理的原始调用逻辑。

JDK代理执行InvocationHandler#invoke方法,代码如下:

  1. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  2. // Check whether we have any advice. If we don't, we can fallback on direct
  3. // reflective invocation of the target, and avoid creating a MethodInvocation.
  4. if (chain.isEmpty()) {
  5. // We can skip creating a MethodInvocation: just invoke the target directly
  6. // Note that the final invoker must be an InvokerInterceptor so we know it does
  7. // nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
  8. Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
  9. retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
  10. }
  11. else {
  12. // We need to create a method invocation...
  13. MethodInvocation invocation =
  14. new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
  15. // Proceed to the joinpoint through the interceptor chain.
  16. retVal = invocation.proceed();
  17. }
  18. }

CGlib则执行org.springframework.cglib.proxy.MethodInterceptor#intercept,代码如下:

  1. /**
  2. * General purpose AOP callback. Used when the target is dynamic or when the
  3. * proxy is not frozen.
  4. */
  5. private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable {
  6. private final AdvisedSupport advised;
  7. public DynamicAdvisedInterceptor(AdvisedSupport advised) {
  8. this.advised = advised;
  9. }
  10. @Override
  11. @Nullable
  12. public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
  13. Object oldProxy = null;
  14. boolean setProxyContext = false;
  15. Object target = null;
  16. TargetSource targetSource = this.advised.getTargetSource();
  17. try {
  18. if (this.advised.exposeProxy) {
  19. // Make invocation available if necessary.
  20. oldProxy = AopContext.setCurrentProxy(proxy);
  21. setProxyContext = true;
  22. }
  23. // Get as late as possible to minimize the time we "own" the target, in case it comes from a pool...
  24. target = targetSource.getTarget();
  25. Class<?> targetClass = (target != null ? target.getClass() : null);
  26. List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
  27. Object retVal;
  28. // Check whether we only have one InvokerInterceptor: that is,
  29. // no real advice, but just reflective invocation of the target.
  30. if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
  31. // We can skip creating a MethodInvocation: just invoke the target directly.
  32. // Note that the final invoker must be an InvokerInterceptor, so we know
  33. // it does nothing but a reflective operation on the target, and no hot
  34. // swapping or fancy proxying.
  35. Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
  36. retVal = methodProxy.invoke(target, argsToUse);
  37. }
  38. else {
  39. // We need to create a method invocation...
  40. retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
  41. }
  42. retVal = processReturnType(proxy, target, method, retVal);
  43. return retVal;
  44. }
  45. finally {
  46. if (target != null && !targetSource.isStatic()) {
  47. targetSource.releaseTarget(target);
  48. }
  49. if (setProxyContext) {
  50. // Restore old proxy.
  51. AopContext.setCurrentProxy(oldProxy);
  52. }
  53. }
  54. }
  55. }