最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

關(guān)于Spring源碼深度解析(AOP功能源碼解析)

 更新時(shí)間:2023年07月27日 10:31:16   作者:恐龍弟旺仔  
這篇文章主要介紹了關(guān)于Spring源碼深度解析(AOP功能源碼解析),具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

前言

有關(guān)于Spring,我們最常用的兩個(gè)功能就是IOC和AOP,前幾篇文章從源碼級別介紹了Spring容器如何為我們生成bean及bean之間的依賴關(guān)系

下面我們接著來看AOP的源碼實(shí)現(xiàn)。

有關(guān)于AOP,我們在面試中也被無數(shù)次問到,AOP是什么?AOP有什么作用與優(yōu)勢?AOP在項(xiàng)目中是如何用到的?

這些還都是比較簡單的,有些可能會問你AOP的實(shí)現(xiàn)是怎樣的?

哪怕沒有看過源碼的同學(xué)也應(yīng)該知道,AOP是通過動(dòng)態(tài)代理實(shí)現(xiàn)的,動(dòng)態(tài)代理又分為兩個(gè)部分:JDK動(dòng)態(tài)代理和CGLIB動(dòng)態(tài)代理

確實(shí),Spring也就是通過這兩種方式來實(shí)現(xiàn)AOP相關(guān)功能,下面就通過源碼來簡單求證下

1.AOP功能簡單實(shí)現(xiàn)

1)引入maven依賴(筆者使用SpringBoot開發(fā))

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.3.RELEASE</version>
</parent>	   
<dependency>      
    <groupId>org.springframework.boot</groupId>  
    <artifactId>spring-boot-starter-aop</artifactId>  
</dependency> 

2)創(chuàng)建接口及其實(shí)現(xiàn)類

public interface Person {
	void say();
}
public class Student implements Person{
	public void say(){
		System.out.println("這是一個(gè)苦逼的程序員");
	}
}

3)創(chuàng)建切面類

@Aspect
public class AspectJTest {
	@Pointcut("execution(* *.say(..))")
	public void test(){}
	@Before("test()")
	public void before(){
		System.out.println("before test..");
	}
	@After("test()")
	public void after(){
		System.out.println("after test..");
	}
	@Around("test()")
	public Object around(ProceedingJoinPoint p){
		System.out.println("before1");
		Object o = null;
		try {
			o = p.proceed();
		} catch (Throwable e) {
			e.printStackTrace();
		}
		System.out.println("after1");
		return o;
	}
}

4)創(chuàng)建beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
	<aop:aspectj-autoproxy/>
    <bean id="student" class="test.Student"/>
	<bean class="test.AspectJTest"/>
</beans>

5)測試類

public class Test {
	public static void main(String[] args) {
		ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
		Person bean2 = (Person)ac.getBean("student");
		bean2.say();
	}
    // 結(jié)果如下:
    before1
    before test..
    這是一個(gè)苦逼的程序員
    after1
    after test..

總結(jié):AOP功能的使用還是比較簡單的,把相關(guān)bean注入到Spring容器中,編寫好相應(yīng)的Aspect類即可

2.寫在分析AOP功能源碼之前

1)在使用ApplicationContext相關(guān)實(shí)現(xiàn)類加載bean的時(shí)候,會針對所有單例且非懶加載的bean,在構(gòu)造ApplicationContext的時(shí)候就會創(chuàng)建好這些bean,而不會等到使用的時(shí)候才去創(chuàng)建。這也就是單例bean默認(rèn)非懶加載的應(yīng)用

2)讀者需要了解BeanPostProcessor的相關(guān)使用,所有實(shí)現(xiàn)BeanPostProcessor接口的類,在初始化bean的時(shí)候都會調(diào)用這些類的方法,一般用于在bean初始化前或后對bean做一些修改。而AOP的功能實(shí)現(xiàn)正式基于此,在bean初始化后創(chuàng)建針對該bean的proxy,然后返回給用戶該proxy

3)結(jié)合以上兩點(diǎn),被代理后的bean,實(shí)際在ApplicationContext構(gòu)造完成之后就已經(jīng)被創(chuàng)建完成,getBean()的操作直接從singletonObjects中獲取即可

