前言
接着万万没想到一个xxl-job源码分析,竟然能引发这么多血案!(上)的篇幅,继续讲解!
源码分析
我们在使用SpringBoot与Dubbo整合的时候,只需要在启动类上注解@EnableDubbo,有没有想到这是为什么?
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@EnableDubboConfig
@DubboComponentScan
public @interface EnableDubbo {
/**
* Base packages to scan for annotated @Service classes.
*
* Use {@link #scanBasePackageClasses()} for a type-safe alternative to String-based
* package names.
*
* @return the base packages to scan
* @see DubboComponentScan#basePackages()
*/
@AliasFor(annotation = DubboComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
/**
* Type-safe alternative to {@link #scanBasePackages()} for specifying the packages to
* scan for annotated @Service classes. The package of each class specified will be
* scanned.
*
* @return classes from the base packages to scan
* @see DubboComponentScan#basePackageClasses
*/
@AliasFor(annotation = DubboComponentScan.class, attribute = "basePackageClasses")
Class>[] scanBasePackageClasses() default {};
/**
* It indicates whether {@link AbstractConfig} binding to multiple Spring Beans.
*
* @return the default value is false
* @see EnableDubboConfig#multiple()
*/
@AliasFor(annotation = EnableDubboConfig.class, attribute = "multiple")
boolean multipleConfig() default false;
}
首先来看@EnableDubboConfig
注解把,import
了DubboConfigConfigurationSelector
这个类。 这个类实现了ImportSelector
接口,主要暴露出Dubbo
的基础配置。如果配置类实现了ImportAware
,可以根据AnnotationMetadata
获取配置本身所需的参数。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Import(DubboConfigConfigurationSelector.class)
public @interface EnableDubboConfig {
/**
* It indicates whether binding to multiple Spring Beans.
*
* @return the default value is false
* @revised 2.5.9
*/
boolean multiple() default false;
}
public class DubboConfigConfigurationSelector implements ImportSelector, Ordered {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
AnnotationAttributes attributes = AnnotationAttributes.fromMap(
importingClassMetadata.getAnnotationAttributes(EnableDubboConfig.class.getName()));
boolean multiple = attributes.getBoolean("multiple");
if (multiple) {
return of(DubboConfigConfiguration.Multiple.class.getName());
} else {
return of(DubboConfigConfiguration.Single.class.getName());
}
}
private static T[] of(T... values) {
return values;
}
@Override
public int getOrder() {
return HIGHEST_PRECEDENCE;
}
}
在DubboConfigConfiguration
使用@EnableDubboConfigBindings
达到类似的@ConfigurationProperties
的作用。指定前缀的配置参数和对应的实体类绑定起来。
感觉这个地方有点过度设计了,后续会详细讲到。
public class DubboConfigConfiguration {
/**
* Single Dubbo {@link AbstractConfig Config} Bean Binding
*/
@EnableDubboConfigBindings({
@EnableDubboConfigBinding(prefix = "dubbo.application", type = ApplicationConfig.class),
@EnableDubboConfigBinding(prefix = "dubbo.module", type = ModuleConfig.class),
@EnableDubboConfigBinding(prefix = "dubbo.registry", type = RegistryConfig.class),
@EnableDubboConfigBinding(prefix = "dubbo.protocol", type = ProtocolConfig.class),
@EnableDubboConfigBinding(prefix = "dubbo.monitor", type = MonitorConfig.class),
@EnableDubboConfigBinding(prefix = "dubbo.provider", type = ProviderConfig.class),
@EnableDubboConfigBinding(prefix = "dubbo.consumer", type = ConsumerConfig.class)
})
public static class Single {
}
/**
* Multiple Dubbo {@link AbstractConfig Config} Bean Binding
*/
@EnableDubboConfigBindings({
@EnableDubboConfigBinding(prefix = "dubbo.applications", type = ApplicationConfig.class, multiple = true),
@EnableDubboConfigBinding(prefix = "dubbo.modules", type = ModuleConfig.class, multiple = true),
@EnableDubboConfigBinding(prefix = "dubbo.registries", type = RegistryConfig.class, multiple = true),
@EnableDubboConfigBinding(prefix = "dubbo.protocols", type = ProtocolConfig.class, multiple = true),
@EnableDubboConfigBinding(prefix = "dubbo.monitors", type = MonitorConfig.class, multiple = true),
@EnableDubboConfigBinding(prefix = "dubbo.providers", type = ProviderConfig.class, multiple = true),
@EnableDubboConfigBinding(prefix = "dubbo.consumers", type = ConsumerConfig.class, multiple = true)
})
public static class Multiple {
}
}
@DubboComponentScan
中的DubboComponentScanRegistrar
将ServiceAnnotationBeanPostProcessor
和ReferenceAnnotationBeanPostProcessor
注册到Spring容器。前者的作用是指定扫描路径扫描服务提供者,将@Service
注解的服务实现类包装成ServiceBean
。后者的作用将@Reference
注解的服务接口,也就是引用的服务包装成ReferenceBean
。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(DubboComponentScanRegistrar.class)
public @interface DubboComponentScan {
/**
* Alias for the {@link #basePackages()} attribute. Allows for more concise annotation
* declarations e.g.: {@code @DubboComponentScan("org.my.pkg")} instead of
* {@code @DubboComponentScan(basePackages="org.my.pkg")}.
*
* @return the base packages to scan
*/
String[] value() default {};
/**
* Base packages to scan for annotated @Service classes. {@link #value()} is an
* alias for (and mutually exclusive with) this attribute.
*
* Use {@link #basePackageClasses()} for a type-safe alternative to String-based
* package names.
*
* @return the base packages to scan
*/
String[] basePackages() default {};
/**
* Type-safe alternative to {@link #basePackages()} for specifying the packages to
* scan for annotated @Service classes. The package of each class specified will be
* scanned.
*
* @return classes from the base packages to scan
*/
Class>[] basePackageClasses() default {};
}
public class DubboComponentScanRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Set packagesToScan = getPackagesToScan(importingClassMetadata);
registerServiceAnnotationBeanPostProcessor(packagesToScan, registry);
registerReferenceAnnotationBeanPostProcessor(registry);
}
/**
* Registers {@link ServiceAnnotationBeanPostProcessor}
*
* @param packagesToScan packages to scan without resolving placeholders
* @param registry {@link BeanDefinitionRegistry}
* @since 2.5.8
*/
private void registerServiceAnnotationBeanPostProcessor(Set packagesToScan, BeanDefinitionRegistry registry) {
BeanDefinitionBuilder builder = rootBeanDefinition(ServiceAnnotationBeanPostProcessor.class);
builder.addConstructorArgValue(packagesToScan);
builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
BeanDefinitionReaderUtils.registerWithGeneratedName(beanDefinition, registry);
}
/**
* Registers {@link ReferenceAnnotationBeanPostProcessor} into {@link BeanFactory}
*
* @param registry {@link BeanDefinitionRegistry}
*/
private void registerReferenceAnnotationBeanPostProcessor(BeanDefinitionRegistry registry) {
// Register @Reference Annotation Bean Processor
BeanRegistrar.registerInfrastructureBean(registry,
ReferenceAnnotationBeanPostProcessor.BEAN_NAME, ReferenceAnnotationBeanPostProcessor.class);
}
private Set getPackagesToScan(AnnotationMetadata metadata) {
AnnotationAttributes attributes = AnnotationAttributes.fromMap(
metadata.getAnnotationAttributes(DubboComponentScan.class.getName()));
String[] basePackages = attributes.getStringArray("basePackages");
Class>[] basePackageClasses = attributes.getClassArray("basePackageClasses");
String[] value = attributes.getStringArray("value");
// Appends value array attributes
Set packagesToScan = new LinkedHashSet(Arrays.asList(value));
packagesToScan.addAll(Arrays.asList(basePackages));
for (Class> basePackageClass : basePackageClasses) {
packagesToScan.add(ClassUtils.getPackageName(basePackageClass));
}
if (packagesToScan.isEmpty()) {
return Collections.singleton(ClassUtils.getPackageName(metadata.getClassName()));
}
return packagesToScan;
}
}
@EnableDubboConfigBinding
中的DubboConfigBindingRegistrar
就获取到上文中的DubboConfigConfiguration
中的信息。每一个前缀都会对应一个配置类。比如dubbo.application
前缀就会对应ApplicationConfig
配置类。DubboConfigBindingRegistrar
会将ApplicationConfig
注册到Spring
容器,beanName
就是dubbo.application.id
的值。同时会为每一个配置类分配一个DubboConfigBindingBeanPostProcessor
。每个DubboConfigBindingBeanPostProcessor
都有作用域,只会处理解析当前作用域内的配置类。其作用域就是配置类的beanName
和配置对应的prefix
。
如果multipe
是true
,就是支持多个ApplicationConfig
的配置了。每个ApplicationConfig
的beanName
就是dubbo.applications.
当前位置到在此之后第一个遇到.
的位置的字符串截取。下文中配置类们的beanName
就是atest1
,atest2
。这里值得注意一下,注册的BeanDefinition
名称不要重复哟。
为每一个ApplicationConfig
,RegistryConfig
, ProtocolConfig
都分配一个DubboConfigBindingBeanProcessor
这样的实现方式,感觉不太友好。还不如全部直接COPY
ConfigurationPropertiesBindingPostProcessor
的实现。
这里感觉一个DubboConfigBindingBeanProcessor
就足够了。内部用map
将beanName
,prefix
维护起来就行了。告知当前配置类解析哪些前缀的参数就行拉。
dubbo.applications.atest1.id=xm-dubbo-provider-test1
dubbo.applications.atest1.name=xm-dubbo-provider-test1
dubbo.applications.atest2.id=xm-dubbo-provider-test2
dubbo.applications.atest2.name=xm-dubbo-provider-test2
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(DubboConfigBindingRegistrar.class)
public @interface EnableDubboConfigBinding {
/**
* The name prefix of the properties that are valid to bind to {@link AbstractConfig Dubbo Config}.
*
* @return the name prefix of the properties to bind
*/
String prefix();
/**
* @return The binding type of {@link AbstractConfig Dubbo Config}.
* @see AbstractConfig
* @see ApplicationConfig
* @see ModuleConfig
* @see RegistryConfig
*/
Class extends AbstractConfig> type();
/**
* It indicates whether {@link #prefix()} binding to multiple Spring Beans.
*
* @return the default value is false
*/
boolean multiple() default false;
}
public class DubboConfigBindingRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware {
private final Log log = LogFactory.getLog(getClass());
private ConfigurableEnvironment environment;
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes attributes = AnnotationAttributes.fromMap(
importingClassMetadata.getAnnotationAttributes(EnableDubboConfigBinding.class.getName()));
registerBeanDefinitions(attributes, registry);
}
protected void registerBeanDefinitions(AnnotationAttributes attributes, BeanDefinitionRegistry registry) {
String prefix = environment.resolvePlaceholders(attributes.getString("prefix"));
Class extends AbstractConfig> configClass = attributes.getClass("type");
boolean multiple = attributes.getBoolean("multiple");
registerDubboConfigBeans(prefix, configClass, multiple, registry);
}
private void registerDubboConfigBeans(String prefix,
Class extends AbstractConfig> configClass,
boolean multiple,
BeanDefinitionRegistry registry) {
Map properties = getSubProperties(environment.getPropertySources(), prefix);
if (CollectionUtils.isEmpty(properties)) {
if (log.isDebugEnabled()) {
log.debug("There is no property for binding to dubbo config class [" + configClass.getName()
+ "] within prefix [" + prefix + "]");
}
return;
}
Set beanNames = multiple ? resolveMultipleBeanNames(properties) :
Collections.singleton(resolveSingleBeanName(properties, configClass, registry));
for (String beanName : beanNames) {
registerDubboConfigBean(beanName, configClass, registry);
registerDubboConfigBindingBeanPostProcessor(prefix, beanName, multiple, registry);
}
}
private void registerDubboConfigBean(String beanName, Class extends AbstractConfig> configClass,
BeanDefinitionRegistry registry) {
BeanDefinitionBuilder builder = rootBeanDefinition(configClass);
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
registry.registerBeanDefinition(beanName, beanDefinition);
if (log.isInfoEnabled()) {
log.info("The dubbo config bean definition [name : " + beanName + ", class : " + configClass.getName() +
"] has been registered.");
}
}
private void registerDubboConfigBindingBeanPostProcessor(String prefix, String beanName, boolean multiple,
BeanDefinitionRegistry registry) {
Class> processorClass = DubboConfigBindingBeanPostProcessor.class;
BeanDefinitionBuilder builder = rootBeanDefinition(processorClass);
String actualPrefix = multiple ? normalizePrefix(prefix) + beanName : prefix;
builder.addConstructorArgValue(actualPrefix).addConstructorArgValue(beanName);
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registerWithGeneratedName(beanDefinition, registry);
if (log.isInfoEnabled()) {
log.info("The BeanPostProcessor bean definition [" + processorClass.getName()
+ "] for dubbo config bean [name : " + beanName + "] has been registered.");
}
}
@Override
public void setEnvironment(Environment environment) {
Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
this.environment = (ConfigurableEnvironment) environment;
}
private Set resolveMultipleBeanNames(Map properties) {
Set beanNames = new LinkedHashSet();
for (String propertyName : properties.keySet()) {
int index = propertyName.indexOf(".");
if (index > 0) {
String beanName = propertyName.substring(0, index);
beanNames.add(beanName);
}
}
return beanNames;
}
private String resolveSingleBeanName(Map properties, Class extends AbstractConfig> configClass,
BeanDefinitionRegistry registry) {
String beanName = properties.get("id");
if (!StringUtils.hasText(beanName)) {
BeanDefinitionBuilder builder = rootBeanDefinition(configClass);
beanName = BeanDefinitionReaderUtils.generateBeanName(builder.getRawBeanDefinition(), registry);
}
return beanName;
}
}
逻辑并不复杂,配置类中参数的解析主要是依托Binder实现。下文中的dubboConfigBinder
实现是RelaxedDubboConfigBinder
。它的内部实现获取environment
中的所有PropertySources
交给binder
完成配置参数到配置类字段的绑定填充。
public class DubboConfigBindingBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware, InitializingBean {
private final Log log = LogFactory.getLog(getClass());
/**
* The prefix of Configuration Properties
*/
private final String prefix;
/**
* Binding Bean Name
*/
private final String beanName;
private DubboConfigBinder dubboConfigBinder;
private ApplicationContext applicationContext;
private boolean ignoreUnknownFields = true;
private boolean ignoreInvalidFields = true;
/**
* @param prefix the prefix of Configuration Properties
* @param beanName the binding Bean Name
*/
public DubboConfigBindingBeanPostProcessor(String prefix, String beanName) {
Assert.notNull(prefix, "The prefix of Configuration Properties must not be null");
Assert.notNull(beanName, "The name of bean must not be null");
this.prefix = prefix;
this.beanName = beanName;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (beanName.equals(this.beanName) && bean instanceof AbstractConfig) {
AbstractConfig dubboConfig = (AbstractConfig) bean;
dubboConfigBinder.bind(prefix, dubboConfig);
if (log.isInfoEnabled()) {
log.info("The properties of bean [name : " + beanName + "] have been binding by prefix of " +
"configuration properties : " + prefix);
}
}
return bean;
}
public boolean isIgnoreUnknownFields() {
return ignoreUnknownFields;
}
public void setIgnoreUnknownFields(boolean ignoreUnknownFields) {
this.ignoreUnknownFields = ignoreUnknownFields;
}
public boolean isIgnoreInvalidFields() {
return ignoreInvalidFields;
}
public void setIgnoreInvalidFields(boolean ignoreInvalidFields) {
this.ignoreInvalidFields = ignoreInvalidFields;
}
public DubboConfigBinder getDubboConfigBinder() {
return dubboConfigBinder;
}
public void setDubboConfigBinder(DubboConfigBinder dubboConfigBinder) {
this.dubboConfigBinder = dubboConfigBinder;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void afterPropertiesSet() throws Exception {
if (dubboConfigBinder == null) {
try {
dubboConfigBinder = applicationContext.getBean(DubboConfigBinder.class);
} catch (BeansException ignored) {
if (log.isDebugEnabled()) {
log.debug("DubboConfigBinder Bean can't be found in ApplicationContext.");
}
// Use Default implementation
dubboConfigBinder = createDubboConfigBinder(applicationContext.getEnvironment());
}
}
dubboConfigBinder.setIgnoreUnknownFields(ignoreUnknownFields);
dubboConfigBinder.setIgnoreInvalidFields(ignoreInvalidFields);
}
/**
* Create {@link DubboConfigBinder} instance.
*
* @param environment
* @return {@link DefaultDubboConfigBinder}
*/
protected DubboConfigBinder createDubboConfigBinder(Environment environment) {
DefaultDubboConfigBinder defaultDubboConfigBinder = new DefaultDubboConfigBinder();
defaultDubboConfigBinder.setEnvironment(environment);
return defaultDubboConfigBinder;
}
}
想不到一个小小的@EnableDubbo
注解做了这么多事情。所以说啊,要知其然知其所以然!我建议大家可以照着源码,手写一遍。
接着再看看ServiceAnnotationBeanPostProcessor
的实现。原谅我贴了这么长的代码,主要还是为了大家看的更仔细。其实逻辑很简单,通过BeanDefinitionRegistry
和DubboClassPathBeanDefinitionScanner
扫描指定basePackges
带@Service
注解的服务提供者,把他们包装成ServiceBean,其beanName格式为ServiceBean:dubboHelloServiceImpl:com.cmazxiaoma.serviceapi.IDubboHelloService:1.0.0
。‘
buildServiceBeanDefinition
方法中通过addPropertyReference
和addPropertyValues
给定义的ServiceBeanDefinition
设置属性值。
registerServiceBeans
方法中依次调用scanner.scan(packageToScan);
,findServiceBeanDefinitionHolders
方法。但是这两个方法都调用到了scanner.findCandidateComponents(packageToScan);
。两个方法的目的都是扫描出符合要求的BeanDefinition
,BeanDefinitionHolder
。
我感觉Dubbo
这样做,有点画蛇添足了。如果Dubbo纯粹的想要获取BeanDefinitionHolder
,直接在DubboClassPathBeanDefinitionScanner
中重写Set
方法。具体操作可以看上文提到的RpcScannerConfigurer
和ClassPathRpcScanner
public class ServiceAnnotationBeanPostProcessor implements BeanDefinitionRegistryPostProcessor, EnvironmentAware,
ResourceLoaderAware, BeanClassLoaderAware {
private static final String SEPARATOR = ":";
private final Logger logger = LoggerFactory.getLogger(getClass());
private final Set packagesToScan;
private Environment environment;
private ResourceLoader resourceLoader;
private ClassLoader classLoader;
public ServiceAnnotationBeanPostProcessor(String... packagesToScan) {
this(Arrays.asList(packagesToScan));
}
public ServiceAnnotationBeanPostProcessor(Collection packagesToScan) {
this(new LinkedHashSet(packagesToScan));
}
public ServiceAnnotationBeanPostProcessor(Set packagesToScan) {
this.packagesToScan = packagesToScan;
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
Set resolvedPackagesToScan = resolvePackagesToScan(packagesToScan);
if (!CollectionUtils.isEmpty(resolvedPackagesToScan)) {
registerServiceBeans(resolvedPackagesToScan, registry);
} else {
if (logger.isWarnEnabled()) {
logger.warn("packagesToScan is empty , ServiceBean registry will be ignored!");
}
}
}
/**
* Registers Beans whose classes was annotated {@link Service}
*
* @param packagesToScan The base packages to scan
* @param registry {@link BeanDefinitionRegistry}
*/
private void registerServiceBeans(Set packagesToScan, BeanDefinitionRegistry registry) {
DubboClassPathBeanDefinitionScanner scanner =
new DubboClassPathBeanDefinitionScanner(registry, environment, resourceLoader);
BeanNameGenerator beanNameGenerator = resolveBeanNameGenerator(registry);
scanner.setBeanNameGenerator(beanNameGenerator);
scanner.addIncludeFilter(new AnnotationTypeFilter(Service.class));
for (String packageToScan : packagesToScan) {
// Registers @Service Bean first
scanner.scan(packageToScan);
// Finds all BeanDefinitionHolders of @Service whether @ComponentScan scans or not.
Set beanDefinitionHolders =
findServiceBeanDefinitionHolders(scanner, packageToScan, registry, beanNameGenerator);
if (!CollectionUtils.isEmpty(beanDefinitionHolders)) {
for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
registerServiceBean(beanDefinitionHolder, registry, scanner);
}
if (logger.isInfoEnabled()) {
logger.info(beanDefinitionHolders.size() + " annotated Dubbo's @Service Components { " +
beanDefinitionHolders +
" } were scanned under package[" + packageToScan + "]");
}
} else {
if (logger.isWarnEnabled()) {
logger.warn("No Spring Bean annotating Dubbo's @Service was found under package["
+ packageToScan + "]");
}
}
}
}
/**
* It'd better to use BeanNameGenerator instance that should reference
* {@link ConfigurationClassPostProcessor#componentScanBeanNameGenerator},
* thus it maybe a potential problem on bean name generation.
*
* @param registry {@link BeanDefinitionRegistry}
* @return {@link BeanNameGenerator} instance
* @see SingletonBeanRegistry
* @see AnnotationConfigUtils#CONFIGURATION_BEAN_NAME_GENERATOR
* @see ConfigurationClassPostProcessor#processConfigBeanDefinitions
* @since 2.5.8
*/
private BeanNameGenerator resolveBeanNameGenerator(BeanDefinitionRegistry registry) {
BeanNameGenerator beanNameGenerator = null;
if (registry instanceof SingletonBeanRegistry) {
SingletonBeanRegistry singletonBeanRegistry = SingletonBeanRegistry.class.cast(registry);
beanNameGenerator = (BeanNameGenerator) singletonBeanRegistry.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);
}
if (beanNameGenerator == null) {
if (logger.isInfoEnabled()) {
logger.info("BeanNameGenerator bean can't be found in BeanFactory with name ["
+ CONFIGURATION_BEAN_NAME_GENERATOR + "]");
logger.info("BeanNameGenerator will be a instance of " +
AnnotationBeanNameGenerator.class.getName() +
" , it maybe a potential problem on bean name generation.");
}
beanNameGenerator = new AnnotationBeanNameGenerator();
}
return beanNameGenerator;
}
/**
* Finds a {@link Set} of {@link BeanDefinitionHolder BeanDefinitionHolders} whose bean type annotated
* {@link Service} Annotation.
*
* @param scanner {@link ClassPathBeanDefinitionScanner}
* @param packageToScan pachage to scan
* @param registry {@link BeanDefinitionRegistry}
* @return non-null
* @since 2.5.8
*/
private Set findServiceBeanDefinitionHolders(
ClassPathBeanDefinitionScanner scanner, String packageToScan, BeanDefinitionRegistry registry,
BeanNameGenerator beanNameGenerator) {
Set beanDefinitions = scanner.findCandidateComponents(packageToScan);
Set beanDefinitionHolders = new LinkedHashSet(beanDefinitions.size());
for (BeanDefinition beanDefinition : beanDefinitions) {
String beanName = beanNameGenerator.generateBeanName(beanDefinition, registry);
BeanDefinitionHolder beanDefinitionHolder = new BeanDefinitionHolder(beanDefinition, beanName);
beanDefinitionHolders.add(beanDefinitionHolder);
}
return beanDefinitionHolders;
}
/**
* Registers {@link ServiceBean} from new annotated {@link Service} {@link BeanDefinition}
*
* @param beanDefinitionHolder
* @param registry
* @param scanner
* @see ServiceBean
* @see BeanDefinition
*/
private void registerServiceBean(BeanDefinitionHolder beanDefinitionHolder, BeanDefinitionRegistry registry,
DubboClassPathBeanDefinitionScanner scanner) {
Class> beanClass = resolveClass(beanDefinitionHolder);
Service service = findAnnotation(beanClass, Service.class);
Class> interfaceClass = resolveServiceInterfaceClass(beanClass, service);
String annotatedServiceBeanName = beanDefinitionHolder.getBeanName();
AbstractBeanDefinition serviceBeanDefinition =
buildServiceBeanDefinition(service, interfaceClass, annotatedServiceBeanName);
// ServiceBean Bean name
String beanName = generateServiceBeanName(service, interfaceClass, annotatedServiceBeanName);
if (scanner.checkCandidate(beanName, serviceBeanDefinition)) { // check duplicated candidate bean
registry.registerBeanDefinition(beanName, serviceBeanDefinition);
if (logger.isInfoEnabled()) {
logger.warn("The BeanDefinition[" + serviceBeanDefinition +
"] of ServiceBean has been registered with name : " + beanName);
}
} else {
if (logger.isWarnEnabled()) {
logger.warn("The Duplicated BeanDefinition[" + serviceBeanDefinition +
"] of ServiceBean[ bean name : " + beanName +
"] was be found , Did @DubboComponentScan scan to same package in many times?");
}
}
}
/**
* Generates the bean name of {@link ServiceBean}
*
* @param service
* @param interfaceClass the class of interface annotated {@link Service}
* @param annotatedServiceBeanName the bean name of annotated {@link Service}
* @return ServiceBean@interfaceClassName#annotatedServiceBeanName
* @since 2.5.9
*/
private String generateServiceBeanName(Service service, Class> interfaceClass, String annotatedServiceBeanName) {
StringBuilder beanNameBuilder = new StringBuilder(ServiceBean.class.getSimpleName());
beanNameBuilder.append(SEPARATOR).append(annotatedServiceBeanName);
String interfaceClassName = interfaceClass.getName();
beanNameBuilder.append(SEPARATOR).append(interfaceClassName);
String version = service.version();
if (StringUtils.hasText(version)) {
beanNameBuilder.append(SEPARATOR).append(version);
}
String group = service.group();
if (StringUtils.hasText(group)) {
beanNameBuilder.append(SEPARATOR).append(group);
}
return beanNameBuilder.toString();
}
private Class> resolveServiceInterfaceClass(Class> annotatedServiceBeanClass, Service service) {
Class> interfaceClass = service.interfaceClass();
if (void.class.equals(interfaceClass)) {
interfaceClass = null;
String interfaceClassName = service.interfaceName();
if (StringUtils.hasText(interfaceClassName)) {
if (ClassUtils.isPresent(interfaceClassName, classLoader)) {
interfaceClass = resolveClassName(interfaceClassName, classLoader);
}
}
}
if (interfaceClass == null) {
Class>[] allInterfaces = annotatedServiceBeanClass.getInterfaces();
if (allInterfaces.length > 0) {
interfaceClass = allInterfaces[0];
}
}
Assert.notNull(interfaceClass,
"@Service interfaceClass() or interfaceName() or interface class must be present!");
Assert.isTrue(interfaceClass.isInterface(),
"The type that was annotated @Service is not an interface!");
return interfaceClass;
}
private Class> resolveClass(BeanDefinitionHolder beanDefinitionHolder) {
BeanDefinition beanDefinition = beanDefinitionHolder.getBeanDefinition();
return resolveClass(beanDefinition);
}
private Class> resolveClass(BeanDefinition beanDefinition) {
String beanClassName = beanDefinition.getBeanClassName();
return resolveClassName(beanClassName, classLoader);
}
private Set resolvePackagesToScan(Set packagesToScan) {
Set resolvedPackagesToScan = new LinkedHashSet(packagesToScan.size());
for (String packageToScan : packagesToScan) {
if (StringUtils.hasText(packageToScan)) {
String resolvedPackageToScan = environment.resolvePlaceholders(packageToScan.trim());
resolvedPackagesToScan.add(resolvedPackageToScan);
}
}
return resolvedPackagesToScan;
}
private AbstractBeanDefinition buildServiceBeanDefinition(Service service, Class> interfaceClass,
String annotatedServiceBeanName) {
BeanDefinitionBuilder builder = rootBeanDefinition(ServiceBean.class);
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();
String[] ignoreAttributeNames = of("provider", "monitor", "application", "module", "registry", "protocol", "interface");
propertyValues.addPropertyValues(new AnnotationPropertyValuesAdapter(service, environment, ignoreAttributeNames));
// References "ref" property to annotated-@Service Bean
addPropertyReference(builder, "ref", annotatedServiceBeanName);
// Set interface
builder.addPropertyValue("interface", interfaceClass.getName());
/**
* Add {@link com.alibaba.dubbo.config.ProviderConfig} Bean reference
*/
String providerConfigBeanName = service.provider();
if (StringUtils.hasText(providerConfigBeanName)) {
addPropertyReference(builder, "provider", providerConfigBeanName);
}
/**
* Add {@link com.alibaba.dubbo.config.MonitorConfig} Bean reference
*/
String monitorConfigBeanName = service.monitor();
if (StringUtils.hasText(monitorConfigBeanName)) {
addPropertyReference(builder, "monitor", monitorConfigBeanName);
}
/**
* Add {@link com.alibaba.dubbo.config.ApplicationConfig} Bean reference
*/
String applicationConfigBeanName = service.application();
if (StringUtils.hasText(applicationConfigBeanName)) {
addPropertyReference(builder, "application", applicationConfigBeanName);
}
/**
* Add {@link com.alibaba.dubbo.config.ModuleConfig} Bean reference
*/
String moduleConfigBeanName = service.module();
if (StringUtils.hasText(moduleConfigBeanName)) {
addPropertyReference(builder, "module", moduleConfigBeanName);
}
/**
* Add {@link com.alibaba.dubbo.config.RegistryConfig} Bean reference
*/
String[] registryConfigBeanNames = service.registry();
List registryRuntimeBeanReferences = toRuntimeBeanReferences(registryConfigBeanNames);
if (!registryRuntimeBeanReferences.isEmpty()) {
builder.addPropertyValue("registries", registryRuntimeBeanReferences);
}
/**
* Add {@link com.alibaba.dubbo.config.ProtocolConfig} Bean reference
*/
String[] protocolConfigBeanNames = service.protocol();
List protocolRuntimeBeanReferences = toRuntimeBeanReferences(protocolConfigBeanNames);
if (!protocolRuntimeBeanReferences.isEmpty()) {
builder.addPropertyValue("protocols", protocolRuntimeBeanReferences);
}
return builder.getBeanDefinition();
}
private ManagedList toRuntimeBeanReferences(String... beanNames) {
ManagedList runtimeBeanReferences = new ManagedList();
if (!ObjectUtils.isEmpty(beanNames)) {
for (String beanName : beanNames) {
String resolvedBeanName = environment.resolvePlaceholders(beanName);
runtimeBeanReferences.add(new RuntimeBeanReference(resolvedBeanName));
}
}
return runtimeBeanReferences;
}
private void addPropertyReference(BeanDefinitionBuilder builder, String propertyName, String beanName) {
String resolvedBeanName = environment.resolvePlaceholders(beanName);
builder.addPropertyReference(propertyName, resolvedBeanName);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
}
文章也快进行到尾声了。上文提到过xxl-job
中的XxlRpcSpringInvokerFactory
给服务提供者接口注入XxlRpcReferenceBean
引用的实现不太好。没有将服务接口和对应的引用对象建立联系并缓存起来,也没有将一个类中的所有需要注入引用的服务接口字段信息缓存起来。
我们可以看看Dubbo
是怎么实现的。具体的实现为ReferenceAnnotationBeanPostProcessor
。
以下我是结合Dubbo
中的实现和自己的业务需求仿写的实现。
入口是postProcessMergedBeanDefinition
和postProcessPropertyValues
(这2个方法就不说了,不懂的可以看通过循环引用问题来分析Spring源码)
将需要注入引用的服务提供者接口信息通过InjectionMetadata
维护起来。
我们还细分了RpcReferenceMethodElement
和RpcReferenceFieldElement
。可以通过field
和method
两种方式去给服务提供者接口设置引用。
@Component
public class RpcReferenceAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter implements
MergedBeanDefinitionPostProcessor, PriorityOrdered, ApplicationContextAware, DisposableBean, BeanClassLoaderAware {
public static final String BEAN_NAME = "rpcReferenceAnnotationBeanPostProcessor";
private ApplicationContext applicationContext;
private ClassLoader classLoader;
private final Map injectionMetadataCache =
new ConcurrentHashMap<>(256);
private final Map> referenceBeansCache =
new ConcurrentHashMap<>(256);
private static class RpcReferenceInjectionMetadata extends InjectionMetadata {
private final Collection fieldElements;
private final Collection methodElements;
public RpcReferenceInjectionMetadata(Class> targetClass,
Collection fieldElements,
Collection methodElements) {
super(targetClass, combine(fieldElements, methodElements));
this.fieldElements = fieldElements;
this.methodElements = methodElements;
}
private static Collection combine(Collection extends T>...elements) {
List allElements = new ArrayList<>();
for (Collection extends T> e : elements) {
allElements.addAll(e);
}
return allElements;
}
public Collection getFieldElements() {
return fieldElements;
}
public Collection getMethodElements() {
return methodElements;
}
}
@EqualsAndHashCode(callSuper = true)
@Getter
@Setter
private class RpcReferenceMethodElement extends InjectionMetadata.InjectedElement {
private final Method method;
private final RpcReference rpcReference;
private volatile RpcFactoryBean> rpcFactoryBean;
protected RpcReferenceMethodElement(Method method, PropertyDescriptor pd,
RpcReference rpcReference) {
super(method, pd);
this.method = method;
this.rpcReference = rpcReference;
}
@Override
protected void inject(Object target, @Nullable String requestingBeanName, @Nullable PropertyValues pvs) throws Throwable {
Class> rpcReferenceClass = pd.getPropertyType();
rpcFactoryBean = buildRpcReference(rpcReference, rpcReferenceClass);
ReflectionUtils.makeAccessible(method);
// 注入方法
method.invoke(target, rpcFactoryBean.getObject());
}
}
private RpcFactoryBean> buildRpcReference(RpcReference rpcReference, Class> rpcReferenceClass) {
String rpcReferenceBeanCacheKey = generateRpcReferenceBeanCacheKey(rpcReference, rpcReferenceClass);
RpcFactoryBean> rpcFactoryBean = referenceBeansCache.get(rpcReferenceBeanCacheKey);
if (rpcFactoryBean == null) {
// 这里是不能@Autowired rpcFactory
// 因为同阶段的BeanPostProcessor是无法享受到相同阶段以及在此之后的BeanPostProcessor的增强的
rpcFactoryBean = new RpcFactoryBean<>(rpcReferenceClass, applicationContext);
referenceBeansCache.putIfAbsent(rpcReferenceBeanCacheKey, rpcFactoryBean);
}
return rpcFactoryBean;
}
@Setter
@Getter
@EqualsAndHashCode(callSuper = true)
private class RpcReferenceFieldElement extends InjectionMetadata.InjectedElement {
private final Field field;
private final RpcReference rpcReference;
private volatile RpcFactoryBean> rpcFactoryBean;
protected RpcReferenceFieldElement(Field field, RpcReference rpcReference) {
super(field, null);
this.field = field;
this.rpcReference = rpcReference;
}
@Override
protected void inject(Object target, @Nullable String requestingBeanName, @Nullable PropertyValues pvs) throws Throwable {
Class> rpcReferenceClass = field.getType();
rpcFactoryBean = buildRpcReference(rpcReference, rpcReferenceClass);
ReflectionUtils.makeAccessible(field);
field.set(target, rpcFactoryBean.getObject());
}
}
private String generateRpcReferenceBeanCacheKey(RpcReference rpcReference, Class> rpcReferenceClass) {
// dubbo中是根据@Reference中的url/interfaceName/version/group生成cacheKey
// 这里我们就简写了
String interfaceName = resolveInterfaceName(rpcReference, rpcReferenceClass);
String key = "netty-rpc-client/" + interfaceName;
Environment environment = applicationContext.getEnvironment();
key = environment.resolvePlaceholders(key);
return key;
}
private static String resolveInterfaceName(RpcReference rpcReference, Class> rpcReferenceClass) {
String interfaceName = "default";
if (rpcReferenceClass.isInterface()) {
return rpcReferenceClass.getName();
}
return interfaceName;
}
@Override
public void destroy() throws Exception {
for (RpcFactoryBean factoryBean : referenceBeansCache.values()) {
// 做销毁factoryBean的操作
}
injectionMetadataCache.clear();
referenceBeansCache.clear();
}
@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class> beanType, String beanName) {
InjectionMetadata metadata = findRpcReferenceMetadata(beanName, beanType, null);
metadata.checkConfigMembers(beanDefinition);
}
@Override
public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
InjectionMetadata metadata = findRpcReferenceMetadata(beanName, bean.getClass(), pvs);
try {
metadata.inject(bean, beanName, pvs);
} catch (BeanCreationException ex) {
throw ex;
} catch (Throwable ex) {
throw new BeanCreationException(beanName, "Injection of @RpcReference dependencies failed", ex);
}
return pvs;
}
private List findFieldReferenceMetadata(final Class> beanClass) {
final List elements = new LinkedList<>();
ReflectionUtils.doWithFields(beanClass, new ReflectionUtils.FieldCallback() {
@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
RpcReference rpcReference = AnnotationUtils.getAnnotation(field, RpcReference.class);
if (rpcReference != null) {
if (Modifier.isStatic(field.getModifiers())) {
return;
}
elements.add(new RpcReferenceFieldElement(field, rpcReference));
}
}
});
return elements;
}
private List findMethodReferenceMeta(final Class> beanClass) {
final List elements = new LinkedList<>();
ReflectionUtils.doWithMethods(beanClass, new ReflectionUtils.MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
// 原始方法
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
// 比较桥接方法和它所桥接的方法的签名
// 这一步会过滤桥接方法的。 因为桥接方法和被桥接方法返回类型不一样。
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
return;
}
RpcReference reference = AnnotationUtils.findAnnotation(bridgedMethod, RpcReference.class);
// ClassUtils.getMostSpecificMethod(method, beanClass)
// 给定一个方法,它可能来自一个接口。
// (意思就是这个方法是来源于接口的, 但是目标类有可能重写了该接口的方法,这一步就会过滤过接口中的方法)
// 并使用一个目标类, 在当前的反射调用中,找到对应的目标方法
if (reference != null && method.equals(ClassUtils.getMostSpecificMethod(method, beanClass))) {
if (Modifier.isStatic(method.getModifiers())) {
return;
}
if (method.getParameterTypes().length == 0) {
//
}
// 原始方法的描述
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, beanClass);
// 反射还是要调用桥接方法
elements.add(new RpcReferenceMethodElement(method, pd, reference));
}
}
});
return elements;
}
private RpcReferenceInjectionMetadata buildRpcReferenceMetadata(final Class> beanClass) {
Collection fieldElements = findFieldReferenceMetadata(beanClass);
Collection methodElements = findMethodReferenceMeta(beanClass);
return new RpcReferenceInjectionMetadata(beanClass, fieldElements, methodElements);
}
private InjectionMetadata findRpcReferenceMetadata(String beanName, Class> clazz,
PropertyValues propertyValues) {
String cacheKey = StringUtils.hasLength(beanName) ? beanName : clazz.getName();
RpcReferenceInjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
if (InjectionMetadata.needsRefresh(metadata, clazz)) {
synchronized (this.injectionMetadataCache) {
metadata = this.injectionMetadataCache.get(cacheKey);
if (InjectionMetadata.needsRefresh(metadata, clazz)) {
if (metadata != null) {
metadata.clear(propertyValues);
}
try {
metadata = buildRpcReferenceMetadata(clazz);
this.injectionMetadataCache.put(cacheKey, metadata);
} catch (NoClassDefFoundError error) {
throw new IllegalStateException(new StringBuilder()
.append("Fail to introspect bean class")
.append("[")
.append(clazz.getName())
.append("]")
.append("for RpcReference metadata:")
.append("could not find class that it depends on").toString(), error);
}
}
}
}
return metadata;
}
public Map> getInjectedFieldReferenceBeanMap() {
Map> injectedElementReferenceBeanMap =
new LinkedHashMap<>();
for (RpcReferenceInjectionMetadata metadata : injectionMetadataCache.values()) {
Collection fieldElements = metadata.getFieldElements();
for (RpcReferenceFieldElement fieldElement: fieldElements) {
injectedElementReferenceBeanMap.put(fieldElement, fieldElement.getRpcFactoryBean());
}
}
return injectedElementReferenceBeanMap;
}
public Map> getInjectedMethodReferenceBeanMap() {
Map> injectedElementRpcFactoryBeanMap =
new LinkedHashMap<>();
for (RpcReferenceInjectionMetadata metadata : injectionMetadataCache.values()) {
Collection methodElements = metadata.getMethodElements();
for (RpcReferenceMethodElement methodElement : methodElements) {
injectedElementRpcFactoryBeanMap.put(methodElement, methodElement.getRpcFactoryBean());
}
}
return injectedElementRpcFactoryBeanMap;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public int getOrder() {
return LOWEST_PRECEDENCE;
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
}
彩蛋
上文写到了ReferenceAnnotationBeanPostProcessor
,看到InjectionMetadata.InjectedElement
这个类,脑海里面蹦出很多知识点。我这里就轮询选择一个把。
在你们项目中使用@Autowired有时候因为一些原因注入不了属性,
会抛出expected at least 1 bean which qualifies as autowire candidate
信息
这些信息是在DefaultListableBeanFactory
中raiseNoMatchingBeanFound
方法抛出的。
private void raiseNoMatchingBeanFound(
Class> type, ResolvableType resolvableType, DependencyDescriptor descriptor) throws BeansException {
checkBeanNotOfRequiredType(type, descriptor);
throw new NoSuchBeanDefinitionException(resolvableType,
"expected at least 1 bean which qualifies as autowire candidate. " +
"Dependency annotations: " + ObjectUtils.nullSafeToString(descriptor.getAnnotations()));
}
@Autowired
被注解的字段,最终会被AutowiredAnnotationBeanPostProcessor
包装成AutowiredFieldElement
信息。
对于Spring
容器加载流程不熟悉的,墙裂推荐我这一篇文章通过循环引用问题来分析Spring源码
在依赖注入的时候 最终会通过DefaultListableBeanFactory.doResolveDependency
方法来完成。
这个方法内部里面比较重要的函数
findAutowireCandidates(beanName, type, descriptor);
根据requireType找到匹配的类,首先会从resolvableDependencies
寻找。
descriptor.resolveCandidate(autowiredBeanName, type, this);
最终会调用beanFactory.getBean(beanName)`
@Nullable
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
@Nullable Set autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
try {
Object shortcut = descriptor.resolveShortcut(this);
if (shortcut != null) {
return shortcut;
}
Class> type = descriptor.getDependencyType();
Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
if (value != null) {
if (value instanceof String) {
String strVal = resolveEmbeddedValue((String) value);
BeanDefinition bd = (beanName != null && containsBean(beanName) ? getMergedBeanDefinition(beanName) : null);
value = evaluateBeanDefinitionString(strVal, bd);
}
TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
return (descriptor.getField() != null ?
converter.convertIfNecessary(value, type, descriptor.getField()) :
converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));
}
Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
if (multipleBeans != null) {
return multipleBeans;
}
Map matchingBeans = findAutowireCandidates(beanName, type, descriptor);
if (matchingBeans.isEmpty()) {
if (isRequired(descriptor)) {
raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
}
return null;
}
String autowiredBeanName;
Object instanceCandidate;
if (matchingBeans.size() > 1) {
autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
if (autowiredBeanName == null) {
if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {
return descriptor.resolveNotUnique(type, matchingBeans);
}
else {
// In case of an optional Collection/Map, silently ignore a non-unique case:
// possibly it was meant to be an empty collection of multiple regular beans
// (before 4.3 in particular when we didn't even look for collection beans).
return null;
}
}
instanceCandidate = matchingBeans.get(autowiredBeanName);
}
else {
// We have exactly one match.
Map.Entry entry = matchingBeans.entrySet().iterator().next();
autowiredBeanName = entry.getKey();
instanceCandidate = entry.getValue();
}
if (autowiredBeanNames != null) {
autowiredBeanNames.add(autowiredBeanName);
}
if (instanceCandidate instanceof Class) {
instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
}
Object result = instanceCandidate;
if (result instanceof NullBean) {
if (isRequired(descriptor)) {
raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
}
result = null;
}
if (!ClassUtils.isAssignableValue(type, result)) {
throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass());
}
return result;
}
finally {
ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);
}
}
如果Spring
容器返回为空,在根据require
此参数判断是否要抛出NoSuchBeanDefinitionException
异常信息
if (instanceCandidate instanceof Class) {
instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
}
Object result = instanceCandidate;
if (result instanceof NullBean) {
if (isRequired(descriptor)) {
raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
}
result = null;
}
尾言
万万没想到,一个知识点竟然能引发这么多血案!
我的博客即将同步至 OSCHINA 社区,这是我的 OSCHINA ID:cmazxiaoma,邀请大家一同入驻:https://www.oschina.net/sharing-plan/apply