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

Spring?ComponentScan的掃描過程解析

 更新時間:2022年03月15日 10:03:12   作者:morris131  
這篇文章主要介紹了spring?ComponentScan的掃描過程解析,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下

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:context="http://www.springframework.org/schema/context"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"
	   default-lazy-init="false">
	<context:component-scan base-package="com.morris.spring.demo.service"/>
</beans>

xml中使用自定義標簽context實現(xiàn),最終會調(diào)用到ComponentScanBeanDefinitionParser.parse()方法進行解析。

ComponentScanBeanDefinitionParser.parse()

// org.springframework.context.annotation.ComponentScanBeanDefinitionParser#parse
public BeanDefinition parse(Element element, ParserContext parserContext) {
	// 獲得base-package指定的包名
	String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
	basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
	// base-package中可能有多個,用逗號分隔,轉(zhuǎn)換為數(shù)組
	String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
			ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);

	// Actually scan for bean definitions and register them.
	// 創(chuàng)建掃描器
	ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
	// 開始掃描
	Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);

	// 注冊了一些組件
	// ConfigurationClassPostProcessor
	// AutowiredAnnotationBeanPostProcessor
	// CommonAnnotationBeanPostProcessor
	// EventListenerMethodProcessor
	// DefaultEventListenerFactory
	registerComponents(parserContext.getReaderContext(), beanDefinitions, element);

	return null;
}

ComponentScanBeanDefinitionParser#configureScanner

創(chuàng)建掃描器ClassPathBeanDefinitionScanner

protected ClassPathBeanDefinitionScanner configureScanner(ParserContext parserContext, Element element) {
    boolean useDefaultFilters = true;
    if (element.hasAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE)) {
        useDefaultFilters = Boolean.parseBoolean(element.getAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE));
    }

    // Delegate bean definition registration to scanner class.
    // ClassPathBeanDefinitionScanner構(gòu)造方法中添加了默認的includeFilters為@Component
    ClassPathBeanDefinitionScanner scanner = createScanner(parserContext.getReaderContext(), useDefaultFilters);
    scanner.setBeanDefinitionDefaults(parserContext.getDelegate().getBeanDefinitionDefaults());
    scanner.setAutowireCandidatePatterns(parserContext.getDelegate().getAutowireCandidatePatterns());

    if (element.hasAttribute(RESOURCE_PATTERN_ATTRIBUTE)) {
        scanner.setResourcePattern(element.getAttribute(RESOURCE_PATTERN_ATTRIBUTE));
    }

    try {
        parseBeanNameGenerator(element, scanner);
    }
    catch (Exception ex) {
        parserContext.getReaderContext().error(ex.getMessage(), parserContext.extractSource(element), ex.getCause());
    }

    try {
        parseScope(element, scanner);
    }
    catch (Exception ex) {
        parserContext.getReaderContext().error(ex.getMessage(), parserContext.extractSource(element), ex.getCause());
    }

    parseTypeFilters(element, scanner, parserContext);

    return scanner;
}

怎么知道掃描哪些注解呢?

ClassPathBeanDefinitionScanner構(gòu)造方法中會注入默認的Filter。