3.AOP源碼架構(gòu)分析    

1)尋找 <aop:aspectj-autoproxy/> 注解對應(yīng)的解析器

但凡注解都有對應(yīng)的解析器,以用來解析該注解的行為。全局搜索之后可發(fā)現(xiàn)

org.springframework.aop.config.AopNamespaceHandler類中有對應(yīng)的解析行為,代碼如下:
public class AopNamespaceHandler extends NamespaceHandlerSupport {
	@Override
	public void init() {
		// In 2.0 XSD as well as in 2.1 XSD.
		registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
		registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());// 就是該段代碼
		registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
		// Only in 2.0 XSD: moved to context namespace as of 2.1
		registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
	}
}

2)了解AspectJAutoProxyBeanDefinitionParser對應(yīng)的行為

class AspectJAutoProxyBeanDefinitionParser implements BeanDefinitionParser {
	@Override
	public BeanDefinition parse(Element element, ParserContext parserContext) {
        // 1.注冊proxy creator
		AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
		extendBeanDefinition(element, parserContext);
		return null;
	}
    ...
    // registerAspectJAnnotationAutoProxyCreatorIfNecessary()
    public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(
			ParserContext parserContext, Element sourceElement) {
        // 注冊行為主要內(nèi)容
		BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(
				parserContext.getRegistry(), parserContext.extractSource(sourceElement));
		useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
		registerComponentIfNecessary(beanDefinition, parserContext);
	}
    // registerAspectJAnnotationAutoProxyCreatorIfNecessary()
	public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, Object source) {
        // 主要就是為了注冊AnnotationAwareAspectJAutoProxyCreator類
		return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
	}
    // 注冊類相關(guān)代碼
    private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry, Object source) {
		Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
		if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
			BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
			if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
				int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
				int requiredPriority = findPriorityForClass(cls);
				if (currentPriority < requiredPriority) {
					apcDefinition.setBeanClassName(cls.getName());
				}
			}
			return null;
		}
        // 類似于我們在使用BeanFactory.getBean()時(shí)候的操作,生成一個(gè)RootBeanDefinition,然后放入map中
		RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
		beanDefinition.setSource(source);
		beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
		beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
		return beanDefinition;
	}

總結(jié):通過以上的代碼分析,可知,AspectJAutoProxyBeanDefinitionParser主要的功能就是將AnnotationAwareAspectJAutoProxyCreator注冊到Spring容器中,把bean交給Spring去托管。

AnnotationAwareAspectJAutoProxyCreator的功能我們大膽猜測一下:應(yīng)該也就是生成對象的代理類的相關(guān)功能,這個(gè)我們接下來再看。

問題:

那么問題來了,我們最開始的類AopNamespaceHandler.init()方法是在什么時(shí)候被調(diào)用的呢?什么時(shí)候生效的?這個(gè)決定了我們注冊到Spring的AnnotationAwareAspectJAutoProxyCreator的生效時(shí)間?讀者可自行思考下。

3)分析AnnotationAwareAspectJAutoProxyCreator主要行為

通過查看AnnotationAwareAspectJAutoProxyCreator的類層次結(jié)構(gòu),可知,其實(shí)現(xiàn)了BeanPostProcessor接口,實(shí)現(xiàn)類為AbstractAutoProxyCreator

類層次結(jié)構(gòu)如下:

  

4)AbstractAutoProxyCreator主要方法

@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) {
		return bean;
	}
    // 主要看這個(gè)方法,在bean初始化之后對生產(chǎn)出的bean進(jìn)行包裝
	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		if (bean != null) {
			Object cacheKey = getCacheKey(bean.getClass(), beanName);
			if (!this.earlyProxyReferences.contains(cacheKey)) {
				return wrapIfNecessary(bean, beanName, cacheKey);
			}
		}
		return bean;
	}
    // wrapIfNecessary
	protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
		if (beanName != null && 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.
        // 意思就是如果該類有advice則創(chuàng)建proxy,
		Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
		if (specificInterceptors != DO_NOT_PROXY) {
			this.advisedBeans.put(cacheKey, Boolean.TRUE);
            // 1.通過方法名也能簡單猜測到,這個(gè)方法就是把bean包裝為proxy的主要方法,
			Object proxy = createProxy(
					bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
			this.proxyTypes.put(cacheKey, proxy.getClass());
            // 2.返回該proxy代替原來的bean
			return proxy;
		}
		this.advisedBeans.put(cacheKey, Boolean.FALSE);
		return bean;
	}

總結(jié):

1)通過AspectJAutoProxyBeanDefinitionParser類將AnnotationAwareAspectJAutoProxyCreator注冊到Spring容器中

