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

Spring boot實(shí)現(xiàn)一個(gè)簡單的ioc(2)

 更新時(shí)間:2017年04月28日 10:23:11   作者:clayanddev  
這篇文章主要為大家詳細(xì)介紹了Spring boot實(shí)現(xiàn)一個(gè)簡單ioc的第二篇,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下

前言

跳過廢話,直接看正文
仿照spring-boot的項(xiàng)目結(jié)構(gòu)以及部分注解,寫一個(gè)簡單的ioc容器。
測(cè)試代碼完成后,便正式開始這個(gè)ioc容器的開發(fā)工作。

正文

項(xiàng)目結(jié)構(gòu)

實(shí)際上三四個(gè)類完全能搞定這個(gè)簡單的ioc容器,但是出于可擴(kuò)展性的考慮,還是寫了不少的類。
因篇幅限制,接下來只將幾個(gè)最重要的類的代碼貼出來并加以說明,完整的代碼請(qǐng)直接參考https://github.com/clayandgithub/simple-ioc

SimpleAutowired

代碼

import java.lang.annotation.*;

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SimpleAutowired {
 boolean required() default true;

 String value() default ""; // this field is moved from @Qualifier to here for simplicity
}

說明

@SimpleAutowired的作用是用于注解需要自動(dòng)裝配的字段。
此類和spring的@Autowired的作用類似。但又有以下兩個(gè)區(qū)別:
- @SimpleAutowired只能作用于類字段,而不能作用于方法(這樣實(shí)現(xiàn)起來相對(duì)簡單些,不會(huì)用到aop)
- @SimpleAutowired中包括了required(是否一定需要裝配)和value(要裝配的bean的名字)兩個(gè)字段,實(shí)際上是將spring中的@Autowired以及Qualifier的功能簡單地融合到了一起

SimpleBean

代碼

import java.lang.annotation.*;

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SimpleBean {
 String value() default "";
}

說明

@SimpleBean作用于方法,根據(jù)方法返回值來生成一個(gè)bean,對(duì)應(yīng)spring中的@Bean
用value來設(shè)置要生成的bean的名字

SimpleComponent

代碼

import java.lang.annotation.*;

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SimpleBean {
 String value() default "";
}

說明

@SimpleComponent作用于類,ioc容器會(huì)為每一個(gè)擁有@SimpleComponent的類生成一個(gè)bean,對(duì)應(yīng)spring中的@Component。特殊說明,為了簡單起見,@SimpleComponent注解的類必須擁有一個(gè)無參構(gòu)造函數(shù),否則無法生成該類的實(shí)例,這個(gè)在之后的SimpleAppliationContext中的processSingleClass方法中會(huì)有說明。

SimpleIocBootApplication

代碼

import java.lang.annotation.*;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SimpleIocBootApplication {
 String[] basePackages() default {};
}

說明

@SimpleIocBootApplication作用于應(yīng)用的入口類。
這個(gè)啟動(dòng)模式是照搬了spring-boot的啟動(dòng)模式,將啟動(dòng)任務(wù)委托給SimpleIocApplication來完成。ioc容器將根據(jù)注解@SimpleIocBootApplication的相關(guān)配置自動(dòng)掃描相應(yīng)的package,生成beans并完成自動(dòng)裝配。(如果沒有配置,默認(rèn)掃描入口類(測(cè)試程序中的SampleApplication)所在的package及其子package)

以上就是這個(gè)ioc容器所提供的所有注解,接下來講解ioc容器的掃描和裝配過程的實(shí)現(xiàn)。

SimpleIocApplication

代碼

import com.clayoverwind.simpleioc.context.*;
import com.clayoverwind.simpleioc.util.LogUtil;

import java.util.Arrays;
import java.util.Map;
import java.util.logging.Logger;


public class SimpleIocApplication {
 private Class<?> applicationEntryClass;

 private ApplicationContext applicationContext;

 private final Logger LOGGER = LogUtil.getLogger(this.getClass());

 public SimpleIocApplication(Class<?> applicationEntryClass) {
 this.applicationEntryClass = applicationEntryClass;
 }

