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

SpringBoot Starter依賴原理與實例詳解

 更新時間:2022年09月27日 15:26:14   作者:Echoo2787  
SpringBoot中的starter是一種非常重要的機(jī)制,能夠拋棄以前繁雜的配置,將其統(tǒng)一集成進(jìn)starter,應(yīng)用者只需要在maven中引入starter依賴,SpringBoot就能自動掃描到要加載的信息并啟動相應(yīng)的默認(rèn)配置。starter讓我們擺脫了各種依賴庫的處理,需要配置各種信息的困擾

1 Starter

在開發(fā) SpringBoot 項目的時候,我們常常通過 Maven 導(dǎo)入自動各種依賴,其中很多依賴都是以 xxx-starter 命名的。

像這種 starter 依賴是怎么工作的呢?

2 了解 spring.factories機(jī)制

導(dǎo)入一個依賴,我們就可以調(diào)用包內(nèi)的公共類,這是因為公共類可以被異包調(diào)用。很多時候我們添加依賴它會自動往我們的主程序注入一些對象或者監(jiān)聽器,這個是怎么做到的?

2.1 不同包路徑下的依賴注入

SpringBoot 默認(rèn)只掃描啟動類所在目錄里面的對象

而我們導(dǎo)入的依賴是在另外一個包里,SpringBoot 是掃描不到的!

如何讓主項目注入(加載)異包對象呢?通常有兩種方法:

  • 在啟動類上加上@SpringBootApplication注解,配置scanBasePackages屬性,指定掃描路徑。
  • resources/META-INF目錄下創(chuàng)建spring.factories配置文件,在里面配置需要加載的類

2.2 spring.factories 機(jī)制

spring.factories機(jī)制是springboot的核心基礎(chǔ)之一,這可以描述為一種 可插拔結(jié)構(gòu),模仿自java中的SPI擴(kuò)展機(jī)制。

spring.factories 實現(xiàn)例子

1.在任意一個項目中新建一個starter模塊(springboot項目)

導(dǎo)入 springboot 的自動配置依賴,這里我們主要用到它的@Configuration、@Bean注解和ApplicationListener監(jiān)聽器接口

<!-- SpringBoot 自動配置 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-autoconfigure</artifactId>
</dependency>

2.隨便創(chuàng)建一個bean

public class User {
    private String name;
    public void setName(String name) { this.name = name; }
    public String getName() { return name; }
}

3.創(chuàng)建一個初始化監(jiān)聽器

一般的starter會在容器啟動時做一些初始化的操作,這里作為演示只打印一句話。

public class ApplicationInitialize implements ApplicationListener {
    @Override
    public void onApplicationEvent(ApplicationEvent applicationEvent) {
        System.out.println("應(yīng)用初始化");
    }
}

4.創(chuàng)建配置類

這個配置類就是主項目注入starter模塊依賴的入口,當(dāng)它掃描到這個配置類的時候就會加載里面的Bean對象

@Configuration
public class StarterGenericConfig {
    @Bean
    public User getUser() {
        User user = new User();
        user.setName("我來自starter模塊");
        return user;
    }
    @Bean
    public ApplicationInitialize getAppli() {
        return new ApplicationInitialize();
    }
}

5.創(chuàng)建spring.factories配置文件

配置類有了,但是因為和主項目不同包啟動類它掃描不到,這時我們就要通過spring.factories機(jī)制讓它能掃描到這個配置類,完成依賴注入。

先在resource資源目錄下創(chuàng)建META-INF文件夾,然后創(chuàng)建一個名為spring.factories的文件

內(nèi)容如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.echoo.cloud.nacos.starter.config.StarterGenericConfig

這里需要配置StarterGenericConfig配置類的全限定名。

把這個項目導(dǎo)入主項目(添加到主項目的pom依賴中),運(yùn)行主項目,看看是否注入成功

這就是starter依賴注入的基本思路,實際可能復(fù)雜得多,需要繼續(xù)摸索。

3 spring.factories 機(jī)制的實現(xiàn)源碼分析

springframework 框架中有這樣一個類

package org.springframework.core.io.support;
public final class SpringFactoriesLoader {
	public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
    private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap();
	...
	/** SpringFactories 
	  * 靜態(tài)方法, 加載spring.factories文件
	  * */
	private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
       		...
            try {
            	// 通過類加載器加載資源目錄下的"META-INF/spring.factories"文件
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();
                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();
                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        String factoryTypeName = ((String)entry.getKey()).trim();
                        String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;
                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryImplementationName = var9[var11];
                            result.add(factoryTypeName, factoryImplementationName.trim());
                        }
                    }
                }
                cache.put(classLoader, result);
                return result;
            } catch (IOException var13) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
            }
        }
    }
 	...
}

