概述
mybatis将与spring集成的代码拆分到了mybatis-spring模块,避免mybatis与spring之间的耦合,如果你只需要纯粹的使用mybatis api,就避免了必须将spring依赖也耦合进来的问题。mybatis使用中一般是将Sql语句写在xml文件中,为方便操作,我们会创建一个Mapper接口文件进行映射,mybatis提供了采用动态代理方式对Mapper接口类进行包装,这样我们就可以像使用普通对象一样执行各种方法调用。
mybatis和spring集成的一个核心任务就是将这些动态代理包装的Mapper对象注入到IoC容器中,这样其它Bean就可以方便的使用如@Autowired等方式进行依赖注入。
(资料图片仅供参考)
MapperScannerConfigurer
需要将mybatis生成的动态代理对象注入到IoC容器中,自然我们想到之前的BeanFactoryPostProcessor的子类BeanDefinitionRegistryPostProcessor这个扩展类。MapperScannerConfigurer就是实现了BeanDefinitionRegistryPostProcessor接口,然后在该接口中通过类扫描器scanner进行扫描注册。
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { if (this.processPropertyPlaceHolders) { processPropertyPlaceHolders(); } ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); scanner.setAddToConfig(this.addToConfig); scanner.setAnnotationClass(this.annotationClass); scanner.setMarkerInterface(this.markerInterface); scanner.setSqlSessionFactory(this.sqlSessionFactory); scanner.setSqlSessionTemplate(this.sqlSessionTemplate); scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);//指定引用的SqlSessionFactory scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName); scanner.setResourceLoader(this.applicationContext); scanner.setBeanNameGenerator(this.nameGenerator); scanner.registerFilters(); //basePackage指定扫描Mapper接口包路径 scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));}ClassPathMapperScanner这个就是继承之前介绍过的Spring中ClassPathBeanDefinitionScanner类扫描器进行了扩展,它可以实现将包路径下至少含有一个方法的接口类注册到IoC中。
这里有个问题:注册进入的BeanDefinition中beanClass指向的都是接口,到后续创建对象时会存在问题,接口是没法创建实例的。所以,ClassPathMapperScanner扫描器在注册完成后,又会对BeanDefinition进行处理。处理逻辑位于ClassPathMapperScanner#processBeanDefinitions()方法中,其核心逻辑见下:
private void processBeanDefinitions(Set beanDefinitions) { GenericBeanDefinition definition; for (BeanDefinitionHolder holder : beanDefinitions) { definition = (GenericBeanDefinition) holder.getBeanDefinition(); String beanClassName = definition.getBeanClassName(); definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59 definition.setBeanClass(this.mapperFactoryBeanClass); ... }} 其中最重要的一条语句:definition.setBeanClass(this.mapperFactoryBeanClass),偷偷的将BeanDefinition的beanClass替换成了MapperFactoryBean,而不再指向Mapper接口类。同时将Mapper接口类作为参数传入到了MapperFactoryBean中,即调用下面构造方法:
public MapperFactoryBean(Class mapperInterface) { this.mapperInterface = mapperInterface;} MapperFactoryBean实现了FactoryBean接口,这样实际上它是通过getObject()方法获取到对象然后注入到IoC容器中。而在getObject()方法中,我们就可以使用mybatis api获取到Mapper接口类的动态代理对象:SqlSession#getMapper()
public T getObject() throws Exception { return getSqlSession().getMapper(this.mapperInterface);}上面我们分析了如何将Mapper接口类注入到IoC容器中的实现思路,现在总结下主要有:
BeanDefinitionRegistryPostProcessor接口实现扩展,然后动态向IoC容器中注入Bean;在注入时,会使用到ClassPathMapperScanner类扫描器将所有的Mapper接口类解析成BeanDefinition集合注入;为了解决接口不能创建对象问题,再注入后又将BeanDefinition的beanClass替换成FactoryBean的实现类:MapperFactoryBean,在该实现类中通过mybatis api:SqlSession#getMapper()获取到Mapper接口的动态代理类扩展点引入
通过MapperScannerConfigurer,解决了如何将Mapper接口类注入到IoC容器的问题,现在还有另外一个问题,这个扩展点只有注册到Spring中才会起作用,那又如何将其注册到Spring中呢?
方式一:最直接方式就是直接创建MapperScannerConfigurer类型的Bean实例,比如:
这种方式是最简单直接的,但是使用角度来说不方便,所以,mybatis-spring-1.2新增了两种方式:标签方式和@MapperScan注解方式。
首先来看下标签方式,添加mybatis的schema,然后就可以使用:
后台处理类NamespaceHandler给标签注册解析器MapperScannerBeanDefinitionParser:
public class NamespaceHandler extends NamespaceHandlerSupport { @Override public void init() { registerBeanDefinitionParser("scan", new MapperScannerBeanDefinitionParser()); }}再看下MapperScannerBeanDefinitionParser解析器:
protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class); ClassLoader classLoader = ClassUtils.getDefaultClassLoader(); builder.addPropertyValue("processPropertyPlaceHolders", true); try { String annotationClassName = element.getAttribute(ATTRIBUTE_ANNOTATION); if (StringUtils.hasText(annotationClassName)) { @SuppressWarnings("unchecked") Class extends Annotation> annotationClass = (Class extends Annotation>) classLoader .loadClass(annotationClassName); builder.addPropertyValue("annotationClass", annotationClass); } String markerInterfaceClassName = element.getAttribute(ATTRIBUTE_MARKER_INTERFACE); if (StringUtils.hasText(markerInterfaceClassName)) { Class> markerInterface = classLoader.loadClass(markerInterfaceClassName); builder.addPropertyValue("markerInterface", markerInterface); } String nameGeneratorClassName = element.getAttribute(ATTRIBUTE_NAME_GENERATOR); if (StringUtils.hasText(nameGeneratorClassName)) { Class> nameGeneratorClass = classLoader.loadClass(nameGeneratorClassName); BeanNameGenerator nameGenerator = BeanUtils.instantiateClass(nameGeneratorClass, BeanNameGenerator.class); builder.addPropertyValue("nameGenerator", nameGenerator); } String mapperFactoryBeanClassName = element.getAttribute(ATTRIBUTE_MAPPER_FACTORY_BEAN_CLASS); if (StringUtils.hasText(mapperFactoryBeanClassName)) { @SuppressWarnings("unchecked") Class extends MapperFactoryBean> mapperFactoryBeanClass = (Class extends MapperFactoryBean>) classLoader .loadClass(mapperFactoryBeanClassName); builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass); } } catch (Exception ex) { XmlReaderContext readerContext = parserContext.getReaderContext(); readerContext.error(ex.getMessage(), readerContext.extractSource(element), ex.getCause()); } builder.addPropertyValue("sqlSessionTemplateBeanName", element.getAttribute(ATTRIBUTE_TEMPLATE_REF)); builder.addPropertyValue("sqlSessionFactoryBeanName", element.getAttribute(ATTRIBUTE_FACTORY_REF)); builder.addPropertyValue("lazyInitialization", element.getAttribute(ATTRIBUTE_LAZY_INITIALIZATION)); builder.addPropertyValue("basePackage", element.getAttribute(ATTRIBUTE_BASE_PACKAGE)); return builder.getBeanDefinition(); }最关键的就是第一句BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);,又是将MapperScannerConfigurer动态注入到Spring中,下面一堆都是解析标签属性进行依赖注入。
再来看下@MapperScan注解方式,如:@MapperScan(basePackages = "org.simon.demo01.mapper"):
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@Documented@Import(MapperScannerRegistrar.class)public @interface MapperScan {}@MapperScan注解上面使用了使用了一种非常常见的扩展方式:@Import扩展。通过@Import注解,引入了MapperScannerRegistrar,它是ImportBeanDefinitionRegistrar类型,通常和@Import注解组合使用,实现动态注入功能:
@Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { //获取注解上属性 AnnotationAttributes mapperScanAttrs = AnnotationAttributes .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName())); if (mapperScanAttrs != null) { registerBeanDefinitions(mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0)); } } void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) { //创建一个MapperScannerConfigurer的BeanDefinition BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class); /** * 下面就是解析注解属性值,通过PropertyValue方式进行依赖注入到Bean中 */ builder.addPropertyValue("processPropertyPlaceHolders", true); Class extends Annotation> annotationClass = annoAttrs.getClass("annotationClass"); if (!Annotation.class.equals(annotationClass)) { builder.addPropertyValue("annotationClass", annotationClass); } Class> markerInterface = annoAttrs.getClass("markerInterface"); if (!Class.class.equals(markerInterface)) { builder.addPropertyValue("markerInterface", markerInterface); } ...//各种依赖注入 builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages)); //将生成的BeanDefinition注册到IoC中 registry.registerBeanDefinition(beanName, builder.getBeanDefinition());}方法中同样有BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class)这句,动态的将MapperScannerConfigurer注入到Spring中,然后是一堆的解析注解属性进行依赖注入,这样通过@Import+ImportBeanDefinitionRegistrar动态注入,就实现了将MapperScannerConfigurer扩展点注册到Spring中。
SpringBoot自动装配
不论是通过标签方式,还是@MapperScan注解方式,这些是常规的第三方模块与Spring进行集成方式。这种集成方式比较繁琐的是:你不光要通过或@MapperScan注解将第三方集成进来,你还需要初始化一些依赖对象,比如这里的DataSource、SqlSessionFactory等。当一个项目集成了很多第三方模块时,每个模块都这样搞一下,配置的工作量就大了,比如最常使用的ssm集成配,传统Spring集成要搞一大堆配置。
所以,SpringBoot提出了一个比较优秀的思想:自动装配。需要什么模块直接把依赖添加进来,自动完成装配,对于个性化可以在属性文件中进行配置,从使用角度来说,即插即用,不需要有太多的编码。第三方程序和spring就像完全融入一体一样,简化项目构建时集成成本,也降低项目配置的复杂性,所以SpringBoot会被越来越多的项目所采用,进而也推动微服务的兴起。
在SpringBoot中使用mybatis,直接依赖mybatis-spring-boot-starter,它会把mybatis、mybatis-spring和mybatis-spring-boot-autoconfigure三个依赖包都添加进来。前面两个依赖包好理解,这里关键是第三个依赖包,就是通过它实现了mybatis自动装配功能。下面我们来看下SpringBoot是如何实现mybatis的主动装配。
1、首先,定义一个mybatis主动装配配置类,如下:
@org.springframework.context.annotation.Configuration@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })@ConditionalOnSingleCandidate(DataSource.class)@EnableConfigurationProperties(MybatisProperties.class)@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })public class MybatisAutoConfiguration implements InitializingBean { public MybatisAutoConfiguration(MybatisProperties properties, ObjectProvider interceptorsProvider, ObjectProvider typeHandlersProvider, ObjectProvider languageDriversProvider, ResourceLoader resourceLoader, ObjectProvider databaseIdProvider, ObjectProvider> configurationCustomizersProvider) { ... } @Bean @ConditionalOnMissingBean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { SqlSessionFactoryBean factory = new SqlSessionFactoryBean(); factory.setDataSource(dataSource); factory.setVfs(SpringBootVFS.class); if (StringUtils.hasText(this.properties.getConfigLocation())) { factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation())); } applyConfiguration(factory); ...//省略一堆配置 if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) { factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver); } return factory.getObject(); } @Bean @ConditionalOnMissingBean public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { ExecutorType executorType = this.properties.getExecutorType(); if (executorType != null) { return new SqlSessionTemplate(sqlSessionFactory, executorType); } else { return new SqlSessionTemplate(sqlSessionFactory); } } public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar { private BeanFactory beanFactory; @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { if (!AutoConfigurationPackages.has(this.beanFactory)) { logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled."); return; } logger.debug("Searching for mappers annotated with @Mapper"); List packages = AutoConfigurationPackages.get(this.beanFactory); if (logger.isDebugEnabled()) { packages.forEach(pkg -> logger.debug("Using auto-configuration base package "{}"", pkg)); } BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class); builder.addPropertyValue("processPropertyPlaceHolders", true); builder.addPropertyValue("annotationClass", Mapper.class); builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages)); BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class); Stream.of(beanWrapper.getPropertyDescriptors()) .filter(x -> x.getName().equals("lazyInitialization")).findAny() .ifPresent(x -> builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}")); registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition()); } @Override public void setBeanFactory(BeanFactory beanFactory) { this.beanFactory = beanFactory; } } @org.springframework.context.annotation.Configuration @Import(AutoConfiguredMapperScannerRegistrar.class) @ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class }) public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean { }}
这里主要利用MapperScannerRegistrarNotFoundConfiguration类上的@Import(AutoConfiguredMapperScannerRegistrar.class)引入,然后在AutoConfiguredMapperScannerRegistrar中BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class)这句又是动态注入MapperScannerConfigurer。不过,主动装配配置类中,还会把相关的依赖也一起创建、初始化,比如:SqlSessionFactory、SqlSessionTemplate。
@EnableConfigurationProperties(MybatisProperties.class)把mybatis相关配置引入进来,这样在创建、初始化过程中的定制需求就可以通过配置修改。
2、有了这个主动装配配置类还不行,下一步就是看如何让主动装配配置类生效。SpringBoot提供了SpringFactoriesLoader工厂加载机制,类似于JDK中的SPI机制,实现将模块META-INF/spring.factories文件中配置注入到Spring容器中。mybatis-spring-boot-autoconfigure模块下META-INF/spring.factories文件中就有MybatisAutoConfiguration:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration3、使用时依赖添加进来,配置下属性,就可以直接使用,基本不再需要编码:
mybatis.mapper-locations: classpath:mapper/*Mapper.xmlmybatis.type-aliases-package: com.example.demo.entity总结
从上面来看,mybatis和spring集成的关键的是将mybatis-spring模块下MapperScannerConfigurer集成进来,因为,它是一个BeanDefinitionRegistryPostProcessor类型的扩展,内部通过自定义scanner扫描Mapper接口自动注册到IoC容器中,这一点在各种集成方式中是统一一样的。不同点在于:MapperScannerConfigurer扩展类是如何被引入的。传统的Spring方式通过@Mapper注解或自定义标签实现,但是对于一些依赖对象还是需要手工创建,比较繁琐;而SpringBoot利用自动装配,让第三方模块集成变成了一个插件,即插即用,无需太多编码。
分析了mybatis集成方式,从中也学习了如何利用Spring的各种扩展点进行定制,更重要的是也为我们开发自己模块和Spring集成提供了思路。
关键词:
推荐内容
- 【Spring源码】- 08 扩展点之mybatis集成
- 鸦karas主角是谁_鸦karas豆瓣
- 俄媒:武契奇称,他对当前世界形势感到“
- 世界信息:继续开发还是狠心“割肉”新冠
- 双环传动2023年一季度预计净利1.58亿-1.7
- 3月28日18时河北张家口疫情最新情况及张
- 以技术为利刃,启辰发布DD-i超混技术
- 机构今日买入这15股,抛售中天科技3.68亿
- 螳螂捕蛇原文及翻译_螳螂捕蛇文言文翻译
- 全球视讯!单场48个进球,却有41次助攻!
- admire是什么意思呀_admire是什么意思 时快讯
- 重庆43期公租房公示名单查询
- 热点在线丨杨颖个人资料_杨颖资料
- 普尔潘工程师学院_对于普尔潘工程师学院
- 【世界播资讯】米体:国米副体育总监到马
- 神嫁小说_神嫁_当前关注
- 快看点丨每日一冷 NO.3650
- 百变之镜怎么使用_百变之镜 环球热头条
- 每日短讯:vae是什么材料_vae是什么意思
- 【新视野】美媒:苹果开始在Apple Store
- 6只股票型ETF成交量超1000万手,华夏上证
- 当日快讯:方直科技:2022年年归母净利同
- 环球焦点!刚刚,招商银行高管回应热点
- 建安区:盏盏路灯照夜路 城市亮化添光彩
- 杰伦-格林:我和小莫布里今天都发挥不错
- 隔夜的腊肠可以吃吗?|全球热闻
- 广州警方通报男研究生进女宿舍偷拍、盗窃
- 热点聚焦:汉川二中录取分数线怎么查_汉
- Chovy:我不去决赛大家可能会觉得可惜,
- 欧预赛-英格兰2-0乌克兰2连胜 凯恩破僵
- 大学生感情问题怎么解决_大学生感情问题_
- 世界最资讯丨阎良信息港房屋出售_阎良信
- 御龙在天手游大乱斗怎么打(《御龙在天》
- 财信发展控股股东协议转让14.27%公司股份
- 三年未见!直击虹桥机场国际、港澳台航线
- 狮族新风貌 东风标致 408X 预售 14.57 万元起
- 大众曝光全新入门电动车ID.1,也将入局纯
- 刘能的扮演者换谁了
- 天天速递!火爆!NBA新星抱怨吃T,拿球砸
- 手臂晒黑后迅速变白的方法_晒黑后迅速变
- 这一新规发布,5月1日起施行→ 短讯
- q点避孕套_q点和q币的区别_世界要闻
- 哈马斯是什么意思 全球观察
- nvidia geforce 930m是什么显卡_NVIDIA
- 晨昏温清_对于晨昏温清简单介绍_环球聚看
- 夕用五笔怎么打?_夕用五笔怎么打 百事通
- 金陵十三钗真正的结局
- 北京买房:理清思路,购房建议773
- 【独家】第八章:天涯海角,我都愿意
- 马克龙邀冯德莱恩一起访华,以向中国表达
- 煮豆燃豆萁豆在釜中泣的下一句_煮豆燃豆
- 建行龙卡信用卡是什么卡 今日精选
- 不抛弃不放弃歌曲原唱_不抛弃不放弃歌曲
- 公安部交通管理局部署做好清明假期交通管
- 环球关注:德力西变频器说明书e102_金立e102
- 少精症可以生育吗_精子质量标准
- 环球微头条丨我国科学家发现耐碱基因可使
- 2020年白露时间几点几分-世界今热点
- 全球观焦点:广西本科大学有哪些_广西有
- 环球消息!南苑街道“幸福餐桌”上线,让
- 6只股票型ETF成交量超1000万手,华夏上证
- 当日快讯:方直科技:2022年年归母净利同
- 环球焦点!刚刚,招商银行高管回应热点
- 建安区:盏盏路灯照夜路 城市亮化添光彩
- 杰伦-格林:我和小莫布里今天都发挥不错
- 隔夜的腊肠可以吃吗?|全球热闻
- 广州警方通报男研究生进女宿舍偷拍、盗窃
- 热点聚焦:汉川二中录取分数线怎么查_汉
- Chovy:我不去决赛大家可能会觉得可惜,
- 欧预赛-英格兰2-0乌克兰2连胜 凯恩破僵
- 大学生感情问题怎么解决_大学生感情问题_
- 世界最资讯丨阎良信息港房屋出售_阎良信
- 御龙在天手游大乱斗怎么打(《御龙在天》
- 财信发展控股股东协议转让14.27%公司股份
- 三年未见!直击虹桥机场国际、港澳台航线
- 狮族新风貌 东风标致 408X 预售 14.57 万元起
- 大众曝光全新入门电动车ID.1,也将入局纯
- 刘能的扮演者换谁了
- 天天速递!火爆!NBA新星抱怨吃T,拿球砸
- 手臂晒黑后迅速变白的方法_晒黑后迅速变
- 这一新规发布,5月1日起施行→ 短讯
- q点避孕套_q点和q币的区别_世界要闻
- 哈马斯是什么意思 全球观察
- nvidia geforce 930m是什么显卡_NVIDIA
- 晨昏温清_对于晨昏温清简单介绍_环球聚看
- 夕用五笔怎么打?_夕用五笔怎么打 百事通
- 金陵十三钗真正的结局
- 北京买房:理清思路,购房建议773
- 【独家】第八章:天涯海角,我都愿意
- 马克龙邀冯德莱恩一起访华,以向中国表达
- 煮豆燃豆萁豆在釜中泣的下一句_煮豆燃豆
- 建行龙卡信用卡是什么卡 今日精选
- 不抛弃不放弃歌曲原唱_不抛弃不放弃歌曲
- 公安部交通管理局部署做好清明假期交通管
- 环球关注:德力西变频器说明书e102_金立e102
- 少精症可以生育吗_精子质量标准
- 环球微头条丨我国科学家发现耐碱基因可使
- 2020年白露时间几点几分-世界今热点
- 全球观焦点:广西本科大学有哪些_广西有
- 环球消息!南苑街道“幸福餐桌”上线,让
- 上海世茂建设31亿元公司债票面利率3.2%保
- 蹄疾步稳势昂扬 ——榆林米脂县2022年经
- 环球视讯!中国福利彩票双色球预测_中国
- 热点聚焦:铁婚是结婚多少年
- 每日动态!迈克-布朗:在我见过的所有大个
- 五类特殊发票的开具情形及开具指南
- 巨人网络发布海外新品牌ZTimes-全球速读
- 焦点热讯:注意!普路通将于4月11日召开股
- 南北朝皇帝列表|世界微头条
- 得润电子:公司高速传输连接器将加强在电
- 【世界速看料】欧度2023春季上新工装风格
- 中牟省级非遗项目《大吕二洪拳》 骨干传
- “三新”发展壮大 “世界工厂”动能正劲
- 全球安全倡议符合国际社会共同利益
- 和讯个股快报:2023年03月23日 远东传动
- 为什么中元节晚上不能出门 环球讯息
- 越南特殊学校女生不雅视频曝光,校方介入
- 中融基金金辉:美联储通胀目标坚定,美国
- 民安华福属于哪个街道和社区_民安华福
- 时讯:双星新材股票,双星新材为高管为何
















