亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

在Spring AOP中代理對象創(chuàng)建的步驟詳解

 更新時間:2023年08月03日 09:37:11   作者:_江南一點雨  
今天和小伙伴們聊一聊 Spring AOP 中的代理對象是怎么創(chuàng)建出來的,透過這個過程再去熟悉一下 Bean 的創(chuàng)建過程,感興趣的小伙伴跟著小編一起來看看吧

1. AOP 用法

先來一個簡單的案例,小伙伴們先回顧一下 AOP,假設我有如下類:

@Service
public class UserService {
    public void hello() {
        System.out.println("hello javaboy");
    }
}

然后我寫一個切面,攔截 UserService 中的方法:

@Component
@Aspect
@EnableAspectJAutoProxy
public class LogAspect {
    @Before("execution(* org.javaboy.bean.aop.UserService.*(..))")
    public void before(JoinPoint jp) {
        String name = jp.getSignature().getName();
        System.out.println(name+" 方法開始執(zhí)行了...");
    }
}

最后,我們看一下從 Spring 容器中獲取到的 UserService 對象:

ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("aop.xml");
UserService us = ctx.getBean(UserService.class);
System.out.println("us.getClass() = " + us.getClass());

打印結果如下:

可以看到,獲取到的 UserService 是一個代理對象。

2. 原理分析

那么注入到 Spring 容器中的 UserService,為什么在獲取的時候變成了一個代理對象,而不是原本的 UserService 了呢?

整體上來說,我們可以將 Spring Bean 的生命周期分為四個階段,分別是:

  • 實例化。
  • 屬性賦值。
  • 初始化。
  • 銷毀。

如下圖:

首先實例化就是通過反射,先把 Bean 的實例創(chuàng)建出來;接下來屬性賦值就是給創(chuàng)建出來的 Bean 的各個屬性賦值;接下來的初始化就是給 Bean 應用上各種需要的后置處理器;最后則是銷毀。

2.1 doCreateBean

AOP 代理對象的創(chuàng)建是在初始化這個過程中完成的,所以今天我們就從初始化這里開始看起。

AbstractAutowireCapableBeanFactory#doCreateBean:

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
		throws BeanCreationException {
    //...
	try {
		populateBean(beanName, mbd, instanceWrapper);
		exposedObject = initializeBean(beanName, exposedObject, mbd);
	}
	//...
	return exposedObject;
}

小伙伴們看到,這里有一個 initializeBean 方法,在這個方法中會對 Bean 執(zhí)行各種后置處理器:

protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
	invokeAwareMethods(beanName, bean);
	Object wrappedBean = bean;
	if (mbd == null || !mbd.isSynthetic()) {
		wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
	}
	try {
		invokeInitMethods(beanName, wrappedBean, mbd);
	}
	catch (Throwable ex) {
		throw new BeanCreationException(
				(mbd != null ? mbd.getResourceDescription() : null), beanName, ex.getMessage(), ex);
	}
	if (mbd == null || !mbd.isSynthetic()) {
		wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
	}
	return wrappedBean;
}

這里一共是執(zhí)行了四個方法,也都是非常常見的 Bean 初始化方法:

  • invokeAwareMethods:執(zhí)行 Aware 接口下的 Bean。
  • applyBeanPostProcessorsBeforeInitialization:執(zhí)行 BeanPostProcessor 中的前置方法。
  • invokeInitMethods:執(zhí)行 Bean 的初始化方法 init。
  • applyBeanPostProcessorsAfterInitialization:執(zhí)行 BeanPostProcessor 中的后置方法。

1、3 這兩個方法很明顯跟 AOP 關系不大,我們自己平時創(chuàng)建的 AOP 對象基本上都是在 applyBeanPostProcessorsAfterInitialization 中進行處理的,我們來看下這個方法:

@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;
}