SpringFactoriesLoaderSpring容器初始化時會加載的一個類,而它的靜態(tài)方法loadSpringFactories()里會用類加載器去加載資源文件resourece/META-INF/spring.factories,然后讀取里面的配置參數(shù)(應(yīng)該都是待加載Bean類的映射數(shù)據(jù)),用集合封裝返回Spring容器,后面應(yīng)該就是Spring容器加載對應(yīng)的Bean類。

4 程序運(yùn)行入口run()

前面知道了Spring容器會加載加載資源文件resourece/META-INF/spring.factories然后加載里面對應(yīng)的類,那為什么對應(yīng)的keyorg.springframework.boot.autoconfigure.EnableAutoConfiguration?

先說結(jié)論:Spring容器初始化會加載org.springframework.boot.autoconfigure.EnableAutoConfiguration這個類,完了還會去掃描resourece/META-INF/spring.factories加載里面的Bean類,配置文件是鍵值對形式的,那keyorg.springframework.boot.autoconfigure.EnableAutoConfiguration是因為這個是一個注解,本身就是為了注入拓展類用的,它會在容器初始化或刷新的適當(dāng)時機(jī)注入對應(yīng)的類。因為掃描不到異包配置類上的@Configuration注解,所以創(chuàng)建了一個@EnableAutoConfiguration注解配合spring.factories配置文件的形式來注入配置類

public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch(); // 創(chuàng)建stopWatch對象
        stopWatch.start(); // 開始計算時間
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
        this.configureHeadlessProperty(); // 是否使用模擬輸入輸出設(shè)備(默認(rèn)是,因為服務(wù)器不一定有鼠標(biāo)鍵盤顯示器)
        SpringApplicationRunListeners listeners = this.getRunListeners(args); // 獲取并啟動監(jiān)聽器
        listeners.starting(); // 獲取的監(jiān)聽器為 Event PublishingRunListener,監(jiān)聽并發(fā)布啟動事件
        Collection exceptionReporters;
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            // 準(zhǔn)備應(yīng)用環(huán)境
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
            Banner printedBanner = this.printBanner(environment); // 打印 banner
            context = this.createApplicationContext(); 創(chuàng)建容器
            // 加載 SpringFactories 實例(返回的是實例加載的記錄、報告)
            exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
            // 準(zhǔn)備上下文環(huán)境
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            this.refreshContext(context); // 刷新容器
            this.afterRefresh(context, applicationArguments); // 容器刷新后的動作,這里默認(rèn)沒有做任何實現(xiàn)
            stopWatch.stop(); // 停止計算時間
            if (this.logStartupInfo) { 
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }
            listeners.started(context);
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
           ...
        }
        ...
    }

里面有個創(chuàng)建應(yīng)用容器的方法createApplicationContext(),深入進(jìn)去發(fā)現(xiàn)他是根據(jù)webApplicationType類型去決定創(chuàng)建那種容器,而webApplicationType類型在SpringApplication初始化的時候指定。

    protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {
                switch(this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
                    break;
                case REACTIVE:
                    contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
                    break;
                default:
                    contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
                }
            } catch (ClassNotFoundException var3) {
                throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
            }
        }
		// 反射創(chuàng)建容器實例
        return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
    }

這里一共有三種類型的容器

  • SERVLET 類型創(chuàng)建 AnnotationConfigServletWebServerApplicationContextServlet容器)
  • REACTIVE類型創(chuàng)建AnnotationConfigReactiveWebServerApplicationContextReactive容器)
  • 默認(rèn)創(chuàng)建AnnotationConfigApplicationContextApplication容器)

SpringApplication的構(gòu)造器中對webApplicationType類型進(jìn)行了初始化,默認(rèn)返回SERVLET 類型。

    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        ...
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        ...
    }

也就是說默認(rèn)創(chuàng)建AnnotationConfigServletWebServerApplicationContextServlet容器)

在上面入口方法run()方法中,有一個refreshContext()方式,這個刷新容器的方法里面

跟蹤這個refreshContext()底層是一個refresh()方法,三種容器都分別實現(xiàn)了這個方法

這里著重看ServletWebServerApplicationContext.refresh()

    public final void refresh() throws BeansException, IllegalStateException {
        try { super.refresh(); } catch (RuntimeException var3) { ... }
    }

