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

SpringBoot超詳細(xì)分析啟動(dòng)流程

 更新時(shí)間:2022年07月04日 10:39:10   作者:喜歡小蘋果的碼農(nóng)  
今天小編就為大家分享一篇關(guān)于SpringBoot整個(gè)啟動(dòng)過程的分析,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧

Springboot啟動(dòng)類

@SpringBootApplication
public class TestApplication {
    public static void main(String[] args) {
        SpringApplication.run(TestApplication.class, args);
    }
}

SpringBootApplication注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
      @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

@SpringBootApplication注解上標(biāo)注了@EnableAutoConfiguration,表示TestApplication是引導(dǎo)類。

Springboot的啟動(dòng)方法中傳入了兩個(gè)參數(shù),引導(dǎo)類,和程序參數(shù)。

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
   return run(new Class<?>[] { primarySource }, args);
}

調(diào)用run方法

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
   return new SpringApplication(primarySources).run(args);
}

run方法中有兩步,創(chuàng)建SpringApplication和執(zhí)行SpringApplication的run方法。

1、創(chuàng)建SpringApplication

SpringApplication的構(gòu)造器

public SpringApplication(Class<?>... primarySources) {
   this(null, primarySources);
}
private ResourceLoader resourceLoader;
private Set<Class<?>> primarySources;
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
   //將resourceLoader賦值成員變量,此處是null
   this.resourceLoader = resourceLoader;
   Assert.notNull(primarySources, "PrimarySources must not be null");
    //將引導(dǎo)類賦值成員變量
   this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    //判斷web類型,賦值成員變量
   this.webApplicationType = WebApplicationType.deduceFromClasspath();
   //加載初始化器
   setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    //加載監(jiān)聽器
   setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    //獲得引導(dǎo)類
   this.mainApplicationClass = deduceMainApplicationClass();
}

1.1、判斷web類型

//org.springframework.boot.WebApplicationType#deduceFromClasspath
static WebApplicationType deduceFromClasspath() {
    //WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";
//JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
   if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
         && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
      return WebApplicationType.REACTIVE;
   }
    //SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
			"org.springframework.web.context.ConfigurableWebApplicationContext" }
   for (String className : SERVLET_INDICATOR_CLASSES) {
      if (!ClassUtils.isPresent(className, null)) {
         return WebApplicationType.NONE;
      }
   }
   return WebApplicationType.SERVLET;
}

1.2、加載上下文初始化器

利用spi加載 ApplicationContextInitializer 的實(shí)現(xiàn)類

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
   return getSpringFactoriesInstances(type, new Class<?>[] {});
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    //獲取classloader
   ClassLoader classLoader = getClassLoader();
   // Use names and ensure unique to protect against duplicates
    //獲取配置文件中接口實(shí)現(xiàn)類的全限定名
   Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    //初始化
   List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
   AnnotationAwareOrderComparator.sort(instances);
    //返回
   return instances;
}
public ClassLoader getClassLoader() {
   if (this.resourceLoader != null) {
      return this.resourceLoader.getClassLoader();
   }
   return ClassUtils.getDefaultClassLoader();
}
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
      ClassLoader classLoader, Object[] args, Set<String> names) {
   List<T> instances = new ArrayList<>(names.size());
   for (String name : names) {
      try {
         Class<?> instanceClass = ClassUtils.forName(name, classLoader);
         Assert.isAssignable(type, instanceClass);
         Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
         T instance = (T) BeanUtils.instantiateClass(constructor, args);
         instances.add(instance);
      }
      catch (Throwable ex) {
         throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
      }
   }
   return instances;
}

將初始化好的上下文初始化器設(shè)置到成員變量

//private List<ApplicationContextInitializer<?>> initializers;
public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
   this.initializers = new ArrayList<>(initializers);
}

1.3、加載監(jiān)聽器

將初始化好的監(jiān)聽器設(shè)置到成員變量

