Spring源码:bean的生命周期(一)

前言

本节将正式介绍Spring源码细节,将讲解Bean生命周期。请注意,虽然我们不希望过于繁琐地理解Spring源码,但也不要认为Spring源码很简单。在本节中,我们将主要讲解Spring 5.3.10版本的源代码。如果您看到的代码与我讲解的不同,也没有关系,因为其中的原理和业务逻辑基本相同。为了更好地理解,我们将先讲解Bean的生命周期,再讲解Spring的启动原理和流程,因为启动是准备工作的一部分。

题外话

目前在该版本中,引入了一个名为jfr的JDK技术,类似于Java飞行日志(JFL),也称为飞行数据记录器(Black Box)技术。具体作用不再详细阐述,读者可以参考此文:JFR介绍
如果您看到以下代码,请直接跳过,因为它并没有太大的作用:

    public AnnotationConfigApplicationContext() {
        StartupStep createAnnotatedBeanDefReader = this.getApplicationStartup().start("spring.context.annotated-bean-reader.create");
        // 额外会创建StandardEnvironment
        this.reader = new AnnotatedBeanDefinitionReader(this);
        createAnnotatedBeanDefReader.end();
        this.scanner = new ClassPathBeanDefinitionScanner(this);
    }

需要注意的是,其中的 StartupStep 关于默认实现并没有什么实际作用。但是,还有一种实现方式是 FlightRecorderStartupStep,它是JDK的JFR技术。

Bean的生成过程

生成BeanDefinition

BeanDefinition的作用大家基本通过前面的文章也知道了大概,就是用来描述bean的。
那么它是如何加载的呢?首先我们看一下 ClassPathBeanDefinitionScanner 类,它是用于扫描的。其中有一个属性是 BeanDefinitionRegistry,即Bean定义的注册类。默认实现是 DefaultListableBeanFactory,但是在 ClassPathBeanDefinitionScanner 类中并没有直接使用该类作为属性,而是使用了它的父接口 BeanDefinitionRegistry。这是因为 ClassPathBeanDefinitionScanner 类实际上并没有使用 BeanDefinitionRegistry 接口中的许多方法来注册Bean定义。

接下来,我们来分析 ClassPathBeanDefinitionScanner 类的 scan 方法:

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

            Set<BeanDefinition> candidates = findCandidateComponents(basePackage);

            for (BeanDefinition candidate : candidates) {
                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) {
                    // 解析@Lazy、@Primary、@DependsOn、@Role、@Description
                    AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
                }

                // 检查Spring容器中是否已经存在该beanName
                if (checkCandidate(beanName, candidate)) {
                    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                    definitionHolder =
                            AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                    beanDefinitions.add(definitionHolder);

                    // 注册
                    registerBeanDefinition(definitionHolder, this.registry);
                }
            }
        }
        return beanDefinitions;
    }

大致逻辑如下:

  1. 获取扫描包路径
  2. findCandidateComponents:获取符合条件的bean
  3. 遍历candidate(候选bean),由于第二部使用了ASM技术,所以并没有真正获取beanclass而是使用了beanname替代所以,遍历的做法就是将符合条件的bean定义进行注册。
  4. scopeMetadata解析scope注解。
  5. beanNameGenerator构建当前bean的唯一名字。
  6. postProcessBeanDefinition这里其实就是进行默认值赋值。
  7. processCommonDefinitionAnnotations进行解析@Lazy、@Primary、@DependsOn、@Role、@Description
  8. checkCandidate(beanName, candidate)再次检查是否该beanName已经注册过。
  9. registerBeanDefinition,注册到我们的DefaultListableBeanFactory的BeanDefinitionMap中。

其实这里基本就已经大概了解 的差不多了,然后再继续讲解下每一个流程里面都走了那些逻辑:

findCandidateComponents

其主要逻辑会进入如下源码:

