欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

關(guān)于spring循環(huán)依賴(lài)問(wèn)題及解決方案

 更新時(shí)間:2022年06月27日 11:32:15   作者:cristianoxm  
這篇文章主要介紹了關(guān)于spring循環(huán)依賴(lài)問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

一、三種循環(huán)依賴(lài)的情況

①構(gòu)造器的循環(huán)依賴(lài):

  • 這種依賴(lài)spring是處理不了的,直接拋出BeanCurrentlylnCreationException異常。

②單例模式下的setter循環(huán)依賴(lài):

  • 通過(guò)“三級(jí)緩存”處理循環(huán)依賴(lài),能處理。

③非單例循環(huán)依賴(lài):

  • 無(wú)法處理。原型(Prototype)的場(chǎng)景是不支持循環(huán)依賴(lài)的,通常會(huì)走到AbstractBeanFactory類(lèi)中下面的判斷,拋出異常。
if (isPrototypeCurrentlyInCreation(beanName))?
{ ?throw new BeanCurrentlyInCreationException(beanName);}

原因很好理解,創(chuàng)建新的A時(shí),發(fā)現(xiàn)要注入原型字段B,又創(chuàng)建新的B發(fā)現(xiàn)要注入原型字段A…這就套娃了, 你猜是先StackOverflow還是OutOfMemory?

Spring怕你不好猜,就先拋出了BeanCurrentlyInCreationException

出現(xiàn)的背景:

比如幾個(gè)Bean之間的互相引用 

在這里插入圖片描述

甚至自己“循環(huán)”依賴(lài)自己

在這里插入圖片描述

二、解決方案

首先,Spring內(nèi)部維護(hù)了三個(gè)Map,也就是我們通常說(shuō)的三級(jí)緩存。

筆者翻閱Spring文檔倒是沒(méi)有找到三級(jí)緩存的概念,可能也是本土為了方便理解的詞匯。

在Spring的DefaultSingletonBeanRegistry類(lèi)中,你會(huì)赫然發(fā)現(xiàn)類(lèi)上方掛著這三個(gè)Map:

  • singletonObjects (一級(jí)緩存)它是我們最熟悉的朋友,俗稱(chēng)“單例池”“容器”,緩存創(chuàng)建完成單例Bean的地方。
  • earlySingletonObjects(二級(jí)緩存)映射Bean的早期引用,也就是說(shuō)在這個(gè)Map里的Bean不是完整的,甚至還不能稱(chēng)之為“Bean”,只是一個(gè)Instance.
  • singletonFactories(三級(jí)緩存) 映射創(chuàng)建Bean的原始工廠(chǎng)

在這里插入圖片描述

后兩個(gè)Map其實(shí)是“墊腳石”級(jí)別的,只是創(chuàng)建Bean的時(shí)候,用來(lái)借助了一下,創(chuàng)建完成就清掉了。

那么Spring 是如何通過(guò)上面介紹的三級(jí)緩存來(lái)解決循環(huán)依賴(lài)的呢?