//private List<ApplicationListener<?>> listeners;
public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
   this.listeners = new ArrayList<>(listeners);
}

1.4、獲得引導(dǎo)類類型

private Class<?> deduceMainApplicationClass() {
   try {
      StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
      for (StackTraceElement stackTraceElement : stackTrace) {
         //獲取main方法的類
         if ("main".equals(stackTraceElement.getMethodName())) {
            return Class.forName(stackTraceElement.getClassName());
         }
      }
   }
   catch (ClassNotFoundException ex) {
      // Swallow and continue
   }
   return null;
}

2、執(zhí)行SpringApplication的run方法啟動(dòng)Springboot

public ConfigurableApplicationContext run(String... args) {
    //啟動(dòng)停止監(jiān)聽器
   StopWatch stopWatch = new StopWatch();
   stopWatch.start();
    //Spring上下文
   ConfigurableApplicationContext context = null;
    //錯(cuò)誤導(dǎo)出
   Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
   configureHeadlessProperty();
    //運(yùn)行時(shí)監(jiān)聽器
   SpringApplicationRunListeners listeners = getRunListeners(args);
    //啟動(dòng)運(yùn)行時(shí)監(jiān)聽器
   listeners.starting();
   try {
       //生成ApplicationArguments
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
       //準(zhǔn)備environment
      ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
      configureIgnoreBeanInfo(environment);
       //打印banner
      Banner printedBanner = printBanner(environment);
       //創(chuàng)建Spring上下文
      context = createApplicationContext();
      exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
            new Class[] { ConfigurableApplicationContext.class }, context);
       //準(zhǔn)備上下文
      prepareContext(context, environment, listeners, applicationArguments, printedBanner);
       //刷新上下文
      refreshContext(context);
       //上下文刷新后
      afterRefresh(context, applicationArguments);
       //停止 停止監(jiān)聽器
      stopWatch.stop();
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
      }
       //停止運(yùn)行時(shí)監(jiān)聽器
      listeners.started(context);
       //執(zhí)行回調(diào)
      callRunners(context, applicationArguments);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, listeners);
      throw new IllegalStateException(ex);
   }
   try {
      listeners.running(context);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, null);
      throw new IllegalStateException(ex);
   }
   return context;
}

2.1、準(zhǔn)備environment

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
      ApplicationArguments applicationArguments) {
   // Create and configure the environment
    //創(chuàng)建environment
   ConfigurableEnvironment environment = getOrCreateEnvironment();
    //配置environment
   configureEnvironment(environment, applicationArguments.getSourceArgs());
   ConfigurationPropertySources.attach(environment);
   listeners.environmentPrepared(environment);
   bindToSpringApplication(environment);
   if (!this.isCustomEnvironment) {
      environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
            deduceEnvironmentClass());
   }
   ConfigurationPropertySources.attach(environment);
   return environment;
}

2.1.1、創(chuàng)建environment

private ConfigurableEnvironment getOrCreateEnvironment() {
   if (this.environment != null) {
      return this.environment;
   }
   switch (this.webApplicationType) {
   case SERVLET:
      return new StandardServletEnvironment();
   case REACTIVE:
      return new StandardReactiveWebEnvironment();
   default:
      return new StandardEnvironment();
   }
}

這里根據(jù)web類型創(chuàng)建不同的environment

2.1.2、配置environment

protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
   if (this.addConversionService) {
       //轉(zhuǎn)換服務(wù)
      ConversionService conversionService = ApplicationConversionService.getSharedInstance();
      environment.setConversionService((ConfigurableConversionService) conversionService);
   }
   configurePropertySources(environment, args);
   configureProfiles(environment, args);
}

配置PropertySources

protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
   MutablePropertySources sources = environment.getPropertySources();
   if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
      sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
   }
   if (this.addCommandLineProperties && args.length > 0) {
      String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
      if (sources.contains(name)) {
         PropertySource<?> source = sources.get(name);
         CompositePropertySource composite = new CompositePropertySource(name);
         composite.addPropertySource(
               new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
         composite.addPropertySource(source);
         sources.replace(name, composite);
      }
      else {
         sources.addFirst(new SimpleCommandLinePropertySource(args));
      }
   }
}