private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
        Set<BeanDefinition> candidates = new LinkedHashSet<>();
        try {
            // 获取basePackage下所有的文件资源
            String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                    resolveBasePackage(basePackage) + '/' + this.resourcePattern;

            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 {
                        MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
                        // excludeFilters、includeFilters判断
                        if (isCandidateComponent(metadataReader)) { // @Component-->includeFilters判断
                            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;
    }

让我们来深入了解一下Bean扫描的具体细节。以下是主要流程:

  1. 获取basePackage下所有的文件资源。以下分析注解信息等都是用到了ASM技术,并没有真正的去加载这个类。
  2. isCandidateComponent(metadataReader),进行判断是否当前类具有@component注解。
  3. isCandidateComponent(sbd),进行判断是否当前类属于内部类、接口、抽象类
  4. 符合上述条件则会加入到bean定义候选集合中。

ASM技术这里不做多解释,主要看下Spring是如何进行判断校验当前bean是否符合条件的,第一个 isCandidateComponent(metadataReader)方法:

	protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
		for (TypeFilter tf : this.excludeFilters) {
			if (tf.match(metadataReader, getMetadataReaderFactory())) {
				return false;
			}
		}
		// 符合includeFilters的会进行条件匹配,通过了才是Bean,也就是先看有没有@Component,再看是否符合@Conditional
		for (TypeFilter tf : this.includeFilters) {
			if (tf.match(metadataReader, getMetadataReaderFactory())) {
				return isConditionMatch(metadataReader);
			}
		}
		return false;
	}

那么includeFilters默认会在启动AnnotationConfigApplicationContext时就会默认注册一个解析Component注解的filter,代码如下:

protected void registerDefaultFilters() {  
  
   // 注册@Component对应的AnnotationTypeFilter  
  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.  
  }  
}

如果符合过滤条件,那么他就会开始生成最初的bean定义:

public ScannedGenericBeanDefinition(MetadataReader metadataReader) {  
   Assert.notNull(metadataReader, "MetadataReader must not be null");  
   this.metadata = metadataReader.getAnnotationMetadata();  
   // 这里只是把className设置到BeanDefinition中  
  setBeanClassName(this.metadata.getClassName());  
   setResource(metadataReader.getResource());  
}

那么就剩下最后的校验了:isCandidateComponent(sbd);再来看看他的作用子什么:

protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {  
   AnnotationMetadata metadata = beanDefinition.getMetadata();  
   return (metadata.isIndependent() && (metadata.isConcrete() ||  
         (metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));  
}

看完一脸懵,那我们就好好解释一下每个判断都是什么意思吧:

  1. metadata.isIndependent():是否当前类为内部类,众说周知java语言编译内部类的时候会产生两个class文件,比如下面这样:
    image
    那么这个Member内部类是不会被Spring作为单独的类去扫描的。除非也加上@component注解,并且为static内部类
  2. metadata.isConcrete():这个类就是判断下是否是接口还是抽象类
  3. metadata.hasAnnotatedMethods(Lookup.class.getName()):判断是否有方法是带有Lookup注解的,Lookup注解工作中用到的确实有些少,我在这里简要说明下:比如我们注册给Spring的bean都是单例,但是如果我们有多例的bean被一个单例的bean所依赖的话,一次属性只能注入一次,也打不到多例的效果,这时候就可以用Lookup注解实现了,比如这样:
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);  
 //UserService 为单例
UserService bean = applicationContext.getBean(UserService.class);  
bean.test();  
bean.test();
@Component  
public class UserService {  

   @Autowired  
  private User user;  
  
   public void test(){  
      System.out.println(user);  
   }  
}
 @Component  
@Scope("prototype")  
public class User {  
  
}

如果这样执行的话,你永远拿不到多例的User类,因为UserService 在属性依赖注入的时候已经做完了赋值,每次调用拿到的都是同一个对象,那如果不这么做可不可以,当然可以:比如这样改下UserService 类:

@Component  
public class UserService {  
  
   @Autowired  
  private User user;  
  
   public void test(){  
      System.out.println(get());  
   }  
  
   @Lookup  
  public User get(){  
      return null;  
   }  
}

至于为什么这里返回null,后续在进行讲解,只要知道他的注解的作用即可。到此我们终于解决完了findCandidateComponents方法找出了符合条件的bean定义。

generateBeanName

获取当前bean的beanname,源码如下:

public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {  
   if (definition instanceof AnnotatedBeanDefinition) {  
      // 获取注解所指定的beanName  
  String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);  
      if (StringUtils.hasText(beanName)) {  
         // Explicit bean name found.  
  return beanName;  
      }  
   }  
   // Fallback: generate a unique default bean name.  
  return buildDefaultBeanName(definition, registry);  
}

buildDefaultBeanName构建注解我们写 beanname这个不难理解,如果没有那么就会走默认的构建,那么这里是jdk提供的,有个点需要注意下如果首字母和第二个字母都是大写那么名字直接return,如果你写的是ABtest,那么beanname就是ABtest:

public static String decapitalize(String name) {  
    if (name == null || name.length() == 0) {  
        return name;  
    }  
    if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&  
                    Character.isUpperCase(name.charAt(0))){  
        return name;  
    }  
    char chars[] = name.toCharArray();  
    chars[0] = Character.toLowerCase(chars[0]);  
    return new String(chars);  
}