 public static void run(Class<?> applicationEntryClass, String[] args) {
 new SimpleIocApplication(applicationEntryClass).run(args);
 }

 public void run(String[] args) {
 LOGGER.info("start running......");

 // create application context and application initializer
 applicationContext = createSimpleApplicationContext();
 ApplicationContextInitializer initializer = createSimpleApplicationContextInitializer(applicationEntryClass);

 // initialize the application context (this is where we create beans)
 initializer.initialize(applicationContext); // here maybe exist a hidden cast

 // process those special beans
 processSpecialBeans(args);

 LOGGER.info("over!");
 }

 private SimpleApplicationContextInitializer createSimpleApplicationContextInitializer(Class<?> entryClass) {
 // get base packages
 SimpleIocBootApplication annotation = entryClass.getDeclaredAnnotation(SimpleIocBootApplication.class);
 String[] basePackages = annotation.basePackages();
 if (basePackages.length == 0) {
  basePackages = new String[]{entryClass.getPackage().getName()};
 }

 // create context initializer with base packages
 return new SimpleApplicationContextInitializer(Arrays.asList(basePackages));
 }

 private SimpleApplicationContext createSimpleApplicationContext() {
 return new SimpleApplicationContext();
 }

 private void processSpecialBeans(String[] args) {
 callRegisteredRunners(args);
 }

 private void callRegisteredRunners(String[] args) {
 Map<String, SimpleIocApplicationRunner> applicationRunners = applicationContext.getBeansOfType(SimpleIocApplicationRunner.class);
 try {
  for (SimpleIocApplicationRunner applicationRunner : applicationRunners.values()) {
  applicationRunner.run(args);
  }
 } catch (Exception e) {
  throw new RuntimeException(e);
 }
 }
}

說明

前面說到應(yīng)用的啟動(dòng)會(huì)委托SimpleIocApplication來完成,通過將應(yīng)用入口類(測(cè)試程序中的SampleApplication)傳入SimpleIocApplication的構(gòu)造函數(shù),構(gòu)造出SimpleIocApplication的一個(gè)實(shí)例并運(yùn)行run方法。在run方法中,會(huì)首先生成一個(gè)applicationContext,并調(diào)用SimpleApplicationContextInitializer來完成applicationContext的初始化(bean的掃描、裝配)。然后調(diào)用processSpecialBeans來處理一些特殊的bean,如實(shí)現(xiàn)了SimpleIocApplicationRunner接口的bean會(huì)調(diào)用run方法來完成一些應(yīng)用程序的啟動(dòng)任務(wù)。
這就是這個(gè)ioc容器的整個(gè)流程。

SimpleApplicationContextInitializer

代碼

import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

public class SimpleApplicationContextInitializer implements ApplicationContextInitializer<SimpleApplicationContext> {

  private Set<String> basePackages = new LinkedHashSet<>();

  public SimpleApplicationContextInitializer(List<String> basePackages) {
    this.basePackages.addAll(basePackages);
  }

