主頁 > 後端開發 > Spring AOP 分享

Spring AOP 分享

2023-05-13 07:22:03 後端開發

初級篇

AOP是什么?

Aspect-oriented Programming (AOP) 即面向切面編程,
簡單來說,AOP 是一種編程范式,允許我們模塊化地定義橫跨多個物件的行為,AOP 可以幫助我們將應用程式的關注點分離,使得代碼更加清晰、易于維護和擴展,

大白話:在方法執行前后運行指定代碼,比如日志記錄、事務開啟/提交/回滾等,

為什么要AOP?

AOP可以幫助我們解決在代碼中耦合度高的問題,讓我們的代碼更加模塊化和易于維護,
具體來說,AOP可以通過在運行時動態地將通用功能(例如日志記錄、性能分析、事務管理)應用于多個模塊,而無需修改它們的代碼,這樣可以避免代碼重復和嵌套,提高代碼的復用性和可維護性,
另外,在復雜的業務場景中,多個模塊可能需要共享某些共同的功能,而AOP可以讓這些功能從模塊中抽離出來,以便更好地進行組織和重用,
總的來說,AOP可以讓我們更好地實作代碼的分離和聚合,從而獲得更高效、更可靠的代碼,

大白話:增強原方法的功能,解耦通用功能,透明化靜默操作,
舉例 事務切面切面:
增強原方法的功能:原本方法使用的是資料庫連接默認的策略自動提交事務的,有了切面能夠保證方法內同一事務了;
解耦通用功能:但是很多方法都需要做事務的控制,有了切面不需要我們每一個方法都加幾行相同的代碼;
透明化靜默操作:方法本身需要知道我怎么開的事務?需要知道我什么時候多列印了日志嗎?

  • 偽代碼:沒有使用AOP前,每個方法都要CV一遍列印方法執行日志
@Override
public UserPO findByUsername(@AutoTrim String username) {
    log.info("execute findByUsername by username={}", username);
    Optional<UserPO> opt = userRepository.findOne((root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("username"), username));
    Assert.isTrue(opt.isPresent(), "沒有找到用戶");
    UserPO userPO = opt.get();
    log.info("execute findByUsername by username={}; return {} ", username, userPO);
    return userPO;
}

@Override
public UserPO findByEmail(@AutoTrim String email) {
    log.info("execute findByEmail by email={}", email);
    Optional<UserPO> opt = userRepository.findOne((root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("email"), email));
    Assert.isTrue(opt.isPresent(), "沒有找到用戶");
    UserPO userPO = opt.get();
    log.info("execute findByEmail by email={}; return {} ", email, userPO);
    return userPO;
}
  • 偽代碼:使用AOP后,無需關心方法執行日志的列印
@Override
public UserPO findByUsername(@AutoTrim String username) {
    Optional<UserPO> opt = userRepository.findOne((root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("username"), username));
    Assert.isTrue(opt.isPresent(), "沒有找到用戶");
    return opt.get();
}

@Override
public UserPO findByEmail(@AutoTrim String email) {
    Optional<UserPO> opt = userRepository.findOne((root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("email"), email));
    Assert.isTrue(opt.isPresent(), "沒有找到用戶");
    return opt.get();
}

此處代碼演示 【part1】
演示內容:

  • 展示未使用AOP前,使用UserServiceImpl注入Bean,測驗介面查看日志列印;
  • 展示使用AOP后,使用UserServiceAopImpl注入Bean,測驗介面查看日志列印;
  • 展示切面列印非專案內的類方法執行日志,切換PointcutlogPointcut2()
  • 展示靈活使用配置,控制切面開啟日志,開啟@ConditionalOnProperty,修改yml檔案;例如:測驗環境要開啟日志,生產環境要關閉日志,通過配置靈活控制,

PS:我們在此暫不考慮基于XmlApplicationContext系列;

怎么實作AOP?

AOP的實作依賴于多型和動態代理,
為了更好的理解,我們可以先舉例一個靜態代理的類來分析,
此處代碼演示 【part2】
演示內容:

  • 展示列印日志的靜態代理,使用UserServiceStaticAopImpl注入Bean,測驗介面查看日志列印;
  • 展示多種通知型別的靜態代理,使用UserServiceStaticAopAnyImpl注入Bean,測驗介面查看日志列印;

PS:我們在此暫不考慮基于XmlApplicationContext系列;

AOP的代碼結構與核心概念

靜態代理的AOP結構(簡單易理解版):
image.png

Aspect

切面:指橫跨多個類的一個關注點,它與不同類中相似的方法相對應,如保存資料時添加日志,可以新建一個切面配置日志記錄邏輯,

大白話:一個模塊化的切面程式,也可以理解為是一個實作切面功能的類;
用@Aspect定義的Bean Class,或者Spring xml配置里的aop:aspect標簽:

<aop:config>
  <aop:aspect id="myAspect" ref="aBean">
    ...
  </aop:aspect>
</aop:config>
Join Point

連接點:指在應用程式執行程序中的某個特定位置,如方法呼叫或例外處理等,

可以理解為要切面的物件型別,比如要加切面的目標是構造器,或者一個方法,或者是一個屬性的賦值;
AspectJ中可以有很多種,詳見AspectJ Join Points;
SpringAOP中只有一種,就是方法執行(Method execution);

Advice

通知:指在切面的某個連接點上執行的代碼,通知有許多型別,包括“前置通知”、“后置通知”、“回傳通知”、“例外通知”、“環繞通知”,其中“環繞通知”能夠完全控制目標方法的執行,

Pointcut

切入點:指一個或多個連接點,通常定義在一個正則運算式中,描述哪些方法會被攔截,

參考:
Join Points and Pointcuts
Spring 之AOP AspectJ切入點語法詳解
例:
within(com.supalle.springaop..*.UserServiceAopImpl)
execution(* com.supalle.springaop.*.*(..))
within(com.supalle.springaop.TestController) && @annotation(com.supalle.springaop.Log)

其它
  • Introduction
  • Target
  • AOP proxy
  • Weaving

詳見:AOP Concepts

Spring AOP常用的方式

此處代碼演示 【part3】

@Aspect

XmlApplicationContext的應用需要開啟<aop:aspectj-autoproxy />
AnnotationConfigApplicationContext的應用需要開啟@EnableAspectJAutoProxy;SpringBoot環境下可以不用,在 org.springframework.boot.autoconfigure.aop.AopAutoConfiguration里已經默認啟用;

package com.supalle.springaop.aspect;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * 日志切面
 *
 * @author supalle
 * @see {@link https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-pointcuts}
 */
@Slf4j
@Aspect
@Component
//@ConditionalOnProperty(value = "https://www.cnblogs.com/supalle/archive/2023/05/12/log-execution", havingValue = "https://www.cnblogs.com/supalle/archive/2023/05/12/true")
public class LogAspect {

    @Pointcut("within(com.supalle.springaop..*.UserServiceAopImpl)")
    // the pointcut within *AopImpl
    private void logPointcut() {
    }

    @Pointcut("within(com.supalle.springaop..*.UserServiceAopImpl) || within(com.zaxxer.hikari.HikariDataSource)")
    private void logPointcut2() {
    }

    @Before("logPointcut()")
    public void beforeLog(JoinPoint joinPoint) {
        log.info("Before Advice 1 execute {}", joinPoint.getSignature().getName());
    }

    // 同一個Aspect內,@Order排序無效,需要靠方法名排序,比如把當前方法改為beforeLog2,就能運行在beforeLog前面
    @Before("logPointcut()")
    public void beforeLog2(JoinPoint joinPoint) {
        log.info("Before Advice 2 execute {}", joinPoint.getSignature().getName());
    }

    @AfterReturning(value = "https://www.cnblogs.com/supalle/archive/2023/05/12/logPointcut()", returning = "returnValue")
    public void afterReturningLog(JoinPoint joinPoint, Object returnValue) {
        log.info("AfterReturning Advice execute {}; {} ", joinPoint.getSignature().getName(), returnValue);
    }

    @AfterThrowing(value = "https://www.cnblogs.com/supalle/archive/2023/05/12/logPointcut()", throwing = "throwable")
    public void afterThrowingLog(JoinPoint joinPoint, Throwable throwable) {
        log.info("AfterThrowing Advice execute {}; {}", joinPoint.getSignature().getName(), throwable.getMessage());
    }

    @After("logPointcut()")
    public void afterLog(JoinPoint joinPoint) {
        log.info("After Advice execute {}", joinPoint.getSignature().getName());
    }

    @Around("logPointcut()")
    public Object aroundLog(ProceedingJoinPoint joinPoint) throws Throwable {
        Signature joinPointSignature = joinPoint.getSignature();
        String name = joinPointSignature.getName();
        Logger logger = LoggerFactory.getLogger(joinPointSignature.getDeclaringTypeName());

        Object[] args = joinPoint.getArgs();
        String[] argNames = ((MethodSignature) joinPointSignature).getParameterNames();
        String argString = "";
        if (args != null && args.length > 0) {
            argString = " by " + IntStream.range(0, args.length)
                    .mapToObj(index -> argNames[index] + "=" + args[index])
                    .collect(Collectors.joining(" , "));
        }

        logger.info("Around Advice execute {}{}", name, argString);
        Object returnValue = https://www.cnblogs.com/supalle/archive/2023/05/12/joinPoint.proceed();
        logger.info("Around Advice execute {}{}; return {} ", name, argString, returnValue);

        return returnValue;
    }

}
XML配置

Advisor

org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator#findEligibleAdvisors中會查找容器中所有的Advisor型別Bean,也屬于AutoProxy的支持;

  • LogBeforeAdvice:定義一個Advice,加@Component注冊為Bean
package com.supalle.springaop.advice;

import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * org.springframework.aop.framework.adapter.DefaultAdvisorAdapterRegistry的支持
 */
@Slf4j
@Component
public class LogBeforeAdvice implements MethodBeforeAdvice {

    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        log.info("LogBeforeAdvice  execute {}", method.getName());
    }

}
  • LogBeforeAdvisor:定義一個Advisor,加@Component注冊為Bean
package com.supalle.springaop.advisor;

import com.supalle.springaop.advice.LogBeforeAdvice;
import lombok.RequiredArgsConstructor;
import org.aopalliance.aop.Advice;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.AbstractPointcutAdvisor;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class LogBeforeAdvisor extends AbstractPointcutAdvisor {
    private final LogBeforeAdvice advice;

    @Override
    public Pointcut getPointcut() {
        Pointcut pointcut = new Pointcut() {
            @Override
            public ClassFilter getClassFilter() {
                return clazz -> "UserServiceAopImpl".equals(clazz.getSimpleName());
            }

            @Override
            public MethodMatcher getMethodMatcher() {
                return MethodMatcher.TRUE;
            }
        };
        return pointcut;
    }

    @Override
    public Advice getAdvice() {
        return advice;
    }
}

運行代碼后除錯介面,看控制臺列印輸出:

: LogBeforeAdvice  execute findByUsername
程式員創建

用的不多,可以參考Programmatic Creation of @AspectJ Proxies

進階篇

Spring AOP與動態代理

動態代理

動態代理是指在程式運行時動態生成代理類的技術,即不需要手工撰寫代理類的源代碼,而是在程式運行期間通過反射等機制動態地生成,
動態代理模式可以幫助我們減少重復的代碼,提高代碼的可維護性和可擴展性,通常情況下,我們都是通過實作介面來創建代理物件,但是如果一個類沒有實作任何介面,我們仍然可以通過動態代理來創建代理物件,動態代理模式適用于一些橫切關注點(cross-cutting concerns)的處理,例如日志、安全、事務等功能,

在 Java 中,動態代理模式主要有兩種實作方式:
基于 JDK 動態代理(JDK Dynamic Proxy):JDK 提供了一個 java.lang.reflect.Proxy 類,可以動態地創建實作一組給定介面的代理類,要求被代理物件必須實作至少一個介面,并通過 Proxy 類的靜態方法 newProxyInstance 來創建代理物件,
基于 CGLIB 動態代理:CGLIB(Code Generation Library) 是一個基于 ASM(Java 位元組碼操作框架)的高性能位元組碼生成庫,可以在運行時動態生成位元組碼,并生成對應的代理類,要求被代理物件必須有默認建構式,并通過 CGLIB 庫提供的代理工廠(Enhancer 類)來創建代理物件,
總之,動態代理模式可以幫助我們在程式運行期間動態地生成代理類,從而達到增強功能、添加處理邏輯等目的,

Spring AOP

Spring AOP 就是動態代理的一種落地實作,底層是通過JDK Proxy和Cglib&ASM策略來進行動態生成代理類;
Spring Native應用下實作AOP的話,剛出來的時候我看過一遍,因為無法動態生成位元組碼,所以是在AOT編譯時,給應用中所有的類都生成了一個代理類,當這個類在實際運行時需要動態代理時,則加載這個代理類直接使用,(時過境遷,現在不知道實作方式變了沒有)

AOT(Ahead-of-time )compiler:預先編譯器;
JIT(Just-In-Time)compiler:即時編譯器;

Spring AOP的結構與核心類

除了上文說的AOP基本概念在Spring中有對應的類外,Spring AOP APIs中還有其它幾個核心類,用于具體的AOP實作,

  • Advised:代理類的頂層介面,包含委托物件和其資訊,以及應用的Advisor的集合;
  • Advisor:一個Pointcut+ Advice的組合;
  • AopProxy:AOP具體的代理實作,可能是JDK Proxy,也可能是Cglib Proxy;
  • JdkDynamicAopProxy
  • ObjenesisCglibAopProxy

org.springframework.aop.framework.DefaultAopProxyFactory#createAopProxy

用UML畫出他們的結構圖如下:
Advised.png
UserService為例,Spring AOP生成的代理類大致結構:
image.png

Spring AOP的呼叫鏈

仔細思考,上文舉例的靜態代理實作AOP的代碼,是不夠健全的,尤其是在Advice的順序上;
此處代碼演示 【part2】:beforeAspects切面中添加 System.out.println(1/0);使其拋出例外
參考Spring官方檔案的原文:

Advice: Action taken by an aspect at a particular join point. Different types of advice include "around", "before", and "after" advice. (Advice types are discussed later.) Many AOP frameworks, including Spring, model an advice as an interceptor and maintain a chain of interceptors around the join point.

這句“Many AOP frameworks, including Spring, model an advice as an interceptor and maintain a chain of interceptors around the join point.”翻譯過來就是“包括Spring在內的許多AOP框架都將Advice建模為攔截器,并在連接點周圍維護攔截器鏈,”
很好,使用類似于javax.servlet.Filter攔截器的方式,就可以很好的控制Advice的執行順序,

詳見:org.springframework.aop.framework.AdvisedSupport#getInterceptorsAndDynamicInterceptionAdvice

時序圖大致如下:
image.png

Advisor/Advice的執行順序

呼叫鏈的順序,基于Advised中的advisors集合順序,主要的排序策略如下:

  1. Advisororder值,沒有則取Adviceorder值,還沒有則為null,約等于最低優先級;
  2. 同一@Aspect類中定義多個Advice時,先按Around, Before, After, AfterReturning, AfterThrowing排序,再按方法名排序;
  3. 同一個Advice類實作多個Advice型別時,按 MethodInterceptorBeforeAdviceAfterReturningAdviceAfterThrowsAdvice排序;
  • 通過AbstractAdvisingBeanPostProcessor的后置處理器添加的Advisor,后置處理器可以通過beforeExistingAdvisors屬性控制是否排序在所有已經存在的Advisor前;
  • Spring官方檔案:Advice Ordering
  • 排序Advisors:org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator#sortAdvisors
  • 排序同一Aspect中的Advice:org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory#getAdvisorMethods

PS:如果Advisor/Advice是實作了Ordered介面的話,@Order注解是不生效的,

Spring AOP添加Advisor的途徑

ProxyConfig.png
ProxyConfig下分幾個家族,都可以添加Advisor,創建AOP代理類,

ProxyProcessorSupport家族

AbstractAdvisingBeanPostProcessor
AbstractAutoProxyCreator

AdvisedSupport家族

ProxyFactoryBean

AbstractSingletonProxyFactoryBean家族

ScopedProxyFactoryBean家族

常見的Advisor & Advice

AsyncAnnotationAdvisor & AsyncExecutionInterceptor

排序:Ordered=Ordered.HIGHEST_PRECEDENCE; // Integer.MIN_VALUE ;方法內寫死,繼承才可以修改
注意:AsyncAnnotationAdvisor是在AsyncAnnotationBeanPostProcessor后置處理器中創建和匹配,beforeExistingAdvisors=true;排序在所有已經存在的Advisor前,

Spring異步執行的AOP支持,

AsyncAnnotationAdvisor & AnnotationAsyncExecutionInterceptor

排序:Ordered=Ordered.HIGHEST_PRECEDENCE; // 繼承自父類AsyncExecutionInterceptor

Spring注解@Async異步執行的AOP支持,

RetryConfiguration & AnnotationAwareRetryOperationsInterceptor

排序:Ordered=Ordered.LOWEST_PRECEDENCE; // 繼承自父類AbstractPointcutAdvisor,可set;
注意:RetryConfiguration實作IntroductionAdvisor,排序優先于同order的未實作IntroductionAdvisor的Advisor,

Spring注解@Retryable方法失敗重試的AOP支持,

BeanFactoryCacheOperationSourceAdvisor & CacheInterceptor

排序:Ordered=Ordered.LOWEST_PRECEDENCE; // 繼承自父類AbstractPointcutAdvisor,可set,
默認來自@EnableCachingorder屬性,也是LOWEST_PRECEDENCE,可以直接在注解里修改,

Spring注解快取@EnableCaching@Cacheable等的AOP支持,

BeanFactoryTransactionAttributeSourceAdvisor & TransactionInterceptor

排序:Ordered=Ordered.LOWEST_PRECEDENCE; // 繼承自父類AbstractPointcutAdvisor,可set,
默認來自@EnableTransactionManagementorder屬性,也是LOWEST_PRECEDENCE,可以直接在注解里修改,

Spring注解事務@Transactional的AOP支持,

高級篇

Spring自動創建AOP代理的時機

Spring AOP代理類創建的時機,主要是在Bean被參考之前,依靠BeanPostProcessor來實作;
這就要靠ProxyProcessorSupport家族的AbstractAdvisingBeanPostProcessorAbstractAutoProxyCreator了,

  • AbstractAdvisingBeanPostProcessor對單個Advisor進行AOP切面的添加;
  • AbstractAutoProxyCreator對所有滿足Pointcut的Bean進行切面的添加;

兩者大部分情況會在postProcessAfterInitialization()鉤子方法中創建Bean的AOP代理物件,但也有一些特殊情況,比如自定義TargetSource的時候,會在postProcessBeforeInstantiation()鉤子方法中創建代理物件;
還有一種情況是在getEarlyBeanReference()鉤子方法時創建,也會提前創建好AOP代理物件;

詳見原始碼:

  • org.springframework.aop.framework.AbstractAdvisingBeanPostProcessor#postProcessAfterInitialization
  • org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization

另外注釋一下:postProcessBeforeInstantiation()鉤子執行時,Bean已經完成了 創建物件、屬性填充、依賴注入、初始化方法的執行;

其它自動創建的還有ProxyFactoryBeanAbstractSingletonProxyFactoryBeanScopedProxyFactoryBean的派生類也會在初始化Bean時自動創建;

為什么BeanPostProcessor可能無法使用AOP的功能

Spring IOC容器在掃描所有的BeanDefinition后,會先按順序初始化完所有的BeanPostProcessor,在初始化其它普通的Bean;而AbstractAdvisingBeanPostProcessorAbstractAutoProxyCreator這兩個處理自動AOP的后置處理器排序都是 Ordered=LOWEST_PRECEDENCE,因此,在這兩后置處理器之前初始化的Bean都無法被他們進行自動AOP代理,

Customizing Beans by Using a BeanPostProcessor
Spring官方提示:
BeanPostProcessor instances and AOP auto-proxying
Classes that implement the BeanPostProcessor interface are special and are treated differently by the container. All BeanPostProcessor instances and beans that they directly reference are instantiated on startup, as part of the special startup phase of the ApplicationContext. Next, all BeanPostProcessor instances are registered in a sorted fashion and applied to all further beans in the container. Because AOP auto-proxying is implemented as a BeanPostProcessor itself, neither BeanPostProcessor instances nor the beans they directly reference are eligible for auto-proxying and, thus, do not have aspects woven into them.
原始碼詳見:org.springframework.context.support.PostProcessorRegistrationDelegate#registerBeanPostProcessors

Spring如何防止反復創建AOP代理類

  1. 首先AbstractAutoProxyCreator中,有三個Map物件保存AOP代理類的創建資訊,分別是 targetSourcedBeansadvisedBeansearlyProxyReferences,當三者記憶體在Bean的代理物件時,則不會重復創建;
  2. AbstractAdvisingBeanPostProcessor中會判斷 bean instanceof Advised,如果已經是AOP代理物件,則不會再創建新的代理物件,直接在此代理物件上添加Advisor

Spring AOP與常說的Spring三級快取的關系

常說的Spring三級快取指的是org.springframework.beans.factory.support.DefaultSingletonBeanRegistry類中的三個Map型別的屬性,分別是:

Map<String, Object> singletonObjects; // 存放初始化完成的單例Bean
Map<String, Object> earlySingletonObjects; // 存放getEarlyBeanReference()鉤子創建的早期Bean
Map<String, ObjectFactory<?>> singletonFactories; // 存放earlySingletonObject的工廠實體

IOC默認開啟允許回圈參考,但是在依賴注入時,但是Spring并不想把創建AOP代理的行為提前,因此設計了getEarlyBeanReference鉤子和singletonFactoriesearlySingletonObjects,以支持回圈參考,
在實體化Bean后,先創建出earlySingletonObject的工廠實體,以備后續依賴注入的其它Bean參考當前Bean,當真實存在回圈依賴時,呼叫earlySingletonObject的工廠的方法構建早期Bean供其注入,并將這個早期Bean存放到earlySingletonObjects中,以備回圈依賴不止一次;最終在當前Bean初始化完成后,移除早期Bean,將最終的Bean放入singletonFactories中,成為正式暴露的Bean實體;
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean

// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
        isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
    if (logger.isTraceEnabled()) {
        logger.trace("Eagerly caching bean '" + beanName +
                "' to allow for resolving potential circular references");
    }
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // Quick check for existing instance without full singleton lock
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject == null && allowEarlyReference) {
            synchronized (this.singletonObjects) {
                // Consistent creation of early reference within full singleton lock
                singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) {
                    singletonObject = this.earlySingletonObjects.get(beanName);
                    if (singletonObject == null) {
                        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                        if (singletonFactory != null) {
                            singletonObject = singletonFactory.getObject();
                            this.earlySingletonObjects.put(beanName, singletonObject);
                            this.singletonFactories.remove(beanName);
                        }
                    }
                }
            }
        }
    }
    return singletonObject;
}

擴展篇

ASM/Cglib動態位元組碼生成

篇幅太長,略

Spring快取失效與AOP的瓜葛

Spring快取切面主要依賴于AOP的實作,當物件內進行this.doSomething()呼叫時,因為沒有經過代理物件,所以也沒有快取切面的功能;

Spring事務失效與AOP的瓜葛

與快取切面一樣,Spring事務切面主要依賴于AOP的實作,當物件內進行this.doSomething()呼叫時,因為沒有經過代理物件,所以也沒有事務切面的功能;

Synchronized和@Transactional 使用時,出現的問題

因為事務切面會在執行方法之前開啟事務,之后再加鎖,當鎖住的代碼執行完成后,在提交事務,
因此synchronized代碼塊執行是在事務之內執行的,可以推斷在代碼塊執行完時,事務還未提交,其他執行緒進入synchronized代碼塊后,讀取的庫存資料不是最新的,
解決上面的方法,比較簡單的可以在update方法之前加上synchronizedQ在還沒有開事務之間就加鎖,那么就可以保證執行緒同步

其他開發語言也可實作AOP

JavaScript的Proxy實作AOP

思考篇

AOP的優點

  • 降低了代碼的耦合度:AOP可以將橫切邏輯與業務邏輯分離,從而避免了代碼的重復和冗余,提高了代碼的可維護性和可讀性,
  • 提高了代碼的復用性:AOP可以將通用的橫切邏輯抽象出來,作為一個切面,從而可以在不同的業務邏輯中重復使用,提高了代碼的復用性,
  • 提高了系統的可擴展性:AOP可以動態地將橫切邏輯織入到業務邏輯中,從而可以方便地添加新的功能,提高了系統的可擴展性,

AOP的缺點

  • AOP增加了系統的復雜度:AOP需要額外的配置和代碼,增加了系統的復雜度,降低了系統的可讀性和可維護性,
  • AOP可能會影響系統的性能:AOP需要在運行時進行額外的處理,可能會影響系統的性能,尤其是在大規模應用中,
  • AOP可能會引入新的問題:AOP可能會引入新的問題,如死鎖、并發問題等,需要特別注意,

使用AOP的建議

  • 不要把應該與業務耦合的代碼放到切面中
  • 注意切面的順序
  • 注意this呼叫可能導致AOP失效

SpringBoot專案中使用AOP的最佳實踐

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/552312.html

標籤:其他

上一篇:閱讀論文的方法和技巧(快速且有效)

下一篇:返回列表

標籤雲
其他(158925) Python(38129) JavaScript(25420) Java(18034) C(15226) 區塊鏈(8265) C#(7972) AI(7469) 爪哇(7425) MySQL(7179) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5871) 数组(5741) R(5409) Linux(5339) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4572) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2433) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1972) 功能(1967) Web開發(1951) HtmlCss(1936) python-3.x(1918) C++(1915) 弹簧靴(1913) xml(1889) PostgreSQL(1875) .NETCore(1860) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Spring AOP 分享

    初級篇 AOP是什么? Aspect-oriented Programming (AOP) 即面向切面編程。簡單來說,AOP 是一種編程范式,允許我們模塊化地定義橫跨多個物件的行為。AOP 可以幫助我們將應用程式的關注點分離,使得代碼更加清晰、易于維護和擴展。 大白話:在方法執行前后運行指定代碼,比 ......

    uj5u.com 2023-05-13 07:22:03 more
  • 閱讀論文的方法和技巧(快速且有效)

    如何從一個小白快速開始入手看論文,然后看論文,發論文。請仔細看下面的講解。歡迎大家一起交流和補充。 閱讀論文的方法和技巧 一.閱讀論文五個重要步驟(通常用時30-60分鐘) 1.第一遍是快速瀏覽論文的摘要、結論、框架圖,有助于把握核心,對論文的內容形成整體感知。(5-10分鐘) 當然,這一遍建議在網 ......

    uj5u.com 2023-05-12 10:48:00 more
  • Java的列舉型別

    如果類的物件的數量只有有限個,并且可以確定物件的屬性,那么考慮使用列舉類。所有的列舉型別都是 Enum 類的子類。它們繼承了這個類的許多方法。 ......

    uj5u.com 2023-05-12 10:47:56 more
  • 閱讀論文的方法和技巧(快速且有效)

    如何從一個小白快速開始入手看論文,然后看論文,發論文。請仔細看下面的講解。歡迎大家一起交流和補充。 閱讀論文的方法和技巧 一.閱讀論文五個重要步驟(通常用時30-60分鐘) 1.第一遍是快速瀏覽論文的摘要、結論、框架圖,有助于把握核心,對論文的內容形成整體感知。(5-10分鐘) 當然,這一遍建議在網 ......

    uj5u.com 2023-05-12 10:47:13 more
  • Java的列舉型別

    如果類的物件的數量只有有限個,并且可以確定物件的屬性,那么考慮使用列舉類。所有的列舉型別都是 Enum 類的子類。它們繼承了這個類的許多方法。 ......

    uj5u.com 2023-05-12 10:46:48 more
  • 高效c語言2物件、函式和型別

    本章中,你將學習物件、函式和型別。我們將研究如何宣告變數(有識別符號的物件)和函式,獲取物件的地址,并對這些物件指標的解參考。你已經看到了C語言程式員可用的一些型別, C語言中的型別不是物件就是函式。 物件、函式、型別和指標 物件是你可以表示數值的存盤。準確地說,C標準(ISO/IEC 9899:20 ......

    uj5u.com 2023-05-12 08:05:02 more
  • Fast-GRPC: 用python輕松開發grpc介面

    簡介 Fast-GRPC 旨在幫助開發者更加輕松快捷地使用 Python 開發 gRPC 介面。它的特點包括簡化步驟、簡單上手、快速開發,同時支持異步和同步代碼,以及支持 middleware,靈感來自FastAPI。 安裝 需要python 3.7+ pip install python-fast ......

    uj5u.com 2023-05-12 08:04:58 more
  • 用tk.mybaits實作指定欄位更新

    ? 去年年底的因為業務需要需要在使用tk.mybaits框架的系統中實作指定欄位的更新,可是tk.mybaits框架本身并不支持這個功能,我翻遍了CSDN和其他相關的技術相關的網站都沒有找到相關的解決方法。于是我通過幾天的翻閱相關資料和摸索后終于實作了這個功能。最近事情不是很多,想到又想到了去年解決 ......

    uj5u.com 2023-05-12 08:04:54 more
  • 存下吧!Spring高頻面試題總結

    Spring是什么? Spring是一個輕量級的控制反轉(IoC)和面向切面(AOP)的容器框架。 Spring的優點 通過控制反轉和依賴注入實作松耦合。 支持面向切面的編程,并且把應用業務邏輯和系統服務分開。 通過切面和模板減少樣板式代碼。 宣告式事務的支持。可以從單調繁冗的事務管理代碼中解脫出來 ......

    uj5u.com 2023-05-12 08:03:10 more
  • Spring MVC官方檔案學習筆記(一)之Web入門

    注: 該章節主要為原創內容,為后續的Spring MVC內容做一個先行鋪墊 1.Servlet的構建使用 (1) 選擇Maven -> webapp來構建一個web應用 (2) 構建好后,打開pom.xml檔案,一要注意打包方式為war包,二匯入servlet依賴,如下 <!-- 打war包 --> ......

    uj5u.com 2023-05-12 08:02:51 more