這里只用 A,B 形成的循環(huán)依賴(lài)來(lái)舉例:

  • 實(shí)例化 A,此時(shí) A 還未完成屬性填充和初始化方法(@PostConstruct)的執(zhí)行,A 只是一個(gè)半成品。
  • 為 A 創(chuàng)建一個(gè) Bean工廠(chǎng),并放入到 singletonFactories 中。
  • 發(fā)現(xiàn) A 需要注入 B 對(duì)象,但是一級(jí)、二級(jí)、三級(jí)緩存均為發(fā)現(xiàn)對(duì)象 B。
  • 實(shí)例化 B,此時(shí) B 還未完成屬性填充和初始化方法(@PostConstruct)的執(zhí)行,B 只是一個(gè)半成品。
  • 為 B 創(chuàng)建一個(gè) Bean工廠(chǎng),并放入到 singletonFactories 中。
  • 發(fā)現(xiàn) B 需要注入 A 對(duì)象,此時(shí)在一級(jí)、二級(jí)未發(fā)現(xiàn)對(duì)象A,但是在三級(jí)緩存中發(fā)現(xiàn)了對(duì)象 A,從三級(jí)緩存中得到對(duì)象 A,并將對(duì)象 A 放入二級(jí)緩存中,同時(shí)刪除三級(jí)緩存中的對(duì)象 A。(注意,此時(shí)的 A還是一個(gè)半成品,并沒(méi)有完成屬性填充和執(zhí)行初始化方法)
  • 將對(duì)象 A 注入到對(duì)象 B 中。
  • 對(duì)象 B 完成屬性填充,執(zhí)行初始化方法,并放入到一級(jí)緩存中,同時(shí)刪除二級(jí)緩存中的對(duì)象 B。(此時(shí)對(duì)象 B 已經(jīng)是一個(gè)成品)
  • 對(duì)象 A 得到對(duì)象B,將對(duì)象 B 注入到對(duì)象 A 中。(對(duì)象 A 得到的是一個(gè)完整的對(duì)象 B)
  • 對(duì)象 A完成屬性填充,執(zhí)行初始化方法,并放入到一級(jí)緩存中,同時(shí)刪除二級(jí)緩存中的對(duì)象 A。

在這里插入圖片描述

我們從源碼的角度來(lái)看一下這個(gè)過(guò)程:

創(chuàng)建 Bean 的方法在 AbstractAutowireCapableBeanFactory::doCreateBean()

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
    BeanWrapper instanceWrapper = null;
	
    if (instanceWrapper == null) {
        // ① 實(shí)例化對(duì)象
        instanceWrapper = this.createBeanInstance(beanName, mbd, args);
    }
    final Object bean = instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null;
    Class<?> beanType = instanceWrapper != null ? instanceWrapper.getWrappedClass() : null;
   
    // ② 判斷是否允許提前暴露對(duì)象,如果允許,則直接添加一個(gè) ObjectFactory 到三級(jí)緩存
	boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
        // 添加三級(jí)緩存的方法詳情在下方
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }
    // ③ 填充屬性
    this.populateBean(beanName, mbd, instanceWrapper);
    // ④ 執(zhí)行初始化方法,并創(chuàng)建代理
    exposedObject = initializeBean(beanName, exposedObject, mbd);
   
    return exposedObject;
}

添加三級(jí)緩存的方法如下:

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) { // 判斷一級(jí)緩存中不存在此對(duì)象
            this.singletonFactories.put(beanName, singletonFactory); // 添加至三級(jí)緩存
            this.earlySingletonObjects.remove(beanName); // 確保二級(jí)緩存沒(méi)有此對(duì)象
            this.registeredSingletons.add(beanName);
        }
    }
}
@FunctionalInterface
public interface ObjectFactory<T> {
	T getObject() throws BeansException;
}

通過(guò)這段代碼,我們可以知道 Spring 在實(shí)例化對(duì)象的之后,就會(huì)為其創(chuàng)建一個(gè) Bean 工廠(chǎng),并將此工廠(chǎng)加入到三級(jí)緩存中。

因此,Spring 一開(kāi)始提前暴露的并不是實(shí)例化的 Bean,而是將 Bean 包裝起來(lái)的 ObjectFactory。為什么要這么做呢?

這實(shí)際上涉及到 AOP,如果創(chuàng)建的 Bean 是有代理的,那么注入的就應(yīng)該是代理 Bean,而不是原始的 Bean。但是 Spring 一開(kāi)始并不知道 Bean 是否會(huì)有循環(huán)依賴(lài),通常情況下(沒(méi)有循環(huán)依賴(lài)的情況下),Spring 都會(huì)在完成填充屬性,并且執(zhí)行完初始化方法之后再為其創(chuàng)建代理。但是,如果出現(xiàn)了循環(huán)依賴(lài)的話(huà),Spring 就不得不為其提前創(chuàng)建代理對(duì)象,否則注入的就是一個(gè)原始對(duì)象,而不是代理對(duì)象。因此,這里就涉及到應(yīng)該在哪里提前創(chuàng)建代理對(duì)象?