  @Override
  public void initialize(SimpleApplicationContext applicationContext) {
    try {
      applicationContext.scan(basePackages, true);
    } catch (ClassNotFoundException e) {
      throw new RuntimeException(e);
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
    applicationContext.setStartupDate(System.currentTimeMillis());
  }
}

說明

在SimpleIocApplication的run中,會(huì)根據(jù)basePackages來構(gòu)造一個(gè)SimpleApplicationContextInitializer 的實(shí)例,進(jìn)而通過這個(gè)ApplicationContextInitializer來完成SimpleApplicationContext 的初始化。
在SimpleApplicationContextInitializer中, 簡單地調(diào)用SimpleApplicationContext 中的scan即可完成SimpleApplicationContext的初始化任務(wù)

SimpleApplicationContext

說明:

終于到了最重要的部分了,在SimpleApplicationContext中將真正完成掃描、生成bean以及自動(dòng)裝配的任務(wù)。這里scan即為SimpleApplicationContext的程序入口,由SimpleApplicationContextInitializer在初始化時(shí)調(diào)用。
代碼的調(diào)用邏輯簡單易懂,就不多加說明了。
這里只簡單列一下各個(gè)字段的含義以及幾個(gè)比較關(guān)鍵的方法的作用。

字段

- startupDate:啟動(dòng)時(shí)間記錄字段
- scannedPackages:已經(jīng)掃描的包的集合,保證不重復(fù)掃描
- registeredBeans:已經(jīng)完全裝配好并注冊(cè)好了的bean
- earlyBeans : 只是生成好了,還未裝配完成的bean,用于處理循環(huán)依賴的問題
- totalBeanCount : 所有bean的計(jì)數(shù)器,在生成bean的名字時(shí)會(huì)用到其唯一性

方法

- processEarlyBeans:用于最終裝配earlyBeans 中的bean,若裝配成功,則將bean移至registeredBeans,否則報(bào)錯(cuò)
- scan : 掃描并處理傳入的package集合
- processSingleClass:處理單個(gè)類,嘗試生成該類的bean并進(jìn)行裝配(前提是此類有@SimpleComponent注解)
- createBeansByMethodsOfClass : 顧名思義,根據(jù)那些被@Bean注解的方法來生成bean
- autowireFields:嘗試裝配某個(gè)bean,lastChance代表是否在裝配失敗是報(bào)錯(cuò)(在第一次裝配時(shí),此值為false,在裝配失敗后會(huì)將bean移至earlyBeans,在第二次裝配時(shí),此值為true,實(shí)際上就是在裝配earlyBeans中的bean,因此若仍然裝配失敗,就會(huì)報(bào)錯(cuò))。在這個(gè)方法中,裝配相應(yīng)的bean時(shí)會(huì)從registeredBeans以及earlyBeans中去尋找符合條件的bean,只要找到,不管是來自哪里,都算裝配成功。

代碼

import com.clayoverwind.simpleioc.context.annotation.SimpleAutowired;
import com.clayoverwind.simpleioc.context.annotation.SimpleBean;
import com.clayoverwind.simpleioc.context.annotation.SimpleComponent;
import com.clayoverwind.simpleioc.context.factory.Bean;
import com.clayoverwind.simpleioc.util.ClassUtil;
import com.clayoverwind.simpleioc.util.ConcurrentHashSet;
import com.clayoverwind.simpleioc.util.LogUtil;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Logger;

/**
 * @author clayoverwind
 * @E-mail clayanddev@163.com
 * @version 2017/4/5
 */

public class SimpleApplicationContext implements ApplicationContext {

 private long startupDate;

 private Set<String> scannedPackages = new ConcurrentHashSet<>();

 private Map<String, Bean> registeredBeans = new ConcurrentHashMap<>();

 private Map<String, Bean> earlyBeans = new ConcurrentHashMap<>();

 private final Logger LOGGER = LogUtil.getLogger(this.getClass());

 AtomicLong totalBeanCount = new AtomicLong(0L);

 AtomicLong nameConflictCount = new AtomicLong(0L);

 @Override
 public Object getBean(String name) {
 return registeredBeans.get(name);
 }

 @Override
 public <T> T getBean(String name, Class<T> type) {
 Bean bean = (Bean)getBean(name);
 return bean == null ? null : (type.isAssignableFrom(bean.getClazz()) ? type.cast(bean.getObject()) : null);
 }

 @Override
 public <T> T getBean(Class<T> type) {
 Map<String, T> map = getBeansOfType(type);
 return map.isEmpty() ? null : type.cast(map.values().toArray()[0]);
 }

 @Override
 public boolean containsBean(String name) {
 return getBean(name) != null;
 }

 @Override
 public <T> Map<String, T> getBeansOfType(Class<T> type) {
 Map<String, T> res = new HashMap<>();
 registeredBeans.entrySet().stream().filter(entry -> type.isAssignableFrom(entry.getValue().getClazz())).forEach(entry -> res.put(entry.getKey(), type.cast(entry.getValue().getObject())));
 return res;
 }

 @Override
 public void setStartupDate(long startupDate) {
 this.startupDate = startupDate;
 }