主要為了在PropertySources添加一個(gè)SimpleCommandLinePropertySource。

配置profiles

protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
   Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
   profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
   environment.setActiveProfiles(StringUtils.toStringArray(profiles));
}

將profile添加到environment

2.2、打印Banner

private Banner.Mode bannerMode = Banner.Mode.CONSOLE;
private Banner printBanner(ConfigurableEnvironment environment) {
   //無需打印
   if (this.bannerMode == Banner.Mode.OFF) {
      return null;
   }
   ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader
         : new DefaultResourceLoader(getClassLoader());
   SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);
   if (this.bannerMode == Mode.LOG) {
      return bannerPrinter.print(environment, this.mainApplicationClass, logger);
   }
   return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}

2.3、創(chuàng)建Spring上下文

//DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot."			//+"web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
//DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework."	//+"boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";
//DEFAULT_CONTEXT_CLASS = "org.springframework.context."
//			+ "annotation.AnnotationConfigApplicationContext";
protected ConfigurableApplicationContext createApplicationContext() {
   Class<?> contextClass = this.applicationContextClass;
   if (contextClass == null) {
      try {
         switch (this.webApplicationType) {
         case SERVLET:
            contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
            break;
         case REACTIVE:
            contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
            break;
         default:
            contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
         }
      }
      catch (ClassNotFoundException ex) {
         throw new IllegalStateException(
               "Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
      }
   }
    //初始化上下文并返回
   return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

2.4、準(zhǔn)備上下文

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
      SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
   context.setEnvironment(environment);
    //ApplicationContext的后置處理
   postProcessApplicationContext(context);
    //應(yīng)用初始化器
   applyInitializers(context);
    //運(yùn)行時(shí)監(jiān)聽器的準(zhǔn)備上下文監(jiān)聽
   listeners.contextPrepared(context);
   if (this.logStartupInfo) {
      logStartupInfo(context.getParent() == null);
      logStartupProfileInfo(context);
   }
   // Add boot specific singleton beans
    //獲取beanFactory
   ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    //將程序入?yún)⒆?cè)bean springApplicationArguments
   beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    //將banner注冊(cè)bean springBootBanner
   if (printedBanner != null) {
      beanFactory.registerSingleton("springBootBanner", printedBanner);
   }
    //設(shè)置BeanDefinition是否允許重寫
   if (beanFactory instanceof DefaultListableBeanFactory) {
      ((DefaultListableBeanFactory) beanFactory)
            .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
   }
    //初始化允許懶加載,設(shè)置BeanfactorypostProcessor
   if (this.lazyInitialization) {
      context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
   }
   // Load the sources
    //獲取source
   Set<Object> sources = getAllSources();
   Assert.notEmpty(sources, "Sources must not be empty");
   load(context, sources.toArray(new Object[0]));
    //運(yùn)行時(shí)監(jiān)聽器,上下文加載完成
   listeners.contextLoaded(context);
}

2.4.1、ApplicationContext的后置處理

protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
    //注冊(cè) beanNameGenerator
   if (this.beanNameGenerator != null) {
      context.getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
            this.beanNameGenerator);
   }
    //注冊(cè) resourceLoader
   if (this.resourceLoader != null) {
      if (context instanceof GenericApplicationContext) {
         ((GenericApplicationContext) context).setResourceLoader(this.resourceLoader);
      }
      if (context instanceof DefaultResourceLoader) {
         ((DefaultResourceLoader) context).setClassLoader(this.resourceLoader.getClassLoader());
      }
   }
    //設(shè)置 ConversionService
   if (this.addConversionService) {
      context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance());
   }
}
//org.springframework.boot.convert.ApplicationConversionService#getSharedInstance
public static ConversionService getSharedInstance() {
   ApplicationConversionService sharedInstance = ApplicationConversionService.sharedInstance;
   if (sharedInstance == null) {
      synchronized (ApplicationConversionService.class) {
         sharedInstance = ApplicationConversionService.sharedInstance;
         if (sharedInstance == null) {
            sharedInstance = new ApplicationConversionService();
            ApplicationConversionService.sharedInstance = sharedInstance;
         }
      }
   }
   return sharedInstance;
}