Spring 的做法就是在 ObjectFactory 中去提前創(chuàng)建代理對(duì)象。它會(huì)執(zhí)行 getObject() 方法來(lái)獲取到 Bean。實(shí)際上,它真正執(zhí)行的方法如下:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                // 如果需要代理,這里會(huì)返回代理對(duì)象;否則返回原始對(duì)象
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
            }
        }
    }
    return exposedObject;
}

因?yàn)樘崆斑M(jìn)行了代理,避免對(duì)后面重復(fù)創(chuàng)建代理對(duì)象,會(huì)在 earlyProxyReferences 中記錄已被代理的對(duì)象。

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
		implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
    @Override
    public Object getEarlyBeanReference(Object bean, String beanName) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        // 記錄已被代理的對(duì)象
        this.earlyProxyReferences.put(cacheKey, bean);
        return wrapIfNecessary(bean, beanName, cacheKey);
    }
}

通過(guò)上面的解析,我們可以知道 Spring 需要三級(jí)緩存的目的是為了在沒(méi)有循環(huán)依賴(lài)的情況下,延遲代理對(duì)象的創(chuàng)建,使 Bean 的創(chuàng)建符合 Spring 的設(shè)計(jì)原則。

如何獲取依賴(lài)

我們目前已經(jīng)知道了 Spring 的三級(jí)依賴(lài)的作用,但是 Spring 在注入屬性的時(shí)候是如何去獲取依賴(lài)的呢?

他是通過(guò)一個(gè)getSingleton()方法去獲取所需要的 Bean 的。

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 一級(jí)緩存
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            // 二級(jí)緩存
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                // 三級(jí)緩存
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    // Bean 工廠(chǎng)中獲取 Bean
                    singletonObject = singletonFactory.getObject();
                    // 放入到二級(jí)緩存中
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

當(dāng) Spring 為某個(gè) Bean 填充屬性的時(shí)候,它首先會(huì)尋找需要注入對(duì)象的名稱(chēng),然后依次執(zhí)行 getSingleton() 方法得到所需注入的對(duì)象,而獲取對(duì)象的過(guò)程就是先從一級(jí)緩存中獲取,一級(jí)緩存中沒(méi)有就從二級(jí)緩存中獲取,二級(jí)緩存中沒(méi)有就從三級(jí)緩存中獲取,如果三級(jí)緩存中也沒(méi)有,那么就會(huì)去執(zhí)行 doCreateBean() 方法創(chuàng)建這個(gè) Bean。

流程圖總結(jié):

在這里插入圖片描述

三、解決循環(huán)依賴(lài)必須要三級(jí)緩存嗎

我們現(xiàn)在已經(jīng)知道,第三級(jí)緩存的目的是為了延遲代理對(duì)象的創(chuàng)建,因?yàn)槿绻麤](méi)有依賴(lài)循環(huán)的話(huà),那么就不需要為其提前創(chuàng)建代理,可以將它延遲到初始化完成之后再創(chuàng)建。

既然目的只是延遲的話(huà),那么我們是不是可以不延遲創(chuàng)建,而是在實(shí)例化完成之后,就為其創(chuàng)建代理對(duì)象,這樣我們就不需要第三級(jí)緩存了。因此,我們可以將addSingletonFactory() 方法進(jìn)行改造。

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) { // 判斷一級(jí)緩存中不存在此對(duì)象
            object o = singletonFactory.getObject(); // 直接從工廠(chǎng)中獲取 Bean
            this.earlySingletonObjects.put(beanName, o); // 添加至二級(jí)緩存中
            this.registeredSingletons.add(beanName);
        }
    }
}