protected void registerDefaultFilters() {
    // 只掃描@Component注解了的類,而@Sevice、@Configuration、@Controller等注解都被@Component修飾
    this.includeFilters.add(new AnnotationTypeFilter(Component.class));
    ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
    try {
        this.includeFilters.add(new AnnotationTypeFilter(
                ((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
        logger.trace("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
    }
    catch (ClassNotFoundException ex) {
        // JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
    }
    try {
        this.includeFilters.add(new AnnotationTypeFilter(
                ((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
        logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
    }
    catch (ClassNotFoundException ex) {
        // JSR-330 API not available - simply skip.
    }
}

在源碼中只會掃描@Component注解,而@Sevice、@Configuration、@Controller等注解都被@Component修飾,最終都會被掃描到。

ClassPathBeanDefinitionScanner#doScan

開始掃描:

// org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
	Assert.notEmpty(basePackages, "At least one base package must be specified");
	Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
	for (String basePackage : basePackages) {
		// 查找basePackage下所有被@Component注解修飾了的類
		Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
		for (BeanDefinition candidate : candidates) {
			// 上面findCandidateComponents只是為BD設(shè)置了幾個屬性,BD的其他屬性并沒有初始化,所以需要遍歷一次初始化屬性并注冊到registry
			ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
			candidate.setScope(scopeMetadata.getScopeName());
			String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
			if (candidate instanceof AbstractBeanDefinition) {
				postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
			}
			if (candidate instanceof AnnotatedBeanDefinition) {
				AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
			}
			if (checkCandidate(beanName, candidate)) {
				BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
				definitionHolder =
						AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
				beanDefinitions.add(definitionHolder);
				// 注冊到registry
				registerBeanDefinition(definitionHolder, this.registry);
			}
		}
	}
	return beanDefinitions;
}

ClassPathScanningCandidateComponentProvider#scanCandidateComponents

查找basePackage下所有被@Component注解修飾了的類:

// org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#scanCandidateComponents
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
	Set<BeanDefinition> candidates = new LinkedHashSet<>();
	try {
		// basePackage=com.morris.spring..service
		// packageSearchPath=classpath*:com/morris/spring/service/**/*.class
		String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
			resolveBasePackage(basePackage) + '/' + this.resourcePattern;
		// 獲取basePackage下所有的class文件
		Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
		boolean traceEnabled = logger.isTraceEnabled();
		boolean debugEnabled = logger.isDebugEnabled();
		for (Resource resource : resources) {
			if (traceEnabled) {
				logger.trace("Scanning " + resource);
			}
			if (resource.isReadable()) {
				try {
					// 使用ASM將class文件的內(nèi)容封裝為MetadataReader對象
					// 注意這里用的不是反射,反射會加載類,占用堆空間
					MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
					// 判斷類是否包含@Component注解
					if (isCandidateComponent(metadataReader)) {
						// 封裝為BD
						ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
						sbd.setSource(resource);
						if (isCandidateComponent(sbd)) {
							if (debugEnabled) {
								logger.debug("Identified candidate component class: " + resource);
							}
							candidates.add(sbd);
						}
... ...
	return candidates;
}

registerAnnotationConfigProcessors

注冊了多種重要的組件:

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);

	// BeanFactoryPostProcessor
	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));
	}

	// BeanPostProcessor
	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));
	}

	// BeanFactoryPostProcessor
	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;
}

掃描的最終結(jié)果就是將類上面有@Component注解的類構(gòu)建為一個BeanDefinition中,Spring容器中有兩個集合來存放這些BeanDefinition:

  • beanDefinitionNames:List<String>,存放所有的BeanDefinition對應(yīng)的name
  • beanDefinitionMap:Map<String, BeanDefinition>,存放所有的BeanDefinition

注解的掃描過程

注解掃描的使用:

package com.morris.spring.demo.annotation;

import com.morris.spring.demo.service.CityService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

@ComponentScan("com.morris.spring.service")
public class ComponentScanDemo {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ComponentScanDemo.class);
		CityService cityService = applicationContext.getBean(CityService.class);
		cityService.city();
	}
}

使用@ComponentScan注解指定包的掃描,掃描過程將由ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry完成。

ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
	int registryId = System.identityHashCode(registry);
	if (this.registriesPostProcessed.contains(registryId)) {
		throw new IllegalStateException(
				"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
	}
	if (this.factoriesPostProcessed.contains(registryId)) {
		throw new IllegalStateException(
				"postProcessBeanFactory already called on this post-processor against " + registry);
	}
	this.registriesPostProcessed.add(registryId);
	// 重點
	processConfigBeanDefinitions(registry);
}

ConfigurationClassPostProcessor#processConfigBeanDefinitions

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
	List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
	String[] candidateNames = registry.getBeanDefinitionNames();
	// 先收集有@Configuration、@Component、@ComponentScan、@Import、@ImportResource、@Bean的BD
	for (String beanName : candidateNames) {
		BeanDefinition beanDef = registry.getBeanDefinition(beanName);
		if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
			if (logger.isDebugEnabled()) {
				logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
			}
		}
		else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
			// 進來需要@Component、@ComponentScan、@Import、@ImportResource、@Bean
			configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
		}
	}
    ... ...
	// Parse each @Configuration class
	ConfigurationClassParser parser = new ConfigurationClassParser(
			this.metadataReaderFactory, this.problemReporter, this.environment,
			this.resourceLoader, this.componentScanBeanNameGenerator, registry);
	Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
	Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
	do {
		// 開始解析
		parser.parse(candidates);
		parser.validate();

ConfigurationClassParser#doProcessConfigurationClass

protected final SourceClass doProcessConfigurationClass(
		ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
		throws IOException {
	... ...
	// Process any @ComponentScan annotations
	// 處理@ComponentScan注解,掃描包下帶有@Component的注解,與xml中自定義標簽context:component-scan的掃描流程一致
	Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
			sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
	if (!componentScans.isEmpty() &&
			!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
		for (AnnotationAttributes componentScan : componentScans) {
			// The config class is annotated with @ComponentScan -> perform the scan immediately
			Set<BeanDefinitionHolder> scannedBeanDefinitions =
					this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
			// Check the set of scanned definitions for any further config classes and parse recursively if needed
			for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
				BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
				if (bdCand == null) {
					bdCand = holder.getBeanDefinition();
				}
				// checkConfigurationClassCandidate這個里面會特殊處理@Configutation為full
				if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
					parse(bdCand.getBeanClassName(), holder.getBeanName());
				}
			}
		}
	}
	... ...
	return null;
}

