代碼如下:
public class TestUtils { private static UserDao logDao = SpringContextHolder.getBean(UserDao.class); public static String getLog(String type){ return "!23"; } }
在controller中使用這Utils的時候出現如下錯誤:
奇怪的是在容器中又能得到UserDao,為何初始化這個TestUtils的時候就失敗了呢。
經過一番排查,發現項目啟動的時候有這么一段日志
2019-10-29 15:36:22.839 WARN 21948 --- [ main] o.mybatis.spring.SqlSessionFactoryBean : Cannot load the 'file [D:\*\utils\TestUtils.class]'. Cause by java.lang.NoClassDefFoundError: Could not initialize class com.*.utils.TestUtils
所以覺得是mybatis的問題,mybaits在掃描所有entity的時候邏輯(mybatis-spring-boot-starter是2.1.1對應的mybatis-spring的版本2.0.3)
if (hasLength(this.typeAliasesPackage)) { scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream() .filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface()) .filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias); } private Set<Class<?>> scanClasses(String packagePatterns, Class<?> assignableType) throws IOException { Set<Class<?>> classes = new HashSet<>(); String[] packagePatternArray = tokenizeToStringArray(packagePatterns, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); for (String packagePattern : packagePatternArray) {
//會先掃描出所有typeAliasesPackage下面所有的類 Resource[] resources = RESOURCE_PATTERN_RESOLVER.getResources(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + ClassUtils.convertClassNameToResourcePath(packagePattern) + "/**/*.class"); for (Resource resource : resources) { try { ClassMetadata classMetadata = METADATA_READER_FACTORY.getMetadataReader(resource).getClassMetadata();
//此時會先加載這個類,加載類的時候里面的靜態屬性會初始化,但是此時容器中還有UserDao的Bean實例,所以導致初始化失敗,觸發異常 Class<?> clazz = Resources.classForName(classMetadata.getClassName());
//此時才判斷superType if (assignableType == null || assignableType.isAssignableFrom(clazz)) { classes.add(clazz); } } catch (Throwable e) { LOGGER.warn(() -> "Cannot load the '" + resource + "'. Cause by " + e.toString()); } } } return classes; }
上面就是導致出現問題的原因。
看了一下之前的項目,邏輯一樣,但為啥沒出現問題呢,發現之前項目引用的mybatis-spring-boot-starter是1.3.2版本,對應的mybatis-spring的版本的1.3.2。
那么里面的實現是怎么樣呢?為何沒出現類似的錯誤呢?
if (hasLength(this.typeAliasesPackage)) { String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); for (String packageToScan : typeAliasPackageArray) { configuration.getTypeAliasRegistry().registerAliases(packageToScan, typeAliasesSuperType == null ? Object.class : typeAliasesSuperType); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases"); } } } public void registerAliases(String packageName, Class<?> superType){ ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>(); //會先過濾superType resolverUtil.find(new ResolverUtil.IsA(superType), packageName); Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses(); for(Class<?> type : typeSet){ // Ignore inner classes and interfaces (including package-info.java) // Skip also inner classes. See issue #6 if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) { registerAlias(type); } } }
兩個解決辦法:
1.降低mybatis-spring-boot-starter 的版本到(經測試mybatis-spring.jar的2.0.0及以下版本都可以,其他版本沒看源碼)
2.精確指定到entity的目錄(建議此,減少掃描和遍歷的次數)