1. 程式人生 > >【Spring Boot】(32)、SpringBoot整合AOP

【Spring Boot】(32)、SpringBoot整合AOP

1、新增pom依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

在這裡插入圖片描述

會看到它依賴於org.aspectj.aspectjweaver,而常用的aop註解都在這個包下org.aspectj.lang.annotation,像 @Before@After@AfterReturning@AfterThrowing

@Around@Pointcut等。

通知方法:

  • 前置通知(@Before): 在目標方法執行之前執行。
  • 後置通知(@After): 在目標方法執行結束之後執行,不管正常結束還是異常結束,都會執行。
  • 返回通知(@AfterReturn): 在目標方法正常放回之後執行。
  • 異常通知(@AfterThrowing): 在目標方法出現異常以後執行。
  • 環繞通知(@Around): 動態代理,手動推進目標方法的執行。

2、Aop例子

@Aspect
public class LogAspects {

	// 抽取公共的切入點表示式
	@Pointcut
("execution(* org.com.cay.spring.annotation.aop.*.*(..))") public void logging() { } @Before(value = "logging()") public void logStart(JoinPoint joinPoint) { System.out .println(joinPoint.getSignature().getName() + "執行,引數列表是: {" + Arrays.asList(joinPoint.getArgs()) + "}"); } @After(value =
"logging()") public void logEnd(JoinPoint joinPoint) { System.out.println(joinPoint.getSignature().getName() + "結束..."); } @AfterReturning(value = "logging()", returning = "result") public void logReturn(JoinPoint joinPoint, Object result) { System.out.println(joinPoint.getSignature().getName() + "正常結束,結果是: {" + result + "}"); } @AfterThrowing(value = "logging()", throwing = "e") public void logException(JoinPoint joinPoint, Exception e) { System.out.println(joinPoint.getSignature().getName() + "異常,異常資訊: {" + e.getMessage() + "}"); } }

3、原理

一旦匯入了spring-boot-starter-aop依賴後,SpringBoot就會啟動AOP的自動配置類AopAutoConfiguration

@Configuration
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {

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

	}

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

	}

}

不管使用jdk代理還是cglib代理,都有 @EnableAspectJAutoProxy註解,所以重點還是要從 @EnableAspectJAutoProxy註解開始說起,咱們先來看看它的簽名:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
	//other code...
}

從其原始碼中看到該註解會匯入一個AspectJAutoProxyRegistrar註冊器,再點進去看看:

class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

	@Override
	public void registerBeanDefinitions(
			AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

		//other code...
	}
}

重點在於registerAspectJAnnotationAutoProxyCreatorIfNecessary方法,進入方法深處,會看到:

public static final String AUTO_PROXY_CREATOR_BEAN_NAME =
			"org.springframework.aop.config.internalAutoProxyCreator";

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

public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, Object source) {
		return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
	}

private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry, 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;
}

看到該方法會註冊一個Bean,其名為internalAutoProxyCreator,而其值為AnnotationAwareAspectJAutoProxyCreator

其繼承樹為:
在這裡插入圖片描述

重點就在於這四個函式,區別在於:

  • BeanPostProcessor介面的兩個方法是在Bean物件建立完成初始化前後呼叫的;
  • InstantiationAwareBeanPostProcessor介面的兩個方法則是在建立Bean例項前後呼叫。

所以執行的邏輯順序則是postProcessBeforeInstantiation -> postProcessAfterInstantiation -> postProcessBeforeInitialization -> postProcessAfterInitialization

其中postProcessAfterInstantiationpostProcessBeforeInitialization僅僅返回一個值,咱們就直接跳過,看其他兩個方法的執行邏輯。

既然是研究AOP原理,那麼咱們來看看MathCalculator和切面類LogAspects的建立過程,首先看postProcessBeforeInstantiation

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;
        }
        //判斷建立的bean是否是基礎類或者切面
        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,所以直接返回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;
}

