diff --git a/README.md b/README.md index 11df1f3..01ac917 100644 --- a/README.md +++ b/README.md @@ -172,6 +172,16 @@ AQS剩余部分,以及阻塞队列源码暂时先搁置一下。 [5.设计模式-行为型-状态&迭代器](docs/design_patterns/behavior_type/设计模式-05.03-行为型-状态&迭代器.md) + + + + +# Spring + +## 用法 + +[Spring常用注解](docs/spring/use/Spring常用注解.md) + # Netty ## 入门 @@ -188,11 +198,13 @@ AQS剩余部分,以及阻塞队列源码暂时先搁置一下。 ## Dubbo源码 -[Dubbo基本应用与高级应用介绍](docs/rpc/dubbo/Dubbo源码系列V1-01和02.Dubbo第一二节-基本应用与高级应用.md) +1. [Dubbo基本应用与高级应用介绍](docs/rpc/dubbo/01&02.Dubbo源码系列V1-Dubbo第一二节-基本应用与高级应用.md) -[Dubbo可扩展机制SPI源码解析](docs/rpc/dubbo/Dubbo源码系列V1-03.Dubbo第三节-可扩展机制SPI源码解析.md) +2. [Dubbo可扩展机制SPI源码解析](docs/rpc/dubbo/03.Dubbo源码系列V1-Dubbo第三节-可扩展机制SPI源码解析.md) +3. [Spring与Dubbo整合原理与源码分析](docs/rpc/dubbo/04.Dubbo源码系列V1-Dubbo第四节-Spring与Dubbo整合原理与源码分析.md) +4. [Dubbo服务导出源码解析](docs/rpc/dubbo/05.Dubbo源码系列V1-Dubbo第五节-服务导出源码解析.md) diff --git a/docs/rpc/dubbo/Dubbo源码系列V1-01和02.Dubbo第一二节-基本应用与高级应用.md b/docs/rpc/dubbo/01&02.Dubbo源码系列V1-Dubbo第一二节-基本应用与高级应用.md similarity index 99% rename from docs/rpc/dubbo/Dubbo源码系列V1-01和02.Dubbo第一二节-基本应用与高级应用.md rename to docs/rpc/dubbo/01&02.Dubbo源码系列V1-Dubbo第一二节-基本应用与高级应用.md index 9c25b9a..8c0ef37 100644 --- a/docs/rpc/dubbo/Dubbo源码系列V1-01和02.Dubbo第一二节-基本应用与高级应用.md +++ b/docs/rpc/dubbo/01&02.Dubbo源码系列V1-Dubbo第一二节-基本应用与高级应用.md @@ -1,5 +1,5 @@ --- -title: Dubbo源码系列V1-01和02.Dubbo第一二节-基本应用与高级应用 +title: 01&02.Dubbo源码系列V1-Dubbo第一二节-基本应用与高级应用 tags: - Dubbo - rpc diff --git a/docs/rpc/dubbo/Dubbo源码系列V1-03.Dubbo第三节-可扩展机制SPI源码解析.md b/docs/rpc/dubbo/03.Dubbo源码系列V1-Dubbo第三节-可扩展机制SPI源码解析.md similarity index 99% rename from docs/rpc/dubbo/Dubbo源码系列V1-03.Dubbo第三节-可扩展机制SPI源码解析.md rename to docs/rpc/dubbo/03.Dubbo源码系列V1-Dubbo第三节-可扩展机制SPI源码解析.md index 880276d..ce96430 100644 --- a/docs/rpc/dubbo/Dubbo源码系列V1-03.Dubbo第三节-可扩展机制SPI源码解析.md +++ b/docs/rpc/dubbo/03.Dubbo源码系列V1-Dubbo第三节-可扩展机制SPI源码解析.md @@ -1,5 +1,5 @@ --- -title: Dubbo源码系列V1-03.Dubbo第三节-可扩展机制SPI源码解析 +title: 03.Dubbo源码系列V1-Dubbo第三节-可扩展机制SPI源码解析 tags: - Dubbo - rpc diff --git a/docs/rpc/dubbo/04.Dubbo源码系列V1-Dubbo第四节-Spring与Dubbo整合原理与源码分析.md b/docs/rpc/dubbo/04.Dubbo源码系列V1-Dubbo第四节-Spring与Dubbo整合原理与源码分析.md new file mode 100644 index 0000000..2b6b0d6 --- /dev/null +++ b/docs/rpc/dubbo/04.Dubbo源码系列V1-Dubbo第四节-Spring与Dubbo整合原理与源码分析.md @@ -0,0 +1,1878 @@ +--- +title: 04.Dubbo源码系列V1-Dubbo第四节-Spring与Dubbo整合原理与源码分析 +tags: + - Dubbo + - rpc +categories: + - rpc + - Dubbo源码系列v1 +keywords: Dubbo,rpc +description: Spring与Dubbo整合原理与源码分析 +cover: 'https://cdn.jsdelivr.net/gh/youthlql/youthlql/img/dubbo.png' +abbrlink: 796f395d +date: 2021-10-06 13:21:58 +--- + + + +## 第四节: Spring与Dubbo整合原理与源码分析 + + + + +#### 处理@Service + +1. Dubbo的@Service注解Spring的@Service注解重名了,dubbo在2.7版本之后改成了@DubboService注解。 + +> 在Dubbo的文章中如果不是特别说明@Service注解均为Dubbo的注解 + +2. Dubbo在处理@Service注解时会生成两个对象,看上面的图,DemoServiceImpl这个是给Spring容器生成的,意思就是@Service注解兼具了Spring@Service注解的功能。同时@Serivce注解会再生成一个ServiceBean类型的对象,这个对象会做一些事情,比如:将对应的服务类注册到注册中心,将service服务进行分组,分版本,控制超时,权重等等。@Service注解上面写的参数,都是由ServiceBean类型的对象来承接。当调用ServiceBean类型里面的export方法就可以控制服务的注册。 +3. ServiceBean的父类里有一个ref属性,指向这个服务的实现类 + +#### 处理Properties文件 + +```properties +# 这个会被解析成ApplicationConfig对象 +dubbo.application.name=dubbo-demo-provider1-application +dubbo.application.logger=log4j +dubbo.application.timeout=3000 + + +# 这个会被解析成ProtocolConfig +dubbo.protocols.p1.name=dubbo +dubbo.protocols.p1.port=20880 +dubbo.protocols.p1.host=0.0.0.0 + +dubbo.protocols.p2.name=dubbo +dubbo.protocols.p2.port=20881 +dubbo.protocols.p2.host=0.0.0.0 + + +# 这个会被解析成RegistrieConfig +dubbo.registries.r1.address=zookeeper://127.0.0.1:2181 +dubbo.registries.r1.timeout=3000 + +# 等等 +``` + +这些xxxConfig最后都会赋值给ServiceBean里相应的属性 + + + + + +### 实例解析 + +应用启动类与配置 + +```java +public class Application { + public static void main(String[] args) throws Exception { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProviderConfiguration.class); + context.start(); + System.in.read(); + } + + @Configuration + @EnableDubbo(scanBasePackages = "org.apache.dubbo.demo.provider") + @PropertySource("classpath:/spring/dubbo-provider.properties") + static class ProviderConfiguration { + + } +} +``` + + + + + +应用配置类为ProviderConfiguration, 在配置上有两个比较重要的注解 + +1. @PropertySource表示将dubbo-provider.properties中的配置项添加到Spring容器中,可以通过@Value的方式获取到配置项中的值 +2. @EnableDubbo(scanBasePackages = "org.apache.dubbo.demo.provider")表示对指定包下的类进行扫描,扫描@Service与@Reference注解,并且进行处理 + +### @EnableDubbo + +在EnableDubbo注解上,有另外两个注解,也是研究Dubbo最重要的两个注解 + +1. @EnableDubboConfig +2. @DubboComponentScan + +```java +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@Import(DubboConfigConfigurationRegistrar.class) +public @interface EnableDubboConfig { + boolean multiple() default true; +} +//解析properties文件 ===> xxxConfig +``` + + + +```java +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Import(DubboComponentScanRegistrar.class) +public @interface DubboComponentScan { + String[] value() default {}; + + String[] basePackages() default {}; + + Class[] basePackageClasses() default {}; + +} +//解析@Service @Refrence注解 +``` + + + +注意两个注解中对应的@Import注解所导入的类: + +1. DubboConfigConfigurationRegistrar +2. DubboComponentScanRegistrar + +Spring在启动时会解析这两个注解,并且执行对应的Registrar类中的registerBeanDefinitions方法(这是Spring中提供的扩展功能。) + + + +### DubboConfigConfigurationRegistrar + +```java +public class DubboConfigConfigurationRegistrar implements ImportBeanDefinitionRegistrar { + + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { + System.out.println("执行DubboConfigConfigurationRegistrar"); + + + AnnotationAttributes attributes = AnnotationAttributes.fromMap( + importingClassMetadata.getAnnotationAttributes(EnableDubboConfig.class.getName())); + + boolean multiple = attributes.getBoolean("multiple"); // 拿EnableDubboConfig里的默认值 + + // Single Config Bindings + registerBeans(registry, DubboConfigConfiguration.Single.class); + + // 默认为true + if (multiple) { // Since 2.6.6 https://github.com/apache/dubbo/issues/3193 + registerBeans(registry, DubboConfigConfiguration.Multiple.class); + } + } + +} +``` + + + +#### 流程 + + + + + + +Spring启动时,会调用DubboConfigConfigurationRegistrar的registerBeanDefinitions方法,该方法是利用Spring中的AnnotatedBeanDefinitionReader来读取: + +1. DubboConfigConfiguration.Single.**class** +2. DubboConfigConfiguration.Multiple.**class** + +这两个类上的注解。 + +```java +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), + @EnableDubboConfigBinding(prefix = "dubbo.config-center", type = ConfigCenterBean.class), + @EnableDubboConfigBinding(prefix = "dubbo.metadata-report", type = MetadataReportConfig.class), + @EnableDubboConfigBinding(prefix = "dubbo.metrics", type = MetricsConfig.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), + @EnableDubboConfigBinding(prefix = "dubbo.config-centers", type = ConfigCenterBean.class, multiple = true), + @EnableDubboConfigBinding(prefix = "dubbo.metadata-reports", type = MetadataReportConfig.class, multiple = true), + @EnableDubboConfigBinding(prefix = "dubbo.metricses", type = MetricsConfig.class, multiple = true) + }) + public static class Multiple { + + } +} +``` + +大概意思就是什么前缀的注解,对应解析到哪个类的对象里。 + + + + + +这两个类主要用到的就是@EnableDubboConfigBindings注解: + +```java +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Import(DubboConfigBindingsRegistrar.class) +public @interface EnableDubboConfigBindings { + + /** + * The value of {@link EnableDubboConfigBindings} + * + * @return non-null + */ + EnableDubboConfigBinding[] value(); + +} +``` + + + +@EnableDubboConfigBindings注解上也有一个@Import注解,导入的是DubboConfigBindingsRegistrar.**class**。该类会获取@EnableDubboConfigBindings注解中的value,也就是多个@EnableDubboConfigBinding注解,然后利用DubboConfigBindingRegistrar去处理这些@EnableDubboConfigBinding注解。 + + + + + +#### DubboConfigBindingRegistrar + +##### 此类总结 + +此类中的主要方法是registerDubboConfigBeans()方法,主要功能就是获取用户所设置的properties文件中的内容,对Properties文件进行解析,根据Properties文件的每个配置项的前缀、参数名、参数值生成对应的BeanDefinition。 + +比如: + +```java +dubbo.application.name=dubbo-demo-provider1-application +dubbo.application.logger=log4j +``` + +前缀为"dubbo.application"的配置项,会生成一个ApplicationConfig类型的BeanDefinition,并且name和logger属性为对应的值。 + + + +再比如: + + ```java + dubbo.protocols.p1.name=dubbo + dubbo.protocols.p1.port=20880 + dubbo.protocols.p1.host=0.0.0.0 + + dubbo.protocols.p2.name=dubbo + dubbo.protocols.p2.port=20881 + dubbo.protocols.p2.host=0.0.0.0 + ``` + +比如前缀为"dubbo.protocols"的配置项,会生成**两**个ProtocolConfig类型的BeanDefinition,两个BeanDefinition的beanName分别为p1和p2。 + +并且还会针对生成的每个BeanDefinition生成一个和它一对一绑定的BeanPostProcessor,类型为DubboConfigBindingBeanPostProcessor.**class**。 + + + +##### 代码注释 + +> 整个类的代码注释 + +```java + +/** + * {@link AbstractConfig Dubbo Config} binding Bean registrar + * + * @see EnableDubboConfigBinding + * @see DubboConfigBindingBeanPostProcessor + * @since 2.5.8 + */ +public class DubboConfigBindingRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware { + + private final Log log = LogFactory.getLog(getClass()); + + private ConfigurableEnvironment environment; + + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { + + System.out.println("执行DubboConfigBindingRegistrar"); + + AnnotationAttributes attributes = AnnotationAttributes.fromMap( + importingClassMetadata.getAnnotationAttributes(EnableDubboConfigBinding.class.getName())); + + registerBeanDefinitions(attributes, registry); + + } + + protected void registerBeanDefinitions(AnnotationAttributes attributes, BeanDefinitionRegistry registry) { + + // prefix = "dubbo.application" + String prefix = environment.resolvePlaceholders(attributes.getString("prefix")); + + // type = ApplicationConfig.class + Class configClass = attributes.getClass("type"); + + boolean multiple = attributes.getBoolean("multiple"); + + registerDubboConfigBeans(prefix, configClass, multiple, registry); + + } + + private void registerDubboConfigBeans(String prefix, + Class configClass, + boolean multiple, + BeanDefinitionRegistry registry) { + + // 从properties文件中根据前缀拿对应的配置项,比如根据dubbo.application前缀, + // 就可以拿到: + // dubbo.application.name=dubbo-demo-provider-application + // dubbo.application.logger=log4j + Map properties = getSubProperties(environment.getPropertySources(), prefix); + + // 如果没有相关的配置项,则不需要注册BeanDefinition + 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; + } + + // 根据配置项生成beanNames,为什么会有多个? + // 普通情况一个dubbo.application前缀对应一个ApplicationConfig类型的Bean + // 特殊情况下,比如dubbo.protocols对应了: +// dubbo.protocols.p1.name=dubbo +// dubbo.protocols.p1.port=20880 +// dubbo.protocols.p1.host=0.0.0.0 + +// dubbo.protocols.p2.name=http +// dubbo.protocols.p2.port=8082 +// dubbo.protocols.p2.host=0.0.0.0 + // 那么就需要对应两个ProtocolConfig类型的Bean,那么就需要两个beanName:p1和p2 + + // 这里就是multiple为true或false的区别,名字的区别,根据multiple用来判断是否从配置项中获取beanName + // 如果multiple为false,则看有没有配置id属性,如果没有配置则自动生成一个beanName. + Set beanNames = multiple ? resolveMultipleBeanNames(properties) : + Collections.singleton(resolveSingleBeanName(properties, configClass, registry)); + + for (String beanName : beanNames) { + + // 为每个beanName,注册一个空的BeanDefinition + registerDubboConfigBean(beanName, configClass, registry); + + // 为每个bean注册一个DubboConfigBindingBeanPostProcessor的Bean后置处理器 + registerDubboConfigBindingBeanPostProcessor(prefix, beanName, multiple, registry); + + } + + // 注册一个NamePropertyDefaultValueDubboConfigBeanCustomizer的bean + // 用来把某个XxConfig所对应的beanName设置到name属性中去 + registerDubboConfigBeanCustomizers(registry); + + } + + private void registerDubboConfigBean(String beanName, Class configClass, + BeanDefinitionRegistry registry) { + + BeanDefinitionBuilder builder = rootBeanDefinition(configClass); + + AbstractBeanDefinition beanDefinition = builder.getBeanDefinition(); + + registry.registerBeanDefinition(beanName, beanDefinition); // ApplicatinoConfig对象 + + 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) { + + // 注册一个DubboConfigBindingBeanPostProcessor的Bean + // 每个XxConfig的Bean对应一个DubboConfigBindingBeanPostProcessor的Bean + // 比如,一个ApplicationConfig对应一个DubboConfigBindingBeanPostProcessor, + // 一个ProtocolConfig也会对应一个DubboConfigBindingBeanPostProcessor + // 在构造DubboConfigBindingBeanPostProcessor的时候会指定构造方法的值,这样就可以区别开来了 + + Class processorClass = DubboConfigBindingBeanPostProcessor.class; + + BeanDefinitionBuilder builder = rootBeanDefinition(processorClass); + + // 真实的前缀,比如dubbo.registries.r2 + String actualPrefix = multiple ? normalizePrefix(prefix) + beanName : prefix; + + // 添加两个构造方法参数值,所以会调用DubboConfigBindingBeanPostProcessor的两个参数的构造方法 + 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."); + } + + } + + private void registerDubboConfigBeanCustomizers(BeanDefinitionRegistry registry) { + registerInfrastructureBean(registry, BEAN_NAME, NamePropertyDefaultValueDubboConfigBeanCustomizer.class); + } + + @Override + public void setEnvironment(Environment environment) { + + Assert.isInstanceOf(ConfigurableEnvironment.class, environment); + + this.environment = (ConfigurableEnvironment) environment; + + } + + private Set resolveMultipleBeanNames(Map properties) { + + Set beanNames = new LinkedHashSet(); + + // 比如dubbo.protocols.p1.name=dubbo的propertyName为p1.name + + for (String propertyName : properties.keySet()) { + + // propertyName为p1.name + int index = propertyName.indexOf("."); + + if (index > 0) { + + // 截取beanName名字为p1 + String beanName = propertyName.substring(0, index); + + beanNames.add(beanName); + } + + } + + return beanNames; + + } + + private String resolveSingleBeanName(Map properties, Class configClass, + BeanDefinitionRegistry registry) { + + // 配置了dubbo.application.id=appl,那么appl就是beanName + String beanName = (String) properties.get("id"); + // 如果beanName为null,则会进入if分支,由spring自动生成一个beanName,比如org.apache.dubbo.config.ApplicationConfig#0 + if (!StringUtils.hasText(beanName)) { + BeanDefinitionBuilder builder = rootBeanDefinition(configClass); + beanName = BeanDefinitionReaderUtils.generateBeanName(builder.getRawBeanDefinition(), registry); + } + + return beanName; + + } + +} +``` + +#### DubboConfigBindingBeanPostProcessor + +DubboConfigBindingBeanPostProcessor是一个BeanPostProcessor,在Spring启动过程中,会针对所有的Bean对象进行后置加工,但是在DubboConfigBindingBeanPostProcessor中有如下判断: + +```java +if (this.beanName.equals(beanName) && bean instanceof AbstractConfig) +``` + +所以DubboConfigBindingBeanPostProcessor并不会处理Spring容器中的所有Bean,它只会处理上文由Dubbo所生成的Bean对象。 + +并且,在afterPropertiesSet()方法中,会先创建一个DefaultDubboConfigBinder。 + + + +##### 代码注释 + +> 只留了关键性的代码注释,其余省略 + +```java +/** + * Dubbo Config Binding {@link BeanPostProcessor} + * + * @see EnableDubboConfigBinding + * @see DubboConfigBindingRegistrar + * @since 2.5.8 + */ + +public class DubboConfigBindingBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware, InitializingBean + , BeanDefinitionRegistryPostProcessor { + + 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 BeanDefinitionRegistry beanDefinitionRegistry; + + private boolean ignoreUnknownFields = true; + + private boolean ignoreInvalidFields = true; + + private List configBeanCustomizers = Collections.emptyList(); + + /** + * @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 { + + + /* + 1.每个XxConfig对应一个BeanPostProcessor,所以每个DubboConfigBindingBeanPostProcessor只处理对应的beanName + 2.阿里这里的代码写的很不好,没必要为每一个Bean都生成一个BeanPostProcessor,多余的加这个if判断,每一个Bean + 都会经过Dubbo生成的BeanPostProcessor,不停的if判断,直到找到自己的BeanPostProcessor。还好Dubbo后面的版本 + 把这里改了。 + 3.看过Spring源码的应该知道Spring的BeanPostProcessor是所有对象公用的,这种处理就比较好. + */ + if (this.beanName.equals(beanName) && bean instanceof AbstractConfig) { + + AbstractConfig dubboConfig = (AbstractConfig) bean; + // 从properties文件中获取值,并设置到dubboConfig对象中 + bind(prefix, dubboConfig); + + // 设置dubboConfig对象的name属性,设置为beanName + customize(beanName, dubboConfig); + + } + + return bean; + + } + + private void bind(String prefix, AbstractConfig dubboConfig) { + + dubboConfigBinder.bind(prefix, dubboConfig); + + if (log.isInfoEnabled()) { + log.info("The properties of bean [name : " + beanName + "] have been binding by prefix of " + + "configuration properties : " + prefix); + } + } + + private void customize(String beanName, AbstractConfig dubboConfig) { + + for (DubboConfigBeanCustomizer customizer : configBeanCustomizers) { + customizer.customize(beanName, dubboConfig); + } + + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof AbstractConfig) { + // 添加别名,id属性的值为别名 + + String id = ((AbstractConfig) bean).getId(); + if (beanDefinitionRegistry != null && beanDefinitionRegistry instanceof DefaultListableBeanFactory) { + DefaultListableBeanFactory factory = (DefaultListableBeanFactory) beanDefinitionRegistry; + if (!StringUtils.isBlank(id) && !factory.hasAlias(beanName, id)) { + beanDefinitionRegistry.registerAlias(beanName, id); + } + } + } + return bean; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + //这里建议看看Spring源码 + @Override + public void afterPropertiesSet() throws Exception { + + initDubboConfigBinder(); // 创建DefaultDubboConfigBinder + + initConfigBeanCustomizers(); + + } + + private void initDubboConfigBinder() { + + if (dubboConfigBinder == null) { + try { + // 先从Spring容器中获取DubboConfigBinder,默认获取不到 + 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); + + } + + private void initConfigBeanCustomizers() { + + // 得到之前创建了的NamePropertyDefaultValueDubboConfigBeanCustomizer + Collection configBeanCustomizers = + beansOfTypeIncludingAncestors(applicationContext, DubboConfigBeanCustomizer.class).values(); + + this.configBeanCustomizers = new ArrayList<>(configBeanCustomizers); + + AnnotationAwareOrderComparator.sort(this.configBeanCustomizers); + } + + /** + * Create {@link DubboConfigBinder} instance. + * + * @param environment + * @return {@link DefaultDubboConfigBinder} + */ + protected DubboConfigBinder createDubboConfigBinder(Environment environment) { + DefaultDubboConfigBinder defaultDubboConfigBinder = new DefaultDubboConfigBinder(); + defaultDubboConfigBinder.setEnvironment(environment); + return defaultDubboConfigBinder; + } + + @Override + public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { + if (this.beanDefinitionRegistry == null) { + this.beanDefinitionRegistry = registry; + } + } + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + //do nothing here + } +} +``` + +#### DefaultDubboConfigBinder + +当某个AbstractConfig类型的Bean,在经过DubboConfigBindingBeanPostProcessor处理时,此时Bean对象中的属性是没有值的,会利用DefaultDubboConfigBinder进行赋值。底层就是利用Spring中的DataBinder技术,结合properties文件对对应的属性进行赋值。 + + + +对应一个AbstractConfig类型(针对的其实是子类,比如ApplicationConfig、RegistryConfig)的Bean,每个类都有一些属性,而properties文件是一个key-value对,所以实际上DataBinder就是将属性名和properties文件中的key进行匹配,如果匹配成功,则把value赋值给属性。具体DataBinder技术是如何工作的,请自行学习(不难)。 + + + +举个例子: + +```java +dubbo.application.name=dubbo-demo-provider1-application +dubbo.application.logger=log4j +``` + + + +对于此配置,它对应ApplicationConfig对象(beanName是自动生成的),所以最终ApplicationConfig对象的name属性的值为“dubbo-demo-provider1-application”,logger属性的值为“log4j”。 + + + +对于 + +```java +dubbo.protocols.p1.name=dubbo +dubbo.protocols.p1.port=20880 +dubbo.protocols.p1.host=0.0.0.0 +``` + + + +它对应ProtocolConfig对象(beanName为p1),所以最终ProtocolConfig对象的name属性的值为“dubbo”,port属性的值为20880,host属性的值为“0.0.0.0”。 + + + +这样就完成了对properties文件的解析。 + + + +```java +public class DefaultDubboConfigBinder extends AbstractDubboConfigBinder { + + @Override + public void bind(String prefix, C dubboConfig) { + DataBinder dataBinder = new DataBinder(dubboConfig); + // Set ignored* + dataBinder.setIgnoreInvalidFields(isIgnoreInvalidFields()); + dataBinder.setIgnoreUnknownFields(isIgnoreUnknownFields()); + // Get properties under specified prefix from PropertySources + // getPropertySources()会拿到由@PropertySource注入进来的properties文件中的内容 + // 同时还包括当前java的所有环境变量,包括手动通过-D添加的配置 + + Map properties = getSubProperties(getPropertySources(), prefix); + // Convert Map to MutablePropertyValues + MutablePropertyValues propertyValues = new MutablePropertyValues(properties); + // Bind + dataBinder.bind(propertyValues); + } + +} +``` + +#### 总结 + +DubboConfigConfigurationRegistrar的主要作用就是对propties文件进行解析并根据不同的配置项项生成对应类型的Bean对象。 + + + + + +### DubboComponentScanRegistrar + +DubboConfigConfigurationRegistrar的作用是向Spring容器中注册两个Bean: + +1. ServiceAnnotationBeanPostProcessor +2. ReferenceAnnotationBeanPostProcessor + + + +#### 代码注释 + +> 部分代码注释 + + ```java + /** + * Dubbo {@link DubboComponentScan} Bean Registrar + * + * @see Service + * @see DubboComponentScan + * @see ImportBeanDefinitionRegistrar + * @see ServiceAnnotationBeanPostProcessor + * @see ReferenceAnnotationBeanPostProcessor + * @since 2.5.7 + */ + public class DubboComponentScanRegistrar implements ImportBeanDefinitionRegistrar { + + + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { + System.out.println("执行DubboComponentScanRegistrar"); + + // 拿到DubboComponentScan注解所定义的包路径,扫描该package下的类,识别这些类上 + Set packagesToScan = getPackagesToScan(importingClassMetadata); + + // 注册ServiceAnnotationBeanPostProcessor一个Bean + // 实现了BeanDefinitionRegistryPostProcessor接口,所以在Spring启动时会调用postProcessBeanDefinitionRegistry方法 + // 该方法会进行扫描,扫描@Service注解了的类,然后生成BeanDefinition(会生成两个,一个普通的bean,一个ServiceBean),后续的Spring周期中会生成Bean + // 在ServiceBean中会监听ContextRefreshedEvent事件,一旦Spring启动完后,就会进行服务导出 + registerServiceAnnotationBeanPostProcessor(packagesToScan, registry); + + // 注册ReferenceAnnotationBeanPostProcessor + // 实现了AnnotationInjectedBeanPostProcessor接口,继而实现了InstantiationAwareBeanPostProcessorAdapter接口 + // 所以Spring在启动时,在对属性进行注入时会调用AnnotationInjectedBeanPostProcessor接口中的postProcessPropertyValues方法 + // 在这个过程中会按照@Reference注解的信息去生成一个RefrenceBean对象 + 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) { + // 生成一个RootBeanDefinition,对应的beanClass为ServiceAnnotationBeanPostProcessor.class + BeanDefinitionBuilder builder = rootBeanDefinition(ServiceAnnotationBeanPostProcessor.class); + // 将包路径作为在构造ServiceAnnotationBeanPostProcessor时调用构造方法时的传入参数 + 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 + // 注册一个ReferenceAnnotationBeanPostProcessor做为bean,ReferenceAnnotationBeanPostProcessor是一个BeanPostProcessor + BeanRegistrar.registerInfrastructureBean(registry, + ReferenceAnnotationBeanPostProcessor.BEAN_NAME, ReferenceAnnotationBeanPostProcessor.class); + + } + //省略... + } + + ``` + + + +#### ServiceAnnotationBeanPostProcessor + +> 主要就是处理@Service注解 + + + + + + +ServiceAnnotationBeanPostProcessor是一个BeanDefinitionRegistryPostProcessor,是用来注册BeanDefinition的。这个类的名字起的不太好。 + + + +它的主要作用是扫描Dubbo的@Service注解,一旦扫描到某个@Service注解就把它以及被它注解的类当做一个Dubbo服务,进行**服务导出**。 + + + +##### DubboClassPathBeanDefinitionScanner + +DubboClassPathBeanDefinitionScanner是所Dubbo自定义的扫描器,继承了Spring中的ClassPathBeanDefinitionScanner了。 + + + +DubboClassPathBeanDefinitionScanner相对于ClassPathBeanDefinitionScanner并没有做太多的改变,只是把useDefaultFilters设置为了false,主要是因为Dubbo中的@Service注解是Dubbo自定义的,在这个注解上并没有用@Component注解(因为Dubbo不是一定要结合Spring才能用),所以为了能利用Spring的扫描逻辑,需要把useDefaultFilters设置为false。 + + + +每扫描到一个@Service注解,就会得到一个BeanDefinition,这个BeanDefinition的beanClass属性就是具体的服务实现类。 + + + +但,如果仅仅只是这样,这只是得到了一个Spring中的Bean,对于Dubbo来说此时得到的Bean是一个**服务**,并且,还需要解析@Service注解的配置信息,因为这些都是服务的参数信息,所以在扫描完了之后,会针对所得到的每个BeanDefinition,都会**额外**的再生成一个**ServiceBean**类型的Bean对象。 + + + +##### ServiceBean + +ServiceBean表示一个Dubbo服务,它有一些参数,比如: + +1. **ref,表示服务的具体实现类** +2. **interface,表示服务的接口** +3. **parameters,表示服务的参数(@Service注解中所配置的信息)** +4. **application,表示服务所属的应用** +5. **protocols,表示服务所使用的协议** +6. **registries,表示服务所要注册的注册中心** + +**所以在扫描到一个@Service注解后,其实会得到两个Bean:** + +1. 一个就是服务实现类本身一个Bean对象 +2. 一个就是对应的ServiceBean类型的一个Bean对象 + + + +并且需要注意的是,ServiceBean实现了ApplicationListener接口,所以当Spring启动完成后会触发onApplicationEvent()方法的调用,而在这个方法内会调用**export()**,这个方法就是**服务导出的入口方法**。 + + + + + +##### 代码注释 + +> 部分代码注释 + +```java +/** + * {@link Service} Annotation + * {@link BeanDefinitionRegistryPostProcessor Bean Definition Registry Post Processor} + * + * @since 2.5.8 + */ +public class ServiceAnnotationBeanPostProcessor implements BeanDefinitionRegistryPostProcessor, EnvironmentAware, + ResourceLoaderAware, BeanClassLoaderAware { + + + 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)) { + // 扫描包,进行Bean注册 + 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); + + // 扫描被Service注解标注的类,先成为Spring里的bean,也就是dubbo服务的实现类 + scanner.addIncludeFilter(new AnnotationTypeFilter(Service.class)); + + /** + * Add the compatibility for legacy Dubbo's @Service + * + * The issue : https://github.com/apache/dubbo/issues/4330 + * @since 2.7.3 + */ + scanner.addIncludeFilter(new AnnotationTypeFilter(com.alibaba.dubbo.config.annotation.Service.class)); + + for (String packageToScan : packagesToScan) { + + // Registers @Service Bean first + // 扫描Dubbo自定义的@Service注解 + scanner.scan(packageToScan); + + // 查找被@Service注解的类的BeanDefinition(无论这个类有没有被@ComponentScan注解标注了) + // Finds all BeanDefinitionHolders of @Service whether @ComponentScan scans or not. + // beanDefinitionHolders装的就是dubbo服务的实现类,存在了Spring容器里 + Set beanDefinitionHolders = + findServiceBeanDefinitionHolders(scanner, packageToScan, registry, beanNameGenerator); + + if (!CollectionUtils.isEmpty(beanDefinitionHolders)) { + + // 扫描到BeanDefinition开始处理它,准备生成ServiceBean + 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 + "]"); + } + + } + + } + + } + + + /** + * 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注解 + Annotation service = findServiceAnnotation(beanClass); + + /** + * The {@link AnnotationAttributes} of @Service annotation + */ + // @Service注解上的信息 + AnnotationAttributes serviceAnnotationAttributes = getAnnotationAttributes(service, false, false); + + // 服务实现类对应的接口 + Class interfaceClass = resolveServiceInterfaceClass(serviceAnnotationAttributes, beanClass); + // 服务实现类对应的bean的名字,比如:demoServiceImpl + String annotatedServiceBeanName = beanDefinitionHolder.getBeanName(); + + // 生成一个ServiceBean + AbstractBeanDefinition serviceBeanDefinition = + buildServiceBeanDefinition(service, serviceAnnotationAttributes, interfaceClass, annotatedServiceBeanName); + + // ServiceBean Bean name + String beanName = generateServiceBeanName(serviceAnnotationAttributes, interfaceClass); + + if (scanner.checkCandidate(beanName, serviceBeanDefinition)) { // check duplicated candidate bean + + // 把ServiceBean注册进去,对应的beanName为ServiceBean:org.apache.dubbo.demo.DemoService + registry.registerBeanDefinition(beanName, serviceBeanDefinition); + + if (logger.isInfoEnabled()) { + logger.info("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 serviceAnnotationAttributes + * @param interfaceClass the class of interface annotated {@link Service} + * @return ServiceBean@interfaceClassName#annotatedServiceBeanName + * @since 2.7.3 + */ + private String generateServiceBeanName(AnnotationAttributes serviceAnnotationAttributes, Class interfaceClass) { + ServiceBeanNameBuilder builder = create(interfaceClass, environment) + .group(serviceAnnotationAttributes.getString("group")) + .version(serviceAnnotationAttributes.getString("version")); + return builder.build(); + } + + + /** + * Build the {@link AbstractBeanDefinition Bean Definition} + * + * @param serviceAnnotation + * @param serviceAnnotationAttributes + * @param interfaceClass + * @param annotatedServiceBeanName + * @return + * @since 2.7.3 + */ + private AbstractBeanDefinition buildServiceBeanDefinition(Annotation serviceAnnotation, + AnnotationAttributes serviceAnnotationAttributes, + Class interfaceClass, + String annotatedServiceBeanName) { + // 生成一个ServiceBean对应的BeanDefinition + BeanDefinitionBuilder builder = rootBeanDefinition(ServiceBean.class); + + AbstractBeanDefinition beanDefinition = builder.getBeanDefinition(); + + MutablePropertyValues propertyValues = beanDefinition.getPropertyValues(); + + + /* + 1.先忽略这些比较特殊的属性,为什么要忽略呢? + 2.其它不忽略的属性,在@Service注解上写的是字符串形式,ServiceBean里也是字符串类型,所以可以直接赋值 + 3.下面的这些特殊属性,比如protocol,在注解上写的是@Service(protocol="p1")这样的字符串,但是在ServiceBean里 + 对应的是ProtocolConfig类型的属性,不可能把字符串赋值给这类型。所以先忽略,后面可以看到单独处理了 + */ + String[] ignoreAttributeNames = of("provider", "monitor", "application", "module", "registry", "protocol", + "interface", "interfaceName", "parameters"); + + // 把@Service注解中的参数值赋值给ServiceBean的属性 + propertyValues.addPropertyValues(new AnnotationPropertyValuesAdapter(serviceAnnotation, environment, ignoreAttributeNames)); + + // References "ref" property to annotated-@Service Bean + // ref属性赋值为另外一个bean, 对应的就是被@Service注解的服务实现类对应的bean + addPropertyReference(builder, "ref", annotatedServiceBeanName); + // Set interface + builder.addPropertyValue("interface", interfaceClass.getName()); + // Convert parameters into map + builder.addPropertyValue("parameters", convertParameters(serviceAnnotationAttributes.getStringArray("parameters"))); + + // 配置了methods属性,则给ServiceBean对应的methods属性赋值 + // Add methods parameters + List methodConfigs = convertMethodConfigs(serviceAnnotationAttributes.get("methods")); + if (!methodConfigs.isEmpty()) { + builder.addPropertyValue("methods", methodConfigs); + } + + /** + * Add {@link org.apache.dubbo.config.ProviderConfig} Bean reference + */ + String providerConfigBeanName = serviceAnnotationAttributes.getString("provider"); + if (StringUtils.hasText(providerConfigBeanName)) { + addPropertyReference(builder, "provider", providerConfigBeanName); + } + + /** + * Add {@link org.apache.dubbo.config.MonitorConfig} Bean reference + */ + String monitorConfigBeanName = serviceAnnotationAttributes.getString("monitor"); + if (StringUtils.hasText(monitorConfigBeanName)) { + addPropertyReference(builder, "monitor", monitorConfigBeanName); + } + + /** + * Add {@link org.apache.dubbo.config.ApplicationConfig} Bean reference + */ + String applicationConfigBeanName = serviceAnnotationAttributes.getString("application"); + if (StringUtils.hasText(applicationConfigBeanName)) { + addPropertyReference(builder, "application", applicationConfigBeanName); + } + + /** + * Add {@link org.apache.dubbo.config.ModuleConfig} Bean reference + */ + String moduleConfigBeanName = serviceAnnotationAttributes.getString("module"); + if (StringUtils.hasText(moduleConfigBeanName)) { + addPropertyReference(builder, "module", moduleConfigBeanName); + } + + + /** + * Add {@link org.apache.dubbo.config.RegistryConfig} Bean reference + * 获取注解上配置的注册中心的beanName + */ + String[] registryConfigBeanNames = serviceAnnotationAttributes.getStringArray("registry"); + + List registryRuntimeBeanReferences = toRuntimeBeanReferences(registryConfigBeanNames); + + if (!registryRuntimeBeanReferences.isEmpty()) { + builder.addPropertyValue("registries", registryRuntimeBeanReferences); + } + + /** + * Add {@link org.apache.dubbo.config.ProtocolConfig} Bean reference + */ + String[] protocolConfigBeanNames = serviceAnnotationAttributes.getStringArray("protocol"); + + List protocolRuntimeBeanReferences = toRuntimeBeanReferences(protocolConfigBeanNames); + + if (!protocolRuntimeBeanReferences.isEmpty()) { + builder.addPropertyValue("protocols", protocolRuntimeBeanReferences); + } + + return builder.getBeanDefinition(); + + } + +} + +``` + +#### ReferenceAnnotationBeanPostProcessor + +> 处理@Reference注解 + + +##### 总结 + + + +ReferenceAnnotationBeanPostProcessor是处理@Reference注解的。 + + + +ReferenceAnnotationBeanPostProcessor的父类是AnnotationInjectedBeanPostProcessor,是一个InstantiationAwareBeanPostProcessorAdapter,是一个BeanPostProcessor。 + + + +Spring在对Bean进行依赖注入时会调用AnnotationInjectedBeanPostProcessor的postProcessPropertyValues()方法来给某个Bean按照ReferenceAnnotationBeanPostProcessor的逻辑进行依赖注入。 + + + +在注入之前会查找注入点,被@Reference注解的属性或方法都是注入点。 + + + +针对某个Bean找到所有注入点之后,就会进行注入了,注入就是给属性或给set方法赋值,但是在赋值之前得先得到一个值,此时就会调用ReferenceAnnotationBeanPostProcessor的**doGetInjectedBean()**方法来得到一个对象,而这个对象的构造就比较复杂了,因为对于Dubbo来说,注入给某个属性的应该是当前这个**属性所对应的服务接口的代理对象**。 + + + +但是在生成这个代理对象之前,还要考虑问题: + +1. 当前所需要引入的这个服务,是不是在本地就存在(就是当前项目)?不存在则要把按Dubbo的逻辑生成一个代理对象 +2. 当前所需要引入的这个服务,是不是已经被引入过了(是不是已经生成过代理对象了),如果是应该是不用再重复去生成了。 + + + +**首先如何判断当前所引入的服务是本地的一个服务(就是当前应用自己所提供的服务)。** + +我们前面提到,Dubbo通过@Service来提供一个服务,并且会生成两个Bean: + +1. 一个服务实现类本身Bean +2. 一个ServiceBean类型的Bean,这个Bean的名字是这么生成的: + +```java +private String generateServiceBeanName(AnnotationAttributes serviceAnnotationAttributes, Class interfaceClass) { + ServiceBeanNameBuilder builder = create(interfaceClass, environment) + .group(serviceAnnotationAttributes.getString("group")) + .version(serviceAnnotationAttributes.getString("version")); + return builder.build(); +} +``` + + + +是通过接口类型+group+version来作为ServiceBean类型Bean的名字的。 + +```java +ServiceBean:org.apache.dubbo.demo.DemoService:group:version +``` + + + + + +所以现在对于服务引入,也应该提前根据@Reference注解中的信息和属性接口类型去判断一下当前Spring容器中是否存在对应的ServiceBean对象,如果存在则直接取出ServiceBean对象的ref属性所对应的对象,作为要注入的结果。 + + + +**然后如何判断当前所引入的这个服务是否已经被引入过了****(是不是已经生成过代理对象了)。** + +这就需要在第一次引入某个服务后(生成代理对象后)进行缓存(记录一下)。Dubbo中是这么做的: + +1. 首先根据@Reference注解的所有信息+属性接口类型生成一个**字符串** +2. 然后@Reference注解的所有信息+属性接口类型生成一个ReferenceBean对象(**ReferenceBean对象中的get方法可以得到一个Dubbo生成的代理对象,可以理解为服务引入的入口方法**) +3. 把字符串作为beanName,ReferenceBean对象作为bean注册到Spring容器中,同时也会放入**referenceBeanCache**中。 + + + + + +有了这些逻辑,@Reference注解服务引入的过程是这样的: + +1. 得到当前所引入服务对应的ServiceBean的beanName(源码中叫referencedBeanName) +2. 根据@Reference注解的所有信息+属性接口类型得到一个referenceBeanName +3. 根据referenceBeanName从**referenceBeanCach**e获取对应的ReferenceBean,如果没有则创建一个ReferenceBean +4. 根据referencedBeanName(ServiceBean的beanName)判断Spring容器中是否存在该bean,如果存在则给ref属性所对应的bean取一个别名,别名为referenceBeanName。 + + 1. 如果Spring容器中不存在referencedBeanName对应的bean,则判断容器中是否存在referenceBeanName所对应的Bean,如果不存在则将创建出来的ReferenceBean注册到Spring容器中(**此处这么做就支持了可以通过@Autowired注解也可以使用服务了,****ReferenceBean是一个FactoryBean**) + +5. 如果referencedBeanName存在对应的Bean,则额外生成一个代理对象,代理对象的InvocationHandler会缓存在**localReferenceBeanInvocationHandlerCache中,这样如果引入的是同一个服务,并且这个服务在本地,** +6. 如果referencedBeanName不存在对应的Bean,则直接调用ReferenceBean的get()方法得到一个代理对象 + + + +##### AnnotationInjectedBeanPostProcessor代码注释 + +> 这个类是Spring的类 + +```java + public AnnotationInjectedBeanPostProcessor(Class... annotationTypes) { + Assert.notEmpty(annotationTypes, "The argument of annotations' types must not empty"); + this.annotationTypes = annotationTypes; + } + + @Override + public PropertyValues postProcessPropertyValues( + PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException { + + // 寻找需要注入的属性(被@Reference标注的Field),也就是寻找注入点 + InjectionMetadata metadata = findInjectionMetadata(beanName, bean.getClass(), pvs); + try { + metadata.inject(bean, beanName, pvs); + } catch (BeanCreationException ex) { + throw ex; + } catch (Throwable ex) { + throw new BeanCreationException(beanName, "Injection of @" + getAnnotationType().getSimpleName() + + " dependencies is failed", ex); + } + return pvs; + } + + private InjectionMetadata findInjectionMetadata(String beanName, Class clazz, PropertyValues pvs) { + // Fall back to class name as cache key, for backwards compatibility with custom callers. + String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName()); + // Quick check on the concurrent map first, with minimal locking. + AnnotationInjectedBeanPostProcessor.AnnotatedInjectionMetadata 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(pvs); + } + try { + metadata = buildAnnotatedMetadata(clazz); + this.injectionMetadataCache.put(cacheKey, metadata); + } catch (NoClassDefFoundError err) { + throw new IllegalStateException("Failed to introspect object class [" + clazz.getName() + + "] for annotation metadata: could not find class that it depends on", err); + } + } + } + } + return metadata; + } + + private AnnotationInjectedBeanPostProcessor.AnnotatedInjectionMetadata buildAnnotatedMetadata(final Class beanClass) { + + // 哪些Filed上有@Reference注解 + Collection fieldElements = findFieldAnnotationMetadata(beanClass); + // 哪些方法上有@Reference注解 + Collection methodElements = findAnnotatedMethodMetadata(beanClass); + // 返回的是Dubbo定义的AnnotatedInjectionMetadata,接下来就会使用这个类去进行属性注入 + return new AnnotationInjectedBeanPostProcessor.AnnotatedInjectionMetadata(beanClass, fieldElements, methodElements); + + } + + /** + * {@link Annotation Annotated} {@link InjectionMetadata} implementation + */ + private class AnnotatedInjectionMetadata extends InjectionMetadata { + + private final Collection fieldElements; + + private final Collection methodElements; + + public AnnotatedInjectionMetadata(Class targetClass, Collection fieldElements, + Collection methodElements) { + super(targetClass, combine(fieldElements, methodElements)); + this.fieldElements = fieldElements; + this.methodElements = methodElements; + } + + public Collection getFieldElements() { + return fieldElements; + } + + public Collection getMethodElements() { + return methodElements; + } + } + + /** + * {@link Annotation Annotated} {@link Method} {@link InjectionMetadata.InjectedElement} + */ + private class AnnotatedMethodElement extends InjectionMetadata.InjectedElement { + + private final Method method; + + private final AnnotationAttributes attributes; + + private volatile Object object; + + protected AnnotatedMethodElement(Method method, PropertyDescriptor pd, AnnotationAttributes attributes) { + super(method, pd); + this.method = method; + this.attributes = attributes; + } + + @Override + protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable { + + // set方法对应的属性的类型 + Class injectedType = pd.getPropertyType(); + + // 从Spring容器中获取一个Bean(注意,这个方法内部会生成Bean而且会缓存,就像Spring中的getBean一样) + Object injectedObject = getInjectedObject(attributes, bean, beanName, injectedType, this); + + ReflectionUtils.makeAccessible(method); + + // 调用set方法 + method.invoke(bean, injectedObject); + + } + + } + + /** + * {@link Annotation Annotated} {@link Field} {@link InjectionMetadata.InjectedElement} + */ + public class AnnotatedFieldElement extends InjectionMetadata.InjectedElement { + + private final Field field; + + private final AnnotationAttributes attributes; + + private volatile Object bean; + + protected AnnotatedFieldElement(Field field, AnnotationAttributes attributes) { + super(field, null); + this.field = field; + this.attributes = attributes; + } + + @Override + protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable { + // 给bean对象进行属性赋值 + + Class injectedType = field.getType(); + + // 获取对象,然后进行注入 + Object injectedObject = getInjectedObject(attributes, bean, beanName, injectedType, this); + + ReflectionUtils.makeAccessible(field); + + // 字段赋值,injectedObject就是值 + field.set(bean, injectedObject); + + } + + } + + protected Object getInjectedObject(AnnotationAttributes attributes, Object bean, String beanName, Class injectedType, + InjectionMetadata.InjectedElement injectedElement) throws Exception { + // ServiceBean:org.apache.dubbo.demo.DemoService#source=private org.apache.dubbo.demo.DemoService org.apache.dubbo.demo.consumer.comp.DemoServiceComponent.demoService#attributes={parameters=[Ljava.lang.String;@42e25b0b} + // 哪个Service应用了哪个类型的服务,通过什么方式引入的 + // cacheKey很鸡肋,属性名不一样的时候,cacheKey不一样,导致不能缓存, 在一个Service中@Reference两次同一个服务缓存不到 + String cacheKey = buildInjectedObjectCacheKey(attributes, bean, beanName, injectedType, injectedElement); + + + Object injectedObject = injectedObjectsCache.get(cacheKey); + + if (injectedObject == null) { + // // 这里会调用子类的方法生成Bean,这里的子类指的就是ReferenceAnnotationBeanPostProcessor + injectedObject = doGetInjectedBean(attributes, bean, beanName, injectedType, injectedElement); + + // Customized inject-object if necessary + injectedObjectsCache.putIfAbsent(cacheKey, injectedObject); + } + + return injectedObject; + + } + +``` + + + + + +##### ReferenceAnnotationBeanPostProcessor代码注释 + +> 部分注释 + +```java +/** + * {@link org.springframework.beans.factory.config.BeanPostProcessor} implementation + * that Consumer service {@link Reference} annotated fields + * + * @since 2.5.7 + */ +public class ReferenceAnnotationBeanPostProcessor extends AnnotationInjectedBeanPostProcessor implements + ApplicationContextAware, ApplicationListener { + + /** + * The bean name of {@link ReferenceAnnotationBeanPostProcessor} + */ + public static final String BEAN_NAME = "referenceAnnotationBeanPostProcessor"; + + /** + * Cache size + */ + private static final int CACHE_SIZE = Integer.getInteger(BEAN_NAME + ".cache.size", 32); + + private final ConcurrentMap> referenceBeanCache = + new ConcurrentHashMap<>(CACHE_SIZE); + + private final ConcurrentHashMap localReferenceBeanInvocationHandlerCache = + new ConcurrentHashMap<>(CACHE_SIZE); + + private final ConcurrentMap> injectedFieldReferenceBeanCache = + new ConcurrentHashMap<>(CACHE_SIZE); + + private final ConcurrentMap> injectedMethodReferenceBeanCache = + new ConcurrentHashMap<>(CACHE_SIZE); + + private ApplicationContext applicationContext; + + /** + * To support the legacy annotation that is @com.alibaba.dubbo.config.annotation.Reference since 2.7.3 + */ + public ReferenceAnnotationBeanPostProcessor() { + ////调用AnnotationInjectedBeanPostProcessor的构造 + super(Reference.class, com.alibaba.dubbo.config.annotation.Reference.class); + } + + /** + * Gets all beans of {@link ReferenceBean} + * + * @return non-null read-only {@link Collection} + * @since 2.5.9 + */ + public Collection> getReferenceBeans() { + return referenceBeanCache.values(); + } + + /** + * Get {@link ReferenceBean} {@link Map} in injected field. + * + * @return non-null {@link Map} + * @since 2.5.11 + */ + public Map> getInjectedFieldReferenceBeanMap() { + return Collections.unmodifiableMap(injectedFieldReferenceBeanCache); + } + + /** + * Get {@link ReferenceBean} {@link Map} in injected method. + * + * @return non-null {@link Map} + * @since 2.5.11 + */ + public Map> getInjectedMethodReferenceBeanMap() { + return Collections.unmodifiableMap(injectedMethodReferenceBeanCache); + } + + // 该方法得到的对象会赋值给@ReferenceBean注解的属性 + // + @Override + protected Object doGetInjectedBean(AnnotationAttributes attributes, Object bean, String beanName, Class injectedType, + InjectionMetadata.InjectedElement injectedElement) throws Exception { + + + // 按ServiceBean的beanName生成规则来生成referencedBeanName, 规则为ServiceBean:interfaceClassName:version:group + String referencedBeanName = buildReferencedBeanName(attributes, injectedType); + + /* + 1.相当于根据@Reference注解的信息(包括注解括号里的参数)按照toString的逻辑,生成referenceBeanName,这个东西是作为缓存的key + 实际上是进行了一些字符串的转换啥的,不用关心 + 2.注意referencedBeanName和referenceBeanName的区别【一个有d字母,一个没有】。referencedBeanName是ServiceBean的 + 名字。referenceBeanName是ReferenceBean的名字 + */ + String referenceBeanName = getReferenceBeanName(attributes, injectedType); + + // 生成一个ReferenceBean对象 + ReferenceBean referenceBean = buildReferenceBeanIfAbsent(referenceBeanName, attributes, injectedType); + + // 把referenceBean添加到Spring容器中去 + registerReferenceBean(referencedBeanName, referenceBean, attributes, injectedType); + + //缓存注入点,不重要 + cacheInjectedReferenceBean(referenceBean, injectedElement); + + // 创建一个代理对象,Service中的属性被注入的就是这个代理对象 + // 内部会调用referenceBean.get(); + return getOrCreateProxy(referencedBeanName, referenceBeanName, referenceBean, injectedType); + } + + /** + * Register an instance of {@link ReferenceBean} as a Spring Bean + * + * @param referencedBeanName The name of bean that annotated Dubbo's {@link Service @Service} in the Spring {@link ApplicationContext} + * @param referenceBean the instance of {@link ReferenceBean} is about to register into the Spring {@link ApplicationContext} + * @param attributes the {@link AnnotationAttributes attributes} of {@link Reference @Reference} + * @param interfaceClass the {@link Class class} of Service interface + * @since 2.7.3 + */ + private void registerReferenceBean(String referencedBeanName, ReferenceBean referenceBean, + AnnotationAttributes attributes, + Class interfaceClass) { + + ConfigurableListableBeanFactory beanFactory = getBeanFactory(); + + // 就是referenceBeanName + String beanName = getReferenceBeanName(attributes, interfaceClass); + + // 当前Spring容器中是否存在referencedBeanName + if (existsServiceBean(referencedBeanName)) { // If @Service bean is local one + /** + * Get the @Service's BeanDefinition from {@link BeanFactory} + * Refer to {@link ServiceAnnotationBeanPostProcessor#buildServiceBeanDefinition} + */ + AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition) beanFactory.getBeanDefinition(referencedBeanName); + RuntimeBeanReference runtimeBeanReference = (RuntimeBeanReference) beanDefinition.getPropertyValues().get("ref"); // ServiceBean --- ref + // The name of bean annotated @Service + String serviceBeanName = runtimeBeanReference.getBeanName(); // DemoServiceImpl对应的beanName + // register Alias rather than a new bean name, in order to reduce duplicated beans + // DemoServiceImpl多了一个别名 + beanFactory.registerAlias(serviceBeanName, beanName); + } else { // Remote @Service Bean + if (!beanFactory.containsBean(beanName)) { + /* + 1.放入Spring容器里的应该是DemoService的实现类的代理对象,但是这里放的却是referenceBean类型对象 + 2.referenceBean的父类有个get()可以直接获取到代理对象,那这里为啥不放呢?其实是为了支持@Autowire注解 + 3.首先ReferenceBean实现了Spring的FactoryBean接口,FactoryBean获取对象时是这样的调用顺序 + FactoryBean.getObject() ==> getObject().get() ==>最终调子类ReferenceBean#get() + 刚好给@Autowire注解注入了代理对象 + */ + beanFactory.registerSingleton(beanName, referenceBean); + } + } + } + + /** + * Get the bean name of {@link ReferenceBean} if {@link Reference#id() id attribute} is present, + * or {@link #generateReferenceBeanName(AnnotationAttributes, Class) generate}. + * + * @param attributes the {@link AnnotationAttributes attributes} of {@link Reference @Reference} + * @param interfaceClass the {@link Class class} of Service interface + * @return non-null + * @since 2.7.3 + */ + private String getReferenceBeanName(AnnotationAttributes attributes, Class interfaceClass) { + // id attribute appears since 2.7.3 + String beanName = getAttribute(attributes, "id"); + + // beanName为null时会进入if判断 + if (!hasText(beanName)) { + beanName = generateReferenceBeanName(attributes, interfaceClass); + } + return beanName; + } + + /** + * Build the bean name of {@link ReferenceBean} + * + * @param attributes the {@link AnnotationAttributes attributes} of {@link Reference @Reference} + * @param interfaceClass the {@link Class class} of Service interface + * @return + * @since 2.7.3 + */ + private String generateReferenceBeanName(AnnotationAttributes attributes, Class interfaceClass) { + StringBuilder beanNameBuilder = new StringBuilder("@Reference"); + + if (!attributes.isEmpty()) { + beanNameBuilder.append('('); + for (Map.Entry entry : attributes.entrySet()) { + beanNameBuilder.append(entry.getKey()) + .append('=') + .append(entry.getValue()) + .append(','); + } + // replace the latest "," to be ")" + beanNameBuilder.setCharAt(beanNameBuilder.lastIndexOf(","), ')'); + } + + beanNameBuilder.append(" ").append(interfaceClass.getName()); + + return beanNameBuilder.toString(); + } + + private boolean existsServiceBean(String referencedBeanName) { + return applicationContext.containsBean(referencedBeanName); + } + + /** + * Get or Create a proxy of {@link ReferenceBean} for the specified the type of Dubbo service interface + * + * @param referencedBeanName The name of bean that annotated Dubbo's {@link Service @Service} in the Spring {@link ApplicationContext} + * @param referenceBeanName the bean name of {@link ReferenceBean} + * @param referenceBean the instance of {@link ReferenceBean} + * @param serviceInterfaceType the type of Dubbo service interface + * @return non-null + * @since 2.7.4 + */ + private Object getOrCreateProxy(String referencedBeanName, String referenceBeanName, ReferenceBean referenceBean, Class serviceInterfaceType) { + /* + 1.引入的服务在Spirng容器里有,说明是本项目的bean,按理来说应该直接把DemoService的实现类赋给@Reference注解标注的属性 + 2.但实际上不是,@Reference实际上最后赋值的是一个代理对象,因为除开需要执行DemoService的实现类里的方法,@Reference + 注解标注的属性还需要实现其它很多逻辑,实现这些增强逻辑之后最终才执行的DemoService的实现类里的方法。如果直接赋值 + DemoServiceImpl,那么Dubbo里面的很多逻辑就走不到了 + */ + if (existsServiceBean(referencedBeanName)) { // If the local @Service Bean exists, build a proxy of ReferenceBean + //wrapInvocationHandler最后也会调用referenceBean.get(),其实和下面的else一样 + return newProxyInstance(getClassLoader(), new Class[]{serviceInterfaceType}, + wrapInvocationHandler(referenceBeanName, referenceBean)); + } else { // ReferenceBean should be initialized and get immediately + // 重点,最终从这里赋值给@Reference注解标注的属性 + return referenceBean.get(); + } + } + + /** + * Wrap an instance of {@link InvocationHandler} that is used to get the proxy of {@link ReferenceBean} after + * the specified local referenced bean that annotated {@link @Service} exported. + * + * @param referenceBeanName the bean name of {@link ReferenceBean} + * @param referenceBean the instance of {@link ReferenceBean} + * @return non-null + * @since 2.7.4 + */ + private InvocationHandler wrapInvocationHandler(String referenceBeanName, ReferenceBean referenceBean) { + return localReferenceBeanInvocationHandlerCache.computeIfAbsent(referenceBeanName, name -> + new ReferenceBeanInvocationHandler(referenceBean)); + } + + private static class ReferenceBeanInvocationHandler implements InvocationHandler { + + private final ReferenceBean referenceBean; + + private Object bean; + + private ReferenceBeanInvocationHandler(ReferenceBean referenceBean) { + this.referenceBean = referenceBean; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + Object result; + try { + if (bean == null) { // If the bean is not initialized, invoke init() + // issue: https://github.com/apache/dubbo/issues/3429 + init(); + } + result = method.invoke(bean, args); + } catch (InvocationTargetException e) { + // re-throws the actual Exception. + throw e.getTargetException(); + } + return result; + } + + private void init() { + this.bean = referenceBean.get(); + } + } + + @Override + protected String buildInjectedObjectCacheKey(AnnotationAttributes attributes, Object bean, String beanName, + Class injectedType, InjectionMetadata.InjectedElement injectedElement) { + return buildReferencedBeanName(attributes, injectedType) + + "#source=" + (injectedElement.getMember()) + + "#attributes=" + AnnotationUtils.resolvePlaceholders(attributes, getEnvironment()); + } + + /** + * @param attributes the attributes of {@link Reference @Reference} + * @param serviceInterfaceType the type of Dubbo's service interface + * @return The name of bean that annotated Dubbo's {@link Service @Service} in local Spring {@link ApplicationContext} + */ + private String buildReferencedBeanName(AnnotationAttributes attributes, Class serviceInterfaceType) { + ServiceBeanNameBuilder serviceBeanNameBuilder = create(attributes, serviceInterfaceType, getEnvironment()); + return serviceBeanNameBuilder.build(); + } + + private ReferenceBean buildReferenceBeanIfAbsent(String referenceBeanName, AnnotationAttributes attributes, + Class referencedType) + throws Exception { + + /* + 1.key就是@Reference注解最终生成的字符串,value却不是简单的代理对象 + 2.一个ServiceBean表示一个Dubbo服务,ReferenceBean表示引用的哪个dubbo服务 + 3.所以ReferenceBean不是简单的代理对象,他保存了诸如超时时间,轮询参数等等这些东西 + 【和ServcieBean很类似,ServcieBean也保存了这些东西】 + 4.ReferenceBean的父类ReferenceConfig#get()最终返回代理对象 + 5.ServiceBean和ReferenceBean思想基本上是一样的 + 6.ReferenceBean最终会放入缓存,赋值给@Reference注解所标注的属性的依然是代理对象 + */ + ReferenceBean referenceBean = referenceBeanCache.get(referenceBeanName); + + if (referenceBean == null) { + + // 生成了一个ReferenceBean对象,attributes是@Reference注解的参数值 + ReferenceBeanBuilder beanBuilder = ReferenceBeanBuilder + .create(attributes, applicationContext) + .interfaceClass(referencedType); + referenceBean = beanBuilder.build(); + + referenceBeanCache.put(referenceBeanName, referenceBean); + } else if (!referencedType.isAssignableFrom(referenceBean.getInterfaceClass())) { + throw new IllegalArgumentException("reference bean name " + referenceBeanName + " has been duplicated, but interfaceClass " + + referenceBean.getInterfaceClass().getName() + " cannot be assigned to " + referencedType.getName()); + } + return referenceBean; + } + + private void cacheInjectedReferenceBean(ReferenceBean referenceBean, + InjectionMetadata.InjectedElement injectedElement) { + if (injectedElement.getMember() instanceof Field) { + injectedFieldReferenceBeanCache.put(injectedElement, referenceBean); + } else if (injectedElement.getMember() instanceof Method) { + injectedMethodReferenceBeanCache.put(injectedElement, referenceBean); + } + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + @Override + public void onApplicationEvent(ApplicationEvent event) { + if (event instanceof ServiceBeanExportedEvent) { + onServiceBeanExportEvent((ServiceBeanExportedEvent) event); + } else if (event instanceof ContextRefreshedEvent) { + onContextRefreshedEvent((ContextRefreshedEvent) event); + } + } + + private void onServiceBeanExportEvent(ServiceBeanExportedEvent event) { + ServiceBean serviceBean = event.getServiceBean(); + initReferenceBeanInvocationHandler(serviceBean); + } + + private void initReferenceBeanInvocationHandler(ServiceBean serviceBean) { + String serviceBeanName = serviceBean.getBeanName(); + // Remove ServiceBean when it's exported + ReferenceBeanInvocationHandler handler = localReferenceBeanInvocationHandlerCache.remove(serviceBeanName); + // Initialize + if (handler != null) { + handler.init(); + } + } + + private void onContextRefreshedEvent(ContextRefreshedEvent event) { + + } + + + @Override + public void destroy() throws Exception { + super.destroy(); + this.referenceBeanCache.clear(); + this.localReferenceBeanInvocationHandlerCache.clear(); + this.injectedFieldReferenceBeanCache.clear(); + this.injectedMethodReferenceBeanCache.clear(); + } +} + +``` \ No newline at end of file diff --git a/docs/rpc/dubbo/05.Dubbo源码系列V1-Dubbo第五节-服务导出源码解析.md b/docs/rpc/dubbo/05.Dubbo源码系列V1-Dubbo第五节-服务导出源码解析.md new file mode 100644 index 0000000..8f848a2 --- /dev/null +++ b/docs/rpc/dubbo/05.Dubbo源码系列V1-Dubbo第五节-服务导出源码解析.md @@ -0,0 +1,1676 @@ +--- +title: 05.Dubbo源码系列V1-Dubbo第五节-服务导出源码解析 +tags: + - Dubbo + - rpc +categories: + - rpc + - Dubbo源码系列v1 +keywords: Dubbo,rpc +description: Dubbo服务导出源码解析 +cover: 'https://cdn.jsdelivr.net/gh/youthlql/youthlql/img/dubbo.png' +abbrlink: '48141866' +date: 2021-10-06 14:11:58 +--- + + + +## 第五节: Dubbo服务注册(导出)源码解析 + + + +### 服务导出原理概述 + +1. 服务导出的入口为ServiceBean中的export()方法,当Spring启动完之后,通过接收Spring的ContextRefreshedEvent事件来触发export()方法的执行。 +2. 一个ServiceBean对象就表示一个Dubbo服务,ServiceBean对象中的参数就表示服务的参数,比如timeout,该对象的参数值来至@Service注解中所定义的。 +3. 服务导出主要得做两件事情: + 1. 根据服务的参数信息,启动对应的网络服务器(netty、tomcat、jetty等),用来接收网络请求 + 2. 将服务的信息注册到注册中心 +4. 但是在做这两件事情之前得先把服务的参数确定好,因为一个Dubbo服务的参数,除开可以在@Service注解中去配置,还会继承Dubbo服务所属应用(Application)上的配置,还可以在配置中心或JVM环境变量中去配置某个服务的参数,所以首先要做的是确定好当前服务最终的(优先级最高)的参数值。 +5. 确定好服务参数之后,就根据所配置的协议启动对应的网络服务器。在启动网络服务器时,并且在网络服务器接收请求的过程中,都可以从服务参数中获取信息,比如最大连接数,线程数,socket超时时间等等。 +6. 启动完网络服务器之后,就将服务信息注册到注册中心。同时还有向注册中心注册监听器,监听Dubbo的中的动态配置信息变更。 + +> 服务导出就是服务注册的意思 + + + + + +### 服务概念的演化 + +1. DemoService接口表示一个服务,此时的服务表示服务定义 +2. DemoServiceImpl表示DemoService服务的具体实现,此时的服务表示服务的具体实现 +3. DemoService+group+version表示一个服务,此时的服务增加了分组和版本概念 +4. [http://192.168.31.211:80/cn.imlql.DemoService](http://192.168.31.211:80/com.luban.DemoService)表示一个服务,此时的服务增加了机器IP和Port,表示远程机器可以访问这个URL来使用cn.imlql.DemoService这个服务 +5. [http://192.168.31.211:80/cn.imlql.DemoService](http://192.168.31.211:80/com.luban.DemoService)?timeout=3000&version=1.0.1&application=dubbo-demo-provider-application表示一个服务,此时的服务是拥有参数的,比如超时时间、版本号、所属应用 + + + +在dubbo中就是用的最后一种方式来表示服务的。 + + + +### 服务导出思想 + +服务导出要做的几件事情: + +1. 确定服务的参数 + 2. 确定服务支持的协议 + 2. 构造服务最终的URL +2. 根据服务支持的不同协议,启动不同的Server,用来接收和处理请求 +3. 将服务URL注册到注册中心去 +4. 因为Dubbo支持动态配置服务参数,所以服务导出时还需要绑定一个监听器Listener来监听服务的参数是否有修改,如果发现有修改,则需要重新进行导出 + + + +### 确定服务的参数 + +#### 确定服务的参数概述 + +- 在执行ServiceConfig.export()时,此时ServiceConfig对象就代表一个服务(也可以说ServiceBena代表一个服务,因为本来就是继承关系),我们已经知道了这个服务的名字(就是服务提供者接口的名字),并且此时这个服务可能已经有一些参数了,就是**@Service注解上所定义的参数**。 + +- 但是在Dubbo中,除开可以在@Service注解中给服务配置参数,还有很多地方也可以给服务配置参数,比如: + - dubbo.properties文件,你可以建立这个文件,dubbo会去读取这个文件的内容作为服务的参数,Dubob的源码中叫做**PropertiesConfiguration** + - 配置中心,dubbo在2.7版本后就支持了分布式配置中心,你可以在Dubbo-Admin中去操作配置中心,分布式配置中心就相当于一个远程的dubbo.properties文件,你可以在Dubbo-Admin中去修改这个dubbo.properties文件,当然配置中心支持按应用进行配置,也可以按全局进行配置两种,在Dubbo的源码中**AppExternalConfiguration**表示应用配置,**ExternalConfiguration**表示全局配置。 + - 系统环境变量,你可以在启动应用程序时,通过-D的方式来指定参数,在Dubbo的源码中叫**SystemConfiguration** + - 再加上通过@Service注解所配置的参数,在Dubbo的源码中叫**AbstractConfig** + +- 服务的参数可以从这四个位置来,这四个位置上如果配了同一个参数的话,优先级从高到低有两种情况: + - SystemConfiguration -> AppExternalConfiguration -> ExternalConfiguration -> AbstractConfig -> PropertiesConfiguration + - SystemConfiguration -> AbstractConfig -> AppExternalConfiguration -> ExternalConfiguration -> PropertiesConfiguration +- 在服务导出时,首先得确定服务的参数。当然,服务的参数除开来自于服务的自身配置外,还可以来自其**上级**。比如如果服务本身没有配置timeout参数,但是如果服务所属的应用的配置了timeout,那么这个应用下的服务都会继承这个timeout配置。**所以在确定服务参数时,需要先从上级获取参数,获取之后,如果服务本身配置了相同的参数,那么则进行覆盖。** + + + +#### 确定服务的参数源码 + +##### ServiceBean + + ```java + //当Spring启动完之后,通过接收Spring的ContextRefreshedEvent事件来触发export()方法的执行。 + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + // 当前服务没有被导出并且没有卸载,才导出服务 + if (!isExported() && !isUnexported()) { + if (logger.isInfoEnabled()) { + logger.info("The service ready on spring started. service: " + getInterface()); + } + // 服务导出(服务注册) + export(); + } + } + + @Override + public void export() { + //调用ServiceConfig#export() + super.export(); + // Publish ServiceBeanExportedEvent + // Spring启动完发布ContextRefreshedEvent事件--->服务导出--->发布ServiceBeanExportedEvent + // 程序员可以通过Spring中的ApplicationListener来监听服务导出是否完成 + publishExportEvent(); + } + + private void publishExportEvent() { + //监听这个事件就可以知道Dubbo的服务有没有注册完成 + ServiceBeanExportedEvent exportEvent = new ServiceBeanExportedEvent(this); + applicationEventPublisher.publishEvent(exportEvent); + } + ``` + +##### ServiceConfig + +```java + public synchronized void export() { + //读取服务配置 + checkAndUpdateSubConfigs(); + + // 检查服务是否需要导出 + if (!shouldExport()) { + return; + } + + // 检查是否需要延迟发布 + if (shouldDelay()) { + DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS); + } else { + // 导出服务 + doExport(); + } + } + + + public void checkAndUpdateSubConfigs() { + // Use default configs defined explicitly on global configs + // ServiceConfig中的某些属性如果是空的,那么就从ProviderConfig、ModuleConfig、ApplicationConfig中获取 + // 补全ServiceConfig中的属性 + completeCompoundConfigs(); + + // Config Center should always being started first. + // 从配置中心获取配置,包括应用配置和全局配置 + // 把获取到的配置放入到Environment中的externalConfigurationMap和appExternalConfigurationMap中 + // 并刷新所有的XxConfig的属性(除开ServiceConfig),刷新的意思就是将配置中心的配置覆盖调用XxConfig中的属性 + // 调用AbstractInterfaceConfig#startConfigCenter() + startConfigCenter(); + + checkDefault(); + + checkProtocol(); + + checkApplication(); + + // if protocol is not injvm checkRegistry + // 如果protocol不是只有injvm协议,表示服务调用不是只在本机jvm里面调用,那就需要用到注册中心 + if (!isOnlyInJvm()) { + checkRegistry(); + } + + // 刷新ServiceConfig,调用AbstractConfig#refresh() + this.refresh(); + + // 如果配了metadataReportConfig,那么就刷新配置 + checkMetadataReport(); + + if (StringUtils.isEmpty(interfaceName)) { + throw new IllegalStateException(" interface not allow null!"); + } + + // 当前服务对应的实现类是一个GenericService,表示没有特定的接口 + if (ref instanceof GenericService) { + interfaceClass = GenericService.class; + if (StringUtils.isEmpty(generic)) { + generic = Boolean.TRUE.toString(); + } + } else { + // 加载接口 + try { + interfaceClass = Class.forName(interfaceName, true, Thread.currentThread() + .getContextClassLoader()); + } catch (ClassNotFoundException e) { + throw new IllegalStateException(e.getMessage(), e); + } + // 刷新MethodConfig,并判断MethodConfig中对应的方法在接口中是否存在 + checkInterfaceAndMethods(interfaceClass, methods); + // 实现类是不是该接口类型 + checkRef(); + generic = Boolean.FALSE.toString(); + } + // local和stub一样,不建议使用了 + if (local != null) { + // 如果本地存根为true,则存根类为interfaceName + "Local" + if (Boolean.TRUE.toString().equals(local)) { + local = interfaceName + "Local"; + } + // 加载本地存根类 + Class localClass; + try { + localClass = ClassUtils.forNameWithThreadContextClassLoader(local); + } catch (ClassNotFoundException e) { + throw new IllegalStateException(e.getMessage(), e); + } + if (!interfaceClass.isAssignableFrom(localClass)) { + throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceName); + } + } + // 本地存根 + if (stub != null) { + // 如果本地存根为true,则存根类为interfaceName + "Stub" + if (Boolean.TRUE.toString().equals(stub)) { + stub = interfaceName + "Stub"; + } + Class stubClass; + try { + stubClass = ClassUtils.forNameWithThreadContextClassLoader(stub); + } catch (ClassNotFoundException e) { + throw new IllegalStateException(e.getMessage(), e); + } + if (!interfaceClass.isAssignableFrom(stubClass)) { + throw new IllegalStateException("The stub implementation class " + stubClass.getName() + " not implement interface " + interfaceName); + } + } + // 检查local和stub + checkStubAndLocal(interfaceClass); + // 检查mock + checkMock(interfaceClass); + } + + + /** + * 1.上节文章我们在启动类上写了这个配置 + * @PropertySource("classpath:/spring/dubbo-provider.properties") + * 2.那么Spring启动的时候就会加载里面的配置到一些xxxConfig里面【Spring整合Dubbo的时候讲过】 + * 3.@Service注解里配置的参数被首先读取到了ServiceBean里 + * 4.接着会调用这个方法进行补全ServiceBean的配置,从哪里补全呢?就是从上面我们配置的 + * dubbo-provider.properties 进行补全 + * 5.ServcieBean继承了ServiceConfig,所以它两是一个意思,这里强调一下 + * 免得后续看不明白 + */ + private void completeCompoundConfigs() { + // 如果配置了provider,那么则从provider中获取信息赋值其他属性,在这些属性为空的情况下 + if (provider != null) { + if (application == null) { + setApplication(provider.getApplication()); + } + if (module == null) { + setModule(provider.getModule()); + } + if (registries == null) { + setRegistries(provider.getRegistries()); + } + if (monitor == null) { + setMonitor(provider.getMonitor()); + } + if (protocols == null) { + setProtocols(provider.getProtocols()); + } + if (configCenter == null) { + setConfigCenter(provider.getConfigCenter()); + } + } + // 如果配置了module,那么则从module中获取信息赋值其他属性,在这些属性为空的情况下 + if (module != null) { + if (registries == null) { + setRegistries(module.getRegistries()); + } + if (monitor == null) { + setMonitor(module.getMonitor()); + } + } + // 如果配置了application,那么则从application中获取信息赋值其他属性,在这些属性为空的情况下 + if (application != null) { + if (registries == null) { + setRegistries(application.getRegistries()); + } + if (monitor == null) { + setMonitor(application.getMonitor()); + } + } + } + + +``` + +##### AbstractInterfaceConfig + +```java + void startConfigCenter() { + if (configCenter == null) { + ConfigManager.getInstance().getConfigCenter().ifPresent(cc -> this.configCenter = cc); + } + + // 如果配置了ConfigCenter + if (this.configCenter != null) { + + // 从其他位置获取配置中心的相关属性信息,比如配置中心地址 + // TODO there may have duplicate refresh + this.configCenter.refresh(); + + // 属性更新后,从远程配置中心获取数据(应用配置,全局配置) + prepareEnvironment(); + } + + // 从配置中心取到配置数据后,刷新所有的XxConfig中的属性,除开ServiceConfig + ConfigManager.getInstance().refreshAll(); + } + + + private void prepareEnvironment() { + if (configCenter.isValid()) { + if (!configCenter.checkOrUpdateInited()) { + return; + } + + // 动态配置中心,管理台上的配置中心 + DynamicConfiguration dynamicConfiguration = getDynamicConfiguration(configCenter.toUrl()); + + // 如果是zookeeper,获取的就是/dubbo/config/dubbo/dubbo.properties节点中的内容 + String configContent = dynamicConfiguration.getProperties(configCenter.getConfigFile(), configCenter.getGroup()); + + String appGroup = application != null ? application.getName() : null; + String appConfigContent = null; + if (StringUtils.isNotEmpty(appGroup)) { + // 获取的就是/dubbo/config/dubbo-demo-consumer-application/dubbo.properties节点中的内容 + // 这里有bug + appConfigContent = dynamicConfiguration.getProperties + (StringUtils.isNotEmpty(configCenter.getAppConfigFile()) ? configCenter.getAppConfigFile() : configCenter.getConfigFile(), + appGroup + ); + } + try { + Environment.getInstance().setConfigCenterFirst(configCenter.isHighestPriority()); + //这个就是全局的,就是在网页上那个配置管理里的global + Environment.getInstance().updateExternalConfigurationMap(parseProperties(configContent)); + //这个就是某个应用的配置 + Environment.getInstance().updateAppExternalConfigurationMap(parseProperties(appConfigContent)); + } catch (IOException e) { + throw new IllegalStateException("Failed to parse configurations from Config Center.", e); + } + } + } +``` + +##### ConfigManager + +```java + public void refreshAll() { + // refresh all configs here, + getApplication().ifPresent(ApplicationConfig::refresh); + getMonitor().ifPresent(MonitorConfig::refresh); + getModule().ifPresent(ModuleConfig::refresh); + + getProtocols().values().forEach(ProtocolConfig::refresh); + getRegistries().values().forEach(RegistryConfig::refresh); + getProviders().values().forEach(ProviderConfig::refresh); + getConsumers().values().forEach(ConsumerConfig::refresh); + } +``` + +##### AbstractConfig + +```java +/** + * 1.刷新XxConfig + * 2.一个XxConfig对象的属性可能是有值的,也可能是没有值的,这时需要从其他位置获取属性值,来进行属性的覆盖 + * 覆盖的优先级,从大到小为系统变量->配置中心应用配置->配置中心全局配置->注解或xml中定义->dubbo.properties文件 + * 3.以ServiceConfig为例,ServiceConfig中包括很多属性,比如timeout + * 但是在定义一个Service时,如果在注解上没有配置timeout,那么就会其他地方获取timeout的配置 + * 比如可以从系统变量->配置中心应用配置->配置中心全局配置->注解或xml中定义->dubbo.properties文件 + * refresh是刷新,将当前ServiceConfig上的set方法所对应的属性更新为优先级最高的值 + */ + public void refresh() { + try { + /** + * 1.这里确定的配置优先级从高到低是这样的 + * 系统环境变量【JVM环境变量->操作系统环境变量】->配置中心应用配置->配置中心全局配置->dubbo.properties文件 + * 2.调用的是Environment#getConfiguration() + */ + CompositeConfiguration compositeConfiguration = Environment.getInstance().getConfiguration(getPrefix(), getId()); + + // 表示XxConfig对象本身- AbstractConfig + Configuration config = new ConfigConfigurationAdapter(this); // ServiceConfig + + if (Environment.getInstance().isConfigCenterFirst()) {//这个是默认的 + // 优先级顺序: SystemConfiguration -> AppExternalConfiguration -> ExternalConfiguration -> AbstractConfig -> PropertiesConfiguration + compositeConfiguration.addConfiguration(4, config); + } else { + // The sequence would be: SystemConfiguration -> AbstractConfig -> AppExternalConfiguration -> ExternalConfiguration -> PropertiesConfiguration + compositeConfiguration.addConfiguration(2, config); + } + + // loop methods, get override value and set the new value back to method + Method[] methods = getClass().getMethods(); //ServiceBean + for (Method method : methods) { + // 是不是setXX()方法 + if (MethodUtils.isSetter(method)) { + // 获取xx配置项的value + String value = StringUtils.trim(compositeConfiguration.getString(extractPropertyName(getClass(), method))); + // isTypeMatch() is called to avoid duplicate and incorrect update, for example, we have two 'setGeneric' methods in ReferenceConfig. + if (StringUtils.isNotEmpty(value) && ClassUtils.isTypeMatch(method.getParameterTypes()[0], value)) { + method.invoke(this, ClassUtils.convertPrimitive(method.getParameterTypes()[0], value)); + } + // 是不是setParameters()方法 + } else if (isParametersSetter(method)) { + // 获取parameter配置项的value + String value = StringUtils.trim(compositeConfiguration.getString(extractPropertyName(getClass(), method))); + if (StringUtils.isNotEmpty(value)) { + Map map = invokeGetParameters(getClass(), this); + map = map == null ? new HashMap<>() : map; + map.putAll(convert(StringUtils.parseParameters(value), "")); + invokeSetParameters(getClass(), this, map); + } + } + } + } catch (Exception e) { + logger.error("Failed to override ", e); + } + } +``` + +##### Environment + +```java + public CompositeConfiguration getConfiguration(String prefix, String id) { + CompositeConfiguration compositeConfiguration = new CompositeConfiguration(); + // Config center has the highest priority + + // JVM环境变量 + compositeConfiguration.addConfiguration(this.getSystemConfig(prefix, id)); + // 操作系统环境变量 + compositeConfiguration.addConfiguration(this.getEnvironmentConfig(prefix, id)); + + // 配置中心APP配置 + compositeConfiguration.addConfiguration(this.getAppExternalConfig(prefix, id)); + + // 配置中心Global配置 + compositeConfiguration.addConfiguration(this.getExternalConfig(prefix, id)); + + // dubbo.properties中的配置 + compositeConfiguration.addConfiguration(this.getPropertiesConfig(prefix, id)); + return compositeConfiguration; + } +``` + +#### 确定服务支持的协议 + +确定服务所支持的协议还是比较简单的,就是看用户配了多少个Protocol。和服务参数意义,Protocol也是可以在各个配置点进行配置的。 + +1. 首先在SpringBoot的application.properties文件中就可能配置了协议 +2. 也可能在dubbo.properties文件中配置了协议 +3. 也可能在配置中心中也配置了协议 +4. 也可能通过-D的方式也配置了协议 + + + +所以在服务导出时,需要从以上几个地方获取协议,结果可能是一个协议,也可能是多个协议,从而确定出协议。 + +#### URL作用 + +1. 资源 + + 1. 注册中心URL:zookeeper://ip+port?dynamic=true + 2. 服务:dubbo://ip+port/接口名?timeout=3000 + 3. 服务:http://ip+port/接口名?timeout=3000 + +2. 方便扩展 + + + +#### 构造服务最终的URL + + + +有了确定的协议,服务名,服务参数后,自然就可以组装成服务的URL了。 + + + +但是还有一点是非常重要的,在Dubbo中支持服务动态配置,注意,这个和配置中心不是同一概念,动态配置是可以在服务导出后动态的去修改服务配置的,而配置中心则不能达到这一的效果(这个我要在确定一下)。 + + + +动态配置,其实就是继续给服务增加了一些参数,所以在把服务的URL注册到注册中心去之前,得先按照动态配置中所添加的配置重写一下URL,也就是应用上动态配置中的参数。 + + + +只有这样作完之后得到的URL才是**真正准确**的服务提供者URL。 + + + +### 开始服务注册相关过程 + +> 1. 根据服务支持的不同协议,启动不同的Server,用来接收和处理请求 +> 2. 将服务URL注册到注册中心去 +> 3. 因为Dubbo支持动态配置服务参数,所以服务导出时还需要绑定一个监听器Listener来监听服务的参数是否有修改,如果发现有修改,则需要重新进行导出 +> +> 这三个步骤都会在`ServiceConfig#export()#doExport()` 这个方法里做,流程比较复杂,就直接看代码吧 + + + +#### 公用源码 + +> 这个部分的源码是前面三个步骤公用的 + +##### ServiceConfig + +```java + public synchronized void export() { + //读取服务配置 + checkAndUpdateSubConfigs(); + + // 检查服务是否需要导出,@Service里可以配置 + if (!shouldExport()) { + return; + } + + // 检查是否需要延迟发布,@Service里可以配置 + if (shouldDelay()) { + DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS); + } else { + // 导出服务 + doExport(); + } + } + + protected synchronized void doExport() { + if (unexported) { + throw new IllegalStateException("The service " + interfaceClass.getName() + " has already unexported!"); + } + // 已经导出了,就不再导出了 + if (exported) { + return; + } + exported = true; + + if (StringUtils.isEmpty(path)) { + path = interfaceName; + } + doExportUrls(); + } + + private void doExportUrls() { + // registryURL 表示一个注册中心 + List registryURLs = loadRegistries(true); + + //配置的每一个protocol都会产生一个dubbo服务,所以这里是循环配置的协议, + // 我们这里假设配置了dubbo,但是配了两个端口,这样也算两个protocol + for (ProtocolConfig protocolConfig : protocols) { + + // pathKey = group/contextpath/path:version + // 例子:myGroup/user/org.apache.dubbo.demo.DemoService:1.0.1 + String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version); + + // ProviderModel中存在服务提供者访问路径,实现类,接口,以及接口中的各个方法对应的ProviderMethodModel + // ProviderMethodModel表示某一个方法,方法名,所属的服务的, + ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass); + + // ApplicationModel表示应用中有哪些服务提供者和引用了哪些服务 + ApplicationModel.initProviderModel(pathKey, providerModel); + + // 重点,每一个协议都会注册一个服务 + doExportUrlsFor1Protocol(protocolConfig, registryURLs); + } + } + + + private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List registryURLs) { + // protocolConfig表示某个协议,registryURLs表示所有的注册中心 + + // 如果配置的某个协议,没有配置name,那么默认为dubbo + String name = protocolConfig.getName(); + if (StringUtils.isEmpty(name)) { + name = DUBBO; + } + + // 这个map表示服务url的参数 + Map map = new HashMap(); + map.put(SIDE_KEY, PROVIDER_SIDE); + + appendRuntimeParameters(map); + + // 监控中心参数 + appendParameters(map, metrics); + // 应用相关参数 + appendParameters(map, application); + // 模块相关参数 + appendParameters(map, module); + // remove 'default.' prefix for configs from ProviderConfig + // appendParameters(map, provider, Constants.DEFAULT_KEY); + + // 提供者相关参数 + appendParameters(map, provider); + + // 协议相关参数 + appendParameters(map, protocolConfig); + + // 服务本身相关参数 + appendParameters(map, this); + + // 服务中某些方法参数,@Service里可以针对某些方法配置某些参数 + if (CollectionUtils.isNotEmpty(methods)) { + for (MethodConfig method : methods) { + // 某个方法的配置参数,注意有prefix + appendParameters(map, method, method.getName()); + String retryKey = method.getName() + ".retry"; + + // 如果某个方法配置存在xx.retry=false,则改成xx.retry=0 + if (map.containsKey(retryKey)) { + String retryValue = map.remove(retryKey); + if (Boolean.FALSE.toString().equals(retryValue)) { + map.put(method.getName() + ".retries", "0"); + } + } + List arguments = method.getArguments(); + if (CollectionUtils.isNotEmpty(arguments)) { + // 遍历当前方法配置中的参数配置 + for (ArgumentConfig argument : arguments) { + + // 如果配置了type,则遍历当前接口的所有方法,然后找到方法名和当前方法名相等的方法,可能存在多个 + // 如果配置了index,则看index对应位置的参数类型是否等于type,如果相等,则向map中存入argument对象中的参数 + // 如果没有配置index,那么则遍历方法所有的参数类型,等于type则向map中存入argument对象中的参数 + // 如果没有配置type,但配置了index,则把对应位置的argument放入map + // convert argument type + if (argument.getType() != null && argument.getType().length() > 0) { + Method[] methods = interfaceClass.getMethods(); + // visit all methods + if (methods != null && methods.length > 0) { + for (int i = 0; i < methods.length; i++) { + String methodName = methods[i].getName(); + // target the method, and get its signature + if (methodName.equals(method.getName())) { + Class[] argtypes = methods[i].getParameterTypes(); + // one callback in the method + if (argument.getIndex() != -1) { + if (argtypes[argument.getIndex()].getName().equals(argument.getType())) { + appendParameters(map, argument, method.getName() + "." + argument.getIndex()); + } else { + throw new IllegalArgumentException("Argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType()); + } + } else { + // multiple callbacks in the method + for (int j = 0; j < argtypes.length; j++) { + Class argclazz = argtypes[j]; + if (argclazz.getName().equals(argument.getType())) { + appendParameters(map, argument, method.getName() + "." + j); + if (argument.getIndex() != -1 && argument.getIndex() != j) { + throw new IllegalArgumentException("Argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType()); + } + } + } + } + } + } + } + } else if (argument.getIndex() != -1) { + appendParameters(map, argument, method.getName() + "." + argument.getIndex()); + } else { + throw new IllegalArgumentException("Argument config must set index or type attribute.eg: or "); + } + + } + } + } // end of methods for + } + + if (ProtocolUtils.isGeneric(generic)) { + map.put(GENERIC_KEY, generic); + map.put(METHODS_KEY, ANY_VALUE); + } else { + String revision = Version.getVersion(interfaceClass, version); + if (revision != null && revision.length() > 0) { + map.put(REVISION_KEY, revision); + } + + // 通过接口对应的Wrapper,拿到接口中所有的方法名字 + String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames(); + if (methods.length == 0) { + logger.warn("No method found in service interface " + interfaceClass.getName()); + map.put(METHODS_KEY, ANY_VALUE); + } else { + map.put(METHODS_KEY, StringUtils.join(new HashSet(Arrays.asList(methods)), ",")); + } + } + + // Token是为了防止服务被消费者直接调用(伪造http请求),可以在@Service里配置 + // 这里防止的是某些消费者不是从注册中心拿到的URL调用提供者,而是消费者自己拼出的URL进行调用 + // 服务调用的是会有个Tokenfilter过滤器进行拦截(后面讲) + if (!ConfigUtils.isEmpty(token)) { + if (ConfigUtils.isDefault(token)) { + map.put(TOKEN_KEY, UUID.randomUUID().toString()); + } else { + map.put(TOKEN_KEY, token); + } + } + + // export service + // 通过该host和port访问该服务 + String host = this.findConfigedHosts(protocolConfig, registryURLs, map); + Integer port = this.findConfigedPorts(protocolConfig, name, map); + // 服务url + URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map); + + /** + * url:http://192.168.40.17:80/org.apache.dubbo.demo.DemoService?anyhost=true&application= + * dubbo-demo-annotation-provider&bean.name=ServiceBean:org.apache.dubbo.demo.DemoService + * &bind.ip=192.168.40.17&bind.port=80&deprecated=false&dubbo=2.0.2&dynamic=true& + * generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&pid=285072 + * &release=&side=provider×tamp=1585206500409 + * + * 1.可以通过ConfiguratorFactory,对服务url再次进行配置 + * 2.意思就是可以自己实现一个ConfiguratorFactory的实现类,实现对应方法对URL进行自定义修改 + * 3.这个实现类是通过SPI进行加载的 + */ + if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class) + .hasExtension(url.getProtocol())) { + url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class) + .getExtension(url.getProtocol()).getConfigurator(url).configure(url); + } + + String scope = url.getParameter(SCOPE_KEY); // scope可能为null,remote, local,none + // don't export when none is configured + if (!SCOPE_NONE.equalsIgnoreCase(scope)) { + // 如果scope为none,则不会进行任何的服务导出,既不会远程,也不会本地 + + // export to local if the config is not remote (export to remote only when config is remote) + if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) { + // 如果scope不是remote,则会进行本地导出,会把当前url的protocol改为injvm,然后进行导出 + // 这样的话就只有本地的JVM才能调用 + exportLocal(url); + } + // export to remote if the config is not local (export to local only when config is local) + if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) { + // 如果scope不是local,则会进行远程导出 + + if (CollectionUtils.isNotEmpty(registryURLs)) { + // 如果有注册中心,则将服务注册到注册中心 + for (URL registryURL : registryURLs) { + + //if protocol is only injvm ,not register + // 如果是injvm,则不需要进行注册中心注册 + if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) { + continue; + } + + // 该服务是否是动态,对应zookeeper上表示是否是临时节点,对应dubbo中的功能就是静态服务 + url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY)); + + // 拿到监控中心地址 + URL monitorUrl = loadMonitor(registryURL); + + // 当前服务连接哪个监控中心 + if (monitorUrl != null) { + url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString()); + } + + // 服务的register参数,如果为true,则表示要注册到注册中心 + if (logger.isInfoEnabled()) { + if (url.getParameter(REGISTER_KEY, true)) { + logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL); + } else { + logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url); + } + } + + // For providers, this is used to enable custom proxy to generate invoker + // 服务使用的动态代理机制,如果为空则使用javassit + String proxy = url.getParameter(PROXY_KEY); + if (StringUtils.isNotEmpty(proxy)) { + registryURL = registryURL.addParameter(PROXY_KEY, proxy); + } + + + /** + * 1.生成一个当前服务接口的代理对象 + * 2.使用代理生成一个Invoker,Invoker表示服务提供者的代理,可以使用Invoker的invoke方法执行服务 + * 就是把注册中心的URL和服务的URL拼起来,registryURL + "export" + url,对应的url为: + * + registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService? + application=dubbo-demo-annotation-provider&dubbo=2.0.2&export= + http://192.168.40.17:80/org.apache.dubbo.demo.DemoService? + anyhost=true&application=dubbo-demo-annotation-provider&bean.name= + ServiceBean:org.apache.dubbo.demo.DemoService&bind.ip=192.168.40.17& + bind.port=80&deprecated=false&dubbo=2.0.2&dynamic=true&generic= + false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello& + pid=19472&release=&side=provider×tamp=1585207994860&pid=19472& + registry=zookeeper×tamp=1585207994828 + * + * 3.这个Invoker中包括了服务的实现者、服务接口类、服务的注册地址(针对当前服务的,参数export + 指定了当前服务) + * 4.此invoker表示一个可执行的服务,调用invoker的invoke()方法即可执行服务,同时此invoker也可用来导出 + * 在服务导出(注册)的时候,invoker只是存在某一个地方,等消费者调用服务的时候才会执行 + * 5.ref就是之前讲过的服务具体实现类 + * 6.这里第二个参数传的是URL(具体就是registryURL),后面exporter马上会用 + */ + Invoker invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString())); + // invoker.invoke(Invocation) + + // DelegateProviderMetaDataInvoker也表示服务提供者,包括了Invoker和服务的配置 + //把this(也就是serviceconfig服务参数)和invoker 服务实现类等 再包装一下 + DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); + + /** + * 使用特定的协议来对服务进行导出,这里的协议为RegistryProtocol,导出成功后得到一个Exporter + * 1.exporter导出器怎么确定用哪个实现类的export方法呢?SPI机制会判断哪个invoker里面有getURL这个方法 + * 【这里不知道怎么调哪个类的哪个方法的请看前面讲的SPI】 + * 2.因为前面invoker传的是registryURL,所以我们这里就会使用RegistryProtocol进行服务注册 + * registryURL可以理解为注册中心的注册协议吧,debug这里,就会看到是这样的registry://127.0.0.1:2181...... + * 3.注册完了之后,使用DubboProtocol进行导出 + * 4.到此为止做了哪些事情? ServiceBean.export()-->刷新ServiceBean的参数-->得到注册中心URL和协议URL--> + * 遍历每个协议URL-->组成服务URL-->生成可执行服务Invoker-->导出服务 + * 5.这里就会调用RegistryProtocol#export(Invoker) + */ + Exporter exporter = protocol.export(wrapperInvoker); + exporters.add(exporter); + } + } else { + // 没有配置注册中心时,也会导出服务 + + if (logger.isInfoEnabled()) { + logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url); + } + + + Invoker invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url); + DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); + + Exporter exporter = protocol.export(wrapperInvoker); + exporters.add(exporter); + } + + + /** + * @since 2.7.0 + * ServiceData Store + */ + // 根据服务url,讲服务的元信息存入元数据中心 + MetadataReportService metadataReportService = null; + if ((metadataReportService = getMetadataReportService()) != null) { + metadataReportService.publishProvider(url); + } + } + } + this.urls.add(url); + } +``` + + + +#### 启动Netty,Tomcat等Server源码 + +##### RegistryProtocol + +```java + @SuppressWarnings("unchecked") + private ExporterChangeableWrapper doLocalExport(final Invoker originInvoker, URL providerUrl) { + String key = getCacheKey(originInvoker); + + return (ExporterChangeableWrapper) bounds.computeIfAbsent(key, s -> { + Invoker invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl); + /** + * 1.这里又是SPI的知识。protocol属性的值是哪来的,是在SPI中注入进来的,是一个代理类 + * 2.InvokerDelegate的父类InvokerWrapper有getURL方法,所以最终SPI决定调哪个扩展点 + * 是通过providerUrl决定的,而providerUrl这里基本就是DubboProtocol或HttpProtocol去export + * 3.我们这里用的是dubbo协议,所以会调用DubboProtocol + * 4.为什么需要ExporterChangeableWrapper?方便注销已经被导出的服务 + */ + return new ExporterChangeableWrapper<>((Exporter) protocol.export(invokerDelegate), originInvoker); + }); + } +``` + + + +##### DubboProtocol + +```java + public Exporter export(Invoker invoker) throws RpcException { + URL url = invoker.getUrl(); + + // 唯一标识一个服务的key + String key = serviceKey(url); + // 构造一个Exporter进行服务导出 + DubboExporter exporter = new DubboExporter(invoker, key, exporterMap); + exporterMap.put(key, exporter); + + //export an stub service for dispatching event + Boolean isStubSupportEvent = url.getParameter(STUB_EVENT_KEY, DEFAULT_STUB_EVENT); + Boolean isCallbackservice = url.getParameter(IS_CALLBACK_SERVICE, false); + if (isStubSupportEvent && !isCallbackservice) { + String stubServiceMethods = url.getParameter(STUB_EVENT_METHODS_KEY); + if (stubServiceMethods == null || stubServiceMethods.length() == 0) { + if (logger.isWarnEnabled()) { + logger.warn(new IllegalStateException("consumer [" + url.getParameter(INTERFACE_KEY) + + "], has set stubproxy support event ,but no stub methods founded.")); + } + + } else { + // 服务的stub方法 + stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods); + } + } + + // 开启NettyServer + // 请求--->invocation--->服务key--->exporterMap.get(key)--->exporter--->invoker--->invoker.invoke(invocation)-->执行服务 + openServer(url); + + // 特殊的一些序列化机制,比如kryo提供了注册机制来注册类,提高序列化和反序列化的速度 + optimizeSerialization(url); + + return exporter; + } + + + private void openServer(URL url) { + // find server. + String key = url.getAddress(); // 获得ip地址和port, 192.168.40.17:20880 + + // NettyClient, NettyServer + //client can export a service which's only for server to invoke + boolean isServer = url.getParameter(IS_SERVER_KEY, true); + if (isServer) { + // 缓存Server对象 + ExchangeServer server = serverMap.get(key); + + // DCL,Double Check Lock + if (server == null) { + synchronized (this) { + server = serverMap.get(key); + if (server == null) { + // 创建Server,并进行缓存 + serverMap.put(key, createServer(url)); + } + } + } else { + // server supports reset, use together with override + // 服务重新导出时,就会走这里 这里会调用HeaderExchangeServer#reset + server.reset(url); + } + } + } +``` + +##### AbstractProtocol + +```java + protected static String serviceKey(URL url) { + int port = url.getParameter(Constants.BIND_PORT_KEY, url.getPort()); + // path就是@Service注解配的,path配了就用,不配就不用。这四个参数作用就是生成唯一标识一个服务的key + // 从这里就可以看出,协议相同的只要端口号不一样,依然算不同的服务 + return serviceKey(port, url.getPath(), url.getParameter(VERSION_KEY), url.getParameter(GROUP_KEY)); + } +``` + + + +##### 启动Server总结 + +> 这里不是dubbo的核心,就不贴源码了 + +在服务URL中指定了协议,比如Http协议、Dubbo协议。根据不同的协议启动对应的Server。 + +比如Http协议就启动Tomcat、Jetty。 + +比如Dubbo协议就启动Netty。 + + + +不能只启动Server,还需要绑定一个RequestHandler,用来处理请求。 + +比如,Http协议对应的就是InternalHandler。Dubbo协议对应的就是ExchangeHandler。 + + + +这里来详细分析一下Dubbo协议所启动的Server。 + + + +1. 调用DubboProtocol的openServer(URL url)方法开启启动Server +2. 调用DubboProtocol的createServer(url)方法,在createServer()方法中调用**Exchangers.bind(url, requestHandler)**得到一个ExchangeServer +3. 其中requestHandler表示请求处理器,用来处理请求 +4. 在**Exchangers.bind(url, requestHandler)**中,先会根据URL得到一个Exchanger,默认为HeaderExchanger +5. HeaderExchanger中包括HeaderExchangeClient、HeaderExchangeServer +6. HeaderExchangeClient负责发送心跳,HeaderExchangeServer负责接收心跳,如果超时则会关闭channel +7. 在构造HeaderExchangeServer之前,会通过调用Transporters._bind_(url, **new** DecodeHandler(**new** HeaderExchangeHandler(handler)))方法的到一个Server +8. 默认会使用getTransporter去bind(URL url, ChannelHandler listener)从而得到一个Servlet,此时的listener就是外部传进来的DecodeHandler +9. 在NettyTransporter的bind方法中会去**new** NettyServer(url, listener),所以上面返回的Server默认就是NettyServer +10. 在构造NettyServer时,会调用ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER\_THREAD\_POOL_NAME))再构造一个ChannelHandler。 +11. wrap中的handler就是上面的listener +12. 在wrap方法中会调用new MultiMessageHandler(new HeartbeatHandler(ExtensionLoader.getExtensionLoader(Dispatcher.class).getAdaptiveExtension().dispatch(handler, url)));构造一个ChannelHandler。 +13. 构造完ChannelHandler后,就是真正的去开启Server了,会调用AbstractServer抽象类的doOpen方法。 +14. 在NettyServer中,会实现doOpen方法,会调用**new** NettyServerHandler(getUrl(), **this**)构造一个NettyServerHandler,并bind地址 +15. 至此,DubboProtocol协议的启动Server流程就结束。 + + + + + + + + +1. NettyServerHandler:与NettyServer直接绑定的请求处理器,负责从Netty接收到请求,channelRead()方法获取到请求,然后调用下一层的Handler(NettyServer)的received()方法将请求传递下去,此时的请求还是Object msg +2. NettyServer:NettyServer的父类AbstractPeer中存在received(),该方法没有做什么,直接把msg传递给下一层Handler(MultiMessageHandler) +3. MultiMessageHandler:此Handler会判断msg是否是一个MultiMessage,如果是,则对MultiMessage进行拆分,则把拆分出来的msg传递给下层Handler(HeartbeatHandler),如果不是,则直接把msg传递给下层Handler(HeartbeatHandler) +4. HeartbeatHandler:此Handler通过received()方法接收到msg,然后判断该msg是不是一个心跳请求或心跳响应,如果是心跳请求,则此Handler返回一个Response对象(很简单的一个对象),如果是心跳响应,则打印一个日志,不会有其他逻辑,如果都不是,则把msg传递给下层Handler(AllChannelHandler)。 +5. AllChannelHandler:此Handler通过received()方法接收到msg,然后把msg封装为一个ChannelEventRunnable对象,并把ChannelEventRunnable扔到线程池中去,异步去处理该msg。在ChannelEventRunnable中会把msg交给下一个Handler(DecodeHandler) +6. DecodeHandler:此Handler通过received()方法接收到msg,会对msg解析decode解码,然后交给下一个Handler(HeaderExchangeHandler) +7. HeaderExchangeHandler:此Handler通过received()方法接收到msg,会判断msg的类型 + + 1. 如果Request是TwoWay,则会调用下一个Handler(DubboProtocol中的**requestHandler**)的reply方法得到一个结果,然后返回 + 2. 如果Request不是TwoWay,则会调用下一个Handler(DubboProtocol中的**requestHandler**)的received方法处理该请求,不会返回结果 + +8. requestHandler:此Handler是真正的处理请求逻辑,在received()方法中,如果msg是Invocation,则会调用reply方法,但不会返回reply方法所返回的结果,在reply方法中把msg强制转换为Invocation类型 inv,然后根据inv得到对应的服务Invoker,然后调用invoke(inv)方法,得到结果。 + +#### 服务注册源码 + +##### RegistryProtocol + +```java + public void register(URL registryUrl, URL registeredProviderUrl) { + // 这里最终也是通过SPI机制,判断传过来的是什么,我们这里在前面把registry转成了zookeeper的URL + Registry registry = registryFactory.getRegistry(registryUrl); + // 所以这里会调用ZookeeperRegistry的register方法,实际上是先调用ZookeeperRegistry的父类FailbackRegistry + registry.register(registeredProviderUrl); + } +``` + + + + + + + +##### FailbackRegistry.java + +```java + public void register(URL url) { + super.register(url); + removeFailedRegistered(url); + removeFailedUnregistered(url); + try { + // 然后在这里调用ZookeeperRegistry#doRegister,这个URL参数很明显又是一个SPI的提现 + doRegister(url); + } catch (Exception e) { + Throwable t = e; + + // If the startup detection is opened, the Exception is thrown directly. + boolean check = getUrl().getParameter(Constants.CHECK_KEY, true) + && url.getParameter(Constants.CHECK_KEY, true) + && !CONSUMER_PROTOCOL.equals(url.getProtocol()); + boolean skipFailback = t instanceof SkipFailbackWrapperException; + if (check || skipFailback) { + if (skipFailback) { + t = t.getCause(); + } + throw new IllegalStateException("Failed to register " + url + " to registry " + getUrl().getAddress() + ", cause: " + t.getMessage(), t); + } else { + logger.error("Failed to register " + url + ", waiting for retry, cause: " + t.getMessage(), t); + } + + // Record a failed registration request to a failed list, retry regularly + addFailedRegistered(url); + } + } +``` + +##### ZookeeperRegistry + +```java + @Override + public void doRegister(URL url) { + try { + zkClient.create(toUrlPath(url), url.getParameter(DYNAMIC_KEY, true)); + } catch (Throwable e) { + throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e); + } + } +``` + + + +### 服务监听器原理 + +> 动态配置不能改端口 + +#### 服务监听器原理总结 + +1. 服务在导出的过程中需要向动态配置中心的数据进行订阅,以便当管理人员修改了动态配置中心中对应服务的参数后,服务提供者能及时做出变化。此功能涉及到版本兼容,因为在Dubbo2.7之前也存在此功能,Dubbo2.7开始对此功能进行了调整。 + +2. 在Dubbo2.7之前,仅支持多某个服务的动态配置 +3. 在Dubbo2.7之后,不仅支持对单个服务的动态配置,也支持对某个应用的动态配置(相当于对这个应用下的所有服务生效) +4. 为了达到这个功能,需要利用Zookeeper的Watcher机制,所以对于服务提供者而言,我到底监听哪个Zookeeper节点的数据变化呢? +5. 这个节点是有规则的,并且在Dubbo2.7前后也不一样: + 1. Dubbo2.7之前:监听的zk路径是:`/dubbo/org.apache.dubbo.demo.DemoService/configurators/override://0.0.0.0/org.apache.dubbo.demo.DemoService?category=configurators&compatible_config=true&dynamic=false&enabled=true&timeout=6000`注意,注意监听的是节点名字的变化,而不是节点内容 + 2. Dubbo2.7之后,监听的zk路径是: + 1. 服务: `/dubbo/config/dubbo/org.apache.dubbo.demo.DemoService.configurators`节点的内容 + 2. 应用: `/dubbo/config/dubbo/dubbo-demo-provider-application.configurators`节点的内容 + +6. 注意,要和配置中心的路径区分开来,配置中心的路径是: + 1. 应用:/dubbo/config/dubbo/org.apache.dubbo.demo.DemoService/dubbo.properties节点的内容 + 2. 全局:/dubbo/config/dubbo/dubbo.properties节点的内容 + + +> 所以在一个服务进行导出时,需要在服务提供者端给当前服务生成一个对应的监听器实例,这个监听器实例为OverrideListener,它负责监听对应服务的动态配置变化,并且根据动态配置中心的参数重写服务URL。 + + + +除开有OverrideListener之外,在Dubbo2.7之后增加了另外两个: + +1. ProviderConfigurationListener:监听的是应用的动态配置数据修改,所以它是在RegistryProtocol类中的一个属性,并且是随着RegistryProtocol实例化而实例化好的,一个应用中只有一个 +2. ServiceConfigurationListener:监听的是服务的动态配置数据修改,和OverrideListener类似,也是对应一个服务的,所以在每个服务进行导出时都会生成一个,实际上ServiceConfigurationListener的内部有一个属性就是OverrideListener,所以当ServiceConfigurationListener监听数据发生了变化时,就会把配置中心的最新数据交给OverrideListener去重写服务URL。 +3. 同时在RegistryProtocol类中保存了所有服务所对应的OverrideListener,所以实际上当ProviderConfigurationListener监听到数据发生了变化时,也会把它所得到的最新数据依次调用每个OverrideListener去重写服务对应的服务URL。 +4. ProviderConfigurationListener会监听/dubbo/config/dubbo/dubbo-demo-provider-application.configurators节点 +5. ServiceConfigurationListener会监听/dubbo/config/dubbo/org.apache.dubbo.demo.DemoService.configurators节点 + + + +**整理修改动态配置触发流程:** + +1. 修改服务动态配置,底层会修改Zookeeper中的数据, + + 1. /dubbo/config/dubbo/org.apache.dubbo.demo.DemoService.configurators节点的内容 + +2. ServiceConfigurationListener会监听到节点内容的变化,会触发ServiceConfigurationListener的父类AbstractConfiguratorListener的process(ConfigChangeEvent event)方法 +3. ConfigChangeEvent表示一个事件,事件中有事件类型,还有事件内容(节点内容),还有触发这个事件的节点名字,事件类型有三个: + + 1. ADDED + 2. MODIFIED + 3. DELETED + +4. 当接收到一个ConfigChangeEvent事件后,会根据事件类型做对应的处理 + + 1. ADDED、MODIFIED:会根据节点内容去生成override://协议的URL,然后根据URL去生成Configurator, Configurator对象很重要,表示一个配置器,根据配置器可以去重写URL + 2. DELETED:删除ServiceConfigurationListener内的所有的Configurator + +5. 生成了Configurator后,调用notifyOverrides()方法对服务URL进行重写 +6. 注意,每次重写并不仅仅只是用到上面所生成的Configurator,每次重写要用到所有的Configurator,包括本服务的Configurator,也包括本应用的Configurator,也包括老版本管理台的Configurator,重写URL的逻辑如下: + + 1. 从exporter中获取目前已经导出了的服务URL-currentUrl + 2. 根据老版本管理台的Configurator重写服务URL + 3. 根据providerConfigurationListener中的Configurator重写服务URL + 4. 根据serviceConfigurationListeners中对应的服务的Configurator重写服务URL + 5. 如果重写之后newUrl和currentUrl相等,那么不需要做什么了 + 6. 如果重写之后newUrl和currentUrl不相等,则需要进行**服务重新导出**: + + 1. 根据newUrl进行导出,注意,这里只是就是调用DubboProtocol的export,再次去启动NettyServer + 2. 对newUrl进行简化,简化为registeredProviderUrl + 3. 调用RegistryProtocol的unregister()方法,把当前服务之前的服务提供URL从注册中心删掉 + 4. 调用RegistryProtocol的register()方法,把新的registeredProviderUrl注册到注册中心 + + + +#### 服务监听器绑定源码 + +##### RegistryProtocol + +```java + @Override + public Exporter export(final Invoker originInvoker) throws RpcException { + // 导出服务 + // registry:// ---> RegistryProtocol + // zookeeper:// ---> ZookeeperRegistry + // dubbo:// ---> DubboProtocol + + + /** + * 1.registry://xxx?xx=xx®istry=zookeeper ---> zookeeper://xxx?xx=xx 表示注册中心 + * 这里就是把registry替换成zookeeper + * 2.示例:zookeeper://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application= + * dubbo-demo-provider-application&dubbo=2.0.2&export=dubbo://192.168.40.17:20880/ + * org.apache.dubbo.demo.DemoService?anyhost=true&application= + * dubbo-demo-provider-application&bean.name=ServiceBean:org.apache.dubbo.demo.DemoService + * &bind.ip=192.168.40.17&bind.port=20880&deprecated=false&dubbo=2.0.2& + * dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService& + * logger=log4j&methods=sayHello&pid=27656&release=2.7.0&side=provider&timeout=3000& + * timestamp=1590735956489&logger=log4j&pid=27656&release=2.7.0×tamp=1590735956479 + */ + URL registryUrl = getRegistryUrl(originInvoker); + // 得到服务提供者url,表示服务提供者 + /** + * 1.这里就是把之前export后面拼的dubbo服务url拿出来 + * 2.示例:dubbo://192.168.40.17:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application= + * dubbo-demo-provider-application&bean.name=ServiceBean:org.apache.dubbo.demo.DemoService& + * bind.ip=192.168.40.17&bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true& + * generic=false&interface=org.apache.dubbo.demo.DemoService&logger=log4j&methods=sayHello + * &pid=27656&release=2.7.0&side=provider&timeout=3000×tamp=1590735956489 + * 3.服务导出最终的目的就是要把providerUrl存到注册中心上,只不过中间有一些其他操作 + */ + URL providerUrl = getProviderUrl(originInvoker); + + // Subscribe the override data + // FIXME When the provider subscribes, it will affect the scene : a certain JVM exposes the service and call + // the same service. Because the subscribed is cached key with the name of the service, it causes the + // subscription information to cover. + + // overrideSubscribeUrl是老版本的动态配置监听url,表示了需要监听的服务以及监听的类型(configurators, 这是老版本上的动态配置) + // 在服务提供者url的基础上,生成一个overrideSubscribeUrl,协议为provider://,增加参数category=configurators&check=false + final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl); + + // 一个overrideSubscribeUrl对应一个OverrideListener,用来监听变化事件,监听到overrideSubscribeUrl的变化后, + // OverrideListener就会根据变化进行相应处理,具体处理逻辑看OverrideListener的实现 + final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker); + overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener); + + /** + * 在这个方法里会利用providerConfigurationListener和serviceConfigurationListener去重写providerUrl + * providerConfigurationListener表示应用级别的动态配置监听器,providerConfigurationListener是RegistyProtocol的一个属性 + * serviceConfigurationListener表示服务级别的动态配置监听器,serviceConfigurationListener是在每暴露一个服务时就会生成一个 + * 这两个监听器都是新版本中的监听器 + * 新版本监听的zk路径是: + * 服务: /dubbo/config/dubbo/org.apache.dubbo.demo.DemoService.configurators节点的内容 + * 应用: /dubbo/config/dubbo/dubbo-demo-provider-application.configurators节点的内容 + * 注意,要和配置中心的路径区分开来,配置中心的路径是: + * 应用:/dubbo/config/dubbo/org.apache.dubbo.demo.DemoService/dubbo.properties节点的内容 + * 全局:/dubbo/config/dubbo/dubbo.properties节点的内容 + */ + providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener); + + // export invoker + // 根据动态配置重写了providerUrl之后,就会调用DubboProtocol或HttpProtocol去进行导出服务了, + // 这里会启动netty,启动tomcat这些 + final ExporterChangeableWrapper exporter = doLocalExport(originInvoker, providerUrl); + + // url to registry + // 得到注册中心-ZookeeperRegistry + final Registry registry = getRegistry(originInvoker); + + // 得到存入到注册中心去的providerUrl,会对服务提供者url中的参数进行简化, + // 因为有些参数存到注册中心是没有用的 + final URL registeredProviderUrl = getRegisteredProviderUrl(providerUrl, registryUrl); + + // 将当前服务提供者Invoker,以及该服务对应的注册中心地址,以及简化后的服务url存入ProviderConsumerRegTable + ProviderInvokerWrapper providerInvokerWrapper = ProviderConsumerRegTable.registerProvider(originInvoker, + registryUrl, registeredProviderUrl); + + + //to judge if we need to delay publish + //是否需要注册到注册中心 + boolean register = providerUrl.getParameter(REGISTER_KEY, true); + if (register) { + // 注册服务,把简化后的服务提供者url注册到registryUrl中去 + register(registryUrl, registeredProviderUrl); + providerInvokerWrapper.setReg(true); + } + /** + * 针对老版本的动态配置,需要把overrideSubscribeListener绑定到overrideSubscribeUrl上去进行监听 + * 兼容老版本的配置修改,利用overrideSubscribeListener去监听旧版本的动态配置变化 + * 监听overrideSubscribeUrl provider://192.168.40.17:20880/org.apache.dubbo.demo.DemoService?anyhost=true& + * application=dubbo-demo-annotation-provider&bean.name=ServiceBean:org.apache.dubbo.demo.DemoService& + * bind.ip=192.168.40.17&bind.port=20880&category=configurators&check=false&deprecated=false&dubbo=2.0.2& + * dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&pid=416332& + * release=&side=provider×tamp=1585318241955 + * 那么新版本的providerConfigurationListener和serviceConfigurationListener是在什么时候进行订阅的呢?在这两个类构造的时候 + * Deprecated! Subscribe to override rules in 2.6.x or before. + * 老版本监听的zk路径是:/dubbo/org.apache.dubbo.demo.DemoService/configurators/override://0.0.0.0/org.apache.dubbo.demo.DemoService?category=configurators&compatible_config=true&dynamic=false&enabled=true&timeout=6000 + * 监听的是路径的内容,不是节点的内容 + */ + registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener); + + + exporter.setRegisterUrl(registeredProviderUrl); + exporter.setSubscribeUrl(overrideSubscribeUrl); + //Ensure that a new exporter instance is returned every time export + return new DestroyableExporter<>(exporter); + } + + private URL overrideUrlWithConfig(URL providerUrl, OverrideListener listener) { + + /** + * 1.应用配置,providerConfigurationListener是在属性那里直接初始化好的, + * providerConfigurationListener会监听配置中心的应用配置信息变动 + * 这个是每一个应用只有一个providerConfigurationListener + * 2.首先这里流程是: + * 1.ProviderConfigurationListener通过构造函数调用父类AbstractConfiguratorListener + * #initWith方法 + * 2.在initWith方法中通过传进来的路径key,监听注册中心(常用的是zookeeper) + * key路径下的节点,会先从注册中心拿到当前配置然后转换成configurators + * 3.接着这里调用overrideUrl,用前面的configurators生成新的providerUrl + * 4.这里因为之前的providerUrl是经过@Service注解,配置中心文件(yml或properties) + * 还有-D这种启动参数里的配置,组合成的一个URL。但是这个providerUrl还没有经过 + * 网页端的动态配置,所以这里需要重写下URL + * 5.ServiceConfigurationListener同理,而且ServiceConfigurationListener代码顺序在后面 + * 所以很明显'服务配置'会覆盖'应用配置' + */ + providerUrl = providerConfigurationListener.overrideUrl(providerUrl); + + // 服务配置,new ServiceConfigurationListener的时候回初始化,ServiceConfigurationListener会监听配置中心的服务信息配置信息变动 + // 这个是每个服务都会重新new一个ServiceConfigurationListener + ServiceConfigurationListener serviceConfigurationListener = new ServiceConfigurationListener(providerUrl, listener); + serviceConfigurationListeners.put(providerUrl.getServiceKey(), serviceConfigurationListener); + return serviceConfigurationListener.overrideUrl(providerUrl); + } + + //RegistryProtocol内部类 + public ProviderConfigurationListener() { + //订阅 应用名+".configurators" 这里就是新版本ProviderConfigurationListener的监听路径 + this.initWith(ApplicationModel.getApplication() + CONFIGURATORS_SUFFIX); + } + +``` + +##### AbstractConfiguratorListener + +```java + // 在构造ProviderConfigurationListener和ServiceConfigurationListener都会调用到这个方法 + // 完成Listener自身订阅到对应的应用和服务 + // 订阅关系绑定完了之后,主动从动态配置中心获取一下对应的配置数据生成configurators,后面需要重写providerUrl + protected final void initWith(String key) { + //这里拿到的就是注册中心,我们大部分情况用的是zookeeper + DynamicConfiguration dynamicConfiguration = DynamicConfiguration.getDynamicConfiguration(); + // 添加Listener,进行了订阅 + dynamicConfiguration.addListener(key, this); + + // 从配置中心ConfigCenter获取属于当前应用的动态配置数据,从zk中拿到原始数据(主动从配置中心获取数据) + String rawConfig = dynamicConfiguration.getRule(key, DynamicConfiguration.DEFAULT_GROUP); + // 如果存在应用配置信息则根据配置信息生成Configurator + if (!StringUtils.isEmpty(rawConfig)) { + genConfiguratorsFromRawRule(rawConfig); + } + + } + + private boolean genConfiguratorsFromRawRule(String rawConfig) { + boolean parseSuccess = true; + try { + // parseConfigurators will recognize app/service config automatically. + // 先把应用或服务配置转成url,再根据url生成对应的Configurator + configurators = Configurator.toConfigurators(ConfigParser.parseConfigurators(rawConfig)) + .orElse(configurators); + } catch (Exception e) { + logger.error("Failed to parse raw dynamic config and it will not take effect, the raw config is: " + + rawConfig, e); + parseSuccess = false; + } + return parseSuccess; + } +``` + + + + + +#### 服务监听器监听源码 + +##### RegistryProtocol + +```java + private class ProviderConfigurationListener extends AbstractConfiguratorListener { + + public ProviderConfigurationListener() { + //订阅 应用名+".configurators" 这里就是新版本ProviderConfigurationListener的监听路径 + this.initWith(ApplicationModel.getApplication() + CONFIGURATORS_SUFFIX); + } + + /** + * Get existing configuration rule and override provider url before exporting. + * + * @param providerUrl + * @param + * @return + */ + private URL overrideUrl(URL providerUrl) { + // 通过configurators去修改/装配providerUrl + return RegistryProtocol.getConfigedInvokerUrl(configurators, providerUrl); + } + + + @Override + protected void notifyOverrides() { + overrideListeners.values().forEach(listener -> ((OverrideListener) listener).doOverrideIfNecessary()); + } + } + + private class ServiceConfigurationListener extends AbstractConfiguratorListener { + private URL providerUrl; + private OverrideListener notifyListener; + + public ServiceConfigurationListener(URL providerUrl, OverrideListener notifyListener) { + this.providerUrl = providerUrl; + this.notifyListener = notifyListener; + // 订阅 服务接口名+group+version+".configurators" + this.initWith(DynamicConfiguration.getRuleKey(providerUrl) + CONFIGURATORS_SUFFIX); + } + + private URL overrideUrl(URL providerUrl) { + return RegistryProtocol.getConfigedInvokerUrl(configurators, providerUrl); + } + + //这里是监听入口 + @Override + protected void notifyOverrides() { + notifyListener.doOverrideIfNecessary(); + } + } + + public synchronized void doOverrideIfNecessary() { + final Invoker invoker; + if (originInvoker instanceof InvokerDelegate) { + invoker = ((InvokerDelegate) originInvoker).getInvoker(); + } else { + invoker = originInvoker; + } + //The origin invoker 当前服务的原始服务提供者url,没有经过任何动态配置改变的URL + URL originUrl = RegistryProtocol.this.getProviderUrl(invoker); + String key = getCacheKey(originInvoker); + + ExporterChangeableWrapper exporter = bounds.get(key); + if (exporter == null) { + logger.warn(new IllegalStateException("error state, exporter should not be null")); + return; + } + + //The current, may have been merged many times,事件触发之前,当前服务被导出的url + URL currentUrl = exporter.getInvoker().getUrl(); + + //根据configurators修改url,configurators是全量的,并不是某个新增的或删除的, + // 所以是基于原始的url进行修改,并不是基于currentUrl,这里是老版本的configurators + //Merged with this configuration + URL newUrl = getConfigedInvokerUrl(configurators, originUrl); + + // 这是新版本的configurators + newUrl = getConfigedInvokerUrl(providerConfigurationListener.getConfigurators(), newUrl); + newUrl = getConfigedInvokerUrl(serviceConfigurationListeners.get(originUrl.getServiceKey()) + .getConfigurators(), newUrl); + + // 修改过的url如果和目前的url不相同,则重新按newUrl导出 + if (!currentUrl.equals(newUrl)) { + RegistryProtocol.this.reExport(originInvoker, newUrl); + logger.info("exported provider url changed, origin url: " + originUrl + + ", old export url: " + currentUrl + ", new export url: " + newUrl); + } + } + + public void reExport(final Invoker originInvoker, URL newInvokerUrl) { + + // 根据newInvokerUrl进行导出 + // update local exporter + ExporterChangeableWrapper exporter = doChangeLocalExport(originInvoker, newInvokerUrl); + + // 获取准确的ProviderUrl + // update registry + URL registryUrl = getRegistryUrl(originInvoker); + // 对于一个服务提供者url,在注册到注册中心时,会先进行简化 + final URL registeredProviderUrl = getRegisteredProviderUrl(newInvokerUrl, registryUrl); + + //decide if we need to re-publish + // 根据getServiceKey获取ProviderInvokerWrapper + ProviderInvokerWrapper providerInvokerWrapper = ProviderConsumerRegTable.getProviderWrapper(registeredProviderUrl, originInvoker); + // 生成一个新的ProviderInvokerWrapper + ProviderInvokerWrapper newProviderInvokerWrapper = ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registeredProviderUrl); + + /** + * Only if the new url going to Registry is different with the previous one should we do unregister and register. + * 如果新的服务提供者url简化后的url和这个服务之前的服务提供者url简化后的url不相等,则需要把新的简化后的服务提供者url注册到注册中心去 + */ + if (providerInvokerWrapper.isReg() && !registeredProviderUrl.equals(providerInvokerWrapper.getProviderUrl())) { + unregister(registryUrl, providerInvokerWrapper.getProviderUrl()); + register(registryUrl, registeredProviderUrl); + newProviderInvokerWrapper.setReg(true); + } + + exporter.setRegisterUrl(registeredProviderUrl); + } + + private ExporterChangeableWrapper doChangeLocalExport(final Invoker originInvoker, URL newInvokerUrl) { + String key = getCacheKey(originInvoker); + final ExporterChangeableWrapper exporter = (ExporterChangeableWrapper) bounds.get(key); + if (exporter == null) { + logger.warn(new IllegalStateException("error state, exporter should not be null")); + } else { + // 到这里才能真正明白,为什么需要InvokerDelegate + // InvokerDelegate表示一个调用者,由invoker+url构成,invoker不变,url可变 + final Invoker invokerDelegate = new InvokerDelegate(originInvoker, newInvokerUrl); + // 这里最后又会走到DubboProtocol#export 那里的逻辑,服务重新导出前面见过了 + exporter.setExporter(protocol.export(invokerDelegate)); + } + return exporter; + } +``` + +Q:这里引出一个问题,配置改变之后Netty,tomcat需要重启吗? + +A:不需要,为什么?前面的DubboProtocol#export 那里的reset逻辑讲过 + + + +#### 服务重新导出源码 + +##### DubboProtocol + +```java + public Exporter export(Invoker invoker) throws RpcException { + // ....省略 + + // 开启NettyServer + // 请求--->invocation--->服务key--->exporterMap.get(key)--->exporter--->invoker--->invoker.invoke(invocation)-->执行服务 + openServer(url); + + // 特殊的一些序列化机制,比如kryo提供了注册机制来注册类,提高序列化和反序列化的速度 + optimizeSerialization(url); + + return exporter; + } + + + private void openServer(URL url) { + // find server. + String key = url.getAddress(); // 获得ip地址和port, 192.168.40.17:20880 + + // NettyClient, NettyServer + //client can export a service which's only for server to invoke + boolean isServer = url.getParameter(IS_SERVER_KEY, true); + if (isServer) { + // 缓存Server对象 + ExchangeServer server = serverMap.get(key); + + // DCL,Double Check Lock + if (server == null) { + synchronized (this) { + server = serverMap.get(key); + if (server == null) { + // 创建Server,并进行缓存 + serverMap.put(key, createServer(url)); + } + } + } else { + // server supports reset, use together with override + // 服务重新导出时,就会走这里 这里会调用HeaderExchangeServer#reset + server.reset(url); + } + } + } +``` + + + +##### HeaderExchangeServer + +```java + //启动netty的时候会调用这个 + public HeaderExchangeServer(Server server) { + Assert.notNull(server, "server == null"); + this.server = server; + // 启动定义关闭Channel(socket)的Task + startIdleCheckTask(getUrl()); + } + + private void startIdleCheckTask(URL url) { + if (!server.canHandleIdle()) { // 底层NettyServer自己有心跳机制,那么上层的ExchangeServer就不用开启心跳任务了 + AbstractTimerTask.ChannelProvider cp = () -> unmodifiableCollection(HeaderExchangeServer.this.getChannels()); + + int idleTimeout = getIdleTimeout(url); + long idleTimeoutTick = calculateLeastDuration(idleTimeout); + + // 定义关闭Channel的Task + CloseTimerTask closeTimerTask = new CloseTimerTask(cp, idleTimeoutTick, idleTimeout); + this.closeTimerTask = closeTimerTask; + + // init task and start timer. + // 定时运行closeTimerTask + IDLE_CHECK_TIMER.newTimeout(closeTimerTask, idleTimeoutTick, TimeUnit.MILLISECONDS); + } + } + + public void reset(URL url) { + server.reset(url); + try { + int currHeartbeat = getHeartbeat(getUrl()); + int currIdleTimeout = getIdleTimeout(getUrl()); + int heartbeat = getHeartbeat(url); + int idleTimeout = getIdleTimeout(url); + /** + * 1.动态改配置,重新导出服务时不需要重新启动netty,tomcat等等 + * 2.这里直接关闭那个服务的channel任务,然后根据新的url重启一个任务就行了 + */ + if (currHeartbeat != heartbeat || currIdleTimeout != idleTimeout) { + cancelCloseTask(); + startIdleCheckTask(url); + } + } catch (Throwable t) { + logger.error(t.getMessage(), t); + } + } +``` + +##### CloseTimerTask + +```java + @Override + protected void doTask(Channel channel) { + try { + Long lastRead = lastRead(channel); + Long lastWrite = lastWrite(channel); + Long now = now(); + // check ping & pong at server + // 表示Server端有多长时间没有读到过数据或写出过数据了,说白就是超时了 + if ((lastRead != null && now - lastRead > idleTimeout) + || (lastWrite != null && now - lastWrite > idleTimeout)) { + logger.warn("Close channel " + channel + ", because idleCheck timeout: " + + idleTimeout + "ms"); + channel.close(); + } + } catch (Throwable t) { + logger.warn("Exception when close remote channel " + channel.getRemoteAddress(), t); + } + } +``` + + + + + +### 服务导出源码流程总结 + +1. ServiceBean.export()方法是导出的入口方法,会执行ServiceConfig.export()方法完成服务导出,导出完了之后会发布一个Spring事件ServiceBeanExportedEvent +2. 在ServiceConfig.export()方法中会先调用checkAndUpdateSubConfigs(),这个方法主要完成AbstractConfig的参数刷新(从配置中心获取参数等等),AbstractConfig是指ApplicationConfig、ProtocolConfig、ServiceConfig等等,刷新完后会检查stub、local、mock等参数是否配置正确 +3. 参数刷新和检查完成了之后,就会开始导出服务,如果配置了延迟导出,那么则按指定的时间利用ScheduledExecutorService来进行延迟导出 +4. 否则调用doExport()进行服务导出 +5. 继续调用doExportUrls()进行服务导出 +6. 首先通过loadRegistries()方法获得所配置的注册中心的URL,可能配了多个配置中心,那么当前所导出的服务需要注册到每个配置中心去,这里,注册中心的是以URL的方式来表示的,使用的是什么注册中心、注册中心的地址和端口,给注册中心所配置的参数等等,都会存在在URL上,此URL以**registry://**开始 +7. 获得到注册中心的registryURLs之后,就会遍历当前服务所有的ProtocolConfig,调用doExportUrlsFor1Protocol(protocolConfig, registryURLs);方法把当前服务按每个协议每个注册中心分别进行导出 +8. 在doExportUrlsFor1Protocol()方法中,会先构造一个服务URL,包括 + + 1. 服务的协议dubbo://, + 2. 服务的IP和PORT,如果指定了就取指定的,没有指定IP就获取服务器上网卡的IP, + 3. 以及服务的PATH,如果没有指定PATH参数,则取接口名 + 4. 以及服务的参数,参数包括服务的参数,服务中某个方法的参数 + 5. 最终得到的URL类似: dubbo://192.168.1.110:20880/cn.imlql.DemoService?timeout=3000&&sayHello.loadbalance=random + +9. 得到服务的URL之后,会把服务URL作为一个参数添加到registryURL中去,然后把registryURL、服务的接口、当前服务实现类ref生成一个Invoker代理对象,再把这个代理对象和当前ServiceConfig对象包装成一个DelegateProviderMetaDataInvoker对象,DelegateProviderMetaDataInvoker就表示了完整的一个服务 +10. 接下来就会使用Protocol去export导出服务了,导出之后将得到一个Exporter对象(该Exporter对象,可以理解为主要可以用来卸载(unexport)服务,什么时候会卸载服务?在优雅关闭Dubbo应用的时候) +11. 接下来我们来详细看看Protocol是怎么导出服务的? +12. 但调用**protocol**.export(wrapperInvoker)方法时,因为protocol是Protocol接口的一个Adaptive对象,所以此时会根据wrapperInvoker的genUrl方法得到一个url,根据此url的协议找到对应的扩展点,此时扩展点就是RegistryProtocol,但是,因为Protocol接口有两个包装类,一个是ProtocolFilterWrapper、ProtocolListenerWrapper,所以实际上在调用export方法时,会经过这两个包装类的export方法,但是在这两个包装类的export方法中都会Registry协议进行了判断,不会做过多处理,所以最终会直接调用到RegistryProtocol的export(Invoker originInvoker)方法 +13. 在RegistryProtocol的export(Invoker originInvoker)方法中,主要完成了以下几件事情: + + 1. 生成监听器,监听动态配置中心此服务的参数数据的变化,一旦监听到变化,则重写服务URL,并且在服务导出时先重写一次服务URL + 2. 拿到重写之后的URL之后,调用doLocalExport()进行服务导出,在这个方法中就会调用DubboProtocol的export方法去导出服务了,导出成功后将得到一个ExporterChangeableWrapper + + 1. 在DubboProtocol的export方法中主要要做的事情就是启动NettyServer,并且设置一系列的RequestHandler,以便在接收到请求时能依次被这些RequestHandler所处理 + 2. 这些RequestHandler在上文已经整理过了 + + 3. 从originInvoker中获取注册中心的实现类,比如ZookeeperRegistry + 4. 将重写后的服务URL进行简化,把不用存到注册中心去的参数去除 + 5. 把简化后的服务URL调用ZookeeperRegistry.registry()方法注册到注册中心去 + 6. 最后将ExporterChangeableWrapper封装为DestroyableExporter对象返回,完成服务导出 + + + +### Exporter架构 + + +一个服务导出成功后,会生成对应的Exporter: + +1. DestroyableExporter:Exporter的最外层包装类,这个类的主要作用是可以用来unexporter对应的服务 +2. ExporterChangeableWrapper:这个类主要负责在unexport对应服务之前,把服务URL从注册中心中移除,把该服务对应的动态配置监听器移除 +3. ListenerExporterWrapper:这个类主要负责在unexport对应服务之后,把服务导出监听器移除 +4. DubboExporter:这个类中保存了对应服务的Invoker对象,和当前服务的唯一标志,当NettyServer接收到请求后,会根据请求中的服务信息,找到服务对应的DubboExporter对象,然后从对象中得到Invoker对象 + + + +### 服务端Invoker架构 + + + +1. ProtocolFilterWrapper$CallbackRegistrationInvoker:会去调用下层Invoker,下层Invoker执行完了之后,会遍历过滤器,查看是否有过滤器实现了ListenableFilter接口,如果有,则回调对应的onResponse方法,比如TimeoutFilter,当调用完下层Invoker之后,就会计算服务的执行时间 +2. ProtocolFilterWrapper$1:ProtocolFilterWrapper中的过滤器组成的Invoker,利用该Invoker,可以执行服务端的过滤器,执行完过滤器之后,调用下层Invoker +3. RegistryProtocol$InvokerDelegate:服务的的委托类,里面包含了DelegateProviderMetaDataInvoker对象和服务对应的providerUrl,执行时直接调用下层Invoker +4. DelegateProviderMetaDataInvoker:服务的的委托类,里面包含了AbstractProxyInvoker对象和ServiceConfig对象,执行时直接调用下层Invoker +5. AbstractProxyInvoker:服务接口的代理类,绑定了对应的实现类,执行时会利用反射调用服务实现类实例的具体方法,并得到结果 + + diff --git a/docs/spring/use/Spring常用注解.md b/docs/spring/use/Spring常用注解.md new file mode 100644 index 0000000..29412ea --- /dev/null +++ b/docs/spring/use/Spring常用注解.md @@ -0,0 +1,1436 @@ +--- +title: Spring常用注解 +tags: + - Spring + - 注解 +categories: + - Spring + - 用法 +keywords: Spriong,框架 +description: 一些常用的注解 +cover: 'https://cdn.jsdelivr.net/gh/youthlql/youthlql/img/spring.png' +abbrlink: 1a003b7b +date: 2021-10-06 00:21:58 +--- + + + + + +> 参考了尚硅谷注解版,注解版后面的源码没看,雷神讲的太散了 + +## Spring注解 + +### AnnotationConfigApplicationContext + +### 组件添加 + +#### @Configuration+@Bean + +##### XML文件方式 + +###### Person + +```java +public class Person { + + private String name; + + private Integer age; + + private String nickName; + + public String getNickName() { + return nickName; + } + public void setNickName(String nickName) { + this.nickName = nickName; + } + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public Integer getAge() { + return age; + } + public void setAge(Integer age) { + this.age = age; + } + + public Person(String name, Integer age) { + super(); + this.name = name; + this.age = age; + } + public Person() { + super(); + // TODO Auto-generated constructor stub + } + @Override + public String toString() { + return "Person [name=" + name + ", age=" + age + ", nickName=" + nickName + "]"; + } +} +``` + +###### beans.xml-配置文件 + +```xml + + + + + + + + + +``` + +###### MainTest + +```java +public class MainTest { + + @SuppressWarnings("resource") + public static void main(String[] args) { + ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml"); + Person bean = (Person) applicationContext.getBean("person"); + System.out.println(bean); + } +} +``` + + + +输出 + +``` +Person [name=zhangsan, age=18, nickName=null] +``` + +##### 注解方式 + +```java +//配置类==配置文件 +@Configuration //告诉Spring这是一个配置类 +public class MainConfig { + + //给容器中注册一个Bean;类型为返回值的类型,id默认是用方法名作为id(就是bean的名字),在这里就是person01 + @Bean + public Person person01(){ + return new Person("lisi", 20); + } + +} +``` + +或者以下面的这种方式 + +```java +//配置类==配置文件 +@Configuration //告诉Spring这是一个配置类 +public class MainConfig { + + //这里bean的name就是person + @Bean("person") + public Person person01(){ + return new Person("lisi", 20); + } + +} +``` + + + +#### @ComponentScans + +```java +//配置类==配置文件 +@Configuration //告诉Spring这是一个配置类 + +@ComponentScans( + value = { + @ComponentScan(value="com.atguigu",includeFilters = { +/* @Filter(type=FilterType.ANNOTATION,classes={Controller.class}), + @Filter(type=FilterType.ASSIGNABLE_TYPE,classes={BookService.class}),*/ + @Filter(type=FilterType.CUSTOM,classes={MyTypeFilter.class}) + },useDefaultFilters = false) + } + ) +//@ComponentScan value:指定要扫描的包 +//excludeFilters = Filter[] :指定扫描的时候按照什么规则排除那些组件 +//includeFilters = Filter[] :指定扫描的时候只需要包含哪些组件 +//FilterType.ANNOTATION:按照注解 +//FilterType.ASSIGNABLE_TYPE:按照给定的类型; +//FilterType.ASPECTJ:使用ASPECTJ表达式 +//FilterType.REGEX:使用正则指定 +//FilterType.CUSTOM:使用自定义规则 +public class MainConfig { + + //给容器中注册一个Bean;类型为返回值的类型,id默认是用方法名作为id + @Bean("person") + public Person person01(){ + return new Person("lisi", 20); + } + +} +``` + + + +##### 自定义TypeFilter指定包扫描规则 + +```java +public class MyTypeFilter implements TypeFilter { + + /** + * metadataReader:读取到的当前正在扫描的类的信息 + * metadataReaderFactory:可以获取到其他任何类信息的 + */ + @Override + public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) + throws IOException { + // TODO Auto-generated method stub + //获取当前类注解的信息 + AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata(); + //获取当前正在扫描的类的类信息 + ClassMetadata classMetadata = metadataReader.getClassMetadata(); + //获取当前类资源(类的路径) + Resource resource = metadataReader.getResource(); + + String className = classMetadata.getClassName(); + System.out.println("--->"+className); + if(className.contains("er")){ + return true; + } + return false; + } + +} +``` + + + +#### @Scope + +```java +@Configuration +public class MainConfig2 { + + /** + * @see ConfigurableBeanFactory#SCOPE_PROTOTYPE 任何环境都可以使用 + * @see ConfigurableBeanFactory#SCOPE_SINGLETON 任何环境都可以使用 + * @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST request 只能在web容器里用 + * @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION sesssion 只能在web容器里用 + * + * @Scope:调整作用域 + * prototype:多实例的:ioc容器启动并不会去调用方法创建对象放在容器中。 + * 每次获取的时候才会调用方法创建对象; + * singleton:单实例的(默认值):ioc容器启动会调用方法创建对象放到ioc容器中。 + * 以后每次获取就是直接从容器(map.get())中拿, + * request:同一次请求创建一个实例 + * session:同一个session创建一个实例 + * + * 默认是单实例的 + * + */ + @Scope("prototype") + @Lazy + @Bean("person") + public Person person(){ + System.out.println("给容器中添加Person...."); + return new Person("张三", 25); + } + +} +``` + + + +#### @Lazy + +```java +@Configuration +public class MainConfig2 { + + /** + * + * 懒加载: + * 单实例bean:默认在容器启动的时候创建对象; + * 懒加载:容器启动不创建对象。第一次使用(获取)Bean创建对象,并初始化; + * + */ + @Lazy + @Bean("person") + public Person person(){ + System.out.println("给容器中添加Person...."); + return new Person("张三", 25); + } +``` + + + +#### @Conditional + +##### MainConfig2 + +```java +//类中组件统一设置。满足当前条件,这个类中配置的所有bean注册才能生效; +@Conditional({WindowsCondition.class}) +@Configuration +public class MainConfig2 { + + + /** + * @Conditional({Condition}) : 按照一定的条件进行判断,满足条件给容器中注册bean + * + * 如果系统是windows,给容器中注册("bill") + * 如果是linux系统,给容器中注册("linus") + */ + + @Bean("bill") + public Person person01(){ + return new Person("Bill Gates",62); + } + + @Conditional(LinuxCondition.class) + @Bean("linus") + public Person person02(){ + return new Person("linus", 48); + } +} +``` + +##### LinuxCondition + +```java +//判断是否linux系统 +public class LinuxCondition implements Condition { + + /** + * ConditionContext:判断条件能使用的上下文(环境) + * AnnotatedTypeMetadata:注释信息 + */ + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + // TODO是否linux系统 + //1、能获取到ioc使用的beanfactory + ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); + //2、获取类加载器 + ClassLoader classLoader = context.getClassLoader(); + //3、获取当前环境信息 + Environment environment = context.getEnvironment(); + //4、获取到bean定义的注册类 + BeanDefinitionRegistry registry = context.getRegistry(); + + String property = environment.getProperty("os.name"); + + //可以判断容器中的bean注册情况,也可以给容器中注册bean + boolean definition = registry.containsBeanDefinition("person"); + if(property.contains("linux")){ + return true; + } + + return false; + } +``` + +##### WindowsCondition + +```java +//判断是否windows系统 +public class WindowsCondition implements Condition { + + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + Environment environment = context.getEnvironment(); + String property = environment.getProperty("os.name"); + if(property.contains("Windows")){ + return true; + } + return false; + } + +} +``` + + + +#### @Import + +##### MainConfig2 + +```java +@Configuration +@Import({Color.class,Red.class,MyImportSelector.class,MyImportBeanDefinitionRegistrar.class}) +//@Import导入组件,id默认是组件的全类名 +public class MainConfig2 { + + /** + * 给容器中注册组件; + * 1)、包扫描+组件标注注解(@Controller/@Service/@Repository/@Component)[只能注册自己写的类] + * 2)、@Bean[导入的第三方包里面的组件] + * 3)、@Import[快速给容器中导入一个组件] + * 1)、@Import(要导入到容器中的组件);容器中就会自动注册这个组件,id默认是全类名 + * 2)、ImportSelector:返回需要导入的组件的全类名数组; + * 3)、ImportBeanDefinitionRegistrar:手动注册bean到容器中 + */ + @Bean + public ColorFactoryBean colorFactoryBean(){ + return new ColorFactoryBean(); + } +} +``` + +##### MyImportSelector + +```java +//自定义逻辑返回需要导入的组件 +public class MyImportSelector implements ImportSelector { + + //返回值,就是到导入到容器中的组件全类名 + //AnnotationMetadata:@Import引入MyImportSelector的类的所有注解信息 + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + //importingClassMetadata.get + //方法不要返回null值,不然会报错 + return new String[]{"com.atguigu.bean.Blue","com.atguigu.bean.Yellow"}; + } + +} +``` + +##### MyImportBeanDefinitionRegistrar + +```java +public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { + + /** + * AnnotationMetadata:当前类的注解信息 + * BeanDefinitionRegistry:BeanDefinition注册类; + * 把所有需要添加到容器中的bean;调用 + * BeanDefinitionRegistry.registerBeanDefinition手工注册进来 + */ + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { + + boolean definition = registry.containsBeanDefinition("com.atguigu.bean.Red"); + boolean definition2 = registry.containsBeanDefinition("com.atguigu.bean.Blue"); + if(definition && definition2){ + //指定Bean定义信息;(Bean的类型,Bean。。。) + RootBeanDefinition beanDefinition = new RootBeanDefinition(RainBow.class); + //注册一个Bean,指定bean名 + registry.registerBeanDefinition("rainBow", beanDefinition); + } + } + +} +``` + + + +```java +public class Color { + + private Car car; + + public Car getCar() { + return car; + } + + public void setCar(Car car) { + this.car = car; + } + + @Override + public String toString() { + return "Color [car=" + car + "]"; + } + +} +``` + + + +```java +public class Blue { + + public Blue(){ + System.out.println("blue...constructor"); + } + + public void init(){ + System.out.println("blue...init..."); + } + + public void detory(){ + System.out.println("blue...detory..."); + } + +} +``` + + + +#### FactoryBean + +##### MainConfig2 + +```java +@Configuration +public class MainConfig2 { + + + /** + * 给容器中注册组件; + * 1)、包扫描+组件标注注解(@Controller/@Service/@Repository/@Component)[自己写的类] + * 2)、@Bean[导入的第三方包里面的组件] + * 3)、@Import[快速给容器中导入一个组件] + * 1)、@Import(要导入到容器中的组件);容器中就会自动注册这个组件,id默认是全类名 + * 2)、ImportSelector:返回需要导入的组件的全类名数组; + * 3)、ImportBeanDefinitionRegistrar:手动注册bean到容器中 + * 4)、使用Spring提供的 FactoryBean(工厂Bean); + * 1)、默认获取到的是工厂bean调用getObject创建的对象 + * 2)、要获取工厂Bean本身,我们需要给id前面加一个& + * &colorFactoryBean + * + * 虽然这里装配的是ColorFactoryBean,但实际上beand的类型是Color + */ + @Bean + public ColorFactoryBean colorFactoryBean(){ + return new ColorFactoryBean(); + } + +} +``` + +##### ColorFactoryBean + +```java +//创建一个Spring定义的FactoryBean +public class ColorFactoryBean implements FactoryBean { + + //返回一个Color对象,这个对象会添加到容器中 + @Override + public Color getObject() throws Exception { + // TODO Auto-generated method stub + System.out.println("ColorFactoryBean...getObject..."); + return new Color(); + } + + @Override + public Class getObjectType() { + // TODO Auto-generated method stub + return Color.class; + } + + //是单例? + //true:这个bean是单实例,在容器中保存一份 + //false:多实例,每次获取都会创建一个新的bean; + @Override + public boolean isSingleton() { + // TODO Auto-generated method stub + return false; + } + +} +``` + +##### IOCTest + +```java +public class IOCTest { + AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class); + + + @Test + public void testImport(){ + printBeans(applicationContext); + Blue bean = applicationContext.getBean(Blue.class); + System.out.println(bean); + + //工厂Bean获取的是调用getObject创建的对象 + Object bean2 = applicationContext.getBean("colorFactoryBean"); + System.out.println("bean的类型:"+bean2.getClass()); //pos_1 输出:bean的类型:class com.atguigu.bean.Color + + Object bean4 = applicationContext.getBean("&colorFactoryBean"); + System.out.println(bean4.getClass()); //pos_2 输出:class com.atguigu.bean.ColorFactoryBean + } + + private void printBeans(AnnotationConfigApplicationContext applicationContext){ + String[] definitionNames = applicationContext.getBeanDefinitionNames(); + for (String name : definitionNames) { + System.out.println(name); + } + } +} +``` + +输出: + +```java +//前面无关的输出省略 + +colorFactoryBean +ColorFactoryBean...getObject... +bean的类型:class com.atguigu.bean.Color +class com.atguigu.bean.ColorFactoryBean +``` + + + +### 生命周期 + +#### @Bean指定初始化和销毁方法 + +##### IOCTest_LifeCycle + +> 后面的几个用的都是这个测试类 + +```java +public class IOCTest_LifeCycle { + + @Test + public void test01(){ + //1、创建ioc容器 + AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class); + System.out.println("容器创建完成..."); + + //applicationContext.getBean("car"); + //关闭容器 + applicationContext.close(); + } + +} +``` + +##### MainConfigOfLifeCycle + +```java +package com.atguigu.config; + +import org.springframework.context.ApplicationListener; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; + +import com.atguigu.bean.Car; + +/** + * bean的生命周期: + * bean创建---初始化----销毁的过程 + * 容器管理bean的生命周期; + * 我们可以自定义初始化和销毁方法;容器在bean进行到当前生命周期的时候来调用我们自定义的初始化和销毁方法 + * + * 构造(对象创建) + * 单实例:在容器启动的时候创建对象 + * 多实例:在每次获取的时候创建对象\ + * 初始化: + * 对象创建完成,并赋值好,调用初始化方法。。。 + * BeanPostProcessor.postProcessAfterInitialization + * 销毁: + * 单实例:容器关闭的时候 + * 多实例:容器不会管理这个bean;容器不会调用销毁方法; + * + * 1)、指定初始化和销毁方法; + * 通过@Bean指定init-method和destroy-method; + * @author lfy + * + */ +@ComponentScan("com.atguigu.bean") +@Configuration +public class MainConfigOfLifeCycle { + + //@Scope("prototype") + @Bean(initMethod="init",destroyMethod="detory") + public Car car(){ + return new Car(); + } + +} +``` + + + +```java +@Component +public class Car { + + public Car(){ + System.out.println("car constructor..."); + } + + public void init(){ + System.out.println("car ... init..."); + } + + public void detory(){ + System.out.println("car ... detory..."); + } + +} +``` + +##### 输出 + +```java +car constructor... +car ... init... +容器创建完成 +car ... detory... +``` + + + +#### InitializingBean和DisposableBean + +##### MainConfigOfLifeCycle + +```java +/** + * bean的生命周期: + * bean创建---初始化----销毁的过程 + * 容器管理bean的生命周期; + * 我们可以自定义初始化和销毁方法;容器在bean进行到当前生命周期的时候来调用我们自定义的初始化和销毁方法 + * + * 构造(对象创建) + * 单实例:在容器启动的时候创建对象 + * 多实例:在每次获取的时候创建对象\ + * + * BeanPostProcessor.postProcessBeforeInitialization + * 初始化: + * 对象创建完成,并赋值好,调用初始化方法。。。 + * BeanPostProcessor.postProcessAfterInitialization + * 销毁: + * 单实例:容器关闭的时候 + * 多实例:容器不会管理这个bean;容器不会调用销毁方法; + * + * + * 遍历得到容器中所有的BeanPostProcessor;挨个执行beforeInitialization, + * 一但返回null,跳出for循环,不会执行后面的BeanPostProcessor.postProcessorsBeforeInitialization + * + * BeanPostProcessor原理 + * populateBean(beanName, mbd, instanceWrapper);给bean进行属性赋值 + * initializeBean + * { + * applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); + * invokeInitMethods(beanName, wrappedBean, mbd);执行自定义初始化 + * applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); + *} + * + * + * + * 1)、指定初始化和销毁方法; + * 通过@Bean指定init-method和destroy-method; + * 2)、通过让Bean实现InitializingBean(定义初始化逻辑), + * DisposableBean(定义销毁逻辑); + * + * @author lfy + * + */ +@ComponentScan("com.atguigu.bean") +@Configuration +public class MainConfigOfLifeCycle { + + //@Scope("prototype") + @Bean(initMethod="init",destroyMethod="detory") + public Car car(){ + return new Car(); + } + +} +``` + +##### Cat + +```java +@Component +public class Cat implements InitializingBean,DisposableBean { + + public Cat(){ + System.out.println("cat constructor..."); + } + + @Override + public void destroy() throws Exception { + // TODO Auto-generated method stub + System.out.println("cat...destroy..."); + } + + @Override + public void afterPropertiesSet() throws Exception { + // TODO Auto-generated method stub + System.out.println("cat...afterPropertiesSet..."); + } + +} +``` + +##### 输出 + +```java +cat constructor... +cat...afterPropertiesSet... +car constructor... +car ... init... +容器创建完成 +car ... detory... +cat...destroy... +``` + + + +#### @PostConstruct和@PreDestroy + +##### MainConfigOfLifeCycle + +```java +/** + * bean的生命周期: + * bean创建---初始化----销毁的过程 + * 容器管理bean的生命周期; + * 我们可以自定义初始化和销毁方法;容器在bean进行到当前生命周期的时候来调用我们自定义的初始化和销毁方法 + * + * 构造(对象创建) + * 单实例:在容器启动的时候创建对象 + * 多实例:在每次获取的时候创建对象\ + * + * BeanPostProcessor.postProcessBeforeInitialization + * 初始化: + * 对象创建完成,并赋值好,调用初始化方法。。。 + * BeanPostProcessor.postProcessAfterInitialization + * 销毁: + * 单实例:容器关闭的时候 + * 多实例:容器不会管理这个bean;容器不会调用销毁方法; + * + * + * 遍历得到容器中所有的BeanPostProcessor;挨个执行beforeInitialization, + * 一但返回null,跳出for循环,不会执行后面的BeanPostProcessor.postProcessorsBeforeInitialization + * + * BeanPostProcessor原理 + * populateBean(beanName, mbd, instanceWrapper);给bean进行属性赋值 + * initializeBean + * { + * applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); + * invokeInitMethods(beanName, wrappedBean, mbd);执行自定义初始化 + * applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); + *} + * + * + * + * 1)、指定初始化和销毁方法; + * 通过@Bean指定init-method和destroy-method; + * 2)、通过让Bean实现InitializingBean(定义初始化逻辑), + * DisposableBean(定义销毁逻辑); + * 3)、可以使用JSR250; + * @PostConstruct:在bean创建完成并且属性赋值完成;来执行初始化方法 + * @PreDestroy:在容器销毁bean之前通知我们进行清理工作 + * + * @author lfy + * + */ +@ComponentScan("com.atguigu.bean") +@Configuration +public class MainConfigOfLifeCycle { + + //@Scope("prototype") + @Bean(initMethod="init",destroyMethod="detory") + public Car car(){ + return new Car(); + } + +} +``` + +##### Dog + +```java +@Component +public class Dog implements ApplicationContextAware { + + //@Autowired + private ApplicationContext applicationContext; + + public Dog(){ + System.out.println("dog constructor..."); + } + + //对象创建并赋值之后调用 + @PostConstruct + public void init(){ + System.out.println("Dog....@PostConstruct..."); + } + + //容器移除对象之前 + @PreDestroy + public void detory(){ + System.out.println("Dog....@PreDestroy..."); + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + // TODO Auto-generated method stub + this.applicationContext = applicationContext; + } +} +``` + + + +##### 输出 + +```java +cat constructor... +cat...afterPropertiesSet... +dog constructor... +Dog....@PostConstruct... +car constructor... +car ... init... +容器创建完成 +car ... detory... +Dog....@PreDestroy... +cat...destroy... +``` + + + +#### BeanPostProcessor + +```java + +/** + * bean的生命周期: + * bean创建---初始化----销毁的过程 + * 容器管理bean的生命周期; + * 我们可以自定义初始化和销毁方法;容器在bean进行到当前生命周期的时候来调用我们自定义的初始化和销毁方法 + * + * 构造(对象创建) + * 单实例:在容器启动的时候创建对象 + * 多实例:在每次获取的时候创建对象\ + * + * BeanPostProcessor.postProcessBeforeInitialization + * 初始化: + * 对象创建完成,并赋值好,调用初始化方法。。。 + * BeanPostProcessor.postProcessAfterInitialization + * 销毁: + * 单实例:容器关闭的时候 + * 多实例:容器不会管理这个bean;容器不会调用销毁方法; + * + * + * 遍历得到容器中所有的BeanPostProcessor;挨个执行beforeInitialization, + * 一但返回null,跳出for循环,不会执行后面的BeanPostProcessor.postProcessorsBeforeInitialization + * + * BeanPostProcessor原理 + * populateBean(beanName, mbd, instanceWrapper);给bean进行属性赋值 + * initializeBean + * { + * applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); + * invokeInitMethods(beanName, wrappedBean, mbd);执行自定义初始化 + * applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); + *} + * + * + * + * 1)、指定初始化和销毁方法; + * 通过@Bean指定init-method和destroy-method; + * 2)、通过让Bean实现InitializingBean(定义初始化逻辑), + * DisposableBean(定义销毁逻辑); + * 3)、可以使用JSR250; + * @PostConstruct:在bean创建完成并且属性赋值完成;来执行初始化方法 + * @PreDestroy:在容器销毁bean之前通知我们进行清理工作 + * 4)、BeanPostProcessor【interface】:bean的后置处理器; + * 在bean初始化前后进行一些处理工作; + * postProcessBeforeInitialization:在初始化之前工作 + * postProcessAfterInitialization:在初始化之后工作 + * + * Spring底层对 BeanPostProcessor 的使用; + * bean赋值,注入其他组件,@Autowired,生命周期注解功能,@Async,xxx BeanPostProcessor; + * + * @author lfy + * + */ +@ComponentScan("com.atguigu.bean") +@Configuration +public class MainConfigOfLifeCycle { + + //@Scope("prototype") + @Bean(initMethod="init",destroyMethod="detory") + public Car car(){ + return new Car(); + } + +} +``` + +##### MyBeanPostProcessor + +```java +/** + * 后置处理器:初始化前后进行处理工作 + * 将后置处理器加入到容器中 + * @author lfy + */ +@Component +public class MyBeanPostProcessor implements BeanPostProcessor { + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + // TODO Auto-generated method stub + System.out.println("postProcessBeforeInitialization..."+beanName+"=>"+bean); + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + // TODO Auto-generated method stub + System.out.println("postProcessAfterInitialization..."+beanName+"=>"+bean); + return bean; + } + +} +``` + +输出 + +> 自己写的组件输出内容 + +```java +car constructor... +postProcessBeforeInitialization...car=>com.atguigu.bean.Car@5ef60048 +car ... init... +postProcessAfterInitialization...car=>com.atguigu.bean.Car@5ef60048 +cat constructor... +postProcessBeforeInitialization...cat=>com.atguigu.bean.Cat@780cb77 +cat...afterPropertiesSet... +postProcessAfterInitialization...cat=>com.atguigu.bean.Cat@780cb77 +dog constructor... +postProcessBeforeInitialization...dog=>com.atguigu.bean.Dog@4034c28c +Dog....@PostConstruct... +postProcessAfterInitialization...dog=>com.atguigu.bean.Dog@4034c28c +容器创建完成... +Dog....@PreDestroy... +cat...destroy... +car ... detory... +``` + + + +> 1. BeanPostProcessor在Spring源码里大量被使用到,仅凭这里雷丰阳老师讲的一点点原理,是无法体会的,建议自己去看看Spring源码。所以这里的原理部分我也就直接省略了,在本视频中讲的太浅了。 + +### 属性赋值 + +#### @Value和@PropertySource + +##### Person + +```java +public class Person { + + //使用@Value赋值; + //1、基本数值 + //2、可以写SpEL; #{} + //3、可以写${};取出配置文件【properties】中的值(在运行环境变量里面的值) + + @Value("张三") + private String name; + @Value("#{20-2}") + private Integer age; + + @Value("${person.nickName}") + private String nickName; + + + + public String getNickName() { + return nickName; + } + public void setNickName(String nickName) { + this.nickName = nickName; + } + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public Integer getAge() { + return age; + } + public void setAge(Integer age) { + this.age = age; + } + + public Person(String name, Integer age) { + super(); + this.name = name; + this.age = age; + } + public Person() { + super(); + // TODO Auto-generated constructor stub + } + @Override + public String toString() { + return "Person [name=" + name + ", age=" + age + ", nickName=" + nickName + "]"; + } +} +``` + +##### person.properties + +```java +person.nickName=\u5C0F\u674E\u56DB +``` + + + +##### MainConfigOfPropertyValues + +```java +//使用@PropertySource读取外部配置文件中的k/v保存到运行的环境变量中;加载完外部的配置文件以后使用${}取出配置文件的值 +@PropertySource(value={"classpath:/person.properties"}) +@Configuration +public class MainConfigOfPropertyValues { + + @Bean + public Person person(){ + return new Person(); + } + +} +``` + +##### IOCTest_PropertyValue + +```java +public class IOCTest_PropertyValue { + AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfPropertyValues.class); + @Test + public void test01(){ + printBeans(applicationContext); + System.out.println("============="); + + Person person = (Person) applicationContext.getBean("person"); + System.out.println(person); + + + ConfigurableEnvironment environment = applicationContext.getEnvironment(); + String property = environment.getProperty("person.nickName"); + System.out.println(property); + applicationContext.close(); + } + + private void printBeans(AnnotationConfigApplicationContext applicationContext){ + String[] definitionNames = applicationContext.getBeanDefinitionNames(); + for (String name : definitionNames) { + System.out.println(name); + } + } + +} +``` + +##### 输出 + +```java +mainConfigOfPropertyValues +person +============= +Person [name=张三, age=18, nickName=小李四] +小李四 +``` + + + +### 自动装配 + +#### @Autowired-@Qualifier-@Primary-@Resource-@Inject + +```java +@Controller +public class BookController { + + @Autowired + private BookService bookService; + +} +``` + + + +```java +@Service +public class BookService { + + //@Qualifier("bookDao") + //@Autowired(required=false) + //@Resource(name="bookDao2") + @Inject + private BookDao bookDao; + + public void print(){ + System.out.println(bookDao); + } + + @Override + public String toString() { + return "BookService [bookDao=" + bookDao + "]"; + } + + +} +``` + + + +```java +//名字默认是类名首字母小写 +@Repository +public class BookDao { + + private String lable = "1"; + + public String getLable() { + return lable; + } + + public void setLable(String lable) { + this.lable = lable; + } + + @Override + public String toString() { + return "BookDao [lable=" + lable + "]"; + } + + + +} +``` + + + +##### MainConifgOfAutowired + +```java +/** + * 自动装配; + * Spring利用依赖注入(DI),完成对IOC容器中中各个组件的依赖关系赋值; + * + * 1)、@Autowired:自动注入: + * 1)、默认优先按照类型去容器中找对应的组件:applicationContext.getBean(BookDao.class);找到就赋值 + * 2)、如果找到多个相同类型的组件,再将属性的名称作为组件的id去容器中查找 + * applicationContext.getBean("bookDao") + * 3)、@Qualifier("bookDao"):使用@Qualifier指定需要装配的组件的id,而不是使用属性名 + * 4)、自动装配默认一定要将属性赋值好,没有就会报错; + * 可以使用@Autowired(required=false); + * 5)、@Primary:让Spring进行自动装配的时候,默认使用首选的bean; + * 也可以继续使用@Qualifier指定需要装配的bean的名字 + * BookService{ + * @Autowired + * BookDao bookDao; + * } + * + * 2)、Spring还支持使用@Resource(JSR250)和@Inject(JSR330)[java规范的注解] + * @Resource: + * 可以和@Autowired一样实现自动装配功能;默认是按照组件名称进行装配的; + * 没有能支持@Primary功能没有支持@Autowired(reqiured=false); + * @Inject: + * 需要导入javax.inject的包,和Autowired的功能一样。没有required=false的功能; + * @Autowired:Spring定义的; @Resource、@Inject都是java规范 + * + * AutowiredAnnotationBeanPostProcessor:解析完成自动装配功能; + * + * 3)、 @Autowired:构造器,参数,方法,属性;都是从容器中获取参数组件的值 + * 1)、[标注在方法位置]:@Bean+方法参数;参数从容器中获取;默认不写@Autowired效果是一样的;都能自动装配 + * 2)、[标在构造器上]:如果组件只有一个有参构造器,这个有参构造器的@Autowired可以省略,参数位置的组件还是可以自动从容器中获取 + * 3)、放在参数位置: + public Boss(@Autowired Car car){ + this.car = car; + System.out.println("Boss...有参构造器"); + } + * + * 4)、自定义组件想要使用Spring容器底层的一些组件(ApplicationContext,BeanFactory,xxx); + * 自定义组件实现xxxAware;在创建对象的时候,会调用接口规定的方法注入相关组件;Aware; + * 把Spring底层一些组件注入到自定义的Bean中; + * xxxAware:功能使用xxxProcessor; + * ApplicationContextAware==》ApplicationContextAwareProcessor; + * + * + * @author lfy + * + */ +@Configuration +@ComponentScan({"com.atguigu.service","com.atguigu.dao", + "com.atguigu.controller","com.atguigu.bean"}) +public class MainConifgOfAutowired { + + @Primary + @Bean("bookDao2") + public BookDao bookDao(){ + BookDao bookDao = new BookDao(); + bookDao.setLable("2"); + return bookDao; + } + + /** + * @Bean标注的方法创建对象的时候,方法参数的值默认从容器中获取 + * @param car + * @return + */ + @Bean + public Color color(Car car){ + Color color = new Color(); + color.setCar(car); + return color; + } + + +} +``` + + + +##### IOCTest_Autowired + +```java +public class IOCTest_Autowired { + + @Test + public void test01(){ + AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConifgOfAutowired.class); + + BookService bookService = applicationContext.getBean(BookService.class); + System.out.println(bookService); + + //BookDao bean = applicationContext.getBean(BookDao.class); + //System.out.println(bean); + + Boss boss = applicationContext.getBean(Boss.class); + System.out.println(boss); + Car car = applicationContext.getBean(Car.class); + System.out.println(car); + + Color color = applicationContext.getBean(Color.class); + System.out.println(color); + System.out.println(applicationContext); + applicationContext.close(); + } + +} +``` + + + +#### @Profle + +##### MainConfigOfProfile + +```java +/** + * Profile: + * Spring为我们提供的可以根据当前环境,动态的激活和切换一系列组件的功能; + * + * 开发环境、测试环境、生产环境; + * 数据源:(/A)(/B)(/C); + * + * + * @Profile:指定组件在哪个环境的情况下才能被注册到容器中,不指定,任何环境下都能注册这个组件 + * + * 1)、加了环境标识的bean,只有这个环境被激活的时候才能注册到容器中。默认是default环境 + * 2)、写在配置类上,只有是指定的环境的时候,整个配置类里面的所有配置才能开始生效 + * 3)、没有标注环境标识的bean在,任何环境下都是加载的; + */ + +@PropertySource("classpath:/dbconfig.properties") +@Configuration +public class MainConfigOfProfile implements EmbeddedValueResolverAware{ + + @Value("${db.user}") + private String user; + + private StringValueResolver valueResolver; + + private String driverClass; + + + @Bean + public Yellow yellow(){ + return new Yellow(); + } + + @Profile("test") + @Bean("testDataSource") + public DataSource dataSourceTest(@Value("${db.password}")String pwd) throws Exception{ + ComboPooledDataSource dataSource = new ComboPooledDataSource(); + dataSource.setUser(user); + dataSource.setPassword(pwd); + dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test"); + dataSource.setDriverClass(driverClass); + return dataSource; + } + + + @Profile("dev") + @Bean("devDataSource") + public DataSource dataSourceDev(@Value("${db.password}")String pwd) throws Exception{ + ComboPooledDataSource dataSource = new ComboPooledDataSource(); + dataSource.setUser(user); + dataSource.setPassword(pwd); + dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/ssm_crud"); + dataSource.setDriverClass(driverClass); + return dataSource; + } + + @Profile("prod") + @Bean("prodDataSource") + public DataSource dataSourceProd(@Value("${db.password}")String pwd) throws Exception{ + ComboPooledDataSource dataSource = new ComboPooledDataSource(); + dataSource.setUser(user); + dataSource.setPassword(pwd); + dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/scw_0515"); + + dataSource.setDriverClass(driverClass); + return dataSource; + } + + @Override + public void setEmbeddedValueResolver(StringValueResolver resolver) { + // TODO Auto-generated method stub + this.valueResolver = resolver; + driverClass = valueResolver.resolveStringValue("${db.driverClass}"); + } + +} +``` + +##### dbconfig.properties + +```properties +db.user=root +db.password=123456 +db.driverClass=com.mysql.jdbc.Driver +``` + + + + + + + + + + +