Spring默认以包名加类名为BeanName解决命名冲突

前言

Spring可以自动将@Component注解修饰的类加载为Bean.
比如MyController这个类, 就会加载为名称是myControllerBean.
但是, 如果在不同的模块下, 不同的包下, 有着相同文件名的类, 就会造成BeanName冲突。

1
2
3
4
5
6
7
- com.ahao.project
- moduleA
- IndexController.java
- moduleAService.java
- moduleB
- IndexController.java
- moduleBService.java

解决方案

为什么我们使用Java类的时候不会发生冲突呢?
因为import将包名导入了, 否则我们需要输入包名.类名才能使用这个类。

引用这个思路, 将BeanName也设置成包名.类名的形式。

1
2
3
4
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<context:component-scan base-package="com.ahao" name-generator="com.ahao.core.spring.bean.PackageBeanNameGenerator"/>
</beans>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class PackageBeanNameGenerator extends AnnotationBeanNameGenerator {
private static final Logger logger = LoggerFactory.getLogger(PackageBeanNameGenerator.class);

@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
String beanClassName = Introspector.decapitalize(definition.getBeanClassName());
if(StringUtils.startsWith(beanClassName, getPackageNamePrefix())) {
logger.debug("初始化Bean: " + beanClassName);
return beanClassName;
}
return super.generateBeanName(definition, registry);
}

private String getPackageNamePrefix() {
String separator = ".";
String[] splits = StringUtils.split(this.getClass().getPackage().getName(), separator);
String prefix = Arrays.stream(splits).limit(3)
.collect(Collectors.joining(separator));
return prefix;
}
}

Mybatis使用BeanNameGenerator

Spring整合发现MybatisBeanName生成没有使用BeanNameGenerator

1
2
3
4
<context:component-scan base-package="com.ahao" use-default-filters="false"
name-generator="com.ahao.core.beans.PackageBeanNameGenerator">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
</context:component-scan>
1
2
3
4
@Repository
public interface ModuleDAO {
List<String> getNames(); // 使用 ModuleMapper.xml
}

在调试时,Mybatis的代理类没有使用@Repository注解,所以include-filter没有扫描到代理类。

Stack Overflow提问也没人回。
后来自己找到了解决方法, 在配置MapperScannerConfigurer时注入NameGenerator即可。

1
2
3
4
5
6
7
8
9
10
<!-- 配置扫描Dao接口包,动态实现DAO接口,注入到spring容器 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--注入SqlSessionFactory-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!-- 给出需要扫描的Dao接口-->
<property name="basePackage" value="com.ahao.**.dao"/>
<property name="nameGenerator">
<bean class="com.ahao.core.spring.beans.PackageBeanNameGenerator"/>
</property>
</bean>

Spring Boot 基于注解的配置

Spring Boot分为两种启动方式

  • 通过main方法启动内嵌Tomcat
  • 打包war包启动外置Tomcat

直接看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 1. main方法启动
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(Application.class);
app.setBeanNameGenerator(new PackageBeanNameGenerator());
app.run(args);
logger.info("Spring Boot 启动成功!");
}
}

// 2. 外置Tomcat启动
public class AppServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(Application.class)
.beanNameGenerator(new PackageBeanNameGenerator());
}
}

同样的, Mybatis依然需要单独配置, 重点在@MapperScan注解

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
@ConditionalOnProperty(prefix = "ahao.datasource", name = "open", havingValue = "false", matchIfMissing = true)
@EnableTransactionManagement
@MapperScan(basePackages = {"com.ahao.**.dao"}, nameGenerator = PackageBeanNameGenerator.class)
public class DataSourceConfig {
// 数据源连接池配置, DruidProperties是自定义的配置类
@Bean
public DruidDataSource dataSource(DruidProperties druidProperties) {
DruidDataSource dataSource = new DruidDataSource();
druidProperties.config(dataSource);
return dataSource;
}
}

参考资料