 @Override
 public long getStartupDate() {
 return startupDate;
 }

 /**
 * try to autowire those beans in earlyBeans
 * if succeed, remove it from earlyBeans and put it into registeredBeans
 * otherwise ,throw a RuntimeException(in autowireFields)
 */
 private synchronized void processEarlyBeans() {
 for (Map.Entry<String, Bean> entry : earlyBeans.entrySet()) {
  Bean myBean = entry.getValue();
  try {
  if (autowireFields(myBean.getObject(), myBean.getClazz(), true)) {
   registeredBeans.put(entry.getKey(), myBean);
   earlyBeans.remove(entry.getKey());
  }
  } catch (IllegalAccessException e) {
  throw new RuntimeException(e);
  }
 }
 }

 /**
 * scan base packages and create beans
 * @param basePackages
 * @param recursively
 * @throws ClassNotFoundException
 */
 public void scan(Set<String> basePackages, boolean recursively) throws ClassNotFoundException, IOException {
 LOGGER.info("start scanning......");

 ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

 // get all classes who haven't been registered
 Set<Class<?>> classes = new LinkedHashSet<>();
 for (String packageName : basePackages) {
  if (scannedPackages.add(packageName)) {
  classes.addAll(ClassUtil.getClassesByPackageName(classLoader, packageName, recursively));
  }
 }

 // autowire or create bean for each class
 classes.forEach(this::processSingleClass);

 processEarlyBeans();

 LOGGER.info("scan over!");
 }

 /**
 * try to create a bean for certain class, put it into registeredBeans if success, otherwise put it into earlyBeans
 * @param clazz
 */
 private void processSingleClass(Class<?> clazz) {
 LOGGER.info(String.format("processSingleClass [%s] ...", clazz.getName()));

 Annotation[] annotations = clazz.getDeclaredAnnotations();
 for (Annotation annotation : annotations) {
  if (annotation instanceof SimpleComponent) {
  Object instance;
  try {
   instance = clazz.newInstance();
  } catch (InstantiationException e) {
   throw new RuntimeException(e);
  } catch (IllegalAccessException e) {
   throw new RuntimeException(e);
  }

  long beanId = totalBeanCount.getAndIncrement();
  SimpleComponent component = (SimpleComponent) annotation;
  String beanName = component.value();
  if (beanName.isEmpty()) {
   beanName = getUniqueBeanNameByClassAndBeanId(clazz, beanId);
  }

  try {
   if (autowireFields(instance, clazz, false)) {
   registeredBeans.put(beanName, new Bean(instance, clazz));
   } else {
   earlyBeans.put(beanName, new Bean(instance, clazz));
   }
  } catch (IllegalAccessException e) {
   throw new RuntimeException(e);
  }

  try {
   createBeansByMethodsOfClass(instance, clazz);
  } catch (InvocationTargetException e) {
   throw new RuntimeException(e);
  } catch (IllegalAccessException e) {
   throw new RuntimeException(e);
  }
  }
 }
 }

 private void createBeansByMethodsOfClass(Object instance, Class<?> clazz) throws InvocationTargetException, IllegalAccessException {
 List<Method> methods = getMethodsWithAnnotation(clazz, SimpleBean.class);
 for (Method method : methods) {
  method.setAccessible(true);
  Object methodBean = method.invoke(instance);
  long beanId = totalBeanCount.getAndIncrement();
  Class<?> methodBeanClass = methodBean.getClass();

  //bean name
  SimpleBean simpleBean = method.getAnnotation(SimpleBean.class);
  String beanName = simpleBean.value();
  if (beanName.isEmpty()) {
  beanName = getUniqueBeanNameByClassAndBeanId(clazz, beanId);
  }

  // register bean
  registeredBeans.put(beanName, new Bean(methodBean, methodBeanClass));
 }
 }