postProcessBeanDefinition

这里主要是进行设置BeanDefinition的默认值,直接看源码就能看懂:

public void applyDefaults(BeanDefinitionDefaults defaults) {  
   Boolean lazyInit = defaults.getLazyInit();  
   if (lazyInit != null) {  
      setLazyInit(lazyInit);  
   }  
   setAutowireMode(defaults.getAutowireMode());  
   setDependencyCheck(defaults.getDependencyCheck());  
   setInitMethodName(defaults.getInitMethodName());  
   setEnforceInitMethod(false);  
   setDestroyMethodName(defaults.getDestroyMethodName());  
   setEnforceDestroyMethod(false);  
}

processCommonDefinitionAnnotations

这一步主要时进行解析类上的注解,看源码也可以基本看懂,没有太多绕的逻辑,如下展示:

static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd, AnnotatedTypeMetadata metadata) {  
   AnnotationAttributes lazy = attributesFor(metadata, Lazy.class);  
   if (lazy != null) {  
      abd.setLazyInit(lazy.getBoolean("value"));  
   }  
   else if (abd.getMetadata() != metadata) {  
      lazy = attributesFor(abd.getMetadata(), Lazy.class);  
      if (lazy != null) {  
         abd.setLazyInit(lazy.getBoolean("value"));  
      }  
   }  
  
   if (metadata.isAnnotated(Primary.class.getName())) {  
      abd.setPrimary(true);  
   }  
   AnnotationAttributes dependsOn = attributesFor(metadata, DependsOn.class);  
   if (dependsOn != null) {  
      abd.setDependsOn(dependsOn.getStringArray("value"));  
   }  
  
   AnnotationAttributes role = attributesFor(metadata, Role.class);  
   if (role != null) {  
      abd.setRole(role.getNumber("value").intValue());  
   }  
   AnnotationAttributes description = attributesFor(metadata, Description.class);  
   if (description != null) {  
      abd.setDescription(description.getString("value"));  
   }  
}

checkCandidate

这一步主要是检查是否我们的bean定义map注册中已经存在了,不过我们工作中基本上都会通过,如果存在多个那会抛异常:

protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException {  
   if (!this.registry.containsBeanDefinition(beanName)) {  
      return true;  
   }  
   BeanDefinition existingDef = this.registry.getBeanDefinition(beanName);  
   BeanDefinition originatingDef = existingDef.getOriginatingBeanDefinition();  
   if (originatingDef != null) {  
      existingDef = originatingDef;  
   }  
   // 是否兼容,如果兼容返回false表示不会重新注册到Spring容器中,如果不冲突则会抛异常。  
  if (isCompatible(beanDefinition, existingDef)) {  
      return false;  
   }  
   throw new ConflictingBeanDefinitionException("Annotation-specified bean name '" + beanName +  
         "' for bean class [" + beanDefinition.getBeanClassName() + "] conflicts with existing, " +  
         "non-compatible bean definition of same name and class [" + existingDef.getBeanClassName() + "]");  
}

可是你像我这样下面写的话就不会出现异常,但是工作中肯定也不会这么用,这里只做展示,AppConfig和AppConfig1都是对同样的配置,会对同一个包路径扫描两次:

AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();  
applicationContext.register(AppConfig.class);  
applicationContext.register(AppConfig1.class);  
applicationContext.refresh();  
UserService bean = applicationContext.getBean(UserService.class);  
bean.test();

registerBeanDefinition

那么最后当前的bean定义正式生成,并注册到我们之前经常说的DefaultListableBeanFactory的Map<String, BeanDefinition> beanDefinitionMap属性。

public static void registerBeanDefinition(  
      BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)  
      throws BeanDefinitionStoreException {  
  
   // Register bean definition under primary name.  
  String beanName = definitionHolder.getBeanName();  
   registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());  
  
   // Register aliases for bean name, if any.  
  String[] aliases = definitionHolder.getAliases();  
   if (aliases != null) {  
      for (String alias : aliases) {  
         registry.registerAlias(beanName, alias);  
      }  
   }  
}

合并bean定义

这个是非常重要的一个步骤,所以单独拿出来说下,这个步骤也是获取bean之前的最后一个对bean定义做修改的地方,getMergedLocalBeanDefinition(beanName);通过beanname来获取合并后的bean定义,这是什么意思呢?看下源码:

protected RootBeanDefinition getMergedBeanDefinition(  
      String beanName, BeanDefinition bd, @Nullable BeanDefinition containingBd)  
      throws BeanDefinitionStoreException {  
  
   synchronized (this.mergedBeanDefinitions) {  
      RootBeanDefinition mbd = null;  
      RootBeanDefinition previous = null;  
  
      // Check with full lock now in order to enforce the same merged instance.  
  if (containingBd == null) {  
         mbd = this.mergedBeanDefinitions.get(beanName);  
      }  
  
      if (mbd == null || mbd.stale) {  
         previous = mbd;  
         if (bd.getParentName() == null) {  
            // Use copy of given root bean definition.  
  if (bd instanceof RootBeanDefinition) {  
               mbd = ((RootBeanDefinition) bd).cloneBeanDefinition();  
            }  
            else {  
               mbd = new RootBeanDefinition(bd);  
            }  
         }  
         else {  
            // Child bean definition: needs to be merged with parent.  
 // pbd表示parentBeanDefinition  
  BeanDefinition pbd;  
            try {  
               String parentBeanName = transformedBeanName(bd.getParentName());  
               if (!beanName.equals(parentBeanName)) {  
                  pbd = getMergedBeanDefinition(parentBeanName);  
               }  
               else {  
                  BeanFactory parent = getParentBeanFactory();  
                  if (parent instanceof ConfigurableBeanFactory) {  
                     pbd = ((ConfigurableBeanFactory) parent).getMergedBeanDefinition(parentBeanName);  
                  }  
                  else {  
                     ......
                    }  
               }  
            }  
            catch (NoSuchBeanDefinitionException ex) {  
               ......
            }  
  
            // Deep copy with overridden values.  
 // 子BeanDefinition的属性覆盖父BeanDefinition的属性,这就是合并  
  mbd = new RootBeanDefinition(pbd);  
            mbd.overrideFrom(bd);  
         }  
  
         // Set default singleton scope, if not configured before.  
  if (!StringUtils.hasLength(mbd.getScope())) {  
            mbd.setScope(SCOPE_SINGLETON);  
         }  
  
  if (containingBd != null && !containingBd.isSingleton() && mbd.isSingleton()) {  
            mbd.setScope(containingBd.getScope());  
         }  
  
		 if (containingBd == null && isCacheBeanMetadata()) {  
            this.mergedBeanDefinitions.put(beanName, mbd);  
         }  
      }  
      if (previous != null) {  
         copyRelevantMergedBeanDefinitionCaches(previous, mbd);  
      }  
      return mbd;  
   }  
}

为什么需要进行合并bean定义,因为每个bean都会可能被其他声明bean所引用,因为我们在工作中用到的都是注解形式,所以很少注意,我们看下Spring是xml时代时写的声明bean:

<bean id="user1" class="com.xiaoyu.service.User" scope="prototype"/>  
  
<bean id="user2" class="com.xiaoyu.service.User" parent="user1"/>

然后我们在看下合并bean定义的源码逻辑:

  1. 判断是否有parent,没有的话就正常包装原始bean定义为RootBeanDefinition。
  2. 如果有parent,那么在判断其父类是否还有父类,如果有则递归合并bean定义方法。
  3. 最关键的其实是这个代码:先以父类的bean定义生成RootBeanDefinition,然后如果子类定义了某个属性的话那就覆盖父类的bean定义。
    mbd = new RootBeanDefinition(pbd); mbd.overrideFrom(bd);
public void overrideFrom(BeanDefinition other) {  
   if (StringUtils.hasLength(other.getBeanClassName())) {  
      setBeanClassName(other.getBeanClassName());  
   }  
   if (StringUtils.hasLength(other.getScope())) {  
      setScope(other.getScope());  
   }  
   setAbstract(other.isAbstract());  
   if (StringUtils.hasLength(other.getFactoryBeanName())) {  
      setFactoryBeanName(other.getFactoryBeanName());  
   }  
   if (StringUtils.hasLength(other.getFactoryMethodName())) {  
      setFactoryMethodName(other.getFactoryMethodName());  
   }  
   setRole(other.getRole());  
   setSource(other.getSource());  
   copyAttributesFrom(other);  
  //此处省略部分代码
   ......
}
  1. 最后将包装的bean定义放入mergedBeanDefinitions合并定义的map中。

现在我们的的Spring中 了两个bean定义Map,那么在启动时进行创建bean时用到的都是合并后的bean定义map。

结语