這樣的話(huà),每次實(shí)例化完 Bean 之后就直接去創(chuàng)建代理對(duì)象,并添加到二級(jí)緩存中。測(cè)試結(jié)果是完全正常的,Spring 的初始化時(shí)間應(yīng)該也是不會(huì)有太大的影響,因?yàn)槿绻?Bean 本身不需要代理的話(huà),是直接返回原始 Bean 的,并不需要走復(fù)雜的創(chuàng)建代理 Bean 的流程。

結(jié)論

測(cè)試證明,二級(jí)緩存也是可以解決循環(huán)依賴(lài)的。為什么 Spring 不選擇二級(jí)緩存,而要額外多添加一層緩存呢?

如果 Spring 選擇二級(jí)緩存來(lái)解決循環(huán)依賴(lài)的話(huà),那么就意味著所有 Bean 都需要在實(shí)例化完成之后就立馬為其創(chuàng)建代理,而Spring 的設(shè)計(jì)原則是在 Bean 初始化完成之后才為其創(chuàng)建代理。所以,Spring 選擇了三級(jí)緩存。但是因?yàn)檠h(huán)依賴(lài)的出現(xiàn),導(dǎo)致了 Spring 不得不提前去創(chuàng)建代理,因?yàn)槿绻惶崆皠?chuàng)建代理對(duì)象,那么注入的就是原始對(duì)象,這樣就會(huì)產(chǎn)生錯(cuò)誤。

四、無(wú)法解決的循環(huán)依賴(lài)問(wèn)題

1.在主bean中通過(guò)構(gòu)造函數(shù)注入所依賴(lài)的bean

如下controller為主bean,service為所依賴(lài)的bean:

@RestController
public class AccountController {
    private static final Logger LOG = LoggerFactory.getLogger(AccountController.class);
    private AccountService accountService;
    // 構(gòu)造函數(shù)依賴(lài)注入
    // 不管是否設(shè)置為required為true,都會(huì)出現(xiàn)循環(huán)依賴(lài)問(wèn)題
    @Autowire
    // @Autowired(required = false)
    public AccountController(AccountService accountService) {
        this.accountService = accountService;
    }
    
}
@Service
public class AccountService {
    private static final Logger LOG = LoggerFactory.getLogger(AccountService.class);
    
    // 屬性值依賴(lài)注入
    @Autowired
    private AccountController accountController;
   } 

啟動(dòng)打印如下:

***************************
APPLICATION FAILED TO START
***************************

Description:

The dependencies of some of the beans in the application context form a cycle:

┌─────┐
|  accountController defined in file [/Users/xieyizun/study/personal-projects/easy-web/target/classes/com/yzxie/easy/log/web/controller/AccountController.class]
↑     ↓
|  accountService (field private com.yzxie.easy.log.web.controller.AccountController com.yzxie.easy.log.web.service.AccountService.accountController)
└─────┘

如果是在主bean中通過(guò)屬性值或者setter方法注入所依賴(lài)的bean,而在所依賴(lài)的bean使用了構(gòu)造函數(shù)注入主bean對(duì)象,這種情況則不會(huì)出現(xiàn)循環(huán)依賴(lài)問(wèn)題。

@RestController
public class AccountController {
    private static final Logger LOG = LoggerFactory.getLogger(AccountController.class);
    // 屬性值注入
    @Autowired
    private AccountService accountService;
    
}
@Service
public class AccountService {
    private AccountController accountController;
    // 構(gòu)造函數(shù)注入
    @Autowired
    public AccountService(AccountController accountController) {
        this.accountController = accountController;
    }
    
}