 private List<Method> getMethodsWithAnnotation(Class<?> clazz, Class<?> annotationClass) {
 List<Method> res = new LinkedList<>();
 Method[] methods = clazz.getDeclaredMethods();
 for (Method method : methods) {
  Annotation[] annotations = method.getAnnotations();
  for (Annotation annotation : annotations) {
  if (annotation.annotationType() == annotationClass) {
   res.add(method);
   break;
  }
  }
 }
 return res;
 }


 /**
 * try autowire all fields of a certain instance
 * @param instance
 * @param clazz
 * @param lastChance
 * @return true if success, otherwise return false or throw a exception if this is the lastChance
 * @throws IllegalAccessException
 */
 private boolean autowireFields(Object instance, Class<?> clazz, boolean lastChance) throws IllegalAccessException {
 Field[] fields = clazz.getDeclaredFields();
 for (Field field : fields) {
  Annotation[] annotations = field.getAnnotations();
  for (Annotation annotation : annotations) {
  if (annotation instanceof SimpleAutowired) {
   SimpleAutowired autowired = (SimpleAutowired) annotation;
   String beanName = autowired.value();
   Bean bean = getSimpleBeanByNameOrType(beanName, field.getType(), true);
   if (bean == null) {
   if (lastChance) {
    if (!autowired.required()) {
    break;
    }
    throw new RuntimeException(String.format("Failed in autowireFields : [%s].[%s]", clazz.getName(), field.getName()));
   } else {
    return false;
   }
   }
   field.setAccessible(true);
   field.set(instance, bean.getObject());
  }
  }
 }
 return true;
 }

 /**
 * only used in autowireFields
 * @param beanName
 * @param type
 * @param allowEarlyBean
 * @return
 */
 private Bean getSimpleBeanByNameOrType(String beanName, Class<?> type, boolean allowEarlyBean) {
 // 1. by name
 Bean res = registeredBeans.get(beanName);
 if (res == null && allowEarlyBean) {
  res = earlyBeans.get(beanName);
 }

 // 2. by type
 if (type != null) {
  if (res == null) {
  res = getSimpleBeanByType(type, registeredBeans);
  }
  if (res == null && allowEarlyBean) {
  res = getSimpleBeanByType(type, earlyBeans);
  }
 }

 return res;
 }

 /**
 * search bean by type in certain beans map
 * @param type
 * @param beansMap
 * @return
 */
 private Bean getSimpleBeanByType(Class<?> type, Map<String, Bean> beansMap) {
 List<Bean> beans = new LinkedList<>();
 beansMap.entrySet().stream().filter(entry -> type.isAssignableFrom(entry.getValue().getClazz())).forEach(entry -> beans.add(entry.getValue()));
 if (beans.size() > 1) {
  throw new RuntimeException(String.format("Autowire by type, but more than one instance of type [%s] is founded!", beans.get(0).getClazz().getName()));
 }
 return beans.isEmpty() ? null : beans.get(0);
 }

 private String getUniqueBeanNameByClassAndBeanId(Class<?> clazz, long beanId) {
 String beanName = clazz.getName() + "_" + beanId;
 while (registeredBeans.containsKey(beanName) || earlyBeans.containsKey(beanName)) {
  beanName = clazz.getName() + "_" + beanId + "_" + nameConflictCount.getAndIncrement();
 }
 return beanName;
 }
}

后記

至此,一個(gè)簡單的ioc容器就完成了,總結(jié)一下優(yōu)缺點(diǎn)。

優(yōu)點(diǎn):

小而簡單。
可以使用@SimpleBean、@SimpleComponent以及@SimpleAutowired 來完成一些簡單但常用的依賴注入任務(wù).

缺點(diǎn):

很明顯,實(shí)現(xiàn)過于簡單,提供的功能太少。
如果你想了解ioc的實(shí)現(xiàn)原理,或者你想要開發(fā)一個(gè)小型個(gè)人項(xiàng)目但又嫌spring過于龐大,這個(gè)簡單的ioc容器或許可以幫到你。

如果你想做的不僅如此,那么你應(yīng)該將目光轉(zhuǎn)向spring-boot

完整代碼參考:https://github.com/clayandgithub/simple-ioc。

以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • SpringBoot整合MongoDB的實(shí)現(xiàn)代碼

