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

帶有@Transactional和@Async的循環(huán)依賴問(wèn)題的解決

 更新時(shí)間:2020年04月30日 10:42:01   作者:黃山技術(shù)猿  
這篇文章主要介紹了帶有@Transactional和@Async的循環(huán)依賴問(wèn)題的解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

今天我們來(lái)探討一個(gè)有意思的spring源碼問(wèn)題,也是一個(gè)學(xué)生告訴了我現(xiàn)象我從源碼里面找到了這個(gè)有意思的問(wèn)題。
首先我們看service層的代碼案例,如下:

@Service("transationServiceImpl")
public class TransationServiceImpl implements TransationService {

  @Autowired
  TransationService transationService;

  @Transactional
  @Async
  @Override
  public void transation() {
  }
}

在transation方法上面加上了@Transactional和@Async兩個(gè)注解,然后在TransationServiceImpl 類中自己把自己的實(shí)例注入到transationService屬性中,存在循環(huán)依賴,理論上單例的循環(huán)依賴是允許的。但是我們啟動(dòng)容器會(huì)報(bào)錯(cuò),測(cè)試代碼如下:

public class MyTest {
  @Test
  public void test1() {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ComponentScanBean.class);
  }
}

@Component
@ComponentScan(basePackages = {"com.xiangxue"})
public class ComponentScanBean {
}

然后右鍵運(yùn)行test1單元測(cè)試加載spring容器就會(huì)報(bào)錯(cuò),報(bào)錯(cuò)信息如下:
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ‘transationServiceImpl': Bean with name ‘transationServiceImpl' has been injected into other beans [transationServiceImpl] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using ‘getBeanNamesOfType' with the ‘a(chǎn)llowEagerInit' flag turned off, for example.
從報(bào)錯(cuò)的字面意思來(lái)看,是存在了多版本的循環(huán)依賴,如果要解決這個(gè)問(wèn)題,我們必須追溯到源碼中。

首先我們從TransationServiceImpl 實(shí)例化開(kāi)始講起。
實(shí)例化從getBean方法看起,前面代碼我就不貼了,這篇文章是給讀過(guò)spring源碼的人看的,沒(méi)讀過(guò)也看不懂,哈哈 。

1、首先第一次創(chuàng)建TransationServiceImpl實(shí)例的時(shí)候會(huì)從緩存中獲取實(shí)例 ,如果緩存里面有實(shí)例則直接返回,第一次創(chuàng)建的時(shí)候緩存中是沒(méi)有實(shí)例的,所以會(huì)走到else代碼塊中。


這里是從三個(gè)緩存中獲取實(shí)例化的詳細(xì)代碼。后面會(huì)分析

	@Nullable
	protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		//根據(jù)beanName從緩存中拿實(shí)例
		//先從一級(jí)緩存拿
		Object singletonObject = this.singletonObjects.get(beanName);
		//如果bean還正在創(chuàng)建,還沒(méi)創(chuàng)建完成,其實(shí)就是堆內(nèi)存有了,屬性還沒(méi)有DI依賴注入
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			synchronized (this.singletonObjects) {
				//從二級(jí)緩存中拿
				singletonObject = this.earlySingletonObjects.get(beanName);

				//如果還拿不到,并且允許bean提前暴露
				if (singletonObject == null && allowEarlyReference) {
					//從三級(jí)緩存中拿到對(duì)象工廠
					ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
					if (singletonFactory != null) {
						//從工廠中拿到對(duì)象
						singletonObject = singletonFactory.getObject();
						//升級(jí)到二級(jí)緩存
						System.out.println("======get instance from 3 level cache->beanName->" + beanName + "->value->" + singletonObject );
						this.earlySingletonObjects.put(beanName, singletonObject);
						//刪除三級(jí)緩存
						this.singletonFactories.remove(beanName);
					}
				}
			}
		}
		return singletonObject;
	}

2、第一次進(jìn)來(lái)緩存中沒(méi)有則創(chuàng)建TransationServiceImpl的實(shí)例


