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

Spring IOC原理補充說明(循環(huán)依賴、Bean作用域等)

 更新時間:2020年08月27日 10:15:57   作者:夜勿語  
這篇文章主要介紹了Spring IOC原理補充說明(循環(huán)依賴、Bean作用域等),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧

前言

通過之前的幾篇文章將Spring基于XML配置的IOC原理分析完成,但其中還有一些比較重要的細(xì)節(jié)沒有分析總結(jié),比如循環(huán)依賴的解決、作用域的實現(xiàn)原理、BeanPostProcessor的執(zhí)行時機以及SpringBoot零配置實現(xiàn)原理(@ComponentScan、@Import、@ImportSource、@Bean注解的使用和解析)等等。下面就先來看看循環(huán)依賴是怎么解決的,在此之前一定要熟悉整個Bean的實例化過程,本篇只會貼出關(guān)鍵性代碼。

正文

循環(huán)依賴

首先來看幾個問題:

什么是循環(huán)依賴?

在熟悉了Bean實例化原理后,你會怎么解決循環(huán)依賴的問題?

Spring怎么解決循環(huán)依賴?有哪些循環(huán)依賴可以被解決?哪些又不能?

什么是循環(huán)依賴?

這個概念很容易理解,簡單說就是兩個類相互依賴,類似線程死鎖的問題,也就是當(dāng)創(chuàng)建A對象時需要注入B的依賴對象,但B同時也依賴A,那到底該先創(chuàng)建A還是先創(chuàng)建B呢?

Spring是如何解決循環(huán)依賴的?

探究Spring的解決方法之前,我們首先得搞清楚Spring Bean有幾種依賴注入的方式:

通過構(gòu)造函數(shù)

通過屬性

通過方法(不一定是setter方法,只要在方法上加上了@Autowired,都會進行依賴注入)

其次,Spring作用域有singleton、prototype、request、session等等,但在非單例模式下發(fā)生循環(huán)依賴是會直接拋出異常的,下面這個代碼不知道你還有沒有印象,在AbstractBeanFactory.doGetBean中有這個判斷:

if (isPrototypeCurrentlyInCreation(beanName)) {
 throw new BeanCurrentlyInCreationException(beanName);
}

為什么這么設(shè)計呢?反過來想,如果不這么設(shè)計,你怎么知道循環(huán)依賴到底是依賴的哪個對象呢?搞清楚了這個再來看哪些依賴注入的方式發(fā)生循環(huán)依賴是可以解決,而那些又不能。結(jié)論是構(gòu)造函數(shù)方式?jīng)]辦法解決循環(huán)依賴,其它兩種都可以。

我們先來看看為什么通過屬性注入和方法注入可以解決?;貞浺幌翨ean的實例化過程:

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

 // Instantiate the bean.
 BeanWrapper instanceWrapper = null;
 if (mbd.isSingleton()) {
  instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
 }
 if (instanceWrapper == null) {
  //創(chuàng)建實例
  instanceWrapper = createBeanInstance(beanName, mbd, args);
 }
 final Object bean = instanceWrapper.getWrappedInstance();
 Class<?> beanType = instanceWrapper.getWrappedClass();
 if (beanType != NullBean.class) {
  mbd.resolvedTargetType = beanType;
 }

 // Allow post-processors to modify the merged bean definition.
 synchronized (mbd.postProcessingLock) {
  if (!mbd.postProcessed) {
  try {

   // Bean實例化完成后收集類中的注解(@PostConstruct,@PreDestroy,@Resource, @Autowired,@Value)
   applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
  }
  catch (Throwable ex) {
   throw new BeanCreationException(mbd.getResourceDescription(), beanName,
    "Post-processing of merged bean definition failed", ex);
  }
  mbd.postProcessed = true;
  }
 }

 // 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");
  }
  //這里著重理解,對理解循環(huán)依賴幫助非常大,重要程度 5  添加三級緩存
  addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
 }

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

  //bean 實例化+ioc依賴注入完以后的調(diào)用,非常重要
  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.");
   }
  }
  }
 }

 // Register bean as disposable.
 try {
  //注冊bean銷毀時的類DisposableBeanAdapter
  registerDisposableBeanIfNecessary(beanName, bean, mbd);
 }
 catch (BeanDefinitionValidationException ex) {
  throw new BeanCreationException(
   mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
 }

 return exposedObject;
 }