發(fā)現(xiàn)它調(diào)用的是父類AbstractApplicationContextrefresh()函數(shù)

    public void refresh() throws BeansException, IllegalStateException {
        synchronized(this.startupShutdownMonitor) { // 上鎖,防止并發(fā)
            this.prepareRefresh();                  // 刷新準(zhǔn)備工作,記錄開始時間,校驗配置文件
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory(); // 獲取Bean工廠
            this.prepareBeanFactory(beanFactory);   // Bean工廠準(zhǔn)備工作,不詳談
            try {
                this.postProcessBeanFactory(beanFactory);
                this.invokeBeanFactoryPostProcessors(beanFactory); // Spring拓展點之一
                ...
            } catch (BeansException var9) {
                ...
            } finally {
                this.resetCommonCaches();
            }
        }
    }

重點在invokeBeanFactoryPostProcessors(beanFactory)方法上,這是SpringBoot實現(xiàn)Spring拓展的關(guān)鍵節(jié)點,這個方法執(zhí)行時會調(diào)用實現(xiàn)了BeanFactoryPostProcessors接口的實現(xiàn)類的postProcessBeanFactory(factory)方法

(也會調(diào)用BeanDefinitionRegistryPostProcessor接口的各個實現(xiàn)類的postProcessBeanDefinitionRegistry(registry)方法)

進(jìn)入invokeBeanFactoryPostProcessors(beanFactory)方法

    public static void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
   			...
            currentRegistryProcessors = new ArrayList();
            // 獲取所有 BeanDefinitionRegistryPostProcessor 接口實現(xiàn)類的全限定名集合
            postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false); 
            String[] var16 = postProcessorNames;
            var9 = postProcessorNames.length;
            int var10;
            String ppName;
            for(var10 = 0; var10 < var9; ++var10) {
                ppName = var16[var10];
                if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
                	// 根據(jù) Bean 名獲取 Bean 對象放入 currentRegistryProcessors
                    currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
                    processedBeans.add(ppName);
                }
            }
            sortPostProcessors(currentRegistryProcessors, beanFactory); // 排序,暫時沒看,不知道排啥
            registryProcessors.addAll(currentRegistryProcessors);
            // 調(diào)用 Bean 定義注冊后處理器
            invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
            currentRegistryProcessors.clear();
			...
	}

每一個Bean類的定義注冊是在Spring容器中完成的,在上面invokeBeanFactoryPostProcessors()方法中,通過 Bean 工廠獲取 了所有BeanDefinitionRegistryPostProcessor接口的實現(xiàn)類名,然后再通過 invokeBeanDefinitionRegistryPostProcessors()方法調(diào)用所有實現(xiàn)類的postProcessBeanDefinitionRegistry()方法去做 Bean 類注冊后的相關(guān)處理動作。

BeanDefinitionRegistryPostProcessor接口:

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
	void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry var1) throws BeansException;
}

知道這個有什么用呢?前面我們知道了Springboot容器中的AnnotationConfigServletWebServerApplicationContextServlet容器)是通過反射獲取AnnotationConfigServletWebServerApplicationContext的構(gòu)造器創(chuàng)建實例的,所以我們看看AnnotationConfigServletWebServerApplicationContext的構(gòu)造器長什么樣兒。

    public AnnotationConfigServletWebServerApplicationContext(DefaultListableBeanFactory beanFactory) {
        super(beanFactory);
        this.annotatedClasses = new LinkedHashSet();
        this.reader = new AnnotatedBeanDefinitionReader(this); // 注解 Bean 定義讀取器
        this.scanner = new ClassPathBeanDefinitionScanner(this);
    }

進(jìn)入AnnotatedBeanDefinitionReader看它的構(gòu)造器

    public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
        ...
        // 通過 AnnotationConfigUtils 工具注冊 注解配置處理器
        AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
    }

再進(jìn)入AnnotationConfigUtils 工具的registerAnnotationConfigProcessors()看看它是如何注冊的

public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(BeanDefinitionRegistry registry, @Nullable Object source) {
	...
	Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet(8); // 存儲 BeanDefinitionHolder 對象的集合
    RootBeanDefinition def;
	if (!registry.containsBeanDefinition("org.springframework.context.annotation.internalConfigurationAnnotationProcessor")) {
		// 創(chuàng)建一個 ConfigurationClassPostProcessor 的 RootBeanDefinition 對象
		def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
        def.setSource(source);
        // 把 ConfigurationClassPostProcessor 的 RootBeanDefinition 對象裝入一個 BeanDefinitionHolder (容器)
        // 并映射名字為 org.springframework.context.annotation.internalConfigurationAnnotationProcessor
        beanDefs.add(registerPostProcessor(registry, def, "org.springframework.context.annotation.internalConfigurationAnnotationProcessor"));
    }
	...
}

