Spring實(shí)現(xiàn)三級(jí)緩存機(jī)制
一、什么是三級(jí)緩存
就是在Bean生成流程中保存Bean對(duì)象三種形態(tài)的三個(gè)Map集合,如下:
// 一級(jí)緩存Map 存放完整的Bean(流程跑完的) private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256); // 二級(jí)緩存Map 存放不完整的Bean(只實(shí)例化完,還沒(méi)屬性賦值、初始化) private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16); // 三級(jí)緩存Map 存放一個(gè)Bean的lambda表達(dá)式(也是剛實(shí)例化完) private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
用來(lái)解決什么問(wèn)題?
這個(gè)大家應(yīng)該熟知了,就是循環(huán)依賴
什么是循環(huán)依賴?
就像下面這樣,AService 中注入了BService ,而B(niǎo)Service 中又注入了AService ,這就是循環(huán)依賴
@Service public class AService { @Resource private BService bService; } @Service public class BService { @Resource private AService aService; }
二、Bean的加載源碼
我們先通過(guò)getBean()流程圖,來(lái)了解Spring的getBean()方法的工作流程,接著根據(jù)這個(gè)工作流程一步一步的閱讀源碼
//在spring中我們平時(shí)用到的getbean()這個(gè)方法實(shí)際上是調(diào)用的AbstractBeanFactory這個(gè)抽象工廠中得getbean方法 public Object getBean(String name) throws BeansException { //看源碼 我們首先看其返回值 如下返回得是doGetBean這個(gè)方法 return this.doGetBean(name, null, null, false); } //接下來(lái)我們看這個(gè)doGetBean這個(gè)方法 protected <T> T doGetBean( final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException { //獲取name對(duì)應(yīng)的真正beanName //有這么幾種情況 傳入得參數(shù)有可能是某個(gè)參數(shù)得別名,也有可能是FactoryBean的name //根據(jù)具體得實(shí)例去解析最終的得name final String beanName = this.transformedBeanName(name); Object bean; // 在創(chuàng)建單例bean的時(shí)候會(huì)存在依賴注入的情況,而在創(chuàng)建依賴的時(shí)候?yàn)榱吮苊庋h(huán)依賴 // Spring創(chuàng)建bean的原則是不等bean創(chuàng)建完成就會(huì)將創(chuàng)建bean的ObjectFactory提前曝光(將對(duì)應(yīng)的ObjectFactory加入到緩存) // 一旦下一個(gè)bean創(chuàng)建需要依賴上一個(gè)bean,則直接使用ObjectFactory對(duì)象 // 獲取單例 Object sharedInstance = this.getSingleton(beanName); if (sharedInstance != null && args == null) { // 實(shí)例已經(jīng)存在 if (logger.isDebugEnabled()) { if (this.isSingletonCurrentlyInCreation(beanName)) { logger.debug("Returning eagerly cached instance of singleton bean '" + beanName + "' that is not fully initialized yet - a consequence of a circular reference"); } else { logger.debug("Returning cached instance of singleton bean '" + beanName + "'"); } } // 返回對(duì)應(yīng)的實(shí)例 bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, null); } else { // 單例實(shí)例不存在 if (this.isPrototypeCurrentlyInCreation(beanName)) { // 只有在單例模式下才會(huì)嘗試解決循環(huán)依賴問(wèn)題 // 對(duì)于原型模式,如果存在循環(huán)依賴,也就是滿足this.isPrototypeCurrentlyInCreation(beanName),拋出異常 throw new BeanCurrentlyInCreationException(beanName); } // 獲取parentBeanFactory實(shí)例 BeanFactory parentBeanFactory = this.getParentBeanFactory(); // 如果在beanDefinitionMap中(即所有已經(jīng)加載的類中)不包含目標(biāo)bean,則嘗試從parentBeanFactory中獲取 if (parentBeanFactory != null && !this.containsBeanDefinition(beanName)) { String nameToLookup = this.originalBeanName(name); // 獲取name對(duì)應(yīng)的真正beanName,如果是factoryBean,則加上“&”前綴 if (args != null) { // 遞歸到BeanFactory中尋找 return (T) parentBeanFactory.getBean(nameToLookup, args); } else { return parentBeanFactory.getBean(nameToLookup, requiredType); } } // 如果不僅僅是做類型檢查,標(biāo)記bean的狀態(tài)已經(jīng)創(chuàng)建,即將beanName加入alreadyCreated集合中 if (!typeCheckOnly) { this.markBeanAsCreated(beanName); } try { //將存儲(chǔ)XML配置的GenericBeanDefinition實(shí)例轉(zhuǎn)換成RootBeanDefinition實(shí)例,方便后續(xù)處理 // 如果存在父bean,則同時(shí)合并父bean的相關(guān)屬性 final RootBeanDefinition mbd = this.getMergedLocalBeanDefinition(beanName); // 檢查bean是否是抽象的,如果是則拋出異常 this.checkMergedBeanDefinition(mbd, beanName, args); // 加載當(dāng)前bean依賴的bean String[] dependsOn = mbd.getDependsOn(); if (dependsOn != null) { // 存在依賴,遞歸實(shí)例化依賴的bean for (String dep : dependsOn) { if (this.isDependent(beanName, dep)) { // 檢查dep是否依賴beanName,從而導(dǎo)致循環(huán)依賴 throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'"); } // 緩存依賴調(diào)用 this.registerDependentBean(dep, beanName); this.getBean(dep); } } // 完成加載依賴的bean后,實(shí)例化mbd自身 if (mbd.isSingleton()) { // scope == singleton sharedInstance = this.getSingleton(beanName, new ObjectFactory<Object>() { @Override public Object getObject() throws BeansException { try { return createBean(beanName, mbd, args); } catch (BeansException ex) { // 清理工作,從單例緩存中移除 destroySingleton(beanName); throw ex; } } }); bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } else if (mbd.isPrototype()) { // scope == prototype Object prototypeInstance; try { // 設(shè)置正在創(chuàng)建的狀態(tài) this.beforePrototypeCreation(beanName); // 創(chuàng)建bean prototypeInstance = this.createBean(beanName, mbd, args); } finally { this.afterPrototypeCreation(beanName); } // 返回對(duì)應(yīng)的實(shí)例 bean = this.getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); } else { // 其它范圍得實(shí)例 String scopeName = mbd.getScope(); final Scope scope = this.scopes.get(scopeName); if (scope == null) { throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'"); } try { Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() { @Override public Object getObject() throws BeansException { beforePrototypeCreation(beanName); try { return createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } } }); // 返回對(duì)應(yīng)的實(shí)例 bean = this.getObjectForBeanInstance(scopedInstance, name, beanName, mbd); } catch (IllegalStateException ex) { throw new BeanCreationException(beanName, "Scope '" + scopeName + "' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton", ex); } } } catch (BeansException ex) { cleanupAfterBeanCreationFailure(beanName); throw ex; } } // 檢查需要的類型是否符合bean的實(shí)際類型,對(duì)應(yīng)getBean時(shí)指定的需要類型 if (requiredType != null && bean != null && !requiredType.isAssignableFrom(bean.getClass())) { try { // 執(zhí)行類型轉(zhuǎn)換,轉(zhuǎn)換成對(duì)應(yīng)的類型 return this.getTypeConverter().convertIfNecessary(bean, requiredType); } catch (TypeMismatchException ex) { if (logger.isDebugEnabled()) { logger.debug("Failed to convert bean '" + name + "' to required type '" + ClassUtils.getQualifiedName(requiredType) + "'", ex); } throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass()); } } 到這里才是真正意義上返回一個(gè)bean而已 return (T) bean; }
三、三級(jí)緩存詳解
不管你了不了解源碼,我們先看一下Bean的生成流程,看看三級(jí)緩存是在什么地方有調(diào)用,就三個(gè)地方:
- Bean實(shí)例化前會(huì)先查詢緩存,判斷Bean是否已經(jīng)存在
- Bean屬性賦值前會(huì)先向三級(jí)緩存中放入一個(gè)lambda表達(dá)式,該表達(dá)式執(zhí)行則會(huì)生成一個(gè)半成品Bean放入二級(jí)緩存
- Bean初始化完成后將完整的Bean放入一級(jí)緩存,同時(shí)清空二、三級(jí)緩存
接下來(lái)我們一個(gè)一個(gè)看!
3.1 Bean實(shí)例化前
AbstractBeanFactory.doGetBean
Bean實(shí)例化前會(huì)從緩存里面獲取Bean,防止重復(fù)實(shí)例化
DefaultSingletonBeanRegistry.getSingleton(String beanName, boolean allowEarlyReference)
我們看看這個(gè)獲取的方法邏輯:
- 從一級(jí)緩存獲取,獲取到了,則返回
- 從二級(jí)緩存獲取,獲取到了,則返回
- 從三級(jí)緩存獲取,獲取到了,則執(zhí)行三級(jí)緩存中的lambda表達(dá)式,將結(jié)果放入二級(jí)緩存,清除三級(jí)緩存
public Object getSingleton(String beanName) { return this.getSingleton(beanName, true); } @Nullable protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 從一級(jí)緩存中獲取Bean 獲取到了則返回 沒(méi)獲取到繼續(xù) Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) { // 從二級(jí)緩存中獲取Bean 獲取到了則返回 沒(méi)獲取到則繼續(xù) singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { // 加一把鎖防止 線程安全 雙重獲取校驗(yàn) synchronized(this.singletonObjects) { // 從一級(jí)緩存中獲取Bean 獲取到了則返回 沒(méi)獲取到繼續(xù) singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { // 從二級(jí)緩存中獲取Bean 獲取到了則返回 沒(méi)獲取到則繼續(xù) singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null) { // 從三級(jí)緩存中獲取 沒(méi)獲取到則返回 ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName); if (singletonFactory != null) { // 獲取到了 執(zhí)行三級(jí)緩存中的lambda表達(dá)式 singletonObject = singletonFactory.getObject(); // 并將結(jié)果放入二級(jí)緩存 this.earlySingletonObjects.put(beanName, singletonObject); // 從三級(jí)緩存中移除 this.singletonFactories.remove(beanName); } } } } } } return singletonObject; }
3.2 屬性賦值/注入前
AbstractAutowireCapableBeanFactory.doCreateBean
DefaultSingletonBeanRegistry.addSingletonFactory
這里就是將一個(gè)lambda表達(dá)式放入了三級(jí)緩存,我們需要去看一下這個(gè)表達(dá)式是干什么的??!
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(singletonFactory, "Singleton factory must not be null"); synchronized(this.singletonObjects) { // 一級(jí)緩存中不存在的話 if (!this.singletonObjects.containsKey(beanName)) { // 將lambda表達(dá)式放入三級(jí)緩存 this.singletonFactories.put(beanName, singletonFactory); // 清除二級(jí)緩存 this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } } }
AbstractAutowireCapableBeanFactory.getEarlyBeanReference
該方法說(shuō)白了就是會(huì)判斷該Bean是否需要被動(dòng)態(tài)代理,兩種返回結(jié)果:
- 不需要代理,返回未屬性注入、未初始化的半成品Bean
- 需要代理,返回未屬性注入、未初始化的半成品Bean的代理對(duì)象
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; if (!mbd.isSynthetic() && this.hasInstantiationAwareBeanPostProcessors()) { Iterator var5 = this.getBeanPostProcessors().iterator(); // 遍歷后置處理器 while(var5.hasNext()) { BeanPostProcessor bp = (BeanPostProcessor)var5.next(); // 找到實(shí)現(xiàn)SmartInstantiationAwareBeanPostProcessor接口的 // 該接口getEarlyBeanReference方法什么時(shí)候會(huì)執(zhí)行? // AOP動(dòng)態(tài)代理的時(shí)候 該方法執(zhí)行就是判斷該Bean是否需要被代理 // 需要代理則會(huì)創(chuàng)建代理對(duì)象返回 if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor)bp; exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName); } } } // 這個(gè)Object有兩種情況,一是實(shí)例化后的半成品Bean,二是半成品Bean動(dòng)態(tài)代理后的代理對(duì)象 return exposedObject; }
注意:這里只是把lambda表達(dá)式放入了三級(jí)緩存,如果不從三級(jí)緩存中獲取,這個(gè)表達(dá)式是不執(zhí)行的,一旦執(zhí)行了,就會(huì)把半成品Bean或者半成品Bean的代理對(duì)象放入二級(jí)緩存中了
3.3初始化后
AbstractBeanFactory.doGetBean
這里注意啊,這個(gè)getSingleton方法傳參傳了個(gè)lambda表達(dá)式,這個(gè)表達(dá)式內(nèi)部就是Bean的實(shí)例化過(guò)程,初始化完成后,是要需要執(zhí)行這個(gè)getSingleton方法的
DefaultSingletonBeanRegistry.getSingleton(beanName, singletonFactory)
這個(gè)方法與上面那個(gè)不一樣,重載了
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { synchronized(this.singletonObjects) { // 第一次進(jìn)來(lái)這里獲取肯定為null Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { // 省略................ try { // 注意啊,這個(gè)就是執(zhí)行外面那個(gè)傳參的lambda表達(dá)式 // 所以這里才會(huì)跳到createBean方法那里去執(zhí)行 singletonObject = singletonFactory.getObject(); newSingleton = true; } // 省略................ finally { if (recordSuppressedExceptions) { this.suppressedExceptions = null; } this.afterSingletonCreation(beanName); } // 到了這說(shuō)明Bean創(chuàng)建完了 if (newSingleton) { // 這里就會(huì)把Bean放入一級(jí)緩存中了 同時(shí)清除二、三級(jí)緩存 this.addSingleton(beanName, singletonObject); } } return singletonObject; } }
DefaultSingletonBeanRegistry.addSingleton
protected void addSingleton(String beanName, Object singletonObject) { synchronized(this.singletonObjects) { // 放入一級(jí)緩存 this.singletonObjects.put(beanName, singletonObject); // 清除二、三級(jí)緩存 this.singletonFactories.remove(beanName); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } }
總結(jié)
整個(gè)過(guò)程就三個(gè)地方跟緩存有關(guān),我們假設(shè)現(xiàn)在要實(shí)例化A這個(gè)Bean,看看緩存是怎么變化的:
- 實(shí)例化前,獲取緩存判斷(三個(gè)緩存中肯定沒(méi)有A,獲取為null,進(jìn)入實(shí)例化流程)
- 實(shí)例化完成,屬性注入前(往三級(jí)緩存中放入了一個(gè)lambda表達(dá)式,一、二級(jí)為null)
- 初始化完成(將A這個(gè)Bean放入一級(jí)緩存,清除二、三級(jí)緩存)
以上則是單個(gè)Bean生成過(guò)程中緩存的變化??!
四、怎么解決的循環(huán)依賴
上面我們把Bean流程中利用緩存的三個(gè)重要的點(diǎn)都找出來(lái)了,也分析了會(huì)帶來(lái)什么變化,接下來(lái)看看是怎么解決的循環(huán)依賴,我們看個(gè)圖就懂了:
以A注入B,B注入A為例:
A屬性注入前就把lambda表達(dá)式放入了第三級(jí)緩存,所以B再注入A的時(shí)候會(huì)從第三級(jí)緩存中找到A的lambda表達(dá)式并執(zhí)行,然后將半成品Bean放入第二級(jí)緩存,所以此時(shí)B注入的只是半成品的A對(duì)象,B創(chuàng)建完成后返回給A注入,A繼續(xù)初始化,完成創(chuàng)建。
注意: B注入的半成品A對(duì)象只是一個(gè)引用,所以之后A初始化完成后,B這個(gè)注入的A就隨之變成了完整的A
從上述看第三級(jí)緩存是用來(lái)提前暴露Bean對(duì)象引用的,所以解決了循環(huán)依賴,但是第二級(jí)緩存的這個(gè)半成品Bean對(duì)象干嘛的呢?
假設(shè)A同時(shí)注入了B和C,B和C又都注入了A,這時(shí)A注入B,實(shí)例化B的過(guò)程和上述是一樣的,但隨后還會(huì)注入C,那這個(gè)C在注入A的時(shí)候還會(huì)有第三級(jí)緩存用嗎?沒(méi)了吧,所以它就只能用第二級(jí)緩存的半成品Bean對(duì)象了,同樣也是引用而已
五、不用三級(jí)緩存不行嗎
可能很多小伙伴得到的答案就是不行,而且答案是因?yàn)椴淮_定這個(gè)Bean是不是代理對(duì)象,所以搞了個(gè)lambda表達(dá)式?答案真的是這樣嗎??
我們分析一下:AOP動(dòng)態(tài)代理在沒(méi)有循環(huán)依賴的時(shí)候是在哪里執(zhí)行的?Bean初始化后!有循環(huán)依賴的時(shí)候是在屬性賦值前,中間就間隔了一個(gè)屬性注入對(duì)吧,沒(méi)錯(cuò),在屬性注入的時(shí)候注入的是原始對(duì)象的引用還是代理對(duì)象的引用這個(gè)很重要,但是屬性注入會(huì)影響AOP的結(jié)果嗎?是否AOP創(chuàng)建代理對(duì)象和切面有關(guān),和屬性注入無(wú)關(guān),所以我們完全可以在屬性注入之前就知道這個(gè)Bean是代理對(duì)象還是非代理對(duì)象,就像下面這樣,我不將表達(dá)式放入第三級(jí)緩存了,而是直接執(zhí)行,將結(jié)果放入第二級(jí)緩存
這樣可不可以?可以吧,這樣用二級(jí)緩存就解決了,但是在一個(gè)對(duì)象沒(méi)有屬性賦值、初始化前就創(chuàng)建代理對(duì)象是有風(fēng)險(xiǎn)的!像這么做不管有沒(méi)有產(chǎn)生循環(huán)依賴,只要有AOP動(dòng)態(tài)代理對(duì)象的產(chǎn)生就有一分風(fēng)險(xiǎn),這么做是得不償失的,所以有了三級(jí)緩存,三級(jí)緩存是只有在循環(huán)依賴以及AOP動(dòng)態(tài)代理同時(shí)產(chǎn)生時(shí)才會(huì)有風(fēng)險(xiǎn)??梢哉f(shuō)是因?yàn)榇嬖谘h(huán)依賴所以被迫的導(dǎo)致Bean對(duì)象提前的暴露了引用?。?! 所以這下懂了吧
至于為什么多例、構(gòu)造器注入這兩種情況解決不了循環(huán)依賴就很簡(jiǎn)單了:
循環(huán)依賴的解決原理是在對(duì)象實(shí)例化后提前暴露了引用,而這兩種情況都還沒(méi)實(shí)例化呢
六、總結(jié)
- 一級(jí)緩存:用于存儲(chǔ)被完整創(chuàng)建了的bean。也就是完成了初始化之后,可以直接被其他對(duì)象使用的bean。
- 二級(jí)緩存:用于存儲(chǔ)半成品的Bean。也就是剛實(shí)例化但是還沒(méi)有進(jìn)行初始化的Bean
- 三級(jí)緩存:三級(jí)緩存存儲(chǔ)的是工廠對(duì)象(lambda表達(dá)式)。工廠對(duì)象可以產(chǎn)生Bean對(duì)象提前暴露的引用(半成品的Bean或者半成品的代理Bean對(duì)象),執(zhí)行這個(gè)lambda表達(dá)式,就會(huì)將引用放入二級(jí)緩存中
經(jīng)過(guò)以上的分析,現(xiàn)在應(yīng)該懂了吧:
循環(huán)依賴是否一定需要三級(jí)緩存來(lái)解決? 不一定,但三級(jí)緩存會(huì)更合適,風(fēng)險(xiǎn)更小
二級(jí)緩存能否解決循環(huán)依賴? 可以,但風(fēng)險(xiǎn)比三級(jí)緩存更大
第二級(jí)緩存用來(lái)干嘛的? 存放半成品的引用,可能產(chǎn)生多對(duì)象循環(huán)依賴,第三級(jí)緩存產(chǎn)生引用后,后續(xù)的就可以直接注入該引用
多例、構(gòu)造器注入為什么不能解決循環(huán)依賴? 因?yàn)檠h(huán)依賴的原理的實(shí)例化后提前暴露的引用,這兩種情況還沒(méi)實(shí)例化
到此這篇關(guān)于Spring實(shí)現(xiàn)三級(jí)緩存機(jī)制的文章就介紹到這了,更多相關(guān)Spring 三級(jí)緩存機(jī)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
2024版本IDEA創(chuàng)建Servlet模板的圖文教程
新版IDEA?2024.1.4中,用戶需要自行創(chuàng)建Servlet模板以解決Web項(xiàng)目無(wú)法通過(guò)右鍵創(chuàng)建Servlet的問(wèn)題,本文詳細(xì)介紹了添加ServletAnnotatedClass.java模板的步驟,幫助用戶快速配置并使用新的Servlet模板,需要的朋友可以參考下2024-10-10Spring Boot 集成Mybatis實(shí)現(xiàn)主從(多數(shù)據(jù)源)分離方案示例
本篇文章主要介紹了Spring Boot 集成Mybatis實(shí)現(xiàn)主從(多數(shù)據(jù)源)分離方案實(shí)例,具有一定的參考價(jià)值,有興趣的可以了解一下。2017-03-03Jeecg-Boot異常處理'jeecg-boot.QRTZ_LOCKS'?doesn'
這篇文章主要介紹了Jeecg-Boot異常處理'jeecg-boot.QRTZ_LOCKS'?doesn't?exist問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12Java的數(shù)據(jù)類型和參數(shù)傳遞(詳解)
下面小編就為大家?guī)?lái)一篇Java的數(shù)據(jù)類型和參數(shù)傳遞(詳解)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-07-07SpringCloud Alibaba 基本開(kāi)發(fā)框架搭建過(guò)程
這篇文章主要介紹了SpringCloud Alibaba 基本開(kāi)發(fā)框架搭建過(guò)程,開(kāi)發(fā)工具選用的idea,本文通過(guò)圖文實(shí)例相結(jié)合給大家分享搭建全過(guò)程,需要的朋友可以參考下2021-06-06攔截Druid數(shù)據(jù)源自動(dòng)注入帳密解密實(shí)現(xiàn)詳解
這篇文章主要為大家介紹了攔截Druid數(shù)據(jù)源自動(dòng)注入帳密解密實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11