2.4.2、應(yīng)用初始化器

protected void applyInitializers(ConfigurableApplicationContext context) {
   for (ApplicationContextInitializer initializer : getInitializers()) {
      Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
            ApplicationContextInitializer.class);
      Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
      initializer.initialize(context);
   }
}

執(zhí)行所有實(shí)現(xiàn)了 ApplicationContextInitializer 接口的類的initialize方法。

2.4.3、獲取sources

public Set<Object> getAllSources() {
   Set<Object> allSources = new LinkedHashSet<>();
   if (!CollectionUtils.isEmpty(this.primarySources)) {
      allSources.addAll(this.primarySources);
   }
   if (!CollectionUtils.isEmpty(this.sources)) {
      allSources.addAll(this.sources);
   }
   return Collections.unmodifiableSet(allSources);
}

這里組裝了引導(dǎo)類和自定義的source

2.4.4、加載

protected void load(ApplicationContext context, Object[] sources) {
   if (logger.isDebugEnabled()) {
      logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
   }
    //創(chuàng)建BeanDefinitionLoader
   BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
   if (this.beanNameGenerator != null) {
      loader.setBeanNameGenerator(this.beanNameGenerator);
   }
   if (this.resourceLoader != null) {
      loader.setResourceLoader(this.resourceLoader);
   }
   if (this.environment != null) {
      loader.setEnvironment(this.environment);
   }
    //加載,spring的邏輯,這里會(huì)將引導(dǎo)類注冊(cè)
   loader.load();
}
protected BeanDefinitionLoader createBeanDefinitionLoader(BeanDefinitionRegistry registry, Object[] sources) {
   return new BeanDefinitionLoader(registry, sources);
}

2.5、刷新上下文

private void refreshContext(ConfigurableApplicationContext context) {
   refresh(context);
   if (this.registerShutdownHook) {
      try {
          //注冊(cè)鉤子	關(guān)閉SpringBoot容器
         context.registerShutdownHook();
      }
      catch (AccessControlException ex) {
         // Not allowed in some environments.
      }
   }
}

刷新

protected void refresh(ApplicationContext applicationContext) {
   Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
    //執(zhí)行spring容器的刷新
   ((AbstractApplicationContext) applicationContext).refresh();
}

2.6、上下文刷新后

protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
}

這里是一個(gè)空實(shí)現(xiàn),應(yīng)該是作為模板擴(kuò)展吧。

2.7、執(zhí)行回調(diào)

private void callRunners(ApplicationContext context, ApplicationArguments args) {
   List<Object> runners = new ArrayList<>();
   runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
   runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
   AnnotationAwareOrderComparator.sort(runners);
   for (Object runner : new LinkedHashSet<>(runners)) {
      if (runner instanceof ApplicationRunner) {
         callRunner((ApplicationRunner) runner, args);
      }
      if (runner instanceof CommandLineRunner) {
         callRunner((CommandLineRunner) runner, args);
      }
   }
}

這里會(huì)調(diào)用所有實(shí)現(xiàn)了ApplicationRunner,CommandLineRunner的接口的實(shí)現(xiàn)類的run方法。