2)AnnotationAwareAspectJAutoProxyCreator類的postProcessAfterInitialization()方法將所有有advice的bean重新包裝成proxy

4.創(chuàng)建proxy過程分析

通過之前的代碼結(jié)構(gòu)分析,我們知道,所有的bean在返回給用戶使用之前都需要經(jīng)過AnnotationAwareAspectJAutoProxyCreator類的postProcessAfterInitialization()方法,而該方法的主要作用也就是將所有擁有advice的bean重新包裝為proxy,那么我們接下來直接分析這個(gè)包裝為proxy的方法即可,看一下bean如何被包裝為proxy,proxy在被調(diào)用方法時(shí),是具體如何執(zhí)行的

以下是AbstractAutoProxyCreator.wrapIfNecessary(Object bean, String beanName, Object cacheKey)中的createProxy()代碼片段分析

protected Object createProxy(
			Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) {
		if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
			AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
		}
        // 1.創(chuàng)建proxyFactory,proxy的生產(chǎn)主要就是在proxyFactory做的
		ProxyFactory proxyFactory = new ProxyFactory();
		proxyFactory.copyFrom(this);
		if (!proxyFactory.isProxyTargetClass()) {
			if (shouldProxyTargetClass(beanClass, beanName)) {
				proxyFactory.setProxyTargetClass(true);
			}
			else {
				evaluateProxyInterfaces(beanClass, proxyFactory);
			}
		}
        // 2.將當(dāng)前bean適合的advice,重新封裝下,封裝為Advisor類,然后添加到ProxyFactory中
		Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
		for (Advisor advisor : advisors) {
			proxyFactory.addAdvisor(advisor);
		}
		proxyFactory.setTargetSource(targetSource);
		customizeProxyFactory(proxyFactory);
		proxyFactory.setFrozen(this.freezeProxy);
		if (advisorsPreFiltered()) {
			proxyFactory.setPreFiltered(true);
		}
        // 3.調(diào)用getProxy獲取bean對應(yīng)的proxy
		return proxyFactory.getProxy(getProxyClassLoader());
	}

1)創(chuàng)建何種類型的Proxy?JDKProxy還是CGLIBProxy?

    // getProxy()方法
	public Object getProxy(ClassLoader classLoader) {
		return createAopProxy().getProxy(classLoader);
	}
    // createAopProxy()方法就是決定究竟創(chuàng)建何種類型的proxy
	protected final synchronized AopProxy createAopProxy() {
		if (!this.active) {
			activate();
		}
        // 關(guān)鍵方法createAopProxy()
		return getAopProxyFactory().createAopProxy(this);
	}
    // createAopProxy()
	public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
        // 1.config.isOptimize()是否使用優(yōu)化的代理策略,目前使用與CGLIB
        // config.isProxyTargetClass() 是否目標(biāo)類本身被代理而不是目標(biāo)類的接口
        // hasNoUserSuppliedProxyInterfaces()是否存在代理接口
		if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
			Class<?> targetClass = config.getTargetClass();
			if (targetClass == null) {
				throw new AopConfigException("TargetSource cannot determine target class: " +
						"Either an interface or a target is required for proxy creation.");
			}
            // 2.如果目標(biāo)類是接口或者是代理類,則直接使用JDKproxy
			if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
				return new JdkDynamicAopProxy(config);
			}
            // 3.其他情況則使用CGLIBproxy
			return new ObjenesisCglibAopProxy(config);
		}
		else {
			return new JdkDynamicAopProxy(config);
		}
	}

2)getProxy()方法

由1)可知,通過createAopProxy()方法來確定具體使用何種類型的Proxy

針對于該示例,我們具體使用的為JdkDynamicAopProxy,下面來看下JdkDynamicAopProxy.getProxy()方法