2.總結(jié)

  • 當(dāng)存在循環(huán)依賴(lài)時(shí),主bean對(duì)象不能通過(guò)構(gòu)造函數(shù)的方式注入所依賴(lài)的bean對(duì)象,而所依賴(lài)的bean對(duì)象則不受限制,即可以通過(guò)三種注入方式的任意一種注入主bean對(duì)象。
  • 如果主bean對(duì)象通過(guò)構(gòu)造函數(shù)方式注入所依賴(lài)的bean對(duì)象,則無(wú)論所依賴(lài)的bean對(duì)象通過(guò)何種方式注入主bean,都無(wú)法解決循環(huán)依賴(lài)問(wèn)題,程序無(wú)法啟動(dòng)。(其實(shí)在主bean加上@Lazy也能解決)

原因主要是主bean對(duì)象通過(guò)構(gòu)造函數(shù)注入所依賴(lài)bean對(duì)象時(shí),無(wú)法創(chuàng)建該所依賴(lài)的bean對(duì)象,獲取該所依賴(lài)bean對(duì)象的引用。因?yàn)槿缦麓a所示。

創(chuàng)建主bean對(duì)象,調(diào)用順序?yàn)椋?/strong>

  • 1.調(diào)用構(gòu)造函數(shù)
  • 2. 放到三級(jí)緩存
  • 3. 屬性賦值。其中調(diào)用構(gòu)造函數(shù)時(shí)會(huì)觸發(fā)所依賴(lài)的bean對(duì)象的創(chuàng)建。
    // bean對(duì)象實(shí)例創(chuàng)建的核心實(shí)現(xiàn)方法
    protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
    		throws BeanCreationException {
    		// 省略其他代碼
    		// 1. 調(diào)用構(gòu)造函數(shù)創(chuàng)建該bean對(duì)象,若不存在構(gòu)造函數(shù)注入,順利通過(guò)
    		instanceWrapper = createBeanInstance(beanName, mbd, args);
    		// 2. 在singletonFactories緩存中,放入該bean對(duì)象,以便解決循環(huán)依賴(lài)問(wèn)題
    		addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    		// 3. populateBean方法:bean對(duì)象的屬性賦值
    		populateBean(beanName, mbd, instanceWrapper); 		
    		// 省略其他代碼
    	return exposedObject;
    }

createBeanInstance是調(diào)用構(gòu)造函數(shù)創(chuàng)建主bean對(duì)象,在里面會(huì)注入構(gòu)造函數(shù)中所依賴(lài)的bean,而此時(shí)并沒(méi)有執(zhí)行到addSingletonFactory方法來(lái)添加主bean對(duì)象的創(chuàng)建工廠(chǎng)到三級(jí)緩存singletonFactories中。故在createBeanInstance內(nèi)部,注入和創(chuàng)建該主bean對(duì)象時(shí),如果在構(gòu)造函數(shù)中存在對(duì)其他bean對(duì)象的依賴(lài),并且該bean對(duì)象也存在對(duì)主bean對(duì)象的依賴(lài),則會(huì)出現(xiàn)循環(huán)依賴(lài)問(wèn)題,原理如下:

主bean對(duì)象為A,A對(duì)象依賴(lài)于B對(duì)象,B對(duì)象也存在對(duì)A對(duì)象的依賴(lài),創(chuàng)建A對(duì)象時(shí),會(huì)觸發(fā)B對(duì)象的創(chuàng)建,則B無(wú)法通過(guò)三級(jí)緩存機(jī)制獲取主bean對(duì)象A的引用(即B如果通過(guò)構(gòu)造函數(shù)注入A,則無(wú)法創(chuàng)建B對(duì)象;如果通過(guò)屬性注入或者setter方法注入A,則創(chuàng)建B對(duì)象后,對(duì)B對(duì)象進(jìn)行屬性賦值,會(huì)卡在populateBean方法也無(wú)法返回)。 故無(wú)法創(chuàng)建主bean對(duì)象所依賴(lài)的B,創(chuàng)建主bean對(duì)象A時(shí),createBeanInstance方法無(wú)法返回,出現(xiàn)代碼死鎖,程序報(bào)循環(huán)依賴(lài)錯(cuò)誤。