AnnotationAwareAspectJAutoProxyCreator#isInfrastructureClass方法

protected boolean isInfrastructureClass(Class<?> beanClass) {
	//呼叫父類同名方法,或者判斷當前Bean是否是Aspect切面
    return (super.isInfrastructureClass(beanClass) || this.aspectJAdvisorFactory.isAspect(beanClass));
}

呼叫父類的同名方法AbstractAutoProxyCreator#isInfrastructureClass

protected boolean isInfrastructureClass(Class<?> beanClass) {
    boolean retVal = Advice.class.isAssignableFrom(beanClass) ||
        Pointcut.class.isAssignableFrom(beanClass) ||
            Advisor.class.isAssignableFrom(beanClass) ||
                AopInfrastructureBean.class.isAssignableFrom(beanClass);
    if (retVal && logger.isTraceEnabled()) {
        logger.trace("Did not attempt to auto-proxy infrastructure class [" + beanClass.getName() + "]");
    }
    return retVal;
}

從原始碼中看到isInfrastructureClass方法用於判斷當前建立的bean是否是基礎型別的AdvicePointcutAdvisorAopInfrastructureBean,或者是否是切面(@Aspect),如果滿足條件,則將當前bean加入快取中,並返回null;否則會查詢當前IOC環境中,對當前建立的Bean有切入點的通知器Advisor(即增強器)。

接著看postProcessAfterInitialization

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;
}

重點來了:

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    if (beanName != null && this.targetSourcedBeans.contains(beanName)) {
        return bean;
    }
    if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
        return bean;
    }
    if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }

    // Create proxy if we have advice.
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    if (specificInterceptors != DO_NOT_PROXY) {
        this.advisedBeans.put(cacheKey, Boolean.TRUE);
        //建立代理物件
        Object proxy = createProxy(
            bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
        this.proxyTypes.put(cacheKey, proxy.getClass());
        return proxy;
    }

    this.advisedBeans.put(cacheKey, Boolean.FALSE);
    return bean;
}

該邏輯和postProcessBeforeInstantiation方法類似,在建立MathCalculator這個Bean的時候,判斷其不是基礎型別的AdvicePointcutAdvisorAopInfrastructureBean,也不是切面Aspect,則會查詢IOC容器中可以切入該Bean的所有增強器(通知器Advisor),並儲存到Object[] specificInterceptors中,而且將當前bean儲存到advisedBeans中;接著給當前bean建立代理物件:

  • JdkDynamicAopProxy(config):jdk動態代理

  • ObjenesisCglibAopProxy(config):cglib的動態代理

以後容器中獲取到的該Bean其實是這個元件的代理物件,所以在執行目標方法的時候,也就是執行代理物件的intercept方法。

而在建立LogAspects之前postProcessBeforeInstantiation方法中由於是切面Aspect,所以在advisedBeans儲存了該bean對應的value值(false),所以在postProcessAfterInitialization之方法中,在Boolean.FALSE.equals(this.advisedBeans.get(cacheKey)) 邏輯true時直接返回bean。

最後在執行目標方法時,呼叫代理物件CglibAopProxy#DynamicAdvisedInterceptor#intercept方法:

public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    Object oldProxy = null;
    boolean setProxyContext = false;
    Class<?> targetClass = null;
    Object target = null;
    try {
        if (this.advised.exposeProxy) {
            // Make invocation available if necessary.
            oldProxy = AopContext.setCurrentProxy(proxy);
            setProxyContext = true;
        }
        // May be null. Get as late as possible to minimize the time we
        // "own" the target, in case it comes from a pool...
        target = getTarget();
        if (target != null) {
            targetClass = target.getClass();
        }
        
        //獲取將要執行的目標方法攔截器鏈
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
        Object retVal;
        // Check whether we only have one InvokerInterceptor: that is,
        // no real advice, but just reflective invocation of the target.
        if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
            // We can skip creating a MethodInvocation: just invoke the target directly.
            // Note that the final invoker must be an InvokerInterceptor, so we know
            // it does nothing but a reflective operation on the target, and no hot
            // swapping or fancy proxying.
            Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
            retVal = methodProxy.invoke(target, argsToUse);
        }
        else {
            // We need to create a method invocation...
            retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
        }
        retVal = processReturnType(proxy, target, method, retVal);
        return retVal;
    }
    finally {
        if (target != null) {
            releaseTarget(target);
        }
        if (setProxyContext) {
            // Restore old proxy.
            AopContext.setCurrentProxy(oldProxy);
        }
    }
}

獲取將要執行的目標方法的方法攔截鏈:

public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
			Advised config, Method method, Class<?> targetClass) {
	// This is somewhat tricky... We have to process introductions first,
	// but we need to preserve order in the ultimate list.
	List<Object> interceptorList = new ArrayList<Object>(config.getAdvisors().length);
	Class<?> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass());
	boolean hasIntroductions = hasMatchingIntroductions(config, actualClass);
	AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();

    //遍歷所有的增強器
	for (Advisor advisor : config.getAdvisors()) {
		if (advisor instanceof PointcutAdvisor) {
			// Add it conditionally.
			PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
			if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {
                
                //獲取增強器的方法攔截器
				MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
				MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
				if (MethodMatchers.matches(mm, method, actualClass, hasIntroductions)) {
					if (mm.isRuntime()) {
						// Creating a new object instance in the getInterceptors() method
						// isn't a problem as we normally cache created chains.
						for (MethodInterceptor interceptor : interceptors) {
							interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm));
						}
					}
					else {
						interceptorList.addAll(Arrays.asList(interceptors));
					}
				}
			}
		}
		else if (advisor instanceof IntroductionAdvisor) {
			IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
			if (config.isPreFiltered() || ia.getClassFilter().matches(actualClass)) {
				Interceptor[] interceptors = registry
            
           

相關推薦

Spring Boot32SpringBoot整合AOP

1、新增pom依賴 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</art

Spring Boot30SpringBoot整合RabbitMQ

1、安裝 1.1、Erlang: Erlang下載地址,下載後安裝即可。 1.2、RabbitMQ安裝 RabbitMQ下載地址,下載後安裝即可。 注意:Erlang的版本要與RabbitMQ版本需要匹配才行。 RabbitMQ Mini

Spring Boot29SpringBoot整合Mybatis原始碼分析

在【Spring Boot】(23)、Spring Boot整合Mybatis的章節中講述了SpringBoot整合Mybatis的過程,以及一些配置說明,這節主要講解一下整合的原始碼。 廢話不多說,直接進入今天的主題。 閱讀過我之前寫的文章的童靴,肯定知道SpringBoot整合第三方

Spring Boot33SpringBoot事務管理@Transactional註解原理

1、依賴包 1.1、 SpringBoot中的依賴包 眾所周知,在SpringBoot中凡是需要跟資料庫打交道的,基本上都要顯式或者隱式新增jdbc的依賴: <dependency> <groupId>org.springfram

Spring Boot19Spring Boot嵌入式Servlet容器自動配置原理

    其中EmbeddedServletContainerAutoConfiguration是嵌入式Servlet容器的自動配置類,該類在spring-boot-autoconfigure-xxx.jar中的web模組可以找到。 @AutoConfig

Spring Boot18Spring Boot配置嵌入式Servlet容器

Spring Boot預設使用Tomcat作為嵌入式的Servlet容器,只要引入了spring-boot-start-web依賴,則預設是用Tomcat作為Servlet容器: 1、定製和修改Servlet容器的相關配置 1)、修改和server有關的配置(ServerProper

Spring Boot15Spring Boot錯誤處理機制

1、Spring Boot預設的錯誤處理機制 如果是瀏覽器,則返回一個預設的錯誤頁面: 如果是其他測試工具,如Postman,則返回一個json資料: 原理: ​ 可以參照ErrorMvcAutoConfiguration,錯誤處理的自動配置

Spring Boot24Spring Boot中使用快取之Spring快取

1、快取依賴 只要新增如下依賴,即可使用快取功能。 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter

Spring Boot23Spring Boot整合Mybatis

首先新增mybatis依賴: <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</

Spring Boot22Spring Boot啟動配置原理

啟動配置原理 重要的事件回撥機制: ApplicationContextInitializer SpringApplicationRunListener ApplicationRunner CommandLineRunner前兩者需要配置在META-INF/spring.f

Spring Boot21Spring Boot使用外接的Servlet容器

嵌入式Servlet容器: ​ 優點:簡單,便攜; ​ 缺點:預設不支援jsp,優化定製比較複雜; 使用外接Servlet容器的步驟: ​ 1)、必須建立一個war專案,需要建立好web專案的目錄結構,特別是webapp/WEB-INF/web.xml; ​ 2)、嵌入式的To

Spring Boot31使用SpringBoot傳送mail郵件

1、前言 傳送郵件應該是網站的必備拓展功能之一,註冊驗證,忘記密碼或者是給使用者傳送營銷資訊。正常我們會用JavaMail相關api來寫傳送郵件的相關程式碼,但現在springboot提供了一套更簡易使用的封裝。   2、Mail依賴 <dependency>

Spring Boot6Profile

Profile是Spring對不同環境提供不同配置功能的支援,可通過啟用、指定引數等方式快速切換環境。1、多Profile檔案(Properties格式)在主配置檔案編寫的時候,檔名可以是applica

Spring Boot13Spring Boot自動配置SpringMVC

1、SpringMVC自動配置官方文件2、Spring MVC auto-configurationSpring Boot 提供了大多數SpringMVC應用常用的自動配置項。以下是Spring Boo

Spring Boot7配置檔案載入位置

Spring Boot啟動會掃描以下位置的application.properties/yml檔案作為Spring Boot預設配置檔案:外接,在相對於應用程式執行目錄的/config子目錄裡外接,在應

Spring Boot4配置檔案值注入

1、配置檔案使用上節中yaml書寫的配置資訊:server: port: 8081 path: /hello person: name: zhangsan age:

Spring Boot10Spring Boot日誌框架

1、日誌框架市面上的日誌框架:JUL、JCL、Jboss-logging、logback、log4j、log4j2、slf4j...日誌門面(日誌的抽象層)日誌的實現層JCL(Jakarta Commo

Spring boot入門SpringBoot整合結合AdminLTE(Freemarker),利用generate自動生成程式碼,利用DataTable和PageHelper進行分頁顯示

  關於SpringBoot和PageHelper,前篇部落格已經介紹過Spring boot入門(二):Spring boot整合MySql,Mybatis和PageHelper外掛,前篇部落格大致講述了SpringBoot如何整合Mybatis和Pagehelper,但是沒有做出實際的範例,本篇部落格是連

知識積累深入Regex正則表示式

\:將下一個字元標記符、或一個向後引用、或一個八進位制轉義符。例如,“\\n”匹配\n。“\n”匹配換行符。序列“\\”匹配“\”而“\(”則匹配“(”。即相當於多種程式語言中都有的“轉義字元”的概念。 ^:匹配輸入字串的開始位置。如果設定了RegExp物件的Multiline屬性,^也匹配“\n

知識積累瞭解Regex正則表示式

一、正則表示式簡介 一種可以用於模式匹配和替換的規範,由普通字元 + 特殊字元構成一個模板,用於對目標字串進行匹配、查詢、替換、判斷。 原始碼:JDK1.4中的java.util.regex下的Pattern和Matcher類。 二、常用語法 1、字元取值範圍 [abc]:表示可能是a