在它的registerAnnotationConfigProcessors()方法中看到了它用注冊后處理器registerPostProcessor去注冊org.springframework.context.annotation.internalConfigurationAnnotationProcessorBeanDefinition(Bean定義描述對象)和BeanDefinitionHolder(Bean定義描述對象容器),然后返回這個BeanDefinitionHolder(Bean定義描述對象容器)存儲到beanDefs(Bean定義描述對象容器集合)里面。

到這里就是說明在初始化AnnotationConfigServletWebServerApplicationContextServlet容器)時,會用org.springframework.context.annotation.internalConfigurationAnnotationProcessor這個名字注冊ConfigurationClassPostProcessor這個 Bean對象,然后就能根據(jù)它的BeanDefinitionHolder(Bean定義描述對象容器)去創(chuàng)建ConfigurationClassPostProcessor對象。

現(xiàn)在問題就來到了ConfigurationAnnotationProcessor對象身上了,為啥要創(chuàng)建它?因為它就是加載spring.factories配置文件的關(guān)鍵。

進(jìn)入它的postProcessBeanDefinitionRegistry()方法

    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        int registryId = System.identityHashCode(registry);
        if (this.registriesPostProcessed.contains(registryId)) {       // 判斷是否有對應(yīng)的注冊記錄
            throw new IllegalStateException("postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
        } else if (this.factoriesPostProcessed.contains(registryId)) { // 是否有對應(yīng)的 Bean 工廠
            throw new IllegalStateException("postProcessBeanFactory already called on this post-processor against " + registry);
        } else { // 都沒有,說明這個 BeanDefinition 沒有注冊加載過
            this.registriesPostProcessed.add(registryId); // 添加注冊記錄
            this.processConfigBeanDefinitions(registry);  // 處理這個 BeanDefinition 的配置
        }
    }

深入processConfigBeanDefinitions()看它怎么處理這個 BeanDefinition 的配置

    public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
        List<BeanDefinitionHolder> configCandidates = new ArrayList(); // 待處理配置集合
        String[] candidateNames = registry.getBeanDefinitionNames();
        String[] var4 = candidateNames;
        int var5 = candidateNames.length;
        for(int var6 = 0; var6 < var5; ++var6) {
            String beanName = var4[var6];
            BeanDefinition beanDef = registry.getBeanDefinition(beanName); // 根據(jù)名稱獲取 BeanDefinition
            // 判斷這個 BeanDefinition 的配置屬性是不是空
            if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
                if (this.logger.isDebugEnabled()) {
                	// 如果不是空就說明這個 BeanDefinition 已經(jīng)被當(dāng)作配置類處理過了
                    this.logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
                }
            } else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
            	// 如果為空,放入待處理配置集合里等待后續(xù)處理
                configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
            }
        }
		// 指令排序相關(guān),不深究
        if (!configCandidates.isEmpty()) {
            configCandidates.sort((bd1, bd2) -> {
                int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
                int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
                return Integer.compare(i1, i2);
            });
            ...
            // 根據(jù)環(huán)境創(chuàng)建了一個配置類解析器
            ConfigurationClassParser parser = new ConfigurationClassParser(this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry);
            Set<BeanDefinitionHolder> candidates = new LinkedHashSet(configCandidates);
            HashSet alreadyParsed = new HashSet(configCandidates.size());

            do {
                parser.parse(candidates); // 解析配置類
                parser.validate();
                Set<ConfigurationClass> configClasses = new LinkedHashSet(parser.getConfigurationClasses());
                configClasses.removeAll(alreadyParsed);
                if (this.reader == null) {
                    this.reader = new ConfigurationClassBeanDefinitionReader(registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry());
                }
				...
            } while(!candidates.isEmpty())
            ...
        }
    }

進(jìn)入ConfigurationClassParser.parse()方法,看看它怎么解析配置類

	public void parse(Set<BeanDefinitionHolder> configCandidates) {
		for (BeanDefinitionHolder holder : configCandidates) {
			BeanDefinition bd = holder.getBeanDefinition();
			try { // 對 BeanDefinition 做解析操作
				if (bd instanceof AnnotatedBeanDefinition) {
					parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
				}
				else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
					parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
				}
				else {
					parse(bd.getBeanClassName(), holder.getBeanName());
				}
			}
			catch (BeanDefinitionStoreException ex) {
				throw ex;
			}
			catch (Throwable ex) {
				throw new BeanDefinitionStoreException(
						"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
			}
		}
		// 延遲導(dǎo)入選擇器
		this.deferredImportSelectorHandler.process();
	}