    SpringBoot整合MongoDB的實(shí)現(xiàn)代碼

    自己本科時(shí)候一直使用的是Mysql,目前的課題組使用的是MongoDB,因此就花了一部分時(shí)間整理了一下,實(shí)現(xiàn)springboot與MongoDB的整合,并且實(shí)現(xiàn)基本的增刪改查操作,從頭到尾給出一個(gè)完整的案例。
    2021-05-05
  • Java中的異常處理機(jī)制介紹(非常全面!)

    Java中的異常處理機(jī)制介紹(非常全面!)

    異??赡苁窃诔绦驁?zhí)行過程中產(chǎn)生的,也可能是程序中throw主動(dòng)拋出的,下面這篇文章主要給大家介紹了關(guān)于Java中異常處理機(jī)制的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-01-01
  • Java中實(shí)現(xiàn)漢字生成拼音首拼和五筆碼

    Java中實(shí)現(xiàn)漢字生成拼音首拼和五筆碼

    這篇文章主要介紹了Java中實(shí)現(xiàn)漢字生成拼音首拼和五筆碼方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • Java實(shí)現(xiàn)屏幕截圖及剪裁

    Java實(shí)現(xiàn)屏幕截圖及剪裁

    這是一篇入門級(jí)文章,高手請(qǐng)略過。在這篇文章中我們將學(xué)習(xí)如何用 Java 對(duì)圖像進(jìn)行剪裁并將剪裁出來的部分單獨(dú)保存到文件中。
    2014-09-09
  • java Beanutils.copyProperties( )用法詳解

    java Beanutils.copyProperties( )用法詳解

    這篇文章主要介紹了java Beanutils.copyProperties( )用法詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-05-05
  • 一篇文章帶你入門Java基本概念

    一篇文章帶你入門Java基本概念

    本文主要介紹了Java編程的基本概念基本概念,可以幫助我們更加深刻的所要講解的Java命令,具有很好的參考價(jià)值。下面跟著小編一起來看下吧,希望能給你帶來幫助
    2021-08-08
  • 深入了解JAVA數(shù)據(jù)類型與運(yùn)算符

    深入了解JAVA數(shù)據(jù)類型與運(yùn)算符

    這篇文章主要介紹了Java基本數(shù)據(jù)類型和運(yùn)算符,結(jié)合實(shí)例形式詳細(xì)分析了java基本數(shù)據(jù)類型、數(shù)據(jù)類型轉(zhuǎn)換、算術(shù)運(yùn)算符、邏輯運(yùn)算符等相關(guān)原理與操作技巧,需要的朋友可以參考下
    2021-07-07
  • Java中的異步回調(diào)問題

    Java中的異步回調(diào)問題

    這篇文章主要介紹了Java中的異步回調(diào)問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • Java如何使用流去除集合中某個(gè)字段為空的對(duì)象

    Java如何使用流去除集合中某個(gè)字段為空的對(duì)象

    這篇文章主要給大家介紹了關(guān)于Java如何使用流去除集合中某個(gè)字段為空的對(duì)象,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Java具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2023-08-08
  • springboot項(xiàng)目之相互依賴報(bào)錯(cuò)問題(基于idea)

    springboot項(xiàng)目之相互依賴報(bào)錯(cuò)問題(基于idea)

    這篇文章主要介紹了springboot項(xiàng)目之相互依賴報(bào)錯(cuò)問題(基于idea),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-02-02

最新評(píng)論

石狮市| 象州县| 彝良县| 临城县| 沾化县| 盐山县| 高雄县| 曲麻莱县| 新建县| 迭部县| 桃源县| 湖州市| 丹巴县| 唐河县| 黔南| 阳江市| 洞头县| 牟定县| 平潭县| 万宁市| 迁安市| 桐乡市| 肃南| 博爱县| 荣昌县| 乐都县| 南川市| 竹山县| 卓尼县| 巩留县| 巨野县| 云和县| 肇源县| 霸州市| 莱芜市| 靖西县| 南通市| 呼玛县| 凤冈县| 吉林省| 芒康县|