到此這篇關(guān)于SpringBoot超詳細(xì)分析啟動(dòng)流程的文章就介紹到這了,更多相關(guān)SpringBoot啟動(dòng)分析內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java?BigDecimal類的一般使用、BigDecimal轉(zhuǎn)double方式

    Java?BigDecimal類的一般使用、BigDecimal轉(zhuǎn)double方式

    這篇文章主要介紹了Java?BigDecimal類的一般使用、BigDecimal轉(zhuǎn)double方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • 淺析Java中Apache BeanUtils和Spring BeanUtils的用法

    淺析Java中Apache BeanUtils和Spring BeanUtils的用法

    這篇文章主要介紹了Java中Apache BeanUtils和Spring BeanUtils的用法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-11-11
  • Spring項(xiàng)目中swagger用法與swagger-ui使用

    Spring項(xiàng)目中swagger用法與swagger-ui使用

    這篇文章主要介紹了Spring項(xiàng)目中swagger用法與swagger-ui使用,通過圖文并茂的形式給大家介紹了編寫springboot項(xiàng)目的方法及導(dǎo)入spring-fox依賴的代碼詳解,需要的朋友可以參考下
    2021-05-05
  • 一篇文章帶你入門Java封裝

    一篇文章帶你入門Java封裝

    Java面向?qū)ο蟮娜筇匦裕悍庋b、繼承、多態(tài)。下面對(duì)三大特性之一封裝進(jìn)行了總結(jié),需要的朋友可以參考下,希望能給你帶來幫助
    2021-08-08
  • 一文詳解Java?etcd的應(yīng)用場(chǎng)景及編碼實(shí)戰(zhàn)

    一文詳解Java?etcd的應(yīng)用場(chǎng)景及編碼實(shí)戰(zhàn)

    etcd?是一個(gè)高度一致的分布式鍵值存儲(chǔ)系統(tǒng)。本文旨在幫助大家理解etcd,從宏觀角度俯瞰etcd全局,掌握etcd的基本操作技能,需要的可以參考一下
    2022-08-08
  • Spring?Data?JPA系列JpaSpecificationExecutor用法詳解

    Spring?Data?JPA系列JpaSpecificationExecutor用法詳解

    這篇文章主要為大家介紹了Spring?Data?JPA系列JpaSpecificationExecutor用法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-09-09
  • Java中的并發(fā)工具類詳細(xì)解析

    Java中的并發(fā)工具類詳細(xì)解析

    這篇文章主要介紹了Java中的并發(fā)工具類詳細(xì)解析,CountDownLatch、 CyclicBarrier 和 Semaphore 工具類提供了一種并發(fā)流程控制的手段,Exchanger 工具類則提供了在線程間交換數(shù)據(jù)的一種手段,需要的朋友可以參考下
    2023-12-12
  • 利用spring aop實(shí)現(xiàn)動(dòng)態(tài)代理

    利用spring aop實(shí)現(xiàn)動(dòng)態(tài)代理

    這篇文章主要為大家詳細(xì)介紹了利用spring aop實(shí)現(xiàn)動(dòng)態(tài)代理的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-03-03
  • 搭建maven私有倉庫的方法實(shí)現(xiàn)

    搭建maven私有倉庫的方法實(shí)現(xiàn)

    Maven是一個(gè)流行的Java項(xiàng)目管理工具,它可以幫助我們管理項(xiàng)目的構(gòu)建、報(bào)告和文檔,本文主要介紹了搭建maven私有倉庫的方法實(shí)現(xiàn),感興趣的可以了解一下
    2023-05-05
  • JavaWeb項(xiàng)目實(shí)戰(zhàn)之表白墻和在線相冊(cè)

    JavaWeb項(xiàng)目實(shí)戰(zhàn)之表白墻和在線相冊(cè)

    這篇文章主要給大家介紹了關(guān)于JavaWeb項(xiàng)目實(shí)戰(zhàn)之表白墻和在線相冊(cè)的相關(guān)資料,JavaWeb表白墻是一款基于JavaWeb技術(shù)開發(fā)的表白墻應(yīng)用,用戶可以在上面發(fā)布表白信息,也可以查看其他用戶的表白信息,需要的朋友可以參考下
    2023-03-03

最新評(píng)論