仔細(xì)看這個過程其實不難理解,首先Spring會通過無參構(gòu)造實例化一個空的A對象,實例化完成后會調(diào)用addSingletonFactory存入到三級緩存中(注意這里存入的是singletonFactory對象):

 protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
 Assert.notNull(singletonFactory, "Singleton factory must not be null");
 synchronized (this.singletonObjects) {
  // 一級緩存
  if (!this.singletonObjects.containsKey(beanName)) {
  System.out.println("========set value to 3 level cache->beanName->" + beanName + "->value->" + singletonFactory);
  // 三級緩存
  this.singletonFactories.put(beanName, singletonFactory);
  // 二級緩存
  this.earlySingletonObjects.remove(beanName);
  this.registeredSingletons.add(beanName);
  }
 }
 }

然后才會去依賴注入觸發(fā)類B的實例化,所以這時緩存中已經(jīng)存在了一個空的A對象;同樣B也是通過無參構(gòu)造實例化,B依賴注入又調(diào)用getBean獲取A的實例,而在創(chuàng)建對象之前,先是從緩存中獲取對象:

 //從緩存中拿實例
 Object sharedInstance = getSingleton(beanName);

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

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

很明顯,會從三級緩存中拿到singletonFactory對象并調(diào)用getObject方法,這是一個Lambda表達(dá)式,在表達(dá)式中又調(diào)用了getEarlyBeanReference方法:

 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;
   exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
  }
  }
 }
 return exposedObject;
 }

這里你點進去看會發(fā)現(xiàn)都是返回之前我們創(chuàng)建的空的A對象,因此B對象能夠依賴注入完成并存入到一級緩存中,接著A對象繼續(xù)未完成的依賴注入自然是可以成功的,也存入到一級緩存中。Spring就是這樣通過緩存解決了循環(huán)依賴,但是不知道你注意到?jīng)]有在上面的getSingleton方法中,從三級緩存中拿到對象后,會添加到二級緩存并刪除三級緩存,這是為什么呢?這個二級緩存有什么用呢?

其實也很簡單,就是為了提高效率的,因為在getEarlyBeanReference方法中是循環(huán)調(diào)用BeanPostProcessor類的方法的,當(dāng)只有一對一的依賴時沒有什么問題,但是當(dāng)A和B相互依賴,A又和C相互依賴,A在注入完B觸發(fā)C的依賴注入時,這個循環(huán)還有必要么?讀者們可以自行推演一下整個過程。

至此,Spring是如何解決循環(huán)依賴的相信你也很清楚了,現(xiàn)在再來看通過構(gòu)造函數(shù)依賴注入為什么不能解決循環(huán)依賴是不是也很清晰了?因為通過構(gòu)造函數(shù)實例化并依賴注入是沒辦法緩存一個實例對象供依賴對象注入的。

作用域?qū)崿F(xiàn)原理以及如何自定義作用域

作用域?qū)崿F(xiàn)原理

在Spring中主要有reqest、session、singleton、prototype等等幾種作用域,前面我們分析了singleton創(chuàng)建bean的原理,是通過緩存來實現(xiàn)的,那么其它的呢?還是回到AbstractBeanFactory.doGetBean方法中來:

if (mbd.isSingleton()) {
 sharedInstance = getSingleton(beanName, () -> {
 try {
  return createBean(beanName, mbd, args);
 }
 catch (BeansException ex) {
  // Explicitly remove instance from singleton cache: It might have been put there
  // eagerly by the creation process, to allow for circular reference resolution.
  // Also remove any beans that received a temporary reference to the bean.
  destroySingleton(beanName);
  throw ex;
 }
 });
 // 該方法是FactoryBean接口的調(diào)用入口
 bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
else if (mbd.isPrototype()) {
 // It's a prototype -> create a new instance.
 Object prototypeInstance = null;
 try {
 beforePrototypeCreation(beanName);
 prototypeInstance = createBean(beanName, mbd, args);
 }
 finally {
 afterPrototypeCreation(beanName);
 }
 // 該方法是FactoryBean接口的調(diào)用入口
 bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
else {
 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, () -> {
  beforePrototypeCreation(beanName);
  try {
  return createBean(beanName, mbd, args);
  }
  finally {
  afterPrototypeCreation(beanName);
  }
 });
 // 該方法是FactoryBean接口的調(diào)用入口
 bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
 }
}

在singleton作用域下,會調(diào)用getSingleton方法,然后回調(diào)createBean創(chuàng)建對象,最終在getSingleton中完成緩存;而當(dāng)scope為prototype時,可以看到是直接調(diào)用了createBean方法并返回,沒有任何的緩存操作,因此每次調(diào)用getBean都會創(chuàng)建新的對象,即使是同一個線程;除此之外都會進入到else片段中。