解析發(fā)現(xiàn)不了什么線索,進(jìn)入this.deferredImportSelectorHandler.process()延遲導(dǎo)入選擇處理器看看

層層追蹤:

this.deferredImportSelectorHandler.process()

DeferredImportSelectorGroupingHandler.processGroupImports()

grouping.getImports()

grouping.getImports()

this.group.selectImports()

追蹤到public interface ImportSelector {...}接口

在找到它的實現(xiàn)類AutoConfigurationImportSelector

在實現(xiàn)類AutoConfigurationImportSelector里層層追蹤

selectImports()

getAutoConfigurationEntry()

getCandidateConfigurations()

getSpringFactoriesLoaderFactoryClass()

最后追蹤到 getSpringFactoriesLoaderFactoryClass()方法

    protected Class<?> getSpringFactoriesLoaderFactoryClass() {
        return EnableAutoConfiguration.class;
    }

里面返回的是一個EnableAutoConfiguration.class類,這個類就是我們在spring.factories配置文件里面配置的org.springframework.boot.autoconfigure.EnableAutoConfiguration

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.echoo.cloud.nacos.starter.config.StarterGenericConfig

到此這篇關(guān)于SpringBoot Starter依賴原理與實例詳解的文章就介紹到這了,更多相關(guān)SpringBoot Starter依賴內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java判斷字符串String是否為空問題淺析

    java判斷字符串String是否為空問題淺析

    這篇文章主要介紹了java判斷字符串String是否為空問題,有需要的朋友可以參考一下
    2014-01-01
  • java swing實現(xiàn)簡單計算器界面

    java swing實現(xiàn)簡單計算器界面

    這篇文章主要為大家詳細(xì)介紹了java swing實現(xiàn)簡單計算器界面,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • Java中高效的判斷數(shù)組中某個元素是否存在詳解

    Java中高效的判斷數(shù)組中某個元素是否存在詳解

    相信大家在操作Java的時候,經(jīng)常會要檢查一個數(shù)組(無序)是否包含一個特定的值?這是一個在Java中經(jīng)常用到的并且非常有用的操作。同時,這個問題在Stack Overflow中也是一個非常熱門的問題。本文將分析幾種常見用法及其時間成本,有需要的朋友們可以參考借鑒。
    2016-11-11
  • Spring學(xué)習(xí)之開發(fā)環(huán)境搭建的詳細(xì)步驟

    Spring學(xué)習(xí)之開發(fā)環(huán)境搭建的詳細(xì)步驟

    本篇文章主要介紹了Spring學(xué)習(xí)之開發(fā)環(huán)境搭建的詳細(xì)步驟,具有一定的參考價值,有興趣的可以了解一下
    2017-07-07
  • Java線程之間的共享與協(xié)作詳解

    Java線程之間的共享與協(xié)作詳解

    這篇文章主要介紹了Java線程之間的共享與協(xié)作詳解,進(jìn)程是操作系統(tǒng)進(jìn)行資源分配的最小單位,線程是進(jìn)程的一個實體,是CPU調(diào)度和分派的基本單位,它是比經(jīng)常更小的、能夠獨立運(yùn)行的基本單位
    2022-07-07
  • java并發(fā)編程Lock鎖可重入性與公平性分析

    java并發(fā)編程Lock鎖可重入性與公平性分析

    這篇文章主要為大家介紹了java并發(fā)編程Lock鎖可重入性與公平性分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-05-05
  • Java通過反射將 Excel 解析成對象集合實例

    Java通過反射將 Excel 解析成對象集合實例

    這篇文章主要介紹了Java通過反射將 Excel 解析成對象集合實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-08-08
  • springboot上傳文件,url直接訪問資源問題

    springboot上傳文件,url直接訪問資源問題

    這篇文章主要介紹了springboot上傳文件,url直接訪問資源問題。具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • 詳解Java如何給按鈕添加監(jiān)視器

    詳解Java如何給按鈕添加監(jiān)視器

    這篇文章主要介紹了詳解Java如何給按鈕添加監(jiān)視器,使用匿名對象、實現(xiàn)接口、實現(xiàn)類、Lambda表達(dá)式、注解等,需要的朋友可以參考下
    2023-04-04
  • webuploader 實現(xiàn)圖片批量上傳功能附實例代碼

    webuploader 實現(xiàn)圖片批量上傳功能附實例代碼

    這篇文章主要介紹了webuploader 實現(xiàn)圖片批量上傳功能,非常不錯,具有參考借鑒價值,需要的朋友可以參考下
    2017-11-11

最新評論