小伙伴們看到,這里就是遍歷各種 BeanPostProcessor,并執(zhí)行其 postProcessAfterInitialization 方法,將執(zhí)行結果賦值給 result 并返回。

2.2 postProcessAfterInitialization

BeanPostProcessor 有一個實現(xiàn)類 AbstractAutoProxyCreator,在 AbstractAutoProxyCreator 的 postProcessAfterInitialization 方法中,進行了 AOP 的處理:

@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
	if (bean != null) {
		Object cacheKey = getCacheKey(bean.getClass(), beanName);
		if (this.earlyProxyReferences.remove(cacheKey) != bean) {
			return wrapIfNecessary(bean, beanName, cacheKey);
		}
	}
	return bean;
}
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
	if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
		return bean;
	}
	if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
		return bean;
	}
	if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
		this.advisedBeans.put(cacheKey, Boolean.FALSE);
		return bean;
	}
	// Create proxy if we have advice.
	Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
	if (specificInterceptors != DO_NOT_PROXY) {
		this.advisedBeans.put(cacheKey, Boolean.TRUE);
		Object proxy = createProxy(
				bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
		this.proxyTypes.put(cacheKey, proxy.getClass());
		return proxy;
	}
	this.advisedBeans.put(cacheKey, Boolean.FALSE);
	return bean;
}

可以看到,首先會嘗試去緩存中獲取代理對象,如果緩存中沒有的話,則會調用 wrapIfNecessary 方法進行 AOP 的創(chuàng)建。

正常來說,普通 AOP 的創(chuàng)建,前面三個 if 的條件都是不滿足的。第一個 if 是說 beanName 是否是一個 targetSource,顯然我們這里不是;第二個 if 是說這個 Bean 是不是不需代理,我們這里顯然是需要代理的。

關于第二個 if 我多說一句,如果這里進來的是一個切面的 Bean,例如第一小節(jié)中的 LogAspect,這種 Bean 顯然是不需要代理的,所以會在第二個方法中直接返回,如果是其他普通的 Bean,則第二個 if 并不會進來。

所在在 wrapIfNecessary 中,最重要的方法實際上就是兩個:getAdvicesAndAdvisorsForBean 和 createProxy,前者用來找出來所有跟當前類匹配的切面,后者則用來創(chuàng)建代理對象。

2.3 getAdvicesAndAdvisorsForBean

這個方法,說白了,就是查找各種 Advice(通知/增強) 和 Advisor(切面)。來看下到底怎么找的:

AbstractAdvisorAutoProxyCreator#getAdvicesAndAdvisorsForBean:

@Override
@Nullable
protected Object[] getAdvicesAndAdvisorsForBean(
		Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {
	List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
	if (advisors.isEmpty()) {
		return DO_NOT_PROXY;
	}
	return advisors.toArray();
}

從這里可看到,這個方法主要就是調用 findEligibleAdvisors 去獲取到所有的切面,繼續(xù):

protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
	List<Advisor> candidateAdvisors = findCandidateAdvisors();
	List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
	extendAdvisors(eligibleAdvisors);
	if (!eligibleAdvisors.isEmpty()) {
		eligibleAdvisors = sortAdvisors(eligibleAdvisors);
	}
	return eligibleAdvisors;
}

這里一共有三個主要方法:

  • findCandidateAdvisors:這個方法是查詢到所有候選的 Advisor,說白了,就是把項目啟動時注冊到 Spring 容器中所有切面都找到,由于一個 Aspect 中可能存在多個 Advice,每個 Advice 最終都能封裝為一個 Advisor,所以在具體查找過程中,找到 Aspect Bean 之后,還需要遍歷 Bean 中的方法。
  • findAdvisorsThatCanApply:這個方法主要是從上個方法找到的所有切面中,根據(jù)切點過濾出來能夠應用到當前 Bean 的切面。
  • extendAdvisors:這個是添加一個 DefaultPointcutAdvisor 切面進來,這個切面使用的 Advice 是 ExposeInvocationInterceptor,ExposeInvocationInterceptor 的作用是用于暴露 MethodInvocation 對象到 ThreadLocal 中,如果其他地方需要使用當前的 MethodInvocation 對象,直接通過調用 currentInvocation 方法取出即可。

接下來我們就來看一下這三個方法的具體實現(xiàn)。

2.3.1 findCandidateAdvisors

AnnotationAwareAspectJAutoProxyCreator#findCandidateAdvisors

@Override
protected List<Advisor> findCandidateAdvisors() {
	List<Advisor> advisors = super.findCandidateAdvisors();
	if (this.aspectJAdvisorsBuilder != null) {
		advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
	}
	return advisors;
}

這個方法的關鍵在于通過 buildAspectJAdvisors 構建出所有的切面,這個方法有點復雜:

public List<Advisor> buildAspectJAdvisors() {
	List<String> aspectNames = this.aspectBeanNames;
	if (aspectNames == null) {
		synchronized (this) {
			aspectNames = this.aspectBeanNames;
			if (aspectNames == null) {
				List<Advisor> advisors = new ArrayList<>();
				aspectNames = new ArrayList<>();
				String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
						this.beanFactory, Object.class, true, false);
				for (String beanName : beanNames) {
					if (!isEligibleBean(beanName)) {
						continue;
					}
					// We must be careful not to instantiate beans eagerly as in this case they
					// would be cached by the Spring container but would not have been weaved.
					Class<?> beanType = this.beanFactory.getType(beanName, false);
					if (beanType == null) {
						continue;
					}
					if (this.advisorFactory.isAspect(beanType)) {
						aspectNames.add(beanName);
						AspectMetadata amd = new AspectMetadata(beanType, beanName);
						if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
							MetadataAwareAspectInstanceFactory factory =
									new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
							List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
							if (this.beanFactory.isSingleton(beanName)) {
								this.advisorsCache.put(beanName, classAdvisors);
							}
							else {
								this.aspectFactoryCache.put(beanName, factory);
							}
							advisors.addAll(classAdvisors);
						}
						else {
							// Per target or per this.
							if (this.beanFactory.isSingleton(beanName)) {
								throw new IllegalArgumentException("Bean with name '" + beanName +
										"' is a singleton, but aspect instantiation model is not singleton");
							}
							MetadataAwareAspectInstanceFactory factory =
									new PrototypeAspectInstanceFactory(this.beanFactory, beanName);
							this.aspectFactoryCache.put(beanName, factory);
							advisors.addAll(this.advisorFactory.getAdvisors(factory));
						}
					}
				}
				this.aspectBeanNames = aspectNames;
				return advisors;
			}
		}
	}
	if (aspectNames.isEmpty()) {
		return Collections.emptyList();
	}
	List<Advisor> advisors = new ArrayList<>();
	for (String aspectName : aspectNames) {
		List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName);
		if (cachedAdvisors != null) {
			advisors.addAll(cachedAdvisors);
		}
		else {
			MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName);
			advisors.addAll(this.advisorFactory.getAdvisors(factory));
		}
	}
	return advisors;
}

這個方法第一次進來的時候,aspectNames 變量是沒有值的,所以會先進入到 if 分支中,給 aspectNames 和 aspectBeanNames 兩個變量賦值。

具體過程就是首先調用 BeanFactoryUtils.beanNamesForTypeIncludingAncestors 方法,去當前容器以及當前容器的父容器中,查找到所有的 beanName,將返回的數(shù)組賦值給 beanNames 變量,然后對 beanNames 進行遍歷。

遍歷時,首先調用 isEligibleBean 方法,這個方法是檢查給定名稱的 Bean 是否符合自動代理的條件的,這個細節(jié)我們就不看了,因為一般情況下,我們項目中的 AOP 都是自動代理的。