這個代碼也很簡單,首先通過我們配置的scopeName從scopes中拿到對應(yīng)的Scope對象,如SessionScope和RequestScope(但這兩個只會在Web環(huán)境中被加載,在WebApplicationContextUtils.registerWebApplicationScopes可以看到注冊操作),然后調(diào)用對應(yīng)的get方法存到對應(yīng)的request或session對象中去。代碼很簡單,這里就不分析了。

自定義Scope

通過以上分析,不難發(fā)現(xiàn)我們是很容易實現(xiàn)一個自己的Scope的,首先實現(xiàn)Scope接口,然后將我們類的實例添加到scopes緩存中來,關(guān)鍵是怎么添加呢?在AbstractBeanFactory類中有一個registerScope方法就是干這個事的,因此我們只要拿到一個BeanFactory對象就行了,那要怎么拿?還記得在refresh中調(diào)用的invokeBeanFactoryPostProcessors方法么?因此我們只需要實現(xiàn)BeanFactoryPostProcessor接口就可以了,是不是So Easy!

BeanPostProcessor的執(zhí)行時機

BeanPostProcessor執(zhí)行點很多,根據(jù)其接口類型在不同的位置進行調(diào)用,只有熟記其執(zhí)行時機,才能更好的進行擴展,這里以一張時序圖來總結(jié):

SpringBoot零配置實現(xiàn)原理淺析

在SpringBoot項目中,省去了大量繁雜的xml配置,只需要使用@ComponentScan、@Configuration以及@Bean注解就可以達(dá)到和使用xml配置的相同效果,大大簡化了我們的開發(fā),那這個實現(xiàn)原理是怎樣的呢?熟悉了xml解析原理,相信對于這種注解的方式基本上也能猜個大概。

首先我們進入到AnnotationConfigApplicationContext類,這個就是注解方式的IOC容器:

 public AnnotationConfigApplicationContext(String... basePackages) {
 this();
 scan(basePackages);
 refresh();
 }

 public AnnotationConfigApplicationContext() {
 this.reader = new AnnotatedBeanDefinitionReader(this);
 this.scanner = new ClassPathBeanDefinitionScanner(this);
 }

這里ClassPathBeanDefinitionScanner在解析xml時出現(xiàn)過,就是用來掃描包找到合格的資源的;同時還創(chuàng)建了一個AnnotatedBeanDefinitionReader對象對應(yīng)XmlBeanDefinitionReader,用來解析注解:

 public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
 Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
 Assert.notNull(environment, "Environment must not be null");
 this.registry = registry;
 this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
 AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
 }

 public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
  BeanDefinitionRegistry registry, @Nullable Object source) {

 DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
 if (beanFactory != null) {
  if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) {
  beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
  }
  if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) {
  beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
  }
 }

 Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);

 if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
  RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
  def.setSource(source);
  beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
 }

 if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
  RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
  def.setSource(source);
  beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
 }

 // Check for JSR-250 support, and if present add the CommonAnnotationBeanPostProcessor.
 if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
  RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
  def.setSource(source);
  beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
 }

 // Check for JPA support, and if present add the PersistenceAnnotationBeanPostProcessor.
 if (jpaPresent && !registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) {
  RootBeanDefinition def = new RootBeanDefinition();
  try {
  def.setBeanClass(ClassUtils.forName(PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME,
   AnnotationConfigUtils.class.getClassLoader()));
  }
  catch (ClassNotFoundException ex) {
  throw new IllegalStateException(
   "Cannot load optional framework class: " + PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, ex);
  }
  def.setSource(source);
  beanDefs.add(registerPostProcessor(registry, def, PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME));
 }

 if (!registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) {
  RootBeanDefinition def = new RootBeanDefinition(EventListenerMethodProcessor.class);
  def.setSource(source);
  beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME));
 }

 if (!registry.containsBeanDefinition(EVENT_LISTENER_FACTORY_BEAN_NAME)) {
  RootBeanDefinition def = new RootBeanDefinition(DefaultEventListenerFactory.class);
  def.setSource(source);
  beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_FACTORY_BEAN_NAME));
 }

 return beanDefs;
 }

在AnnotatedBeanDefinitionReader構(gòu)造方法中可以看到調(diào)用了registerAnnotationConfigProcessors注冊一些列注解解析的Processor類,重點關(guān)注ConfigurationClassPostProcessor類,該類是BeanDefinitionRegistryPostProcessor的子類,所以會在refresh中調(diào)用,該類又會委托ConfigurationClassParser去解析@Configuration、@Bean、@ComponentScan等注解,所以這兩個類就是SpringBoot實現(xiàn)零配置的關(guān)鍵類,實現(xiàn)和之前分析的注解解析流程差不多,所以具體的實現(xiàn)邏輯讀者請自行分析。