final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable// JdkDynamicAopProxy類結(jié)構(gòu),由此可知,其實(shí)現(xiàn)了InvocationHandler,則必定有invoke方法,來被調(diào)用,也就是用戶調(diào)用bean相關(guān)方法時(shí),此invoke()被真正調(diào)用
    // getProxy()
    public Object getProxy(ClassLoader classLoader) {
		if (logger.isDebugEnabled()) {
			logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
		}
		Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
		findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
        // JDK proxy 動(dòng)態(tài)代理的標(biāo)準(zhǔn)用法
		return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
	}

3)invoke()方法

以上的代碼模式可以很明確的看出來,使用了JDK動(dòng)態(tài)代理模式,真正的方法執(zhí)行在invoke()方法里,下面我們來看下該方法,來看下bean方法如何被代理執(zhí)行的

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		MethodInvocation invocation;
		Object oldProxy = null;
		boolean setProxyContext = false;
		TargetSource targetSource = this.advised.targetSource;
		Class<?> targetClass = null;
		Object target = null;
		try {
            // 1.以下的幾個(gè)判斷,主要是為了判斷method是否為equals、hashCode等Object的方法
			if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
				// The target does not implement the equals(Object) method itself.
				return equals(args[0]);
			}
			else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
				// The target does not implement the hashCode() method itself.
				return hashCode();
			}
			else if (method.getDeclaringClass() == DecoratingProxy.class) {
				// There is only getDecoratedClass() declared -> dispatch to proxy config.
				return AopProxyUtils.ultimateTargetClass(this.advised);
			}
			else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
					method.getDeclaringClass().isAssignableFrom(Advised.class)) {
				// Service invocations on ProxyConfig with the proxy config...
				return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
			}
			Object retVal;
			if (this.advised.exposeProxy) {
				// Make invocation available if necessary.
				oldProxy = AopContext.setCurrentProxy(proxy);
				setProxyContext = true;
			}
			// May be null. Get as late as possible to minimize the time we "own" the target,
			// in case it comes from a pool.
			target = targetSource.getTarget();
			if (target != null) {
				targetClass = target.getClass();
			}
			// 2.獲取當(dāng)前bean被攔截方法鏈表
			List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
			// 3.如果為空,則直接調(diào)用target的method
			if (chain.isEmpty()) {
				Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
				retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
			}
            // 4.不為空,則逐一調(diào)用chain中的每一個(gè)攔截方法的proceed
			else {
				// We need to create a method invocation...
				invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
				// Proceed to the joinpoint through the interceptor chain.
				retVal = invocation.proceed();
			}
			...
			return retVal;
		}
		...
	}

4)攔截方法真正被執(zhí)行調(diào)用invocation.proceed()

public Object proceed() throws Throwable {
		//	We start with an index of -1 and increment early.
		if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
			return invokeJoinpoint();
		}
		Object interceptorOrInterceptionAdvice =
				this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
		if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
			// Evaluate dynamic method matcher here: static part will already have
			// been evaluated and found to match.
			InterceptorAndDynamicMethodMatcher dm =
					(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
			if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
				return dm.interceptor.invoke(this);
			}
			else {
				// Dynamic matching failed.
				// Skip this interceptor and invoke the next in the chain.
				return proceed();
			}
		}
		else {
			// It's an interceptor, so we just invoke it: The pointcut will have
			// been evaluated statically before this object was constructed.
			return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
		}
	}

總結(jié)4:依次遍歷攔截器鏈的每個(gè)元素,然后調(diào)用其實(shí)現(xiàn),將真正調(diào)用工作委托給各個(gè)增強(qiáng)器    

心得

縱觀以上過程可知:實(shí)際就是為bean創(chuàng)建一個(gè)proxy,JDKproxy或者CGLIBproxy,然后在調(diào)用bean的方法時(shí),會通過proxy來調(diào)用bean方法

重點(diǎn)過程可分為:

1)通過AspectJAutoProxyBeanDefinitionParser類將AnnotationAwareAspectJAutoProxyCreator注冊到Spring容器中

2)AnnotationAwareAspectJAutoProxyCreator類的postProcessAfterInitialization()方法將所有有advice的bean重新包裝成proxy

