SpringBoot(四)原理剖析:AOP原理


  AOP(Aspect Oriented Programming)是基於切面編程的,可無侵入的在原本功能的切面層添加自定義代碼,一般用於日志收集、權限認證等場景。

AOP基本概念

  通知(Advice): AOP 框架中的增強處理。通知描述了切面何時執行以及如何執行增強處理;

Before :前置通知,在連接點方法前調用;對應Spring中@Before注解;
After :后置通知,在連接點方法后調用;對應Spring中的@After注解;
AfterReturning:返回通知,在連接點方法執行並正常返回后調用,要求連接點方法在執行過程中沒有發生異常;對應Spring中的@AfterReturning注解;
AfterThrowing:異常通知,當連接點方法異常時調用;對應Spring中的@AfterThrowing注解;
Around:環繞通知,它將覆蓋原有方法,但是允許你通過反射調用原有方法;對應Spring中的@Around注解;

  連接點(Join Point): 連接點表示應用執行過程中能夠插入切面的一個點。在 Spring AOP 中,連接點總是方法的調用,可以說目標對象中的方法就是一個連接點;
  切點(Pointcut): 就是連接點的集合;對應Spring中的@Pointcut注解;

目前Spring支持的切點匹配表達式主要有以下幾種:
execution:可以定義到的最小粒度是方法,修飾符,包名,類名,方法名,Spring AOP主要也是使用這個匹配表達式;
within:只能定義到類;例如@Pointcut(within(com.jnu.example.*))
this:當前生成的代理對象的類型匹配;
target:目標對象類型匹配;
annotation:針對注解;
args:只針對參數;

例如: execution (* com.sample.service..*. *(..)) 整個表達式可以分為五個部分:
1、execution()::表達式主體;
2、第一個*號:表示返回類型, *號表示所有的類型;
3、包名:表示需要攔截的包名,包名后面的..,表明com.sample.service包、及其子包;
4、第二個*號:表示類名,*號表示所有的類;
5、*(..):最后這個星號表示方法名,*號表示所有的方法,后面括弧里面表示方法的參數,兩個點表示任何參數; 

  切面(Aspect): 切面是通知和切點的結合;對應Spring中的注解@Aspect修飾的一個類;
  目標對象(Target object):即被代理的對象;    
  代理對象(AOP proxy):包含了目標對象的代碼和增強后的代碼的那個對象;

   自定義注解實例可以參見:注解

@EnableAspectJAutoProxy分析

  在spring-boot-autoconfigure-**.jar中,有個配置類,來控制AOP功能的開啟和關閉:

@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(Advice.class)
    static class AspectJAutoProxyingConfiguration {

        @Configuration(proxyBeanMethods = false)
        @EnableAspectJAutoProxy(proxyTargetClass = false)
        @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false",
                matchIfMissing = false)
        static class JdkDynamicAutoProxyConfiguration {

        }

        @Configuration(proxyBeanMethods = false)
        @EnableAspectJAutoProxy(proxyTargetClass = true)
        @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
                matchIfMissing = true)
        static class CglibAutoProxyConfiguration {

        }

    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnMissingClass("org.aspectj.weaver.Advice")
    @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
            matchIfMissing = true)
    static class ClassProxyingConfiguration {

        ClassProxyingConfiguration(BeanFactory beanFactory) {
            if (beanFactory instanceof BeanDefinitionRegistry) {
                BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
                AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
                AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
            }
        }

    }

}
AopAutoConfiguration
  • 使用@ConditionalOnProperty注解,默認開啟AOP功能;
  • spring.aop.proxy-target-class沒有配置時,默認開啟Cglib動態代理;
  • @EnableAspectJAutoProxy注解,聲明開啟AOP功能。

   @EnableAspectJAutoProxy注解的核心是引入了AspectJAutoProxyRegistrar,然后向容器中注冊一個AnnotationAwareAspectJAutoProxyCreator:

class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

    /**
     * Register, escalate, and configure the AspectJ auto proxy creator based on the value
     * of the @{@link EnableAspectJAutoProxy#proxyTargetClass()} attribute on the importing
     * {@code @Configuration} class.
     */
    @Override
    public void registerBeanDefinitions(
            AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

        AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

        AnnotationAttributes enableAspectJAutoProxy =
                AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
        if (enableAspectJAutoProxy != null) {
            if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
                AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
            }
            if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
                AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
            }
        }
    }

}
registerBeanDefinitions

  此時下面源碼中的第三個方法會進入if代碼塊內,然后返回null:

@Nullable
    public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry) {
        return registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry, null);
    }

    @Nullable
    public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(
            BeanDefinitionRegistry registry, @Nullable Object source) {

        return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
    }

@Nullable
    private static BeanDefinition registerOrEscalateApcAsRequired(
            Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {

        Assert.notNull(registry, "BeanDefinitionRegistry must not be null");

        if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
            BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
            if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
                int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
                int requiredPriority = findPriorityForClass(cls);
                if (currentPriority < requiredPriority) {
                    apcDefinition.setBeanClassName(cls.getName());
                }
            }
            return null;
        }

        RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
        beanDefinition.setSource(source);
        beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
        beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
        return beanDefinition;
    }
AopConfigUtils

 

AnnotationAwareAspectJAutoProxyCreator分析

  在AnnotationAwareAspectJAutoProxyCreator的繼承關系中,有一個AbstractAutoProxyCreator類。該類定義了下面兩個方法,它們分別攔截目標bean創建,和初始化流程:
    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        Object cacheKey = getCacheKey(beanClass, beanName);
        if (beanName == null || !this.targetSourcedBeans.contains(beanName)) {
            if (this.advisedBeans.containsKey(cacheKey)) {
                return null;
            }
            if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
                this.advisedBeans.put(cacheKey, Boolean.FALSE);
                return null;
            }
        }
        // Create proxy here if we have a custom TargetSource.
        // Suppresses unnecessary default instantiation of the target bean:
        // The TargetSource will handle target instances in a custom fashion.
        if (beanName != null) {
            TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
            if (targetSource != null) {
                this.targetSourcedBeans.add(beanName);
                Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
                Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
                this.proxyTypes.put(cacheKey, proxy.getClass());
                return proxy;
            }
        }

        return null;
    }

  //在創建其他bean時,調用該方法,創建代理的bean,從而將AOP方式注入的方法,注入到目標bean中
  @Override
  public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
      if (bean != null) {
          Object cacheKey = getCacheKey(bean.getClass(), beanName);
          if (!this.earlyProxyReferences.contains(cacheKey)) {
                return wrapIfNecessary(bean, beanName, cacheKey);
          }
    }
    return bean;
  postProcessAfterInitialization方法中的wrapIfNecessary方法,會為目標對象動態地創建 JdkDynamicAopProxy或者 ObjenesisCglibAopProxy的代理對象。
 

AOP實戰

  日期代理:

 1 @Aspect
 2 @Component("logAspect")
 3 public class LogAspect {
 4 
 5     private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
 6 
 7     // 配置織入點
 8     @Pointcut("@annotation(com.ryj.annotation.Log)")
 9     public void logPointCut() {
10     }
11 
12     /**
13      * 是否存在注解,如果存在就獲取
14      */
15     private static Log getAnnotationLog(JoinPoint joinPoint) throws Exception {
16         Signature signature = joinPoint.getSignature();
17         MethodSignature methodSignature = (MethodSignature) signature;
18         Method method = methodSignature.getMethod();
19         if (method != null) {
20             return method.getAnnotation(Log.class);
21         }
22         return null;
23     }
24 
25 
26     @Around("logPointCut()")
27     public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
28         long  startTimeMillis = System.currentTimeMillis();
29         Log controllerLog = getAnnotationLog(joinPoint);
30         if (controllerLog == null) {
31             return null;
32         }
33         // 獲得方法名稱
34         String className = joinPoint.getTarget().getClass().getSimpleName();
35         String methodName = joinPoint.getSignature().getName();
36         String action = controllerLog.action();
37         String title = controllerLog.title();
38         Object[] args = joinPoint.getArgs();
39         //序列化時過濾掉request和response
40         List<Object> logArgs = Arrays.stream(args)
41                 .filter(arg -> (!(arg instanceof HttpServletRequest) && !(arg instanceof HttpServletResponse)))
42                 .collect(Collectors.toList());
43         String argStr = JSON.toJSONString(logArgs);
44         //打印日志,如有需要還可以存入數據庫
45         log.info(">>>>>模塊[{}]:{} 操作[{}]:{} 參數:{}",className,title,methodName,action,argStr);
46         //log.info("訪問的接口 ==> {} 請求參數 ==> {}" ,requestPath, argStr);
47         Object result = joinPoint.proceed() ;
48         log.info(">>>>>耗時 ==> {}ms" ,System.currentTimeMillis() - startTimeMillis);
49         return result ;
50     }
51 
52 }
LogAspect

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM