成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

避坑!為了性能,Spring挖了一個大坑

開發(fā) 前端
將save方法的final去掉后,那么生成的代理類就可以重寫save方法了,最終調(diào)用save方法時先執(zhí)行增強部分,然后再調(diào)用真正的那個目標類對象(真正的目標類是并沒有通過objenesis創(chuàng)建,所以name是有值的)。

環(huán)境:SpringBoot2.7.18

1. 問題復現(xiàn)

該問題是在類中定義了一個實例變量并且賦了初始值,當通過AOP代理后出現(xiàn)了NPE(空指針異常),代碼如下:

定義一個Service對象

@Service
public class PersonService {


  private String name = "Pack" ;


  public final void save() {
    System.err.printf("class: %s, name: %s%n", this.getClass(), this.name) ;
  }
}

該類中定義的save方法使用final修飾,方法體打印了當前的class對象及name。

定義切面

在該切面中切入點明確指定處理PersonService類中的任意方法,如下代碼:

@Component
@Aspect
public class PersonAspect {


  @Pointcut("execution(* com.pack.aop.PersonService.*(..))")
  private void log() {}


  @Around("log()")
  public Object around(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("before...") ;
    Object ret = pjp.proceed() ;
    System.out.println("after...") ;
    return ret ;
  }
}

該切面非常簡單目標方法前后打印日志。以上代碼就準備完成;在運行代碼前,我們先回顧下Spring的代理機制

Spring AOP通過JDK動態(tài)代理或CGLIB來為給定的目標對象創(chuàng)建代理。JDK動態(tài)代理是JDK內(nèi)置的功能,而CGLIB是一個常見的開源類定義庫。

當需要代理的目標對象實現(xiàn)了至少一個接口時,Spring AOP會使用JDK動態(tài)代理。此時,目標類型實現(xiàn)的所有接口都會被代理。如果目標對象沒有實現(xiàn)任何接口,則會創(chuàng)建一個CGLIB代理。

如果你想強制使用CGLIB代理(例如,為了代理目標對象定義的所有方法,而不僅僅是那些由接口實現(xiàn)的方法)。

而在上面的代碼中PersonService并沒有實現(xiàn)如何接口,所以會通過CGLIB創(chuàng)建代碼(SpringBoot中默認也使用的CGLIB)。

但是,通過CGLIB代理要注意下面這個問題:在使用CGLIB時,final方法不能被建議(即不能被AOP增強),因為它們在運行時生成的子類中無法被覆蓋。

所以,在上面的PersonService中的save方法是不能被AOP增強的。了解了這么多以后我們來編寫一個測試程序來調(diào)用save方法看看執(zhí)行的結(jié)果。

@Service
public class AppRunService {


  private final PersonService personService ;
  public AppRunService(PersonService personService) {
    this.personService = personService ;
  }
  
  @PostConstruct
  public void init() {
    this.personService.save() ; 
  }
}

在該類中初始化階段會調(diào)用PersonService#save方法,輸出結(jié)果如下:

class: class com.pack.aop.PersonService$$EnhancerBySpringCGLIB$$557ca555, name: null

根據(jù)輸出結(jié)果得到,PersonService類被代理了,但是name為null,定義name屬性是明明是賦初始值Pack,為什么會出現(xiàn)null呢?

2. 原因分析

在上面已經(jīng)提到,Spring Boot中默認會使用CGLIB創(chuàng)建代理對象。而CGLIB代理對象的創(chuàng)建會通過ObjenesisCglibAopProxy創(chuàng)建,如下源碼:

public abstract class AbstractAutoProxyCreator {
  protected Object wrapIfNecessary(...) {
    // ...
    Object proxy = createProxy(...) ;
    return proxy ;
  }
  protected Object createProxy() {
    ProxyFactory proxyFactory = new ProxyFactory();
    // ...
    return proxyFactory.getProxy(classLoader) ;
  }
}
// 代理工廠
public class ProxyFactory {
  public Object getProxy(@Nullable ClassLoader classLoader) {
    return createAopProxy().getProxy(classLoader) ;
  }
}

上面的createAopProxy方法會返回一個ObjenesisCglibAopProxy對象,由該對象創(chuàng)建代理。我們這里跳過中間流程,直接進入到創(chuàng)建對象的代碼

class ObjenesisCglibAopProxy extends CglibAopProxy {
  private static final SpringObjenesis objenesis = new SpringObjenesis();
  protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) {
    Class<?> proxyClass = enhancer.createClass() ;
    Object proxyInstance = null ;


    proxyInstance = objenesis.newInstance(proxyClass, enhancer.getUseCache()) ;


    ((Factory) proxyInstance).setCallbacks(callbacks) ;
    return proxyInstance ;
  }
}

以上代碼是Spring 通過CGLIB創(chuàng)建代碼的過程;看到這里大家可以先去搜索下    objenesis,這是一個開源的庫,該庫提供了一種機制,可以直接創(chuàng)建對象而跳過構(gòu)造函數(shù)。Spring重新打包了objenesis。下面通過代碼演示objenesis庫

public class Person {
  private String name = "Pack" ;


  public String toString() {
    return "Person [name=" + name + "]";
  }
}
public static void main(String[] args) {
  Objenesis obj = new ObjenesisStd() ;
  Person person = obj.newInstance(Person.class) ;
  System.out.println(person) ;
}

上通過ObjenesisStd創(chuàng)建對象,運行結(jié)果:

Person [name=null]

name同樣為null。可能到這里你還是不能理解為什么為null。這里我們需要對類的生命周期有了解才行,對于實例變量的初始化,是在構(gòu)造函數(shù)當中,我們通過javap命令查看生成的字節(jié)碼

圖片圖片

通過反編譯知道了,實例變量的初始化是在構(gòu)造函數(shù)中。

到此,總結(jié)下為null的原因:

  • Spring通過cglib創(chuàng)建代理,但是對于final修飾的方法代理類是無法重新的;既然無法重寫,那么當你調(diào)用的時候必然是調(diào)用父類中的方法。
  • 代理類的創(chuàng)建是通過objenesis,該庫創(chuàng)建的示例會跳過構(gòu)造函數(shù),而實例變量的最終初始化是在構(gòu)造函數(shù)中。

3. 解決辦法

上面分析了為什么為null的原因,那么該如何解決呢?我們可以通過3種辦法解決

3.1 成員變量添加final修飾符

public class PersonService {
  private final String name = "Pack" ;
}

輸出結(jié)果:

class: class com.pack.aop.PersonService$$EnhancerBySpringCGLIB$$87211922, name: Pack

正確輸出,因為final修飾的實例變量在編譯為字節(jié)碼class時就已經(jīng)確定了值。

圖片圖片

3.2 將save方法的final去掉

將save方法的final去掉后,那么生成的代理類就可以重寫save方法了,最終調(diào)用save方法時先執(zhí)行增強部分,然后再調(diào)用真正的那個目標類對象(真正的目標類是并沒有通過objenesis創(chuàng)建,所以name是有值的)。

3.3 設(shè)置系統(tǒng)屬性

啟動程序是添加如下系統(tǒng)屬性

-Dspring.objenesis.ignore=true

Spring容器在創(chuàng)建對象前會判斷,該系統(tǒng)屬性是否為true。

責任編輯:武曉燕 來源: Spring全家桶實戰(zhàn)案例源碼
相關(guān)推薦

2021-05-07 07:59:52

WebFluxSpring5系統(tǒng)

2024-08-30 11:40:19

2025-01-16 16:16:53

2019-05-20 09:09:44

Web前端JavaScript

2020-06-09 08:05:11

Android 代碼操作系統(tǒng)

2017-12-27 14:51:12

Kotlin谷歌Java

2015-05-11 10:39:19

2024-09-24 13:31:33

2020-03-27 10:20:05

安全眾測滲透測試網(wǎng)絡(luò)安全

2022-03-15 17:35:20

電商系統(tǒng)架構(gòu)

2021-02-03 07:56:08

版本游戲邏輯

2020-05-22 10:35:07

CPU線程操作系統(tǒng)

2020-09-02 07:44:13

后端Long前端

2018-07-03 10:49:22

性能故障排查

2019-10-18 12:57:38

邊緣計算云計算安全

2018-01-20 20:46:33

2016-03-09 11:19:01

2012-05-30 09:40:55

Linux鍋爐

2023-04-28 12:01:56

Spring項目編譯

2022-05-09 11:01:18

配置文件數(shù)據(jù)庫
點贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 嫩草研究影院 | 激情综合五月 | 一区二区精品 | 中文字幕三区 | 国产小视频在线观看 | 日韩在线视频一区二区三区 | 另类视频在线 | 一级黄色片网站 | 一区二区视频在线 | 91久久国产综合久久 | 日本高清在线一区 | 日韩精品在线观看一区二区三区 | 国产精品成人一区二区 | 欧美激情一区二区三区 | 91久久夜色精品国产网站 | 一区二区在线 | 欧美精品久久久久 | 中文字幕在线观看一区二区 | 免费不卡视频 | av在线一区二区三区 | 国产一区不卡 | 亚洲成网| 91偷拍精品一区二区三区 | 免费日本视频 | av中文字幕在线 | 91不卡 | 视频在线亚洲 | 91在线电影 | 日韩一级 | 国产精品亚洲一区 | 国产一区二区 | 亚洲一区 中文字幕 | 久久久噜噜噜www成人网 | 91精品国产综合久久久久 | 国产亚洲一区二区三区在线观看 | 国产精品中文字幕在线 | 久久久久1 | 在线免费观看毛片 | 日韩欧美亚洲 | 亚洲资源在线 | 中文字幕一区二区三区四区五区 |