最終會(huì)走到doCreateBean方法中進(jìn)行實(shí)例化,部分代碼如下

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
			throws BeanCreationException {

		............非關(guān)鍵代碼不貼了

		// Eagerly cache singletons to be able to resolve circular references
		// even when triggered by lifecycle interfaces like BeanFactoryAware.
		//是否	單例bean提前暴露
		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");
			}
			//這里著重理解,對(duì)理解循環(huán)依賴幫助非常大,重要程度 5  添加三級(jí)緩存
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}

		// Initialize the bean instance.
		Object exposedObject = bean;
		try {
			//ioc di,依賴注入的核心方法,該方法必須看,重要程度:5
			populateBean(beanName, mbd, instanceWrapper);

			//bean 實(shí)例化+ioc依賴注入完以后的調(diào)用,非常重要,重要程度:5
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}
		catch (Throwable ex) {
			if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
				throw (BeanCreationException) ex;
			}
			else {
				throw new BeanCreationException(
						mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
			}
		}

		if (earlySingletonExposure) {
			Object earlySingletonReference = getSingleton(beanName, false);
			if (earlySingletonReference != null) {
				if (exposedObject == bean) {
					exposedObject = earlySingletonReference;
				}
				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
					String[] dependentBeans = getDependentBeans(beanName);
					Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
					for (String dependentBean : dependentBeans) {
						if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
							actualDependentBeans.add(dependentBean);
						}
					}
					if (!actualDependentBeans.isEmpty()) {
						throw new BeanCurrentlyInCreationException(beanName,
								"Bean with name '" + beanName + "' has been injected into other beans [" +
								StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
								"] in its raw version as part of a circular reference, but has eventually been " +
								"wrapped. This means that said other beans do not use the final version of the " +
								"bean. This is often the result of over-eager type matching - consider using " +
								"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
					}
				}
			}
		}

		............非關(guān)鍵代碼不貼了

		return exposedObject;
	}

由于業(yè)務(wù)類有循環(huán)依賴


所以在第一次實(shí)例化業(yè)務(wù)類的時(shí)候,在populateBean(beanName, mbd, instanceWrapper);進(jìn)行依賴注入時(shí)會(huì)觸發(fā)TransationServiceImpl業(yè)務(wù)類的getBean操作,也就是會(huì)調(diào)用TransationServiceImpl業(yè)務(wù)類的getBean方法,第二次會(huì)走到TransationServiceImpl實(shí)例化的邏輯中。這里明白的刷朵鮮花敲個(gè)1,哈哈。

但是在觸發(fā)第二次業(yè)務(wù)類的getBean操作之前,還有一個(gè)非常重要的步驟,就是業(yè)務(wù)類的提前暴露,也就是三級(jí)緩存的建立。這塊會(huì)建立業(yè)務(wù)類和ObjectFactory的映射關(guān)系這個(gè)建立映射關(guān)系是在依賴注入之前?。。?!


3、循環(huán)依賴注入觸發(fā)TransationServiceImpl類的第二次getBean獲取實(shí)例化的邏輯
第二次進(jìn)來(lái)的時(shí)候,由于第一次實(shí)例化的時(shí)候在三級(jí)緩存中建立了映射關(guān)系,所以第二次會(huì)從緩存中獲取實(shí)例


ObjectFactory對(duì)象的getObject方法就會(huì)調(diào)用到。getEarlyBeanReference方法,這個(gè)方法是會(huì)從BeanPostProcessor中獲取實(shí)例,這里可能就會(huì)返回代理實(shí)例


三級(jí)緩存的getObject方法會(huì)調(diào)用到getEarlyBeanReference中,斷點(diǎn)一下,看看。


從斷點(diǎn)看,
3:是獲取事務(wù)代理的BeanPostProcessor類型是SmartInstantiationAwareBeanPostProcessor類型的,所以事務(wù)代理的BeanPostProcessor會(huì)進(jìn)來(lái),然后生成代理
4:是獲取@Async異步代理的BeanPostProcessor,但是不是SmartInstantiationAwareBeanPostProcessor類型的,所以這里if就不會(huì)進(jìn)來(lái),所以最后這里從三級(jí)緩存中拿到的是事務(wù)切面的代碼對(duì)象,注意這里是類中的依賴注入的實(shí)例是事務(wù)切面的代理實(shí)例,如圖:


可以看到,這里的advisors切面容器明顯是一個(gè)事務(wù)切面,所以業(yè)務(wù)類中依賴注入的是一個(gè)事務(wù)切面的代理實(shí)例。
但是在這里我還是要說(shuō)一下,在生成事務(wù)代理的時(shí)候其實(shí)是有做緩存的,如下代碼:


這里的cacheKey就是TransationServiceImpl業(yè)務(wù)類的bean的名稱的字符串,然后會(huì)把這個(gè)字符串加入到一個(gè)earlyProxyReferences的Set容器中