3)調(diào)用bean方法時(shí)通過proxy來調(diào)用,proxy依次調(diào)用增強(qiáng)器的相關(guān)方法,來實(shí)現(xiàn)方法切入

總結(jié)

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

相關(guān)文章

  • IDEA如何在當(dāng)前類中查找方法快捷鍵

    IDEA如何在當(dāng)前類中查找方法快捷鍵

    這篇文章主要介紹了IDEA如何在當(dāng)前類中查找方法快捷鍵問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-11-11
  • java數(shù)據(jù)庫批量插入數(shù)據(jù)的實(shí)現(xiàn)

    java數(shù)據(jù)庫批量插入數(shù)據(jù)的實(shí)現(xiàn)

    本文主要介紹了java數(shù)據(jù)庫批量插入數(shù)據(jù)的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-05-05
  • 詳細(xì)分析Java并發(fā)集合ArrayBlockingQueue的用法

    詳細(xì)分析Java并發(fā)集合ArrayBlockingQueue的用法

    這篇文章主要介紹了詳細(xì)分析Java并發(fā)集合ArrayBlockingQueue的用法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-04-04
  • RedisTemplate操作String及Hash數(shù)據(jù)方式

    RedisTemplate操作String及Hash數(shù)據(jù)方式

    本文主要介紹了使用RedisTemplate封裝操作方法,包括單個(gè)和多個(gè)key的刪除、設(shè)置key失效時(shí)間、獲取key過期時(shí)間、判斷key是否存在、String類型操作(如添加、刪除緩存、順序遞增遞減)以及Hash類型數(shù)據(jù)操作(如添加緩存數(shù)據(jù)、設(shè)置過期時(shí)間等)
    2026-05-05
  • 解決Nacos集群啟動(dòng)失敗:java版本問題

    解決Nacos集群啟動(dòng)失敗:java版本問題

    這篇文章主要介紹了解決Nacos集群啟動(dòng)失敗:java版本問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-06-06
  • 非常適合新手學(xué)生的Java線程池超詳細(xì)分析

    非常適合新手學(xué)生的Java線程池超詳細(xì)分析

    作者是一個(gè)來自河源的大三在校生,以下筆記都是作者自學(xué)之路的一些淺薄經(jīng)驗(yàn),如有錯(cuò)誤請指正,將來會不斷的完善筆記,幫助更多的Java愛好者入門
    2022-03-03
  • Java遍歷Map對象的四種方式

    Java遍歷Map對象的四種方式

    本文給大家介紹java遍歷map對象的四種方式,對java中遍歷map感興趣的朋友可以一起了解了解
    2015-10-10
  • Spring boot集成RabbitMQ的示例代碼

    Spring boot集成RabbitMQ的示例代碼

    本篇文章主要介紹了Spring boot集成RabbitMQ的示例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-05-05
  • springboot后臺session的存儲與取出方式

    springboot后臺session的存儲與取出方式

    這篇文章主要介紹了springboot后臺session的存儲與取出方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-06-06
  • java 輸入一個(gè)數(shù)字,反轉(zhuǎn)輸出這個(gè)數(shù)字的值(實(shí)現(xiàn)方法)

    java 輸入一個(gè)數(shù)字,反轉(zhuǎn)輸出這個(gè)數(shù)字的值(實(shí)現(xiàn)方法)

    下面小編就為大家?guī)硪黄猨ava 輸入一個(gè)數(shù)字,反轉(zhuǎn)輸出這個(gè)數(shù)字的值(實(shí)現(xiàn)方法)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2016-10-10

最新評論

焉耆| 聂荣县| 惠水县| 磴口县| 朝阳市| 江华| 温泉县| 平顺县| 西青区| 广东省| 怀宁县| 尖扎县| 龙岩市| 屏山县| 海城市| 庆元县| 红原县| 元阳县| 武川县| 交口县| 桐城市| 铜陵市| 锡林浩特市| 大冶市| 平乐县| 北流市| 福鼎市| 岳阳市| 江川县| 绥德县| 济宁市| 无为县| 科尔| 项城市| 辉南县| 商洛市| 炎陵县| 广水市| 静乐县| 阳朔县| 河津市|