回頭看當(dāng)解析器和掃描器創(chuàng)建好后,同樣是調(diào)用scan方法掃描包,然后refresh啟動容器,所以實現(xiàn)邏輯都是一樣的,殊途同歸,只不過通過父子容器的構(gòu)造方式使得我們可以很方便的擴展Spring。

總結(jié)

本篇是關(guān)于IOC實現(xiàn)的一些補充,最重要的是要理解循環(huán)依賴的解決辦法,其次SpringBoot零配置實現(xiàn)原理雖然這里只是簡單起了個頭,但需要好好閱讀源碼分析。另外還有很多細(xì)節(jié),不可能全都講到,需要我們自己反復(fù)琢磨,尤其是Bean實例化那一塊,這將是后面我們理解AOP的基礎(chǔ)。希望大家多多支持腳本之家。

相關(guān)文章

  • java IP地址網(wǎng)段計算的示例代碼

    java IP地址網(wǎng)段計算的示例代碼

    這篇文章主要介紹了java IP地址網(wǎng)段計算的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-03-03
  • Spring注解之Service用法及示例詳解

    Spring注解之Service用法及示例詳解

    使用 @Service 注解可以將一個類聲明為業(yè)務(wù)邏輯組件,并將其對象存入 Spring 容器中,在控制器類中,通過注入該組件的實例,即可調(diào)用其中的方法,這篇文章主要介紹了Spring注解之Service用法及示例詳解,需要的朋友可以參考下
    2024-04-04
  • SpringMvc web.xml配置實現(xiàn)原理過程解析

    SpringMvc web.xml配置實現(xiàn)原理過程解析

    這篇文章主要介紹了SpringMvc web.xml配置實現(xiàn)原理過程解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-08-08
  • Maven Plugins報錯的解決方法

    Maven Plugins報錯的解決方法

    本文主要介紹了Maven Plugins報錯的解決方法,文中通過圖文介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-02-02
  • Java延時的3種實現(xiàn)方法舉例

    Java延時的3種實現(xiàn)方法舉例

    這篇文章主要給大家介紹了關(guān)于Java延時的3種實現(xiàn)方法舉例,java開發(fā)中常會用到延時任務(wù),文中通過實例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)具有一定參考借鑒價值,需要的朋友可以參考下
    2023-07-07
  • 詳細(xì)介紹idea如何設(shè)置類頭注釋和方法注釋(圖文)

    詳細(xì)介紹idea如何設(shè)置類頭注釋和方法注釋(圖文)

    本篇文章主要介紹了idea如何設(shè)置類頭注釋和方法注釋(圖文),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-12-12
  • 在IDEA中集成maven詳細(xì)流程圖示例

    在IDEA中集成maven詳細(xì)流程圖示例

    最近利用兩天時間學(xué)習(xí)了MyBatis以及maven,避免經(jīng)典的學(xué)過就忘記,我打算做出點總結(jié)以便日后復(fù)習(xí),當(dāng)然如果能幫到需要的人也是極好的,需要的朋友可以參考下
    2021-06-06
  • java兩個線程同時寫一個文件

    java兩個線程同時寫一個文件

    這篇文章主要為大家詳細(xì)介紹了java兩個線程同時寫一個文件,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-08-08
  • Mybatis的Mapper中的方法為什么不能重載

    Mybatis的Mapper中的方法為什么不能重載

    這篇文章主要介紹了Mybatis的Mapper中的方法為什么不能重載,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09
  • 使用SpringBoot創(chuàng)建一個RESTful API的詳細(xì)步驟

    使用SpringBoot創(chuàng)建一個RESTful API的詳細(xì)步驟

    使用 Java 的 Spring Boot 創(chuàng)建 RESTful API 可以滿足多種開發(fā)場景,它提供了快速開發(fā)、易于配置、可擴展、可維護的優(yōu)點,尤其適合現(xiàn)代軟件開發(fā)的需求,幫助你快速構(gòu)建出高性能的后端服務(wù),需要的朋友可以參考下
    2025-01-01

最新評論

西林县| 朔州市| 德清县| 辽阳市| 金沙县| 凤台县| 广元市| 兴和县| 庆云县| 秭归县| 无极县| 光泽县| 呼图壁县| 宁都县| 宜宾县| 石家庄市| 惠安县| 尼玛县| 宁强县| 海伦市| 金门县| 崇仁县| 东山县| 济阳县| 五常市| 静安区| 吴桥县| 天长市| 突泉县| 本溪市| 元江| 蕉岭县| 申扎县| 遵义县| 永泰县| 赤峰市| 宝鸡市| 福安市| 塘沽区| 丰都县| 大余县|