ComponentScanAnnotationParser#parse

public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
	// 解析@ComponentScan注解,構(gòu)建ClassPathBeanDefinitionScanner
	// 構(gòu)建方法中會添加默認的includeFilters為@Component
	ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
			componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
	Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
	boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
	scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
			BeanUtils.instantiateClass(generatorClass));
	ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
	if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
		scanner.setScopedProxyMode(scopedProxyMode);
	}
	else {
		Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
		scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
	}
	scanner.setResourcePattern(componentScan.getString("resourcePattern"));
	for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
		for (TypeFilter typeFilter : typeFiltersFor(filter)) {
			scanner.addIncludeFilter(typeFilter);
		}
	}
	for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
		for (TypeFilter typeFilter : typeFiltersFor(filter)) {
			scanner.addExcludeFilter(typeFilter);
		}
	}
	boolean lazyInit = componentScan.getBoolean("lazyInit");
	if (lazyInit) {
		scanner.getBeanDefinitionDefaults().setLazyInit(true);
	}
	Set<String> basePackages = new LinkedHashSet<>();
	String[] basePackagesArray = componentScan.getStringArray("basePackages");
	for (String pkg : basePackagesArray) {
		String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
				ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
		Collections.addAll(basePackages, tokenized);
	}
	for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
		basePackages.add(ClassUtils.getPackageName(clazz));
	}
	if (basePackages.isEmpty()) {
		basePackages.add(ClassUtils.getPackageName(declaringClass));
	}
	scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
		@Override
		protected boolean matchClassName(String className) {
			return declaringClass.equals(className);
		}
	});
	// 開始掃描
	return scanner.doScan(StringUtils.toStringArray(basePackages));
}

可以發(fā)現(xiàn)注解的掃描最后會調(diào)用ClassPathBeanDefinitionScanner#doScan(),與XML中的掃描是同一個方法。

總結(jié)

  • XML的掃描過程發(fā)生在obtainFreshBeanFactory(),也就是創(chuàng)建BeanFactory時,而注解的掃描過程發(fā)生在invokeBeanFactoryPostProcessors()。
  • XML的掃描會在obtainFreshBeanFactory()時注入ConfigurationClassPostProcessor,而注解的掃描是在創(chuàng)建AnnotationConfigApplicationContext實例時注入ConfigurationClassPostProcessor,如果xml掃描到的類帶有@ComponentScan注解,那么還會繼續(xù)在invokeBeanFactoryPostProcessors()階段繼續(xù)掃描。