那么现在Spring的Bean定义环节就基本讲解完毕了。其实最主要的是Spring是如何判断是否将Bean定义加入,并生成Bean定义,以及最后如何使用合并的Bean定义来包装原始的Bean定义。下一节我们将开始讲解Spring的Bean实例化。
公众号

原文链接:https://www.cnblogs.com/guoxiaoyu/p/17365456.html

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Spring源码:bean的生命周期(一) - Python技术站

(0)
上一篇 2023年4月30日
下一篇 2023年5月1日

相关文章

  • 在Java的Spring框架的程序中使用JDBC API操作数据库

    使用JDBC API操作数据库是Java程序开发的基本技能之一,而在Spring框架中使用JDBC则是非常常见的情况。下面是在Java的Spring框架中使用JDBC API操作数据库的完整攻略。 配置JDBC数据源 在Spring中,我们需要先配置一个JDBC数据源。数据源的配置通常是在Spring的XML配置文件中完成的。如下是一个典型的JDBC数据源配…

    Java 2023年5月20日
    00
  • JSP Struts过滤xss攻击的解决办法

    JSP Struts是在Java Web开发中广泛使用的MVC框架。由于其流行,JSP Struts通常成为黑客尝试攻击Web应用程序的目标之一。大多数Web应用程序都包含处理用户输入的代码,因此,如果代码存在漏洞,则容易成为恶意用户攻击的目标,其中包括跨站脚本攻击(XSS)。 为了过滤XSS攻击,在JSP Struts开发中,可以采取以下步骤: 对用户输入…

    Java 2023年5月20日
    00
  • Java线程池复用线程的秘密你知道吗

    Java线程池复用线程的秘密你知道吗 线程池的工作原理 线程池是专门用来管理线程的,其主要作用是维护一个空闲的线程队列和一个任务队列,将任务提交到线程池后,线程池会从线程队列中取出一个空闲线程,然后将任务分配给该线程执行,任务执行完毕后该线程就会返回线程队列等待执行下一个任务,这样就能大大提升线程的复用率和运行效率。 线程复用的实现 线程池中的线程是可以复用…

    Java 2023年5月19日
    00
  • MyBatisPlus TypeHandler自定义字段类型转换Handler

    下面是”MyBatisPlus TypeHandler自定义字段类型转换Handler”的完整攻略: 什么是 MyBatisPlus TypeHandler MyBatisPlus TypeHandler是MyBatis用于处理Java的JDBC类型与数据库的JDBC类型相互转换的接口。 MyBatisPlus为我们提供了很多预定义的TypeHandler,…

    Java 2023年5月20日
    00
  • IDEA提高开发效率的7个插件(推荐)

    简介 IntelliJ IDEA是一款功能强大的Java集成开发环境,它支持多种编程语言和框架,包括Java、Kotlin、JavaScript、Python、PHP等。IDEA提供了许多插件来提高开发效率,而本文将介绍7个能够提高开发效率的插件,这些插件的安装和配置非常简单,不需要花费大量的时间来学习和使用。这些插件包括: Lombok Plugin So…

    Java 2023年5月26日
    00
  • Java实现AES算法的实例代码

    以下是Java实现AES算法的实例代码的完整攻略。 1. 什么是AES算法? AES(Advanced Encryption Standard,高级加密标准)是一种常见的对称加密算法,可用于加密和解密数据。它支持128位、192位和256位密钥长度,并被广泛应用于安全通信和数据保护领域。 2. AES算法的Java实现 Java 提供了一个官方实现的AES算…

    Java 2023年5月19日
    00
  • Java Apache POI报错“POIXMLException”的原因与解决办法

    “POIXMLException”是Java的Apache POI类库中的一个异常,通常由以下原因之一引起: 文件格式错误:如果文件格式不正确,则可能会出现此异常。例如,可能会尝试读取非Excel文件或尝试读取损坏的Excel文件。 以下是两个实例: 例1 如果文件格式错误,则可以尝试使用正确的文件格式以解决此问题。例如,在Java中,可以使用以下代码: F…

    Java 2023年5月5日
    00
  • Java虚拟机最多支持多少个线程的探讨

    Java虚拟机最多支持多少个线程的探讨 Java虚拟机(JVM)是一种能够在不同操作系统上运行Java程序的虚拟机,它的主要功能是将Java字节码转换为计算机可执行代码。在Java程序中,线程(Thread)是用来实现多任务处理的最基本单元,线程的数量对于程序执行的效率和性能有着至关重要的作用。 JVM的线程数量上限 JVM的线程并发数量并不是无限的,它受到…

    Java 2023年5月19日
    00
合作推广
合作推广
分享本页
返回顶部