注意:spring的循環(huán)依賴(lài)其實(shí)是可以關(guān)閉的,設(shè)置allowCircularReference=false

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • fastjson對(duì)JSONObject中的指定字段重新賦值的實(shí)現(xiàn)

    fastjson對(duì)JSONObject中的指定字段重新賦值的實(shí)現(xiàn)

    這篇文章主要介紹了fastjson對(duì)JSONObject中的指定字段重新賦值的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11
  • MybatisPlus的IPage失效的問(wèn)題解決方案

    MybatisPlus的IPage失效的問(wèn)題解決方案

    這篇文章主要介紹了MybatisPlus的IPage失效的問(wèn)題解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-01-01
  • Java CyclicBarrier源碼層分析與應(yīng)用

    Java CyclicBarrier源碼層分析與應(yīng)用

    這篇文章主要介紹了Java CyclicBarrier的源碼層分析與應(yīng)用,CyclicBarrier也叫同步屏障,可以讓一組線(xiàn)程達(dá)到一個(gè)屏障時(shí)被阻塞,直到最后一個(gè)線(xiàn)程達(dá)到屏障,感興趣的的朋友可以參考下
    2023-12-12
  • RocketMQ發(fā)送事務(wù)消息詳解

    RocketMQ發(fā)送事務(wù)消息詳解

    這篇文章主要介紹了RocketMQ發(fā)送事務(wù)消息詳解,RocketMQ分布式事務(wù)消息不僅可以實(shí)現(xiàn)應(yīng)用之間的解耦,又能保證數(shù)據(jù)的最終一致性,傳統(tǒng)的大事務(wù)可以被拆分為小事務(wù),不僅能提升效率,還不會(huì)因?yàn)槟骋粋€(gè)關(guān)聯(lián)應(yīng)用的不可用導(dǎo)致整體回滾,需要的朋友可以參考下
    2023-09-09
  • Java使用DateFormatter格式化日期時(shí)間的方法示例

    Java使用DateFormatter格式化日期時(shí)間的方法示例

    這篇文章主要介紹了Java使用DateFormatter格式化日期時(shí)間的方法,結(jié)合具體實(shí)例分析了java使用DateFormatter格式化日期時(shí)間的相關(guān)操作技巧,需要的朋友可以參考下
    2017-04-04
  • Spring中@Transactional用法詳細(xì)介紹

    Spring中@Transactional用法詳細(xì)介紹

    這篇文章主要介紹了Spring中@Transactional用法詳細(xì)介紹的相關(guān)資料,需要的朋友可以參考下
    2017-02-02
  • Java實(shí)現(xiàn)把文件及文件夾壓縮成zip

    Java實(shí)現(xiàn)把文件及文件夾壓縮成zip

    這篇文章主要介紹了Java實(shí)現(xiàn)把文件及文件夾壓縮成zip,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-03-03
  • Java BigDecimal除法精度和格式化輸出方式

    Java BigDecimal除法精度和格式化輸出方式

    這篇文章主要介紹了Java BigDecimal除法精度和格式化輸出方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • Java幾個(gè)實(shí)例帶你進(jìn)階升華上篇

    Java幾個(gè)實(shí)例帶你進(jìn)階升華上篇

    與其明天開(kāi)始,不如現(xiàn)在行動(dòng),本文為你帶來(lái)幾個(gè)Java書(shū)寫(xiě)的實(shí)際案例,對(duì)鞏固編程的基礎(chǔ)能力很有幫助,快來(lái)一起往下看看吧
    2022-03-03
  • java實(shí)現(xiàn)點(diǎn)贊功能

    java實(shí)現(xiàn)點(diǎn)贊功能

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)點(diǎn)贊功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-08-08

最新評(píng)論