到此這篇關(guān)于spring ComponentScan的掃描過程的文章就介紹到這了,更多相關(guān)spring ComponentScan掃描內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java MongoDB實現(xiàn)REST過程解析

    Java MongoDB實現(xiàn)REST過程解析

    這篇文章主要介紹了Java MongoDB實現(xiàn)REST過程解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-08-08
  • SpringBoot 下的 Static 文件夾打包成前端資源的示例代碼

    SpringBoot 下的 Static 文件夾打包成前端資源的示例代碼

    這篇文章主要介紹了SpringBoot 下的 Static 文件夾如何打包成前端資源,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-06-06
  • VsCode配置java環(huán)境的詳細圖文教程

    VsCode配置java環(huán)境的詳細圖文教程

    vscode是一個免費的代碼編輯器,支持多種主題,應(yīng)用起來簡單方便,下面這篇文章主要給大家介紹了關(guān)于VsCode配置java環(huán)境的詳細圖文教程,文中通過圖文介紹的非常詳細,需要的朋友可以參考下
    2023-02-02
  • 基于strict-origin-when-cross-origin問題的解決

    基于strict-origin-when-cross-origin問題的解決

    這篇文章主要介紹了基于strict-origin-when-cross-origin問題的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-03-03
  • SpringBoot中使用Swagger的超簡單方法

    SpringBoot中使用Swagger的超簡單方法

    大家一致認為springBoot使用swagger太麻煩了,每次都需要編寫config,今天小編告訴大家一種超簡單配置方法,教大家如何整合swagger,感興趣的朋友跟隨小編一起看看吧
    2021-07-07
  • xxl-job如何濫用netty導(dǎo)致的問題及解決方案

    xxl-job如何濫用netty導(dǎo)致的問題及解決方案

    本篇文章講解xxl-job作為一款分布式任務(wù)調(diào)度系統(tǒng)是如何濫用netty的,導(dǎo)致了怎樣的后果以及如何修改源碼解決這些問題,netty作為一種高性能的網(wǎng)絡(luò)編程框架,十分受大家喜愛,今天就xxl-job濫用netty這一問題給大家詳細下,感興趣的朋友一起看看吧
    2021-05-05
  • Java可重入鎖reentrantLock解析

    Java可重入鎖reentrantLock解析

    這篇文章主要介紹了Java可重入鎖reentrantLock解析,reentrantLock跟synchronized代碼結(jié)構(gòu)差不多,只是多了一個lock和unlock的過程,需要的朋友可以參考下
    2023-12-12
  • Java 在PDF中添加條形碼的兩種方法

    Java 在PDF中添加條形碼的兩種方法

    本文就將通過使用Java程序來演示如何在PDF文檔中添加Codebar、Code128A和Code39條形碼。通過實例代碼給大家講解的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2020-03-03
  • java版十大排序經(jīng)典算法:完整代碼

    java版十大排序經(jīng)典算法:完整代碼

    優(yōu)秀的文章也不少,但是Java完整版的好像不多,我把所有的寫一遍鞏固下,同時也真誠的希望閱讀到這篇文章的小伙伴們可以自己去從頭敲一遍,不要粘貼復(fù)制!希望我的文章對你有所幫助,每天進步一點點
    2021-07-07
  • java短信驗證碼登錄功能設(shè)計與實現(xiàn)

    java短信驗證碼登錄功能設(shè)計與實現(xiàn)

    現(xiàn)在不管是各類的網(wǎng)站,還是大小社交app,登錄方式是越來越多了,但是大部分還是以短信登錄為主,本文主要介紹了java短信驗證碼登錄功能設(shè)計與實現(xiàn),感興趣的可以了解一下
    2021-11-11

最新評論