接下來根據(jù) beanName,找到對應的 bean 類型 beanType,然后調用 advisorFactory.isAspect 方法去判斷這個 beanType 是否是一個 Aspect。

如果當前 beanName 對應的 Bean 是一個 Aspect,那么就把 beanName 添加到 aspectNames 集合中,并且把 beanName 和 beanType 封裝為一個 AspectMetadata 對象。

接下來會去判斷 kind 是否為 SINGLETON,這個默認都是 SINGLETON,所以這里會進入到分支中,進來之后,會調用 this.advisorFactory.getAdvisors 方法去 Aspect 中找到各種通知和切點并封裝成 Advisor 對象返回,由于一個切面中可能定義多個通知,所以最終返回的 Advisor 是一個集合,最后把找到的 Advisor 集合存入到 advisorsCache 緩存中。

后面方法的邏輯就很好懂了,從 advisorsCache 中找到某一個 aspect 對應的所有 Advisor,并將之存入到 advisors 集合中,然后返回集合。

這樣,我們就找到了所有的 Advisor。

2.3.2 findAdvisorsThatCanApply

接下來 findAdvisorsThatCanApply 方法主要是從眾多的 Advisor 中,找到能匹配上當前 Bean 的 Advisor,小伙伴們知道,每一個 Advisor 都包含一個切點 Pointcut,不同的切點意味著不同的攔截規(guī)則,所以現(xiàn)在需要進行匹配,檢查當前類需要和哪個 Advisor 匹配:

protected List<Advisor> findAdvisorsThatCanApply(
		List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {
	ProxyCreationContext.setCurrentProxiedBeanName(beanName);
	try {
		return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
	}
	finally {
		ProxyCreationContext.setCurrentProxiedBeanName(null);
	}
}

這里實際上就是調用了靜態(tài)方法 AopUtils.findAdvisorsThatCanApply 去查找匹配的 Advisor:

public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
	if (candidateAdvisors.isEmpty()) {
		return candidateAdvisors;
	}
	List<Advisor> eligibleAdvisors = new ArrayList<>();
	for (Advisor candidate : candidateAdvisors) {
		if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
			eligibleAdvisors.add(candidate);
		}
	}
	boolean hasIntroductions = !eligibleAdvisors.isEmpty();
	for (Advisor candidate : candidateAdvisors) {
		if (candidate instanceof IntroductionAdvisor) {
			// already processed
			continue;
		}
		if (canApply(candidate, clazz, hasIntroductions)) {
			eligibleAdvisors.add(candidate);
		}
	}
	return eligibleAdvisors;
}

這個方法中首先會去判斷 Advisor 的類型是否是 IntroductionAdvisor 類型,IntroductionAdvisor 類型的 Advisor 只能在類級別進行攔截,靈活度不如 PointcutAdvisor,所以我們一般都不是 IntroductionAdvisor,因此這里最終會走入到最后一個分支中:

public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {
	if (advisor instanceof IntroductionAdvisor ia) {
		return ia.getClassFilter().matches(targetClass);
	}
	else if (advisor instanceof PointcutAdvisor pca) {
		return canApply(pca.getPointcut(), targetClass, hasIntroductions);
	}
	else {
		// It doesn't have a pointcut so we assume it applies.
		return true;
	}
}

從這里小伙伴們就能看到,IntroductionAdvisor 類型的 Advisor 只需要調用 ClassFilter 過濾一下就行了,小伙伴們看這里的匹配邏輯也是非常 easy!而 PointcutAdvisor 類型的 Advisor 則會繼續(xù)調用 canApply 方法進行判斷:

public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
	if (!pc.getClassFilter().matches(targetClass)) {
		return false;
	}
	MethodMatcher methodMatcher = pc.getMethodMatcher();
	if (methodMatcher == MethodMatcher.TRUE) {
		// No need to iterate the methods if we're matching any method anyway...
		return true;
	}
	IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
	if (methodMatcher instanceof IntroductionAwareMethodMatcher iamm) {
		introductionAwareMethodMatcher = iamm;
	}
	Set<Class<?>> classes = new LinkedHashSet<>();
	if (!Proxy.isProxyClass(targetClass)) {
		classes.add(ClassUtils.getUserClass(targetClass));
	}
	classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
	for (Class<?> clazz : classes) {
		Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
		for (Method method : methods) {
			if (introductionAwareMethodMatcher != null ?
					introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :
					methodMatcher.matches(method, targetClass)) {
				return true;
			}
		}
	}
	return false;
}

小伙伴們看一下,這里就是先按照類去匹配,匹配通過則繼續(xù)按照方法去匹配,方法匹配器要是設置的 true,那就直接返回 true 就行了,否則就加載當前類,也就是 targetClass,然后遍歷 targetClass 中的所有方法,最后調用 introductionAwareMethodMatcher.matches 方法去判斷方法是否和切點契合。

就這樣,我們就從所有的 Advisor 中找到了所有和當前類匹配的 Advisor 了。

2.3.3 extendAdvisors

這個是添加一個 DefaultPointcutAdvisor 切面進來,這個切面使用的 Advice 是 ExposeInvocationInterceptor,ExposeInvocationInterceptor 的作用是用于暴露 MethodInvocation 對象到 ThreadLocal 中,如果其他地方需要使用當前的 MethodInvocation 對象,直接通過調用 currentInvocation 方法取出即可。

這個方法的邏輯比較簡單,我就不貼出來了,小伙伴們可以自行查看。

2.4 createProxy

看完了 getAdvicesAndAdvisorsForBean 方法,我們已經(jīng)找到了適合我們的 Advisor,接下來繼續(xù)看 createProxy 方法,這個方法用來創(chuàng)建一個代理對象:

protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
		@Nullable Object[] specificInterceptors, TargetSource targetSource) {
	return buildProxy(beanClass, beanName, specificInterceptors, targetSource, false);
}
private Object buildProxy(Class<?> beanClass, @Nullable String beanName,
		@Nullable Object[] specificInterceptors, TargetSource targetSource, boolean classOnly) {
	if (this.beanFactory instanceof ConfigurableListableBeanFactory clbf) {
		AutoProxyUtils.exposeTargetClass(clbf, beanName, beanClass);
	}
	ProxyFactory proxyFactory = new ProxyFactory();
	proxyFactory.copyFrom(this);
	if (proxyFactory.isProxyTargetClass()) {
		// Explicit handling of JDK proxy targets and lambdas (for introduction advice scenarios)
		if (Proxy.isProxyClass(beanClass) || ClassUtils.isLambdaClass(beanClass)) {
			// Must allow for introductions; can't just set interfaces to the proxy's interfaces only.
			for (Class<?> ifc : beanClass.getInterfaces()) {
				proxyFactory.addInterface(ifc);
			}
		}
	}
	else {
		// No proxyTargetClass flag enforced, let's apply our default checks...
		if (shouldProxyTargetClass(beanClass, beanName)) {
			proxyFactory.setProxyTargetClass(true);
		}
		else {
			evaluateProxyInterfaces(beanClass, proxyFactory);
		}
	}
	Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
	proxyFactory.addAdvisors(advisors);
	proxyFactory.setTargetSource(targetSource);
	customizeProxyFactory(proxyFactory);
	proxyFactory.setFrozen(this.freezeProxy);
	if (advisorsPreFiltered()) {
		proxyFactory.setPreFiltered(true);
	}
	// Use original ClassLoader if bean class not locally loaded in overriding class loader
	ClassLoader classLoader = getProxyClassLoader();
	if (classLoader instanceof SmartClassLoader smartClassLoader && classLoader != beanClass.getClassLoader()) {
		classLoader = smartClassLoader.getOriginalClassLoader();
	}
	return (classOnly ? proxyFactory.getProxyClass(classLoader) : proxyFactory.getProxy(classLoader));
}

好啦,經(jīng)過上面這一頓操作,代理對象就創(chuàng)建出來了~本文是一個大致的邏輯,還有一些特別細的小細節(jié)沒和小伙伴們梳理。

以上就是在Spring AOP中代理對象創(chuàng)建的步驟詳解的詳細內容,更多關于Spring AOP創(chuàng)建代理對象的資料請關注腳本之家其它相關文章!

相關文章

  • Java mutable對象和immutable對象的區(qū)別說明

    Java mutable對象和immutable對象的區(qū)別說明

    這篇文章主要介紹了Java mutable對象和immutable對象的區(qū)別,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-06-06
  • 關于SpringCloud中Ribbon的7種負載均衡策略解析

    關于SpringCloud中Ribbon的7種負載均衡策略解析

    這篇文章主要介紹了關于SpringCloud中Ribbon的7種負載均衡策略解析,服務端負載均衡器的問題是,它提供了更強的流量控制權,但無法滿足不同的消費者希望使用不同負載均衡策略的需求,而使用不同負載均衡策略的場景確實是存在的,需要的朋友可以參考下
    2023-07-07
  • Spring jndi數(shù)據(jù)源配置方法詳解

    Spring jndi數(shù)據(jù)源配置方法詳解

    這篇文章主要為大家詳細介紹了Spring jndi數(shù)據(jù)源的配置方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下解
    2017-07-07
  • Java?String源碼contains題解重復疊加字符串匹配

    Java?String源碼contains題解重復疊加字符串匹配

    這篇文章主要為大家介紹了Java?String源碼contains題解重復疊加字符串匹配示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-11-11
  • Spring boot跨域設置實例詳解

    Spring boot跨域設置實例詳解

    這篇文章主要介紹了Spring boot跨域設置實例詳解,簡單介紹了跨域的定義,原因,使用場景及解決方案,具有一定參考價值,需要的朋友可以了解下。
    2017-11-11
  • java開發(fā)微服務架構設計消息隊列的水有多深

    java開發(fā)微服務架構設計消息隊列的水有多深

    今天我們說說消息隊列的問題,來帶大家探一探消息隊列的水有多深,希望看完本文大家在引入消息隊列的時候先想一想,是不是一定要引入?引入消息隊列后產(chǎn)生的問題能不能解決
    2021-10-10
  • java連接SQL?Server數(shù)據(jù)庫的超詳細教程

    java連接SQL?Server數(shù)據(jù)庫的超詳細教程

    最近在java連接SQL數(shù)據(jù)庫時會出現(xiàn)一些問題,所以這篇文章主要給大家介紹了關于java連接SQL?Server數(shù)據(jù)庫的超詳細教程,文中通過圖文介紹的非常詳細,需要的朋友可以參考下
    2022-06-06
  • 詳解Java刪除Map中元素java.util.ConcurrentModificationException”異常解決

    詳解Java刪除Map中元素java.util.ConcurrentModificationException”異常解決

    這篇文章主要介紹了詳解Java刪除Map中元素java.util.ConcurrentModificationException”異常解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-01-01
  • Netty搭建WebSocket服務器實戰(zhàn)教程

    Netty搭建WebSocket服務器實戰(zhàn)教程

    這篇文章主要介紹了Netty搭建WebSocket服務器實戰(zhàn),本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2024-03-03
  • SpringBoot整合RabbitMQ實現(xiàn)延遲隊列的示例詳解

    SpringBoot整合RabbitMQ實現(xiàn)延遲隊列的示例詳解

    這篇文章主要為大家詳細介紹了SpringBoot如何整合RabbitMQ實現(xiàn)延遲隊列,文中的示例代碼講解詳細,具有一定的學習價值,感興趣的可以了解一下
    2023-04-04

最新評論