Closed lucky-xin closed 3 years ago
同上
org.apache.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor#isAnnotatedReferenceBean 165行: annotatedBeanDefinition.getFactoryMethodMetadata() //这个值为null!
只需要有一个被扫描中的接口,没有定义任何方法就会抛这个异常,使用mybatis plus 100%可重现这个错误
最近升级到dubbo3时也遇到了这个问题,如下是我的环境及相关配置: 遗漏了一点忘记提了,在dubbo2.7.12版本下,所有代码都是正常工作的,只在dubbo升级到3时出现了这个问题。
dubbo-spring-boot-starter 3.0.0 mybatis-spring-boot-starter 2.2.0 spring-cloud-starter 2.3.12.RELEASE JDK 11
经过调试后,发现在同时使用mybatis的@MapperScan与dubbo的@DubboComponentScan的情况下,会导致程序运行到下面的地方,即 getFactoryMethodMetadata():
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition; import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.MethodMetadata; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.lang.Nullable; import org.springframework.util.Assert;
/**
@see AnnotatedGenericBeanDefinition */ @SuppressWarnings("serial") public class ScannedGenericBeanDefinition extends GenericBeanDefinition implements AnnotatedBeanDefinition {
private final AnnotationMetadata metadata;
/**
@Override public final AnnotationMetadata getMetadata() { return this.metadata; }
@Override @Nullable public MethodMetadata getFactoryMethodMetadata() { return null; }
}
而这里是被 org.apache.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor所调用的,调用处的代码段在162行,如下:
private boolean isAnnotatedReferenceBean(BeanDefinition beanDefinition) { if (beanDefinition instanceof AnnotatedBeanDefinition) { AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) beanDefinition; String beanClassName = annotatedBeanDefinition.getFactoryMethodMetadata().getReturnTypeName(); if (ReferenceBean.class.getName().equals(beanClassName)) { return true; } } return false; }
ScannedGenericBeanDefinition继承自AnnotatedBeanDefinition,但在spring的源代码中,这里是返回null的,但是关于这个类的介绍是这样的:
Extension of the GenericBeanDefinition class, based on an ASM ClassReader, with support for annotation metadata exposed through the AnnotatedBeanDefinition interface. This class does not load the bean Class early. It rather retrieves all relevant metadata from the ".class" file itself, parsed with the ASM ClassReader. It is functionally equivalent to AnnotatedGenericBeanDefinition.AnnotatedGenericBeanDefinition(AnnotationMetadata) but distinguishes by type beans that have been scanned vs those that have been otherwise registered or detected by other means. Since: 2.5 See Also: getMetadata(), getBeanClassName(), org.springframework.core.type.classreading.MetadataReaderFactory, AnnotatedGenericBeanDefinition Author: Juergen Hoeller, Chris Beams
这里提到和ASM有关,所以我怀疑这里有可能实际上被调用的是一个增强后的方法,但我目前还不确定。
这个问题发生的很奇怪,当你使用mybatis的@MapperScan与dubbo的@DubboComponentScan其中之一的时候,是不会出现这个问题的,所以我不清楚是不是我在这两个组件的集成过程中是不是使用了不恰当的方式。
我目前在这个问题上花费的时间在5小时左右,因为我现在的工作比较忙,回家的话需要带娃,所以还没有抽出更多的时间去排查。如果可以,我希望能在这里更快地得到一些指点,如果从这里找不到答案的话,我会在后面专门抽出时间去排查一下这个问题。
另外,我不确定是不是我集成的其他组件或代码,例如其他组件的bean注册或是SPI等机制导致了这个问题,我在下面贴出可能涉及此问题的代码: 其中可能相关的组件包括
spring-boot-starter 2.3.12.RELEASE spring-boot-starter-webflux 2.3.12.RELEASE mybatis-spring-boot-starter 2.2.0 dubbo-spring-boot-starter 3.0.0 sharding-jdbc-spring-boot-starter 4.1.1 seata-spring-boot-starter 1.4.2 spring-cloud Hoxton.SR11 pulsar-client 2.8.0
以下是相关的配置代码:
dubbo相关配置: package com.blue.finance.config.universal;
import com.blue.finance.config.deploy.DubboDeploy; import org.apache.dubbo.config.*; import org.apache.dubbo.config.spring.context.annotation.DubboComponentScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;
import static com.blue.dubbo.api.FilterBeanName.BLUE_EXCEPTION_FILTER;
/**
@author blue */ @Configuration @DubboComponentScan(basePackages = {"com.blue.finance.remote"}) public class DubboConfig {
private final DubboDeploy dubboDeploy;
public DubboConfig(DubboDeploy dubboDeploy) { this.dubboDeploy = dubboDeploy; }
@Bean ApplicationConfig applicationConfig() { ApplicationConfig applicationConfig = new ApplicationConfig(); applicationConfig.setId(dubboDeploy.getApplicationId()); applicationConfig.setName(dubboDeploy.getApplicationName()); applicationConfig.setOwner(dubboDeploy.getApplicationOwner()); applicationConfig.setOrganization(dubboDeploy.getApplicationOrganization()); return applicationConfig; }
@Bean RegistryConfig registryConfig() { RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setId(dubboDeploy.getRegistryId()); registryConfig.setProtocol(dubboDeploy.getRegistryProtocol()); registryConfig.setAddress(dubboDeploy.getRegistryAddress()); registryConfig.setFile(dubboDeploy.getRegistryFile()); registryConfig.setCheck(dubboDeploy.getRegistryCheck()); return registryConfig; }
@Bean MetadataReportConfig metadataReportConfig() { MetadataReportConfig metadataReportConfig = new MetadataReportConfig(); metadataReportConfig.setAddress(dubboDeploy.getRegistryMetadataReportAddress()); return metadataReportConfig; }
@Bean ProtocolConfig protocolConfig() { ProtocolConfig protocolConfig = new ProtocolConfig(); protocolConfig.setName(dubboDeploy.getProtocolName()); protocolConfig.setPort(dubboDeploy.getProtocolPort()); protocolConfig.setAccesslog(dubboDeploy.getProtocolAccesslog()); return protocolConfig; }
@Bean ProviderConfig providerConfig() { ProviderConfig providerConfig = new ProviderConfig(); providerConfig.setRetries(dubboDeploy.getProviderRetries()); providerConfig.setTimeout(dubboDeploy.getProviderTimeout()); providerConfig.setFilter(BLUE_EXCEPTION_FILTER.name); providerConfig.setLoadbalance(dubboDeploy.getProviderLoadbalance()); return providerConfig; }
@Bean ConsumerConfig consumerConfig() { ConsumerConfig consumerConfig = new ConsumerConfig(); consumerConfig.setRetries(dubboDeploy.getConsumerRetries()); consumerConfig.setTimeout(dubboDeploy.getConsumerTimeout()); consumerConfig.setFilter(BLUE_EXCEPTION_FILTER.name); consumerConfig.setLoadbalance(dubboDeploy.getConsumerLoadbalance()); consumerConfig.setCheck(dubboDeploy.getConsumerCheck()); return consumerConfig; }
}
数据访问相关配置: package com.blue.finance.config.universal;
import com.blue.identity.api.IdentityConf; import com.blue.base.common.pro.MathProcessor; import com.blue.base.constant.common.Symbol; import com.blue.sharding.core.DatabaseShardingAlgorithm; import com.blue.sharding.core.TableShardingAlgorithm; import com.blue.sharding.model.ShardYmlAttr; import com.blue.seata.model.UndoYmlAttr; import com.blue.finance.config.deploy.DataDeploy; import com.blue.finance.config.deploy.IdentityDeploy; import com.zaxxer.hikari.HikariDataSource; import io.seata.rm.datasource.DataSourceProxy; import org.apache.ibatis.logging.stdout.StdOutImpl; import org.apache.ibatis.session.AutoMappingBehavior; import org.apache.ibatis.session.ExecutorType; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.shardingsphere.api.config.sharding.ShardingRuleConfiguration; import org.apache.shardingsphere.api.config.sharding.TableRuleConfiguration; import org.apache.shardingsphere.api.config.sharding.strategy.StandardShardingStrategyConfiguration; import org.apache.shardingsphere.shardingjdbc.api.ShardingDataSourceFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.annotation.MapperScan; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.AdviceMode; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource; import java.io.IOException; import java.util.*; import java.util.stream.Collectors; import java.util.stream.LongStream;
import static java.util.Optional.of; import static java.util.Optional.ofNullable;
/**
@author DarkBlue */ @SuppressWarnings({"JavaDoc", "DefaultAnnotationParam"}) @Configuration @MapperScan(basePackages = "com.blue.finance.repository.mapper", sqlSessionTemplateRef = "sqlSessionTemplate") @EnableTransactionManagement(proxyTargetClass = false, mode = AdviceMode.PROXY, order = Ordered.LOWEST_PRECEDENCE) public class DataConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(DataConfig.class);
private final DataDeploy dataDeploy;
private final IdentityDeploy identityDeploy;
public DataConfig(DataDeploy dataDeploy, IdentityDeploy identityDeploy) { this.dataDeploy = dataDeploy; this.identityDeploy = identityDeploy; }
@Bean public DataSource dataSource() { try { DataConfAttr dataConfAttr = generateDataConfAttr(dataDeploy, identityDeploy); return ShardingDataSourceFactory.createDataSource(dataConfAttr.getDataSources(), dataConfAttr.getShardingRuleConfiguration(), dataConfAttr.getProps()); } catch (Exception e) { LOGGER.error("初始化数据源失败,e = ", e); throw new RuntimeException("初始化数据源失败,e = {}", e); } }
@Bean public PlatformTransactionManager txManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); }
@Bean SqlSessionFactory sqlSessionFactory(DataSource dataSource) { org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
configuration.setCacheEnabled(dataDeploy.getCacheEnabled());
configuration.setLazyLoadingEnabled(dataDeploy.getLazyLoadingEnabled());
configuration.setAggressiveLazyLoading(dataDeploy.getAggressiveLazyLoading());
configuration.setMultipleResultSetsEnabled(dataDeploy.getMultipleResultSetsEnabled());
configuration.setUseColumnLabel(dataDeploy.getUseColumnLabel());
configuration.setUseGeneratedKeys(dataDeploy.getUseGeneratedKeys());
configuration.setAutoMappingBehavior(AutoMappingBehavior.FULL);
configuration.setDefaultExecutorType(ExecutorType.BATCH);
configuration.setDefaultStatementTimeout(dataDeploy.getConnectionTimeout());
//TODO 开发阶段打印sql日志
configuration.setLogImpl(StdOutImpl.class);
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setConfiguration(configuration);
sqlSessionFactoryBean.setDataSource(dataSource);
try {
ResourcePatternResolver resourceLoader = new PathMatchingResourcePatternResolver();
sqlSessionFactoryBean.setMapperLocations(resourceLoader.getResources(dataDeploy.getMapperLocation()));
} catch (IOException e) {
throw new RuntimeException("mapper.xml文件路径异常");
}
try {
return sqlSessionFactoryBean.getObject();
} catch (Exception e) {
throw new RuntimeException("获取sqlSessionFactory异常");
}
}
@Bean SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { return new SqlSessionTemplate(sqlSessionFactory, ExecutorType.BATCH); }
//
/**
@return */ @SuppressWarnings({"AlibabaMethodTooLong", "DuplicatedCode"}) private DataConfAttr generateDataConfAttr(DataDeploy dataDeploy, IdentityDeploy identityDeploy) {
//
int shardSize; if (shards == null || (shardSize = shards.size()) < 1) { throw new RuntimeException("dataBases配置不能为空"); }
Map<String, DataSource> dataSources = new HashMap<>(shardSize);
List
Map<Long, String> indexAndDataBaseMapping = new HashMap<>(shardSize);
HikariDataSource dataSource;
String logicDataBaseName = null; String[] urlParts; String url;
String dataBaseName; String tempLogicDataBaseName; String dataBaseIndexStr; int dataBaseIndex;
for (ShardYmlAttr shardAttr : shards) {
if (shardAttr == null) {
throw new RuntimeException("shardAttr不能为空");
}
url = shardAttr.getUrl();
if ("".equals(url)) {
throw new RuntimeException("url不能为空");
}
urlParts = url.split(Symbol.PATH_SEPARATOR.identity);
if (urlParts.length != VALID_DB_URL_PARTS_LEN) {
throw new RuntimeException("url错误,url = " + url);
}
dataBaseName = urlParts[DATA_BASE_NAME_PAR];
if ("".equals(dataBaseName)) {
throw new RuntimeException("数据库名称不能为空,url = " + url);
}
String[] dataBaseNameParts = dataBaseName.split(Symbol.PAR_CONCATENATION.identity);
if (dataBaseNameParts.length != VALID_DB_NAME_PARTS_LEN) {
throw new RuntimeException("数据库名称必须由逻辑名称 + " + "_" + " + index组成,例如 member_0");
}
tempLogicDataBaseName = dataBaseNameParts[DATA_BASE_LOGIC_NAME_PAR];
if ("".equals(tempLogicDataBaseName)) {
throw new RuntimeException("数据库逻辑名称为空,数据库名称必须由逻辑名称 + " + "_" + " + index组成,例如 member_0");
}
dataBaseIndexStr = dataBaseNameParts[DATA_BASE_INDEX_PAR];
if ("".equals(dataBaseIndexStr)) {
throw new RuntimeException("数据库索引为空,数据库名称必须由逻辑名称 + " + "_" + " + index组成,例如 member_0");
}
try {
dataBaseIndex = Integer.parseInt(dataBaseIndexStr);
} catch (NumberFormatException e) {
throw new RuntimeException("数据库索引只能为数字,数据库名称必须由逻辑名称 + " + "_" + " + index组成,例如 member_0");
}
if (dataBaseIndex < 0) {
throw new RuntimeException("数据库索引不能小于0,数据库名称必须由逻辑名称 + " + "_" + " + index组成,例如 member_0");
}
assertIndexList.add(dataBaseIndex);
indexAndDataBaseMapping.put((long) dataBaseIndex, dataBaseName);
if (logicDataBaseName == null) {
logicDataBaseName = tempLogicDataBaseName;
} else {
if (!tempLogicDataBaseName.equals(logicDataBaseName)) {
throw new RuntimeException("数据库的逻辑名称/前缀必须相同, " + tempLogicDataBaseName + "/" + logicDataBaseName);
}
}
String driverClassName = shardAttr.getDriverClassName();
if (driverClassName == null || "".equals(driverClassName)) {
throw new RuntimeException("driverClassName不能为空");
}
dataSource = DataSourceBuilder.create()
.type(HikariDataSource.class)
.driverClassName(driverClassName)
.url(shardAttr.getUrl() + ofNullable(shardAttr.getDataBaseConf()).map(c -> Symbol.PAR_CONCATENATION_DATABASE_CONF.identity + c).orElse(""))
.username(shardAttr.getUsername())
.password(shardAttr.getPassword())
.build();
dataSource.setReadOnly(shardAttr.getReadOnly());
dataSource.setConnectionTimeout(shardAttr.getConnectionTimeout());
dataSource.setMaxLifetime(shardAttr.getMaxLifetime());
dataSource.setMaximumPoolSize(shardAttr.getMaximumPoolSize());
dataSource.setMinimumIdle(shardAttr.getMinimumIdle());
dataSource.setConnectionTestQuery(shardAttr.getTestQuery());
dataSources.put(dataBaseName, new DataSourceProxy(dataSource));
}
if (0 != assertIndexList.stream().min(Integer::compare).orElse(-1)) { throw new RuntimeException("数据库索引应由0开始"); } if (!MathProcessor.assertDisorderIntegerContinuous(assertIndexList)) { throw new RuntimeException("数据库索引集应为由0开始的连续数字"); } //
//
int maxDataBaseIndex = shards.size() - 1; int maxTableIndex = tableSizePerDataBase - 1;
int dataCenter = (int) identityDeploy.getDataCenter(); if (dataCenter < 0 || dataCenter > maxDataBaseIndex) { throw new RuntimeException("dataCenter不能小于0或高于maxDataBaseIndex"); } int worker = (int) identityDeploy.getWorker(); if (worker < 0 || worker > maxTableIndex) { throw new RuntimeException("worker不能小于0或高于maxTableIndex"); }
List
String shardingColumn = dataDeploy.getShardingColumn(); if (shardingColumn == null || "".equals(shardingColumn)) { throw new RuntimeException("shardingColumn配置不能为空"); }
StandardShardingStrategyConfiguration dataBaseShardingStrategyConfiguration = new StandardShardingStrategyConfiguration(shardingColumn, new DatabaseShardingAlgorithm(indexAndDataBaseMapping));
ShardingRuleConfiguration shardingRuleConfiguration = new ShardingRuleConfiguration(); String finalLogicDataBaseName = logicDataBaseName;
shardingRuleConfiguration.getTableRuleConfigs().addAll( tables.stream().distinct().map(tb -> {
String expression = finalLogicDataBaseName + "_$->{0.." + maxDataBaseIndex + "}." + tb + "_$->{0.." + maxTableIndex + "}";
TableRuleConfiguration conf = new TableRuleConfiguration(tb, expression);
conf.setDatabaseShardingStrategyConfig(dataBaseShardingStrategyConfiguration);
Map<Long, String> indexAndTableMapping = new HashMap<>(tableSizePerDataBase);
LongStream.range(0L, tableSizePerDataBase)
.forEach(index -> indexAndTableMapping.put(index, tb + Symbol.PAR_CONCATENATION.identity + index));
conf.setTableShardingStrategyConfig(new StandardShardingStrategyConfiguration(shardingColumn, new TableShardingAlgorithm(indexAndTableMapping)));
return conf;
}).collect(Collectors.toList()));
of(dataDeploy.getUndoLogs()) .filter(us -> us.size() > 0) .ifPresent(us -> shardingRuleConfiguration.getBroadcastTables().addAll( us.stream() .map(UndoYmlAttr::getTable) .filter(tb -> tb != null && !"".equals(tb)) .distinct() .collect(Collectors.toList()) ));
//
//
return new DataConfAttr(dataSources, shardingRuleConfiguration, props); }
/**
@author DarkBlue */ public static class DataConfAttr {
private final Map<String, DataSource> dataSources;
private final ShardingRuleConfiguration shardingRuleConfiguration;
private final Properties props;
public DataConfAttr(Map<String, DataSource> dataSources, ShardingRuleConfiguration shardingRuleConfiguration, Properties props) { this.dataSources = dataSources; this.shardingRuleConfiguration = shardingRuleConfiguration; this.props = props; }
public Map<String, DataSource> getDataSources() { return dataSources; }
public ShardingRuleConfiguration getShardingRuleConfiguration() { return shardingRuleConfiguration; }
public Properties getProps() { return props; }
}
}
最后,谢谢作者及所有的维护人员及帮助dubbo更好发展的人们。
Spring在org.apache.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor#postProcessBeanFactory方法中扫描所有IoC中的BeanDefinition,如果BeanDefinition为ScannedGenericBeanDefinition则都会空指针(代码没有很严谨) 我看了3.0.1-SNAPSHOT里面已经修复了
dubbo 3.0 和 mybatis 2.2.0 一起使用就会报错,dubbo单独使用OK
This error has bean fixed: https://github.com/apache/dubbo/pull/8080 We will release version 3.0.1 recently to solve this problem.
Environment