在這里已經(jīng)在TransationServiceImpl的第二次getBean的時(shí)候從三級(jí)緩存中獲取到了代理對(duì)象了,那么第二次的實(shí)例化已經(jīng)完成了,并且已經(jīng)依賴注入到了TransationServiceImpl的屬性中了,這時(shí)候依賴注入已經(jīng)完成了,好,我們還是接著第一次TransationServiceImpl的實(shí)例來(lái)講,貼代碼:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
			throws BeanCreationException {

		............非關(guān)鍵代碼不貼了

		// Eagerly cache singletons to be able to resolve circular references
		// even when triggered by lifecycle interfaces like BeanFactoryAware.
		//是否	單例bean提前暴露
		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");
			}
			//這里著重理解,對(duì)理解循環(huán)依賴幫助非常大,重要程度 5  添加三級(jí)緩存
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}

		// Initialize the bean instance.
		Object exposedObject = bean;
		try {
			//ioc di,依賴注入的核心方法,該方法必須看,重要程度:5
			populateBean(beanName, mbd, instanceWrapper);

			//bean 實(shí)例化+ioc依賴注入完以后的調(diào)用,非常重要,重要程度:5
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}
		catch (Throwable ex) {
			if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
				throw (BeanCreationException) ex;
			}
			else {
				throw new BeanCreationException(
						mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
			}
		}

		if (earlySingletonExposure) {
			Object earlySingletonReference = getSingleton(beanName, false);
			if (earlySingletonReference != null) {
				if (exposedObject == bean) {
					exposedObject = earlySingletonReference;
				}
				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
					String[] dependentBeans = getDependentBeans(beanName);
					Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
					for (String dependentBean : dependentBeans) {
						if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
							actualDependentBeans.add(dependentBean);
						}
					}
					if (!actualDependentBeans.isEmpty()) {
						throw new BeanCurrentlyInCreationException(beanName,
								"Bean with name '" + beanName + "' has been injected into other beans [" +
								StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
								"] in its raw version as part of a circular reference, but has eventually been " +
								"wrapped. This means that said other beans do not use the final version of the " +
								"bean. This is often the result of over-eager type matching - consider using " +
								"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
					}
				}
			}
		}

		............非關(guān)鍵代碼不貼了

		return exposedObject;
	}

也就是populateBean(beanName, mbd, instanceWrapper);依賴注入已經(jīng)完成了,代碼接著往下走。
代理會(huì)執(zhí)行到:

//bean 實(shí)例化+ioc依賴注入完以后的調(diào)用,非常重要,重要程度:5
exposedObject = initializeBean(beanName, exposedObject, mbd);

在這里,業(yè)務(wù)類會(huì)在這個(gè)方法里面再次生成代理,這里就有意思了。代碼如下

	protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
		if (System.getSecurityManager() != null) {
			AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
				invokeAwareMethods(beanName, bean);
				return null;
			}, getAccessControlContext());
		}
		else {
			//調(diào)用Aware方法
			invokeAwareMethods(beanName, bean);
		}

		Object wrappedBean = bean;
		if (mbd == null || !mbd.isSynthetic()) {
			//對(duì)類中某些特殊方法的調(diào)用,比如@PostConstruct,Aware接口,非常重要 重要程度 :5
			wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
		}

		try {
			//InitializingBean接口,afterPropertiesSet,init-method屬性調(diào)用,非常重要,重要程度:5
			invokeInitMethods(beanName, wrappedBean, mbd);
		}
		catch (Throwable ex) {
			throw new BeanCreationException(
					(mbd != null ? mbd.getResourceDescription() : null),
					beanName, "Invocation of init method failed", ex);
		}
		if (mbd == null || !mbd.isSynthetic()) {
			//這個(gè)地方可能生出代理實(shí)例,是aop的入口
			wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
		}

		return wrappedBean;
	}

wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
在這個(gè)方法里面可能會(huì)生成業(yè)務(wù)類的代理,我們看看這個(gè)方法:

@Override
	public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
			throws BeansException {

		Object result = existingBean;
		for (BeanPostProcessor processor : getBeanPostProcessors()) {
			Object current = processor.postProcessAfterInitialization(result, beanName);
			if (current == null) {
				return result;
			}
			result = current;
		}
		return result;
	}

我們斷點(diǎn)看看情況


**效果跟我們預(yù)期的一樣,第一次實(shí)例化的時(shí)候,在屬性依賴注入的時(shí)候會(huì)在三級(jí)緩存中獲取事務(wù)的代理對(duì)象,從斷點(diǎn)看,里面的屬性確實(shí)是一個(gè)事務(wù)的代理對(duì)象,自己本身是沒(méi)生成代理的。

由于方法上面有 @Transactional @Async在,3,4兩個(gè)AOP入口的BeanPostProcessor中會(huì)生成相應(yīng)的代理對(duì)象,這里為什么會(huì)生成代理對(duì)象,就不贅述了,核心思想是獲取所有advisors,然后挨個(gè)判斷advisors的pointCut是否matches這兩個(gè)注解,matches的思路是看方法上面是否有@Transactional 或@Async注解,如果有則返回true就匹配了,如果能找到匹配的切面則生成bean的代理,但是這里要注意的是,事務(wù)切面在這里就不會(huì)生成代理了,為什么呢???**看代碼


這里會(huì)判斷earlyProxyReferences的Set容器中是否有這個(gè)cacheKey,這個(gè)cacheKey就是類的名稱,而這個(gè)容器在提前暴露的三級(jí)緩存獲取實(shí)例的時(shí)候就已經(jīng)設(shè)置進(jìn)去了,所以Set容器中是有這個(gè)類的
所以3的AOP入口這里會(huì)原樣返回Bean,如圖:


OK,有意思的來(lái)了,這時(shí)候就輪到4這個(gè)BeanPostProcessor的異步切面的AOP入口執(zhí)行了。如圖:


在這里就返回了bean的異步切面代理,實(shí)例如圖:


我解釋一下這個(gè)截圖內(nèi)容,
exposedObject是異步代理對(duì)象,在targetSource是代理對(duì)象的目標(biāo)對(duì)象,目標(biāo)對(duì)象中有一個(gè)transationService屬性,這個(gè)屬性是一個(gè)事務(wù)的代理對(duì)象,OK,從這里我們發(fā)現(xiàn),我去,一個(gè)同樣的類,居然生成了兩個(gè)不同的代理對(duì)象,一個(gè)是異步的代理對(duì)象,一個(gè)是事務(wù)的代理對(duì)象,代理對(duì)象居然不一致了。為什么會(huì)這樣,前面我已經(jīng)分享得很清楚了

然后在spring中,這種情況默認(rèn)是不被允許的,代碼如下:

if (earlySingletonExposure) {
			Object earlySingletonReference = getSingleton(beanName, false);
			if (earlySingletonReference != null) {
				if (exposedObject == bean) {
					exposedObject = earlySingletonReference;
				}
				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
					String[] dependentBeans = getDependentBeans(beanName);
					Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
					for (String dependentBean : dependentBeans) {
						if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
							actualDependentBeans.add(dependentBean);
						}
					}
					if (!actualDependentBeans.isEmpty()) {
						throw new BeanCurrentlyInCreationException(beanName,
								"Bean with name '" + beanName + "' has been injected into other beans [" +
								StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
								"] in its raw version as part of a circular reference, but has eventually been " +
								"wrapped. This means that said other beans do not use the final version of the " +
								"bean. This is often the result of over-eager type matching - consider using " +
								"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
					}
				}
			}
		}

Object earlySingletonReference = getSingleton(beanName, false);
這里我們前面分析過(guò),這里會(huì)從三級(jí)緩存中獲取到事務(wù)代理對(duì)象

if (exposedObject == bean) {
	exposedObject = earlySingletonReference;
}

然后這里有個(gè)if判斷,bean是第一次實(shí)例化的bean,是沒(méi)被initializeBean代理之前的bean


而exposedObject對(duì)象是一個(gè)異步切面的代理對(duì)象


這里兩者是不相等的,而這個(gè)變量默認(rèn)是allowRawInjectionDespiteWrapping=false的


所有這里就會(huì)拋異常,就是文章前面的那個(gè)異常,所有我們找到了為什么會(huì)有這么一個(gè)異常的出現(xiàn)了。
其實(shí)要解決這個(gè)異常也比較簡(jiǎn)單,只要把a(bǔ)llowRawInjectionDespiteWrapping這個(gè)屬性變成true就行了。
如何變了,代碼如下:


這是這個(gè)變量就為true了 ,就不會(huì)拋異常了


但是就會(huì)存在一個(gè)現(xiàn)象,單元測(cè)試中獲取到的bean對(duì)象和類中依賴注入的對(duì)象不是同一個(gè)了
這個(gè)bean對(duì)象是異步代理對(duì)象


類中屬性的對(duì)象是事務(wù)切面的代理對(duì)象


有意思吧,哈哈 。

如果在類里面沒(méi)有@Async異步注解,其實(shí)就不會(huì)有問(wèn)題,默認(rèn)是允許單例循環(huán)依賴的,為什么沒(méi)問(wèn)題

@Service("transationServiceImpl")
public class TransationServiceImpl implements TransationService {

  @Autowired
  TransationService transationService;

  @Transactional
  @Override
  public void transation() {
    System.out.println(transationService.hashCode());
    System.out.println("s");
  }
}

因?yàn)?/p>

if (earlySingletonExposure) {
			Object earlySingletonReference = getSingleton(beanName, false);
			if (earlySingletonReference != null) {
				if (exposedObject == bean) {
					exposedObject = earlySingletonReference;
				}
				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
					String[] dependentBeans = getDependentBeans(beanName);
					Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
					for (String dependentBean : dependentBeans) {
						if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
							actualDependentBeans.add(dependentBean);
						}
					}
					if (!actualDependentBeans.isEmpty()) {
						throw new BeanCurrentlyInCreationException(beanName,
								"Bean with name '" + beanName + "' has been injected into other beans [" +
								StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
								"] in its raw version as part of a circular reference, but has eventually been " +
								"wrapped. This means that said other beans do not use the final version of the " +
								"bean. This is often the result of over-eager type matching - consider using " +
								"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
					}
				}
			}
		}

如果只要存在循環(huán)依賴,第一次業(yè)務(wù)類實(shí)例化的時(shí)候代理對(duì)象就是從這里獲取的


這個(gè)地方

//bean 實(shí)例化+ioc依賴注入完以后的調(diào)用,非常重要,重要程度:5
			exposedObject = initializeBean(beanName, exposedObject, mbd);

由于三級(jí)緩存中建立了緩存了


所以會(huì)直接返回對(duì)應(yīng)的bean,沒(méi)有生成代理。代理對(duì)象是從這個(gè)獲取的


是從提前暴露的三級(jí)緩存中獲取的代理對(duì)象賦值給了第一次實(shí)例化的bean對(duì)象,所以這個(gè)else if中可能出現(xiàn)異常的地方就不會(huì)走了,因?yàn)檫@兩個(gè)bean exposedObject 和 bean是相等的。

到此這篇關(guān)于帶有@Transactional和@Async的循環(huán)依賴問(wèn)題的解決的文章就介紹到這了,更多相關(guān)@Transactional和@Async的循環(huán)依賴內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 深入淺出MappedByteBuffer(推薦)

    深入淺出MappedByteBuffer(推薦)

    MappedByteBuffer使用虛擬內(nèi)存,因此分配(map)的內(nèi)存大小不受JVM的-Xmx參數(shù)限制,但是也是有大小限制的,這篇文章主要介紹了MappedByteBuffer的基本知識(shí),需要的朋友可以參考下
    2022-12-12
  • java正則替換括號(hào)中的逗號(hào)實(shí)現(xiàn)示例

    java正則替換括號(hào)中的逗號(hào)實(shí)現(xiàn)示例

    本文主要介紹了java正則替換括號(hào)中的逗號(hào)實(shí)現(xiàn)示例,主要介紹了兩種示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2024-01-01
  • Spring Security使用單點(diǎn)登錄的權(quán)限功能

    Spring Security使用單點(diǎn)登錄的權(quán)限功能

    本文主要介紹了Spring Security使用單點(diǎn)登錄的權(quán)限功能,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-04-04
  • Java內(nèi)存區(qū)域和內(nèi)存模型講解

    Java內(nèi)存區(qū)域和內(nèi)存模型講解

    今天小編就為大家分享一篇關(guān)于Java內(nèi)存區(qū)域和內(nèi)存模型講解,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2019-01-01
  • Spring整合Mybatis詳細(xì)步驟

    Spring整合Mybatis詳細(xì)步驟

    今天帶大家來(lái)學(xué)習(xí)Spring怎么整合Mybatis,文中有非常詳細(xì)的代碼示例及介紹,對(duì)正在學(xué)習(xí)java的小伙伴們有很好地幫助,需要的朋友可以參考下
    2021-05-05
  • Java技能點(diǎn)之SimpleDateFormat進(jìn)行日期格式化問(wèn)題

    Java技能點(diǎn)之SimpleDateFormat進(jìn)行日期格式化問(wèn)題

    這篇文章主要介紹了Java技能點(diǎn)之SimpleDateFormat進(jìn)行日期格式化問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-04-04
  • Spring?Boot之Validation自定義實(shí)現(xiàn)方式的總結(jié)

    Spring?Boot之Validation自定義實(shí)現(xiàn)方式的總結(jié)

    這篇文章主要介紹了Spring?Boot之Validation自定義實(shí)現(xiàn)方式的總結(jié),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-07-07
  • 最新評(píng)論