diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml new file mode 100644 index 000000000000..b16223aeb0dc --- /dev/null +++ b/.github/workflows/cd.yml @@ -0,0 +1,116 @@ +name: Deploy GitHub Pages +on: + push: + branches: + - analysis +jobs: + deploy-gh-pages: + runs-on: ubuntu-latest + steps: + # https://github.com/actions/checkout + - name: Checkout 🛎️ + uses: actions/checkout@v4 + with: + ref: analysis + persist-credentials: false + + # https://github.com/actions/setup-java + - name: Set up JDK ☕️ + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + # https://github.com/actions/setup-node + - name: Setup Node.js 🕸 + uses: actions/setup-node@v4 + with: + # https://github.com/nvm-sh/nvm#long-term-support + node-version: 'lts/*' + + - name: Install Graphviz 🐰 + run: | + sudo apt update -y -m + sudo apt install -y python3-pip + # https://graphviz.org/ + sudo apt install -y graphviz + # https://blockdiag.com/en/seqdiag/index.html + pip3 install seqdiag + # https://blockdiag.com/en/blockdiag/index.html + pip3 install blockdiag + # https://blockdiag.com/en/actdiag/index.html + pip3 install actdiag + # https://blockdiag.com/en/nwdiag/index.html + pip3 install nwdiag + # https://github.com/Deep-Symmetry/bytefield-svg + npm install -g bytefield-svg + # https://github.com/gtudan/bpmn-js-cmd + npm install -g bpmn-js-cmd + + - name: Install font 🎃 + run: | + mkdir $HOME/.fonts + cd $HOME/.fonts + wget https://github.com/diguage/open-fonts/releases/download/latest/SourceHanSerifSC-Regular.otf + wget https://github.com/diguage/open-fonts/releases/download/latest/SourceHanSansSC-Regular.otf + wget https://github.com/diguage/open-fonts/releases/download/latest/SourceCodePro-Regular.otf + wget https://github.com/diguage/open-fonts/releases/download/latest/SourceCodePro-It.otf + wget https://github.com/diguage/open-fonts/releases/download/latest/SourceCodePro-Bold.otf + wget https://github.com/diguage/open-fonts/releases/download/latest/SourceCodePro-BoldIt.otf + echo -e "[seqdiag]\nfontpath = $HOME/.fonts/SourceHanSerifSC-Regular.otf" > $HOME/.blockdiagrc + echo -e "\n[blockdiag]\nfontpath = $HOME/.fonts/SourceHanSerifSC-Regular.otf" >> $HOME/.blockdiagrc + echo -e "\n[actdiag]\nfontpath = $HOME/.fonts/SourceHanSerifSC-Regular.otf" >> $HOME/.blockdiagrc + echo -e "\n[nwdiag]\nfontpath = $HOME/.fonts/SourceHanSerifSC-Regular.otf" >> $HOME/.blockdiagrc + # Check result + ls -lh $HOME/.fonts + cat $HOME/.blockdiagrc + + - name: Build 🔧 + continue-on-error: true + run: ./gradlew :truman:asciidoctor --no-build-cache + + - name: Custom Code Style 🐦 + run: | + sudo apt install -y sed + sed -i 's/<\/head>/ \ No newline at end of file diff --git a/truman/src/docs/asciidoc/dynamic-proxy.adoc b/truman/src/docs/asciidoc/dynamic-proxy.adoc new file mode 100644 index 000000000000..114ed07c5f7f --- /dev/null +++ b/truman/src/docs/asciidoc/dynamic-proxy.adoc @@ -0,0 +1,2 @@ +[#dynamic-proxy] += 动态代理 diff --git a/truman/src/docs/asciidoc/environment.adoc b/truman/src/docs/asciidoc/environment.adoc new file mode 100644 index 000000000000..ece2e49a0ddc --- /dev/null +++ b/truman/src/docs/asciidoc/environment.adoc @@ -0,0 +1,5 @@ += `Environment` + +`Environment` 主要用于读取当前应用运行环境的环境变量和一些配置信息。另外,常见的指定不同配置的 `spring.profiles.active` 的处理,也是由 `Environment` 来处理。 + +plantuml::{includedir}/puml/org.springframework.core.env.PropertyResolver.puml[{diagram_attr}] diff --git a/truman/src/docs/asciidoc/extensions-and-dubbo.adoc b/truman/src/docs/asciidoc/extensions-and-dubbo.adoc new file mode 100644 index 000000000000..2463431c8f9a --- /dev/null +++ b/truman/src/docs/asciidoc/extensions-and-dubbo.adoc @@ -0,0 +1,605 @@ += 整合 Apache Dubbo + +在上一篇文章 https://www.diguage.com/post/spring-extensions-overview/[Spring 扩展点概览及实践^] 中介绍了 Spring 内部存在的扩展点。 https://www.diguage.com/post/spring-extensions-and-mybatis/[Spring 扩展点实践:整合 MyBATIS^] 中,D瓜哥带大家了解了一下 MyBATIS 如何利用 Spring 的扩展点实现了与 Spring 的完美整合。现在,学以致用,我们继续来分析一下 Spring 与 Apache Dubbo 的整合流程。 + + +== 示例程序 + +Apache Dubbo 仓库中就有很完整的示例。D瓜哥直接拿来使用就不再搭建示例程序了。 + +首先,需要启动一个 ZooKeeper 实例。查看 Dubbo 的依赖可以看出,最新版代码依赖的 ZooKeeper 是 3.4.13 版。所以,为了最好的兼容性,就要选用 3.4.X 版的 ZooKeeper 服务器。D瓜哥直接使用 Docker 启动 ZooKeeper 了。命令如下: + +[source,bash,{source_attr}] +---- +docker run --rm --name zookeeper -d -p 2181:2181 zookeeper:3.4.14 +---- + +这次我们使用 https://github.com/apache/dubbo[Apache Dubbo^] 的 `dubbo-demo/dubbo-demo-xml` 示例。 + +第二步,启动服务提供者程序,找到 `DUBBO/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-provider/src/main/java/org/apache/dubbo/demo/provider/Application.java`,运行该类。 + +第三步,运行服务消费者程序,找到 `DUBBO/dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-consumer/src/main/java/org/apache/dubbo/demo/consumer/Application.java`,运行该类。 + +如果没有任何错误,则在终端可以看到 `result: async result` 输出。 + +在开始正餐之前,D瓜哥先给大家来个开胃菜。 + +== Spring 插件机制简介 + +不知道大家有没有想过一个问题:Spring 框架是如何支持越来越多的功能的? + +在D瓜哥了解到 Spring 的插件机制后,非常叹服 Spring 精巧的设计和灵活的扩展性。闲言少叙,好戏上演。 + +这里再问大家一个问题: + +[source,xml,{source_attr}] +---- + + + + + + + + + + + + + + + + + + + + + + + + + +---- + +这是非常典型的 Spring XML 配置。相信大家都见过。大家有没有想过,Spring 是怎么处理这些不同的命名空间的?如果说 AOP、事务这些是 Spring 内置支持的功能,这样配置,Spring 可以正确解析。但是,Dubbo 的配置又是怎么回事? + +要回答这个问题,就要说起 Spring 的插件机制。在 Spring 的插件机制面前,无论是 Dubbo,还是 Spring 的 AOP、事务管理都是人人平等的。它们都是依靠 Spring 的插件机制插拔在 Spring 核心模块之上的。 + +这篇文章不是专门介绍 Spring 插件机制的。这里抛砖引玉,对 Spring 插件机制做个简介。后续有机会再做更详细的介绍和说明。 + +要利用 Spring 插件机制,需要做这么几个事情: + +. 定义自己业务的类。 +. 编写 XSD 文件,定义自己的 XML 格式,将文件放在 `src/main/resources/META-INF` 目录下。 +. 针对每一个标签,定义一个实现 `BeanDefinitionParser` 接口的类,在 `parse` 方法中完成对这个标签的解析工作,将其转化成一个 `BeanDefinition` 对象。 +. 继承 `NamespaceHandlerSupport` 类,在 `init()` 方法中,使用 `registerBeanDefinitionParser()` 将标签名称和上面写的 `BeanDefinitionParser` 实现类之间建起起对应关系。 +. 创建 `src/main/resources/META-INF/spring.schemas` 文件,在其中写上: `http\://www.diguage.com/schema/diguage/diguage.xsd=META-INF/diguage.xsd`,为该 XSD 文件定义唯一的命名空间。 +. 创建 `src/main/resources/META-INF/spring.handlers` 文件,在其中写上: `http\://www.diguage.com/schema/diguage=com.diguage.schema.DiguageNamespaceHandler`。 + +完成上面这些步骤就相当于制作了一个 Spring 插件。这样就可以在 Spring XML 配置文件中,像使用 AOP、事务管理那样来使用这个新插件了。 + +仔细想想,Spring 的插件机制还是挺简单的:首先,定义一个 Bean 类,然后设计 XSD 文件来对 Bean 的属性进行定义。用户在使用插件时,使用 XML 来定义 Bean 类的属性值,再自定义的 `BeanDefinitionParser` 实现类将 XML 中的配置信息解析出来,封装在 `BeanDefinition`(关于 `BeanDefinition` 的更多信息,请移步 https://www.diguage.com/post/dive-into-spring-core-data-structure-bean-definition/[深入剖析 Spring 核心数据结构:BeanDefinition^])。到了 `BeanDefinition` 之后,Spring 在内部就可以统一处理了。 + +下面,结合代理来具体说明一下 Apache Dubbo 的实现过程。 + +== Apache Dubbo 插件机制解析 + +Apache Dubbo 最初就说通过 Spring 插件机制实现了它与 Spring 的整合过程。 + +. 相关业务类有 `ApplicationConfig`、 `ModuleConfig`、 `RegistryConfig`、 `ConfigCenterBean`、 `MetadataReportConfig`、 `MonitorConfig`、 `MetricsConfig`、 `SslConfig`、 `ProviderConfig`、 `ConsumerConfig`、 `ProtocolConfig`、 `ServiceBean` 和 `ReferenceBean`。这些类的命名也都非常讲究,见文知意,与 Dubbo 常见配置可以说是一一对应。 +. Dubbo 的 XSD 定义在 https://github.com/apache/dubbo/blob/master/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/dubbo.xsd[dubbo.xsd^],懂 XSD 的朋友应该都能看出来,这个文件就是规范上一步提到的类的属性的。 +. `DubboBeanDefinitionParser` 实现了 `BeanDefinitionParser` 接口,用于解析 XML 配置,并将其“翻译”为第一步中那些类的对象。另外,还注册了一个 `AnnotationBeanDefinitionParser`,用来处理 `annotation` 标签,进而用来处理注解。 +. `DubboNamespaceHandler` 继承了 `NamespaceHandlerSupport`,并且在 `init()` 方法中完成了对上述类的 `DubboBeanDefinitionParser` 注册。 +. 在 `dubbo-config/dubbo-config-spring/src/main/resources/META-INF` 目录下,有 `spring.schemas` 文件和 `spring.handlers` 文件。 + +下面以调试跟进的方式来分析整个处理过程。 + +== Apache Dubbo 配置解析 + +这里使用示例程序中的配置文件: + +.`dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-provider/src/main/resources/spring/dubbo-provider.xml` +[source,xml,{source_attr}] +---- + + + + + + + + + + + + + + + +---- + +在 `org.apache.dubbo.config.spring.schema.DubboNamespaceHandler#init` 方法、 `org.apache.dubbo.config.spring.schema.DubboNamespaceHandler#parse` 方法 和 `org.apache.dubbo.config.spring.schema.DubboBeanDefinitionParser#parse(Element, ParserContext)` 方法打断点开始调试。注意:这三个方法都是重载方法,很容易识别。 + +打好断点后重启服务提供者程序,程序会在 `init()` 方法处暂停: + +.`org.apache.dubbo.config.spring.schema.DubboNamespaceHandler#init` +[{java_src_attr}] +---- + @Override + public void init() { + registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true)); + registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true)); + registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true)); + registerBeanDefinitionParser("config-center", new DubboBeanDefinitionParser(ConfigCenterBean.class, true)); + registerBeanDefinitionParser("metadata-report", new DubboBeanDefinitionParser(MetadataReportConfig.class, true)); + registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true)); + registerBeanDefinitionParser("metrics", new DubboBeanDefinitionParser(MetricsConfig.class, true)); + registerBeanDefinitionParser("ssl", new DubboBeanDefinitionParser(SslConfig.class, true)); + registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true)); + registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true)); + registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true)); + registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true)); + registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false)); + registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser()); + } +---- + +从这里可以明显看到,都注册哪些 `BeanDefinitionParser`,都需要处理哪些标签。点击 `registerBeanDefinitionParser` 方法就可以看出,所谓的“注册”其实就是将它们放在了 `org.springframework.beans.factory.xml.NamespaceHandlerSupport#Map parsers` 变量中。 + +这里不要深究,继续向下执行,就会到了 `DubboNamespaceHandler#parse` 方法: + +.`org.apache.dubbo.config.spring.schema.DubboNamespaceHandler#parse` +[{java_src_attr}] +---- + @Override + public BeanDefinition parse(Element element, ParserContext parserContext) { + BeanDefinitionRegistry registry = parserContext.getRegistry(); + registerAnnotationConfigProcessors(registry); + /** + * @since 2.7.8 + * issue : https://github.com/apache/dubbo/issues/6275 + */ + registerCommonBeans(registry); + BeanDefinition beanDefinition = super.parse(element, parserContext); + setSource(beanDefinition); + return beanDefinition; + } +---- + +这里,我们需要注意的是 `registerCommonBeans(registry)` 方法: + +[#register-common-beans] +.`org.apache.dubbo.config.spring.util.DubboBeanUtils#registerCommonBeans` +[{java_src_attr}] +---- + /** + * Register the common beans + * + * @param registry {@link BeanDefinitionRegistry} + * @see ReferenceAnnotationBeanPostProcessor + * @see DubboConfigDefaultPropertyValueBeanPostProcessor + * @see DubboConfigAliasPostProcessor + * @see DubboLifecycleComponentApplicationListener + * @see DubboBootstrapApplicationListener + */ + static void registerCommonBeans(BeanDefinitionRegistry registry) { + + // Since 2.5.7 Register @Reference Annotation Bean Processor as an infrastructure Bean + registerInfrastructureBean(registry, ReferenceAnnotationBeanPostProcessor.BEAN_NAME, + ReferenceAnnotationBeanPostProcessor.class); + + // Since 2.7.4 [Feature] https://github.com/apache/dubbo/issues/5093 + registerInfrastructureBean(registry, DubboConfigAliasPostProcessor.BEAN_NAME, + DubboConfigAliasPostProcessor.class); + + // Since 2.7.5 Register DubboLifecycleComponentApplicationListener as an infrastructure Bean + registerInfrastructureBean(registry, DubboLifecycleComponentApplicationListener.BEAN_NAME, + DubboLifecycleComponentApplicationListener.class); + + // Since 2.7.4 Register DubboBootstrapApplicationListener as an infrastructure Bean + registerInfrastructureBean(registry, DubboBootstrapApplicationListener.BEAN_NAME, + DubboBootstrapApplicationListener.class); + + // Since 2.7.6 Register DubboConfigDefaultPropertyValueBeanPostProcessor as an infrastructure Bean + registerInfrastructureBean(registry, DubboConfigDefaultPropertyValueBeanPostProcessor.BEAN_NAME, + DubboConfigDefaultPropertyValueBeanPostProcessor.class); + } +---- + +这里需要重点关注的是 `ReferenceAnnotationBeanPostProcessor` 和 `DubboBootstrapApplicationListener`,前者设计到 Dubbo 注解的处理,后者着牵涉整个 Dubbo 的启动。先在 `DubboBootstrapApplicationListener` 的 `onApplicationContextEvent` 方法上打上断点。后续涉及到时,再具体分析。 + +然后,我们单步调试,跟进 `BeanDefinition beanDefinition = super.parse(element, parserContext);` 这个调用中: + + +.`org.springframework.beans.factory.xml.NamespaceHandlerSupport` +[{java_src_attr}] +---- +include::{beans_src_dir}/factory/xml/NamespaceHandlerSupport.java[tag=parse] + +include::{beans_src_dir}/factory/xml/NamespaceHandlerSupport.java[tag=findParserForElement] +---- + +结合上面的 `init()`,上面是“放”,现在是根据标签名称来“拿”。这样就找到每个标签对应的 `BeanDefinitionParser`。这些 `BeanDefinitionParser` 的作用就是处理对应的标签并将其转化为 `BeanDefinition`。 + +Dubbo XML 配置的解析就这么些,后续的过程要依赖 Spring 的流程了。 + +== Dubbo 暴露服务提供者的过程 + +让程序继续执行,就到了我们上面打断点的地方: `DubboBootstrapApplicationListener#onApplicationContextEvent`。一路单步调试跟下去,就到了 `DubboBootstrap#start` 方法。到这一步,Dubbo 就开始启动了。 + +`start()` 方法中,调用了 `DubboBootstrap#initialize` 方法,这个方法就有点像 Spring 的 `AbstractApplicationContext#refresh` 方法。如果分析 Dubbo 的源代码,这必定是一个好的入口。在 `initialize()` 方法中,Dubbo 完成了以下功能: + +. `initFrameworkExts()` -- 初始化框架 +. `startConfigCenter()` -- 启动配置中心 +. `loadRemoteConfigs()` -- 加载远程配置 +. `checkGlobalConfigs()` -- 检查全局配置 +. `startMetadataCenter()` -- 开始元数据中心,这里特别标明是从 2.7.8 开始的。 +. `initMetadataService()` -- 初始化元数据服务 +. `initMetadataServiceExports()` -- 初始化元数据服务导出 +. `initEventListener()` -- 初始化时间监听。 + +WARNING: 暂时没有深入研究这些方法的实现。说明也都是直译的方法名。 + +继续向下执行,进入 `DubboBootstrap#exportServices` 方法: + +.`org.apache.dubbo.config.bootstrap.DubboBootstrap#exportServices` +[{java_src_attr}] +---- + private void exportServices() { + configManager.getServices().forEach(sc -> { + // TODO, compatible with ServiceConfig.export() + ServiceConfig serviceConfig = (ServiceConfig) sc; + serviceConfig.setBootstrap(this); + + if (exportAsync) { + ExecutorService executor = executorRepository.getServiceExporterExecutor(); + Future future = executor.submit(() -> { + sc.export(); + exportedServices.add(sc); + }); + asyncExportingFutures.add(future); + } else { + sc.export(); + exportedServices.add(sc); + } + }); + } +---- + +在这里可以清楚看到,Dubbo 通过 `org.apache.dubbo.config.ServiceConfig#export` 方法把服务暴露到注册中心的。由于这不是 Dubbo 源码分析,所以,实现细节就不再介绍了。 + +不知道大家有没有一个疑问:这里的 `configManager.getServices()` 是如何获取带业务实现类对象呢? + +要回答这个问题,需要查看一下 `configManager.getServices()` 返回的是 `Collection` 对象。我们就从 `ServiceConfigBase` 上找原因。经过研究发现, `ServiceConfigBase` 是 `org.apache.dubbo.config.AbstractConfig` 的子类,而 `AbstractConfig` 中有一个 `addIntoConfigManager` 方法如下: + + +.`org.apache.dubbo.config.AbstractConfig#addIntoConfigManager` +[{java_src_attr}] +---- + @PostConstruct + public void addIntoConfigManager() { + ApplicationModel.getConfigManager().addConfig(this); + } +---- + +阅读过 https://www.diguage.com/post/spring-bean-lifecycle-overview/[Spring Bean 生命周期概述^] 文章的朋友应该都清楚,使用 `@PostConstruct` 的方法会在 Bean 创建过程中,由 `AbstractAutowireCapableBeanFactory#invokeInitMethods` 方法来统一调用。所以,如果在上面这个方法中打断点,就可以看到调用过程了。 + +另外,这里给大家介绍一个小技巧:追本溯源,现在开始。从上面的 `configManager.getServices()` 开始,一步一步打开源代码就会发现, 这些数据是从 `org.apache.dubbo.config.context.ConfigManager#configsCache` 变量中获取的,那就在这个类中搜 `configsCache`,找到向这个变量添加元素的地方,会找到如下方法: + +.`org.apache.dubbo.config.context.ConfigManager#addConfig(AbstractConfig, boolean)` +[{java_src_attr}] +---- + protected void addConfig(AbstractConfig config, boolean unique) { + if (config == null) { + return; + } + write(() -> { + Map configsMap = configsCache.computeIfAbsent(getTagName(config.getClass()), type -> newMap()); + addIfAbsent(config, configsMap, unique); + }); + } +---- + +而且,整个类中,这一个地方是向 `configsCache` 变量添加元素的。在这个类打断点,你就看到所有添加的变量信息。再次启动服务提供者程序,你会发现上面提到的相关业务类 `ApplicationConfig`、 `ModuleConfig`、 `RegistryConfig`、 `ConfigCenterBean`、 `MetadataReportConfig`、 `MonitorConfig`、 `MetricsConfig`、 `SslConfig`、 `ProviderConfig`、 `ConsumerConfig`、 `ProtocolConfig`、 `ServiceBean` 和 `ReferenceBean` 都是 `AbstractConfig` 的子类。换句话说,这些类的实例都会注册到 `ConfigManager` 中。 + + +洋洋洒洒又写了好长好长。还有很多东西没写呢,比如 Dubbo 注解的集成实现,Dubbo 服务消费者的创建过程。限于篇幅原因,这些内容就放在下一篇文章介绍。 + +== Dubbo 生成服务消费者的过程 + +先来看看 XML 配置文件: + +.`dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-consumer/src/main/resources/spring/dubbo-consumer.xml` +[{java_src_attr}] +---- + + + + + + + + + + +---- + +我们先看一下 `ReferenceBean` 类的声明: + +.`org.apache.dubbo.config.spring.ReferenceBean` +[{java_src_attr}] +---- +public class ReferenceBean extends ReferenceConfig implements FactoryBean, + ApplicationContextAware, InitializingBean, DisposableBean { + + // 此处省略 N 行代码 + + @Override + public Object getObject() { + return get(); + } + + // 此处省略 N 行代码 + + @Override + @SuppressWarnings({"unchecked"}) + public void afterPropertiesSet() throws Exception { + + // Initializes Dubbo's Config Beans before @Reference bean autowiring + prepareDubboConfigBeans(); + + // lazy init by default. + if (init == null) { + init = false; + } + + // eager init if necessary. + if (shouldInit()) { + getObject(); + } + } + + // 此处省略 N 行代码 +} +---- + +这个类实现了 `FactoryBean` 接口,D瓜哥在 https://www.diguage.com/post/spring-extensions-overview/#factory-bean[Spring 扩展点概览及实践:FactoryBean] 中对 `FactoryBean` 介绍。所以,请在上面的 `getObject()` 打个断点。 + +另外,这个类还实现了 `InitializingBean`,D瓜哥在 https://www.diguage.com/post/spring-bean-lifecycle-overview/[Spring Bean 生命周期概述] 中介绍了这个接口的用途。不了解的,请移步。 + +启动服务消费者程序,开始调试代码。跳过上文结束的配置解析阶段,进入到 `org.apache.dubbo.config.bootstrap.DubboBootstrap#start` 方法中。在这里,它调用了内部私有方法 `referServices()`。但是,这个方法其实啥也没做。 + +上面提到,`ReferenceBean` 实现了 `FactoryBean` 接口,那么直接在 `org.apache.dubbo.config.spring.ReferenceBean#getObject` 方法上打断点。当调用 `applicationContext.getBean(XXX)` 时,就会触发断点,一路跟下去就会发现,现在 `org.apache.dubbo.config.ReferenceConfig#init` 方法中完成各种初始化准备工作,然后调用 `org.apache.dubbo.config.ReferenceConfig#createProxy` 方法创建代理。而实际代理的创建工作是由 `org.apache.dubbo.rpc.proxy.AbstractProxyFactory#getProxy(Invoker, boolean)` 方法创建的。这样说,也不算准确。因为 `AbstractProxyFactory` 对象是一个子类对象,子类是通过 Dubbo 的类 SPI 加载机制来动态选择创建的。 + +其实,Dubbo 服务消费者实例只是一个代理,通过代理封装统一的网络请求,实现 RPC 的调用过程。 + +== Dubbo 注解集成简述 + +使用 Dubbo 注解集成的入口是 `org.apache.dubbo.config.spring.context.annotation.EnableDubbo`,直接上代码: + + +.`org.apache.dubbo.config.spring.context.annotation.EnableDubbo` +[{java_src_attr}] +---- + +/** + * Enables Dubbo components as Spring Beans, equals + * {@link DubboComponentScan} and {@link EnableDubboConfig} combination. + *

+ * Note : {@link EnableDubbo} must base on Spring Framework 4.2 and above + * + * @see DubboComponentScan + * @see EnableDubboConfig + * @since 2.5.8 + */ +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@EnableDubboConfig +@DubboComponentScan +public @interface EnableDubbo { + + /** + * Base packages to scan for annotated @Service classes. + *

+ * Use {@link #scanBasePackageClasses()} for a type-safe alternative to String-based + * package names. + * + * @return the base packages to scan + * @see DubboComponentScan#basePackages() + */ + @AliasFor(annotation = DubboComponentScan.class, attribute = "basePackages") + String[] scanBasePackages() default {}; + + /** + * Type-safe alternative to {@link #scanBasePackages()} for specifying the packages to + * scan for annotated @Service classes. The package of each class specified will be + * scanned. + * + * @return classes from the base packages to scan + * @see DubboComponentScan#basePackageClasses + */ + @AliasFor(annotation = DubboComponentScan.class, attribute = "basePackageClasses") + Class[] scanBasePackageClasses() default {}; + + + /** + * It indicates whether {@link AbstractConfig} binding to multiple Spring Beans. + * + * @return the default value is true + * @see EnableDubboConfig#multiple() + */ + @AliasFor(annotation = EnableDubboConfig.class, attribute = "multiple") + boolean multipleConfig() default true; + +} +---- + +这个注解非常重要。一共有两点需要注意。这个方法就是注解的三个属性,分别给出了三个最重要的参数: + +. `scanBasePackages` -- 定义了基础扫描的包。通过 `@AliasFor` 注解表明,这是定义 `@DubboComponentScan` 注解的 `basePackages` 属性。 +. `scanBasePackageClasses` -- 定义扫描的基础类。通过 `@AliasFor` 注解表明,这是定义 `@DubboComponentScan` 注解的 `basePackageClasses` 属性。 +. `multipleConfig` -- 可以将 `AbstractConfig`(上一篇文章 https://www.diguage.com/post/spring-extensions-and-dubbo-1/[Spring 扩展点实践:整合 Apache Dubbo(一)] 已经做过说明) 向 Spring 中多次注册。换句话说,你可以配置多个注册中心,配置多个监控中心等等。通过 `@AliasFor` 注解表明,这是定义 `@EnableDubboConfig` 注解的 `multiple` 属性,默认为 `true`。 + +接下来,让我们看看非常重要的两点内容。 + +=== `@EnableDubboConfig` + +`@EnableDubbo` 注解上面加了 `@EnableDubboConfig` 注解,我们来看一下它的源码: + +.`org.apache.dubbo.config.spring.context.annotation.EnableDubboConfig` +[{java_src_attr}] +---- +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@Import(DubboConfigConfigurationRegistrar.class) +public @interface EnableDubboConfig { + + /** + * It indicates whether binding to multiple Spring Beans. + * + * @return the default value is true + * @revised 2.5.9 + */ + boolean multiple() default true; + +} +---- + +这里,我们看到了熟悉的 `@Import`。 `DubboConfigConfigurationRegistrar` 从名字就能看出应该是实现了 `ImportBeanDefinitionRegistrar` 接口的,打开代码,果然如此。更 + +在 https://www.diguage.com/post/spring-extensions-overview/[Spring 扩展点概览及实践] 和 https://www.diguage.com/post/spring-extensions-and-mybatis/[Spring 扩展点实践:整合 MyBATIS] 中有针对 `@Import` 和 `ImportBeanDefinitionRegistrar` 的详细介绍。尤其是 MyBATIS 就是使用 `ImportBeanDefinitionRegistrar` 来做扩展的。不懂的,请移步。 + +关于 `DubboConfigConfigurationRegistrar` 的功能,这里做个简要总结: + +. 使用 `@EnableConfigurationBeanBindings` 注解,将配置项和对一个的 Bean 类型做一个绑定。如果 `multiple` 属性为 `true`,则指出多次注册。 +. 调用 `org.apache.dubbo.config.spring.util.DubboBeanUtils#registerCommonBeans` 方法,将公共的 Bean 注册到 Spring 中。这部分内容在 https://www.diguage.com/post/spring-extensions-and-dubbo-1/#register-common-beans[Spring 扩展点实践:整合 Apache Dubbo(一):registerCommonBeans] 中已经给出了详细介绍,就不再赘述。 + +=== `@DubboComponentScan` + +`@EnableDubbo` 注解上面加了 `@DubboComponentScan` 注解,直接上代码: + + +.`org.apache.dubbo.config.spring.context.annotation.DubboComponentScan` +[{java_src_attr}] +---- +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Import(DubboComponentScanRegistrar.class) +public @interface DubboComponentScan { + + /** + * Alias for the {@link #basePackages()} attribute. Allows for more concise annotation + * declarations e.g.: {@code @DubboComponentScan("org.my.pkg")} instead of + * {@code @DubboComponentScan(basePackages="org.my.pkg")}. + * + * @return the base packages to scan + */ + String[] value() default {}; + + /** + * Base packages to scan for annotated @Service classes. {@link #value()} is an + * alias for (and mutually exclusive with) this attribute. + *

+ * Use {@link #basePackageClasses()} for a type-safe alternative to String-based + * package names. + * + * @return the base packages to scan + */ + String[] basePackages() default {}; + + /** + * Type-safe alternative to {@link #basePackages()} for specifying the packages to + * scan for annotated @Service classes. The package of each class specified will be + * scanned. + * + * @return classes from the base packages to scan + */ + Class[] basePackageClasses() default {}; + +} +---- + +又双叒叕看到了 `@Import`;又双叒叕看到了 `Registrar`,只是这次名字叫 `DubboComponentScanRegistrar`。跟上面的一样,不再赘述。 + +这里总结一下 `DubboComponentScanRegistrar` 的功能:注册了一个类为 `ServiceAnnotationBeanPostProcessor` 的 `BeanDefinition`,将配置项的配置信息传递给这个 `BeanDefinition` 实例。 `ServiceAnnotationBeanPostProcessor` 实现了 `BeanDefinitionRegistryPostProcessor` 接口,会在 Spring 的启动过程中,通过调用 `postProcessBeanDefinitionRegistry` 方法来注册相关的 `BeanDefinition`。关于这部分内容,请移步: https://www.diguage.com/post/spring-aop-process-overview/[Spring AOP 处理流程概述]。 + +在 Spring 启动过程中,就会调用 `ServiceAnnotationBeanPostProcessor` 的 `postProcessBeanDefinitionRegistry` 方法,在这个方法中,通过创建 `DubboClassPathBeanDefinitionScanner` (继承了 `ClassPathBeanDefinitionScanner` 类)实例,调用 `scanner.scan(packageToScan)` 来注册 `BeanDefinition`。另外,有一点需要指出的是: `ServiceAnnotationBeanPostProcessor` 目前是 `@Deprecated`,后续推荐使用 `ServiceClassPostProcessor`,而 `ServiceAnnotationBeanPostProcessor` 就是 `ServiceClassPostProcessor` 的子类。所以,目前处理逻辑都集中在了 `ServiceClassPostProcessor` 中。 + +关于 Apache Dubbo 与 Spring 的整合原理就全部介绍完毕了。如有什么问题,欢迎留言讨论。以后有时间,写写分布式事务解决方案 Seata 的一些原理。 + + +// == Apache Dubbo Consumer Service Bean 的创建 + +// . `ReferenceAnnotationBeanPostProcessor` + + +// [source,java,{source_attr}] +// ---- +// 这是啥? +// ---- + +// image::/images/spring-framework/dubbo-logo.jpg[{image_attr}] + + + +// . `org.apache.dubbo.config.spring.context.annotation.EnableDubbo` + +// . `org.apache.dubbo.config.spring.context.annotation.EnableDubboConfig` +// . `org.apache.dubbo.config.spring.context.annotation.DubboConfigConfigurationRegistrar` + + +// . `org.apache.dubbo.config.spring.context.annotation.DubboComponentScan` +// . `org.apache.dubbo.config.spring.context.annotation.DubboComponentScanRegistrar` + + +// . `org.apache.dubbo.config.spring.beans.factory.config.DubboConfigDefaultPropertyValueBeanPostProcessor` +// . `org.apache.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor` +// . `org.apache.dubbo.xml.rpc.protocol.xmlrpc.XmlRpcProxyFactoryBean` + +// . `org.apache.dubbo.config.spring.beans.factory.annotation.ServiceAnnotationBeanPostProcessor` -- 弃用,推荐 `ServiceClassPostProcessor`。 +// . `org.apache.dubbo.config.spring.beans.factory.annotation.ServiceClassPostProcessor` + +// . `org.apache.dubbo.config.spring.schema.DubboBeanDefinitionParser` + +// . `org.apache.dubbo.config.spring.beans.factory.annotation.DubboConfigAliasPostProcessor` + + +// === Seata 与 Spring 整合 + +// . `io.seata.config.springcloud.EnableSeataSpringConfig` +// . `io.seata.config.springcloud.SpringApplicationContextProviderRegistrar` +// . `HttpAutoConfiguration` +// . `RequiredAnnotationBeanPostProcessor` +// . `SpringCacheAnnotationParser` \ No newline at end of file diff --git a/truman/src/docs/asciidoc/extensions-and-mybatis.adoc b/truman/src/docs/asciidoc/extensions-and-mybatis.adoc new file mode 100644 index 000000000000..60678bbeb40b --- /dev/null +++ b/truman/src/docs/asciidoc/extensions-and-mybatis.adoc @@ -0,0 +1,513 @@ +[#mybatis] += 整合 MyBATIS + +Spring 与 MyBATIS 的整合并不是 Spring 实现的,而且由 MyBATIS 项目组提供的。通过这个整合,也可以学习一下如何提供整合自己的类型框架。 + +在上一篇文章 https://www.diguage.com/post/spring-extensions-overview/[Spring 扩展点概览及实践^] 中介绍了 Spring 内部存在的扩展点。学以致用,现在来分析一下 Spring 与 MyBATIS 的整合流程。 + +== 示例程序 + +为了方便分析源码,先根据官方文档 https://mybatis.org/spring/getting-started.html[mybatis-spring – MyBatis-Spring | Getting Started^] 搭建起一个简单实例。 + +数据库方面,直接使用功能了 MySQL 示例数据库: https://dev.mysql.com/doc/employee/en/[MySQL : Employees Sample Database^],需要的话,自行下载。 + +[#SpringMybatisTest] +.SpringMybatisTest +[{java_src_attr}] +---- +include::{truman_src_dir}/mybatis/SpringMybatisTest.java[] +---- + +[#EmployeesMapper] +.EmployeesMapper +[{java_src_attr}] +---- +include::{truman_src_dir}/mybatis/EmployeesMapper.java[] +---- + +[#EmployeesMapper] +.EmployeesMapper +[{java_src_attr}] +---- +include::{truman_src_dir}/mybatis/MapperAop.java[] +---- + +[#Employees] +.Employees +[{java_src_attr}] +---- +include::{truman_src_dir}/mybatis/Employees.java[] +---- + +整个实例代码中,只有 `@MapperScan(basePackages = "com.diguage.truman.mybatis")` 这个注解和 MyBATIS 的配置相关,我们就从这里开始吧。 + +[#mapper-scan] +== `@MapperScan` 处理 + +D瓜哥在 https://www.diguage.com/post/spring-extensions-overview/#bean-definition-registry-post-processor[Spring 扩展点概览及实践:BeanDefinitionRegistryPostProcessor^] 中已经指出 `ConfigurationClassPostProcessor` 负责处理 `@Configuration` 注解。所以,可以直接去看这个类的代码。 + +`ConfigurationClassPostProcessor` 的处理流程都是在 `processConfigBeanDefinitions(BeanDefinitionRegistry registry)` 方法中完成的。在这个方法中,可以看到如下代码: + + +.`ConfigurationClassPostProcessor#processConfigBeanDefinitions` +[{java_src_attr},highlight=68..80] +---- +include::{context_src_dir}/context/annotation/ConfigurationClassPostProcessor.java[tag=processConfigBeanDefinitions] +---- + +在 `parser.parse(candidates);` 这行代码打一个断点,然后一步一步跟下去,就到了 `ConfigurationClassParser` 的 `doProcessConfigurationClass` 方法里,重点关注 `processImports` 这行: + +.`ConfigurationClassParser#doProcessConfigurationClass` +[{java_src_attr},highlight=53..56] +---- +include::{context_src_dir}/context/annotation/ConfigurationClassParser.java[tag=doProcessConfigurationClass] +---- + +请注意这里的 `getImports(sourceClass)`,我们看一下这个方法: + +[{java_src_attr}] +---- +include::{context_src_dir}/context/annotation/ConfigurationClassParser.java[tag=getImports] + +include::{context_src_dir}/context/annotation/ConfigurationClassParser.java[tag=collectImports] +---- + +在 `String annName = annotation.getMetadata().getClassName();` 这行代码打断点,然后调试,注意观察 `annName` 变量的值,相信肯定可以看到 `org.mybatis.spring.annotation.MapperScan`,接着就可以看到,通过 `sourceClass.getAnnotationAttributes(Import.class.getName(), "value")` 解析 `@Import` 注解,把其中的 `org.mybatis.spring.annotation.MapperScannerRegistrar` 的相关信息(被封装成了 `SourceClass` 对象)加入到了 `imports` 变量中。 + +下面看一下是如何处理 `MapperScannerRegistrar` 的。 + +== `MapperScannerRegistrar` + +我们接着看 `processImports` 方法: + +[{java_src_attr}] +---- + private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass, + Collection importCandidates, Predicate exclusionFilter, + boolean checkForCircularImports) { + + //...此处省去 N 行代码 + else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) { + // 很明显,会进入到这个分支 + // Candidate class is an ImportBeanDefinitionRegistrar -> + // delegate to it to register additional bean definitions + Class candidateClass = candidate.loadClass(); + ImportBeanDefinitionRegistrar registrar = + ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class, + this.environment, this.resourceLoader, this.registry); + // 创建一个实例,然后加入到 configClass 中 + configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata()); + //...此处省去 N 行代码 + } +---- + +接着,回到 `processConfigBeanDefinitions` 方法: + +.`ConfigurationClassPostProcessor#processConfigBeanDefinitions` +[{java_src_attr},highlight=79..91] +---- +include::{context_src_dir}/context/annotation/ConfigurationClassPostProcessor.java[tag=processConfigBeanDefinitions] +---- + +进入 `this.reader.loadBeanDefinitions(configClasses);` 方法: + +.`ConfigurationClassBeanDefinitionReader#loadBeanDefinitions` +[{java_src_attr}] +---- +include::{context_src_dir}/context/annotation/ConfigurationClassBeanDefinitionReader.java[tag=loadBeanDefinitions] + +include::{context_src_dir}/context/annotation/ConfigurationClassBeanDefinitionReader.java[tag=loadBeanDefinitionsForConfigurationClass] + +include::{context_src_dir}/context/annotation/ConfigurationClassBeanDefinitionReader.java[tag=loadBeanDefinitionsFromImportedResources] + +include::{context_src_dir}/context/annotation/ConfigurationClassBeanDefinitionReader.java[tag=loadBeanDefinitionsFromRegistrars] +---- + +到这里就调用到了 `MapperScannerRegistrar` 的 `registerBeanDefinitions` 方法: + +.`MapperScannerRegistrar#registerBeanDefinitions(AnnotationMetadata, BeanDefinitionRegistry)` +[{java_src_attr}] +---- + /** + * {@inheritDoc} + */ + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { + AnnotationAttributes mapperScanAttrs = AnnotationAttributes + .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName())); + if (mapperScanAttrs != null) { + registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry, + generateBaseBeanName(importingClassMetadata, 0)); + } + } + + void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs, + BeanDefinitionRegistry registry, String beanName) { + + // 注意这行代码: + BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class); + builder.addPropertyValue("processPropertyPlaceHolders", true); + + Class annotationClass = annoAttrs.getClass("annotationClass"); + if (!Annotation.class.equals(annotationClass)) { + builder.addPropertyValue("annotationClass", annotationClass); + } + + Class markerInterface = annoAttrs.getClass("markerInterface"); + if (!Class.class.equals(markerInterface)) { + builder.addPropertyValue("markerInterface", markerInterface); + } + + Class generatorClass = annoAttrs.getClass("nameGenerator"); + if (!BeanNameGenerator.class.equals(generatorClass)) { + builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass)); + } + + Class mapperFactoryBeanClass = annoAttrs.getClass("factoryBean"); + if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) { + builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass); + } + + String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef"); + if (StringUtils.hasText(sqlSessionTemplateRef)) { + builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef")); + } + + String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef"); + if (StringUtils.hasText(sqlSessionFactoryRef)) { + builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef")); + } + + List basePackages = new ArrayList<>(); + basePackages.addAll( + Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList())); + + basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText) + .collect(Collectors.toList())); + + basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName) + .collect(Collectors.toList())); + + if (basePackages.isEmpty()) { + basePackages.add(getDefaultBasePackage(annoMeta)); + } + + String lazyInitialization = annoAttrs.getString("lazyInitialization"); + if (StringUtils.hasText(lazyInitialization)) { + builder.addPropertyValue("lazyInitialization", lazyInitialization); + } + + builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages)); + + registry.registerBeanDefinition(beanName, builder.getBeanDefinition()); + + } +---- + +其实只干了一件事情,就是在想容器中注册了一个类为 `MapperScannerConfigurer` 的 `BeanDefinition`,在创建过程中,还把 `@MapperScan` 注解中的属性给添加到了 `BeanDefinition` 属性中。下面,来看看 `MapperScannerConfigurer` 是何方神圣。 + +== `MapperScannerConfigurer` + +先看一下 `MapperScannerConfigurer` 的类型定义: + +[{java_src_attr}] +---- +public class MapperScannerConfigurer + implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware { +---- + +结合上一篇文章 https://www.diguage.com/post/spring-extensions-overview/#bean-definition-registry-post-processor[Spring 扩展点概览及实践:BeanDefinitionRegistryPostProcessor^] 中的介绍,可以知道 `BeanDefinitionRegistryPostProcessor` 也是 Spring 生命周期中的一环,将其注册到容器中,就可以通过对 `postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)` 来实现注册自定义 `BeanDefinition` 的功能。 + +来看看 `postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)` 的定义: + +.`MapperScannerConfigurer#postProcessBeanDefinitionRegistry` +[{java_src_attr}] +---- + @Override + public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { + if (this.processPropertyPlaceHolders) { + processPropertyPlaceHolders(); + } + + ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); + scanner.setAddToConfig(this.addToConfig); + scanner.setAnnotationClass(this.annotationClass); + scanner.setMarkerInterface(this.markerInterface); + scanner.setSqlSessionFactory(this.sqlSessionFactory); + scanner.setSqlSessionTemplate(this.sqlSessionTemplate); + scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName); + scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName); + scanner.setResourceLoader(this.applicationContext); + scanner.setBeanNameGenerator(this.nameGenerator); + scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass); + if (StringUtils.hasText(lazyInitialization)) { + scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization)); + } + scanner.registerFilters(); + scanner.scan( + StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); + } +---- + +代码已经非常明确了,就是注册了一个 `ClassPathMapperScanner`,同事调用了 `scanner.scan` 方法。下面,来看一下 `ClassPathMapperScanner`。 + +== `ClassPathMapperScanner` + +老规矩,先看看 `ClassPathMapperScanner` 的定义: + +[{java_src_attr}] +---- +public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner { + + //...此处省去 N 行代码 + + private Class mapperFactoryBeanClass = MapperFactoryBean.class; + + public ClassPathMapperScanner(BeanDefinitionRegistry registry) { + super(registry, false); + } +---- + +从这里可以看出,`ClassPathMapperScanner` 就是一个 `ClassPathBeanDefinitionScanner`,根据类名可以得知,扫描 `class path` 并生成 `BeanDefinition`。来看一下 `scan(String... basePackages)` + +.`ClassPathBeanDefinitionScanner#scan` +[{java_src_attr}] +---- +include::{context_src_dir}/context/annotation/ClassPathBeanDefinitionScanner.java[tag=scan] +---- + +这里把实际扫描工作委托给了 `doScan(basePackages)` 方法,而这个方法被 `ClassPathMapperScanner` 重写了,来看一下它的实现: + +.`ClassPathMapperScanner#doScan` +[{java_src_attr}] +---- + /** + * Calls the parent search that will search and register all the candidates. Then the registered objects are post + * processed to set them as MapperFactoryBeans + */ + @Override + public Set doScan(String... basePackages) { + Set beanDefinitions = super.doScan(basePackages); + + if (beanDefinitions.isEmpty()) { + LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + + "' package. Please check your configuration."); + } else { + processBeanDefinitions(beanDefinitions); + } + + return beanDefinitions; + } +---- + +实际的扫描工作还是由父类 `super.doScan(basePackages)` 完成,只是又对扫描结果做了进一步处理: `processBeanDefinitions(beanDefinitions)`。 + +.`ClassPathMapperScanner#processBeanDefinitions` +[{java_src_attr}] +---- + private void processBeanDefinitions(Set beanDefinitions) { + GenericBeanDefinition definition; + for (BeanDefinitionHolder holder : beanDefinitions) { + definition = (GenericBeanDefinition) holder.getBeanDefinition(); + String beanClassName = definition.getBeanClassName(); + LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName + + "' mapperInterface"); + + // the mapper interface is the original class of the bean + // but, the actual class of the bean is MapperFactoryBean + // 注意这行代码 + definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59 + // 注意这行代码 + definition.setBeanClass(this.mapperFactoryBeanClass); + + definition.getPropertyValues().add("addToConfig", this.addToConfig); + + boolean explicitFactoryUsed = false; + if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) { + definition.getPropertyValues().add("sqlSessionFactory", + new RuntimeBeanReference(this.sqlSessionFactoryBeanName)); + explicitFactoryUsed = true; + } else if (this.sqlSessionFactory != null) { + definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory); + explicitFactoryUsed = true; + } + + if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) { + if (explicitFactoryUsed) { + LOGGER.warn( + () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored."); + } + definition.getPropertyValues().add("sqlSessionTemplate", + new RuntimeBeanReference(this.sqlSessionTemplateBeanName)); + explicitFactoryUsed = true; + } else if (this.sqlSessionTemplate != null) { + if (explicitFactoryUsed) { + LOGGER.warn( + () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored."); + } + definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate); + explicitFactoryUsed = true; + } + + if (!explicitFactoryUsed) { + LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'."); + definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); + } + definition.setLazyInit(lazyInitialization); + } + } +---- + +这里特别需要注意的是 `definition.setBeanClass(this.mapperFactoryBeanClass);` 这行代码。为什么把扫描出来的 `Mapper` 的 `Bean Class` 给设置成 `mapperFactoryBeanClass` 呢?通过上面的 `ClassPathMapperScanner` 类型定义可以知道,`mapperFactoryBeanClass` 就是 `MapperFactoryBean`。 + +另外,还有一点值得思考,扫描出来的是接口,怎么生成对应的实例呢?带着这两个问题,来看一下 `MapperFactoryBean`。 + +== `MapperFactoryBean` + +来看一下 `MapperFactoryBean` 的类型定义: + +[{java_src_attr}] +---- +public class MapperFactoryBean extends SqlSessionDaoSupport implements FactoryBean { + + private Class mapperInterface; + + private boolean addToConfig = true; + + public MapperFactoryBean() { + // intentionally empty + } + + public MapperFactoryBean(Class mapperInterface) { + this.mapperInterface = mapperInterface; + } + + /** + * {@inheritDoc} + */ + @Override + protected void checkDaoConfig() { + super.checkDaoConfig(); + + notNull(this.mapperInterface, "Property 'mapperInterface' is required"); + + Configuration configuration = getSqlSession().getConfiguration(); + if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) { + try { + configuration.addMapper(this.mapperInterface); + } catch (Exception e) { + logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e); + throw new IllegalArgumentException(e); + } finally { + ErrorContext.instance().reset(); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public T getObject() throws Exception { + return getSqlSession().getMapper(this.mapperInterface); + } +---- + +可以看出 `MapperFactoryBean` 是一个 `FactoryBean`,上一篇文章 https://www.diguage.com/post/spring-extensions-overview/#factory-bean[Spring 扩展点概览及实践:FactoryBean^] 中提到,`FactoryBean` 就是专门生产 Bean 的工厂。 + +再看构造函数 `public MapperFactoryBean(Class mapperInterface)`,结合上一个片段代码中注意的地方可以看出,从 `Class Path` 扫描出来的 `BeanDefinition`,把扫描出来的接口设置为构造函数参数 `definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);` 然后通过实例化 `FactoryBean`,然后调用 `getObject()` 就可以获得接口对应的实例对象。 + +实例化对象的过程是由 MyBATIS 完成的,以后单独开篇来介绍,这里不再多做介绍。 + +还有个疑问,MyBATIS 是怎么知道 Mapper 接口信息呢?这个问题就要看 `checkDaoConfig()` 方法了,单步调试代码可以知道父类 `DaoSupport#afterPropertiesSet` 调用的,在这个方法中,把 Mapper 接口信息条件到了 MyBATIS 中 `configuration.addMapper(this.mapperInterface)`。 + +自此,MyBATIS 和 Spring 的整个流程就全部介绍完毕了。下面做个小节。 + +== 小节 + +本文从源码角度,深入绍了 MyBATIS 和 Spring 整合过程。整个过程中,用到了 Spring 的如下扩展点: + +. `@Import` +. `MapperScannerRegistrar` - `ImportBeanDefinitionRegistrar` +. `MapperScannerConfigurer` - `BeanDefinitionRegistryPostProcessor` +. `ClassPathMapperScanner` - `ClassPathBeanDefinitionScanner` +. `MapperFactoryBean` - `FactoryBean` +. `InitializingBean` + +可见,和 Spring 整合并不是只靠一个扩展点就可以完成的,需要多个扩展点多方配合才能更好地完成整合过程。 + +== 为什么在 Spring+MyBATIS 时,一级缓存失效? + +在原生 MyBATIS 实现中,在执行查询时,使用的 `SqlSession` 是 `DefaultSqlSession`, `DefaultSqlSession` 实例是在执行 `SqlSession session = sqlSessionFactory.openSession();` 时创建的。执行查询操作也是在 `DefaultSqlSession.selectList(String, Object, RowBounds, ResultHandler)` 中完成的。 + +.使用 MyBAITS 原生查询 +[{java_src_attr}] +---- +include::{truman_src_dir}/mybatis/MybatisTest.java[tag=testCacheQuery] + +include::{truman_src_dir}/mybatis/MybatisTest.java[tag=getDataSource] +---- + +Spring 的示例请看 <>、 <>、 <>。 + +在 Spring + MyBATIS 搭配中,在执行查询时,使用的 `SqlSession` 是 `SqlSessionTemplate`(由“mybatis-spring”实现)。而 `SqlSessionTemplate` 的查询执行是委托给 `SqlSessionTemplate.sqlSessionProxy`(`SqlSession` 类型)来操作。 `SqlSessionTemplate.sqlSessionProxy` 是通过动态代理创建出来的代理实例。在代理实现内部执行时,从创建 `SqlSessionTemplate` 实例时经构造函数传入的 `SqlSessionFactory` 对象中获取 `SqlSession` 对象(创建过程与原生 MyBATIS 的构造过程相同)。最后,再去执行查询操作。 + +在创建 `SqlSessionTemplate.sqlSessionProxy` 代理时,代理切面在执行完查询后,执行了 `closeSqlSession` 操作。正是因为执行了次操作,导致了一级缓存失效。 + +.org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor +[{java_src_attr},highlight=34] +---- + /** + * Proxy needed to route MyBatis method calls to the proper SqlSession got from Spring's Transaction Manager It also + * unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to pass a {@code PersistenceException} to the + * {@code PersistenceExceptionTranslator}. + */ + private class SqlSessionInterceptor implements InvocationHandler { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, + SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); + try { + Object result = method.invoke(sqlSession, args); + if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) { + // force commit even on non-dirty sessions because some databases require + // a commit/rollback before calling close() + sqlSession.commit(true); + } + return result; + } catch (Throwable t) { + Throwable unwrapped = unwrapThrowable(t); + if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) { + // release the connection to avoid a deadlock if the translator is no loaded. See issue #22 + closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); + sqlSession = null; + Throwable translated = SqlSessionTemplate.this.exceptionTranslator + .translateExceptionIfPossible((PersistenceException) unwrapped); + if (translated != null) { + unwrapped = translated; + } + } + throw unwrapped; + } finally { + if (sqlSession != null) { + closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); + } + } + } + } +---- + +为什么要关闭 `SqlSession`?因为 Spring 没有把 `SqlSession` 实例暴露给用户,那么用户不能控制 `SqlSession` 的关闭操作。所以,在执行完查询操作后,就马上关闭 `SqlSession` 是一个比较合理的操作。 + +在 Spring + MyBATIS 中, `Mapper` 的信息什么时候加入到 `Configuration` 的? // TODO + +// == `MapperProxyFactory` + +// == `MapperProxy` + +== 参考资料 + +. https://tech.meituan.com/2018/01/19/mybatis-cache.html[聊聊MyBatis缓存机制^] -- 文章写的很好,值得认真阅读! +. https://www.cnblogs.com/java-chen-hao/p/11833780.html[Mybaits 源码解析 (十)- Spring-Mybatis框架使用与源码解析^] +. https://juejin.im/post/5dbff6fae51d455c042008e6[Mybatis源码解析(一) — mybatis与Spring是如何整合的?^] + diff --git a/truman/src/docs/asciidoc/extensions-overview.adoc b/truman/src/docs/asciidoc/extensions-overview.adoc new file mode 100644 index 000000000000..e4814d95f9e3 --- /dev/null +++ b/truman/src/docs/asciidoc/extensions-overview.adoc @@ -0,0 +1,578 @@ += 扩展点概览及实践 + +学习 Spring 代码,最重要的是掌握 Spring 有哪些扩展点,可以利用这些扩展点对 Spring 做什么扩展操作。说得更具体一点,如果自己开发一个框架,如何与 Spring 进行整合,如果对 Spring 的扩展点有一个比较清晰的认识,势必会事半功倍。 + +== `@Import` + +先来看一下 `@Import` 注解的定义: + +[{java_src_attr}] +---- +include::{context_src_dir}/context/annotation/Import.java[] +---- + +从声明可以看出,使用时,只需要指定 `Class` 实例即可;从方法的文档中可以看出,`Class` 实例可以分为三种:`ImportSelector`、`ImportBeanDefinitionRegistrar` 和常规组件类。示例如下: + +[{java_src_attr}] +---- +@Configuration +@Import(LogImportSelector.class) +public static class Config { +} +---- + +在 `org.springframework.context.annotation.ConfigurationClassParser#processImports` 方法中,集中了对 `@Import` 注解的处理。从代码可以非常清晰地看出,分了三种情况进行处理: + +. `ImportSelector` +. `ImportBeanDefinitionRegistrar` +. 常规组件 `Class` + +下面分别对其进行介绍。 + +=== `ImportSelector` + +先来看一下 `ImportSelector` 接口的定义: + +[{java_src_attr}] +---- +include::{context_src_dir}/context/annotation/ImportSelector.java[] +---- + +从接口文档中就可以看出,使用 `String[] selectImports(AnnotationMetadata importingClassMetadata)` 方法,返回所需要引入的类全限定名即可。实例如下: + +[{java_src_attr}] +---- +public class LogImportSelector implements ImportSelector { + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + return new String[]{ + UserDao.class.getName(), + UserService.class.getName(), + ProtoService.class.getName() + }; + } +} +---- + +=== `ImportBeanDefinitionRegistrar` + +先来看一下 `ImportBeanDefinitionRegistrar` 接口的定义: + +[{java_src_attr}] +---- +include::{context_src_dir}/context/annotation/ImportBeanDefinitionRegistrar.java[] +---- + +这里使用到了 `BeanDefinitionRegistry` 接口,来看一下这个接口的定义: + +[{java_src_attr}] +---- +include::{beans_src_dir}/factory/support/BeanDefinitionRegistry.java[] +---- + +很明显,可以通过 `registerBeanDefinition(String beanName, BeanDefinition beanDefinition)` 方法,向容器在中注入所需要的 `BeanDefinition`,而 `BeanDefinition` 是常见的 Bean 实例的基石。示例如下: + +[{java_src_attr}] +---- +public class LogImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, + BeanDefinitionRegistry registry) { + RootBeanDefinition definition = new RootBeanDefinition(UserService.class); + registry.registerBeanDefinition(UserService.class.getName(), definition); + } +} +---- + +=== 常规组件 `Class` + +这是最简单的情况,直接举例: + +[{java_src_attr}] +---- +@Configuration +@Import(UserService.class) +public static class Config { +} +---- + +[#bean-definition-registry-post-processor] +== `BeanDefinitionRegistryPostProcessor` + +先来看一下 `BeanDefinitionRegistryPostProcessor` 的定义: + +[{java_src_attr}] +---- +include::{beans_src_dir}/factory/support/BeanDefinitionRegistryPostProcessor.java[] +---- + +这个接口扩展了标准的 `BeanFactoryPostProcessor` 接口,允许在普通的 `BeanFactoryPostProcessor` 接口实现类执行之前注册更多的 `BeanDefinition`。特别地是,`BeanDefinitionRegistryPostProcessor` 可以注册 `BeanFactoryPostProcessor` 的 `BeanDefinition`。 + +`postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)` 方法可以修改在 `BeanDefinitionRegistry` 接口实现类中注册的任意 `BeanDefinition`,也可以增加和删除 `BeanDefinition`。原因是这个方法执行前,所有常规的 `BeanDefinition` 已经被加载到 `BeanDefinitionRegistry` 接口实现类中,但还没有bean被实例化。 + +实例如下: + +[{java_src_attr}] +---- +public class LogBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor { + @Override + public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { + System.out.println(getAndIncrement() + + "LogBeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry\n"); + RootBeanDefinition beanDefinition = new RootBeanDefinition(LogBeanFactoryPostProcessor.class); + registry.registerBeanDefinition(beanDefinition.getBeanClassName(), beanDefinition); + } + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + System.out.println(getAndIncrement() + + "LogBeanDefinitionRegistryPostProcessor.postProcessBeanFactory\n"); + } +} +---- + +`BeanDefinitionRegistryPostProcessor` 在 Spring 内部的使用,最重要的示例就是 `ConfigurationClassPostProcessor`,这个类负责解析 `@Import` 和 `@Configuration` 等注解。感兴趣可以认真研究一下这个类的代码。 + + +[#bean-factory-post-processor] +== `BeanFactoryPostProcessor` + +`BeanFactory` 生成后,如果想对 `BeanFactory` 进行一些处理,该怎么办呢?`BeanFactoryPostProcessor` 接口就是用来处理 `BeanFactory` 的。 + +先来看一下接口定义: + +[{java_src_attr}] +---- +include::{beans_src_dir}/factory/config/BeanFactoryPostProcessor.java[] +---- + +若 IoC 容器内添加了实现了 `BeanFactoryPostProcessor` 接口的实现类 Bean,那么在该容器中实例化任何其他 Bean 之前可以回调该 Bean 中的 `postPrcessorBeanFactory()` 方法来对 Bean 的配置元数据进行更改,比如设置 `init-method`,或者将 `Scope` 从 `SINGLETON` 改为 `PROTOTYPE`。示例如下: + +[{java_src_attr}] +---- +public class LogBeanFactoryPostProcessor implements BeanFactoryPostProcessor { + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + System.out.println(getAndIncrement() + + "LogBeanFactoryPostProcessor.postProcessBeanFactory\n"); + System.out.println(Arrays.toString(beanFactory.getBeanDefinitionNames()).replaceAll(",", ",\n")); + BeanDefinition definition = beanFactory.getBeanDefinition(UserService.class.getName()); + // 设置 init 方法 + definition.setInitMethodName("init"); + } +} +---- + +在代码 `org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors` 中,集中了对 `BeanFactoryPostProcessor` 的调用。该方法把处理过程,委托给了 `org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory, java.util.List)` 方法来处理。根据代码可以整理出处理流程如下: + +. 如果 `beanFactory` 是一个 `BeanDefinitionRegistry` 实例,则: +.. 首先处理参数传过来的 `List beanFactoryPostProcessors` 对象 +... 如果 `postProcessor` 是 `BeanDefinitionRegistryPostProcessor` 实现类,则直接调用 `postProcessBeanDefinitionRegistry`,然后加入到 `List registryProcessors` 列表中; +... 如果不是,则加入到 `List regularPostProcessors` 列表中; +.. 从 `BeanFactory` 中通过 `beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false)` 方法获取 `BeanDefinitionRegistryPostProcessor` 名称列表。筛选出实现了 `PriorityOrdered` 接口的实例,然后排序再逐一调用 `postProcessBeanDefinitionRegistry` 方法。最后,加入到 `List registryProcessors` 列表中。 +.. 从 `BeanFactory` 中通过 `beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false)` 方法获取 `BeanDefinitionRegistryPostProcessor` 名称列表。筛选出实现了 `Ordered` 接口的实例,然后排序再逐一调用 `postProcessBeanDefinitionRegistry` 方法。最后,加入到 `List registryProcessors` 列表中。(注意:上一步已经调用过的则不再重复调用。) +.. 从 `BeanFactory` 中通过 `beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false)` 方法获取 `BeanDefinitionRegistryPostProcessor` 名称列表。剔除掉前两步调用过的类,排序再逐一调用 `postProcessBeanDefinitionRegistry` 方法。最后,加入到 `List registryProcessors` 列表中。要强调的一点是:这里是通过一个循环来反复执行这一步,D瓜哥认为是在调用 `postProcessBeanDefinitionRegistry` 方法中,有会参数新注册的 `BeanDefinitionRegistryPostProcessor`,所以需要反复调用。大家如果有不同见解,也欢迎留言讨论。 +.. 调用 `BeanDefinitionRegistryPostProcessor` 对象的 `postProcessBeanFactory` 方法; +.. 调用 `BeanFactoryPostProcessor` 对象的 `postProcessBeanFactory` 方法; +. 如果 `beanFactory` 不是 `BeanDefinitionRegistry` 实例,则直接调用 `BeanFactoryPostProcessor` 对象的 `postProcessBeanFactory` 方法; +. 从 `BeanFactory` 中通过 `beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false)` 方法获取 `BeanFactoryPostProcessor` 名称列表。将其分为: +.. 实现 `PriorityOrdered` 接口的实例 +.. 实现 `Ordered` 接口的实例 +.. 未排序的实例 ++ +按照这个顺序,排除已经处理过的实例,再分类,然后排序再跟着这个顺序依次逐一调用 `BeanFactoryPostProcessor` 对象的 `postProcessBeanFactory` 方法; ++ +. 最后,向 `BeanFactory` 注册 `ApplicationListenerDetector` 实例。 + + +== `InstantiationAwareBeanPostProcessor` + +注意区分 *`Instantiation`* 和 *`Initialization`*。 + +* *`Instantiation`* -- 实例化,在实例化之前还没有生成对象。 +* *`Initialization`* -- 初始化,对象已经生成,需要对其做进一步的处理,比如赋值等。 + +[#factory-bean] +== `FactoryBean` + +在对象生成上,有时也许需要做些特殊处理。比如,创建对象过程比较繁琐,希望可以通过实现 `FactoryBean` 来封装初始化过程。 + +在 Spring 官方文档 https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-factory-extension-factorybean[Core Technologies: Customizing Instantiation Logic with a `FactoryBean`^] 也有进一步的说明。 + +目前,Spring 源码中,`FactoryBean` 的实现类就有五十多个,随便举几个栗子🌰: + +* `org.springframework.http.converter.json.GsonFactoryBean` +* `org.springframework.cache.jcache.JCacheManagerFactoryBean` +* `org.springframework.aop.framework.ProxyFactoryBean` + +示例如下: + +[{java_src_attr}] +---- +package com.diguage.truman.context; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.context.annotation.*; + +import java.util.Arrays; + +/** + * FactoryBean 测试 + * + * @author D瓜哥 · https://www.diguage.com + * @since 2020-05-26 16:34 + */ +public class FactoryBeanTest { + @Test + public void test() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(Config.class); + context.refresh(); + + UserService userService = context.getBean(UserService.class); + System.out.println(userService.getById(119L)); + + System.out.println("-↓----"); + System.out.println("&userServiceFactoryBean = " // <1> + + context.getBean("&userServiceFactoryBean")); + System.out.println(" userServiceFactoryBean = " // <2> + + context.getBean("userServiceFactoryBean")); + System.out.println("-↑----"); + + UserServiceFactoryBean factoryBean = context.getBean(UserServiceFactoryBean.class); + System.out.println(factoryBean); + System.out.println(Arrays.toString(context.getBeanDefinitionNames()) + .replaceAll(",", ",\n")); + } + + @Configuration + public static class Config { + @Bean + public UserServiceFactoryBean userServiceFactoryBean() { + return new UserServiceFactoryBean(); + } + } + + + public static class UserService { + public String getById(Long id) { + return "Name-" + id; + } + } + + public static class UserServiceFactoryBean implements FactoryBean { + @Override + public UserService getObject() throws Exception { + return new UserService(); + } + + @Override + public Class getObjectType() { + return UserService.class; + } + + @Override + public boolean isSingleton() { + return false; + } + } +} +---- +<1> 通过 Bean 名称 `&userServiceFactoryBean` 获得的 Bean 是 `UserServiceFactoryBean` 对象; +<2> 通过 Bean 名称 `userServiceFactoryBean` 获得的 Bean 是 `UserService` 对象; + +有一点需要强调一下:`&` 符号的使用需要注意。上面的代码和相应注释给出了说明。 + + +== `ObjectFactory` + +D瓜哥个人认为 `FactoryBean` 和 `ObjectFactory` 功能有些重叠,都是为了创建对象而设计的。 + +通过 `ObjectFactory` 的文档,Spring 给出了官方解释: + +**** +这个接口通常用于封装一个通用的工厂,它在每次调用时返回某个目标对象的新实例(原型)。 + +这个接口类似于 `FactoryBean`,但后者的实现通常是作为 `BeanFactory` 中的 SPI 实例来定义,而这个类的实现通常是作为 API 馈送给其他 Bean(通过注入)。因此,getObject()方法有不同的异常处理行为。 +**** + +Spring 在解决循环依赖时和在创建 Bean 时,都使用到接口。它似乎可以脱离 Spring 单独使用。 + +== `ObjectProvider` + +`ObjectProvider` 继承了 `ObjectFactory` 接口,它是后者的一个变体,提供了更加丰富的操作 `T getIfAvailable()`,T getIfUnique() 等。在 Spring 5.1 以后,有继承了 `Iterable` 接口,方法用于循环或者 `forEach` 方法。在 `org.springframework.beans.factory.support.DefaultListableBeanFactory` 中有使用示例。 + +== `BeanPostProcessor` + +`BeanPostProcessor` 是 Spring 中最最重要的扩展点。Spring 内部大量的功能 IoC 和 AOP 也都是通过 `BeanPostProcessor` 来实现的。先来看一下接口定义: + +[{java_src_attr}] +---- +include::{beans_src_dir}/factory/config/BeanPostProcessor.java[] +---- + +具体到实际应用上,Spring 内置了大量的应用: + +. `ApplicationContextAwareProcessor` -- `Aware` 接口的处理。 +. `InitDestroyAnnotationBeanPostProcessor` -- `init-method` 和 `destroy-method` 方法的调用。 +. `InstantiationAwareBeanPostProcessor` +. `CommonAnnotationBeanPostProcessor` -- 常用注解 `@Resource`、`@PostConstruct` 和 `@PreDestroy` 的解析。 +. `AutowiredAnnotationBeanPostProcessor` -- 常用注解 `@Autowired`、`@Value` 和 `@Inject` 的解析。 +. `BeanValidationPostProcessor` -- 字段校验。 +. `AbstractAutoProxyCreator` -- 生成代理。 + +少废话,直接上代码: + +[{java_src_attr}] +---- +public class LogBeanPostProcessor implements BeanPostProcessor { + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof UserService) { + System.out.println(getAndIncrement() + + "LogBeanPostProcessor.postProcessBeforeInitialization"); + System.out.println(bean); + System.out.println(); + } + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof UserService) { + System.out.println(getAndIncrement() + + "LogBeanPostProcessor.postProcessAfterInitialization"); + System.out.println(bean); + System.out.println(); + } + return bean; + } +} + +// 将其注册到 BeanFactory 上 +beanFactory.addBeanPostProcessor(new LogBeanPostProcessor()); +---- + +在 `org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean(String, Object, RootBeanDefinition)` 方法中,通过 `applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName)` 和 `applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName)` 来分别调用 `postProcessBeforeInitialization` 和 `postProcessAfterInitialization` 方法。 + +== 各种 Aware + +有时,自己开发的代码可能需要 `ApplicationContext` 或者 `BeanFactory` 等实例。则可以通过实现相应的 `Aware` 接口来获得对应的实例。目前有如下这些 `Aware` 接口: + +. `ApplicationContextAware` +. `ApplicationEventPublisherAware` +. `BeanClassLoaderAware` +. `BeanFactoryAware` +. `BeanNameAware` +. `BootstrapContextAware` +. `EmbeddedValueResolverAware` +. `EnvironmentAware` +. `ImportAware` +. `LoadTimeWeaverAware` +. `MessageSourceAware` +. `NotificationPublisherAware` +. `ResourceLoaderAware` +. `SchedulerContextAware` +. `ServletConfigAware` +. `ServletContextAware` + +在代码 `org.springframework.context.support.ApplicationContextAwareProcessor#invokeAwareInterfaces` 中,集中处理了 `EnvironmentAware`、`EmbeddedValueResolverAware`、`ResourceLoaderAware`、`ApplicationEventPublisherAware`、`MessageSourceAware` 和 `ApplicationContextAware` 等六种 `Aware` 注入。值得一提的是,通过类的定义可以得知,`ApplicationContextAwareProcessor` 是一个 `BeanPostProcessor` 实现类,那么 `BeanPostProcessor` 的处理机制也通过适用于该类。 + +=== `ApplicationContextAware` + +如果某个 Bean 实现了 `ApplicationContextAware` 接口,那么 Spring 将会将该 Bean 所在的上下文环境 `ApplicationContext` 传递给 `setApplicationContext()` 方法,在 Bean 类中新增一个 `ApplicationContext` 字段用来保存 `ApplicationContext` 的值,并实现 `setApplicationContext()` 方法。 + +[{java_src_attr}] +---- +@Service +public static class UserService implements InitializingBean, ApplicationContextAware { + @Resource + UserDao userDao; + + ApplicationContext applicationContext; + + public UserService() { + System.out.println(getAndIncrement() + + "UserService()\n"); + } + + @Override + public void afterPropertiesSet() throws Exception { + System.out.println(getAndIncrement() + + "UserService.afterPropertiesSet\n"); + } + + public void init() { + System.out.println(getAndIncrement() + + "UserService.init\n"); + } + + String getById(Long id) { + return userDao.getById(id); + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + System.out.println(getAndIncrement() + + "UserService.setApplicationContext\n"); + this.applicationContext = applicationContext; + } +} +---- + +=== `BeanClassLoaderAware` + +如果某个 Bean 实现了 `BeanClassLoaderAware` 接口,那么 Spring 将会将创建 Bean 的 `ClassLoader` 传递给 `setBeanClassLoader()` 方法,在 Bean 类中新增了一个 `classLoader` 字段用来保存 `ClassLoader` 的值,并实现 `setBeanClassLoader()` 方法。 + +=== `BeanFactoryAware` + +如果某个 Bean 实现了 `BeanFactoryAware` 接口,那么 Spring 将会将创建 Bean 的 `BeanFactory` 传递给 `setBeanFactory()` 方法,在 Bean 类中新增了一个 `beanFactory` 字段用来保存 `BeanFactory` 的值,并实现 `setBeanFactory()` 方法。 + +=== `BeanNameAware` + +如果某个 Bean 实现了 `BeanNameAware` 接口,那么 Spring 将会将 Bean 实例的ID传递给 `setBeanName()` 方法,在 Bean 类中新增一个 `beanName` 字段,并实现 `setBeanName()` 方法。 + +=== `ServletContextAware` + +这个接口只能在 Web 项目中使用。 + +如果某个 Bean 实现了 `ServletContextAware` 接口,那么 Spring 将会将 `ServletContext` 传递给 `setServletContext()` 方法,在 Bean 类中新增一个字段,并实现 `setServletContext()` 方法。 + +[#InitializingBean-vs-init-method] +== `InitializingBean` 与 `init-method` + +设置 `init-method` 方法和实现 `InitializingBean` 方法达到的效果是一样的。在代码 `org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#invokeInitMethods` 中可以看到很详细的处理流程: + +. 判断 Bean 是否是 `InitializingBean` 实例,如果是,则做类型转换,然后再调用其 `afterPropertiesSet()` 方法; +. 获取 `AbstractBeanDefinition#initMethodName` 属性,然后判断是否合法(①长度大于零,②和第一步条件不重复,③不是外部管理的初始化方法),如果合法,则调用该方法。 + +`init-method` 是通过反射执行的,而 `afterPropertiesSet()` 是直接执行的。所以 `afterPropertiesSet()` 的执行效率比 `init-method` 要高;不过 `init-method` 消除了 Bean 对 Spring 依赖。 + +其实,按照一种方式设置即可。如果两者同时存在,则按照上述顺序执行。示例见上面的 `ApplicationContextAware` 示例。 + +== `DestructionAwareBeanPostProcessor` + +能否在 Bean 销毁之前,对其做些操作呢?答案是可以的。 + +`DestructionAwareBeanPostProcessor` 就可以实现这个功能。先来看一下接口定义: + +[{java_src_attr}] +---- +include::{beans_src_dir}/factory/config/DestructionAwareBeanPostProcessor.java[] +---- + +由于 `DestructionAwareBeanPostProcessor` 是 `BeanPostProcessor` 子类,由此可见,可以像操作 `BeanPostProcessor` 一样来操作 `DestructionAwareBeanPostProcessor` 实现类。示例如下: + + +[{java_src_attr}] +---- +public class LogDestructionAwareBeanPostProcessor implements DestructionAwareBeanPostProcessor { + @Override + public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException { + System.out.println(getAndIncrement() + + "LogDestructionAwareBeanPostProcessor.postProcessBeforeDestruction"); + System.out.println(bean.getClass().getName()); + } +} + +// 将其注册到 BeanFactory 上 +beanFactory.addBeanPostProcessor(new LogDestructionAwareBeanPostProcessor()); +---- + +调用是在 `org.springframework.beans.factory.support.DisposableBeanAdapter#destroy` 方法中实现的。 + +当调用 `beanFactory.destroyBean(bean)` 来手动销毁 Bean 时,就会创建 `DisposableBeanAdapter` 实例,然后调用 `destroy()` 来触发这个回调。也是在这个方法中,当调用完回调后,就会触发下面的 `DisposableBean` 回调。 + +== `DisposableBean` 与 `destroy-method` + +想要触发生命周期函数的 `destroy()` 方法,必须要要手动调用 `beanFactory.destroyBean(bean)` 方法才行: + +[{java_src_attr}] +---- +DggDisposableBean dggDisposableBean = applicationContext.getBean(DggDisposableBean.class); +ConfigurableListableBeanFactory beanFactory = ApplicationContext.getBeanFactory(); +beanFactory.destroyBean(dggDisposableBean); +---- + +调用是在 `org.springframework.beans.factory.support.DisposableBeanAdapter#destroy` 方法中实现的。 + +和 <> 类似,`destroy-method` 也是在 `DisposableBean#destroy()` 之后执行的。如果同时存在,只要两者不重复,则两个同时都会执行。 + +== `ApplicationListener` + +在 `org.springframework.context.support.AbstractApplicationContext#finishRefresh` 中,发布了 `ContextRefreshedEvent` 事件。 + +// == `ReaderEventListener` + +== 整合实践 + +上面介绍那么多,现在找一些实际项目对整合过程做个分析。先来个简单的。 + +=== Hibernate 与 Spring 整合 + +在 Spring 官网中,给出了非常详细的介绍: https://docs.spring.io/spring/docs/current/spring-framework-reference/data-access.html#orm-hibernate[Data Access: Hibernate^] + +Hibernate 与 Spring 整合主要涉及下面几个类: + +. `LocalSessionFactoryBean` -- 声明 Hibernate 配置信息;或者注入数据库连接池对象。 +. `HibernateTransactionManager` -- 负责处理 Hibernate 的事务。 + +实例代码: + +[source,xml,{source_attr}] +---- + + + + + + + + + + + + + product.hbm.xml + + + + + hibernate.dialect=org.hibernate.dialect.HSQLDialect + + + + + + + + + + + + + + + + + + +---- + +Spring 与 Hibernate 的整合过程还是比较简单的,就是把 Hibernate 的相关对象当做普通的 Bean 注册到 Spring 容器中即可。 + +另外,还有一种 `HibernateTemplate` 方式,和上面的方式类似,就不再赘述。 + +原计划还准备添加 Spring 与 MyBATIS 和 Apache Dubbo 整合分析。考虑到本篇内容已经非常长,仔细分析它们的整合过程又需要大篇幅内容,所以,另外单独开文章进行说明。 + +== 参考资料 + +. https://www.jianshu.com/p/397c15cbf34a[Spring扩展点总结 - 简书^] +. https://www.cnblogs.com/v1haoge/p/6106456.html[Spring中Bean的生命周期及其扩展点 - 唯一浩哥 - 博客园^] +. https://leokongwq.github.io/2017/04/02/spring-expandPoint.html[spring扩展点整理 | 戒修-沉迷技术的小沙弥^] +. https://juejin.im/post/5da995d25188256a49204d7b[spring源码系列7:Spring中的InstantiationAwareBeanPostProcessor和BeanPostProcessor的区别 - 掘金^] +. https://juejin.im/post/5d31b1d2518825276a6f9c70[Dubbo源码之Spring整合 - 掘金^] +. https://blog.csdn.net/canot/article/details/50512217[详细解释Spring与Hibernate的整合原理_java_不能说的秘密的博客-CSDN博客^] +. https://blog.csdn.net/u012291108/article/details/51886269[bean的加载(九)记录创建bean的ObjectFactory_java_u012291108的博客-CSDN博客^] diff --git a/truman/src/docs/asciidoc/factory-bean.adoc b/truman/src/docs/asciidoc/factory-bean.adoc new file mode 100644 index 000000000000..f0fae059c9e0 --- /dev/null +++ b/truman/src/docs/asciidoc/factory-bean.adoc @@ -0,0 +1,14 @@ += `FactoryBean` 详解 + +[#FactoryBeanTest] +.FactoryBeanTest +[{java_src_attr}] +---- +include::{truman_src_dir}/beans/FactoryBeanTest.java[] +---- +<1> 获取 `FactoryBean` 的实现类的实例。 +<2> 获取 `FactoryBean` 的 `getObject()` 方法创建的实例。 + +由 `FactoryBean` 实现类创建的 Bean 实例,在实际 `getBean` 之前,不会创建。 会根据 `isSingleton()` 方法的返回值来决定创建之后是否缓存实例。 + +`FactoryBean` 实现类创建的 Bean 实例并不是存储在 `singletonObjects` 实例变量中(由 `DefaultSingletonBeanRegistry` 声明, `AbstractBeanFactory` 间接继承了 `DefaultSingletonBeanRegistry`),而是保存在 `factoryBeanObjectCache` 实例变量中(由 `FactoryBeanRegistrySupport` 声明, `AbstractBeanFactory` 直接继承了 `FactoryBeanRegistrySupport`)。具体情况,请参考类图: <>。 \ No newline at end of file diff --git a/truman/src/docs/asciidoc/hibernate.adoc b/truman/src/docs/asciidoc/hibernate.adoc new file mode 100644 index 000000000000..fdaa9f4c3adf --- /dev/null +++ b/truman/src/docs/asciidoc/hibernate.adoc @@ -0,0 +1,2 @@ +[#hibernate] += 整合 Hibernate diff --git a/truman/src/docs/asciidoc/images/DataAccessException.png b/truman/src/docs/asciidoc/images/DataAccessException.png new file mode 100644 index 000000000000..746f17399b99 Binary files /dev/null and b/truman/src/docs/asciidoc/images/DataAccessException.png differ diff --git a/truman/src/docs/asciidoc/images/TargetSource-invocation-flow.png b/truman/src/docs/asciidoc/images/TargetSource-invocation-flow.png new file mode 100644 index 000000000000..a35ca2c9d296 Binary files /dev/null and b/truman/src/docs/asciidoc/images/TargetSource-invocation-flow.png differ diff --git a/truman/src/docs/asciidoc/images/alipay.png b/truman/src/docs/asciidoc/images/alipay.png new file mode 100644 index 000000000000..e1bb3340e662 Binary files /dev/null and b/truman/src/docs/asciidoc/images/alipay.png differ diff --git a/truman/src/docs/asciidoc/images/aop-concepts.png b/truman/src/docs/asciidoc/images/aop-concepts.png new file mode 100644 index 000000000000..85f1243c02e5 Binary files /dev/null and b/truman/src/docs/asciidoc/images/aop-concepts.png differ diff --git a/truman/src/docs/asciidoc/images/aop-principle.png b/truman/src/docs/asciidoc/images/aop-principle.png new file mode 100644 index 000000000000..712eebf2d4ca Binary files /dev/null and b/truman/src/docs/asciidoc/images/aop-principle.png differ diff --git a/truman/src/docs/asciidoc/images/aop-proxy-call.png b/truman/src/docs/asciidoc/images/aop-proxy-call.png new file mode 100644 index 000000000000..de6be86ed543 Binary files /dev/null and b/truman/src/docs/asciidoc/images/aop-proxy-call.png differ diff --git a/truman/src/docs/asciidoc/images/aop-proxy-plain-pojo-call.png b/truman/src/docs/asciidoc/images/aop-proxy-plain-pojo-call.png new file mode 100644 index 000000000000..8ece077d3445 Binary files /dev/null and b/truman/src/docs/asciidoc/images/aop-proxy-plain-pojo-call.png differ diff --git a/truman/src/docs/asciidoc/images/aspect-args-flow.png b/truman/src/docs/asciidoc/images/aspect-args-flow.png new file mode 100644 index 000000000000..3c6f09da245c Binary files /dev/null and b/truman/src/docs/asciidoc/images/aspect-args-flow.png differ diff --git a/truman/src/docs/asciidoc/images/aspects-applied-process.jpg b/truman/src/docs/asciidoc/images/aspects-applied-process.jpg new file mode 100644 index 000000000000..2302777912ce Binary files /dev/null and b/truman/src/docs/asciidoc/images/aspects-applied-process.jpg differ diff --git a/truman/src/docs/asciidoc/images/cglib-architecture.jpg b/truman/src/docs/asciidoc/images/cglib-architecture.jpg new file mode 100644 index 000000000000..3a8a9e33656a Binary files /dev/null and b/truman/src/docs/asciidoc/images/cglib-architecture.jpg differ diff --git a/truman/src/docs/asciidoc/images/cglib.png b/truman/src/docs/asciidoc/images/cglib.png new file mode 100644 index 000000000000..899a51d44191 Binary files /dev/null and b/truman/src/docs/asciidoc/images/cglib.png differ diff --git a/truman/src/docs/asciidoc/images/circular-dependence.jpg b/truman/src/docs/asciidoc/images/circular-dependence.jpg new file mode 100644 index 000000000000..23fa7ab2b9ce Binary files /dev/null and b/truman/src/docs/asciidoc/images/circular-dependence.jpg differ diff --git a/truman/src/docs/asciidoc/images/circular-dependence.png b/truman/src/docs/asciidoc/images/circular-dependence.png new file mode 100644 index 000000000000..7c1a9e507a10 Binary files /dev/null and b/truman/src/docs/asciidoc/images/circular-dependence.png differ diff --git a/truman/src/docs/asciidoc/images/container-magic.png b/truman/src/docs/asciidoc/images/container-magic.png new file mode 100644 index 000000000000..2628e59b00e8 Binary files /dev/null and b/truman/src/docs/asciidoc/images/container-magic.png differ diff --git a/truman/src/docs/asciidoc/images/create-instance.jpg b/truman/src/docs/asciidoc/images/create-instance.jpg new file mode 100644 index 000000000000..34453203b6ca Binary files /dev/null and b/truman/src/docs/asciidoc/images/create-instance.jpg differ diff --git a/truman/src/docs/asciidoc/images/dirty-read-process.png b/truman/src/docs/asciidoc/images/dirty-read-process.png new file mode 100644 index 000000000000..f1bf22d3f015 Binary files /dev/null and b/truman/src/docs/asciidoc/images/dirty-read-process.png differ diff --git a/truman/src/docs/asciidoc/images/dubbo-logo.jpg b/truman/src/docs/asciidoc/images/dubbo-logo.jpg new file mode 100644 index 000000000000..ad6c4f545352 Binary files /dev/null and b/truman/src/docs/asciidoc/images/dubbo-logo.jpg differ diff --git a/truman/src/docs/asciidoc/images/java-exceptions.jpg b/truman/src/docs/asciidoc/images/java-exceptions.jpg new file mode 100644 index 000000000000..9d83d1d8bdf8 Binary files /dev/null and b/truman/src/docs/asciidoc/images/java-exceptions.jpg differ diff --git a/truman/src/docs/asciidoc/images/manual-new-test.png b/truman/src/docs/asciidoc/images/manual-new-test.png new file mode 100644 index 000000000000..0e4d40e03a35 Binary files /dev/null and b/truman/src/docs/asciidoc/images/manual-new-test.png differ diff --git a/truman/src/docs/asciidoc/images/message-flow-broker-relay.png b/truman/src/docs/asciidoc/images/message-flow-broker-relay.png new file mode 100644 index 000000000000..3cf93fa1439c Binary files /dev/null and b/truman/src/docs/asciidoc/images/message-flow-broker-relay.png differ diff --git a/truman/src/docs/asciidoc/images/message-flow-simple-broker.png b/truman/src/docs/asciidoc/images/message-flow-simple-broker.png new file mode 100644 index 000000000000..9afd54f57c23 Binary files /dev/null and b/truman/src/docs/asciidoc/images/message-flow-simple-broker.png differ diff --git a/truman/src/docs/asciidoc/images/mvc-context-hierarchy.png b/truman/src/docs/asciidoc/images/mvc-context-hierarchy.png new file mode 100644 index 000000000000..9c4a950caadb Binary files /dev/null and b/truman/src/docs/asciidoc/images/mvc-context-hierarchy.png differ diff --git a/truman/src/docs/asciidoc/images/mvc-context-hierarchy.svg b/truman/src/docs/asciidoc/images/mvc-context-hierarchy.svg new file mode 100644 index 000000000000..07148744b549 --- /dev/null +++ b/truman/src/docs/asciidoc/images/mvc-context-hierarchy.svg @@ -0,0 +1,612 @@ + + + +image/svg+xmlPage-1DispatcherServlet +Servlet WebApplicationContext +(containing controllers, view resolvers,and other web-related beans) +Controllers +ViewResolver +HandlerMapping +Root WebApplicationContext +(containing middle-tier services, datasources, etc.) +Services +Repositories +Delegates if no bean found + \ No newline at end of file diff --git a/truman/src/docs/asciidoc/images/non-repeatable-read-process.png b/truman/src/docs/asciidoc/images/non-repeatable-read-process.png new file mode 100644 index 000000000000..9702493efd90 Binary files /dev/null and b/truman/src/docs/asciidoc/images/non-repeatable-read-process.png differ diff --git a/truman/src/docs/asciidoc/images/oxm-exceptions.graffle b/truman/src/docs/asciidoc/images/oxm-exceptions.graffle new file mode 100644 index 000000000000..4b72bf45285b --- /dev/null +++ b/truman/src/docs/asciidoc/images/oxm-exceptions.graffle @@ -0,0 +1,1619 @@ + + + + + ActiveLayerIndex + 0 + ApplicationVersion + + com.omnigroup.OmniGraffle + 137.11.0.108132 + + AutoAdjust + + BackgroundGraphic + + Bounds + {{0, 0}, {756, 553}} + Class + SolidGraphic + ID + 2 + Style + + shadow + + Draws + NO + + stroke + + Draws + NO + + + + CanvasOrigin + {0, 0} + ColumnAlign + 1 + ColumnSpacing + 36 + CreationDate + 2009-09-11 10:15:26 -0400 + Creator + Thomas Risberg + DisplayScale + 1 0/72 in = 1 0/72 in + GraphDocumentVersion + 6 + GraphicsList + + + Class + LineGraphic + FontInfo + + Font + Helvetica + Size + 18 + + ID + 42 + Points + + {334.726, 144} + {394.042, 102.288} + + Style + + stroke + + HeadArrow + FilledArrow + TailArrow + 0 + + + + + Class + LineGraphic + FontInfo + + Font + Helvetica + Size + 18 + + ID + 41 + Points + + {489.5, 143.713} + {430.452, 102.287} + + Style + + stroke + + HeadArrow + FilledArrow + TailArrow + 0 + + + + + Class + LineGraphic + FontInfo + + Font + Helvetica + Size + 18 + + Head + + ID + 4 + + ID + 40 + Points + + {230, 217} + {275.683, 175.337} + + Style + + stroke + + HeadArrow + FilledArrow + TailArrow + 0 + + + + + Class + LineGraphic + FontInfo + + Font + Helvetica + Size + 18 + + Head + + ID + 4 + + ID + 39 + Points + + {430.381, 216.81} + {329.369, 175.19} + + Style + + stroke + + HeadArrow + FilledArrow + TailArrow + 0 + + + Tail + + ID + 5 + + + + Bounds + {{56, 217}, {249, 30}} + Class + ShapedGraphic + FontInfo + + Font + Helvetica + Size + 18 + + ID + 6 + Shape + Rectangle + Text + + Text + {\rtf1\ansi\ansicpg1252\cocoartf949\cocoasubrtf540 +{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +\deftab720 +\pard\pardeftab720\qc + +\f0\fs36 \cf0 MarshallingFailureException} + + + + Bounds + {{325.5, 217}, {283.5, 30}} + Class + ShapedGraphic + FontInfo + + Font + Helvetica + Size + 18 + + ID + 5 + Shape + Rectangle + Text + + Text + {\rtf1\ansi\ansicpg1252\cocoartf949\cocoasubrtf540 +{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +\deftab720 +\pard\pardeftab720\qc + +\f0\fs36 \cf0 UnmarshallingFailureException} + + + + Bounds + {{184, 145}, {217, 30}} + Class + ShapedGraphic + FontInfo + + Font + Helvetica + Size + 18 + + ID + 4 + Shape + Rectangle + Text + + Text + {\rtf1\ansi\ansicpg1252\cocoartf949\cocoasubrtf540 +{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +\deftab720 +\pard\pardeftab720\qc + +\f0\fs36 \cf0 MarshallingException} + + + + Bounds + {{430, 145}, {239, 30}} + Class + ShapedGraphic + FontInfo + + Font + Helvetica + Size + 18 + + ID + 3 + Shape + Rectangle + Text + + Text + {\rtf1\ansi\ansicpg1252\cocoartf949\cocoasubrtf540 +{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +\deftab720 +\pard\pardeftab720\qc + +\f0\fs36 \cf0 ValidationFailureException} + + + + Bounds + {{294, 72}, {244, 30}} + Class + ShapedGraphic + FontInfo + + Font + Helvetica + Size + 18 + + ID + 1 + Shape + Rectangle + Style + + Text + + Text + {\rtf1\ansi\ansicpg1252\cocoartf949\cocoasubrtf540 +{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +\deftab720 +\pard\pardeftab720\qc + +\f0\i\fs36 \cf0 XmlMappingException} + + Wrap + NO + + + GridInfo + + GuidesLocked + NO + GuidesVisible + YES + HPages + 1 + ImageCounter + 1 + KeepToScale + + Layers + + + Lock + NO + Name + Layer 1 + Print + YES + View + YES + + + LayoutInfo + + Animate + NO + circoMinDist + 18 + circoSeparation + 0.0 + layoutEngine + dot + neatoSeparation + 0.0 + twopiSeparation + 0.0 + + LinksVisible + NO + MagnetsVisible + NO + MasterSheets + + ModificationDate + 2009-09-11 10:38:54 -0400 + Modifier + Thomas Risberg + NotesVisible + NO + Orientation + 2 + OriginVisible + NO + PageBreaks + YES + PrintInfo + + NSBottomMargin + + float + 41 + + NSLeftMargin + + float + 18 + + NSOrientation + + int + 1 + + NSPaperSize + + size + {792, 612} + + NSRightMargin + + float + 18 + + NSTopMargin + + float + 18 + + + PrintOnePage + + QuickLookPreview + + JVBERi0xLjMKJcTl8uXrp/Og0MTGCjQgMCBvYmoKPDwgL0xlbmd0aCA1IDAgUiAvRmls + dGVyIC9GbGF0ZURlY29kZSA+PgpzdHJlYW0KeAGVlk1vG0cMhu/zK3h0Dx4Ph/N5rZsA + DRCgqdW0V0GVGhkryZGdoj+/L2e1q4W1cloLhhZrfr0PORx/pU/0lRw+OSaKUei4pt9p + T84m135oS3f3z0yrZ+L2eV7RrbPx9Nfz0ymAQYAN3f2yPq7WTy/flh0dt0jhU2ppoidf + hIIkWu3o7ucd00+HVoRPPFgEriRJTG/hRwupgwVnUYtTDBksxI1ZhION5Csqb3mCGfLk + c56pQeyD3P267pYv27/X94fucNzu1i/H7Uo1+BooRCYfAonrZTJ9AJPHntD9Q6vO0cM9 + BPdJbvVLsaIIDZAhv/kr5wfoBltvwNYRuDrAnngGThTADb4/LohLC3+L79tSbQwZDDMt + oO49W4eEi425+WPXfVw+PW33f737RxuwPex/oMUjvVsg2ZtNDeJIciEPyoO+STGjDLXj + AHLNbqpDZ2RORwwol6S2hr5Swi7aUpVMr8SflNDN53PdkzLeilWj9d7ly1DLbvsnenrY + v19uu2/H9Ws05jtouKDlioYz0KjkzbRPIxq1a2ianY7I0OJraHz1PZq5Jkc0WWqkbFqT + z2g+Lo/PX5Zd9/+7bMQjKkQkPYbt6bqc3lZFT20HSVenNmXrkcK3k/e63R6nMpZxcJsm + s9jQzW/73VnVlT59b4Sxwpqy8PYEw6yJamb/ZYC5YM2pIt1IrxWxWBdlZuomXZrTYy6O + 5GTMx5HCabNSOKDiZIvDNOxIpNjowZdzsTVxNd0waG1P6zpv51CELXtsH8nZuhJzc85W + cvV4x9anINQhYLUpKY6MJNwCfsHbS+8NAn/A7+Ps/I8enKOtjNg7I3LKx4VtFtQwycfI + x6Uw3k3ynb2brNOSNXN4PI6j9hLbNRUrSQ9gwVC5gpA6qT4H6zP2i0qT4kszxWNI1UgW + 6x2msYMdOOutU2zOIKYFzfleB6CzMXqosMTY9lpYnw3dqjaDyjkb9gU2VlAkk2yDr9lN + 5c8CD3oRYOWIzQzYuFYxGTHhlcu2ZqhtFEwQZZJxgWEVJ48rRG3Ra5GId9hBudUVgutp + hZBtKNI35sIblV3noJts9GAnmLaiHMZ8zM4G36gP+YxeA5FTbSTmvLWXw207NwgiwWaf + wCKgOimYP8DoORSvhDWCYN2Ggk3eODD+OVBbNAFDg3fQrBwxoCXjQNRoGpsk/TzMeb/N + YfRoHHB8W22nfE2zL+1AnPJRYyOpn4gLb1SrKj79C2PwIN4KZW5kc3RyZWFtCmVuZG9i + ago1IDAgb2JqCjkyNgplbmRvYmoKMiAwIG9iago8PCAvVHlwZSAvUGFnZSAvUGFyZW50 + IDMgMCBSIC9SZXNvdXJjZXMgNiAwIFIgL0NvbnRlbnRzIDQgMCBSIC9NZWRpYUJveCBb + MCAwIDc1NiA1NTNdCj4+CmVuZG9iago2IDAgb2JqCjw8IC9Qcm9jU2V0IFsgL1BERiAv + VGV4dCAvSW1hZ2VCIC9JbWFnZUMgL0ltYWdlSSBdIC9Db2xvclNwYWNlIDw8IC9DczIg + MTggMCBSCi9DczEgNyAwIFIgPj4gL0ZvbnQgPDwgL0YxLjAgMTkgMCBSIC9GMi4wIDIw + IDAgUiA+PiAvWE9iamVjdCA8PCAvSW0yIDEwIDAgUgovSW0zIDEyIDAgUiAvSW00IDE0 + IDAgUiAvSW01IDE2IDAgUiAvSW0xIDggMCBSID4+ID4+CmVuZG9iagoxMCAwIG9iago8 + PCAvTGVuZ3RoIDExIDAgUiAvVHlwZSAvWE9iamVjdCAvU3VidHlwZSAvSW1hZ2UgL1dp + ZHRoIDUyMiAvSGVpZ2h0IDEwNCAvQ29sb3JTcGFjZQoyMSAwIFIgL1NNYXNrIDIyIDAg + UiAvQml0c1BlckNvbXBvbmVudCA4IC9GaWx0ZXIgL0ZsYXRlRGVjb2RlID4+CnN0cmVh + bQp4Ae3QMQEAAADCoPVPbQhfiEBhwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIAB + AwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBg + wIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYM + GDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIAB + AwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBg + wIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYM + GDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIAB + AwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBg + wIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYM + GDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIAB + AwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBg + wIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYM + GDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIAB + AwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBg + wIABAwYMGDBgwIABAwYMvAMDfE4AAQplbmRzdHJlYW0KZW5kb2JqCjExIDAgb2JqCjcz + NAplbmRvYmoKMTIgMCBvYmoKPDwgL0xlbmd0aCAxMyAwIFIgL1R5cGUgL1hPYmplY3Qg + L1N1YnR5cGUgL0ltYWdlIC9XaWR0aCA0NzggL0hlaWdodCAxMDQgL0NvbG9yU3BhY2UK + MjQgMCBSIC9TTWFzayAyNSAwIFIgL0JpdHNQZXJDb21wb25lbnQgOCAvRmlsdGVyIC9G + bGF0ZURlY29kZSA+PgpzdHJlYW0KeAHt0DEBAAAAwqD1T+1pCYhAYcCAAQMGDBgwYMCA + AQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgw + YMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMG + DBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCA + AQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgw + YMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMG + DBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCA + AQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgw + YMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMG + DBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCA + AQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgw + YMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMG + DBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCA + AQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYOADA0auAAEKZW5kc3RyZWFtCmVuZG9iagox + MyAwIG9iago2NzQKZW5kb2JqCjE0IDAgb2JqCjw8IC9MZW5ndGggMTUgMCBSIC9UeXBl + IC9YT2JqZWN0IC9TdWJ0eXBlIC9JbWFnZSAvV2lkdGggNjEyIC9IZWlnaHQgMTA0IC9D + b2xvclNwYWNlCjI3IDAgUiAvU01hc2sgMjggMCBSIC9CaXRzUGVyQ29tcG9uZW50IDgg + L0ZpbHRlciAvRmxhdGVEZWNvZGUgPj4Kc3RyZWFtCngB7dCBAAAAAMOg+VNf4AiFUGHA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgIE/MOn+AAEKZW5kc3RyZWFtCmVuZG9iagoxNSAwIG9iago4NTYK + ZW5kb2JqCjE2IDAgb2JqCjw8IC9MZW5ndGggMTcgMCBSIC9UeXBlIC9YT2JqZWN0IC9T + dWJ0eXBlIC9JbWFnZSAvV2lkdGggNTQyIC9IZWlnaHQgMTA0IC9Db2xvclNwYWNlCjMw + IDAgUiAvU01hc2sgMzEgMCBSIC9CaXRzUGVyQ29tcG9uZW50IDggL0ZpbHRlciAvRmxh + dGVEZWNvZGUgPj4Kc3RyZWFtCngB7dAxAQAAAMKg9U9tCy+IQGHAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgy8BwaUrgABCmVuZHN0cmVhbQplbmRvYmoKMTcgMCBvYmoKNzYxCmVuZG9i + ago4IDAgb2JqCjw8IC9MZW5ndGggOSAwIFIgL1R5cGUgL1hPYmplY3QgL1N1YnR5cGUg + L0ltYWdlIC9XaWR0aCA1MzIgL0hlaWdodCAxMDQgL0NvbG9yU3BhY2UKMzMgMCBSIC9T + TWFzayAzNCAwIFIgL0JpdHNQZXJDb21wb25lbnQgOCAvRmlsdGVyIC9GbGF0ZURlY29k + ZSA+PgpzdHJlYW0KeAHt0DEBAAAAwqD1T20KP4hAYcCAAQMGDBgwYMCAAQMGDBgwYMCA + AQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgw + YMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMG + DBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCA + AQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgw + YMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMG + DBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCA + AQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgw + YMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMG + DBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCA + AQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgw + YMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMG + DBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCA + AQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgw + YMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMCAAQMGDBgwYMDAa2CIfgABCmVuZHN0 + cmVhbQplbmRvYmoKOSAwIG9iago3NDcKZW5kb2JqCjIyIDAgb2JqCjw8IC9MZW5ndGgg + MjMgMCBSIC9UeXBlIC9YT2JqZWN0IC9TdWJ0eXBlIC9JbWFnZSAvV2lkdGggNTIyIC9I + ZWlnaHQgMTA0IC9Db2xvclNwYWNlCi9EZXZpY2VHcmF5IC9CaXRzUGVyQ29tcG9uZW50 + IDggL0ZpbHRlciAvRmxhdGVEZWNvZGUgPj4Kc3RyZWFtCngB7Z3tT1PZFsZBCqXvLZS2 + 9GVaTgvtaSmdY4sFCtM2bXhHFISpM0LQqhkYkNHYSAZ1MIwSiSI4EF6iyBDRgEPAECVG + zfxrd53CvXOFcrD3fto96/lATDYmez/rl7XXaTlrZWWh0AF0AB1AB9ABdAAdQAfQAXQA + Hfh/HMhGZaADaREB5z/xj3JQGeHAPxE9AQH+CiD2KICzCwS5qIxzQCCA0LJQHAdDkoM9 + CPKEwvw9iVDEO7AfSqEwD+AGHI5hYZ+D3Nw8gEAkFkskEqlUKkNlgAMQSAinWCzKz2dp + 4GaBBSEH7gTAACCQyuRyhVKpQmWIA0qlQi6XAQ9igGGPhSOuiCQIkA9YDmRyhUpVUKhW + FxVpNFoU8Q5oNEVFanVhgUqlkMtYFiAvwBWRGgU2I7AJgeVACRRotLpivd5gNJpQxDtg + NBr0+mKdVgM0KJMsQFpgUUjxEJEEAQoEiRQ4AAyAAZPZYimhrKgMcIAqsVjMJuABYAAW + pBK2XEiNQjZbIwhFkBBUhRqdHiigrKVldgdNO50uFNEOOJ007bCXlVopoEGv0xSqIC2I + hGzdeDgpQEoAEPIlMoVKrdWbLJStjHaWuz0ehmFOogh3AILo8bjLnXSZjbKY9Fq1SiGD + rJArSHE/QEqAYlGcBMFgpkodLreH8Vae8lfXgAIogh1gI1jtP1XpZTxul6OUMhuSKIih + bEyRFLIhJeSLpXKVWmcwW+2uCsbnrw7UBUPhSCQSRRHtAIQwHArWBar9PqbCZbeaDTq1 + Si4V50NSOHg97KUECYCgNVhstJvxVQWC4Wh9Y1NLa9tpFOEOtLW2NDXWR8PBQJWPcdM2 + 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 + Wlpmd9C00+lCEe2A00nTDntZqZWymE16naZQJZdJRADCoXqR/etWSApQNEJWkMqVBWqN + Vm8wAg2WEsqKygAHqBILUGA06LUadYFSLoWMwN4NKVLCPgpwQYghLSgLCgEGXbEeeDCa + UMQ7YAQG9MU6wKAQOJBJxHA1HAVCVnYyK8CzZJIFhUoFNKiLijQaLYp4BzSaoiI1UKBS + KZIcQLGYBOHAhwn7rz4kURDkQloAFiRSmVyuUCpVqAxxQKlUyOUyqQTyAZsQoEY4kZ0a + BLgfICuwdSNbLuSLxICDRCqVylAZ4AAEEsIpFosAA8gHLAdHg8CWjXssAAxAA+CQlAhF + vAP7oRSyFOQKjuUg+QjBsnAiJydHwOKAyjAHAIIcNh1w5oP9aoFNDEka2N8Hwf9EZYAD + e9FM/oQA/yfYX/MP+H1UxjnwNZHH30EH0AF0AB1AB9ABdAAdQAfQAXTgaAf+BYU9EtcK + ZW5kc3RyZWFtCmVuZG9iagoyMyAwIG9iagoyNzE5CmVuZG9iagoyOCAwIG9iago8PCAv + TGVuZ3RoIDI5IDAgUiAvVHlwZSAvWE9iamVjdCAvU3VidHlwZSAvSW1hZ2UgL1dpZHRo + IDYxMiAvSGVpZ2h0IDEwNCAvQ29sb3JTcGFjZQovRGV2aWNlR3JheSAvQml0c1BlckNv + bXBvbmVudCA4IC9GaWx0ZXIgL0ZsYXRlRGVjb2RlID4+CnN0cmVhbQp4Ae2d7U9T2RaH + BQql7y2U03LaTutpS3taS+fYaoXitKRNESm+oDh1FIJWzVSLHY2NzaAOxlEi8Q1HghhF + xohGHSKGqDGjmX/trl3MnTt290jvyf101+8DMfvI/vDkyVq7B9hrwwYMEkACSAAJIAEk + gASQABJAAkgACSCB/wWBOgwSWD+B2hSEfev/TgMGCYgR+FuVejBnPaqt+QV7ymSNGCSw + XgIyGThDdPuqZmXD1vRqksub16LAIIFqBD47Ipc3gY0g2tcs+2xYY2MT6KVQKlUqlVqt + 1mCQQHUCYAh4olQqmpuJZ1+xjCjWAB0SBAO91BqtVqfXGzBIQJyAXq/TajVgmhI0W7Os + WsMsKwY1jBim0eoMhpZWo7GtjWFMGCRQjQDDtLUZja0tBoNOqyGWQS2DhllFMlLFSBEj + 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 + WX1DQ4OMiIZBAusjAHo1kBImXsM+n8pIMSt7Rr4BAt+KQQLVCaxpUv4K5vzbonX9A74B + gwTWS2BdTuF/QgJIAAkgASSABJAAEkACSAAJIAEkUDuBfwFWtww3CmVuZHN0cmVhbQpl + bmRvYmoKMjkgMCBvYmoKMzAwNwplbmRvYmoKMzEgMCBvYmoKPDwgL0xlbmd0aCAzMiAw + IFIgL1R5cGUgL1hPYmplY3QgL1N1YnR5cGUgL0ltYWdlIC9XaWR0aCA1NDIgL0hlaWdo + dCAxMDQgL0NvbG9yU3BhY2UKL0RldmljZUdyYXkgL0JpdHNQZXJDb21wb25lbnQgOCAv + RmlsdGVyIC9GbGF0ZURlY29kZSA+PgpzdHJlYW0KeAHtnetPU+kWxrkUer9B2S29TMtu + ueyW0tlSLFCclrQBykXkOnUUghTNwICMxkYyqINhlEgUwYFwiSJDRAMOAUOUGDXzr521 + CzlzhLKZnk/nvHs9H4zJWz+sZ/1ca+1e3pWWhkIH0AF0AB1AB9ABdAAdQAfQAXTg/9GB + dJRAHEiJTvAk429looh14O8sZ0DS/wEkB2SAHyJRFkoQDohEkG4OlNMASbBxAEa2WCw5 + kBRFpAOH6RWLs+E/ASByCh+HbGRlZQMYUplMLpcrFAolilAHILmQYplMKpFwhPDzwcGR + Cf0E0AAwFEqVSq3RaFEEO6DRqFUqJTAiA0AO+DihvSTggLrBsaFUqbXanFydLi+PovQo + Ih2gqLw8nS43R6tVq5QcH1A/oL0kx4OrHFzh4NjQABmU3pBvNJrMZguKSAfMZpPRmG/Q + U0CIJsEHlA8OjyQPLwk4YOCQK4ANQAO4sFhttgLajiLUAbrAZrNagBEABPhQyLnxIzke + 6dzMIZZC4dDmUgYjkEHbC4uKSxjG6XShiHPA6WSYkuKiQjsNhBgNVK4WyodUzM2mx4sH + lA6AQyJXqrU6vdFiox1FjLPU7fGwLHsGRaADkFiPx13qZIoctM1i1Ou0aiVUjyxRkt4C + pQMGUlkCDpOVLixxuT1secVZX1U1yI8izAEuq1W+sxXlrMftKimkraYEHjIYTZMUj3Qo + HRKZQqXVGUxWe7GrjPX6qvznAsHaUCgURhHnAKS1Nhg456/yedkyV7HdajLotCqFTALF + 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 + YQ5wWa3yna0oZz1uV0khzbUVrRr6SrLSAbUDiodYIk/gYbTYaEcR4yx1ezwsy55BEegA + JNbjcZc6mSIHbbMYE3DIJWIoHUfHDviSKcylgIdUrlRpcymD0WK10fbCouIShnE6XSji + HHA6GaakuKjQTtusFqOBytWqlHIpwHFsJuW+gQzFIzMrG6qHQqXJ0VF6o8kMhNgKaDuK + UAfoAhuQYTYZ9ZQuR6NSQOXg+kqS0nGIBzQXGZQPTU4uAGLINwIjZguKSAfMwIUx3wBo + 5AIbSrkM2spJcKSlJ6pHVrYkwYdaqwVCdHl5FKVHEekAReXl6YAMrVadYAMG0gQcR94K + O/xpSwIPURaUD+BDrlCqVGqNRosi2AGNRq1SKRVyqBtc4YCZIyM9ORzQW6B6cLMpN35I + pDJARK5QKJQoQh2A5EKKZTIpoAF1g2PjZDi40fSADwAECAFEEpKiiHTgML1ijows0als + JB5dOD4yMjMzRRwiKAE4AGBkcmWDt24cTh9cAUkQwr0eBP8SRagDBxlO/AlJ/zcA/+Qv + 8HqUIBz4JzTga9ABdAAdQAfQAXQAHUAH0AF0AB3433PgX6y7qcQKZW5kc3RyZWFtCmVu + ZG9iagozMiAwIG9iagoyNzYyCmVuZG9iagoyNSAwIG9iago8PCAvTGVuZ3RoIDI2IDAg + UiAvVHlwZSAvWE9iamVjdCAvU3VidHlwZSAvSW1hZ2UgL1dpZHRoIDQ3OCAvSGVpZ2h0 + IDEwNCAvQ29sb3JTcGFjZQovRGV2aWNlR3JheSAvQml0c1BlckNvbXBvbmVudCA4IC9G + aWx0ZXIgL0ZsYXRlRGVjb2RlID4+CnN0cmVhbQp4Ae2d/U8T2RfGeSn0fToD7bRM222Z + Uui0lO4IWAFdIBAUAV9Q3LorBK2ahQW7GhubRV0Mq8RGEVwIL1FkiWjAJWCIErOa/de+ + Z4rZXaEdvt2f7iTn+cFoLiaH58Nz723pnJOTg0IH0AF0AB1AB9ABdAAdQAfQgf/mQC5K + IQ5kxRe+p7x/lI8i1oF/KOUBtP8D8g5Z+H5UqgKUIhxQqQCXBHo/wCm2O2AL1WrNjrQo + Ih34jEetLoQfQkC8D9/PbAsKCgGsVqfT6/UGg8GIItQBgAOIdDqtRiMRlucrwc2H/RjQ + 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 + KjV28CWuS69cQdl18MXu28Q12JYtKLvu29g5n8Du+HIlZdM5Px+nXhA42UKupGymXkjz + iHBiDYGDaTKWlNXEGpw2ReBEKbmSspg2hZPiiBsFt09B2UyKwymPxI1x3Keg7KY84oRW + IsewyhSVzYRWaTA6TlcmcIpyppKymK6cg5PRiZt9Ll9QNpPRga4UXp3RJM1Gd/FeXyAY + EqtrD4br6kENKMIckKjUhQ/WVouhYMDn5aXB2YwJJmcX7h2dnQN081QFao0+hZdzuvmy + csFfGQyFRFE8gCLQAQATCgUr/UJ5Ge92cim4eo26QJW3e7gyfMgKwgt4tXojxRSzNs7p + cvMeb3mFTxD8/gCKOAf8fkHwVZR7Pbzb5eRsbDFDGfUwN1u1Z+q99Ak6CC/szZBeA0UX + mVkrZ3cAYXcp70ER6gBf6gayDjtnZc1FNGWA5Er7cprofsYLm7MO4ksXFQNgWwkHjB1O + FJEOOIArV2IDtMXA1qjXwbacCW5Obiq9cLVK8TUxDBA2Wywsa0UR6QDLWixmIMswphRb + uFCl4H4x8eLvDzan8KoKIL7AV28wUpSJphkUwQ7QtImijAY95FYKLpy5ebnp4cLeDOmV + 7lbS8avR6gCx3mAwGFGEOgBwAJFOpwW0kFuJbWa40tVqhy8ABsKAOCUtikgHPuNRS2QL + VPuyTV2dJb55+fn5KgkxSgEOANh8Kbayuf3X+ZsiLH09CP4nilAHdgil/szNeN7+zfWL + v8DXoxThwBfY8B/oADqADqAD6AA6gA6gA+gAOpCFA/8DclEtHwplbmRzdHJlYW0KZW5k + b2JqCjI2IDAgb2JqCjI1NTgKZW5kb2JqCjM0IDAgb2JqCjw8IC9MZW5ndGggMzUgMCBS + IC9UeXBlIC9YT2JqZWN0IC9TdWJ0eXBlIC9JbWFnZSAvV2lkdGggNTMyIC9IZWlnaHQg + MTA0IC9Db2xvclNwYWNlCi9EZXZpY2VHcmF5IC9CaXRzUGVyQ29tcG9uZW50IDggL0Zp + bHRlciAvRmxhdGVEZWNvZGUgPj4Kc3RyZWFtCngB7Z3rT1PpFsZBCqX3Fnqjl2nZbaG7 + pXS2LZZSmLZpwx1REKbOCEGrZmBARmMjGdTBMEokiuBAuESRIaIBh4AhSoya86+dtQvn + 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 + kC48a6HuVQ+qfWqL2gpNPaa2q21qF9+OqgPlHcOclYkLrNtl9Sn2YGOa3spJV2aL4N/C + L4b/pV5hC9c0NPkPTbi5jGkJ3xHcNnCHlP/DX7MDDd4KZW5kc3RyZWFtCmVuZG9iagoz + NyAwIG9iago3OTIKZW5kb2JqCjcgMCBvYmoKWyAvSUNDQmFzZWQgMzYgMCBSIF0KZW5k + b2JqCjM4IDAgb2JqCjw8IC9MZW5ndGggMzkgMCBSIC9OIDMgL0FsdGVybmF0ZSAvRGV2 + aWNlUkdCIC9GaWx0ZXIgL0ZsYXRlRGVjb2RlID4+CnN0cmVhbQp4Ae1XiTvU2xs/YwnZ + 953JPoylsSY72UNIlqwzjG0GM3bZomyhRIgsIZHsW0KSkq0ikiSUJFfRvT9Kda97ht99 + +j2/5/YfdL7Pe87nvO857znfed/n+3kHAFacvo2NJQ0AgEAMJdmaGCAdnZyRdDOAHtAC + Btize2LJwZQ1cMlP2tYzgKCYnqJN7I8a3DcdqHu3Sc2SPdrIp7lelPyTTf+oWXDeZCwA + CDRUeJDg4QBQcUHMhd/DMhTstYe1KJhkb2sI19hAYcLvYpqTFOy1i/f5UnBEaHAoAPRQ + ABc2mETBxRCf9QrY1adS9OFYPNQzSACwjwPr64kDgFUD6tE4AhFixDmIbXEEHAUvQIwh + BIbBe+42ytsyeROP28HRCIoQMAVhwBtEARtgC6wBEhiCIBAIhQSxJZwZwVEG9mpAFSIT + gAEKUJBAHyhBpAQf1E/8q+36dwTy0LMfCIWnIIEZIAIs3EfxGbH7KIBIeNaeXQHaggAB + oGBM//3OPLs+/+2OSgBQ4g/tsBH5ATCg/HY8P3TOvQBkwpgIon/o5L8DoOQFQMM7bBgp + fG8v4p+BGtABRsAO+OBtUUAFvrEVcIX3TwC5oAb0gefgdwQzQhZhhvBDZCAaEJOIb1QS + VNZUMVQ1VDPU+6m1qUnUNdSvaYRonGjyaaZp+Wldaa/Sru5T3he3b4ROgC6Arpeek96f + /h6DMEM0w8z+Q/uLGRGMeMYnTJpM1cw8zGeZt1mILG9Z3Vhn2BzYptgd2Gc43DiWOYM4 + v3GlcfNz1/Lo8kzxEvjo+Sr5DfmXBFIE0YKTQnHCKOFpkRRRTdGPyKoDbmL8YtPilyQc + JYUk30jdkA6XMURxoJZkO+Sy0D7yegrCCt8VXyndPViNOa8crYJXdVAzVdfSwByS1ZQ8 + LKZ1QFtcR1pXQU9NX9/A2tDdKOTIGeNikzbTcbN1C1ZLzNETVvHWNTZTtjR2qva+x0sc + ph3Znaycs1yeuHK5ubhXemx46WCzca991PHZvu/9jQOqCHTEgKCJkMOk6lDusDPhXyKJ + UcsxHqfm4lzj5xOxp1eTyWd2UrLSRNObz5lmLmafusCfcyv35CVEfk2hXREobijxLOMu + H604W2VQjbjeX3umzrKeu2Ghqb4lvs22Q+YW6Jrt7uwt6Ivp9xgwfYB5iBxmHUWMbT3+ + OL7y9O3U0vTyzOrsp7lvC/SveZdklrVX7FcJa2kfr28M//5xk/uzzrb/t8I/h3d2fuXC + r1z4lQu/vgv//134wRtbU/+tG+R+6EA0JI44KL4/4S5/aBPa5ddgyLcUzsMDX8iLFC7E + QoZBgv/lSjScY3b59SBk0D2kvsuc+pCfA6GVwqp7Hsi7M29AhhxLAuGwx8GVYK9O2KMz + QI2A5QWsCAhUMzTGtM10ovQZDF8YcUzjLNqstew8HEmc69wneTx58XyB/CSBCMFYodPC + qSKZoheRhQdKxCrFayQaJC9JxUp7y1iilGUF5ajk3qPH5TsVShVTlIgHj2O0laVU2FS+ + qr5RG1Pv0Cg7lK5JPuyqZaKtpCOgS6P7QW9N/zeD94YrRu+OvDVeMnltumi2YP7KYs7y + 5dEXVjPWqzZfbRnthOzRx7UdrE64OQY5JTifdyk/2eza7/bUfcFjzXMbS4vj8BbxkcOr + +xr52fi7BvgHhhOSiNlBVcG3Q8ZJy+TvYRzhMhE6kXZRftHxMXmnbsT2xT2Ln0h4nDhy + ejDpXnLfme6znSmtqY1pN9NrMqrOlWeWZJ3O9jlvfkExhzPn88WXuX15lZfS8gkFxwpV + Lwtc/qvoZXHXlfySsFK7MqVylvK1q8MV1ZVnqrDX9KtFqr9fn6vpqb1yI67O7aZuvWj9 + TsNCY19TeXNii1erQZtYO1X7YkdfZ+mtuC7X21rdgt1fe2Z6O+/k94XfdehXvUd3b2ag + 7n7cA5tB8cHNh4NDhcOBIzqjbKNvxtoepTw2ekL15M74qYlDE1tPWyaDp+SnVp9VT3s/ + F3s+P1P8wnoWMdv6Ej8nMDf2KmEeM/92oWDRYvGv141vsEs8S71vfZaZl9veua7QrNS9 + t3//fbXiN4vf/li7/MHgwyqMvwKVC3UaTQftEh0HvR4DYX8R4xDTFosEqy1bAvtNjlRO + LJcOrC3+w/OIt4Yvmd9dQFOQW3BDaES4SiRBVFC0HXkMuXYgRUxMrEfcUfwPiSxJlOSA + lLvUV+k8GQOZDdQVWQvZL3LX0HbyCPl6BRdFBsV2JdxB9oN3MCRlSeXnKhmqOqq/q1Wp + O2owavQeCtGU0Hx+OENLR5tGe0gnW/e4nqDeon61QaAhxvCLUe+RZGMzE1aTKdNGs3Rz + bwtdSwHLzaOPrWqtk23cjx2y5bJdtxuyrzye6RBxwsPRzEnZWchln8v6yRmYM3XueR5x + nr5ex7AoHAI3493ok4r38NXwY/V7538nID8wiGBMFCFuBfUHZ4U4kSRJn8g9oWlh9uHI + 8LWIzsjkKOtoweh3MUOn6mKz4oLj7RJUE3kTv55+mdSbXH4m6ax3immqbOp8Wl66eQbI + aDsXkInMfJaVka2fvXW+7oJnDm/O6MWMXPM8+rzBS2fzjQqoC/oLEy7rwozqLo65onnl + S0lLKaFMumyxvPiqQwVbxTDMKt2q7Wst1cTrMtdf16TXKtfO3kiqk6ubvHmqXqJ+rCGs + UbjxfhOxmae5tyWglbd1oC24Xbh9qCO8U6zz8a2YLpmuyduJ3fLdL3oyerV71++U9dne + pb7b1u9zj+/e0EDMffT9Vw+yB/Vh/LWoIqlbaTb2oel86SsZFhiFmByZc1mesDGxm3Ik + c+K5zLjRPCw8n3if8rXzFwjECLoJ6QmLi9CKrIgOIyMPSB14KpYorij+UiJdUkNyWeqi + tL70ukwxyhz1Rfa6nCOaHn1L3k+BX2FIMUoJpfTiYDpGE7OqfFnFXJVFdVwtD8ZdSGP+ + UIWmz2HZwx+0mrRDddR1vun26hXohxhYGEoY/mU0faTJON0EZ6ptxmu2YT5sUWEZe5Rs + 5WftYeNw7KitoZ2mvdJxaQfhE5yO+52A02fnDy6zJx+6trtVul/wiPckeDljTXFq3uI+ + 7D47+DXfF34T/iMBA4HdhDZifdC14NKQAtJ5clro6bDocHJEYGRgFCGaGBN0Kig2OC4k + npRASiSfDk0KTYbF6dmIFNdUwzRUOmv6ZsbsubuZ1VmZ2eTzThd0cyQvMl78lDuV13Wp + JD+pAF9ocVmpiKtou3juSn/JtdL0sqByu6smFZqV8lXIa1zVdNVfr3+oWaydvDFYd/tm + fX15Q25jYVNpc2VLTWt9W0t7Z0dPZ/+twa6x2xPd0z0vexfvfL/L2698z2rA737yg9LB + 2w+fDX0aYRqVHjN85PY46knReM/E/CRiSvyZ8bTv8/SZuhePZjfmOF6pzDssRL7OWapb + frCysPr1A9e64iezP7CbsZ/zt5u+jfz5dmcHALKPMmaXERDM2wDQLUFSgMTABP8fbh7Y + 2dnZghnivrPzJzdACIX/Ddo0yhkKZW5kc3RyZWFtCmVuZG9iagozOSAwIG9iagoyNjM0 + CmVuZG9iagoyNyAwIG9iagpbIC9JQ0NCYXNlZCAzOCAwIFIgXQplbmRvYmoKNDAgMCBv + YmoKPDwgL0xlbmd0aCA0MSAwIFIgL04gMyAvQWx0ZXJuYXRlIC9EZXZpY2VSR0IgL0Zp + bHRlciAvRmxhdGVEZWNvZGUgPj4Kc3RyZWFtCngB7VeJO9TbGz9jCdn3nck+jKWxJjvZ + Q0iWrDOMbQYzdtmibKFEiCwhkexbQpKSrSKSJJQkV9G9P0p1r3uG3336Pb/n9h90vs97 + zue87znvOd953+f7eQcAVpy+jY0lDQCAQAwl2ZoYIB2dnJF0M4Ae0AIG2LN7YsnBlDVw + yU/a1jOAoJieok3sjxrcNx2oe7dJzZI92sinuV6U/JNN/6hZcN5kLAAINFR4kODhAFBx + QcyF38MyFOy1h7UomGRvawjX2EBhwu9impMU7LWL9/lScERocCgA9FAAFzaYRMHFEJ/1 + CtjVp1L04Vg81DNIALCPA+vriQOAVQPq0TgCEWLEOYhtcQQcBS9AjCEEhsF77jbK2zJ5 + E4/bwdEIihAwBWHAG0QBG2ALrAESGIIgEAiFBLElnBnBUQb2akAVIhOAAQpQkEAfKEGk + BB/UT/yr7fp3BPLQsx8IhacggRkgAizcR/EZsfsogEh41p5dAdqCAAGgYEz//c48uz7/ + 7Y5KAFDiD+2wEfkBMKD8djw/dM69AGTCmAiif+jkvwOg5AVAwztsGCl8by/in4Ea0AFG + wA744G1RQAW+sRVwhfdPALmgBvSB5+B3BDNCFmGG8ENkIBoQk4hvVBJU1lQxVDVUM9T7 + qbWpSdQ11K9phGicaPJppmn5aV1pr9Ku7lPeF7dvhE6ALoCul56T3p/+HoMwQzTDzP5D + +4sZEYx4xidMmkzVzDzMZ5m3WYgsb1ndWGfYHNim2B3YZzjcOJY5gzi/caVx83PX8ujy + TPES+Oj5KvkN+ZcEUgTRgpNCccIo4WmRFFFN0Y/IqgNuYvxi0+KXJBwlhSTfSN2QDpcx + RHGglmQ75LLQPvJ6CsIK3xVfKd09WI05rxytgld1UDNV19LAHJLVlDwspnVAW1xHWldB + T01f38Da0N0o5MgZ42KTNtNxs3ULVkvM0RNW8dY1NlO2NHaq9r7HSxymHdmdrJyzXJ64 + crm5uFd6bHjpYLNxr33U8dm+7/2NA6oIdMSAoImQw6TqUO6wM+FfIolRyzEep+biXOPn + E7GnV5PJZ3ZSstJE05vPmWYuZp+6wJ9zK/fkJUR+TaFdEShuKPEs4y4frThbZVCNuN5f + e6bOsp67YaGpviW+zbZD5hbomu3u7C3oi+n3GDB9gHmIHGYdRYxtPf44vvL07dTS9PLM + 6uynuW8L9K95l2SWtVfsVwlraR+vbwz//nGT+7POtv+3wj+Hd3Z+5cKvXPiVC7++C/// + XfjBG1tT/60b5H7oQDQkjjgovj/hLn9oE9rl12DItxTOwwNfyIsULsRChkGC/+VKNJxj + dvn1IGTQPaS+y5z6kJ8DoZXCqnseyLszb0CGHEsC4bDHwZVgr07YozNAjYDlBawICFQz + NMa0zXSi9BkMXxhxTOMs2qy17DwcSZzr3Cd5PHnxfIH8JIEIwVih08KpIpmiF5GFB0rE + KsVrJBokL0nFSnvLWKKUZQXlqOTeo8flOxVKFVOUiAePY7SVpVTYVL6qvlEbU+/QKDuU + rkk+7Kploq2kI6BLo/tBb03/N4P3hitG7468NV4yeW26aLZg/spizvLl0RdWM9arNl9t + Ge2E7NHHtR2sTrg5BjklOJ93KT/Z7Nrv9tR9wWPNcxtLi+PwFvGRw6v7GvnZ+LsG+AeG + E5KI2UFVwbdDxknL5O9hHOEyETqRdlF+0fExeaduxPbFPYufSHicOHJ6MOlect+Z7rOd + Ka2pjWk302syqs6VZ5Zknc72OW9+QTGHM+fzxZe5fXmVl9LyCQXHClUvC1z+q+hlcdeV + /JKwUrsypXKW8rWrwxXVlWeqsNf0q0Wqv1+fq+mpvXIjrs7tpm69aP1Ow0JjX1N5c2KL + V6tBm1g7VftiR19n6a24LtfbWt2C3V97Zno77+T3hd916Fe9R3dvZqDuftwDm0Hxwc2H + g0OFw4EjOqNso2/G2h6lPDZ6QvXkzvipiUMTW09bJoOn5KdWn1VPez8Xez4/U/zCehYx + 2/oSPycwN/YqYR4z/3ahYNFi8a/XjW+wSzxLvW99lpmX2965rtCs1L23f/99teI3i9/+ + WLv8weDDKoy/ApULdRpNB+0SHQe9HgNhfxHjENMWiwSrLVsC+02OVE4slw6sLf7D84i3 + hi+Z311AU5BbcENoRLhKJEFUULQdeQy5diBFTEysR9xR/A+JLEmU5ICUu9RX6TwZA5kN + 1BVZC9kvctfQdvII+XoFF0UGxXYl3EH2g3cwJGVJ5ecqGao6qr+rVak7ajBq9B4K0ZTQ + fH44Q0tHm0Z7SCdb97ieoN6ifrVBoCHG8ItR75FkYzMTVpMp00azdHNvC11LAcvNo4+t + aq2TbdyPHbLlsl23G7KvPJ7pEHHCw9HMSdlZyGWfy/rJGZgzde55HnGevl7HsCgcAjfj + 3eiTivfw1fBj9XvnfycgPzCIYEwUIW4F9QdnhTiRJEmfyD2haWH24cjwtYjOyOQo62jB + 6HcxQ6fqYrPiguPtElQTeRO/nn6Z1JtcfibprHeKaaps6nxaXrp5BshoOxeQicx8lpWR + rZ+9db7ugmcOb87oxYxc8zz6vMFLZ/ONCqgL+gsTLuvCjOoujrmieeVLSUspoUy6bLG8 + +KpDBVvFMMwq3artay3VxOsy11/XpNcq187eSKqTq5u8eapeon6sIaxRuPF+E7GZp7m3 + JaCVt3WgLbhduH2oI7xTrPPxrZguma7J24nd8t0vejJ6tXvX75T12d6lvtvW73OP797Q + QMx99P1XD7IH9WH8tagiqVtpNvah6XzpKxkWGIWYHJlzWZ6wMbGbciRz4rnMuNE8LDyf + eJ/ytfMXCMQIugnpCYuL0IqsiA4jIw9IHXgqliiuKP5SIl1SQ3JZ6qK0vvS6TDHKHPVF + 9rqcI5oefUveT4FfYUgxSgml9OJgOkYTs6p8WcVclUV1XC0Pxl1IY/5QhabPYdnDH7Sa + tEN11HW+6fbqFeiHGFgYShj+ZTR9pMk43QRnqm3Ga7ZhPmxRYRl7lGzlZ+1h43DsqK2h + naa90nFpB+ETnI77nYDTZ+cPLrMnH7q2u1W6X/CI9yR4OWNNcWre4j7sPjv4Nd8XfhP+ + IwEDgd2ENmJ90LXg0pAC0nlyWujpsOhwckRgZGAUIZoYE3QqKDY4LiSelEBKJJ8OTQpN + hsXp2YgU11TDNFQ6a/pmxuy5u5nVWZnZ5PNOF3RzJC8yXvyUO5XXdakkP6kAX2hxWamI + q2i7eO5Kf8m10vSyoHK7qyYVmpXyVchrXNV01V+vf6hZrJ28MVh3+2Z9fXlDbmNhU2lz + ZUtNa31bS3tnR09n/63BrrHbE93TPS97F+98v8vbr3zPasDvfvKD0sHbD58NfRphGpUe + M3zk9jjqSdF4z8T8JGJK/JnxtO/z9Jm6F49mN+Y4XqnMOyxEvs5Zqlt+sLKw+vUD17ri + J7M/sJuxn/O3m76N/Pl2ZwcAso8yZpcREMzbANAtQVKAxMAE/x9uHtjZ2dmCGeK+s/Mn + N0AIhf8N2jTKGQplbmRzdHJlYW0KZW5kb2JqCjQxIDAgb2JqCjI2MzQKZW5kb2JqCjI0 + IDAgb2JqClsgL0lDQ0Jhc2VkIDQwIDAgUiBdCmVuZG9iago0MiAwIG9iago8PCAvTGVu + Z3RoIDQzIDAgUiAvTiAzIC9BbHRlcm5hdGUgL0RldmljZVJHQiAvRmlsdGVyIC9GbGF0 + ZURlY29kZSA+PgpzdHJlYW0KeAHtV4k71NsbP2MJ2fedyT6MpbEmO9lDSJasM4xtBjN2 + 2aJsoUSILCGR7FtCkpKtIpIklCRX0b0/SnWve4bfffo9v+f2H3S+z3vO57zvOe8533nf + 5/t5BwBWnL6NjSUNAIBADCXZmhggHZ2ckXQzgB7QAgbYs3tiycGUNXDJT9rWM4CgmJ6i + TeyPGtw3Hah7t0nNkj3ayKe5XpT8k03/qFlw3mQsAAg0VHiQ4OEAUHFBzIXfwzIU7LWH + tSiYZG9rCNfYQGHC72KakxTstYv3+VJwRGhwKAD0UAAXNphEwcUQn/UK2NWnUvThWDzU + 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 + qu1rLdXE6zLXX9ek1yrXzt5IqpOrm7x5ql6ifqwhrFG48X4TsZmnubcloJW3daAtuF24 + fagjvFOs8/GtmC6Zrsnbid3y3S96Mnq1e9fvlPXZ3qW+29bvc4/v3tBAzH30/VcPsgf1 + Yfy1qCKpW2k29qHpfOkrGRYYhZgcmXNZnrAxsZtyJHPiucy40TwsPJ94n/K18xcIxAi6 + CekJi4vQiqyIDiMjD0gdeCqWKK4o/lIiXVJDclnqorS+9LpMMcoc9UX2upwjmh59S95P + gV9hSDFKCaX04mA6RhOzqnxZxVyVRXVcLQ/GXUhj/lCFps9h2cMftJq0Q3XUdb7p9uoV + 6IcYWBhKGP5lNH2kyTjdBGeqbcZrtmE+bFFhGXuUbOVn7WHjcOyoraGdpr3ScWkH4ROc + jvudgNNn5w8usycfura7Vbpf8Ij3JHg5Y01xat7iPuw+O/g13xd+E/4jAQOB3YQ2Yn3Q + teDSkALSeXJa6Omw6HByRGBkYBQhmhgTdCooNjguJJ6UQEoknw5NCk2GxenZiBTXVMM0 + VDpr+mbG7Lm7mdVZmdnk804XdHMkLzJe/JQ7ldd1qSQ/qQBfaHFZqYiraLt47kp/ybXS + 9LKgcrurJhWalfJVyGtc1XTVX69/qFmsnbwxWHf7Zn19eUNuY2FTaXNlS01rfVtLe2dH + T2f/rcGusdsT3dM9L3sX73y/y9uvfM9qwO9+8oPSwdsPnw19GmEalR4zfOT2OOpJ0XjP + xPwkYkr8mfG07/P0mboXj2Y35jheqcw7LES+zlmqW36wsrD69QPXuuInsz+wm7Gf87eb + vo38+XZnBwCyjzJmlxEQzNsA0C1BUoDEwAT/H24e2NnZ2YIZ4r6z8yc3QAiF/w3aNMoZ + CmVuZHN0cmVhbQplbmRvYmoKNDMgMCBvYmoKMjYzNAplbmRvYmoKMjEgMCBvYmoKWyAv + SUNDQmFzZWQgNDIgMCBSIF0KZW5kb2JqCjQ0IDAgb2JqCjw8IC9MZW5ndGggNDUgMCBS + IC9OIDEgL0FsdGVybmF0ZSAvRGV2aWNlR3JheSAvRmlsdGVyIC9GbGF0ZURlY29kZSA+ + PgpzdHJlYW0KeAGFUk9IFFEc/s02EoSIQYV4iHcKCZUprKyg2nZ1WZVtW5XSohhn37qj + szPTm9k1xZMEXaI8dQ+iY3Ts0KGbl6LArEvXIKkgCDx16PvN7OoohG95O9/7/f1+33tE + bZ2m7zspQVRzQ5UrpaduTk2Lgx8pRR3UTlimFfjpYnGMseu5kr+719Zn0tiy3se1dvv2 + PbWVZWAh6i22txD6IZFmAB+ZnyhlgLPAHZav2D4BPFgOrBrwI6IDD5q5MNPRnHSlsi2R + U+aiKCqvYjtJrvv5uca+i7WJg/5cj2bWjr2z6qrRTNS090ShvA+uRBnPX1T2bDUUpw3j + nEhDGinyrtXfK0zHEZErEEoGUjVkuZ9qTp114HUYu126k+P49hClPslgqIm16bKZHYV9 + AHYqy+wQ8AXo8bJiD+eBe2H/W1HDk8AnYT9kh3nWrR/2F65T4HuEPTXgzhSuxfHaih9e + LQFD91QjaIxzTcTT1zlzpIjvMdQZmPdGOaYLMXeWqhM3gDthH1mqZgqxXfuu6iXuewJ3 + 0+M70Zs5C1ygHElysRXZFNA8CVgUfYuwSQ48Ps4eVeB3qJjAHLmJ3M0o9x7VERtno1KB + VnqNV8ZP47nxxfhlbBjPgH6sdtd7fP/p4xV117Y+PPmNetw5rr2dG1VhVnFlC93/xzKE + j9knOabB06FZWGvYduQPmsxMsAwoxH8FPpf6khNV3NXu7bhFEsxQPixsJbpLVG4p1Oo9 + g0qsHCvYAHZwksQsWhy4U2u6OXh32CJ6bflNV7Lrhv769nr72vIebcqoKSgTzbNEZpSx + W6Pk3Xjb/WaREZ84Or7nvYpayf5JRRA/hTlaKvIUVfRWUNbEb2cOfhu2flw/pef1Qf08 + CT2tn9Gv6KMRvgx0Sc/Cc1Efo0nwsGkh4hKgioMz1E5UY40D4inx8rRbZJH9D0AZ/WYK + ZW5kc3RyZWFtCmVuZG9iago0NSAwIG9iago3MDQKZW5kb2JqCjE4IDAgb2JqClsgL0lD + Q0Jhc2VkIDQ0IDAgUiBdCmVuZG9iago0NiAwIG9iago8PCAvTGVuZ3RoIDQ3IDAgUiAv + TiAzIC9BbHRlcm5hdGUgL0RldmljZVJHQiAvRmlsdGVyIC9GbGF0ZURlY29kZSA+Pgpz + dHJlYW0KeAHtV4k71NsbP2MJ2fedyT6MpbEmO9lDSJasM4xtBjN22aJsoUSILCGR7FtC + kpKtIpIklCRX0b0/SnWve4bfffo9v+f2H3S+z3vO57zvOe8533nf5/t5BwBWnL6NjSUN + AIBADCXZmhggHZ2ckXQzgB7QAgbYs3tiycGUNXDJT9rWM4CgmJ6iTeyPGtw3Hah7t0nN + kj3ayKe5XpT8k03/qFlw3mQsAAg0VHiQ4OEAUHFBzIXfwzIU7LWHtSiYZG9rCNfYQGHC + 72KakxTstYv3+VJwRGhwKAD0UAAXNphEwcUQn/UK2NWnUvThWDzUM0gAsI8D6+uJA4BV + A+rROAIRYsQ5iG1xBBwFL0CMIQSGwXvuNsrbMnkTj9vB0QiKEDAFYcAbRAEbYAusARIY + giAQCIUEsSWcGcFRBvZqQBUiE4ABClCQQB8oQaQEH9RP/Kvt+ncE8tCzHwiFpyCBGSAC + LNxH8Rmx+yiASHjWnl0B2oIAAaBgTP/9zjy7Pv/tjkoAUOIP7bAR+QEwoPx2PD90zr0A + ZMKYCKJ/6OS/A6DkBUDDO2wYKXxvL+KfgRrQAUbADvjgbVFABb6xFXCF908AuaAG9IHn + 4HcEM0IWYYbwQ2QgGhCTiG9UElTWVDFUNVQz1PuptalJ1DXUr2mEaJxo8mmmaflpXWmv + 0q7uU94Xt2+EToAugK6XnpPen/4egzBDNMPM/kP7ixkRjHjGJ0yaTNXMPMxnmbdZiCxv + Wd1YZ9gc2KbYHdhnONw4ljmDOL9xpXHzc9fy6PJM8RL46Pkq+Q35lwRSBNGCk0Jxwijh + aZEUUU3Rj8iqA25i/GLT4pckHCWFJN9I3ZAOlzFEcaCWZDvkstA+8noKwgrfFV8p3T1Y + jTmvHK2CV3VQM1XX0sAcktWUPCymdUBbXEdaV0FPTV/fwNrQ3SjkyBnjYpM203GzdQtW + S8zRE1bx1jU2U7Y0dqr2vsdLHKYd2Z2snLNcnrhyubm4V3pseOlgs3GvfdTx2b7v/Y0D + qgh0xICgiZDDpOpQ7rAz4V8iiVHLMR6n5uJc4+cTsadXk8lndlKy0kTTm8+ZZi5mn7rA + n3Mr9+QlRH5NoV0RKG4o8SzjLh+tOFtlUI243l97ps6ynrthoam+Jb7NtkPmFuia7e7s + LeiL6fcYMH2AeYgcZh1FjG09/ji+8vTt1NL08szq7Ke5bwv0r3mXZJa1V+xXCWtpH69v + DP/+cZP7s862/7fCP4d3dn7lwq9c+JULv74L//9d+MEbW1P/rRvkfuhANCSOOCi+P+Eu + f2gT2uXXYMi3FM7DA1/IixQuxEKGQYL/5Uo0nGN2+fUgZNA9pL7LnPqQnwOhlcKqex7I + uzNvQIYcSwLhsMfBlWCvTtijM0CNgOUFrAgIVDM0xrTNdKL0GQxfGHFM4yzarLXsPBxJ + nOvcJ3k8efF8gfwkgQjBWKHTwqkimaIXkYUHSsQqxWskGiQvScVKe8tYopRlBeWo5N6j + x+U7FUoVU5SIB49jtJWlVNhUvqq+URtT79AoO5SuST7sqmWiraQjoEuj+0FvTf83g/eG + K0bvjrw1XjJ5bbpotmD+ymLO8uXRF1Yz1qs2X20Z7YTs0ce1HaxOuDkGOSU4n3cpP9ns + 2u/21H3BY81zG0uL4/AW8ZHDq/sa+dn4uwb4B4YTkojZQVXBt0PGScvk72Ec4TIROpF2 + UX7R8TF5p27E9sU9i59IeJw4cnow6V5y35nus50pramNaTfTazKqzpVnlmSdzvY5b35B + MYcz5/PFl7l9eZWX0vIJBccKVS8LXP6r6GVx15X8krBSuzKlcpbytavDFdWVZ6qw1/Sr + Raq/X5+r6am9ciOuzu2mbr1o/U7DQmNfU3lzYotXq0GbWDtV+2JHX2fprbgu19ta3YLd + X3tmejvv5PeF33XoV71Hd29moO5+3AObQfHBzYeDQ4XDgSM6o2yjb8baHqU8NnpC9eTO + +KmJQxNbT1smg6fkp1afVU97Pxd7Pj9T/MJ6FjHb+hI/JzA39iphHjP/dqFg0WLxr9eN + b7BLPEu9b32WmZfb3rmu0KzUvbd//3214jeL3/5Yu/zB4MMqjL8ClQt1Gk0H7RIdB70e + A2F/EeMQ0xaLBKstWwL7TY5UTiyXDqwt/sPziLeGL5nfXUBTkFtwQ2hEuEokQVRQtB15 + DLl2IEVMTKxH3FH8D4ksSZTkgJS71FfpPBkDmQ3UFVkL2S9y19B28gj5egUXRQbFdiXc + QfaDdzAkZUnl5yoZqjqqv6tVqTtqMGr0HgrRlNB8fjhDS0ebRntIJ1v3uJ6g3qJ+tUGg + Icbwi1HvkWRjMxNWkynTRrN0c28LXUsBy82jj61qrZNt3I8dsuWyXbcbsq88nukQccLD + 0cxJ2VnIZZ/L+skZmDN17nkecZ6+XsewKBwCN+Pd6JOK9/DV8GP1e+d/JyA/MIhgTBQh + bgX1B2eFOJEkSZ/IPaFpYfbhyPC1iM7I5CjraMHodzFDp+pis+KC4+0SVBN5E7+efpnU + m1x+Jumsd4ppqmzqfFpeunkGyGg7F5CJzHyWlZGtn711vu6CZw5vzujFjFzzPPq8wUtn + 840KqAv6CxMu68KM6i6OuaJ55UtJSymhTLpssbz4qkMFW8UwzCrdqu1rLdXE6zLXX9ek + 1yrXzt5IqpOrm7x5ql6ifqwhrFG48X4TsZmnubcloJW3daAtuF24fagjvFOs8/GtmC6Z + rsnbid3y3S96Mnq1e9fvlPXZ3qW+29bvc4/v3tBAzH30/VcPsgf1Yfy1qCKpW2k29qHp + fOkrGRYYhZgcmXNZnrAxsZtyJHPiucy40TwsPJ94n/K18xcIxAi6CekJi4vQiqyIDiMj + D0gdeCqWKK4o/lIiXVJDclnqorS+9LpMMcoc9UX2upwjmh59S95PgV9hSDFKCaX04mA6 + RhOzqnxZxVyVRXVcLQ/GXUhj/lCFps9h2cMftJq0Q3XUdb7p9uoV6IcYWBhKGP5lNH2k + yTjdBGeqbcZrtmE+bFFhGXuUbOVn7WHjcOyoraGdpr3ScWkH4ROcjvudgNNn5w8usycf + ura7Vbpf8Ij3JHg5Y01xat7iPuw+O/g13xd+E/4jAQOB3YQ2Yn3QteDSkALSeXJa6Omw + 6HByRGBkYBQhmhgTdCooNjguJJ6UQEoknw5NCk2GxenZiBTXVMM0VDpr+mbG7Lm7mdVZ + mdnk804XdHMkLzJe/JQ7ldd1qSQ/qQBfaHFZqYiraLt47kp/ybXS9LKgcrurJhWalfJV + yGtc1XTVX69/qFmsnbwxWHf7Zn19eUNuY2FTaXNlS01rfVtLe2dHT2f/rcGusdsT3dM9 + L3sX73y/y9uvfM9qwO9+8oPSwdsPnw19GmEalR4zfOT2OOpJ0XjPxPwkYkr8mfG07/P0 + mboXj2Y35jheqcw7LES+zlmqW36wsrD69QPXuuInsz+wm7Gf87ebvo38+XZnBwCyjzJm + lxEQzNsA0C1BUoDEwAT/H24e2NnZ2YIZ4r6z8yc3QAiF/w3aNMoZCmVuZHN0cmVhbQpl + bmRvYmoKNDcgMCBvYmoKMjYzNAplbmRvYmoKMzMgMCBvYmoKWyAvSUNDQmFzZWQgNDYg + MCBSIF0KZW5kb2JqCjQ4IDAgb2JqCjw8IC9MZW5ndGggNDkgMCBSIC9OIDMgL0FsdGVy + bmF0ZSAvRGV2aWNlUkdCIC9GaWx0ZXIgL0ZsYXRlRGVjb2RlID4+CnN0cmVhbQp4Ae1X + iTvU2xs/YwnZ953JPoylsSY72UNIlqwzjG0GM3bZomyhRIgsIZHsW0KSkq0ikiSUJFfR + vT9Kda97ht99+j2/5/YfdL7Pe87nvO857znfed/n+3kHAFacvo2NJQ0AgEAMJdmaGCAd + nZyRdDOAHtACBtize2LJwZQ1cMlP2tYzgKCYnqJN7I8a3DcdqHu3Sc2SPdrIp7lelPyT + Tf+oWXDeZCwACDRUeJDg4QBQcUHMhd/DMhTstYe1KJhkb2sI19hAYcLvYpqTFOy1i/f5 + UnBEaHAoAPRQABc2mETBxRCf9QrY1adS9OFYPNQzSACwjwPr64kDgFUD6tE4AhFixDmI + bXEEHAUvQIwhBIbBe+42ytsyeROP28HRCIoQMAVhwBtEARtgC6wBEhiCIBAIhQSxJZwZ + wVEG9mpAFSITgAEKUJBAHyhBpAQf1E/8q+36dwTy0LMfCIWnIIEZIAIs3EfxGbH7KIBI + eNaeXQHaggABoGBM//3OPLs+/+2OSgBQ4g/tsBH5ATCg/HY8P3TOvQBkwpgIon/o5L8D + oOQFQMM7bBgpfG8v4p+BGtABRsAO+OBtUUAFvrEVcIX3TwC5oAb0gefgdwQzQhZhhvBD + ZCAaEJOIb1QSVNZUMVQ1VDPU+6m1qUnUNdSvaYRonGjyaaZp+Wldaa/Sru5T3he3b4RO + gC6Arpeek96f/h6DMEM0w8z+Q/uLGRGMeMYnTJpM1cw8zGeZt1mILG9Z3Vhn2BzYptgd + 2Gc43DiWOYM4v3GlcfNz1/Lo8kzxEvjo+Sr5DfmXBFIE0YKTQnHCKOFpkRRRTdGPyKoD + bmL8YtPilyQcJYUk30jdkA6XMURxoJZkO+Sy0D7yegrCCt8VXyndPViNOa8crYJXdVAz + VdfSwByS1ZQ8LKZ1QFtcR1pXQU9NX9/A2tDdKOTIGeNikzbTcbN1C1ZLzNETVvHWNTZT + tjR2qva+x0scph3Znaycs1yeuHK5ubhXemx46WCzca991PHZvu/9jQOqCHTEgKCJkMOk + 6lDusDPhXyKJUcsxHqfm4lzj5xOxp1eTyWd2UrLSRNObz5lmLmafusCfcyv35CVEfk2h + XREobijxLOMuH604W2VQjbjeX3umzrKeu2Ghqb4lvs22Q+YW6Jrt7uwt6Ivp9xgwfYB5 + iBxmHUWMbT3+OL7y9O3U0vTyzOrsp7lvC/SveZdklrVX7FcJa2kfr28M//5xk/uzzrb/ + t8I/h3d2fuXCr1z4lQu/vgv//134wRtbU/+tG+R+6EA0JI44KL4/4S5/aBPa5ddgyLcU + zsMDX8iLFC7EQoZBgv/lSjScY3b59SBk0D2kvsuc+pCfA6GVwqp7Hsi7M29AhhxLAuGw + x8GVYK9O2KMzQI2A5QWsCAhUMzTGtM10ovQZDF8YcUzjLNqstew8HEmc69wneTx58XyB + /CSBCMFYodPCqSKZoheRhQdKxCrFayQaJC9JxUp7y1iilGUF5ajk3qPH5TsVShVTlIgH + j2O0laVU2FS+qr5RG1Pv0Cg7lK5JPuyqZaKtpCOgS6P7QW9N/zeD94YrRu+OvDVeMnlt + umi2YP7KYs7y5dEXVjPWqzZfbRnthOzRx7UdrE64OQY5JTifdyk/2eza7/bUfcFjzXMb + S4vj8BbxkcOr+xr52fi7BvgHhhOSiNlBVcG3Q8ZJy+TvYRzhMhE6kXZRftHxMXmnbsT2 + xT2Ln0h4nDhyejDpXnLfme6znSmtqY1pN9NrMqrOlWeWZJ3O9jlvfkExhzPn88WXuX15 + lZfS8gkFxwpVLwtc/qvoZXHXlfySsFK7MqVylvK1q8MV1ZVnqrDX9KtFqr9fn6vpqb1y + I67O7aZuvWj9TsNCY19TeXNii1erQZtYO1X7YkdfZ+mtuC7X21rdgt1fe2Z6O+/k94Xf + dehXvUd3b2ag7n7cA5tB8cHNh4NDhcOBIzqjbKNvxtoepTw2ekL15M74qYlDE1tPWyaD + p+SnVp9VT3s/F3s+P1P8wnoWMdv6Ej8nMDf2KmEeM/92oWDRYvGv141vsEs8S71vfZaZ + l9veua7QrNS9t3//fbXiN4vf/li7/MHgwyqMvwKVC3UaTQftEh0HvR4DYX8R4xDTFosE + qy1bAvtNjlROLJcOrC3+w/OIt4Yvmd9dQFOQW3BDaES4SiRBVFC0HXkMuXYgRUxMrEfc + UfwPiSxJlOSAlLvUV+k8GQOZDdQVWQvZL3LX0HbyCPl6BRdFBsV2JdxB9oN3MCRlSeXn + KhmqOqq/q1WpO2owavQeCtGU0Hx+OENLR5tGe0gnW/e4nqDeon61QaAhxvCLUe+RZGMz + E1aTKdNGs3RzbwtdSwHLzaOPrWqtk23cjx2y5bJdtxuyrzye6RBxwsPRzEnZWchln8v6 + yRmYM3XueR5xnr5ex7AoHAI3493ok4r38NXwY/V7538nID8wiGBMFCFuBfUHZ4U4kSRJ + n8g9oWlh9uHI8LWIzsjkKOtoweh3MUOn6mKz4oLj7RJUE3kTv55+mdSbXH4m6ax3immq + bOp8Wl66eQbIaDsXkInMfJaVka2fvXW+7oJnDm/O6MWMXPM8+rzBS2fzjQqoC/oLEy7r + wozqLo65onnlS0lLKaFMumyxvPiqQwVbxTDMKt2q7Wst1cTrMtdf16TXKtfO3kiqk6ub + vHmqXqJ+rCGsUbjxfhOxmae5tyWglbd1oC24Xbh9qCO8U6zz8a2YLpmuyduJ3fLdL3oy + erV71++U9dnepb7b1u9zj+/e0EDMffT9Vw+yB/Vh/LWoIqlbaTb2oel86SsZFhiFmByZ + c1mesDGxm3Ikc+K5zLjRPCw8n3if8rXzFwjECLoJ6QmLi9CKrIgOIyMPSB14KpYorij+ + UiJdUkNyWeqitL70ukwxyhz1Rfa6nCOaHn1L3k+BX2FIMUoJpfTiYDpGE7OqfFnFXJVF + dVwtD8ZdSGP+UIWmz2HZwx+0mrRDddR1vun26hXohxhYGEoY/mU0faTJON0EZ6ptxmu2 + YT5sUWEZe5Rs5WftYeNw7KitoZ2mvdJxaQfhE5yO+52A02fnDy6zJx+6trtVul/wiPck + eDljTXFq3uI+7D47+DXfF34T/iMBA4HdhDZifdC14NKQAtJ5clro6bDocHJEYGRgFCGa + GBN0Kig2OC4knpRASiSfDk0KTYbF6dmIFNdUwzRUOmv6ZsbsubuZ1VmZ2eTzThd0cyQv + Ml78lDuV13WpJD+pAF9ocVmpiKtou3juSn/JtdL0sqByu6smFZqV8lXIa1zVdNVfr3+o + WaydvDFYd/tmfX15Q25jYVNpc2VLTWt9W0t7Z0dPZ/+twa6x2xPd0z0vexfvfL/L2698 + z2rA737yg9LB2w+fDX0aYRqVHjN85PY46knReM/E/CRiSvyZ8bTv8/SZuhePZjfmOF6p + zDssRL7OWapbfrCysPr1A9e64iezP7CbsZ/zt5u+jfz5dmcHALKPMmaXERDM2wDQLUFS + gMTABP8fbh7Y2dnZghnivrPzJzdACIX/Ddo0yhkKZW5kc3RyZWFtCmVuZG9iago0OSAw + IG9iagoyNjM0CmVuZG9iagozMCAwIG9iagpbIC9JQ0NCYXNlZCA0OCAwIFIgXQplbmRv + YmoKMyAwIG9iago8PCAvVHlwZSAvUGFnZXMgL01lZGlhQm94IFswIDAgNzU2IDU1M10g + L0NvdW50IDEgL0tpZHMgWyAyIDAgUiBdID4+CmVuZG9iago1MCAwIG9iago8PCAvVHlw + ZSAvQ2F0YWxvZyAvUGFnZXMgMyAwIFIgL1ZlcnNpb24gLzEuNCA+PgplbmRvYmoKNTEg + MCBvYmoKPDwgL0xlbmd0aCA1MiAwIFIgL0xlbmd0aDEgNzE3MiAvRmlsdGVyIC9GbGF0 + ZURlY29kZSA+PgpzdHJlYW0KeAG9WXtYlNW6f9f3fXNBUG4Kw3Vm+BiugwgooJiMOMNF + TFEUGfMygCCSGClSlhK7dJd4OVtNbWtHs4s7JXME0gG2Ru7c6a6dVrub2043szpPPtU5 + dWqHzJzf+mYk7dn1+EdPfM87613X97d+77vW960FMSIKoDYSyVLTWNVEa2gAJa9AWmta + mg2bP5+0l4hNIxKX1TUtaQz+4C9/I5JcRMMClixbXff11rzpRCNeJNIa6murFn+jX9ZN + FHYJ/bPrURAwMOIOovBo5OPrG5vvtm1UP4O8BXnLsjtqqkJygxzItyEf11h1d5N25bDv + kX8SecPyqsbahPzouchjfEppumNlM3tGqEP+K+QLmlbUNv35geUZRLqxwHcOZQwP/wsg + Na1AaqOxvhKlWPkRflSv08TrdK8qIVFB1BANRAsh8qNh5I/xhys5TN2XBqJxPwWpTlCS + qo0ipXTSE3nehVzgqXuO57LqJQpyN3q+FvPQp4eL4M6fSP20mfbQEdh5GnoSLaRH6Cxr + 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 + MlwC69SfSnRJSF6IRc4SqO+P2at/PmaJ/iSkw1t1KBktjukPxizTb491sd2d+m0xLoY+ + W73Jqhh0PaZvTN6pX5yh1E/b6RI6OvXjUV9h8ddn5xr142Iu6dMTXVqGfFrMNH1Kxt/1 + 8eiIZgYMarIE66NjtusnoCo2xpY4AdLHDrE9lML2dJqm6nuhYrpdJcm5O13s3q7ipAyT + i91jyS5O2plcnGhKnqY3JRcmJkKvOKNZp7lNM1mTqUnFBW2CxqiJ0ozUhmiDtCO0Adph + Wq1W42LPdObr1X2sg/JBS0eXVq1VudizKJT62GGl8PBxraQVtKQd6fJ8gH/mMBrpYh3d + CAxGUI6pFU3tYoexFnjRYYseoYwNRKkIQoTxj2H+SwLTCgghJ9vsUtP6sJZ8XX7IpODx + hdaf+3EoNdd+U3/+T8dinDtxF+M8FGPHtRcUT4z9WnPdNeVn0+ZVqKotSE0tnbW6q6Wp + oU65xpNttQ7c5jk3tuBata3aYDja0OS7o0xwVNfU83ukqlpnk1xrdTbIVsPRFqUfL76u + uo5Xt8jWo1Rnm115tM5Sa+1ssbTY+HVmV3XBigU32NowZGtFwb+xVcAHW8FtVSv9fmJr + Aa+u5rYWcFsLuK1qS7Vii0/etrS8YGUzohNXfbhqSyp3lsycV4kbbbvVxQ7w+79V9P+8 + rYdICmVuZHN0cmVhbQplbmRvYmoKNTIgMCBvYmoKNDM3MQplbmRvYmoKNTMgMCBvYmoK + PDwgL1R5cGUgL0ZvbnREZXNjcmlwdG9yIC9Bc2NlbnQgNzcwIC9DYXBIZWlnaHQgNzI3 + IC9EZXNjZW50IC0yMzAgL0ZsYWdzIDk2Ci9Gb250QkJveCBbLTkzMyAtNDgxIDE1NzEg + MTEzOF0gL0ZvbnROYW1lIC9YUUlGU1crSGVsdmV0aWNhLU9ibGlxdWUgL0l0YWxpY0Fu + Z2xlCi0xMiAvU3RlbVYgMCAvTWF4V2lkdGggMTUwMCAvWEhlaWdodCA1MzEgL0ZvbnRG + aWxlMiA1MSAwIFIgPj4KZW5kb2JqCjU0IDAgb2JqClsgNjY3IDAgMCAwIDAgMCAwIDAg + ODMzIDAgMCAwIDAgMCAwIDAgMCAwIDAgNjY3IDAgMCAwIDAgMCAwIDAgMCA1NTYgMCA1 + MDAKMCA1NTYgMCA1NTYgMCAyMjIgMCAwIDIyMiA4MzMgNTU2IDU1NiA1NTYgMCAwIDAg + Mjc4IDAgMCAwIDUwMCBdCmVuZG9iagoxOSAwIG9iago8PCAvVHlwZSAvRm9udCAvU3Vi + dHlwZSAvVHJ1ZVR5cGUgL0Jhc2VGb250IC9YUUlGU1crSGVsdmV0aWNhLU9ibGlxdWUg + L0ZvbnREZXNjcmlwdG9yCjUzIDAgUiAvV2lkdGhzIDU0IDAgUiAvRmlyc3RDaGFyIDY5 + IC9MYXN0Q2hhciAxMjAgL0VuY29kaW5nIC9NYWNSb21hbkVuY29kaW5nCj4+CmVuZG9i + ago1NSAwIG9iago8PCAvTGVuZ3RoIDU2IDAgUiAvTGVuZ3RoMSA5OTcyIC9GaWx0ZXIg + L0ZsYXRlRGVjb2RlID4+CnN0cmVhbQp4Ab16e3iU1bX32vu9ziWTmcncM5PJZDIzmdxD + SMiQQMaQhHsMBCFBggkQCAg1YIyiwkGFIhGpQrkIPVS05RKKGUIKAxQ/ykHR1lPxitdW + j2i1T/N4Tg/aVpiZb+13Qgo+/fr5R5/OO/u+373X+q2199qXFwgAaGEdcBBeuKK9C54n + 4zHnFXRrF/Z0Zz7+xfi9AGQaALd8cdeSFYaP/uNXAHwUQK1dsnz14rJtr8wH0J0HMF7u + 7Ghf9Kf/XX4SwHMQ3y/vxAx1llSE6Y8wnd25ovu+RRUqC0AWj+l5y+9a2F4WHVuO6TZM + F69ov69LfkD9V0w/genM77Wv6Jj7wMPbMB3BdFbXXXd3000clmW9ienGrlUdXb945Hsl + AN5spO9VzCP4sJ8WRFiFYR2Mxhyq5F33uOuR4ZAH4Vs5yaSIgQQyqDBUgwbbZL8U0EEq + 6MGAcSOkgQnMSj5yJZwFvXAGcoR14OCLwA2QeBfdeyyM35b4TLgA+viKxP9wlfgGogQn + aby6Cs7C47AH+pHigxjPgfmwC14my+AkmQeD8DbJgEKUDw9RmAavkETiNVgMP8H63XAO + tsNRpCsHViAV02AL8SXux3QY4wtgfeIZyIYK+D6cgRC2ugWGEocSx7B0JtwGfXAY3/81 + 8dKjfFriucRl5HQGtrkeS15LTEv0I3f5UAONmLsetcLHvZfoBBtUInU/gh/DPvgl/JE8 + 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 + VrXe1Nemkb5W1fydvmpYY6tYXwuU977VVysrXsD6amV9tbK+FoQXKH0xCOqWNtXc3Y3a + iVfbeLWc0xSZPGNuM37B0VIbJfvZffc98H8BcaX7nAplbmRzdHJlYW0KZW5kb2JqCjU2 + IDAgb2JqCjY2ODkKZW5kb2JqCjU3IDAgb2JqCjw8IC9UeXBlIC9Gb250RGVzY3JpcHRv + ciAvQXNjZW50IDc3MCAvQ2FwSGVpZ2h0IDcyNyAvRGVzY2VudCAtMjMwIC9GbGFncyAz + MgovRm9udEJCb3ggWy05NTEgLTQ4MSAxNDQ1IDExMjJdIC9Gb250TmFtZSAvWFlVVFBT + K0hlbHZldGljYSAvSXRhbGljQW5nbGUgMAovU3RlbVYgOTggL01heFdpZHRoIDE1MDAg + L1N0ZW1IIDg1IC9YSGVpZ2h0IDUzMSAvRm9udEZpbGUyIDU1IDAgUiA+PgplbmRvYmoK + NTggMCBvYmoKWyA2NjcgNjExIDAgMCAwIDAgMCAwIDgzMyAwIDAgMCAwIDAgMCAwIDcy + MiA2NjcgMCAwIDAgMCAwIDAgMCAwIDAgMCA1NTYgMAo1MDAgNTU2IDU1NiAwIDU1NiA1 + NTYgMjIyIDAgMCAyMjIgODMzIDU1NiA1NTYgNTU2IDAgMzMzIDUwMCAyNzggNTU2IDAg + MCA1MDAKXQplbmRvYmoKMjAgMCBvYmoKPDwgL1R5cGUgL0ZvbnQgL1N1YnR5cGUgL1Ry + dWVUeXBlIC9CYXNlRm9udCAvWFlVVFBTK0hlbHZldGljYSAvRm9udERlc2NyaXB0b3IK + NTcgMCBSIC9XaWR0aHMgNTggMCBSIC9GaXJzdENoYXIgNjkgL0xhc3RDaGFyIDEyMCAv + RW5jb2RpbmcgL01hY1JvbWFuRW5jb2RpbmcKPj4KZW5kb2JqCjEgMCBvYmoKPDwgL1Rp + dGxlIChVbnRpdGxlZCkgL0F1dGhvciAoVGhvbWFzIFJpc2JlcmcpIC9DcmVhdG9yIChP + bW5pR3JhZmZsZSkgL1Byb2R1Y2VyCihNYWMgT1MgWCAxMC41LjggUXVhcnR6IFBERkNv + bnRleHQpIC9DcmVhdGlvbkRhdGUgKEQ6MjAwOTA5MTExNDQwMzFaMDAnMDAnKQovTW9k + RGF0ZSAoRDoyMDA5MDkxMTE0NDAzMVowMCcwMCcpID4+CmVuZG9iagp4cmVmCjAgNTkK + MDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDQ5MjUyIDAwMDAwIG4gCjAwMDAwMDEwNDEg + MDAwMDAgbiAKMDAwMDAzNjY3MiAwMDAwMCBuIAowMDAwMDAwMDIyIDAwMDAwIG4gCjAw + MDAwMDEwMjIgMDAwMDAgbiAKMDAwMDAwMTE0NSAwMDAwMCBuIAowMDAwMDIxNzk2IDAw + MDAwIG4gCjAwMDAwMDUyMDQgMDAwMDAgbiAKMDAwMDAwNjEzMiAwMDAwMCBuIAowMDAw + MDAxMzY3IDAwMDAwIG4gCjAwMDAwMDIyODQgMDAwMDAgbiAKMDAwMDAwMjMwNCAwMDAw + MCBuIAowMDAwMDAzMTYxIDAwMDAwIG4gCjAwMDAwMDMxODEgMDAwMDAgbiAKMDAwMDAw + NDIyMCAwMDAwMCBuIAowMDAwMDA0MjQwIDAwMDAwIG4gCjAwMDAwMDUxODQgMDAwMDAg + biAKMDAwMDAzMTA0NSAwMDAwMCBuIAowMDAwMDQxNjkwIDAwMDAwIG4gCjAwMDAwNDkw + NzcgMDAwMDAgbiAKMDAwMDAzMDE4MCAwMDAwMCBuIAowMDAwMDA2MTUxIDAwMDAwIG4g + CjAwMDAwMDkwNDQgMDAwMDAgbiAKMDAwMDAyNzM4NSAwMDAwMCBuIAowMDAwMDE1MjI0 + IDAwMDAwIG4gCjAwMDAwMTc5NTYgMDAwMDAgbiAKMDAwMDAyNDU5MCAwMDAwMCBuIAow + MDAwMDA5MDY1IDAwMDAwIG4gCjAwMDAwMTIyNDYgMDAwMDAgbiAKMDAwMDAzNjYzNSAw + MDAwMCBuIAowMDAwMDEyMjY3IDAwMDAwIG4gCjAwMDAwMTUyMDMgMDAwMDAgbiAKMDAw + MDAzMzg0MCAwMDAwMCBuIAowMDAwMDE3OTc3IDAwMDAwIG4gCjAwMDAwMjA4NjAgMDAw + MDAgbiAKMDAwMDAyMDg4MSAwMDAwMCBuIAowMDAwMDIxNzc2IDAwMDAwIG4gCjAwMDAw + MjE4MzIgMDAwMDAgbiAKMDAwMDAyNDU2OSAwMDAwMCBuIAowMDAwMDI0NjI3IDAwMDAw + IG4gCjAwMDAwMjczNjQgMDAwMDAgbiAKMDAwMDAyNzQyMiAwMDAwMCBuIAowMDAwMDMw + MTU5IDAwMDAwIG4gCjAwMDAwMzAyMTcgMDAwMDAgbiAKMDAwMDAzMTAyNSAwMDAwMCBu + IAowMDAwMDMxMDgyIDAwMDAwIG4gCjAwMDAwMzM4MTkgMDAwMDAgbiAKMDAwMDAzMzg3 + NyAwMDAwMCBuIAowMDAwMDM2NjE0IDAwMDAwIG4gCjAwMDAwMzY3NTUgMDAwMDAgbiAK + MDAwMDAzNjgxOSAwMDAwMCBuIAowMDAwMDQxMjgwIDAwMDAwIG4gCjAwMDAwNDEzMDEg + MDAwMDAgbiAKMDAwMDA0MTUzNiAwMDAwMCBuIAowMDAwMDQxODczIDAwMDAwIG4gCjAw + MDAwNDg2NTIgMDAwMDAgbiAKMDAwMDA0ODY3MyAwMDAwMCBuIAowMDAwMDQ4OTA5IDAw + MDAwIG4gCnRyYWlsZXIKPDwgL1NpemUgNTkgL1Jvb3QgNTAgMCBSIC9JbmZvIDEgMCBS + IC9JRCBbIDxmOTA3NjFiZGExNmY3ZTJlMDkyMjA2Mjg3ZmQ4ZjAzYT4KPGY5MDc2MWJk + YTE2ZjdlMmUwOTIyMDYyODdmZDhmMDNhPiBdID4+CnN0YXJ0eHJlZgo0OTQ2MAolJUVP + RgoxIDAgb2JqCjw8L0F1dGhvciAoVGhvbWFzIFJpc2JlcmcpL0NyZWF0aW9uRGF0ZSAo + RDoyMDA5MDkxMTE0MTUwMFopL0NyZWF0b3IgKE9tbmlHcmFmZmxlIDUuMS4xKS9Nb2RE + YXRlIChEOjIwMDkwOTExMTQzODAwWikvUHJvZHVjZXIgKE1hYyBPUyBYIDEwLjUuOCBR + dWFydHogUERGQ29udGV4dCkvVGl0bGUgKFVudGl0bGVkKT4+CmVuZG9iagp4cmVmCjEg + MQowMDAwMDUwNzk4IDAwMDAwIG4gCnRyYWlsZXIKPDwvSUQgWzxmOTA3NjFiZGExNmY3 + ZTJlMDkyMjA2Mjg3ZmQ4ZjAzYT4gPGY5MDc2MWJkYTE2ZjdlMmUwOTIyMDYyODdmZDhm + MDNhPl0gL0luZm8gMSAwIFIgL1ByZXYgNDk0NjAgL1Jvb3QgNTAgMCBSIC9TaXplIDU5 + Pj4Kc3RhcnR4cmVmCjUwOTkzCiUlRU9GCg== + + QuickLookThumbnail + + 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 + AdQB+wIkAk8CewKpAtkDCgM9A3IDqQPiBBwEWQSXBNcFGQVdBaIF6gYzBn4GywcaB2sH + vggTCGoIwwkdCXoJ2Qo5CpwLAQtnC9AMOgynDRYNhg35Dm4O5Q9eD9kQVhDVEVYR2RJe + EuYTbxP7FIkVGRWqFj8W1RdtGAgYpBlDGeQahxssG9Qcfh0pHdcehx86H+4gpSFeIhki + 1yOWJFglHCXjJqsndihDKRIp5Cq3K44sZi1ALh0u/C/eMMExpzKQM3o0ZzVWNkg3PDgy + OSo6JTsiPCE9Iz4nPy5ANkFBQk9DX0RxRYVGnEe1SNFJ70sPTDJNV05/T6lQ1VIEUzVU + aFWeVtdYEVlOWo5b0F0UXltfpGDwYj5jj2TiZjdnj2jpakZrpW0Hbmtv0nE7cqd0FXWF + dvh4bnnme2B83X5df9+BY4LqhHOF/4eOiR+KsoxIjeGPfJEZkrmUXJYBl6mZU5sAnK+e + YaAVocyjhqVCpwGowqqFrEyuFa/gsa6zf7VStyi5ALrbvLi+mMB7wmDESMYyyCDKD8wB + zfbP7tHo0+XV5Nfm2erb8d374AjiF+Qo5j3oVOpt7InuqPDK8u71Ffc++Wr7mf3K//9j + dXJ2AAAAAAAAAQAAAAAAAAEAAwAHAAsAEQAYACAAKQA0AEEATgBdAG4AgACUAKkAwADY + APIBDQEqAUkBaQGLAa8B1AH7AiQCTwJ7AqkC2QMKAz0DcgOpA+IEHARZBJcE1wUZBV0F + ogXqBjMGfgbLBxoHawe+CBMIagjDCR0JegnZCjkKnAsBC2cL0Aw6DKcNFg2GDfkObg7l + D14P2RBWENURVhHZEl4S5hNvE/sUiRUZFaoWPxbVF20YCBikGUMZ5BqHGywb1Bx+HSkd + 1x6HHzof7iClIV4iGSLXI5YkWCUcJeMmqyd2KEMpEinkKrcrjixmLUAuHS78L94wwTGn + MpAzejRnNVY2SDc8ODI5KjolOyI8IT0jPic/LkA2QUFCT0NfRHFFhUacR7VI0UnvSw9M + Mk1XTn9PqVDVUgRTNVRoVZ5W11gRWU5ajlvQXRReW1+kYPBiPmOPZOJmN2ePaOlqRmul + bQdua2/ScTtyp3QVdYV2+HhueeZ7YHzdfl1/34FjguqEc4X/h46JH4qyjEiN4Y98kRmS + uZRclgGXqZlTmwCcr55hoBWhzKOGpUKnAajCqoWsTK4Vr+CxrrN/tVK3KLkAutu8uL6Y + wHvCYMRIxjLIIMoPzAHN9s/u0ejT5dXk1+bZ6tvx3fvgCOIX5CjmPehU6m3sie6o8Mry + 7vUV9z75avuZ/cr//2N1cnYAAAAAAAABAAAAAAAAAQADAAcACwARABgAIAApADQAQQBO + AF0AbgCAAJQAqQDAANgA8gENASoBSQFpAYsBrwHUAfsCJAJPAnsCqQLZAwoDPQNyA6kD + 4gQcBFkElwTXBRkFXQWiBeoGMwZ+BssHGgdrB74IEwhqCMMJHQl6CdkKOQqcCwELZwvQ + DDoMpw0WDYYN+Q5uDuUPXg/ZEFYQ1RFWEdkSXhLmE28T+xSJFRkVqhY/FtUXbRgIGKQZ + QxnkGocbLBvUHH4dKR3XHocfOh/uIKUhXiIZItcjliRYJRwl4yarJ3YoQykSKeQqtyuO + LGYtQC4dLvwv3jDBMacykDN6NGc1VjZINzw4MjkqOiU7IjwhPSM+Jz8uQDZBQUJPQ19E + cUWFRpxHtUjRSe9LD0wyTVdOf0+pUNVSBFM1VGhVnlbXWBFZTlqOW9BdFF5bX6Rg8GI+ + Y49k4mY3Z49o6WpGa6VtB25rb9JxO3KndBV1hXb4eG555ntgfN1+XX/fgWOC6oRzhf+H + jokfirKMSI3hj3yRGZK5lFyWAZepmVObAJyvnmGgFaHMo4alQqcBqMKqhaxMrhWv4LGu + s3+1UrcouQC627y4vpjAe8JgxEjGMsggyg/MAc32z+7R6NPl1eTX5tnq2/Hd++AI4hfk + KOY96FTqbeyJ7qjwyvLu9RX3Pvlq+5n9yv//WFlaIAAAAAAAAPbVAAEAAAAA0ytYWVog + AAAAAAAAAHoAAAB+AAAAaG1sdWMAAAAAAAAAAQAAAAxlblVTAAAAagAAABwAQwBvAHAA + eQByAGkAZwBoAHQAIAAoAGMAKQAgAFgALQBSAGkAdABlACwAIAAyADAAMAAxAC0AMgAw + ADAANwAuACAAQQBsAGwAIABSAGkAZwBoAHQAcwAgAFIAZQBzAGUAcgB2AGUAZAAuAAB2 + Y2d0AAAAAAAAAAAAAwEAAAIAAAFtAtkERgWyBx8Iiwn4C2QM0Q49D6oRFhKDE+8VXBZh + F2cYbBlyGncbfRyCHYgejR+TIJkhniKkI6kkryWWJn0nZShMKTMqGysCK+ks0S24Lp8v + hzBuMVUyPTMmNBA0+jXjNs03tzigOYo6cztdPEc9MD4aPwQ/7UDsQetC6UPoROdF5Ubk + R+JI4UngSt5L3UzcTdpO2U/qUPpSC1McVCxVPVZOV15Yb1mAWpBboVyyXcJe01/gYOxh + +WMFZBJlHmYrZzdoRGlQal1ramx2bYNuj2+lcLtx0XLmc/x1EnYodz54U3lpen97lXyr + fcB+1n/SgM+By4LHg8OEwIW8hriHtIiwia2KqYuljKGNno6Cj2aQSpEvkhOS95PclMCV + pJaJl22YUZk1mhqa/pvcnLqdl551n1OgMKEOoeyiyqOnpIWlY6ZBpx6n/Kjdqb2qnat+ + rF6tP64frv+v4LDAsaGygbNitEK1IrYCtuG3wLifuX66Xbs8vBu8+r3Zvri/l8B2wVbC + NcMHw9nErMV+xlDHI8f1yMfJmcpsyz7MEMzjzbXOh89E0ALQv9F80jnS9tOz1HDVLdXq + 1qfXZdgi2N/ZnNpP2wHbtNxn3Rrdzd6A3zLf5eCY4Uvh/uKw42PkFuS+5WbmDea1513o + BOis6VTp/Oqj60vr8+ya7ULt6gAAAS4CWwOJBLcF5AcSCEAJbQqbC8kM9g4kD1IQgBGt + EogTYxQ+FRkV9BbOF6kYhBlfGjobFRvwHMsdpR6AHxsftiBRIOwhhyIiIr0jWCPzJI4l + KSXEJl8m+ieVKEIo8CmdKksq+CumLFMtAS2uLlsvCS+2MGQxETG/MnIzJTPYNIs1PjXy + NqU3WDgLOL45cTokOtg7izw+PQQ9yT6PP1VAG0DhQadCbEMyQ/hEvkWERklHD0fVSLBJ + ikplSz9MGkz1Tc9Oqk+EUF9ROVIUUu9TyVSkVY1Wd1dgWElZM1ocWwZb71zZXcJerF+V + YH5haGJRYylkAWTZZbBmiGdgaDhpD2nnar9rl2xvbUZuHm72b8JwjnFZciVy8XO9dIl1 + VHYgdux3uHiEeU96G3rne8l8rH2OfnB/U4A1gReB+oLcg76EoYWDhmWHSIgqiN+JlYpK + iwCLtYxrjSCN1o6Lj0GP9pCskWGSF5LMk4uUSpUIlceWhpdEmAOYwpmAmj+a/pu8nHud + Op34nrOfbaAnoOGhnKJWoxCjyqSFpT+l+aazp26oKKjiqYqqM6rbq4OsK6zUrXyuJK7N + r3WwHbDFsW6yFrK+s2u0F7TEtXC2HbbJt3a4IrjPuXu6KLrUu4G8LbzavYu+Pb7vv6DA + UsEDwbXCZsMYw8nEe8Usxd7Gj8dBAAABPAJ4A7QE8AYsB2gIpAngCxwMWA2UDtAQDBFI + EoQTZxRJFSwWDhbxF9MYthmYGnsbXhxAHSMeBR7oH8ogeCEmIdMigSMvI9wkiiU4JeYm + kydBJ+8onClKKfgqqCtYLAgsuS1pLhkuyS95MCkw2jGKMjoy6jOaNEo1DjXRNpU3WDgc + ON85ozpmOyo77TyxPXQ+Nz77P75AmEFxQktDJEP+RNdFsUaKR2RIPUkXSfBKykujTH1N + c05pT2BQVlFNUkNTOlQwVSdWHVcTWApZAFn3Wu1b21zIXbZepF+RYH9hbWJaY0hkNmUj + ZhFm/2fsaNpp0mrLa8RsvG21bq5vpnCfcZhykHOJdIJ1enZzd2x4bHltem57b3xvfXB+ + cX9ygHKBc4J0g3SEdYV2hneHXYhDiSmKD4r1i9uMwY2njo2Pc5BZkT+SJZMLk/GU1ZW6 + lp6Xg5hnmUuaMJsUm/mc3Z3CnqafiqBvoVOiR6M6pC2lIKYUpwen+qjtqeGq1KvHrLut + rq6hr5SwmbGfsqSzqbSutbO2uLe9uMK5x7rNu9K8173cvuG//MEXwjPDTsRpxYTGn8e7 + yNbJ8csMzCfNQ85ez3nQm9G90t/UAdUj1kbXaNiK2azaztvw3RLeNN9W4HjikuSs5sbo + 4Or67RTvL/FJ82P1ffeX+bH7y/3l//8AAHNmMzIAAAAAAAEN+QAAB+QAAAIBAAAMYwAA + 9SH////2AAABX////RUAARx2 + + ReadOnly + NO + RowAlign + 1 + RowSpacing + 36 + SheetTitle + Canvas 1 + SmartAlignmentGuidesActive + YES + SmartDistanceGuidesActive + YES + UniqueID + 1 + UseEntirePage + + VPages + 1 + WindowInfo + + CurrentSheet + 0 + ExpandedCanvases + + + name + Canvas 1 + + + Frame + {{218, 52}, {999, 826}} + ListView + + OutlineWidth + 142 + RightSidebar + + ShowRuler + + Sidebar + + SidebarWidth + 120 + VisibleRegion + {{-54, -59}, {864, 672}} + Zoom + 1 + ZoomValues + + + Canvas 1 + 1 + 1 + + + + saveQuickLookFiles + YES + + diff --git a/truman/src/docs/asciidoc/images/oxm-exceptions.png b/truman/src/docs/asciidoc/images/oxm-exceptions.png new file mode 100644 index 000000000000..8515e7c4887a Binary files /dev/null and b/truman/src/docs/asciidoc/images/oxm-exceptions.png differ diff --git a/truman/src/docs/asciidoc/images/phantom-read-process.png b/truman/src/docs/asciidoc/images/phantom-read-process.png new file mode 100644 index 000000000000..f0db67bf262f Binary files /dev/null and b/truman/src/docs/asciidoc/images/phantom-read-process.png differ diff --git a/truman/src/docs/asciidoc/images/problem-reads.png b/truman/src/docs/asciidoc/images/problem-reads.png new file mode 100644 index 000000000000..2fd072e34ed9 Binary files /dev/null and b/truman/src/docs/asciidoc/images/problem-reads.png differ diff --git a/truman/src/docs/asciidoc/images/prototype.png b/truman/src/docs/asciidoc/images/prototype.png new file mode 100644 index 000000000000..26fa2c1cf2d9 Binary files /dev/null and b/truman/src/docs/asciidoc/images/prototype.png differ diff --git a/truman/src/docs/asciidoc/images/relationships-between-classes.png b/truman/src/docs/asciidoc/images/relationships-between-classes.png new file mode 100644 index 000000000000..e478526a1bfb Binary files /dev/null and b/truman/src/docs/asciidoc/images/relationships-between-classes.png differ diff --git a/truman/src/docs/asciidoc/images/second-lost-update-process.png b/truman/src/docs/asciidoc/images/second-lost-update-process.png new file mode 100644 index 000000000000..f6c39c0efd44 Binary files /dev/null and b/truman/src/docs/asciidoc/images/second-lost-update-process.png differ diff --git a/truman/src/docs/asciidoc/images/setting-gradle-test-runner.png b/truman/src/docs/asciidoc/images/setting-gradle-test-runner.png new file mode 100644 index 000000000000..15df36f9d8d9 Binary files /dev/null and b/truman/src/docs/asciidoc/images/setting-gradle-test-runner.png differ diff --git a/truman/src/docs/asciidoc/images/singleton.png b/truman/src/docs/asciidoc/images/singleton.png new file mode 100644 index 000000000000..591520ec1dcc Binary files /dev/null and b/truman/src/docs/asciidoc/images/singleton.png differ diff --git a/truman/src/docs/asciidoc/images/spring-and-mybatis.jpg b/truman/src/docs/asciidoc/images/spring-and-mybatis.jpg new file mode 100644 index 000000000000..c1442b430011 Binary files /dev/null and b/truman/src/docs/asciidoc/images/spring-and-mybatis.jpg differ diff --git a/truman/src/docs/asciidoc/images/spring-aop-process.png b/truman/src/docs/asciidoc/images/spring-aop-process.png new file mode 100644 index 000000000000..61baaebd92cb Binary files /dev/null and b/truman/src/docs/asciidoc/images/spring-aop-process.png differ diff --git a/truman/src/docs/asciidoc/images/spring-bean-lifecycle.svg b/truman/src/docs/asciidoc/images/spring-bean-lifecycle.svg new file mode 100644 index 000000000000..4003367e7af5 --- /dev/null +++ b/truman/src/docs/asciidoc/images/spring-bean-lifecycle.svg @@ -0,0 +1 @@ +ImportAwareBeanPostProcessor#postProcessBeforeInstantiationAutowiredAnnotationBeanPostProcessor#setBeanFactoryAnnotationAwareAspectJAutoProxyCreator#postProcessBeforeInstantiationCommonAnnotationBeanPostProcessor#postProcessBeforeInstantiationAutowiredAnnotationBeanPostProcessor#postProcessBeforeInstantiation构造函数CommonAnnotationBeanPostProcessor#postProcessMergedBeanDefinitionApplicationListenerDetector#postProcessMergedBeanDefinitionAutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinitionImportAwareBeanPostProcessor#postProcessAfterInstantiationAnnotationAwareAspectJAutoProxyCreator#postProcessAfterInstantiationCommonAnnotationBeanPostProcessor#postProcessAfterInstantiationAutowiredAnnotationBeanPostProcessor#postProcessAfterInstantiationImportAwareBeanPostProcessor#postProcessPropertiesAnnotationAwareAspectJAutoProxyCreator#postProcessPropertiesCommonAnnotationBeanPostProcessor#postProcessPropertiesAutowiredAnnotationBeanPostProcessor#postProcessPropertiesImportAwareBeanPostProcessor#postProcessPropertyValuesAnnotationAwareAspectJAutoProxyCreator#postProcessPropertyValuesCommonAnnotationBeanPostProcessor#postProcessPropertyValuesAutowiredAnnotationBeanPostProcessor#postProcessPropertiesApplicationContextAwareProcessor#postProcessBeforeInitializationImportAwareBeanPostProcessor#postProcessBeforeInitializationBeanPostProcessorChecker#postProcessBeforeInitializationAnnotationAwareAspectJAutoProxyCreator#postProcessBeforeInitializationCommonAnnotationBeanPostProcessor#postProcessBeforeInitializationAutowiredAnnotationBeanPostProcessor#postProcessBeforeInitializationApplicationListenerDetector#postProcessBeforeInitializationInitializingBean#afterPropertiesSetinit-methodApplicationContextAwareProcessor#postProcessAfterInitializationImportAwareBeanPostProcessor#postProcessAfterInitializationBeanPostProcessorChecker#postProcessAfterInitializationAnnotationAwareAspectJAutoProxyCreator#postProcessAfterInitializationCommonAnnotationBeanPostProcessor#postProcessAfterInitializationAutowiredAnnotationBeanPostProcessor#postProcessAfterInitializationApplicationListenerDetector#postProcessAfterInitializationDisposableBean#destroyDestructionAwareBeanPostProcessor#postProcessBeforeDestructionbeanFactory#destroyBean(bean)destroy-methodBeanInstantiationAwareBeanPostProcessor#postProcessBeforeInstantiationMergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinitionInstantiationAwareBeanPostProcessor#postProcessAfterInstantiationInstantiationAwareBeanPostProcessor#postProcessPropertiesBeanPostProcessor#postProcessBeforeInitializationBeanPostProcessor#postProcessAfterInitializationInstantiationAwareBeanPostProcessorInstantiationAwareBeanPostProcessorInstantiationAwareBeanPostProcessorBeanPostProcessorBeanPostProcessorMergedBeanDefinitionPostProcessor图例:1. 斜体表示空方法,无任何操作。2. 加粗斜体表示继承父类实现,但父类是空方法,无任何操作。3. 深灰色背景,则表示跳过,不执行。 "地瓜哥"博客网 · https://www.diguage.com · 出品postProcessBeforeInstantiation 是断路功能,如果创建实例,则直接返回,不再进行后续的调用。 如果创建了示例,则直接调用 BeanPostProcessor#postProcessAfterInitialization 方法,接着直接返回,不再执行后续操作。判断是否属于基础切面类,如果有指定的 Target 则生成代理。收集 @Resource 依赖信息,@PostConstruct 和 @PreDestroy 等信息。收集 @Autowired 的依赖信息。判断 Bean 是否是一个 `ApplicationListener`,是则保留如果 Bean 是 EnhancedConfiguration的实现类,则注入 `BeanFactory`。返回 null,则执行下面的方法。完成 @Resource 依赖注入。完成 @Autowired 和 @Value 注入注入 BeanNameAware、BeanClassLoaderAware 和 BeanFactoryAware注入EnvironmentAware、EmbeddedValueResolverAware、ResourceLoaderAwareApplicationEventPublisherAware、MessageSourceAware 和 ApplicationContextAware如果实现了 ImportAware 接口,则注入 importMetadata 信息。要调用 LifecycleMetadata 的 initMethod 方法,但是,里面去没有任何实现。如果 Bean 是 BeanPostProcessor 子类,则检查 BeanPostProcessor 数量。检查 Bean 和提前暴露的引用是否相同,不同则重新生成代理对象。将 ApplicationListener类型的 Bean,加入到容器的 applicationListeners中Spring Bean 生命周期流程图ApplicationListenerDetector就是一个 DestructionAwareBeanPostProcessor。但是,Bean 销毁时,不知道为什么没有被调用。"地瓜哥"博客网 · https://www.diguage.com · 出品 \ No newline at end of file diff --git a/truman/src/docs/asciidoc/images/spring-logo.jpg b/truman/src/docs/asciidoc/images/spring-logo.jpg new file mode 100644 index 000000000000..bf25598fcbb8 Binary files /dev/null and b/truman/src/docs/asciidoc/images/spring-logo.jpg differ diff --git a/truman/src/docs/asciidoc/images/spring-mvc-and-webflux-venn.png b/truman/src/docs/asciidoc/images/spring-mvc-and-webflux-venn.png new file mode 100644 index 000000000000..6e0eeab744d4 Binary files /dev/null and b/truman/src/docs/asciidoc/images/spring-mvc-and-webflux-venn.png differ diff --git a/truman/src/docs/asciidoc/images/spring-overview.png b/truman/src/docs/asciidoc/images/spring-overview.png new file mode 100644 index 000000000000..952c9db6c86f Binary files /dev/null and b/truman/src/docs/asciidoc/images/spring-overview.png differ diff --git a/truman/src/docs/asciidoc/images/stack-frame.png b/truman/src/docs/asciidoc/images/stack-frame.png new file mode 100644 index 000000000000..18b317205151 Binary files /dev/null and b/truman/src/docs/asciidoc/images/stack-frame.png differ diff --git a/truman/src/docs/asciidoc/images/transactional-summary.png b/truman/src/docs/asciidoc/images/transactional-summary.png new file mode 100644 index 000000000000..2083435b4eb8 Binary files /dev/null and b/truman/src/docs/asciidoc/images/transactional-summary.png differ diff --git a/truman/src/docs/asciidoc/images/tx.png b/truman/src/docs/asciidoc/images/tx.png new file mode 100644 index 000000000000..06f2e77c76f8 Binary files /dev/null and b/truman/src/docs/asciidoc/images/tx.png differ diff --git a/truman/src/docs/asciidoc/images/tx_prop_required.png b/truman/src/docs/asciidoc/images/tx_prop_required.png new file mode 100644 index 000000000000..218790aca635 Binary files /dev/null and b/truman/src/docs/asciidoc/images/tx_prop_required.png differ diff --git a/truman/src/docs/asciidoc/images/tx_prop_requires_new.png b/truman/src/docs/asciidoc/images/tx_prop_requires_new.png new file mode 100644 index 000000000000..a8ece48193f3 Binary files /dev/null and b/truman/src/docs/asciidoc/images/tx_prop_requires_new.png differ diff --git a/truman/src/docs/asciidoc/images/wx-jikerizhi.png b/truman/src/docs/asciidoc/images/wx-jikerizhi.png new file mode 100644 index 000000000000..9653b7e4252e Binary files /dev/null and b/truman/src/docs/asciidoc/images/wx-jikerizhi.png differ diff --git a/truman/src/docs/asciidoc/images/wxpay.jpg b/truman/src/docs/asciidoc/images/wxpay.jpg new file mode 100644 index 000000000000..1697d263d10a Binary files /dev/null and b/truman/src/docs/asciidoc/images/wxpay.jpg differ diff --git a/truman/src/docs/asciidoc/images/wxpay.png b/truman/src/docs/asciidoc/images/wxpay.png new file mode 100644 index 000000000000..8b848b5a5449 Binary files /dev/null and b/truman/src/docs/asciidoc/images/wxpay.png differ diff --git a/truman/src/docs/asciidoc/import-into-eclipse.adoc b/truman/src/docs/asciidoc/import-into-eclipse.adoc new file mode 100644 index 000000000000..598df5e44962 --- /dev/null +++ b/truman/src/docs/asciidoc/import-into-eclipse.adoc @@ -0,0 +1,4 @@ +[#import-into-eclipse] += 将 Spring 导入到 Eclipse 中 + +在 Mac OSX 、 Ubuntu 上执行 `SPRING_HOME/import-into-eclipse.sh`;在 Windows 上 `{var_spring_home}/import-into-eclipse.bat`,并参考相关的输出内容来导入到 Eclipse 中。 diff --git a/truman/src/docs/asciidoc/import-into-idea.adoc b/truman/src/docs/asciidoc/import-into-idea.adoc new file mode 100644 index 000000000000..22faf4659f0a --- /dev/null +++ b/truman/src/docs/asciidoc/import-into-idea.adoc @@ -0,0 +1,64 @@ +[#import-into-idea] += 将 Spring 导入到 IntelliJ IDEA + +古语有云:“工欲善其事,必先利其器!”尤其是 Java 平台,对调试的支持堪称完美,相关的工具又 +是很好很强大。做 Spring 源码分析,也必须有得心应手的工具相辅相成才能事半功倍。 + +在 Java 开发领域中,众所周知的集成开发工具有三种:Eclipse、IDEA、Netbeans。D瓜哥在工作中,自己用过 +Eclipse、IDEA。平时见到的开发工具也是以这两款为主。所以,重点介绍一下 Spring 如何导入 +到这两款工具,以及如何在其中运行相关代码。本节介绍如何导入到 IDEA。 + +== 正常导入 + +在 Spring 的版本库中,也有相关文档来介绍如何导入到 IDEA。我们先按照这个文档来进行操作一 +遍: + +. 在终端(Windows 系统上,推荐使用 Cmder)中,进入到 Spring 源码根目录 `SPRING_HOME`。 +. 预编译 `spring-oxm` 模块: `./gradlew cleanIdea :spring-oxm:compileTestJava`;在 +Windows 系统上,请执行 `gradlew cleanIdea :spring-oxm:compileTestJava`。 +. 导入到 IDEA 中:File → New → Project from Existing Sources... → 选中 Spring 源码 +目录 → OK → import from external model → Gradle → Next → Finish,然后经过漫长的等待 +后就成功导入到 IDEA 中了。 +. 设计 Project 的 JDK 版本。这里分析 Spring {spring-version},要求 JDK 的版本最低为 JDK 8。 +. 官方文档上显示,需要排除 `spring-aspects`,但是根据D瓜哥自己的运行测试情况来看,没有 +发现什么问题,这里就不再排除。 + +按照文档来操作,可以顺利导入,但是在导入过程以及运行单元测试代码时会遇到一些问题。接下来, +D瓜哥介绍一下如何解决这些问题。 + +== 加快 Gradle 下载速度 + +遇到的第一个问题:在执行 *预编译 `spring-oxm` 模块* 时,耗时特别长。只是一味地打印出一些点点,不确定是否出现什么问题。 + +这是由于在首次执行命令时,相当于执行 Gradle 任务,这需要下载构建工具 Gradle。由于国内特殊的网络问题,有些地方下载得很慢,有些地方甚至根本下载不了。 + +其实,解决这个问题也很简单,基本思路是这样的:在 `SPRING_HOME/gradle/wrapper/gradle-wrapper.properties` 文件中的 `distributionUrl` 属性指明了所需的 Gradle 下载路径。初次执行 Gradle 命令时,需要下载对应版本的 Gradle。下载中 Gradle 会存放在 `GRADLE_USER_HOME/wrapper/dists/` 的子目录下,Gradle 会在该目录下创建一个对应的目录(该目录不固定,中间有一段是一个自动生成的字符串。)。直接复制下来 `distributionUrl` 对应的下载链接,通过迅雷下载完成后,将 Gradle 压缩包复制到上述目录下,借此来“欺骗” Gradle,让其以为是自己下载完成的。终止运行的任务,然后再次执行 `./gradlew cleanIdea :spring-oxm:compileTestJava`,你会发现,很快就能完成。 + +== 自动手动建立 Gradle 执行任务。 + +上面的问题还好,即使不 Hack 一下,慢慢等待也能顺利完成。但是,把 Spring 导入到 IDEA 后, +分析 Spring 源代码,运行单元测试,进行单步调试,这是基本要求了。但是,在 IDEA 15.02以后 +的版本中运行时,你就会发现,依赖的各种库都需要下载,典型的如 JUnit、Apache Commons Loggings。 +实际这些库在导入时,都已经下载好了。你可以在 `USER_HOME/.gradle/caches/modules-2/files-2.1` +(可能后面的数字会有变化)目录中找到相应的库。但是,为什么 IDEA 却找不到呢? + +其实,这是由于单元测试代码的运行方式导致的。在 IDEA 15.02 以后,运行单元测试时,默认使用 +的是 *Platform Test Runner*。可以通过手动建立 Gradle 方式的测试运行配置来解决这个问题。 +如下图: + +image::images/manual-new-test.png[title="手动建测试运行配置", {image_attr}] + +上图中建立的整个测试类的运行配置。如果只想运行单个测试方法,只需要修改一下 +*Script parameters*。将其修改为: `--tests "org.springframework.beans.factory.DefaultListableBeanFactoryTests.testUnreferencedSingletonWasInstantiated"` +即可。 + +新建完成后,就可以点击运行或者调试按钮来进行测试了。 + +== 使用 Gradle 方式来运行单元测试 + +上面的方式治标不治本,而且相当麻烦。更简单也更根本的解决办法是将默认 *Test Runner* 修改 +为 *Gradle Test Runner*。截图如下: + +image::images/setting-gradle-test-runner.png[title="设置 Test Runner", {image_attr}] + +经过上面的设置后,所有的单元测试都可以顺利执行了。 diff --git a/truman/src/docs/asciidoc/index.adoc b/truman/src/docs/asciidoc/index.adoc new file mode 100644 index 000000000000..d24dbfa1cc5f --- /dev/null +++ b/truman/src/docs/asciidoc/index.adoc @@ -0,0 +1,34 @@ += Spring 源码分析^Alpha^ +include::{includedir}/_attributes.adoc[] + +:!sectnums: + +include::{includedir}/preface.adoc[leveloffset=+1] + +include::{includedir}/questions.adoc[leveloffset=+1] + +:sectnums: + +include::{includedir}/development-environment.adoc[leveloffset=+1] + +include::{includedir}/architecture.adoc[leveloffset=+1] + +include::{includedir}/ioc.adoc[leveloffset=+1] + +include::{includedir}/aop.adoc[leveloffset=+1] + +include::{includedir}/data-access.adoc[leveloffset=+1] ++ +include::{includedir}/mvc.adoc[leveloffset=+1] + +include::{includedir}/extensions-and-dubbo.adoc[leveloffset=+1] + +include::{includedir}/tips.adoc[leveloffset=+1] + +include::{includedir}/uml.adoc[leveloffset=+1] + +include::{includedir}/tools.adoc[leveloffset=+1] + +include::{includedir}/xmls.adoc[leveloffset=+1] + +include::{includedir}/references.adoc[leveloffset=+1] \ No newline at end of file diff --git a/truman/src/docs/asciidoc/inject-static-field.adoc b/truman/src/docs/asciidoc/inject-static-field.adoc new file mode 100644 index 000000000000..3a6ec6870ad0 --- /dev/null +++ b/truman/src/docs/asciidoc/inject-static-field.adoc @@ -0,0 +1,26 @@ +[#inject-static-field] += 注入静态属性 + +[source,xml,{source_attr}] +---- + + + + + + + + +---- + +[source,java,{source_attr}] +---- +public class SessionUtil { + + private static LocationService locationService; + + public static void setLocationService(LocationService ls) { + locationService = ls; + } +} +---- diff --git a/truman/src/docs/asciidoc/install-git.adoc b/truman/src/docs/asciidoc/install-git.adoc new file mode 100644 index 000000000000..ad4430969d6b --- /dev/null +++ b/truman/src/docs/asciidoc/install-git.adoc @@ -0,0 +1,60 @@ +[#install-git] += 安装 Git + +由于 Spring 的源代码是使用 Git 来做版本管理的,而且是托管在 https://github.com/[Github^] 上。所以,需要先安装相应的版本管理工具 Git。本节D瓜哥就来介绍一下 Git 的安装。 + +[#install-git-on-mac] +== 在 Mac OSX 上安装 Git + +在 Mac OSX 系统上安装 Git 非常简单。只需一条命令即可: + +[source,bash,{source_attr}] +---- +brew insatll git +---- + +NOTE: 没有使用过 Homebrew,请参考 https://brew.sh/index_zh-cn.html[Homebrew — OS X 不可或缺的套件管理器^]。 + +[#install-git-on-win] +== 在 Windows 上安装 Git + +在 Windows 上安装 Git,也比较简单了,直接去 https://gitforwindows.org/[Git for Windows^] 下载最新版,然后下一步下一步就OK了。 + +NOTE: 由于国内的特殊网络状况,有可能下载可能会很慢甚至失败。实在不行,请“科学上网”。 + +[#install-git-on-linux] +== 在 Ubuntu 上安装 Git + +在 Ubuntu 上安装 Git,相对来说,稍微麻烦一点点,需要多执行几个命令。命令如下: + +[source,bash,{source_attr}] +---- +sudo apt-add-repository ppa:git-core/ppa # <1> +sudo apt-get update # <2> +sudo apt-get install git # <3> +---- +<1> 这是添加 Git 的软件源; +<2> 更新软件源,这样可以应用上第一步安装的软件源,并且可以安装到最新版; +<3> 安装 Git。 + +[NOTE] +==== +如果在执行第一步时,提示找不到命令时,请执行 `sudo apt-get install -y python-software-properties`。 + +`add-apt-repository` 命令可以向本地软件源中添加PPA软件库提供的软件地址,然后就可以使用 `apt-get` 更新安装、更新软件。而 `add-apt-repository` 是由 `python-software-properties` 这个工具包提供的。所以要先安装 `python-software-properties` 就能使用 `add-apt-repository`。 +==== + +[#config-git] +== 配置 Git + +经过上一节的内容后,Git 已经安装好了。但是,在使用之前,需要做一些简单的配置。Mac OSX、Ubuntu 上直接打开终端,在 Windows 上打开刚刚安装的 *Git Bash*,然后执行如下命令: + +[source,bash,{source_attr}] +---- +git config --global user.name # <1> +git config --global user.email # <2> +---- +<1> 配置用户名; +<2> 配置电子邮箱。 + +然后,就可以正常使用了。由于 Git 不是本书的重点。这里就不做过多介绍了。等后续用到再视情况来介绍。 diff --git a/truman/src/docs/asciidoc/ioc.adoc b/truman/src/docs/asciidoc/ioc.adoc new file mode 100644 index 000000000000..5592222cdbd8 --- /dev/null +++ b/truman/src/docs/asciidoc/ioc.adoc @@ -0,0 +1,163 @@ +[#ioc] += IoC 的实现原理 + +TODO: 感觉可以通过向 `ClassPathScanningCandidateComponentProvider` 中添加过滤注解来实现扩展功能。抽空尝试一下。 + +TODO: 除了 singleton 和 prototype 外,其他 Scope 类型的 Bean 实例是怎么缓存的? + +TODO: `default-autowire="byName"` 等实现自动装配的功能在哪里实现的?怎么实现的?是否会遍历 Bean 的所有属性? `org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean` 中有相关类似实现。 + +autowiring 的实现过程: + +. 对 Bean 的属性代调用 getBean()方法,完成依赖 Bean 的初始化和依赖注入。 +. 将依赖 Bean 的属性引用设置到被依赖的 Bean 属性上。 +. 将依赖 Bean 的名称和被依赖 Bean 的名称存储在 IOC 容器的集合中。 + +对属性的注入过程分以下两种情况: + +. 属性值类型不需要强制转换时,不需要解析属性值,直接准备进行依赖注入。 +. 属性值需要进行类型强制转换时,如对其他对象的引用等,首先需要解析属性值,然后对解析后的属性值进行依赖注入。 + + +Spring IoC 容器是如何将属性的值注入到 Bean 实例对象中去的: + +. 对于集合类型的属性,将其属性值解析为目标类型的集合后直接赋值给属性。 +. 对于非集合类型的属性,大量使用了 JDK 的反射机制,通过属性的 Getter 方法获取指定属性注入以前的值,同时调用属性的 Setter 方法为属性设置注入后的值。 + +从 `ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForBeanMethod` 可以看出,对 `@Bean` 的处理复用了自定义 `factory-method` 的处理。在 `spring-beans.xsd` 文件中,可以找到对 `factory-method` 属性的说明。 + +include::{includedir}/bean-definition.adoc[leveloffset=+1] + +include::{includedir}/bean-factory.adoc[leveloffset=+1] + +include::{includedir}/factory-bean.adoc[leveloffset=+1] + +include::{includedir}/environment.adoc[leveloffset=+1] + +include::{includedir}/application-context.adoc[leveloffset=+1] + +include::{includedir}/startup-process-overview.adoc[leveloffset=+1] + +include::{includedir}/bean-lifecycle-overview.adoc[leveloffset=+1] + +include::{includedir}/extensions-overview.adoc[leveloffset=+1] + +include::{includedir}/circular-dependence.adoc[leveloffset=+1] + +== I18n + +properties 文件内容是以 ISO-8859-1 编码的。所以,不支持中文,需要把中文进行转码。 + +plantuml::{includedir}/puml/MessageSource.puml[{diagram_attr}] + +== 事件发布 + +plantuml::{includedir}/puml/ApplicationEvent.puml[{diagram_attr}] + +容器启动伊始,就会检查容器内是否存在名称为 `applicationEventMulticaster` 的 `ApplicationEventMulticaster` 对象实例。有的话就使用提供的实现,没有则默认初始化一个 `SimpleApplicationEventMulticaster` 作为将会使用的 `ApplicationEventMulticaster`。 + +在 `refresh()` 时,先调用 `initMessageSource()` 初始化 `MessageSource` 实例;然后调用 `initApplicationEventMulticaster()` 初始化 `ApplicationEventMulticaster`。 + +Spring 是以 Bean 为核心的。Bean 的配置、配置的读取和解析、以合适的数据结构对 Bean 元数据进行各种操作等。 + +Spring 是如何解决构造函数依赖的呢?以及如何注入呢? + +== 内置 `PostProcessor` 的注册 + +在 `AnnotationConfigUtils.registerAnnotationConfigProcessors(BeanDefinitionRegistry)` 方法中,注册了 Spring 内置的一些核心 `PostProcessor`: + +. `ConfigurationClassPostProcessor` +. `AutowiredAnnotationBeanPostProcessor` +. `CommonAnnotationBeanPostProcessor` +. `PersistenceAnnotationBeanPostProcessor`? -- 这个得看是否需要 +. `EventListenerMethodProcessor` +. `DefaultEventListenerFactory` + +通过对 `AnnotationConfigUtils.registerAnnotationConfigProcessors` 调用追踪来看,在如下地方进行了调用: + +. 通过 `AnnotationConfigBeanDefinitionParser.parse` 在处理 `` 时; +. 通过 `ComponentScanBeanDefinitionParser.parse` 在处理 `` 时; +. 通过 `AnnotatedBeanDefinitionReader` 构造函数在初始化时; +. 通过 `ClassPathBeanDefinitionScanner.scan` 在扫描类路径时; + +覆盖了 XML 配置文件和注解配置两种最核心的场景。 + + +plantuml::{includedir}/puml/BeanDefinition.puml[{diagram_attr}] + +[#uml-BeanFactory] +plantuml::{includedir}/puml/BeanFactory.puml[{diagram_attr}] + +plantuml::{includedir}/puml/ConfigurationClassPostProcessor.puml[{diagram_attr}] + +include::{includedir}/common-interfaces-introduction.adoc[leveloffset=+1] + +include::{includedir}/resource.adoc[leveloffset=+1] + +include::{includedir}/tag-resolve.adoc[leveloffset=+1] + +include::{includedir}/annotations-resolve.adoc[leveloffset=+1] + +可以使用 p-namespace 来简化属性的赋值操作: + +[{xml_src_attr}] +---- + + + + + + + + + +---- + +可以使用 c-namespace 来简化构造函数参数的声明: + +[{xml_src_attr}] +---- + + + + + + + + + + + + + + + + + +---- + +除了常规的 `@Autowired` 和 `@Resource` 注入外,还可以使用 `@Lookup` 注解来注入依赖。示例如下: + +[{java_src_attr}] +---- +include::{truman_src_dir}/context/AnnoLookupTest.java[] +---- + +这里有个疑问:为什么要把 `@Lookup` 注解标注在抽象方法上? + +== `BeanNameGenerator` 自定义 Bean 名称 + +如果想自定义 Bean 名称,可以实现 `BeanNameGenerator` 接口,然后将其配置到 Spring 容器上。更详细文档,请看: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-scanning-name-generator[Core Technologies:Naming Autodetected Components^]。 + + +include::{includedir}/property-placeholder.adoc[leveloffset=+1] \ No newline at end of file diff --git a/truman/src/docs/asciidoc/jdbc.adoc b/truman/src/docs/asciidoc/jdbc.adoc new file mode 100644 index 000000000000..b637a2c90085 --- /dev/null +++ b/truman/src/docs/asciidoc/jdbc.adoc @@ -0,0 +1,41 @@ +[#jdbc] += JdbcTemplate + +`SQLExceptionTranslator` 是一个接口,如果你需要在 `SQLException` 和 `org.springframework.dao.DataAccessException` 之间作转换,那么必须实现该接口。 + +转换器类的实现可以采用一般通用的做法(比如使用 JDBC 的 SQLState code),如果为了使转换更准确,也可以进行定制(比如使用 Oracle 的 error code)。 + +`SQLErrorCodeSQLExceptionTranslator` 是 `SQLExceptionTranslator` 的默认实现。该实现使用指定数据库厂商的 error code,比采用 SQLState 更精确。转换过程基于一个 JavaBean ( 类 型 为 SQLErrorCodes ) 中 的 error code 。 + +这 个 JavaBean 由 `SQLErrorCodesFactory` 工厂类创建,其中的内容来自于 *`sql-error-codes.xml`* 配置文 件 。 + +该文件中的数据库厂商代码基于 Database MetaData 信息中的 DatabaseProductName,从而配合当前数据库的使用。 + +`SQLErrorCodeSQLExceptionTranslator` 使用以下的匹配规则:首先检查是否存在完成定制转换的子类实现 。通常 `SQLErrorCodeSQLExceptionTranslator` 这个类可以作为一个具体类使用,不需要进行定制,那么这个规则将不适用。 +接着将 `SQLException` 的 error code 与错误代码集中的 error code 进行匹配。默认情况下错误代码集将从 `SQLErrorCodesFactory` 取得。错误代码集来自 classpath 下的 `sql-error-codes.xml` 文件,它们将与数据库 metadata 信息中的 database name 进行映射。 + +== 基于 `AbstractRoutingDataSource` 的主从切换 + +可以考虑使用 `AbstractRoutingDataSource` 做一个透明主从切换代理: + +[{java_src_attr}] +---- +include::{truman_src_dir}/jdbc/RoutingDataSourceTest.java[] +---- + +WARNING: 这还是一个半成品!如果用于生成,还需要打磨,比如简化配置和侵入、接入配置中心等。 + +== 异常体系 + +Spring 会根据错误码和 SQL 状态码将 `SQLExeption` 转换为对应的 Spring DAO 异常 。 在 `org.springframework.jdbc.support` 包中定义了 `SQLExceptionTranslator` 接口,该接口的两个实现类 `SQLErrorCodeSQLExceptionTranslator` 和 `SQLStateSQLExceptionTranslator` 分别负责处理 `SQLException` 中错误代码和 SQL 状态码的转换工作 。 + + +`SQLErrorCodeSQLExceptionTranslator.doTranslate` 是真正实现从错误码到异常的转换工作。在 `sql-error-codes.xml` 文件中定义异常类型,实现可扩展性。 + +在两个地方完成异常转换工作: + +. 在执行 SQL 时报错,这个时候就要进行回滚。所以,在回滚时,执行异常转换。 ++ +TODO dgg 如果"关闭事务"(事务是否可以关闭?)或只读事务时,有事务吗?会执行回滚吗? ++ +. 在提交时报错,进行异常转换。 diff --git a/truman/src/docs/asciidoc/jpa.adoc b/truman/src/docs/asciidoc/jpa.adoc new file mode 100644 index 000000000000..d24ff10d0cda --- /dev/null +++ b/truman/src/docs/asciidoc/jpa.adoc @@ -0,0 +1,2 @@ +[#jpa] += 整合 JPA diff --git a/truman/src/docs/asciidoc/lifecycle-callback.adoc b/truman/src/docs/asciidoc/lifecycle-callback.adoc new file mode 100644 index 000000000000..aa1edec62cbe --- /dev/null +++ b/truman/src/docs/asciidoc/lifecycle-callback.adoc @@ -0,0 +1,9 @@ +[#lifecycle-callback] += 生命周期回调 + +. `@PostConstruct` +. `@PreDestroy` +. `InitializingBean` +. `DisposableBean` +. `init-method` +. `destroy-method` diff --git a/truman/src/docs/asciidoc/mvc.adoc b/truman/src/docs/asciidoc/mvc.adoc new file mode 100644 index 000000000000..0ce1b3adfd00 --- /dev/null +++ b/truman/src/docs/asciidoc/mvc.adoc @@ -0,0 +1,11 @@ +[#mvc] += Spring MVC + +通过 `org.springframework.web.servlet.HttpServletBean.init` 方法开始初始化容器的动作,再进一步委托给 `org.springframework.web.servlet.FrameworkServlet.initServletBean` 方法完成初始化容器。 + +这个操作和 `org.springframework.web.context.ContextLoaderListener.contextInitialized` 启动 Spring 容器有什么区别? + +plantuml::{includedir}/puml/DispatcherServlet.puml[{diagram_attr}] + + +plantuml::{includedir}/puml/DispatchServlet-init-sequence.puml[{diagram_attr}] diff --git a/truman/src/docs/asciidoc/orm.adoc b/truman/src/docs/asciidoc/orm.adoc new file mode 100644 index 000000000000..8249f78b97d7 --- /dev/null +++ b/truman/src/docs/asciidoc/orm.adoc @@ -0,0 +1,8 @@ +[#orm] += Spring 整合 ORM 框架 + +include::{includedir}/hibernate.adoc[leveloffset=+1] + +include::{includedir}/jpa.adoc[leveloffset=+1] + +include::{includedir}/extensions-and-mybatis.adoc[leveloffset=+1] diff --git a/truman/src/docs/asciidoc/performance-monitor.adoc b/truman/src/docs/asciidoc/performance-monitor.adoc new file mode 100644 index 000000000000..3f68416b9c9b --- /dev/null +++ b/truman/src/docs/asciidoc/performance-monitor.adoc @@ -0,0 +1,52 @@ +[#performance-monitor] += 性能监视器 + +[source,xml,{source_attr}] +.引入依赖 +---- + + com.jamonapi + jamon + 2.81 + +---- + +[source,xml,{source_attr}] +.增加 Spring 配置 +---- + + + + + + + + +---- + +[source,xml,{source_attr}] +.增加日志配置 +---- + + /tmp/durian_performance.log + + + /tmp/durian_performance.%i.log.zip + 1 + 2 + + + 100MB + + + + %date [%thread] %-5level %logger{72} - %msg%n + + + + + + +---- + +TIP: 注意 `JamonPerformanceMonitorInterceptor` 的日志级别。线上环境的输入级别一般是 `INFO` ,如果不配置,则这里的日志直接被过滤掉了。 diff --git a/truman/src/docs/asciidoc/preface.adoc b/truman/src/docs/asciidoc/preface.adoc new file mode 100644 index 000000000000..0f53af68a306 --- /dev/null +++ b/truman/src/docs/asciidoc/preface.adoc @@ -0,0 +1,28 @@ +[preface] += 前言 + +本文档是 D瓜哥 阅读 Spring 源码以及相关文档时的笔记。对学习内容做一些总结和提炼,分享出来也方便大家一起学习,共同进步。 + +== 友情支持 + +如果您觉得这个笔记对您有所帮助,看在D瓜哥码字的辛苦上,请友情支持一下,D瓜哥感激不尽,😜 + +[cols="2*^",frame=none] +|=== +| image:images/alipay.png[title="支付宝", alt="支付宝", width="95%"] +| image:images/wxpay.jpg[title="微信", alt="微信", width="95%"] +|=== + +有些打赏的朋友希望可以加个好友,欢迎关注D瓜哥的微信公众号,这样就可以通过公众号的回复直接给我发信息。 + +image::images/wx-jikerizhi.png[{image_attr}] + +TIP: **公众号的微信号是: jikerizhi (“极客日志”全拼)**。__因为众所周知的原因,有时图片加载不出来。如果图片加载不出来可以直接通过搜索微信号来查找我的公众号。__ + +== 官网及版本库 + +本文档的版本库托管在 Github 上,另外单独发布。 + +“地瓜哥”博客网:: https://www.diguage.com/[^] 。D瓜哥的个人博客。欢迎光临,不过,内容很杂乱,请见谅。不见谅,你来打我啊,😂😂 +本文档官网:: https://diguage.github.io/spring-framework/[^] 。为了方便阅读,这里展示了处理好的文档。阅读请点击这个网址。 +本文档版本库:: https://github.com/diguage/spring-framework[^] 。由于组织方式的特殊性,坦白讲,不建议大家发 PR。有问题,欢迎发 Issue 讨论。 \ No newline at end of file diff --git a/truman/src/docs/asciidoc/property-placeholder.adoc b/truman/src/docs/asciidoc/property-placeholder.adoc new file mode 100644 index 000000000000..704f2c381a49 --- /dev/null +++ b/truman/src/docs/asciidoc/property-placeholder.adoc @@ -0,0 +1,25 @@ += 占位符解析 + +.`PropertyPlaceholderHelper.parseStringValue` +[{java_src_attr}] +---- +include::{core_src_dir}/util/PropertyPlaceholderHelper.java[tag=parseStringValue] +---- + + +.`PropertyPlaceholderHelper.parseStringValue` +[{java_src_attr}] +---- +include::{core_src_dir}/util/PropertyPlaceholderHelper.java[tag=findPlaceholderEndIndex] +---- + +plantuml::{includedir}/puml/org.springframework.context.config.PropertyPlaceholderBeanDefinitionParser.puml[{diagram_attr}] + +plantuml::{includedir}/puml/org.springframework.context.config.PropertyPlaceholderBeanDefinitionParser-parse.puml[{diagram_attr}] + +plantuml::{includedir}/puml/org.springframework.context.support.PropertySourcesPlaceholderConfigurer.puml[{diagram_attr}] + +plantuml::{includedir}/puml/org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.puml[{diagram_attr}] + + +. 可以解析到其他的 `PropertySourcesPlaceholderConfigurer` 吗?或者可以配置多个 `PropertySourcesPlaceholderConfigurer` 吗? diff --git a/truman/src/docs/asciidoc/proxy-pattern.adoc b/truman/src/docs/asciidoc/proxy-pattern.adoc new file mode 100644 index 000000000000..88d642853e73 --- /dev/null +++ b/truman/src/docs/asciidoc/proxy-pattern.adoc @@ -0,0 +1,2 @@ +[#proxy-pattern] += 代理模式 diff --git a/truman/src/docs/asciidoc/puml/00AA.puml b/truman/src/docs/asciidoc/puml/00AA.puml new file mode 100644 index 000000000000..20e891ae4a04 --- /dev/null +++ b/truman/src/docs/asciidoc/puml/00AA.puml @@ -0,0 +1,7 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **标题** + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml diff --git a/truman/src/docs/asciidoc/puml/AbstractApplicationContext-invokeBeanFactoryPostProcessors-2.puml b/truman/src/docs/asciidoc/puml/AbstractApplicationContext-invokeBeanFactoryPostProcessors-2.puml new file mode 100644 index 000000000000..4eccc81c89ff --- /dev/null +++ b/truman/src/docs/asciidoc/puml/AbstractApplicationContext-invokeBeanFactoryPostProcessors-2.puml @@ -0,0 +1,248 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **""BeanDefinitionRegistryPostProcessor & BeanFactoryPostProcessor"" 调用过程** + + +actor Actor +participant AbstractApplicationContext << (A,#AADCDF) >> +participant PostProcessorRegistrationDelegate << (C,#ADD1B2) >> +participant BeanDefinitionRegistryPostProcessor << (I,#AB9DE1) >> +participant BeanFactoryPostProcessor << (I,#AB9DE1) >> + +Actor -> AbstractApplicationContext: ""refresh""\n重塑容器 +activate AbstractApplicationContext + + ||| + + AbstractApplicationContext -> AbstractApplicationContext: ""invokeBeanFactoryPostProcessors"" + activate AbstractApplicationContext #53E516 + AbstractApplicationContext -> PostProcessorRegistrationDelegate: + activate PostProcessorRegistrationDelegate + loop #D5E8D4 **遍历所有在 refresh 之前添加的 ""BeanDefinitionRegistryPostProcessor"" 实例** + + ||| + + PostProcessorRegistrationDelegate -> BeanDefinitionRegistryPostProcessor: ""postProcessBeanDefinitionRegistry""\n向容器添加 Bean 的定义(也可删除、修改) + activate BeanDefinitionRegistryPostProcessor + note right: 默认为空。这里是直接获取一个 `List` 类型的对象。可以在在执行\n""refresh"" 之前,通过调用 "AbstractApplicationContext"\n"".addBeanFactoryPostProcessor"" 方法添加。 + + ||| + + PostProcessorRegistrationDelegate <- BeanDefinitionRegistryPostProcessor + deactivate BeanDefinitionRegistryPostProcessor + + ||| + + end + + ||| + + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate: ""sortPostProcessors""\n排序 ""BeanDefinitionRegistryPostProcessor"" 实现类 + activate PostProcessorRegistrationDelegate #FF33FF + ||| + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate + deactivate PostProcessorRegistrationDelegate + + ||| + + loop #F8CECC **遍历所有实现 ""PriorityOrdered"" 接口的 ""BeanDefinitionRegistryPostProcessor"" 实例** + + ||| + + PostProcessorRegistrationDelegate -> BeanDefinitionRegistryPostProcessor: ""postProcessBeanDefinitionRegistry""\n向容器添加 Bean 的定义(也可删除、修改) + activate BeanDefinitionRegistryPostProcessor + note over PostProcessorRegistrationDelegate,BeanDefinitionRegistryPostProcessor: 实现 ""PriorityOrdered"" 接口的 ""BeanDefinitionRegistryPostProcessor"" 实例 + PostProcessorRegistrationDelegate <- BeanDefinitionRegistryPostProcessor + deactivate BeanDefinitionRegistryPostProcessor + + ||| + + end + + ||| + + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate: ""sortPostProcessors""\n排序 ""BeanDefinitionRegistryPostProcessor"" 实现类 + activate PostProcessorRegistrationDelegate #FF33FF + ||| + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate + deactivate PostProcessorRegistrationDelegate + + ||| + + loop #FFF2CC **遍历所有实现 ""Ordered"" 接口的 ""BeanDefinitionRegistryPostProcessor"" 实例** + + ||| + + PostProcessorRegistrationDelegate -> BeanDefinitionRegistryPostProcessor: ""postProcessBeanDefinitionRegistry""\n向容器添加 Bean 的定义(也可删除、修改) + activate BeanDefinitionRegistryPostProcessor + note over PostProcessorRegistrationDelegate,BeanDefinitionRegistryPostProcessor: 实现 ""Ordered"" 接口的 ""BeanDefinitionRegistryPostProcessor"" 实例 + PostProcessorRegistrationDelegate <- BeanDefinitionRegistryPostProcessor + deactivate BeanDefinitionRegistryPostProcessor + + ||| + + end + + ||| + + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate: ""sortPostProcessors""\n排序 ""BeanDefinitionRegistryPostProcessor"" 实现类 + activate PostProcessorRegistrationDelegate #FF33FF + ||| + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate + deactivate PostProcessorRegistrationDelegate + + ||| + + loop #DAE8FC **“递归”遍历剩余所有 ""BeanDefinitionRegistryPostProcessor"" 实例** + + ||| + + note over PostProcessorRegistrationDelegate,BeanDefinitionRegistryPostProcessor: 在添加 ""BeanDefinition"" 的过程中,有可能有新加入的 ""BeanDefinitionRegistryPostProcessor"" 类型的 Bean,\n所以要递归调用这个过程,以求把所有的 ""BeanDefinitionRegistryPostProcessor"" 类型的 Bean 都执行到。 + + ||| + + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate: ""sortPostProcessors""\n排序 ""BeanDefinitionRegistryPostProcessor"" 实现类 + activate PostProcessorRegistrationDelegate #FF33FF + ||| + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate + deactivate PostProcessorRegistrationDelegate + + ||| + + loop #DDFFFF **遍历剩余的 ""BeanDefinitionRegistryPostProcessor"" 实例** + + ||| + + PostProcessorRegistrationDelegate -> BeanDefinitionRegistryPostProcessor: ""postProcessBeanDefinitionRegistry""\n向容器添加 Bean 的定义(也可删除、修改) + activate BeanDefinitionRegistryPostProcessor + note over PostProcessorRegistrationDelegate,BeanDefinitionRegistryPostProcessor: 剩余所有的 ""BeanDefinitionRegistryPostProcessor"" 实例 + PostProcessorRegistrationDelegate <- BeanDefinitionRegistryPostProcessor + deactivate BeanDefinitionRegistryPostProcessor + + ||| + + end + + ||| + + end + + ||| + + loop #E1D5E7 **遍历所有 ""BeanDefinitionRegistryPostProcessor"" 实例** + + ||| + + note over PostProcessorRegistrationDelegate,BeanFactoryPostProcessor: 由于 ""BeanDefinitionRegistryPostProcessor"" 继承了 ""BeanFactoryPostProcessor""。\n所以,所有的 ""BeanDefinitionRegistryPostProcessor"" 实例,也是 ""BeanFactoryPostProcessor"" 实例。 + + ||| + + PostProcessorRegistrationDelegate -> BeanFactoryPostProcessor: ""postProcessBeanFactory""\n更新容器内 Bean 的定义(增加、删除、修改) + activate BeanFactoryPostProcessor + + ||| + + PostProcessorRegistrationDelegate <- BeanFactoryPostProcessor + deactivate BeanFactoryPostProcessor + + ||| + + end + + ||| + + loop #D5E8D4 **遍历所有在 refresh 之前添加的 ""BeanFactoryPostProcessor"" 实例** + + ||| + + PostProcessorRegistrationDelegate -> BeanFactoryPostProcessor: ""postProcessBeanFactory""\n更新容器内 Bean 的定义(增加、删除、修改) + activate BeanFactoryPostProcessor + + ||| + + PostProcessorRegistrationDelegate <- BeanFactoryPostProcessor + deactivate BeanFactoryPostProcessor + + ||| + + end + + ||| + + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate: ""sortPostProcessors""\n排序 ""BeanFactoryPostProcessor"" 实现类 + activate PostProcessorRegistrationDelegate #FF33FF + ||| + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate + deactivate PostProcessorRegistrationDelegate + + ||| + + loop #F8CECC **遍历所有实现了 ""PriorityOrdered"" 的 ""BeanFactoryPostProcessor"" 实例** + + ||| + + PostProcessorRegistrationDelegate -> BeanFactoryPostProcessor: ""postProcessBeanFactory""\n更新容器内 Bean 的定义(增加、删除、修改) + note over PostProcessorRegistrationDelegate,BeanFactoryPostProcessor: 实现 ""PriorityOrdered"" 接口的 ""BeanFactoryPostProcessor"" 实例 + activate BeanFactoryPostProcessor + PostProcessorRegistrationDelegate <- BeanFactoryPostProcessor + deactivate BeanFactoryPostProcessor + + ||| + + end + + ||| + + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate: ""sortPostProcessors""\n排序 ""BeanFactoryPostProcessor"" 实现类 + activate PostProcessorRegistrationDelegate #FF33FF + ||| + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate + deactivate PostProcessorRegistrationDelegate + + ||| + + loop #FFF2CC **遍历所有实现了 ""Ordered"" 的 ""BeanFactoryPostProcessor"" 实例** + + ||| + + PostProcessorRegistrationDelegate -> BeanFactoryPostProcessor: ""postProcessBeanFactory""\n更新容器内 Bean 的定义(增加、删除、修改) + note over PostProcessorRegistrationDelegate,BeanFactoryPostProcessor: 实现 ""Ordered"" 接口的 ""BeanFactoryPostProcessor"" 实例 + activate BeanFactoryPostProcessor + PostProcessorRegistrationDelegate <- BeanFactoryPostProcessor + deactivate BeanFactoryPostProcessor + + ||| + + end + + ||| + + loop #DDFFFF **遍历剩余所有的 ""BeanFactoryPostProcessor"" 实例** + + ||| + + PostProcessorRegistrationDelegate -> BeanFactoryPostProcessor: ""postProcessBeanFactory""\n更新容器内 Bean 的定义(增加、删除、修改) + note over PostProcessorRegistrationDelegate,BeanFactoryPostProcessor: 剩余所有的 ""BeanFactoryPostProcessor"" 实例 + activate BeanFactoryPostProcessor + PostProcessorRegistrationDelegate <- BeanFactoryPostProcessor + deactivate BeanFactoryPostProcessor + + ||| + + end + + ||| + + AbstractApplicationContext <- PostProcessorRegistrationDelegate + deactivate PostProcessorRegistrationDelegate + AbstractApplicationContext -> AbstractApplicationContext + deactivate AbstractApplicationContext + + ||| + +Actor <- AbstractApplicationContext: 完成容器初始化 +deactivate AbstractApplicationContext + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml diff --git a/truman/src/docs/asciidoc/puml/AbstractApplicationContext-invokeBeanFactoryPostProcessors.puml b/truman/src/docs/asciidoc/puml/AbstractApplicationContext-invokeBeanFactoryPostProcessors.puml new file mode 100644 index 000000000000..38b219f574a4 --- /dev/null +++ b/truman/src/docs/asciidoc/puml/AbstractApplicationContext-invokeBeanFactoryPostProcessors.puml @@ -0,0 +1,238 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **AbstractApplicationContext.invokeBeanFactoryPostProcessors -- 调用 ""BeanFactoryPostProcessor""** + + +actor Actor +participant AbstractApplicationContext << (C,#ADD1B2) >> +participant PostProcessorRegistrationDelegate << (C,#ADD1B2) >> +participant BeanDefinitionRegistryPostProcessor << (I,#AB9DE1) >> +note over BeanDefinitionRegistryPostProcessor: ""BeanDefinitionRegistryPostProcessor"" \n 是 ""BeanFactoryPostProcessor"" 的子接口。 +participant List << (I,#AB9DE1) >> +participant ListableBeanFactory << (I,#AB9DE1) >> +participant BeanFactoryPostProcessor << (I,#AB9DE1) >> + +Actor -> AbstractApplicationContext: ""refresh""\n重塑容器 +activate AbstractApplicationContext + + AbstractApplicationContext -> AbstractApplicationContext: ""invokeBeanFactoryPostProcessors""\n调用 ""BeanFactoryPostProcessor"" + activate AbstractApplicationContext + + AbstractApplicationContext -> PostProcessorRegistrationDelegate: ""invokeBeanFactoryPostProcessors""\n调用 ""BeanFactoryPostProcessor"" + activate PostProcessorRegistrationDelegate + loop 所有的 ""BeanFactoryPostProcessor"" + alt #CFFCED 实现 ""BeanDefinitionRegistryPostProcessor"" 接口 + PostProcessorRegistrationDelegate -> BeanDefinitionRegistryPostProcessor: ""postProcessBeanDefinitionRegistry""\n调用 ""BeanFactoryPostProcessor"" + activate BeanDefinitionRegistryPostProcessor + note over PostProcessorRegistrationDelegate,BeanDefinitionRegistryPostProcessor: 只遍历实现 ""BeanDefinitionRegistryPostProcessor"" 的 ""BeanFactoryPostProcessor"" + PostProcessorRegistrationDelegate <- BeanDefinitionRegistryPostProcessor + deactivate BeanDefinitionRegistryPostProcessor + + ||| + + PostProcessorRegistrationDelegate -> List: ""add""\n添加元素 + activate List + note over PostProcessorRegistrationDelegate,List: 添加到 ""registryProcessors"" 集合中 + PostProcessorRegistrationDelegate <- List + deactivate List + ||| + else #FFF7FA 没有实现 ""BeanDefinitionRegistryPostProcessor"" 接口 + PostProcessorRegistrationDelegate -> List: ""add""\n添加元素 + activate List + note over PostProcessorRegistrationDelegate,List: 添加到 ""regularPostProcessors"" 集合中 + PostProcessorRegistrationDelegate <- List + deactivate List + ||| + end + end + + ||| + ==首先,调用实现了 ""PriorityOrdered"" 接口的 ""BeanDefinitionRegistryPostProcessors"" 的 ""postProcessBeanDefinitionRegistry"" 方法 == + ||| + + PostProcessorRegistrationDelegate -> ListableBeanFactory: ""getBeanNamesForType""\n获取指定类型的 Bean 名称 + activate ListableBeanFactory + note over PostProcessorRegistrationDelegate,ListableBeanFactory: 获取类型为 ""BeanDefinitionRegistryPostProcessor"" 的 Bean 名称 + PostProcessorRegistrationDelegate <- ListableBeanFactory + deactivate ListableBeanFactory + + ||| + + loop 上述获取的 Bean 名称 + PostProcessorRegistrationDelegate -> ListableBeanFactory: ""isTypeMatch""\n检查 Bean 是否为指定类型 + activate ListableBeanFactory + note over PostProcessorRegistrationDelegate,ListableBeanFactory: 筛选出实现了 ""PriorityOrdered"" 接口的 ""BeanFactoryPostProcessor"",供后面优先调用。 + PostProcessorRegistrationDelegate <- ListableBeanFactory + deactivate ListableBeanFactory + end + + ||| + + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate: ""sortPostProcessors""\n对上述筛选出来的 Bean 进行排序 + activate PostProcessorRegistrationDelegate + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate + deactivate PostProcessorRegistrationDelegate + + ||| + + PostProcessorRegistrationDelegate -> List: ""addAll""\n将上述 Bean 全部添加到 ""registryProcessors"" 集合中 + activate List + note over PostProcessorRegistrationDelegate,List: 添加到 ""registryProcessors"" 集合中 + PostProcessorRegistrationDelegate <- List + deactivate List + + ||| + + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate: ""invokeBeanDefinitionRegistryPostProcessors""\n调用 ""BeanDefinitionRegistryPostProcessor"" + activate PostProcessorRegistrationDelegate + loop 上述筛选出来的所有 ""BeanFactoryPostProcessor"" + PostProcessorRegistrationDelegate -> BeanDefinitionRegistryPostProcessor: ""postProcessBeanDefinitionRegistry""\n调用 ""BeanFactoryPostProcessor"" + activate BeanDefinitionRegistryPostProcessor + note over PostProcessorRegistrationDelegate,BeanDefinitionRegistryPostProcessor: 只遍历实现 ""PriorityOrdered"" 接口的 ""BeanDefinitionRegistryPostProcessor"" Bean + PostProcessorRegistrationDelegate <- BeanDefinitionRegistryPostProcessor + deactivate BeanDefinitionRegistryPostProcessor + end + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate + deactivate PostProcessorRegistrationDelegate + + ||| + ==其次,调用实现了 ""Ordered"" 接口的 ""BeanDefinitionRegistryPostProcessors"" 的 ""postProcessBeanDefinitionRegistry"" 方法 == + ||| + + PostProcessorRegistrationDelegate -> ListableBeanFactory: ""getBeanNamesForType""\n获取指定类型的 Bean 名称 + activate ListableBeanFactory + note over PostProcessorRegistrationDelegate,ListableBeanFactory: 获取类型为 ""BeanDefinitionRegistryPostProcessor"" 的 Bean 名称 + PostProcessorRegistrationDelegate <- ListableBeanFactory + deactivate ListableBeanFactory + + ||| + + loop 上述获取的 Bean 名称 + PostProcessorRegistrationDelegate -> ListableBeanFactory: ""isTypeMatch""\n检查 Bean 是否为指定类型 + activate ListableBeanFactory + note over PostProcessorRegistrationDelegate,ListableBeanFactory: 筛选出实现了 ""Ordered"" 接口的 ""BeanFactoryPostProcessor"",供后面优先调用。 + PostProcessorRegistrationDelegate <- ListableBeanFactory + deactivate ListableBeanFactory + end + + ||| + + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate: ""sortPostProcessors""\n对上述筛选出来的 Bean 进行排序 + activate PostProcessorRegistrationDelegate + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate + deactivate PostProcessorRegistrationDelegate + + ||| + + PostProcessorRegistrationDelegate -> List: ""addAll""\n将上述 Bean 全部添加到 ""registryProcessors"" 集合中 + activate List + note over PostProcessorRegistrationDelegate,List: 添加到 ""registryProcessors"" 集合中 + PostProcessorRegistrationDelegate <- List + deactivate List + + ||| + + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate: ""invokeBeanDefinitionRegistryPostProcessors""\n调用 ""BeanDefinitionRegistryPostProcessor"" + activate PostProcessorRegistrationDelegate + loop 上述筛选出来的所有 ""BeanFactoryPostProcessor"" + PostProcessorRegistrationDelegate -> BeanDefinitionRegistryPostProcessor: ""postProcessBeanDefinitionRegistry""\n调用 ""BeanFactoryPostProcessor"" + activate BeanDefinitionRegistryPostProcessor + note over PostProcessorRegistrationDelegate,BeanDefinitionRegistryPostProcessor: 只遍历实现 ""PriorityOrdered"" 接口的 ""BeanDefinitionRegistryPostProcessor"" Bean + PostProcessorRegistrationDelegate <- BeanDefinitionRegistryPostProcessor + deactivate BeanDefinitionRegistryPostProcessor + end + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate + deactivate PostProcessorRegistrationDelegate + + ||| + ==最后,调用剩余的 ""BeanDefinitionRegistryPostProcessors"" 的 ""postProcessBeanDefinitionRegistry"" 方法== + ||| + loop 有新创建出来的 ""BeanDefinitionRegistryPostProcessor"" 就遍历 + PostProcessorRegistrationDelegate -> ListableBeanFactory: ""getBeanNamesForType""\n获取指定类型的 Bean 名称 + activate ListableBeanFactory + note over PostProcessorRegistrationDelegate,ListableBeanFactory: 获取类型为 ""BeanDefinitionRegistryPostProcessor"" 的 Bean 名称 + PostProcessorRegistrationDelegate <- ListableBeanFactory + deactivate ListableBeanFactory + + ||| + + loop 上述获取的 Bean 名称 + PostProcessorRegistrationDelegate -> ListableBeanFactory: ""isTypeMatch""\n检查 Bean 是否为指定类型 + activate ListableBeanFactory + note over PostProcessorRegistrationDelegate,ListableBeanFactory: 筛选出实现了 ""Ordered"" 接口的 ""BeanFactoryPostProcessor"",供后面优先调用。 + PostProcessorRegistrationDelegate <- ListableBeanFactory + deactivate ListableBeanFactory + end + + ||| + + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate: ""sortPostProcessors""\n对上述筛选出来的 Bean 进行排序 + activate PostProcessorRegistrationDelegate + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate + deactivate PostProcessorRegistrationDelegate + + ||| + + PostProcessorRegistrationDelegate -> List: ""addAll""\n将上述 Bean 全部添加到 ""registryProcessors"" 集合中 + activate List + note over PostProcessorRegistrationDelegate,List: 添加到 ""registryProcessors"" 集合中 + PostProcessorRegistrationDelegate <- List + deactivate List + + ||| + + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate: ""invokeBeanDefinitionRegistryPostProcessors""\n调用 ""BeanDefinitionRegistryPostProcessor"" + activate PostProcessorRegistrationDelegate + loop 上述筛选出来的所有 ""BeanFactoryPostProcessor"" + PostProcessorRegistrationDelegate -> BeanDefinitionRegistryPostProcessor: ""postProcessBeanDefinitionRegistry""\n调用 ""BeanFactoryPostProcessor"" + activate BeanDefinitionRegistryPostProcessor + note over PostProcessorRegistrationDelegate,BeanDefinitionRegistryPostProcessor: 只遍历实现 ""PriorityOrdered"" 接口的 ""BeanDefinitionRegistryPostProcessor"" Bean + PostProcessorRegistrationDelegate <- BeanDefinitionRegistryPostProcessor + deactivate BeanDefinitionRegistryPostProcessor + end + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate + deactivate PostProcessorRegistrationDelegate + end + + ||| + ==结尾,调用 ""BeanFactoryPostProcessor"" 的 ""postProcessBeanFactory"" 方法 == + ||| + + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate: ""invokeBeanFactoryPostProcessors""\n调用 ""BeanFactoryPostProcessor""\n 的 ""postProcessBeanFactory"" + activate PostProcessorRegistrationDelegate + loop 遍历 ""registryProcessors"" + PostProcessorRegistrationDelegate -> BeanFactoryPostProcessor: ""postProcessBeanFactory""\n调用 ""BeanFactoryPostProcessor""\n 的 ""postProcessBeanFactory"" + activate BeanFactoryPostProcessor + note over PostProcessorRegistrationDelegate,BeanFactoryPostProcessor: 遍历 ""registryProcessors"" + PostProcessorRegistrationDelegate <- BeanFactoryPostProcessor + deactivate BeanFactoryPostProcessor + end + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate + deactivate PostProcessorRegistrationDelegate + + ||| + + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate: ""invokeBeanFactoryPostProcessors""\n调用 ""BeanFactoryPostProcessor""\n 的 ""postProcessBeanFactory"" + activate PostProcessorRegistrationDelegate + loop 遍历 ""regularPostProcessors"" + PostProcessorRegistrationDelegate -> BeanFactoryPostProcessor: ""postProcessBeanFactory""\n调用 ""BeanFactoryPostProcessor""\n 的 ""postProcessBeanFactory"" + activate BeanFactoryPostProcessor + note over PostProcessorRegistrationDelegate,BeanFactoryPostProcessor: 遍历 ""regularPostProcessors"" + PostProcessorRegistrationDelegate <- BeanFactoryPostProcessor + deactivate BeanFactoryPostProcessor + end + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate + deactivate PostProcessorRegistrationDelegate + + AbstractApplicationContext <- PostProcessorRegistrationDelegate + deactivate PostProcessorRegistrationDelegate + + AbstractApplicationContext -> AbstractApplicationContext + deactivate AbstractApplicationContext + +Actor <- AbstractApplicationContext: 完成容器初始化 +deactivate AbstractApplicationContext + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml diff --git a/truman/src/docs/asciidoc/puml/AbstractApplicationContext-obtainFreshBeanFactory.puml b/truman/src/docs/asciidoc/puml/AbstractApplicationContext-obtainFreshBeanFactory.puml new file mode 100644 index 000000000000..1690bae40514 --- /dev/null +++ b/truman/src/docs/asciidoc/puml/AbstractApplicationContext-obtainFreshBeanFactory.puml @@ -0,0 +1,174 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **AbstractApplicationContext.obtainFreshBeanFactory -- XML 配置文件解析** + + +actor Actor +participant AbstractApplicationContext << (C,#ADD1B2) >> +participant AbstractRefreshableApplicationContext << (C,#ADD1B2) >> +participant AbstractXmlApplicationContext << (C,#ADD1B2) >> +participant XmlBeanDefinitionReader << (C,#ADD1B2) >> +participant ResourceLoader << (I,#AB9DE1) >> +participant BeanDefinitionDocumentReader << (C,#ADD1B2) >> +participant BeanDefinitionParserDelegate << (C,#ADD1B2) >> +participant DefaultListableBeanFactory << (C,#ADD1B2) >> +participant NamespaceHandler << (I,#AB9DE1) >> + +Actor -> AbstractApplicationContext: ""refresh""\n重塑容器 +activate AbstractApplicationContext + + AbstractApplicationContext -> AbstractApplicationContext: ""obtainFreshBeanFactory""\n获取BeanFactory + activate AbstractApplicationContext + + AbstractApplicationContext -> AbstractRefreshableApplicationContext: ""refreshBeanFactory""\n刷新BeanFactory + activate AbstractRefreshableApplicationContext + + AbstractRefreshableApplicationContext -> AbstractXmlApplicationContext: ""loadBeanDefinitions""\n加载 ""BeanDefinition"" + activate AbstractXmlApplicationContext + + AbstractXmlApplicationContext -> AbstractXmlApplicationContext: ""loadBeanDefinitions""\n加载 ""BeanDefinition"" + activate AbstractXmlApplicationContext + + AbstractXmlApplicationContext -> XmlBeanDefinitionReader: ""loadBeanDefinitions""\n加载 ""BeanDefinition"" + activate XmlBeanDefinitionReader + note right #FFAAAA: ""loadBeanDefinitions"" 方法被重\n载了多次,在这里也被调用了多次。 + + XmlBeanDefinitionReader -> ResourceLoader: ""getResources""\n加载 ""Resource"" + activate ResourceLoader + note over XmlBeanDefinitionReader,ResourceLoader: 将配置路径转化成 ""Resource"" 对象。\n注:这里的路径也支持占位符替换。可以从环境变量中取值。 + XmlBeanDefinitionReader <- ResourceLoader + deactivate ResourceLoader + + ||| + + XmlBeanDefinitionReader -> XmlBeanDefinitionReader: ""doLoadBeanDefinitions""\n加载 ""BeanDefinition"" + activate XmlBeanDefinitionReader + XmlBeanDefinitionReader -> BeanDefinitionDocumentReader: ""registerBeanDefinitions""\n注册 ""BeanDefinition"" + activate BeanDefinitionDocumentReader + BeanDefinitionDocumentReader -> BeanDefinitionDocumentReader: ""doRegisterBeanDefinitions""\n注册 ""BeanDefinition"" + activate BeanDefinitionDocumentReader + BeanDefinitionDocumentReader -> BeanDefinitionDocumentReader: ""parseBeanDefinitions""\n处理 ""BeanDefinition"" 定义 + activate BeanDefinitionDocumentReader + note right #FFAAAA: 对于 XML 的解析都在次方法中! + alt #LightGreen **默认命名空间(即 ""beans"")** + ||| + group#yellow """" + BeanDefinitionDocumentReader -> BeanDefinitionDocumentReader: ""importBeanDefinitionResource""\n处理 """" 标签 + activate BeanDefinitionDocumentReader + note right #FFAAAA: 该方法时序图做了极大简化处理。\n不过,大致流程是这样的。 + BeanDefinitionDocumentReader -> ResourceLoader: ""getResources""\n加载 ""Resource"" + activate ResourceLoader + note over BeanDefinitionDocumentReader,ResourceLoader: 将配置路径转化成 ""Resource"" 对象。\n注:这里的路径也支持占位符替换。可以从环境变量中取值。 + BeanDefinitionDocumentReader <- ResourceLoader + deactivate ResourceLoader + ||| + BeanDefinitionDocumentReader -> XmlBeanDefinitionReader: ""loadBeanDefinitions""\n加载 ""BeanDefinition"" + activate XmlBeanDefinitionReader + note over BeanDefinitionDocumentReader,XmlBeanDefinitionReader: “递归”调用 ""loadBeanDefinitions"" 方法,解析配置文件并加载 Bean。 + BeanDefinitionDocumentReader <- XmlBeanDefinitionReader + deactivate XmlBeanDefinitionReader + BeanDefinitionDocumentReader -> BeanDefinitionDocumentReader + deactivate BeanDefinitionDocumentReader + ||| + end + + ||| + + group#yellow """" + BeanDefinitionDocumentReader -> BeanDefinitionDocumentReader: ""processAliasRegistration""\n处理 """" 标签 + activate BeanDefinitionDocumentReader + BeanDefinitionDocumentReader -> DefaultListableBeanFactory: ""registerAlias""\n注册别名 + activate DefaultListableBeanFactory + note over BeanDefinitionDocumentReader,DefaultListableBeanFactory: 其实,这里调用的是 ""AliasRegistry"" 的 ""registerAlias"" 方法。\n 但, ""DefaultListableBeanFactory"" 是 ""AliasRegistry"" 的一个实现类。 + BeanDefinitionDocumentReader -> DefaultListableBeanFactory + deactivate DefaultListableBeanFactory + BeanDefinitionDocumentReader -> BeanDefinitionDocumentReader + deactivate BeanDefinitionDocumentReader + ||| + end + + ||| + + group#yellow """" + BeanDefinitionDocumentReader -> BeanDefinitionDocumentReader: ""processBeanDefinition""\n处理 """" 标签 + activate BeanDefinitionDocumentReader + BeanDefinitionDocumentReader -> BeanDefinitionParserDelegate: ""parseBeanDefinitionElement""\n处理 """" 标签元素 + activate BeanDefinitionParserDelegate + note over BeanDefinitionDocumentReader,BeanDefinitionParserDelegate: 这里也一并处理 """" 标签的子标签,比如 """" 等标签。\n还会设置一些默认值,比如 ""lazy-init"" 等 + BeanDefinitionDocumentReader -> BeanDefinitionParserDelegate + deactivate BeanDefinitionParserDelegate + + ||| + + BeanDefinitionDocumentReader -> DefaultListableBeanFactory: ""registerBeanDefinition""\n注册 ""BeanDefinition"" + activate DefaultListableBeanFactory + note over BeanDefinitionDocumentReader,DefaultListableBeanFactory: 其实,这里调用的是 ""BeanDefinitionRegistry"" 的 ""registerBeanDefinition"" 方法。\n 但, ""DefaultListableBeanFactory"" 是 ""BeanDefinitionRegistry"" 的一个实现类。 + BeanDefinitionDocumentReader -> DefaultListableBeanFactory + deactivate DefaultListableBeanFactory + + ||| + loop 上述 ""BeanDefinition"" 对象中包含的所有别名 + BeanDefinitionDocumentReader -> DefaultListableBeanFactory: ""registerAlias""\n注册别名 + activate DefaultListableBeanFactory + note over BeanDefinitionDocumentReader,DefaultListableBeanFactory : 其实,这里调用的是 ""AliasRegistry"" 的方法。\n但, ""DefaultListableBeanFactory"" 是 ""AliasRegistry"" 的一个实现类。 + BeanDefinitionDocumentReader -> DefaultListableBeanFactory + deactivate DefaultListableBeanFactory + end + BeanDefinitionDocumentReader -> BeanDefinitionDocumentReader + deactivate BeanDefinitionDocumentReader + ||| + end + + ||| + + group#yellow """" + BeanDefinitionDocumentReader -> BeanDefinitionDocumentReader: ""doRegisterBeanDefinitions""\n处理 """" 标签 + activate BeanDefinitionDocumentReader + note over BeanDefinitionDocumentReader,BeanDefinitionDocumentReader:递归调用,处理 """" 标签。 + BeanDefinitionDocumentReader -> BeanDefinitionDocumentReader + deactivate BeanDefinitionDocumentReader + ||| + end + ||| + else #FFF0C5 **除 ""beans"" 外的其他命名空间** + BeanDefinitionDocumentReader -> BeanDefinitionParserDelegate: ""parseCustomElement""\n处理自定义标签元素 + activate BeanDefinitionParserDelegate + BeanDefinitionParserDelegate -> NamespaceHandler: ""parse""\n处理自定义标签元素 + activate NamespaceHandler + note over BeanDefinitionParserDelegate,NamespaceHandler: 除 ""beans"" 命名空间外,其他所有标签都是通过该机制来扩展 Spring 的能力的。\n只需将 Spring 其他命名空间的或自定义命名空间的标签处理成 ""BeanDefinition"" 对象即可,\n后续,Spring 会调用 ""BeanDefinitionRegistry"" 的 ""registerBeanDefinition"" 方法注册该对象。 + BeanDefinitionParserDelegate <- NamespaceHandler + deactivate NamespaceHandler + BeanDefinitionDocumentReader -> BeanDefinitionParserDelegate + deactivate BeanDefinitionParserDelegate + ||| + end + BeanDefinitionDocumentReader -> BeanDefinitionDocumentReader + deactivate BeanDefinitionDocumentReader + BeanDefinitionDocumentReader -> BeanDefinitionDocumentReader + deactivate BeanDefinitionDocumentReader + XmlBeanDefinitionReader <- BeanDefinitionDocumentReader + deactivate BeanDefinitionDocumentReader + XmlBeanDefinitionReader -> XmlBeanDefinitionReader + deactivate XmlBeanDefinitionReader + + AbstractXmlApplicationContext <- XmlBeanDefinitionReader + deactivate XmlBeanDefinitionReader + + AbstractXmlApplicationContext -> AbstractXmlApplicationContext + deactivate AbstractXmlApplicationContext + + AbstractRefreshableApplicationContext <- AbstractXmlApplicationContext + deactivate AbstractXmlApplicationContext + + AbstractApplicationContext <- AbstractRefreshableApplicationContext + deactivate AbstractRefreshableApplicationContext + + AbstractApplicationContext -> AbstractApplicationContext + deactivate AbstractApplicationContext + +Actor <- AbstractApplicationContext: 完成容器初始化 +deactivate AbstractApplicationContext + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml diff --git a/truman/src/docs/asciidoc/puml/AbstractApplicationContext-prepareBeanFactory.puml b/truman/src/docs/asciidoc/puml/AbstractApplicationContext-prepareBeanFactory.puml new file mode 100644 index 000000000000..a927b8510419 --- /dev/null +++ b/truman/src/docs/asciidoc/puml/AbstractApplicationContext-prepareBeanFactory.puml @@ -0,0 +1,168 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **AbstractApplicationContext.prepareBeanFactory -- 准备 ""BeanFactory""** + + +actor Actor +participant AbstractApplicationContext << (C,#ADD1B2) >> +participant DefaultListableBeanFactory << (C,#ADD1B2) >> + +Actor -> AbstractApplicationContext: ""refresh""\n重塑容器 +activate AbstractApplicationContext + + AbstractApplicationContext -> AbstractApplicationContext: ""prepareBeanFactory""\n准备 ""BeanFactory"" + activate AbstractApplicationContext + + AbstractApplicationContext -> DefaultListableBeanFactory: ""setBeanClassLoader""\n设置类加载器 + activate DefaultListableBeanFactory + ||| + AbstractApplicationContext <- DefaultListableBeanFactory + deactivate DefaultListableBeanFactory + + ||| + + AbstractApplicationContext -> DefaultListableBeanFactory: ""setBeanExpressionResolver""\n设置Spring表达式解析器 + activate DefaultListableBeanFactory + note over AbstractApplicationContext,DefaultListableBeanFactory: ""StandardBeanExpressionResolver"" + AbstractApplicationContext <- DefaultListableBeanFactory + deactivate DefaultListableBeanFactory + + ||| + + AbstractApplicationContext -> DefaultListableBeanFactory: ""addPropertyEditorRegistrar""\n添加默认 ""PropertyEditor"" + activate DefaultListableBeanFactory + note over AbstractApplicationContext,DefaultListableBeanFactory: ""ResourceEditorRegistrar"" + AbstractApplicationContext <- DefaultListableBeanFactory + deactivate DefaultListableBeanFactory + + ||| + + AbstractApplicationContext -> DefaultListableBeanFactory: ""addBeanPostProcessor""\n添加 ""BeanPostProcessor"" + activate DefaultListableBeanFactory + note over AbstractApplicationContext,DefaultListableBeanFactory: ""ApplicationContextAwareProcessor""\n处理各种 ""Aware"" 接口,比如 ""ApplicationContextAware"" 等 + AbstractApplicationContext <- DefaultListableBeanFactory + deactivate DefaultListableBeanFactory + + ||| + + AbstractApplicationContext -> DefaultListableBeanFactory: ""ignoreDependencyInterface""\n添加“依赖忽略接口” + activate DefaultListableBeanFactory + note over AbstractApplicationContext,DefaultListableBeanFactory: 忽略各种 ""Aware"" 接口,\n这些接口由上述 ""ApplicationContextAwareProcessor"" 处理,\n不需要再执行依赖注入。 + AbstractApplicationContext <- DefaultListableBeanFactory + deactivate DefaultListableBeanFactory + + ||| + + AbstractApplicationContext -> DefaultListableBeanFactory: ""registerResolvableDependency""\n添加“可解析的依赖” + activate DefaultListableBeanFactory + note over AbstractApplicationContext,DefaultListableBeanFactory: ""BeanFactory""、""ResourceLoader""、\n""ApplicationEventPublisher""、""ApplicationContext"" + AbstractApplicationContext <- DefaultListableBeanFactory + deactivate DefaultListableBeanFactory + + ||| + + AbstractApplicationContext -> DefaultListableBeanFactory: ""addBeanPostProcessor""\n添加 ""BeanPostProcessor"" + activate DefaultListableBeanFactory + note over AbstractApplicationContext,DefaultListableBeanFactory: ""ApplicationListenerDetector""\n探测 Bean 是否为 ""ApplicationListener"" + AbstractApplicationContext <- DefaultListableBeanFactory + deactivate DefaultListableBeanFactory + + ||| + + alt 不是 Native 镜像 && 包含了 loadTimeWeaver Bean + AbstractApplicationContext -> DefaultListableBeanFactory: ""addBeanPostProcessor""\n添加 ""BeanPostProcessor"" + activate DefaultListableBeanFactory + note over AbstractApplicationContext,DefaultListableBeanFactory: ""LoadTimeWeaverAwareProcessor""\n加载时织入 + AbstractApplicationContext <- DefaultListableBeanFactory + deactivate DefaultListableBeanFactory + + ||| + + AbstractApplicationContext -> DefaultListableBeanFactory: ""setTempClassLoader""\n设置临时类加载器 + activate DefaultListableBeanFactory + note over AbstractApplicationContext,DefaultListableBeanFactory: ""ContextTypeMatchClassLoader"" + AbstractApplicationContext <- DefaultListableBeanFactory + deactivate DefaultListableBeanFactory + end + + ||| + + alt ""BeanFactory"" 实例中不含 ""environment"" + AbstractApplicationContext -> DefaultListableBeanFactory: ""containsLocalBean""\n是否包含 ""environment"" + activate DefaultListableBeanFactory + ||| + AbstractApplicationContext <- DefaultListableBeanFactory + deactivate DefaultListableBeanFactory + + ||| + + AbstractApplicationContext -> DefaultListableBeanFactory: ""registerSingleton""\n注册 Bean + activate DefaultListableBeanFactory + note over AbstractApplicationContext,DefaultListableBeanFactory: ""environment"" // 此为 Bean 名称 + AbstractApplicationContext <- DefaultListableBeanFactory + deactivate DefaultListableBeanFactory + end + + ||| + + alt ""BeanFactory"" 实例中不含 ""systemProperties"" + AbstractApplicationContext -> DefaultListableBeanFactory: ""containsLocalBean""\n是否包含 ""systemProperties"" + activate DefaultListableBeanFactory + ||| + AbstractApplicationContext <- DefaultListableBeanFactory + deactivate DefaultListableBeanFactory + + ||| + + AbstractApplicationContext -> DefaultListableBeanFactory: ""registerSingleton""\n注册 Bean + activate DefaultListableBeanFactory + note over AbstractApplicationContext,DefaultListableBeanFactory: ""systemProperties"" // 此为 Bean 名称 + AbstractApplicationContext <- DefaultListableBeanFactory + deactivate DefaultListableBeanFactory + end + + ||| + + alt ""BeanFactory"" 实例中不含 ""systemEnvironment"" + AbstractApplicationContext -> DefaultListableBeanFactory: ""containsLocalBean""\n是否包含 ""systemEnvironment"" + activate DefaultListableBeanFactory + ||| + AbstractApplicationContext <- DefaultListableBeanFactory + deactivate DefaultListableBeanFactory + + ||| + + AbstractApplicationContext -> DefaultListableBeanFactory: ""registerSingleton""\n注册 Bean + activate DefaultListableBeanFactory + note over AbstractApplicationContext,DefaultListableBeanFactory: ""systemEnvironment"" // 此为 Bean 名称 + AbstractApplicationContext <- DefaultListableBeanFactory + deactivate DefaultListableBeanFactory + end + + ||| + + alt ""BeanFactory"" 实例中不含 ""applicationStartup"" + AbstractApplicationContext -> DefaultListableBeanFactory: ""containsLocalBean""\n是否包含 ""applicationStartup"" + activate DefaultListableBeanFactory + ||| + AbstractApplicationContext <- DefaultListableBeanFactory + deactivate DefaultListableBeanFactory + + ||| + + AbstractApplicationContext -> DefaultListableBeanFactory: ""registerSingleton""\n注册 Bean + activate DefaultListableBeanFactory + note over AbstractApplicationContext,DefaultListableBeanFactory: ""applicationStartup"" // 此为 Bean 名称 + AbstractApplicationContext <- DefaultListableBeanFactory + deactivate DefaultListableBeanFactory + end + + AbstractApplicationContext -> AbstractApplicationContext + deactivate AbstractApplicationContext + +Actor <- AbstractApplicationContext: 完成容器初始化 +deactivate AbstractApplicationContext + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml diff --git a/truman/src/docs/asciidoc/puml/AbstractApplicationContext-refresh.puml b/truman/src/docs/asciidoc/puml/AbstractApplicationContext-refresh.puml new file mode 100644 index 000000000000..3231a0336f78 --- /dev/null +++ b/truman/src/docs/asciidoc/puml/AbstractApplicationContext-refresh.puml @@ -0,0 +1,91 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **""AbstractApplicationContext.refresh"" -- 重塑容器** + + +actor Actor +participant AbstractApplicationContext << (C,#ADD1B2) >> +participant BeanFactoryPostProcessor << (I,#AB9DE1) >> + +Actor -> AbstractApplicationContext: ""refresh""\n重塑容器 +activate AbstractApplicationContext + + AbstractApplicationContext -> AbstractApplicationContext: ""prepareRefresh""\n准备环境 + + ||| + + AbstractApplicationContext -> AbstractApplicationContext: ""obtainFreshBeanFactory""\n获取BeanFactory + note right: 加载并解析配置文件 + + ||| + + AbstractApplicationContext -> AbstractApplicationContext: ""prepareBeanFactory""\n准备 ""BeanFactory"" + note right: 忽略各种 Aware 接口的注入;同时,\n注册一些必要的 Bean,比如 ""BeanFactory""。 + + ||| + + AbstractApplicationContext -> AbstractApplicationContext: ""postProcessBeanFactory""\n对 ""BeanFactory"" 做后期处理 + note right: 预留扩展点,\n目前是空实现 + + ||| + + AbstractApplicationContext -> AbstractApplicationContext: ""invokeBeanFactoryPostProcessors""\n调用 ""BeanFactoryPostProcessor"" 的 \n""postProcessBeanFactory"" 方法 + activate AbstractApplicationContext #53E516 + + loop **遍历所有 ""BeanFactoryPostProcessor"" 实例** + AbstractApplicationContext -> BeanFactoryPostProcessor: ""postProcessBeanFactory""\n更新容器内 Bean 的定义(增加、删除、修改) + activate BeanFactoryPostProcessor + note left #FFAAAA: ""BeanFactoryPostProcessor""\n是 Spring 容器非常重要的扩展点!\n\n很多第三方框架集成 Spring 就是\n用的该扩展点,比如 MyBATIS。 + + ||| + + AbstractApplicationContext <- BeanFactoryPostProcessor: 完成 Bean 定义的更新 + deactivate BeanFactoryPostProcessor + ||| + end + + AbstractApplicationContext -> AbstractApplicationContext + deactivate AbstractApplicationContext + + ||| + + AbstractApplicationContext -> AbstractApplicationContext: ""registerBeanPostProcessors""\n注册 ""BeanPostProcessor"" + note right #FFAAAA: ""BeanPostProcessor"" \n是 Spring 非常重要的扩展点!\n\n依赖注入,AOP切面生成等就是\n通过不同 ""BeanPostProcessor""\n实例来完成的。 + + ||| + + AbstractApplicationContext -> AbstractApplicationContext: ""initMessageSource""\n初始化 ""MessageSource"" + note right: 国际化相关 + + ||| + + AbstractApplicationContext -> AbstractApplicationContext: ""initApplicationEventMulticaster""\n初始化应用事件广播器 + + ||| + + AbstractApplicationContext -> AbstractApplicationContext: ""onRefresh"" + note right: 在 Web 容器中,注册\n""DispatchServlet"" 等九大组件 + + ||| + + AbstractApplicationContext -> AbstractApplicationContext: ""registerListeners""\n注册事件监听器 + note right: 将 ""ApplicationListener"" 类型的 Bean 注册\n到 ""ApplicationEventMulticaster"" 中,\n后续容器启动完成事件即由此广播并处理。 + + ||| + + AbstractApplicationContext -> AbstractApplicationContext: ""finishBeanFactoryInitialization""\n完成预初始化 + note right: 完成非懒加载的\n单例 Bean 实例化 + + ||| + + AbstractApplicationContext -> AbstractApplicationContext: ""finishRefresh""\n完成重塑 + note right: 删除一些不必要的配置信息。\n最后,广播容器启动完成的消息。 + + ||| + +Actor <- AbstractApplicationContext: 完成容器初始化 +deactivate AbstractApplicationContext + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml diff --git a/truman/src/docs/asciidoc/puml/AbstractApplicationContext-registerBeanPostProcessors.puml b/truman/src/docs/asciidoc/puml/AbstractApplicationContext-registerBeanPostProcessors.puml new file mode 100644 index 000000000000..00520b7fa9a6 --- /dev/null +++ b/truman/src/docs/asciidoc/puml/AbstractApplicationContext-registerBeanPostProcessors.puml @@ -0,0 +1,197 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **AbstractApplicationContext.registerBeanPostProcessors -- 注册 ""BeanPostProcessor""** + + +actor Actor +participant AbstractApplicationContext << (C,#ADD1B2) >> +participant PostProcessorRegistrationDelegate << (C,#ADD1B2) >> +participant ConfigurableListableBeanFactory << (I,#AB9DE1) >> +note over ConfigurableListableBeanFactory: 为了简化,所有 ""BeanFactory"" \n 相关操作,都指向该类。 +participant List << (I,#AB9DE1) >> + +Actor -> AbstractApplicationContext: ""refresh""\n重塑容器 +activate AbstractApplicationContext + ||| + + AbstractApplicationContext -> AbstractApplicationContext: ""registerBeanPostProcessors""\n注册 ""BeanPostProcessor"" + activate AbstractApplicationContext + AbstractApplicationContext -> PostProcessorRegistrationDelegate: ""registerBeanPostProcessors""\n处理 ""BeanPostProcessor"" 注册 + activate PostProcessorRegistrationDelegate + + PostProcessorRegistrationDelegate -> ConfigurableListableBeanFactory: ""getBeanNamesForType""\n获取类型为 ""BeanPostProcessor"" 的 Bean 名称 + activate ConfigurableListableBeanFactory + ||| + PostProcessorRegistrationDelegate <- ConfigurableListableBeanFactory + deactivate ConfigurableListableBeanFactory + + ||| + + PostProcessorRegistrationDelegate -> ConfigurableListableBeanFactory: ""addBeanPostProcessor""\n添加 ""BeanPostProcessor"" + activate ConfigurableListableBeanFactory + note over PostProcessorRegistrationDelegate, ConfigurableListableBeanFactory: 添加类型为 ""BeanPostProcessorChecker"" 的 ""BeanPostProcessor"" + PostProcessorRegistrationDelegate <- ConfigurableListableBeanFactory + deactivate ConfigurableListableBeanFactory + + ||| + + loop 第一步获取的 ""postProcessorNames"" + alt #Fuchsia 类型匹配 ""PriorityOrdered"" + PostProcessorRegistrationDelegate -> List: ""add""\n添加元素 + activate List + note over PostProcessorRegistrationDelegate, List: 添加到 ""priorityOrderedPostProcessors"" 集合中 + PostProcessorRegistrationDelegate <- List + deactivate List + ||| + alt 如果是 ""MergedBeanDefinitionPostProcessor"" 类型 + PostProcessorRegistrationDelegate -> List: ""add""\n添加元素 + activate List + note over PostProcessorRegistrationDelegate, List: 再添加到 ""internalPostProcessors"" 集合中 + PostProcessorRegistrationDelegate <- List + deactivate List + ||| + end + ||| + else #Yellow 类型匹配 ""Ordered"" + PostProcessorRegistrationDelegate -> List: ""add""\n添加元素 + activate List + note over PostProcessorRegistrationDelegate, List: 添加到 ""orderedPostProcessorNames"" 集合中 + PostProcessorRegistrationDelegate <- List + deactivate List + ||| + else #DodgerBlue + PostProcessorRegistrationDelegate -> List: ""add""\n添加元素 + activate List + note over PostProcessorRegistrationDelegate, List: 添加到 ""nonOrderedPostProcessorNames"" 集合中 + PostProcessorRegistrationDelegate <- List + deactivate List + ||| + end + end + + group #Fuchsia + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate: ""sortPostProcessors""\n排序 + activate PostProcessorRegistrationDelegate + note over PostProcessorRegistrationDelegate,PostProcessorRegistrationDelegate: 对 ""priorityOrderedPostProcessors"" 进行排序 + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate + deactivate PostProcessorRegistrationDelegate + + ||| + + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate: ""registerBeanPostProcessors""\n注册 ""BeanPostProcessor"" + activate PostProcessorRegistrationDelegate + PostProcessorRegistrationDelegate -> ConfigurableListableBeanFactory: ""addBeanPostProcessors""\n添加 ""BeanPostProcessor"" + activate ConfigurableListableBeanFactory + note over PostProcessorRegistrationDelegate, ConfigurableListableBeanFactory: 注册 ""priorityOrderedPostProcessors"" 集合中的 ""BeanPostProcessor"" + PostProcessorRegistrationDelegate <- ConfigurableListableBeanFactory + deactivate ConfigurableListableBeanFactory + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate + deactivate PostProcessorRegistrationDelegate + ||| + end + + ||| + + group #Yellow + loop 第一步获取的 ""orderedPostProcessorNames"" + PostProcessorRegistrationDelegate -> ConfigurableListableBeanFactory: ""getBean""\n获取指定名称的 Bean + activate ConfigurableListableBeanFactory + note over PostProcessorRegistrationDelegate, ConfigurableListableBeanFactory: 获取 ""orderedPostProcessorNames"" 集合中的 ""BeanPostProcessor"" + PostProcessorRegistrationDelegate <- ConfigurableListableBeanFactory + deactivate ConfigurableListableBeanFactory + end + + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate: ""sortPostProcessors""\n排序 + activate PostProcessorRegistrationDelegate + note over PostProcessorRegistrationDelegate,PostProcessorRegistrationDelegate: 对 ""orderedPostProcessorNames"" 进行排序 + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate + deactivate PostProcessorRegistrationDelegate + + ||| + + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate: ""registerBeanPostProcessors""\n注册 ""BeanPostProcessor"" + activate PostProcessorRegistrationDelegate + PostProcessorRegistrationDelegate -> ConfigurableListableBeanFactory: ""addBeanPostProcessors""\n添加 ""BeanPostProcessor"" + activate ConfigurableListableBeanFactory + note over PostProcessorRegistrationDelegate, ConfigurableListableBeanFactory: 注册 ""orderedPostProcessorNames"" 集合中的 ""BeanPostProcessor"" + PostProcessorRegistrationDelegate <- ConfigurableListableBeanFactory + deactivate ConfigurableListableBeanFactory + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate + deactivate PostProcessorRegistrationDelegate + ||| + end + + ||| + + group #DodgerBlue + loop 第一步获取的 ""nonOrderedPostProcessorNames"" + PostProcessorRegistrationDelegate -> ConfigurableListableBeanFactory: ""getBean""\n获取指定名称的 Bean + activate ConfigurableListableBeanFactory + note over PostProcessorRegistrationDelegate, ConfigurableListableBeanFactory: 获取 ""nonOrderedPostProcessorNames"" 集合中的 ""BeanPostProcessor"" + PostProcessorRegistrationDelegate <- ConfigurableListableBeanFactory + deactivate ConfigurableListableBeanFactory + ||| + alt 如果是 ""MergedBeanDefinitionPostProcessor"" 类型 + PostProcessorRegistrationDelegate -> List: ""add""\n添加元素 + activate List + note over PostProcessorRegistrationDelegate, List: 再添加到 ""internalPostProcessors"" 集合中 + PostProcessorRegistrationDelegate <- List + deactivate List + ||| + end + end + + ||| + + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate: ""registerBeanPostProcessors""\n注册 ""BeanPostProcessor"" + activate PostProcessorRegistrationDelegate + PostProcessorRegistrationDelegate -> ConfigurableListableBeanFactory: ""addBeanPostProcessors""\n添加 ""BeanPostProcessor"" + activate ConfigurableListableBeanFactory + note over PostProcessorRegistrationDelegate, ConfigurableListableBeanFactory: 注册 ""orderedPostProcessorNames"" 集合中的 ""BeanPostProcessor"" + PostProcessorRegistrationDelegate <- ConfigurableListableBeanFactory + deactivate ConfigurableListableBeanFactory + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate + deactivate PostProcessorRegistrationDelegate + ||| + end + + ||| + + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate: ""sortPostProcessors""\n排序 + activate PostProcessorRegistrationDelegate + note over PostProcessorRegistrationDelegate,PostProcessorRegistrationDelegate: 对 ""internalPostProcessors"" 进行排序 + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate + deactivate PostProcessorRegistrationDelegate + + ||| + + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate: ""registerBeanPostProcessors""\n注册 ""BeanPostProcessor"" + activate PostProcessorRegistrationDelegate + PostProcessorRegistrationDelegate -> ConfigurableListableBeanFactory: ""addBeanPostProcessors""\n添加 ""BeanPostProcessor"" + activate ConfigurableListableBeanFactory + note over PostProcessorRegistrationDelegate, ConfigurableListableBeanFactory: 注册 ""internalPostProcessors"" 集合中的 ""BeanPostProcessor""\n这样处理后,这些 ""BeanPostProcessor"" 就在列表的最后,也就是最后生效。 + PostProcessorRegistrationDelegate <- ConfigurableListableBeanFactory + deactivate ConfigurableListableBeanFactory + PostProcessorRegistrationDelegate -> PostProcessorRegistrationDelegate + deactivate PostProcessorRegistrationDelegate + ||| + + PostProcessorRegistrationDelegate -> ConfigurableListableBeanFactory: ""addBeanPostProcessor""\n添加 ""BeanPostProcessor"" + activate ConfigurableListableBeanFactory + note over PostProcessorRegistrationDelegate, ConfigurableListableBeanFactory: 添加类型为 ""ApplicationListenerDetector"" 的 ""BeanPostProcessor"" + PostProcessorRegistrationDelegate <- ConfigurableListableBeanFactory + deactivate ConfigurableListableBeanFactory + ||| + AbstractApplicationContext <- PostProcessorRegistrationDelegate + deactivate PostProcessorRegistrationDelegate + AbstractApplicationContext -> AbstractApplicationContext + deactivate AbstractApplicationContext + + ||| + +Actor <- AbstractApplicationContext: 完成容器初始化 +deactivate AbstractApplicationContext + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml diff --git a/truman/src/docs/asciidoc/puml/AnnotationAwareAspectJAutoProxyCreator.puml b/truman/src/docs/asciidoc/puml/AnnotationAwareAspectJAutoProxyCreator.puml new file mode 100644 index 000000000000..01401ba37fe5 --- /dev/null +++ b/truman/src/docs/asciidoc/puml/AnnotationAwareAspectJAutoProxyCreator.puml @@ -0,0 +1,59 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **AnnotationAwareAspectJAutoProxyCreator 继承体系及关键方法** + +interface BeanPostProcessor { + + postProcessBeforeInitialization(Object bean, String beanName): Object + + postProcessAfterInitialization(Object bean, String beanName): Object +} + +interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor { + + postProcessBeforeInstantiation(Class, String beanName): Object + + postProcessAfterInstantiation(Object bean, String beanName): boolean + + postProcessProperties(PropertyValues, Object bean, String beanName): PropertyValues + + postProcessPropertyValues(PropertyValues, PropertyDescriptor[], Object bean, String beanName): PropertyValues +} + +interface BeanFactoryAware { + + setBeanFactory(BeanFactory): void +} +interface BeanClassLoaderAware { + + setBeanClassLoader(ClassLoader): void +} + +interface SmartInstantiationAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessor { + + predictBeanType(Class beanClass, String beanName): Class + + determineCandidateConstructors(Class beanClass, String beanName): Constructor[] + + getEarlyBeanReference(Object bean, String beanName): Object +} + +class ProxyConfig { + + setProxyTargetClass(boolean): void + + isProxyTargetClass(): boolean + + setOptimize(boolean): void + + isOptimize(): boolean + + setOpaque(boolean): void + + isOpaque(): boolean + + setExposeProxy(boolean): void + + isExposeProxy(): boolean + + setFrozen(boolean): void + + isFrozen(): boolean +} + +class ProxyProcessorSupport extends ProxyConfig implements BeanClassLoaderAware, AopInfrastructureBean + +abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware + +class BeanNameAutoProxyCreator extends AbstractAutoProxyCreator + +abstract class AbstractAdvisorAutoProxyCreator extends AbstractAutoProxyCreator + +class AspectJAwareAdvisorAutoProxyCreator extends AbstractAdvisorAutoProxyCreator + +class AnnotationAwareAspectJAutoProxyCreator extends AspectJAwareAdvisorAutoProxyCreator + +class DefaultAdvisorAutoProxyCreator extends AbstractAdvisorAutoProxyCreator implements BeanNameAware + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml \ No newline at end of file diff --git a/truman/src/docs/asciidoc/puml/AopProxy.puml b/truman/src/docs/asciidoc/puml/AopProxy.puml new file mode 100644 index 000000000000..ab14a14ed273 --- /dev/null +++ b/truman/src/docs/asciidoc/puml/AopProxy.puml @@ -0,0 +1,32 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **AopProxy 相关结构图** + +interface AopProxy { + + getProxy(): Object + + getProxy(classLoader: ClassLoader): Object +} + +class CglibAopProxy implements AopProxy +class ObjenesisCglibAopProxy extends CglibAopProxy + +interface AopProxyFactory { + createAopProxy(config: AdvisedSupport): AopProxy +} + +AopProxyFactory o-right-> AopProxy :create + +class AdvisedSupport extends ProxyConfig + +AopProxyFactory .up.> AdvisedSupport :according to + +class DefaultAopProxyFactory implements AopProxyFactory + +class JdkDynamicAopProxy implements AopProxy + +interface InvocationHandler +JdkDynamicAopProxy ..|> InvocationHandler + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml \ No newline at end of file diff --git a/truman/src/docs/asciidoc/puml/ApplicationContext.puml b/truman/src/docs/asciidoc/puml/ApplicationContext.puml new file mode 100644 index 000000000000..bd628e8769a5 --- /dev/null +++ b/truman/src/docs/asciidoc/puml/ApplicationContext.puml @@ -0,0 +1,20 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **""ApplicationContext""的组成** +card ApplicationContext { + card AnnotatedBeanDefinitionReader + card ClassPathBeanDefinitionScanner + card DefaultListableBeanFactory { + card dependencyComparator + card autowireCandidateResolver + } +} + +AnnotatedBeanDefinitionReader -[hidden]-> ClassPathBeanDefinitionScanner +ClassPathBeanDefinitionScanner -[hidden]-> DefaultListableBeanFactory + +dependencyComparator -[hidden]-> autowireCandidateResolver + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml diff --git a/truman/src/docs/asciidoc/puml/ApplicationEvent.puml b/truman/src/docs/asciidoc/puml/ApplicationEvent.puml new file mode 100644 index 000000000000..42a09d15b7f5 --- /dev/null +++ b/truman/src/docs/asciidoc/puml/ApplicationEvent.puml @@ -0,0 +1,44 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **Spring 容器内事件发布实现类图** + + +abstract class ApplicationEvent extends EventObject +note top of EventObject : JDK 内置事件类型 +note top of ApplicationEvent : Spring 容器内\n自定义事件类型 + +interface ApplicationListener extends EventListener { + + onApplicationEvent(E event):void +} + +interface ApplicationEventPublisher { + + publishEvent(ApplicationEvent event):void + + publishEvent(Object event):void +} +interface ApplicationContext extends ApplicationEventPublisher + +interface ConfigurableApplicationContext extends ApplicationContext + +abstract class AbstractApplicationContext implements ConfigurableApplicationContext { + - applicationEventMulticaster:ApplicationEventMulticaster +} + +interface ApplicationEventMulticaster + +ApplicationEventMulticaster .right.> ApplicationListener : notify + +ApplicationEventMulticaster .up.> ApplicationEvent : publish + +ApplicationListener .up.> ApplicationEvent : receive + +abstract class AbstractApplicationEventMulticaster implements ApplicationEventMulticaster + +class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster { + - taskExecutor:Executor +} + +AbstractApplicationContext *--> ApplicationEventMulticaster + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml \ No newline at end of file diff --git a/truman/src/docs/asciidoc/puml/BeanDefinition.puml b/truman/src/docs/asciidoc/puml/BeanDefinition.puml new file mode 100644 index 000000000000..25c53ee70d40 --- /dev/null +++ b/truman/src/docs/asciidoc/puml/BeanDefinition.puml @@ -0,0 +1,67 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **BeanDefinition 继承体系及关键属性** + + +abstract class AttributeAccessorSupport implements AttributeAccessor, Serializable { + - attributes: Map +} + +class BeanMetadataAttributeAccessor extends AttributeAccessorSupport implements BeanMetadataElement { + - source: Object +} + +interface BeanDefinition extends AttributeAccessor, BeanMetadataElement + +abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor implements BeanDefinition, Cloneable { + - beanClass: Object + - abstractFlag: boolean + - lazyInit: Boolean + - autowireMode: int + - dependencyCheck: int + - dependsOn: String[] + - autowireCandidate: boolean + - primary: boolean + - qualifiers: Map + - instanceSupplier: Supplier + - nonPublicAccessAllowed: boolean + - lenientConstructorResolution: boolean + - factoryBeanName: String + - factoryMethodName: String + - constructorArgumentValues: ConstructorArgumentValues + - propertyValues: MutablePropertyValues + - methodOverrides: MethodOverrides + - initMethodName: String + - destroyMethodName: String + - enforceInitMethod: boolean + - enforceDestroyMethod: boolean + - synthetic: boolean + - role: int + - description: String + - resource: Resource +} + +class RootBeanDefinition extends AbstractBeanDefinition + +interface AnnotatedBeanDefinition extends BeanDefinition + +class ChildBeanDefinition extends AbstractBeanDefinition { + - parentName: String +} + +class GenericBeanDefinition extends AbstractBeanDefinition { + - parentName: String +} + +class ScannedGenericBeanDefinition extends GenericBeanDefinition implements AnnotatedBeanDefinition { + - metadata: AnnotationMetadata +} + +class AnnotatedGenericBeanDefinition extends GenericBeanDefinition implements AnnotatedBeanDefinition { + - metadata: AnnotationMetadata + - factoryMethodMetadata: MethodMetadata +} + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml diff --git a/truman/src/docs/asciidoc/puml/BeanFactory-getBean.puml b/truman/src/docs/asciidoc/puml/BeanFactory-getBean.puml new file mode 100644 index 000000000000..9e3a63e1bcf7 --- /dev/null +++ b/truman/src/docs/asciidoc/puml/BeanFactory-getBean.puml @@ -0,0 +1,23 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **BeanFactory.getBean Bean 创建** + +actor Actor +participant BeanFactory << (C,#ADD1B2) >> +participant BeanPostProcessor << (I,#AB9DE1) >> + +Actor -> BeanFactory: getBean +activate BeanFactory + + BeanFactory -> BeanPostProcessor + activate BeanPostProcessor + + BeanFactory <- BeanPostProcessor + deactivate BeanPostProcessor + +Actor -> BeanFactory +deactivate BeanFactory + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml diff --git a/truman/src/docs/asciidoc/puml/BeanFactory.puml b/truman/src/docs/asciidoc/puml/BeanFactory.puml new file mode 100644 index 000000000000..0cf0c77e31aa --- /dev/null +++ b/truman/src/docs/asciidoc/puml/BeanFactory.puml @@ -0,0 +1,96 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **BeanFactory 继承体系及关键属性** + +interface BeanDefinitionRegistry extends AliasRegistry + +class SimpleAliasRegistry implements AliasRegistry { + - aliasMap: Map +} + +class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry { + - singletonObjects: Map + - singletonFactories: Map> + - earlySingletonObjects: Map + - registeredSingletons: Set + - singletonsCurrentlyInCreation: Set + - inCreationCheckExclusions: Set + - suppressedExceptions: Set + - singletonsCurrentlyInDestruction: boolean + - disposableBeans: Map + - containedBeanMap: Map> + - dependentBeanMap: Map> + - dependenciesForBeanMap: Map> +} + +abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanRegistry { + - factoryBeanObjectCache: Map +} + +interface ListableBeanFactory extends BeanFactory + +'StaticListableBeanFactory 不常用,不予展示 +'class StaticListableBeanFactory implements ListableBeanFactory + +interface HierarchicalBeanFactory extends BeanFactory + +interface ApplicationContext extends ListableBeanFactory, HierarchicalBeanFactory + +interface ConfigurableBeanFactory extends HierarchicalBeanFactory, SingletonBeanRegistry + +interface AutowireCapableBeanFactory extends BeanFactory + +interface ConfigurableListableBeanFactory extends ListableBeanFactory, AutowireCapableBeanFactory, ConfigurableBeanFactory + +abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory { + - parentBeanFactory: BeanFactory + - beanClassLoader: ClassLoader + - tempClassLoader: ClassLoader + - beanExpressionResolver: BeanExpressionResolver + - conversionService: ConversionService + - propertyEditorRegistrars: Set + - customEditors: Map, Class> + - typeConverter: TypeConverter + - embeddedValueResolvers: List + - beanPostProcessors: List + - beanPostProcessorCache: BeanPostProcessorCache + - scopes: Map + - mergedBeanDefinitions: Map + - alreadyCreated: Set + - prototypesCurrentlyInCreation: ThreadLocal +} + +abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory { + - instantiationStrategy: InstantiationStrategy + - parameterNameDiscoverer: ParameterNameDiscoverer + - allowCircularReferences: boolean + - allowRawInjectionDespiteWrapping: boolean + - ignoredDependencyTypes: Set> + - ignoredDependencyInterfaces: Set> + - currentlyCreatedBean: NamedThreadLocal + - factoryBeanInstanceCache: ConcurrentMap + - factoryMethodCandidateCache: ConcurrentMap, Method[]> + - filteredPropertyDescriptorsCache: ConcurrentMap, PropertyDescriptor[]> +} + +class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements ConfigurableListableBeanFactory, BeanDefinitionRegistry { + - {static} serializableFactories: Map> + - allowBeanDefinitionOverriding: boolean + - allowEagerClassLoading: boolean + - dependencyComparator: Comparator + - autowireCandidateResolver: AutowireCandidateResolver + - resolvableDependencies: Map, Object> + - beanDefinitionMap: Map + - mergedBeanDefinitionHolders: Map + - allBeanNamesByType: Map, String[]> + - singletonBeanNamesByType: Map, String[]> + - beanDefinitionNames: List + - manualSingletonNames: Set + - frozenBeanDefinitionNames: String[] + - configurationFrozen: boolean +} + + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml diff --git a/truman/src/docs/asciidoc/puml/ConfigurationClassPostProcessor.puml b/truman/src/docs/asciidoc/puml/ConfigurationClassPostProcessor.puml new file mode 100644 index 000000000000..baf7c66d298e --- /dev/null +++ b/truman/src/docs/asciidoc/puml/ConfigurationClassPostProcessor.puml @@ -0,0 +1,18 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title "**BeanFactoryPostProcessor 继承体系**" + +interface BeanFactoryPostProcessor { + + postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory): void +} + +interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor { + + postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry): void +} + +class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor + + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml \ No newline at end of file diff --git a/truman/src/docs/asciidoc/puml/DispatchServlet-init-sequence.puml b/truman/src/docs/asciidoc/puml/DispatchServlet-init-sequence.puml new file mode 100644 index 000000000000..9509ab2ab749 --- /dev/null +++ b/truman/src/docs/asciidoc/puml/DispatchServlet-init-sequence.puml @@ -0,0 +1,116 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **SpringMVC 容器初始化时序图** + +participant Actor +Actor -> HttpServletBean : init +activate HttpServletBean +HttpServletBean -> HttpServletBean : initBeanWrapper +activate HttpServletBean +HttpServletBean --> HttpServletBean +deactivate HttpServletBean +HttpServletBean -> HttpServletBean : initServletBean +activate HttpServletBean +HttpServletBean -> FrameworkServlet : initServletBean +activate FrameworkServlet +FrameworkServlet -> FrameworkServlet : initWebApplicationContext +activate FrameworkServlet +FrameworkServlet -> WebApplicationContextUtils : getWebApplicationContext +activate WebApplicationContextUtils +WebApplicationContextUtils --> FrameworkServlet +deactivate WebApplicationContextUtils + +FrameworkServlet -> FrameworkServlet : findWebApplicationContext +activate FrameworkServlet +FrameworkServlet --> FrameworkServlet +deactivate FrameworkServlet +FrameworkServlet -> FrameworkServlet : createWebApplicationContext +activate FrameworkServlet +FrameworkServlet -> FrameworkServlet : createWebApplicationContext +activate FrameworkServlet +FrameworkServlet -> FrameworkServlet : configureAndRefreshWebApplicationContext +activate FrameworkServlet +FrameworkServlet -> FrameworkServlet : postProcessWebApplicationContext +activate FrameworkServlet +FrameworkServlet --> FrameworkServlet +deactivate FrameworkServlet +FrameworkServlet -> ConfigurableApplicationContext : refresh +activate ConfigurableApplicationContext +ConfigurableApplicationContext --> FrameworkServlet +deactivate ConfigurableApplicationContext +FrameworkServlet --> FrameworkServlet +deactivate FrameworkServlet +FrameworkServlet --> FrameworkServlet +deactivate FrameworkServlet +FrameworkServlet --> FrameworkServlet +deactivate FrameworkServlet +FrameworkServlet -> FrameworkServlet : onRefresh +activate FrameworkServlet + +FrameworkServlet -> DispatcherServlet : onRefresh +activate DispatcherServlet +DispatcherServlet -> DispatcherServlet : initStrategies +activate DispatcherServlet #89FA4F +DispatcherServlet -> DispatcherServlet : initMultipartResolver +activate DispatcherServlet +DispatcherServlet --> DispatcherServlet +deactivate DispatcherServlet +DispatcherServlet -> DispatcherServlet : initLocaleResolver +activate DispatcherServlet +DispatcherServlet --> DispatcherServlet +deactivate DispatcherServlet +DispatcherServlet -> DispatcherServlet : initThemeResolver +activate DispatcherServlet +DispatcherServlet --> DispatcherServlet +deactivate DispatcherServlet +DispatcherServlet -> DispatcherServlet : initHandlerMappings +activate DispatcherServlet +DispatcherServlet -> HandlerMapping : usesPathPatterns +activate HandlerMapping +HandlerMapping --> DispatcherServlet +deactivate HandlerMapping +DispatcherServlet --> DispatcherServlet +deactivate DispatcherServlet +DispatcherServlet -> DispatcherServlet : initHandlerAdapters +activate DispatcherServlet +DispatcherServlet --> DispatcherServlet +deactivate DispatcherServlet +DispatcherServlet -> DispatcherServlet : initHandlerExceptionResolvers +activate DispatcherServlet +DispatcherServlet --> DispatcherServlet +deactivate DispatcherServlet +DispatcherServlet -> DispatcherServlet : initRequestToViewNameTranslator +activate DispatcherServlet +DispatcherServlet --> DispatcherServlet +deactivate DispatcherServlet +DispatcherServlet -> DispatcherServlet : initViewResolvers +activate DispatcherServlet +DispatcherServlet --> DispatcherServlet +deactivate DispatcherServlet +DispatcherServlet -> DispatcherServlet : initFlashMapManager +activate DispatcherServlet +DispatcherServlet --> DispatcherServlet +deactivate DispatcherServlet +DispatcherServlet --> DispatcherServlet +deactivate DispatcherServlet +DispatcherServlet --> FrameworkServlet +deactivate DispatcherServlet + +FrameworkServlet --> FrameworkServlet +deactivate FrameworkServlet +FrameworkServlet --> FrameworkServlet +deactivate FrameworkServlet +FrameworkServlet -> FrameworkServlet : initFrameworkServlet +activate FrameworkServlet +FrameworkServlet --> FrameworkServlet +deactivate FrameworkServlet +FrameworkServlet --> HttpServletBean +deactivate FrameworkServlet +HttpServletBean --> HttpServletBean +deactivate HttpServletBean +return + + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml \ No newline at end of file diff --git a/truman/src/docs/asciidoc/puml/DispatcherServlet-init.puml b/truman/src/docs/asciidoc/puml/DispatcherServlet-init.puml new file mode 100644 index 000000000000..f9ab5ac9f5e4 --- /dev/null +++ b/truman/src/docs/asciidoc/puml/DispatcherServlet-init.puml @@ -0,0 +1,44 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **Spring Web 初始化流程** + +actor Actor +participant DispatcherServlet << (C,#ADD1B2) >> +participant FrameworkServlet << (A,#AADCDF) >> + +Actor -> DispatcherServlet: ""init""\n初始化 +note right: 该 ""init"" 方法是从 ""HttpServletBean"" 继承的\n读取 """" +activate DispatcherServlet + + DispatcherServlet -> FrameworkServlet: ""initServletBean"" + activate FrameworkServlet + + FrameworkServlet -> FrameworkServlet: ""initWebApplicationContext""\n初始化 Spring 容器\n先获取父容器,再初始化 Web 容器 + activate FrameworkServlet + + FrameworkServlet -> DispatcherServlet: ""onRefresh""\n初始化 Spring Web + activate DispatcherServlet + + ||| + + DispatcherServlet -> DispatcherServlet: ""initStrategies""\n初始化 Spring Web 的九大组件 + activate DispatcherServlet + ||| + DispatcherServlet -> DispatcherServlet + deactivate DispatcherServlet + + FrameworkServlet <- DispatcherServlet + deactivate DispatcherServlet + + FrameworkServlet -> FrameworkServlet + deactivate FrameworkServlet + + DispatcherServlet <- FrameworkServlet + deactivate FrameworkServlet + +Actor <- DispatcherServlet: 完成初始化 +deactivate DispatcherServlet + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml diff --git a/truman/src/docs/asciidoc/puml/DispatcherServlet.puml b/truman/src/docs/asciidoc/puml/DispatcherServlet.puml new file mode 100644 index 000000000000..586c69d732cb --- /dev/null +++ b/truman/src/docs/asciidoc/puml/DispatcherServlet.puml @@ -0,0 +1,42 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **DispatcherServlet 继承结构** + + +abstract class GenericServlet implements Servlet, ServletConfig, java.io.Serializable + +abstract class HttpServlet extends GenericServlet + +abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware { + + init(): void +} +note right of HttpServletBean : 通过 init() 方法\n开始启动容器 + +abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware { + + initServletBean(): void + + initWebApplicationContext(): WebApplicationContext + + configureAndRefreshWebApplicationContext(wac: ConfigurableWebApplicationContext): void + + onRefresh(context: ApplicationContext): void +} +note right of FrameworkServlet : 在 initServletBean() 中\n真正完成初始化容器工作 + +class DispatcherServlet extends FrameworkServlet { + __ + + initStrategies(context: ApplicationContext): void + .. 九大组件 .. + + initMultipartResolver(context: ApplicationContext): void + + initLocaleResolver(context: ApplicationContext): void + + initThemeResolver(context: ApplicationContext): void + + initHandlerMappings(context: ApplicationContext): void + + initHandlerAdapters(context: ApplicationContext): void + + initHandlerExceptionResolvers(context: ApplicationContext): void + + initRequestToViewNameTranslator(context: ApplicationContext): void + + initViewResolvers(context: ApplicationContext): void + + initFlashMapManager(context: ApplicationContext): void +} +note right of DispatcherServlet : 通过事件触发 onRefresh 方法,然后调用\ninitStrategies 初始化 SpringMVC 的九大组件 + + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml \ No newline at end of file diff --git a/truman/src/docs/asciidoc/puml/MessageSource.puml b/truman/src/docs/asciidoc/puml/MessageSource.puml new file mode 100644 index 000000000000..9f6a564ff033 --- /dev/null +++ b/truman/src/docs/asciidoc/puml/MessageSource.puml @@ -0,0 +1,38 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **MessageSource 类层次结构** + + +interface HierarchicalMessageSource extends MessageSource + +interface ApplicationContext extends MessageSource + +interface ConfigurableApplicationContext extends ApplicationContext + +abstract class AbstractApplicationContext implements ConfigurableApplicationContext { + - messageSource:MessageSource +} + +abstract class AbstractMessageSource implements HierarchicalMessageSource + +class StaticMessageSource extends AbstractMessageSource +note bottom of StaticMessageSource : 多用于测试 + +abstract class AbstractResourceBasedMessageSource extends AbstractMessageSource + +class ResourceBundleMessageSource extends AbstractResourceBasedMessageSource + +note bottom of ResourceBundleMessageSource : 常用 + +class ReloadableResourceBundleMessageSource extends AbstractResourceBasedMessageSource implements ResourceLoaderAware + +class DelegatingMessageSource implements HierarchicalMessageSource + +note bottom of DelegatingMessageSource : Spring 内部使用该类,启动时\nAbstractApplicationContext\n.initMessageSource 注册该实例 + +AbstractApplicationContext *--> DelegatingMessageSource : has a > + + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml \ No newline at end of file diff --git a/truman/src/docs/asciidoc/puml/ProxyCreatorSupport.puml b/truman/src/docs/asciidoc/puml/ProxyCreatorSupport.puml new file mode 100644 index 000000000000..c81eb8972ff9 --- /dev/null +++ b/truman/src/docs/asciidoc/puml/ProxyCreatorSupport.puml @@ -0,0 +1,21 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **ProxyFactory 的"兄弟"** + + +interface Advised extends TargetClassAware + +class AdvisedSupport extends ProxyConfig implements Advised + +class ProxyCreatorSupport extends AdvisedSupport + +class ProxyFactory extends ProxyCreatorSupport + +class ProxyFactoryBean extends ProxyCreatorSupport implements FactoryBean + +class AspectJProxyFactory extends ProxyCreatorSupport + + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml \ No newline at end of file diff --git a/truman/src/docs/asciidoc/puml/ProxyFactory.puml b/truman/src/docs/asciidoc/puml/ProxyFactory.puml new file mode 100644 index 000000000000..d0a7b037b47f --- /dev/null +++ b/truman/src/docs/asciidoc/puml/ProxyFactory.puml @@ -0,0 +1,26 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **ProxyFactory 继承体系** + + +interface Advised extends TargetClassAware + +class AdvisedSupport extends ProxyConfig implements Advised + +class ProxyCreatorSupport extends AdvisedSupport + +class ProxyFactory extends ProxyCreatorSupport + +interface AopProxy { + + getProxy(): Object + + getProxy(classLoader: ClassLoader): Object +} + +ProxyFactory .right.> AopProxy + +note left of ProxyFactory : 初始化时,就创建一个 AopProxyFactory\n对象(即 DefaultAopProxyFactory 对象),\n然后通过该对象去创建 AopProxy。 + + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml \ No newline at end of file diff --git a/truman/src/docs/asciidoc/puml/Resource-ResourceLoader.puml b/truman/src/docs/asciidoc/puml/Resource-ResourceLoader.puml new file mode 100644 index 000000000000..363957074c3b --- /dev/null +++ b/truman/src/docs/asciidoc/puml/Resource-ResourceLoader.puml @@ -0,0 +1,39 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **Resource 与 ResourceLoader** + + +interface InputStreamSource { + + getInputStream():InputStream +} + +interface Resource extends InputStreamSource + +interface ResourceLoader { + +{static} String CLASSPATH_URL_PREFIX = "classpath:" + + + getResource(String location):Resource + + getClassLoader():ClassLoader +} + +note top of ResourceLoader : 加载单个 Resource + +class DefaultResourceLoader implements ResourceLoader +class FileSystemResourceLoader extends DefaultResourceLoader + +interface ResourcePatternResolver extends ResourceLoader { + +{static} CLASSPATH_ALL_URL_PREFIX = "classpath*:" + + + getResources(String locationPattern):Resource[] +} + +class PathMatchingResourcePatternResolver implements ResourcePatternResolver + +note top of ResourcePatternResolver : 可以批量加载\nResource + +ResourceLoader *- Resource + + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml \ No newline at end of file diff --git a/truman/src/docs/asciidoc/puml/ResourceLoader-ApplicationContext.puml b/truman/src/docs/asciidoc/puml/ResourceLoader-ApplicationContext.puml new file mode 100644 index 000000000000..cf34aa537338 --- /dev/null +++ b/truman/src/docs/asciidoc/puml/ResourceLoader-ApplicationContext.puml @@ -0,0 +1,31 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **ResourceLoader 与 ApplicationContext** + + +interface ResourceLoader + +note right of ResourceLoader : 加载单个 Resource + +class DefaultResourceLoader implements ResourceLoader + +interface ResourcePatternResolver extends ResourceLoader + +class PathMatchingResourcePatternResolver implements ResourcePatternResolver + +note right of ResourcePatternResolver : 可以批量加载\nResource + +interface ApplicationContext extends ResourcePatternResolver +interface ConfigurableApplicationContext extends ApplicationContext +abstract class AbstractApplicationContext implements ConfigurableApplicationContext + +abstract class AbstractApplicationContext extends DefaultResourceLoader { + - resourcePatternResolver:ResourcePatternResolver +} + +AbstractApplicationContext o-- PathMatchingResourcePatternResolver : has a > + + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml \ No newline at end of file diff --git a/truman/src/docs/asciidoc/puml/attr-colors.puml b/truman/src/docs/asciidoc/puml/attr-colors.puml new file mode 100644 index 000000000000..b0e061135f90 --- /dev/null +++ b/truman/src/docs/asciidoc/puml/attr-colors.puml @@ -0,0 +1,3 @@ +@startuml +colors +@enduml diff --git a/truman/src/docs/asciidoc/puml/attr-fonts.puml b/truman/src/docs/asciidoc/puml/attr-fonts.puml new file mode 100644 index 000000000000..42ee24df2cd9 --- /dev/null +++ b/truman/src/docs/asciidoc/puml/attr-fonts.puml @@ -0,0 +1,3 @@ +@startuml +listfonts 这 是 一 群 叠 字:犇 驫 骉 叒 鑫 森 淼 焱 垚 壵 厽 惢 掱 品 舙 瞐 晶 畾 磊 矗 尛 孨 众 毳 麤 鱻 猋 羴 㐂 龘 㸚 𠈌 㗊 㠭 𣊫 朤 𨰻 㵘 燚 㙓 +@enduml diff --git a/truman/src/docs/asciidoc/puml/build.sh b/truman/src/docs/asciidoc/puml/build.sh new file mode 100755 index 000000000000..ff68bbd7905e --- /dev/null +++ b/truman/src/docs/asciidoc/puml/build.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# +# 生成图片 +# + +# get base dir +baseDir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +echo "baseDir=${baseDir}" + +## 声明一个数字变量,可以带引号 +declare -a files=($(ls -l *.puml | awk '{print $9}')) + +# 获取数组长度 +arraylength=${#files[@]} +echo "arraylength=$arraylength" + +# 遍历数组,获取下标以及各个元素 +for (( i=1; i<${arraylength}+1; i++ )); +do + echo $i ": " ${files[$i-1]} +done + +read -p "请跟上面的数字选择需要构建的简历 [1] : " index +index=${index:-1} # 默认值 +echo "index=$index" + +if [[ "$index" -gt "$arraylength" || "$index" -lt "1" ]]; then + echo "输入错误,请重新开始!" + exit 0 +fi + +origin_file_name=${files[$index-1]} + +echo "start to convert ${baseDir}/${origin_file_name}" +plantuml -tsvg \ + -SdefaultFontSize=18 \ + -StitleFontSize=36 \ + -StitleFontName='Source Han Sans SC' \ + -SheaderFontSize=20 \ + -SheaderFontName='Source Han Serif SC' \ + -SfooterFontSize=20 \ + -SfooterFontColor='#e22d30' \ + -SfooterFontName='Source Han Sans SC' \ + -SnoteFontName='Source Han Serif SC' \ + -SdefaultMonospacedFontName='JetBrains Mono' \ + -v \ + "${baseDir}/${origin_file_name}" diff --git a/truman/src/docs/asciidoc/puml/org.aopalliance.aop.Advice.puml b/truman/src/docs/asciidoc/puml/org.aopalliance.aop.Advice.puml new file mode 100644 index 000000000000..50b2f9d7e1fc --- /dev/null +++ b/truman/src/docs/asciidoc/puml/org.aopalliance.aop.Advice.puml @@ -0,0 +1,56 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **Advice 继承体系** + + +interface Advice + +interface Interceptor extends Advice + +interface BeforeAdvice extends Advice + +interface DynamicIntroductionAdvice extends Advice + +abstract class AbstractAspectJAdvice implements Advice + +interface AfterAdvice extends Advice + +interface ConstructorInterceptor extends Interceptor { + + construct(ConstructorInvocation invocation): Object +} + +interface MethodInterceptor extends Interceptor { + + invoke(MethodInvocation invocation): Object +} + + + +interface IntroductionInterceptor extends MethodInterceptor, DynamicIntroductionAdvice + +interface MethodBeforeAdvice extends BeforeAdvice + +class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice + + +class DelegatingIntroductionInterceptor implements IntroductionInterceptor + +class AfterReturningAdviceInterceptor implements MethodInterceptor, AfterAdvice + +interface ThrowsAdvice extends AfterAdvice + +class AspectJAfterAdvice extends AbstractAspectJAdvice implements MethodInterceptor, AfterAdvice + +class AspectJAfterReturningAdvice extends AbstractAspectJAdvice implements AfterReturningAdvice, AfterAdvice + +class AspectJAfterThrowingAdvice extends AbstractAspectJAdvice implements MethodInterceptor, AfterAdvice + +class ThrowsAdviceInterceptor implements MethodInterceptor, AfterAdvice + +interface AfterReturningAdvice extends AfterAdvice + +class AspectJMethodBeforeAdvice extends AbstractAspectJAdvice implements MethodBeforeAdvice + + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml \ No newline at end of file diff --git a/truman/src/docs/asciidoc/puml/org.aopalliance.intercept.Joinpoint.puml b/truman/src/docs/asciidoc/puml/org.aopalliance.intercept.Joinpoint.puml new file mode 100644 index 000000000000..4923669dff70 --- /dev/null +++ b/truman/src/docs/asciidoc/puml/org.aopalliance.intercept.Joinpoint.puml @@ -0,0 +1,45 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **Joinpoint 继承体系** + + +interface Joinpoint { + + proceed(): Object + + getThis(): Object + + getStaticPart(): AccessibleObject +} + +interface Invocation extends Joinpoint { + + getArguments(): Object[] +} + +interface ConstructorInvocation extends Invocation { + + getConstructor(): Constructor +} + +interface MethodInvocation extends Invocation { + + getMethod(): Method +} + +interface ProxyMethodInvocation extends MethodInvocation { + + getProxy(): Object + + invocableClone(): MethodInvocation + + invocableClone(Object... arguments): MethodInvocation + + setArguments(Object... arguments): void + + setUserAttribute(String key, @Nullable Object value): void + + getUserAttribute(String key): Object +} + +class ReflectiveMethodInvocation implements ProxyMethodInvocation { + # proxy: Object + # target: Object + # method: Method + # interceptorsAndDynamicMethodMatchers: List + + + getUserAttributes(): Map +} + + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml \ No newline at end of file diff --git a/truman/src/docs/asciidoc/puml/org.springframework.aop.ClassFilter.puml b/truman/src/docs/asciidoc/puml/org.springframework.aop.ClassFilter.puml new file mode 100644 index 000000000000..78a47f3ae2de --- /dev/null +++ b/truman/src/docs/asciidoc/puml/org.springframework.aop.ClassFilter.puml @@ -0,0 +1,29 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **ClassFilter 继承体系** + +interface ClassFilter { + + TRUE: ClassFilter + + matches(Class clazz): boolean +} + +class DefaultIntroductionAdvisor +DefaultIntroductionAdvisor .right.|> ClassFilter + +class AspectJExpressionPointcut +AspectJExpressionPointcut .left.|> ClassFilter + +class AnnotationClassFilter implements ClassFilter + +class ControlFlowPointcut implements ClassFilter + +class TypePatternClassFilter implements ClassFilter + +class RootClassFilter implements ClassFilter + +class TrueClassFilter implements ClassFilter + + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml \ No newline at end of file diff --git a/truman/src/docs/asciidoc/puml/org.springframework.aop.MethodMatcher.puml b/truman/src/docs/asciidoc/puml/org.springframework.aop.MethodMatcher.puml new file mode 100644 index 000000000000..58e28f9a5204 --- /dev/null +++ b/truman/src/docs/asciidoc/puml/org.springframework.aop.MethodMatcher.puml @@ -0,0 +1,36 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **MethodMatcher 继承体系** + + +interface MethodMatcher { + + TRUE: MethodMatcher + + matches(Method method, Class targetClass): boolean + + isRuntime(): boolean + + matches(Method method, Class targetClass, Object... args): boolean +} + +class TrueMethodMatcher +TrueMethodMatcher .right.|> MethodMatcher + +interface Pointcut +Pointcut *.up. MethodMatcher + +abstract class DynamicMethodMatcher implements MethodMatcher + +abstract class DynamicMethodMatcherPointcut extends DynamicMethodMatcher implements Pointcut + +interface IntroductionAwareMethodMatcher extends MethodMatcher + +class AspectJExpressionPointcut implements IntroductionAwareMethodMatcher, Pointcut + +abstract class StaticMethodMatcher implements MethodMatcher + +class AnnotationMethodMatcher extends StaticMethodMatcher + +abstract class StaticMethodMatcherPointcut extends StaticMethodMatcher implements Pointcut + + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml \ No newline at end of file diff --git a/truman/src/docs/asciidoc/puml/org.springframework.aop.Pointcut.puml b/truman/src/docs/asciidoc/puml/org.springframework.aop.Pointcut.puml new file mode 100644 index 000000000000..85fc41144e8f --- /dev/null +++ b/truman/src/docs/asciidoc/puml/org.springframework.aop.Pointcut.puml @@ -0,0 +1,46 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **Pointcut 继承体系** + + +interface Pointcut { + + TRUE: Pointcut + + getClassFilter(): ClassFilter + + getMethodMatcher(): MethodMatcher +} + +Pointcut *-up- MethodMatcher +Pointcut *-up- ClassFilter + +abstract class DynamicMethodMatcherPointcut +DynamicMethodMatcherPointcut .right.|> Pointcut + +class AnnotationMatchingPointcut +AnnotationMatchingPointcut .left.|> Pointcut + +interface ExpressionPointcut extends Pointcut + +class ControlFlowPointcut implements Pointcut + +class ComposablePointcut implements Pointcut + +abstract class StaticMethodMatcherPointcut implements Pointcut + +class TruePointcut implements Pointcut + +abstract class AbstractExpressionPointcut implements ExpressionPointcut + +class AspectJExpressionPointcut extends AbstractExpressionPointcut + +abstract class StaticMethodMatcherPointcutAdvisor extends StaticMethodMatcherPointcut + +abstract class AbstractRegexpMethodPointcut extends StaticMethodMatcherPointcut + +class JdkRegexpMethodPointcut extends AbstractRegexpMethodPointcut + +class NameMatchMethodPointcut extends StaticMethodMatcherPointcut + + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml \ No newline at end of file diff --git a/truman/src/docs/asciidoc/puml/org.springframework.aop.PointcutAdvisor.puml b/truman/src/docs/asciidoc/puml/org.springframework.aop.PointcutAdvisor.puml new file mode 100644 index 000000000000..403accd250bd --- /dev/null +++ b/truman/src/docs/asciidoc/puml/org.springframework.aop.PointcutAdvisor.puml @@ -0,0 +1,50 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **PointcutAdvisor 继承体系** + + +interface Advisor { + + EMPTY_ADVICE: Advice + + + getAdvice(): Advice + +isPerInstance(): boolean +} + +interface PointcutAdvisor extends Advisor { + + getPointcut(): Pointcut +} + +interface IntroductionInfo { + + getInterfaces(): Class[] +} + +interface IntroductionAdvisor extends Advisor, IntroductionInfo { + + getClassFilter(): ClassFilter + + validateInterfaces(): void +} + +interface Ordered { + int HIGHEST_PRECEDENCE = Integer.MIN_VALUE; + int LOWEST_PRECEDENCE = Integer.MAX_VALUE; + + + getOrder(): int +} + +class DefaultIntroductionAdvisor implements IntroductionAdvisor, ClassFilter, Ordered + +abstract class AbstractPointcutAdvisor implements PointcutAdvisor, Ordered + +abstract class AbstractGenericPointcutAdvisor extends AbstractPointcutAdvisor + +class RegexpMethodPointcutAdvisor extends AbstractGenericPointcutAdvisor + +class NameMatchMethodPointcutAdvisor extends AbstractGenericPointcutAdvisor + +class DefaultPointcutAdvisor extends AbstractGenericPointcutAdvisor + +class AspectJExpressionPointcutAdvisor extends AbstractGenericPointcutAdvisor + + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml \ No newline at end of file diff --git a/truman/src/docs/asciidoc/puml/org.springframework.aop.aspectj.AspectInstanceFactory.puml b/truman/src/docs/asciidoc/puml/org.springframework.aop.aspectj.AspectInstanceFactory.puml new file mode 100644 index 000000000000..661898f6f6e2 --- /dev/null +++ b/truman/src/docs/asciidoc/puml/org.springframework.aop.aspectj.AspectInstanceFactory.puml @@ -0,0 +1,28 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **AspectInstanceFactory 继承体系** + +interface AspectInstanceFactory + +interface MetadataAwareAspectInstanceFactory extends AspectInstanceFactory + +class SingletonAspectInstanceFactory implements AspectInstanceFactory + +class SimpleBeanFactoryAwareAspectInstanceFactory implements AspectInstanceFactory + +class SimpleAspectInstanceFactory implements AspectInstanceFactory + +class SimpleMetadataAwareAspectInstanceFactory extends SimpleAspectInstanceFactory implements MetadataAwareAspectInstanceFactory + +class SingletonMetadataAwareAspectInstanceFactory extends SingletonAspectInstanceFactory implements MetadataAwareAspectInstanceFactory + +class BeanFactoryAspectInstanceFactory implements MetadataAwareAspectInstanceFactory + +class PrototypeAspectInstanceFactory extends BeanFactoryAspectInstanceFactory + +class LazySingletonAspectInstanceFactoryDecorator implements MetadataAwareAspectInstanceFactory + + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml \ No newline at end of file diff --git a/truman/src/docs/asciidoc/puml/org.springframework.beans.BeanMetadataElement.puml b/truman/src/docs/asciidoc/puml/org.springframework.beans.BeanMetadataElement.puml new file mode 100644 index 000000000000..d3aa0ea36fb9 --- /dev/null +++ b/truman/src/docs/asciidoc/puml/org.springframework.beans.BeanMetadataElement.puml @@ -0,0 +1,98 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **BeanMetadataElement 继承体系** + + +interface BeanMetadataElement { + + getSource(): Object +} + +class ManagedProperties implements BeanMetadataElement { + + isMergeEnabled(): boolean + + merge(Object parent): Object +} + +abstract class MethodOverride implements BeanMetadataElement { + # MethodOverride(String methodName) + + getMethodName(): String + # setOverloaded(boolean overloaded): void + # isOverloaded(): boolean + + setSource(Object source): void + + {abstract} matches(Method method): boolean +} + +class AliasDefinition implements BeanMetadataElement { + + getBeanName(): String + + getAlias(): String +} + +interface BeanReference extends BeanMetadataElement { + + getBeanName(): String +} + +class BeanMetadataAttribute +BeanMetadataAttribute .right.|> BeanMetadataElement + +class BeanMetadataAttributeAccessor implements BeanMetadataElement + +interface BeanDefinition extends BeanMetadataElement + +class ImportDefinition implements BeanMetadataElement + +interface ComponentDefinition extends BeanMetadataElement + +class BeanDefinitionHolder implements BeanMetadataElement + +interface DefaultsDefinition extends BeanMetadataElement + +class TypedStringValue +TypedStringValue .left.|> BeanMetadataElement + + +class LookupOverride extends MethodOverride { + + getBeanName(): String +} + +class ReplaceOverride extends MethodOverride { + + getMethodReplacerBeanName(): String + + addTypeIdentifier(String identifier): void +} + + +class RuntimeBeanNameReference implements BeanReference { + + setSource(Object source): void +} + +class RuntimeBeanReference implements BeanReference { + + isToParent(): boolean + + setSource(Object source): void +} + + +abstract class AbstractComponentDefinition implements ComponentDefinition + + +class BeanComponentDefinition extends BeanDefinitionHolder implements ComponentDefinition + + +class PropertyValue extends BeanMetadataAttributeAccessor + +class AutowireCandidateQualifier extends BeanMetadataAttributeAccessor + +abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor implements BeanDefinition + +class CompositeComponentDefinition extends AbstractComponentDefinition + +class PointcutComponentDefinition extends AbstractComponentDefinition + +class AdvisorComponentDefinition extends AbstractComponentDefinition + +class AspectComponentDefinition extends CompositeComponentDefinition + + +class DocumentDefaultsDefinition implements DefaultsDefinition + + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml \ No newline at end of file diff --git a/truman/src/docs/asciidoc/puml/org.springframework.beans.factory.BeanFactory.puml b/truman/src/docs/asciidoc/puml/org.springframework.beans.factory.BeanFactory.puml new file mode 100644 index 000000000000..58484a056440 --- /dev/null +++ b/truman/src/docs/asciidoc/puml/org.springframework.beans.factory.BeanFactory.puml @@ -0,0 +1,44 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **BeanFactory 继承体系** + + +interface BeanFactory +interface HierarchicalBeanFactory +interface ListableBeanFactory +interface ConfigurableBeanFactory +interface AutowireCapableBeanFactory +abstract class AbstractBeanFactory +class StaticListableBeanFactory +interface ApplicationContext +interface ConfigurableListableBeanFactory +abstract class AbstractAutowireCapableBeanFactory +class DefaultListableBeanFactory +class XmlBeanFactory + + +BeanFactory <|-- HierarchicalBeanFactory +BeanFactory <|-- ListableBeanFactory +HierarchicalBeanFactory <|-- ConfigurableBeanFactory +BeanFactory <|-- AutowireCapableBeanFactory + +ConfigurableBeanFactory <|.. AbstractBeanFactory + +ListableBeanFactory <|.. StaticListableBeanFactory + +ListableBeanFactory <|-- ApplicationContext +HierarchicalBeanFactory <|-- ApplicationContext + +ListableBeanFactory <|-- ConfigurableListableBeanFactory +AutowireCapableBeanFactory <|-- ConfigurableListableBeanFactory +ConfigurableBeanFactory <|-- ConfigurableListableBeanFactory + +AutowireCapableBeanFactory <|.. AbstractAutowireCapableBeanFactory +AbstractBeanFactory <|-- AbstractAutowireCapableBeanFactory + +AbstractAutowireCapableBeanFactory <|-- DefaultListableBeanFactory +ConfigurableListableBeanFactory <|.. DefaultListableBeanFactory + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml diff --git a/truman/src/docs/asciidoc/puml/org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.puml b/truman/src/docs/asciidoc/puml/org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.puml new file mode 100644 index 000000000000..be3f2f4062d0 --- /dev/null +++ b/truman/src/docs/asciidoc/puml/org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.puml @@ -0,0 +1,42 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **""AutowiredAnnotationBeanPostProcessor"" 继承体系** + +interface BeanPostProcessor { + + Object postProcessBeforeInitialization(Object bean, String beanName) + + Object postProcessAfterInitialization(Object bean, String beanName) +} + +interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor { + + Object postProcessBeforeInstantiation(Class beanClass, String beanName) + + boolean postProcessAfterInstantiation(Object bean, String beanName) + + PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) +} + +interface MergedBeanDefinitionPostProcessor extends BeanPostProcessor { + + void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class beanType, String beanName) + + void resetBeanDefinition(String beanName) +} + +interface SmartInstantiationAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessor { + + Class predictBeanType(Class beanClass, String beanName) + + Class determineBeanType(Class beanClass, String beanName) + + Constructor[] determineCandidateConstructors(Class beanClass, String beanName) + + Object getEarlyBeanReference(Object bean, String beanName) +} + +interface BeanRegistrationAotProcessor { + + BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) + + boolean isBeanExcludedFromAotProcessing() +} +note bottom of BeanRegistrationAotProcessor: Spring 6 为了 AOT 新加的。 + +class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor, MergedBeanDefinitionPostProcessor, BeanRegistrationAotProcessor { + - Set> autowiredAnnotationTypes + - Map injectionMetadataCache +} +note bottom of AutowiredAnnotationBeanPostProcessor: 从此图可以看出, ""AutowiredAnnotationBeanPostProcessor"" 是一个 ""BeanPostProcessor""。\n由此可知, ""AutowiredAnnotationBeanPostProcessor"" 是在 Spring Bean 初始化时,发挥作用。 + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml \ No newline at end of file diff --git a/truman/src/docs/asciidoc/puml/org.springframework.beans.factory.config.BeanFactoryPostProcessor.puml b/truman/src/docs/asciidoc/puml/org.springframework.beans.factory.config.BeanFactoryPostProcessor.puml new file mode 100644 index 000000000000..d15f9e441f36 --- /dev/null +++ b/truman/src/docs/asciidoc/puml/org.springframework.beans.factory.config.BeanFactoryPostProcessor.puml @@ -0,0 +1,49 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **BeanFactoryPostProcessor 继承关系** + + +interface BeanFactoryPostProcessor { + + void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) +} + +interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor { + + void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) +} + +class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor, PriorityOrdered + +class CustomAutowireConfigurer implements BeanFactoryPostProcessor, Ordered + +class CustomEditorConfigurer implements BeanFactoryPostProcessor, Ordered + +class CustomScopeConfigurer implements BeanFactoryPostProcessor, Ordered + +class DeprecatedBeanWarner implements BeanFactoryPostProcessor + +class EventListenerMethodProcessor implements BeanFactoryPostProcessor + +abstract class PropertyResourceConfigurer extends PropertiesLoaderSupport implements BeanFactoryPostProcessor, PriorityOrdered { + - order = Ordered.LOWEST_PRECEDENCE +} + +abstract class PlaceholderConfigurerSupport extends PropertyResourceConfigurer + +class PreferencesPlaceholderConfigurer <> + +class PreferencesPlaceholderConfigurer extends PropertyPlaceholderConfigurer + +class PropertyOverrideConfigurer extends PropertyResourceConfigurer + +class "~PropertyPlaceholderConfigurer~" as PropertyPlaceholderConfigurer <> + +class PropertyPlaceholderConfigurer extends PlaceholderConfigurerSupport + +class PropertySourcesPlaceholderConfigurer extends PlaceholderConfigurerSupport + +PreferencesPlaceholderConfigurer .left.> PropertySourcesPlaceholderConfigurer : 推荐 +PropertyPlaceholderConfigurer .left.> PropertySourcesPlaceholderConfigurer : 推荐 + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml diff --git a/truman/src/docs/asciidoc/puml/org.springframework.beans.factory.config.BeanPostProcessor.puml b/truman/src/docs/asciidoc/puml/org.springframework.beans.factory.config.BeanPostProcessor.puml new file mode 100644 index 000000000000..e616e8d3a434 --- /dev/null +++ b/truman/src/docs/asciidoc/puml/org.springframework.beans.factory.config.BeanPostProcessor.puml @@ -0,0 +1,64 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **BeanFactory.getBean Bean 创建** + +interface BeanPostProcessor { + + + Object postProcessBeforeInitialization(Object bean, String beanName); + + + Object postProcessAfterInitialization(Object bean, String beanName); +} + + +class ServletContextAwareProcessor implements BeanPostProcessor + +interface DestructionAwareBeanPostProcessor extends BeanPostProcessor + +class ScheduledAnnotationBeanPostProcessor implements MergedBeanDefinitionPostProcessor, DestructionAwareBeanPostProcessor + +class SimpleServletPostProcessor implements DestructionAwareBeanPostProcessor + +class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareBeanPostProcessor, MergedBeanDefinitionPostProcessor, BeanRegistrationAotProcessor + +class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBeanPostProcessor implements InstantiationAwareBeanPostProcessor + +class ApplicationListenerDetector implements DestructionAwareBeanPostProcessor, MergedBeanDefinitionPostProcessor + + + + +interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor + + +class ApplicationContextAwareProcessor implements BeanPostProcessor + +class AdvisorAdapterRegistrationManager implements BeanPostProcessor + +class ImportAwareAotBeanPostProcessor implements BeanPostProcessor + +interface MergedBeanDefinitionPostProcessor extends BeanPostProcessor + + +'interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor +' +'interface SmartInstantiationAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessor +' +'class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBeanPostProcessor implements InstantiationAwareBeanPostProcessor, BeanFactoryAware, Serializable +' +'abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSupport implements SmartInstantiationAwareBeanPostProcessor +' +'abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware +' +'abstract class AbstractAdvisorAutoProxyCreator extends AbstractAutoProxyCreator +' +'class ServletContextAwareProcessor implements BeanPostProcessor +' +'interface DestructionAwareBeanPostProcessor extends BeanPostProcessor +' +'class BeanValidationPostProcessor implements BeanPostProcessor, InitializingBean +' +'class AdvisorAdapterRegistrationManager implements BeanPostProcessor + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml diff --git a/truman/src/docs/asciidoc/puml/org.springframework.beans.factory.support.BeanDefinitionReader.puml b/truman/src/docs/asciidoc/puml/org.springframework.beans.factory.support.BeanDefinitionReader.puml new file mode 100644 index 000000000000..c85dd29d9922 --- /dev/null +++ b/truman/src/docs/asciidoc/puml/org.springframework.beans.factory.support.BeanDefinitionReader.puml @@ -0,0 +1,20 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **BeanDefinitionReader 继承体系** + + +interface BeanDefinitionReader +abstract class AbstractBeanDefinitionReader +class PropertiesBeanDefinitionReader +class GroovyBeanDefinitionReader +class XmlBeanDefinitionReader + +BeanDefinitionReader <|.. AbstractBeanDefinitionReader +AbstractBeanDefinitionReader <|-- PropertiesBeanDefinitionReader +AbstractBeanDefinitionReader <|-- GroovyBeanDefinitionReader +AbstractBeanDefinitionReader <|-- XmlBeanDefinitionReader + + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml diff --git a/truman/src/docs/asciidoc/puml/org.springframework.beans.factory.xml.BeanDefinitionParser.puml b/truman/src/docs/asciidoc/puml/org.springframework.beans.factory.xml.BeanDefinitionParser.puml new file mode 100644 index 000000000000..fdcfed7d542c --- /dev/null +++ b/truman/src/docs/asciidoc/puml/org.springframework.beans.factory.xml.BeanDefinitionParser.puml @@ -0,0 +1,36 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **""PropertyPlaceholderBeanDefinitionParser"" 继承体系** + + +interface BeanDefinitionParser { + + BeanDefinition parse(Element element, ParserContext parserContext) +} + +abstract class AbstractBeanDefinitionParser implements BeanDefinitionParser { + # void registerBeanDefinition(BeanDefinitionHolder definition, BeanDefinitionRegistry registry) + + final BeanDefinition parse(Element element, ParserContext parserContext) +} +note left: 如果想自己扩展 Spring,\n可以考虑继承该类,\n减少很多不必要的麻烦。 + +abstract class AbstractSingleBeanDefinitionParser extends AbstractBeanDefinitionParser { + # final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) +} +note left: 如果单例 Bean,\n扩展该类是最佳选择。 + +abstract class AbstractPropertyLoadingBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { + # void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) +} + +class PropertyPlaceholderBeanDefinitionParser extends AbstractPropertyLoadingBeanDefinitionParser { + # Class getBeanClass(Element element) + # void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) +} + +note bottom of PropertyPlaceholderBeanDefinitionParser: 从该继承关系图上来看, ""PropertyPlaceholderBeanDefinitionParser"" 是\n一个 ""BeanDefinitionParser"",将 标签处理成一个\n ""BeanDefinition"",然后后续交给 Spring 来处理。 + +abstract class AbstractSimpleBeanDefinitionParser extends AbstractSingleBeanDefinitionParser + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml \ No newline at end of file diff --git a/truman/src/docs/asciidoc/puml/org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions.puml b/truman/src/docs/asciidoc/puml/org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions.puml new file mode 100644 index 000000000000..c209886598eb --- /dev/null +++ b/truman/src/docs/asciidoc/puml/org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions.puml @@ -0,0 +1,58 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **XmlBeanDefinitionReader.loadBeanDefinitions(Resource) 函数执行时序图** + + +'autonumber +skinparam sequenceMessageAlign direction + +participant XmlBeanFactory << (C,#ADD1B2) >> +participant XmlBeanDefinitionReader << (C,#ADD1B2) >> +participant EncodedResource << (C,#ADD1B2) >> +participant Resource << (I,#AB9DE1) >> +participant InputSource << (C,#ADD1B2) >> <> + + +XmlBeanFactory -> XmlBeanDefinitionReader : loadBeanDefinition(resource) +activate XmlBeanDefinitionReader + + XmlBeanDefinitionReader -> EncodedResource : new EncodedResource(resource) + activate EncodedResource + XmlBeanDefinitionReader <- EncodedResource : EncodedResource:EncodedResource + deactivate EncodedResource + + XmlBeanDefinitionReader -> XmlBeanDefinitionReader : loadBeanDefinitions(EncodedResource) + activate XmlBeanDefinitionReader + + XmlBeanDefinitionReader -> EncodedResource : getResource() + activate EncodedResource + XmlBeanDefinitionReader <-- EncodedResource : resource:Resource + deactivate EncodedResource + + XmlBeanDefinitionReader -> Resource : getInputStream() + activate Resource + XmlBeanDefinitionReader <- Resource : inputStream:InputStream + deactivate Resource + + XmlBeanDefinitionReader -> InputSource : new InputSource(inputStream) + activate InputSource + XmlBeanDefinitionReader <- InputSource : inputSource:InputSource + deactivate InputSource + + XmlBeanDefinitionReader -> XmlBeanDefinitionReader : doLoadBeanDefinitions(inputSource, EncodedResource.getResource()) + activate XmlBeanDefinitionReader + XmlBeanDefinitionReader --> XmlBeanDefinitionReader : loadedBeanDefinitionNum:int + deactivate XmlBeanDefinitionReader + + XmlBeanDefinitionReader -> XmlBeanDefinitionReader + deactivate XmlBeanDefinitionReader + + ||| + +XmlBeanFactory <- XmlBeanDefinitionReader : loadedBeanDefinitionNum:int +deactivate XmlBeanDefinitionReader + + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml \ No newline at end of file diff --git a/truman/src/docs/asciidoc/puml/org.springframework.beans.factory.xml.XmlBeanFactory.puml b/truman/src/docs/asciidoc/puml/org.springframework.beans.factory.xml.XmlBeanFactory.puml new file mode 100644 index 000000000000..d72b0596aace --- /dev/null +++ b/truman/src/docs/asciidoc/puml/org.springframework.beans.factory.xml.XmlBeanFactory.puml @@ -0,0 +1,30 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **XmlBeanFactory 初始化时序图** + + +autonumber + +participant beanFactoryTest <> +participant classPathResource <> +participant xmlBeanFactory <> +participant xmlBeanDefinitionReader <> + +beanFactoryTest -> classPathResource : new ClassPathResource("beanFactory.xml") +activate classPathResource +beanFactoryTest <-- classPathResource : resource:Resource +deactivate classPathResource + +beanFactoryTest -> xmlBeanFactory : new XmlBeanFactory(resource) +activate xmlBeanFactory + xmlBeanFactory -> xmlBeanDefinitionReader : loadBeanDefinitions(resource) + activate xmlBeanDefinitionReader + xmlBeanFactory <- xmlBeanDefinitionReader : loadedBeanDefinitionNum:int + deactivate xmlBeanDefinitionReader +beanFactoryTest <- xmlBeanFactory : beanFactory +deactivate xmlBeanFactory + + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml \ No newline at end of file diff --git a/truman/src/docs/asciidoc/puml/org.springframework.context.ApplicationContext.puml b/truman/src/docs/asciidoc/puml/org.springframework.context.ApplicationContext.puml new file mode 100644 index 000000000000..782ea1b541f0 --- /dev/null +++ b/truman/src/docs/asciidoc/puml/org.springframework.context.ApplicationContext.puml @@ -0,0 +1,45 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **ApplicationContext 继承体系** + + +interface BeanFactory +interface HierarchicalBeanFactory +interface ListableBeanFactory + +BeanFactory <|-- HierarchicalBeanFactory +BeanFactory <|-- ListableBeanFactory + +ListableBeanFactory <|-- ApplicationContext +HierarchicalBeanFactory <|-- ApplicationContext + +interface ConfigurableApplicationContext extends ApplicationContext +interface WebApplicationContext extends ApplicationContext +abstract class AbstractApplicationContext implements ConfigurableApplicationContext +abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext +interface ConfigurablePortletApplicationContext extends WebApplicationContext, ConfigurableApplicationContext +abstract class AbstractRefreshableConfigApplicationContext extends AbstractRefreshableApplicationContext +interface ConfigurableWebApplicationContext extends WebApplicationContext, ConfigurableApplicationContext +class GenericApplicationContext extends AbstractApplicationContext +abstract class AbstractRefreshablePortletApplicationContext extends AbstractRefreshableConfigApplicationContext implements WebApplicationContext, ConfigurablePortletApplicationContext +class StaticPortletApplicationContext extends StaticApplicationContext implements ConfigurablePortletApplicationContext +abstract class AbstractRefreshableWebApplicationContext extends AbstractRefreshableConfigApplicationContext implements ConfigurableWebApplicationContext +abstract class AbstractXmlApplicationContext extends AbstractRefreshableConfigApplicationContext +class StaticWebApplicationContext extends StaticApplicationContext implements ConfigurableWebApplicationContext +class GenericWebApplicationContext extends GenericApplicationContext implements ConfigurableWebApplicationContext +class GenericXmlApplicationContext extends GenericApplicationContext +class AnnotationConfigApplicationContext extends GenericApplicationContext +class XmlPortletApplicationContext extends AbstractRefreshablePortletApplicationContext +class ComplexPortletApplicationContext extends StaticPortletApplicationContext +class XmlWebApplicationContext extends AbstractRefreshableWebApplicationContext +class GroovyWebApplicationContext extends AbstractRefreshableWebApplicationContext +class AnnotationConfigWebApplicationContext extends AbstractRefreshableWebApplicationContext +class ClassPathXmlApplicationContext extends AbstractXmlApplicationContext +class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext +class ComplexWebApplicationContext extends StaticWebApplicationContext +class SimpleWebApplicationContext extends StaticWebApplicationContext + + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml \ No newline at end of file diff --git a/truman/src/docs/asciidoc/puml/org.springframework.context.ApplicationEvent.puml b/truman/src/docs/asciidoc/puml/org.springframework.context.ApplicationEvent.puml new file mode 100644 index 000000000000..3a4214a2a1d9 --- /dev/null +++ b/truman/src/docs/asciidoc/puml/org.springframework.context.ApplicationEvent.puml @@ -0,0 +1,80 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **""ApplicationEvent"" 继承体系** + +class EventObject implements java.io.Serializable { + # Object source + + Object getSource() +} + +abstract class ApplicationEvent extends EventObject { + - long timestamp + + long getTimestamp() +} + +abstract class ApplicationContextEvent extends ApplicationEvent + +class ContextStartedEvent extends ApplicationContextEvent + +class ContextRefreshedEvent extends ApplicationContextEvent + +class ContextStoppedEvent extends ApplicationContextEvent + +class ContextClosedEvent extends ApplicationContextEvent + +interface ResolvableTypeProvider { + + ResolvableType getResolvableType() +} + +class PayloadApplicationEvent extends ApplicationEvent implements ResolvableTypeProvider { + - T payload + - ResolvableType payloadType + + ResolvableType getResolvableType() + + T getPayload() +} + +class RequestHandledEvent extends ApplicationEvent { + - String sessionId + - String userName + - long processingTimeMillis + - Throwable failureCause + + long getProcessingTimeMillis() + + String getSessionId() + + String getUserName() + + boolean wasFailure() + + Throwable getFailureCause() +} + +class ServletRequestHandledEvent extends RequestHandledEvent { + - String requestUrl + - String clientAddress + - String method + - String servletName + - int statusCode + + String getRequestUrl() + + String getClientAddress() + + String getMethod() + + String getServletName() + + int getStatusCode() +} + +abstract class AbstractSubProtocolEvent extends ApplicationEvent { + - Message message + - Principal user + + Message getMessage() + + Principal getUser() +} + +class SessionConnectedEvent extends AbstractSubProtocolEvent +class SessionConnectEvent extends AbstractSubProtocolEvent +class SessionDisconnectEvent extends AbstractSubProtocolEvent +class SessionSubscribeEvent extends AbstractSubProtocolEvent +class SessionUnsubscribeEvent extends AbstractSubProtocolEvent + +class EmailReceivedEvent extends ApplicationEvent + +class BrokerAvailabilityEvent extends ApplicationEvent + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml diff --git a/truman/src/docs/asciidoc/puml/org.springframework.context.config.PropertyPlaceholderBeanDefinitionParser-parse.puml b/truman/src/docs/asciidoc/puml/org.springframework.context.config.PropertyPlaceholderBeanDefinitionParser-parse.puml new file mode 100644 index 000000000000..bba86b7632c1 --- /dev/null +++ b/truman/src/docs/asciidoc/puml/org.springframework.context.config.PropertyPlaceholderBeanDefinitionParser-parse.puml @@ -0,0 +1,47 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **""PropertyPlaceholderBeanDefinitionParser - parse"" 时序图 ** + + +actor Actor + +Actor -> AbstractBeanDefinitionParser: parse +activate AbstractBeanDefinitionParser +note right: 实际调用的是 ""PropertyPlaceholderBeanDefinitionParser.parse"" 方法,\n而写 ""AbstractBeanDefinitionParser"",是因为方法就在 ""AbstractBeanDefinitionParser"" 里实现的。 + + AbstractBeanDefinitionParser -> AbstractSingleBeanDefinitionParser: parseInternal + activate AbstractSingleBeanDefinitionParser + + AbstractSingleBeanDefinitionParser -> PropertyPlaceholderBeanDefinitionParser:getBeanClass + activate PropertyPlaceholderBeanDefinitionParser + ||| + AbstractSingleBeanDefinitionParser <- PropertyPlaceholderBeanDefinitionParser:PropertySourcesPlaceholderConfigurer.class + deactivate PropertyPlaceholderBeanDefinitionParser + + AbstractSingleBeanDefinitionParser -> PropertyPlaceholderBeanDefinitionParser:doParse + activate PropertyPlaceholderBeanDefinitionParser + note right: 使用 ""super.doParse"" 调用\n""AbstractPropertyLoadingBeanDefinitionParser""\n中的 ""doParse"" 方法 + + PropertyPlaceholderBeanDefinitionParser -> AbstractPropertyLoadingBeanDefinitionParser:doParse + activate AbstractPropertyLoadingBeanDefinitionParser + note left: 读取 ""location"" 属性 + ||| + PropertyPlaceholderBeanDefinitionParser <- AbstractPropertyLoadingBeanDefinitionParser + deactivate AbstractPropertyLoadingBeanDefinitionParser + + AbstractSingleBeanDefinitionParser <- PropertyPlaceholderBeanDefinitionParser + deactivate PropertyPlaceholderBeanDefinitionParser + + AbstractBeanDefinitionParser <- AbstractSingleBeanDefinitionParser + deactivate AbstractSingleBeanDefinitionParser + + AbstractBeanDefinitionParser -> AbstractBeanDefinitionParser:registerBeanDefinition + note right: 注册 ""BeanDefinition"" + +Actor <- AbstractBeanDefinitionParser +deactivate AbstractBeanDefinitionParser + + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml \ No newline at end of file diff --git a/truman/src/docs/asciidoc/puml/org.springframework.context.config.PropertyPlaceholderBeanDefinitionParser.puml b/truman/src/docs/asciidoc/puml/org.springframework.context.config.PropertyPlaceholderBeanDefinitionParser.puml new file mode 100644 index 000000000000..d6267f4bcc13 --- /dev/null +++ b/truman/src/docs/asciidoc/puml/org.springframework.context.config.PropertyPlaceholderBeanDefinitionParser.puml @@ -0,0 +1,32 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **""PropertyPlaceholderBeanDefinitionParser"" 继承体系** + + +interface BeanDefinitionParser { + + BeanDefinition parse(Element element, ParserContext parserContext) +} + +abstract class AbstractBeanDefinitionParser implements BeanDefinitionParser { + # void registerBeanDefinition(BeanDefinitionHolder definition, BeanDefinitionRegistry registry) + + final BeanDefinition parse(Element element, ParserContext parserContext) +} + +abstract class AbstractSingleBeanDefinitionParser extends AbstractBeanDefinitionParser { + # final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) +} + +abstract class AbstractPropertyLoadingBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { + # void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) +} + +class PropertyPlaceholderBeanDefinitionParser extends AbstractPropertyLoadingBeanDefinitionParser { + # Class getBeanClass(Element element) + # void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) +} + +note bottom of PropertyPlaceholderBeanDefinitionParser: 从该继承关系图上来看, ""PropertyPlaceholderBeanDefinitionParser"" 是\n一个 ""BeanDefinitionParser"",将 标签处理成一个\n ""BeanDefinition"",然后后续交给 Spring 来处理。 + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml \ No newline at end of file diff --git a/truman/src/docs/asciidoc/puml/org.springframework.context.support.PropertySourcesPlaceholderConfigurer.puml b/truman/src/docs/asciidoc/puml/org.springframework.context.support.PropertySourcesPlaceholderConfigurer.puml new file mode 100644 index 000000000000..c0016998b755 --- /dev/null +++ b/truman/src/docs/asciidoc/puml/org.springframework.context.support.PropertySourcesPlaceholderConfigurer.puml @@ -0,0 +1,24 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **""PropertySourcesPlaceholderConfigurer"" 继承体系** + + +abstract class PropertiesLoaderSupport { + # Properties[] localProperties + - Resource[] locations +} + +interface BeanFactoryPostProcessor { + + void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) +} + +abstract class PropertyResourceConfigurer extends PropertiesLoaderSupport implements BeanFactoryPostProcessor + +abstract class PlaceholderConfigurerSupport extends PropertyResourceConfigurer + +class PropertySourcesPlaceholderConfigurer extends PlaceholderConfigurerSupport +note bottom of PropertySourcesPlaceholderConfigurer: 从该继承关系图上来看, ""PropertySourcesPlaceholderConfigurer"" 是一个\n ""BeanFactoryPostProcessor""。根据 ""BeanFactoryPostProcessor"" 的特性\n可知,它会在 Spring 容器初始化时、Bean 创建之前,完成对占位符的处理。 + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml \ No newline at end of file diff --git a/truman/src/docs/asciidoc/puml/org.springframework.core.AliasRegistry.puml b/truman/src/docs/asciidoc/puml/org.springframework.core.AliasRegistry.puml new file mode 100644 index 000000000000..16eb27f3d814 --- /dev/null +++ b/truman/src/docs/asciidoc/puml/org.springframework.core.AliasRegistry.puml @@ -0,0 +1,23 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **AliasRegistry 继承体系** +' 删除去不必要的某些类 + +class SimpleAliasRegistry implements AliasRegistry + +interface BeanDefinitionRegistry extends AliasRegistry + +class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry + +class SimpleBeanDefinitionRegistry extends SimpleAliasRegistry implements BeanDefinitionRegistry + +class FactoryBeanRegistrySupport extends DefaultSingletonBeanRegistry + +class DefaultListableBeanFactory implements BeanDefinitionRegistry + +class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry + + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml diff --git a/truman/src/docs/asciidoc/puml/org.springframework.core.env.PropertyResolver.puml b/truman/src/docs/asciidoc/puml/org.springframework.core.env.PropertyResolver.puml new file mode 100644 index 000000000000..638ba8258c63 --- /dev/null +++ b/truman/src/docs/asciidoc/puml/org.springframework.core.env.PropertyResolver.puml @@ -0,0 +1,28 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **PropertyResolver 继承体系** +' 删除去不必要的某些类 + +interface PropertyResolver + +interface ConfigurablePropertyResolver extends PropertyResolver + +abstract class AbstractPropertyResolver implements ConfigurablePropertyResolver + +class PropertySourcesPropertyResolver extends AbstractPropertyResolver + +interface Environment extends PropertyResolver + +interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver + +interface ConfigurableWebEnvironment extends ConfigurableEnvironment + +abstract class AbstractEnvironment implements ConfigurableEnvironment + +class StandardEnvironment extends AbstractEnvironment + +class StandardServletEnvironment extends StandardEnvironment implements ConfigurableWebEnvironment + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml diff --git a/truman/src/docs/asciidoc/puml/org.springframework.core.io.Resource.puml b/truman/src/docs/asciidoc/puml/org.springframework.core.io.Resource.puml new file mode 100644 index 000000000000..43c68c4a22aa --- /dev/null +++ b/truman/src/docs/asciidoc/puml/org.springframework.core.io.Resource.puml @@ -0,0 +1,78 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **Resource 继承体系** + + +interface InputStreamSource +interface Resource +abstract class AbstractResource +interface WritableResource +class InputStreamResource +class DescriptiveResource +class ByteArrayResource +abstract class AbstractFileResolvingResource +class FileSystemResource +class PathResource +class UrlResource +interface ContextResource +class ClassPathResource +class FileSystemContextResource +class ClassRelativeContextResource +class ClassPathContextResource +class VfsResource + +class EncodedResource + +class BeanDefinitionResource + +class TransformedResource + +class ServletContextResource + +class PortletContextResource + + +InputStreamSource <|-- Resource +InputStreamSource <|.. EncodedResource + +Resource <|.. AbstractResource + +Resource <|-- WritableResource +AbstractResource <|-right- InputStreamResource +AbstractResource <|-- VfsResource +AbstractResource <|-- DescriptiveResource +AbstractResource <|-- ByteArrayResource +AbstractResource <|-- AbstractFileResolvingResource +AbstractResource <|-left- BeanDefinitionResource + +WritableResource <|.. FileSystemResource +AbstractResource <|-- FileSystemResource + +WritableResource <|.. PathResource +AbstractResource <|-- PathResource + +ByteArrayResource <|-- TransformedResource + +AbstractFileResolvingResource <|-- UrlResource +Resource <|-- ContextResource +AbstractFileResolvingResource <|-- ClassPathResource + +ContextResource <|.. FileSystemContextResource +FileSystemResource <|-- FileSystemContextResource + +ContextResource <|.. ServletContextResourceLoader +AbstractFileResolvingResource <|-- ServletContextResource + +ContextResource <|.. PortletContextResource +AbstractFileResolvingResource <|-- PortletContextResource + +ContextResource <|.. ClassRelativeContextResource +ClassPathResource <|-- ClassRelativeContextResource + +ContextResource <|.. ClassPathContextResource +ClassPathResource <|-- ClassPathContextResource + + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml \ No newline at end of file diff --git a/truman/src/docs/asciidoc/puml/org.springframework.dao.DataAccessException.puml b/truman/src/docs/asciidoc/puml/org.springframework.dao.DataAccessException.puml new file mode 100644 index 000000000000..19b732bb9a8e --- /dev/null +++ b/truman/src/docs/asciidoc/puml/org.springframework.dao.DataAccessException.puml @@ -0,0 +1,141 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **DataAccessException 继承体系** + + +abstract class NestedRuntimeException extends RuntimeException { + + NestedRuntimeException(String msg) + + NestedRuntimeException(String msg, Throwable cause) + .. + + Throwable getRootCause() + + Throwable getMostSpecificCause() + + boolean contains(Class exType) +} + +abstract class DataAccessException extends NestedRuntimeException { + + DataAccessException(String msg) + + DataAccessException(String msg, Throwable cause) +} + +abstract class NonTransientDataAccessException extends DataAccessException + +abstract class TransientDataAccessException extends DataAccessException + +class RecoverableDataAccessException extends DataAccessException + +abstract class r2dbc.ScriptException extends DataAccessException + +abstract class ScriptException extends DataAccessException + +class ConnectionFactoryLookupFailureException extends NonTransientDataAccessException + +class CleanupFailureDataAccessException extends NonTransientDataAccessException + +class DataIntegrityViolationException extends NonTransientDataAccessException + +class DataSourceLookupFailureException extends NonTransientDataAccessException + +class InvalidDataAccessResourceUsageException extends NonTransientDataAccessException + +class NonTransientDataAccessResourceException extends NonTransientDataAccessException + +abstract class UncategorizedDataAccessException extends NonTransientDataAccessException + +class InvalidDataAccessApiUsageException extends NonTransientDataAccessException + +class PermissionDeniedDataAccessException extends NonTransientDataAccessException + +class DataRetrievalFailureException extends NonTransientDataAccessException + +class TransientDataAccessResourceException extends TransientDataAccessException + +class ConcurrencyFailureException extends TransientDataAccessException + +class QueryTimeoutException extends TransientDataAccessException + +class r2dbc.ScriptParseException extends r2dbc.ScriptException + +class r2dbc.UncategorizedScriptException extends r2dbc.ScriptException + +class r2dbc.CannotReadScriptException extends r2dbc.ScriptException + +class r2dbc.ScriptStatementFailedException extends r2dbc.ScriptException + +class CannotReadScriptException extends ScriptException + +class ScriptParseException extends ScriptException + +class ScriptStatementFailedException extends ScriptException + +class UncategorizedScriptException extends ScriptException + +class DuplicateKeyException extends DataIntegrityViolationException + +class HibernateQueryException extends InvalidDataAccessResourceUsageException + +class TypeMismatchDataAccessException extends InvalidDataAccessResourceUsageException + +class r2dbc.BadSqlGrammarException extends InvalidDataAccessResourceUsageException + +class InvalidResultSetAccessException extends InvalidDataAccessResourceUsageException + +class BadSqlGrammarException extends InvalidDataAccessResourceUsageException + +class IncorrectUpdateSemanticsDataAccessException extends InvalidDataAccessResourceUsageException + +class JdbcUpdateAffectedIncorrectNumberOfRowsException extends IncorrectUpdateSemanticsDataAccessException + + +class DataAccessResourceFailureException extends NonTransientDataAccessResourceException + +class CannotGetJdbcConnectionException extends DataAccessResourceFailureException + +class UncategorizedR2dbcException extends UncategorizedDataAccessException + +class JpaSystemException extends UncategorizedDataAccessException + +class SQLWarningException extends UncategorizedDataAccessException + +class UncategorizedSQLException extends UncategorizedDataAccessException + +class HibernateJdbcException extends UncategorizedDataAccessException + +class HibernateSystemException extends UncategorizedDataAccessException + +class SqlXmlFeatureNotImplementedException extends InvalidDataAccessApiUsageException + +class IncorrectResultSizeDataAccessException extends DataRetrievalFailureException + +class EmptyResultDataAccessException extends IncorrectResultSizeDataAccessException + +class IncorrectResultSetColumnCountException extends DataRetrievalFailureException + +class ObjectRetrievalFailureException extends DataRetrievalFailureException + +class JpaObjectRetrievalFailureException extends ObjectRetrievalFailureException + +class HibernateObjectRetrievalFailureException extends ObjectRetrievalFailureException + +class LobRetrievalFailureException extends DataRetrievalFailureException + +class PessimisticLockingFailureException extends ConcurrencyFailureException + +class DeadlockLoserDataAccessException extends PessimisticLockingFailureException + +class CannotSerializeTransactionException extends PessimisticLockingFailureException + +class CannotAcquireLockException extends PessimisticLockingFailureException + +class OptimisticLockingFailureException extends ConcurrencyFailureException + +class ObjectOptimisticLockingFailureException extends OptimisticLockingFailureException + +class HibernateOptimisticLockingFailureException extends ObjectOptimisticLockingFailureException + +class JpaOptimisticLockingFailureException extends ObjectOptimisticLockingFailureException + + + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml diff --git a/truman/src/docs/asciidoc/puml/read-xml.puml b/truman/src/docs/asciidoc/puml/read-xml.puml new file mode 100644 index 000000000000..cb8f2d79faf2 --- /dev/null +++ b/truman/src/docs/asciidoc/puml/read-xml.puml @@ -0,0 +1,71 @@ +@startuml +header D瓜哥 · ""https://www.diguage.com"" + +title **XML 读取涉及的相关类及继承关系** +header D瓜哥 · ""https://www.diguage.com"" + +abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext + +abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext { + # {abstract} void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) +} + +abstract class AbstractRefreshableConfigApplicationContext extends AbstractRefreshableApplicationContext + +abstract class AbstractXmlApplicationContext extends AbstractRefreshableConfigApplicationContext { + # void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) + # void loadBeanDefinitions(XmlBeanDefinitionReader reader) +} + +interface BeanDefinitionReader { + + {abstract} int loadBeanDefinitions(Resource resource) + + {abstract} int loadBeanDefinitions(Resource... resources) + + {abstract} int loadBeanDefinitions(String location) + + {abstract} int loadBeanDefinitions(String... locations) +} + +abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader { + + int loadBeanDefinitions(Resource resource) + + int loadBeanDefinitions(Resource... resources) + + int loadBeanDefinitions(String location) + + int loadBeanDefinitions(String... locations) + + int loadBeanDefinitions(String location, @Nullable Set actualResources) +} + +class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { + + int loadBeanDefinitions(Resource resource) + # int doLoadBeanDefinitions(InputSource inputSource, Resource resource) +} + +AbstractXmlApplicationContext o-right-> XmlBeanDefinitionReader : 调用 + +interface BeanDefinitionDocumentReader { + + {abstract} void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) +} + +class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader { + # void doRegisterBeanDefinitions(Element root) + # void preProcessXml(Element root) + # void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) + # void postProcessXml(Element root) + - void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) + # void importBeanDefinitionResource(Element ele) + # void processAliasRegistration(Element ele) + # void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) +} + +XmlBeanDefinitionReader o-right-> DefaultBeanDefinitionDocumentReader : 调用 + +class BeanDefinitionParserDelegate { + + boolean isDefaultNamespace(Node node) + + boolean nodeNameEquals(Node node, String desiredName) + + BeanDefinitionHolder parseBeanDefinitionElement(Element ele) + + BeanDefinition parseCustomElement(Element ele) +} +note bottom of BeanDefinitionParserDelegate: XML 的解析在这里完成 + +DefaultBeanDefinitionDocumentReader o-right-> BeanDefinitionParserDelegate : 调用 + + +footer D瓜哥 · ""https://www.diguage.com"" · 出品 +@enduml \ No newline at end of file diff --git a/truman/src/docs/asciidoc/questions.adoc b/truman/src/docs/asciidoc/questions.adoc new file mode 100644 index 000000000000..27a2179dc05f --- /dev/null +++ b/truman/src/docs/asciidoc/questions.adoc @@ -0,0 +1,4 @@ += 待解问题 + +. Spring 中的事件发布和处理的处理流程是一个什么样的?有哪些实质性的使用场景或案例? +. Spring 在关闭时,还未提交的事务,怎么处理? \ No newline at end of file diff --git a/truman/src/docs/asciidoc/references.adoc b/truman/src/docs/asciidoc/references.adoc new file mode 100644 index 000000000000..f8fbd6db3412 --- /dev/null +++ b/truman/src/docs/asciidoc/references.adoc @@ -0,0 +1,43 @@ +[appendix] += 参考资料 + +[[tips-refer]] +== Spring + +. https://blog.csdn.net/chjttony/article/details/6286144[Spring基于 Annotation 的简单介绍 - Tony Chen的专栏 - 博客频道 - CSDN.NET] +. https://stackoverflow.com/questions/11324372/how-to-make-spring-inject-value-into-a-static-field/11324464#11324464[java - How to make spring inject value into a static field - Stack Overflow] +. https://stackoverflow.com/questions/6990767/inject-bean-reference-into-a-quartz-job-in-spring[inject bean reference into a Quartz job in Spring? - Stack Overflow] +. https://fanshuyao.iteye.com/blog/2309702[Spring+quartz集群配置,Spring定时任务集群,quartz定时任务集群 - 蕃薯耀 - ITeye技术网站] +. https://fanshuyao.iteye.com/blog/2309223[Spring定时任务,Spring4整合quartz2.2,quartz-scheduler定时任务 - 蕃薯耀 - ITeye技术网站] + + +[[uml-refer]] +== UML + +. https://my.oschina.net/u/735642/blog/647205[Java异常的统一返回处理 - 边号007的个人空间 - 开源中国社区] +. https://my.oschina.net/kaywu123/blog/614325?fromerr=3jGbYlhw[Spring 源码分析(二) —— 核心容器 - 水门-kay的个人页面 - 开源中国社区] +. https://www.cnblogs.com/ywqu/archive/2009/12/22/1629426.html[UML建模之时序图(Sequence Diagram) - 灵动生活 - 博客园] +. https://blog.51cto.com/smartlife/284874[UML建模之时序图(Sequence Diagram) - 灵动生活 - 51CTO技术博客] +. https://blog.csdn.net/shulianghan/article/details/17927131[【UML 建模】UML入门 之 交互图 -- 时序图 协作图详解 - 让 学习 成为一种 习惯 ( 韩曙亮 の 技术博客 ) - 博客频道 - CSDN.NET] +. http://www.uml.org.cn/oobject/201009081.asp[UML建模之时序图(Sequence Diagram)] +. https://www.sparxsystems.cn/resources/uml2_tutorial/uml2_sequencediagram.html[Sparx Systems - UML 2 教程 - 顺序图] +. https://msdn.microsoft.com/zh-cn/library/dd409377.aspx[UML 序列图:参考] +. https://www.ibm.com/developerworks/cn/rational/rationaledge/content/feb05/bell/3101.html[UML 基础: 序列图] +. https://conglang.github.io/2015/02/04/uml_class_sequence/[UML类图与序列图 ] +. https://conglang.github.io/img/class_diagram_class.jpg[class_diagram_class.jpg (563×138)] +. https://www.cnblogs.com/duanxz/archive/2012/06/13/2547801.html[UML类图符号 各种关系说明以及举例 - duanxz - 博客园] +. https://www.cnblogs.com/olvo/archive/2012/05/03/2481014.html[UML类图关系(泛化 、继承、实现、依赖、关联、聚合、组合) - 明明的天天 - 博客园] +. https://www.ibm.com/developerworks/cn/rational/rationaledge/content/feb05/bell/[UML 基础: 类图] +. http://www.uml.org.cn/oobject/201211231.asp[深入浅出UML类图] + +[[environment-refer]] +== 环境配置 + +* https://stackoverflow.com/questions/34332580/run-gradle-test-and-not-junit-test-in-intellij-idea-15-when-choosing-configurati[Run Gradle test and not Junit test in IntelliJ IDEA 15 when choosing configuration type to run with] + +[[xml-refer]] +== XML + +. https://www.w3school.com.cn/xml/index.asp[XML 教程] +. https://www.w3school.com.cn/dtd/index.asp[DTD 教程] +. https://www.w3school.com.cn/schema/index.asp[Schema 教程] diff --git a/truman/src/docs/asciidoc/resource.adoc b/truman/src/docs/asciidoc/resource.adoc new file mode 100644 index 000000000000..a681e45b0c4a --- /dev/null +++ b/truman/src/docs/asciidoc/resource.adoc @@ -0,0 +1,24 @@ +[#resource] += 资源加载 与 Resource + +`ResourceLoader` 也是一种策略模式,加载资源的策略。 + +plantuml::{includedir}/puml/Resource-ResourceLoader.puml[{diagram_attr}] + +`classpath*:` 与 `classpath:` 的唯一区别就在于,如果能够在 classpath 中找到多个指定的资源,则返回多个。 + + +`ApplicationContext` 继承了 `ResourcePatternResolver`,当 然就间接实现了 `ResourceLoader` 接口。所以,任何的 `ApplicationContext` 实现都可以看作是一个 `ResourceLoader` 甚至 `ResourcePatternResolver`。而这就是 `ApplicationContext` 支持 Spring 内统一资源加载策略的真相。 + +plantuml::{includedir}/puml/ResourceLoader-ApplicationContext.puml[{diagram_attr}] + +`AbstractApplicationContext` 继承了 `DefaultResourceLoader`,那么,它的 `getResource(String)` 当然就直接用 `DefaultResourceLoader` 的了。 + +`AbstractApplicationContext` 类的内 部声明有一个 `resourcePatternResolver`,类型是 `ResourcePatternResolver`,对应的实例类型为 `PathMatchingResourcePatternResolver`。 + +`ApplicationContext` 的实现类在作为 `ResourceLoader` 或者 `ResourcePatternResolver` 时候的行为,完全就是委派给了 `PathMatchingResourcePatternResolver` 和 `DefaultResourceLoader` 来做。 + + +`Resource` 类图 + +plantuml::{includedir}/puml/org.springframework.core.io.Resource.puml[{diagram_attr}] diff --git a/truman/src/docs/asciidoc/scheduler.adoc b/truman/src/docs/asciidoc/scheduler.adoc new file mode 100644 index 000000000000..a6de954a73a3 --- /dev/null +++ b/truman/src/docs/asciidoc/scheduler.adoc @@ -0,0 +1,120 @@ +[#scheduler] += 定时调度 + +[source,xml,{source_attr}] +.Spring Quartz 配置 +---- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +---- + +[source,{source_attr}] +.spring-quartz.properties +---- +include::{includedir}/spring-quartz.properties[] +---- + +[source,java,{source_attr}] +.定时任务的实现 +---- +/** + * @author D瓜哥,http://www.diguage.com/ + * @since 2016-11-28 16:53 + */ +public class SnapInventoryQuartzJobBean extends QuartzJobBean implements ApplicationContextAware { + private CityService cityService; + + private ItemDao itemDao; + + private WmsInventoryService wmsInventoryService; + + /** 将 Sku 的库存更新到 sku 表中,方便取用。 仅仅在不重要的查询中使用。 */ + @Override + protected void executeInternal(JobExecutionContext context) throws JobExecutionException { + // 查询每个城市 + List cityList = cityService.findAll(); + + for (City city : cityList) { + // 根据城市查询出在线的Item对应的itemId 和 skuId + List itemList = itemDao.findByCityId(city.getId(), Constants.ITEM_ONLINE); + if (CollectionUtils.isEmpty(itemList)) { + continue; + } + // 获取每个Item的库存 + Set skuIds = Sets.newHashSetWithExpectedSize(itemList.size()); + Map skuIdToItemMap = Maps.newHashMapWithExpectedSize(itemList.size()); + for (Item item : itemList) { + skuIds.add(item.getSkuId()); + skuIdToItemMap.put(item.getSkuId(), item); + } + Map skuIdToInvertoryMap = + wmsInventoryService.checkSkuOnSalesBySiteId(skuIds, city.getId()); + + // 将库存更新到 Item 表中 + for (Item item : itemList) { + SkuForSales inventory = skuIdToInvertoryMap.get(item.getSkuId()); + skuIdToItemMap.get(item.getSkuId()).setSnapInventory(inventory.getCount()); + } + itemDao.batchUpdateSnapInvertory(itemList); + } + } + + /** + * 处理办法借鉴 + * http://stackoverflow.com/questions/6990767/inject-bean-reference-into-a-quartz-job-in-spring + * + * @param applicationContext ApplicationContext 实例 + * @throws BeansException 异常 + */ + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.cityService = applicationContext.getBean(CityService.class); + this.itemDao = applicationContext.getBean(ItemDao.class); + this.wmsInventoryService = applicationContext.getBean(WmsInventoryService.class); + } +} +---- + +TIP: 注意这里实现的 `ApplicationContextAware` 接口。由于不能依赖不能注入进来,只能通过这种方式来获取。 + +[source,sql,{source_attr}] +.MySQL 相关脚本 +---- +include::{includedir}/tables_mysql_innodb.sql[] +---- + +TIP: 这是 Quartz 2.2.3 提供的脚本。可以从它的分发包中获取更多数据库支持的脚本。 diff --git a/truman/src/docs/asciidoc/seqdiag/AbstractApplicationContext-refresh.diag b/truman/src/docs/asciidoc/seqdiag/AbstractApplicationContext-refresh.diag new file mode 100644 index 000000000000..8a830626754d --- /dev/null +++ b/truman/src/docs/asciidoc/seqdiag/AbstractApplicationContext-refresh.diag @@ -0,0 +1,33 @@ +seqdiag { + edge_length = 200; + span_height = 20; + span_width = 300; + + "Actor" => "AbstractApplicationContext" [label = "refresh\n重塑容器"] { + "AbstractApplicationContext" -> "AbstractApplicationContext" [label = "prepareRefresh\n准备环境"]; + + "AbstractApplicationContext" -> "AbstractApplicationContext" [label = "obtainFreshBeanFactory\n获取BeanFactory"]; + + "AbstractApplicationContext" -> "AbstractApplicationContext" [label = "prepareBeanFactory\n准备 BeanFactory"]; + + "AbstractApplicationContext" -> "AbstractApplicationContext" [label = "postProcessBeanFactory\n对 BeanFactory 做后期处理", note = "\n预留扩展点\n目前是空实现\n_"]; + + "AbstractApplicationContext" -> "AbstractApplicationContext" [label = "invokeBeanFactoryPostProcessors\n调用 BeanFactoryPostProcessor 的 \npostProcessBeanFactory 方法"]; + + "AbstractApplicationContext" -> "AbstractApplicationContext" [label = "registerBeanPostProcessors\n注册 BeanPostProcessor"]; + + "AbstractApplicationContext" -> "AbstractApplicationContext" [label = "initMessageSource\n初始化 MessageSource", note = "\n国际化相关\n_"]; + + "AbstractApplicationContext" -> "AbstractApplicationContext" [label = "initApplicationEventMulticaster\n初始化应用事件广播器"]; + + "AbstractApplicationContext" -> "AbstractApplicationContext" [label = "onRefresh", note = "\n在 Web 容器中\n完成 DispatchServlet 九大组件的注册\n_"]; + + "AbstractApplicationContext" -> "AbstractApplicationContext" [label = "registerListeners\n注册事件监听器"]; + + "AbstractApplicationContext" -> "AbstractApplicationContext" [label = "finishBeanFactoryInitialization\n完成预初始化", note = "\n完成单例Bean实例化\n_"]; + + "AbstractApplicationContext" -> "AbstractApplicationContext" [label = "finishRefresh\n完成重塑", note = "\n删除一些不必要的配置信息\n_"]; + } + +=== D瓜哥 · https://www.diguage.com · 出品 === +} \ No newline at end of file diff --git a/truman/src/docs/asciidoc/spring-quartz.properties b/truman/src/docs/asciidoc/spring-quartz.properties new file mode 100644 index 000000000000..666fd9040da5 --- /dev/null +++ b/truman/src/docs/asciidoc/spring-quartz.properties @@ -0,0 +1,39 @@ +#============================================================== +#Configure Main Scheduler Properties +#============================================================== +org.quartz.scheduler.instanceName = quartzScheduler +org.quartz.scheduler.instanceId = AUTO + +#============================================================================ +# Configure ThreadPool +#============================================================================ + +org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool +org.quartz.threadPool.threadCount = 5 +org.quartz.threadPool.threadPriority = 5 + +#============================================================================ +# Configure JobStore +#============================================================================ + +org.quartz.jobStore.misfireThreshold = 60000 + +org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX +org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate +org.quartz.jobStore.useProperties = false +org.quartz.jobStore.dataSource = dataSource +org.quartz.jobStore.tablePrefix = QRTZ_ + +org.quartz.jobStore.isClustered = true +org.quartz.jobStore.clusterCheckinInterval = 20000 + +#============================================================================ +# Configure Datasources +#============================================================================ + +#org.quartz.dataSource.myDS.driver = oracle.jdbc.driver.OracleDriver +#org.quartz.dataSource.myDS.URL = jdbc:oracle:thin:@polarbear:1521:dev +#org.quartz.dataSource.myDS.user = quartz +#org.quartz.dataSource.myDS.password = quartz +#org.quartz.dataSource.myDS.maxConnections = 5 +#org.quartz.dataSource.myDS.validationQuery=select 0 from dual \ No newline at end of file diff --git a/truman/src/docs/asciidoc/startup-process-overview.adoc b/truman/src/docs/asciidoc/startup-process-overview.adoc new file mode 100644 index 000000000000..230968f2a1ed --- /dev/null +++ b/truman/src/docs/asciidoc/startup-process-overview.adoc @@ -0,0 +1,563 @@ += 启动流程概述 + +对于 Spring 启动流程和 Bean 的生命周期,总有一些小地方搞的不是很清楚,干脆直接通过修改代码增加日志输出,使用断点单步调试,把整个流程捋顺了一点点的。 + +除了加载配置文件或者基础配置类外,Spring 的启动过程几乎都被封装在 `AbstractApplicationContext#refresh` 方法中,可以说弄清楚了这个方法的执行过程,就摸清楚了 Spring 启动全流程,下面的流程分析也是以这个方法为骨架来展开的。 + +== 流程概要 + +下面完整流程有些太复杂,所以,提炼一个简要的过程,方便糊弄面试官,哈哈哈😆 + +. 创建容器,读取 `applicationContext.register(Config.class)` 指定的配置。 +. 准备 `BeanFactory`,注册容器本身和 `BeanFactory` 实例,以及注册环境配置信息等。 +. 执行 `BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry` 注册 `BeanDefinition`。有三点需要注意: +.. 目前只有一个 `ConfigurationClassPostProcessor` 实现类,Spring 中大量的 Bean 都是在这一步被该类注册到容器中的。 +.. 执行顺序是 ① `PriorityOrdered` ② `Ordered` ③ 普通的顺序来执行 +.. 在执行上一步是,如果发现注册了 `BeanDefinitionRegistryPostProcessor` 类型的 Bean,就会在循环里继续调用 `postProcessBeanDefinitionRegistry` 方法。MyBATIS 和 Spring 整合的 `MapperScannerConfigurer` 类就是在这一步执行的。 +. 执行 `BeanFactoryPostProcessor#postProcessBeanFactory` 方法。目前只有一个 `ConfigurationClassPostProcessor` 实现类。 +. 注册 `CommonAnnotationBeanPostProcessor` 和 `AutowiredAnnotationBeanPostProcessor` 为 `BeanPostProcessor`。 +. 注册 `ApplicationEventMulticaster`,用于广播事件的。 +. 注册 `ApplicationListener` +. 预加载以及注册所有非懒加载的 Bean + +plantuml::{includedir}/puml/AbstractApplicationContext-refresh.puml[{diagram_attr}] + +plantuml::{includedir}/puml/org.springframework.beans.factory.config.BeanFactoryPostProcessor.puml[{diagram_attr}] + +plantuml::{includedir}/puml/AbstractApplicationContext-obtainFreshBeanFactory.puml[{diagram_attr}] + +plantuml::{includedir}/puml/AbstractApplicationContext-prepareBeanFactory.puml[{diagram_attr}] + +plantuml::{includedir}/puml/AbstractApplicationContext-invokeBeanFactoryPostProcessors.puml[{diagram_attr}] + +plantuml::{includedir}/puml/AbstractApplicationContext-invokeBeanFactoryPostProcessors-2.puml[{diagram_attr}] + +plantuml::{includedir}/puml/AbstractApplicationContext-registerBeanPostProcessors.puml[{diagram_attr}] + +plantuml::{includedir}/puml/read-xml.puml[{diagram_attr}] + + +.BeanFactoryPostProcessorOrderTest.java +[{java_src_attr}] +---- +include::{truman_src_dir}/context/BeanFactoryPostProcessorFailTest.java[] +---- + +.BeanFactoryPostProcessorOrderTest.java +[{java_src_attr}] +---- +include::{truman_src_dir}/context/BeanFactoryPostProcessorOkTest.java[] +---- + +.BeanFactoryPostProcessorOrderTest.java +[{java_src_attr}] +---- +include::{truman_src_dir}/context/BeanFactoryPostProcessorOrderTest.java[] +---- + +目前, `BeanFactoryPostProcessor` 的排序和筛选,还不支持 `@Order(value = orderFactor)`,使用 `@Order` 会被当做没有设置排序因数来处理。 + + +== 完整启动流程 + +. 调用 `prepareRefresh()` 方法,初始化属性源(property source)配置。 +. 调用 `obtainFreshBeanFactory()` 获得 `ConfigurableListableBeanFactory` 对象。 +. 调用 `prepareBeanFactory`,准备 `BeanFactory`,添加必要的 Bean;添加 `ApplicationContextAwareProcessor`、`ApplicationListenerDetector` 处理器;注册环境相关的 Bean。 +. 下面通过 `AbstractApplicationContext#invokeBeanFactoryPostProcessors` 方法,开始执行 `BeanDefinitionRegistryPostProcessor` 和 `BeanFactoryPostProcessor` 相关的方法。这个方法流程起始也很简单: ++ +目前,除了用户自定义的 `BeanDefinitionRegistryPostProcessor` 和 `BeanFactoryPostProcessor` 外,Spring 内置的,只有 `ConfigurationClassPostProcessor` 一个类。所以,把这个类的实现摸清楚了,`AbstractApplicationContext#invokeBeanFactoryPostProcessors` 就可以跳过了。 ++ +.. 首先,执行 `BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry` 方法,顺序如下: +... 用户手动添加的 `BeanDefinitionRegistryPostProcessor`; +... 实现 `PriorityOrdered` 接口的 `BeanDefinitionRegistryPostProcessor`; +... 实现 `Ordered` 接口的 `BeanDefinitionRegistryPostProcessor`; +... 普通 `BeanDefinitionRegistryPostProcessor`,只要发现有新加入的,就循环调用。 +.. 然后,执行 `BeanFactoryPostProcessor#postProcessBeanFactory` 方法。顺序如下: +... 实现 `BeanDefinitionRegistryPostProcessor` 接口的类; +... 实现 `BeanFactoryPostProcessor` 接口的类。 +. 先执行用户手动添加的 `BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry(DefaultListableBeanFactory)` ++ +关于 `BeanDefinitionRegistryPostProcessor` 的处理流程,D瓜哥在 https://www.diguage.com/post/spring-extensions-overview/#bean-factory-post-processor[Spring 扩展点概览及实践:BeanDefinitionRegistryPostProcessor^] 中有更详细的描述,不了解的朋友请参考那篇文章的介绍。 ++ +. 创建 `ConfigurationClassPostProcessor` 对象,并针对该对象依次执行 +.. 构造函数 +.. `ApplicationListenerDetector#postProcessMergedBeanDefinition(RootBeanDefinition, ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor)` +.. 调用用户手动添加的 `BeanPostProcessor#postProcessBeforeInitialization` 方法 +.. `ApplicationContextAwareProcessor#postProcessBeforeInitialization(ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor)` +.. `ApplicationListenerDetector#postProcessBeforeInitialization(ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor)` +.. 执行 `init` 方法 +.. 调用用户手动添加的 `BeanPostProcessor#postProcessAfterInitialization` 方法 +.. `ApplicationContextAwareProcessor#postProcessAfterInitialization(ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor)` -- 由于 `ApplicationContextAwareProcessor` 并没有该方法,所以不执行。 +.. `ApplicationListenerDetector#postProcessBeforeInitialization(ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor)` +. 执行 `ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry(DefaultListableBeanFactory)` -- 在这里,处理 `@Configuration`、`@Import`、 `@ImportResource`、 `@Bean` 和 。 +. 执行用户手动添加的 `BeanDefinitionRegistryPostProcessor#postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)` +. 执行 `ConfigurationClassPostProcessor#postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)` -- 在这里给 `@Configuration` 标注的类,生成 cglib 增强后的代理类。注意:在这里,还增加了一个 `ImportAwareBeanPostProcessor` 后置处理器。 ++ +因为 `ConfigurationClassPostProcessor` 是一个 `InstantiationAwareBeanPostProcessor` 实例。所以,实例化 `ConfigurationClassPostProcessor` 对象并加入到容器后。__这句话啥意思?想想再补充一下。__ ++ +. 创建了 `EventListenerMethodProcessor` 实例,和创建 `ConfigurationClassPostProcessor` 时类似,依次执行 +.. `InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation` -- 目前有 `ImportAwareBeanPostProcessor`。 +.. 构造函数 +.. `MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition` -- 目前有 `ApplicationListenerDetector`。 +.. `InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation` +.. `InstantiationAwareBeanPostProcessor#postProcessProperties` -- 目前有 `ImportAwareBeanPostProcessor`。 +.. `InstantiationAwareBeanPostProcessor#postProcessPropertyValues` -- 从 5.1 开始废弃,使用上面方法代替。 +.. `BeanPostProcessor#postProcessBeforeInitialization` -- 目前有 +... 用户手动添加的 `BeanPostProcessor` +... `ApplicationContextAwareProcessor` +... `ApplicationListenerDetector` +... `ImportAwareBeanPostProcessor` +.. `init` +.. `BeanPostProcessor#postProcessAfterInitialization` 方法。 -- 与 `postProcessBeforeInitialization` 相同,不再赘述。 ++ +有一点需要注意,上面增加了 `ImportAwareBeanPostProcessor` 实例,这里也会执行。以下都是如此,不再赘述。 ++ +. 实例化用户通过 `BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry(DefaultListableBeanFactory)` 或者 `@Configuration` 添加的 `BeanFactoryPostProcessor`,以及 Spring 自己添加的 `BeanFactoryPostProcessor`。依次执行如下方法: +.. `InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation` -- 目前有 `ImportAwareBeanPostProcessor`。 +.. Bean 的构造函数 +.. `MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition` -- 目前有 `ApplicationListenerDetector`。 +.. `InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation` +.. `InstantiationAwareBeanPostProcessor#postProcessProperties` -- 目前有 `ImportAwareBeanPostProcessor`。 +.. `InstantiationAwareBeanPostProcessor#postProcessPropertyValues` -- 从 5.1 开始废弃,使用上面方法代替。 +.. `BeanPostProcessor#postProcessBeforeInitialization` -- 目前有 +... 用户手动添加的 `BeanPostProcessor` +... `ApplicationContextAwareProcessor` +... `ApplicationListenerDetector` +... `ImportAwareBeanPostProcessor` +.. `init` +.. `BeanPostProcessor#postProcessAfterInitialization` 方法 +. 调用上一步创建的 `BeanFactoryPostProcessor` 对象的 `postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)` 方法。这里目前包含 `EventListenerMethodProcessor` 对象。`EventListenerMethodProcessor` 是 `AnnotationConfigApplicationContext()` 初始化时,创建 `new AnnotatedBeanDefinitionReader(this)` 对象时,通过调用 `AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry)` 方法注册到容器中的。 +.. 这里调用 `EventListenerMethodProcessor#postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)`,创建 `EventListenerFactory` 对象,依次执行 ++ +这个 `EventListenerFactory` 对象不重要。或者说,目前没有发现它特别重要的地方。 ++ +... `InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation` +... Bean 的构造函数 +... `MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition` +... `InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation` +... `InstantiationAwareBeanPostProcessor#postProcessProperties` +... `InstantiationAwareBeanPostProcessor#postProcessPropertyValues` -- 从 5.1 开始废弃,使用上面方法代替。 +... `BeanPostProcessor#postProcessBeforeInitialization` +... `init` +... `BeanPostProcessor#postProcessAfterInitialization` 方法 +. 到此为止,`invokeBeanFactoryPostProcessors(beanFactory)` 方法调用完毕。 +. 下面开始调用 `registerBeanPostProcessors(beanFactory)` 方法。 +. 添加 `PostProcessorRegistrationDelegate.BeanPostProcessorChecker` 实例,以下执行 `BeanPostProcessor` 方法时,都会带上。 +. 创建 `AutowiredAnnotationBeanPostProcessor`、 `CommonAnnotationBeanPostProcessor` 对象,依次执行如下方法: +.. `InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation` -- 目前有 `ImportAwareBeanPostProcessor`。 +.. 构造函数 +.. `MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition` -- 目前有 `ApplicationListenerDetector`。 +.. `InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation` +.. `InstantiationAwareBeanPostProcessor#postProcessProperties` +.. `InstantiationAwareBeanPostProcessor#postProcessPropertyValues` -- 从 5.1 开始废弃,使用上面方法代替。 +.. `AutowiredAnnotationBeanPostProcessor#setBeanFactory(DefaultListableBeanFactory)` -- 完成 `BeanNameAware`, `BeanClassLoaderAware`, `BeanFactoryAware` 三个 `Aware` 的注入。通过 `AbstractAutowireCapableBeanFactory#invokeAwareMethods` 方法来完成。 +.. `BeanPostProcessor#postProcessBeforeInitialization` -- 目前有 +... 用户手动添加的 `BeanPostProcessor` +... `ApplicationContextAwareProcessor` -- 完成如下六个 `Aware` 的注入: +.... `EnvironmentAware` +.... `EmbeddedValueResolverAware` +.... `ResourceLoaderAware` +.... `ApplicationEventPublisherAware` +.... `MessageSourceAware` +.... `ApplicationContextAware` +... `ApplicationListenerDetector` +... `ImportAwareBeanPostProcessor` +... `BeanPostProcessorChecker` +.. `init` +.. `BeanPostProcessor#postProcessAfterInitialization` 方法 +. 将 `AutowiredAnnotationBeanPostProcessor`、 `CommonAnnotationBeanPostProcessor` 对象注册到容器中。以下会随着 `BeanPostProcessor` 的调用,也会被执行。 +. 创建 `AnnotationAwareAspectJAutoProxyCreator` 对象,依次执行如下方法: +.. `InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation` -- 目前有如下三个: +... `ImportAwareBeanPostProcessor` +... `CommonAnnotationBeanPostProcessor` +... `AutowiredAnnotationBeanPostProcessor` +.. 构造函数 +.. `MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition` -- 目前有如下三个: +... `ApplicationListenerDetector` +... `CommonAnnotationBeanPostProcessor` -- 收集依赖信息。 +... `AutowiredAnnotationBeanPostProcessor` -- 收集依赖信息。 +.. `InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation` +.. `InstantiationAwareBeanPostProcessor#postProcessProperties` 目前有如下三个: +... `ImportAwareBeanPostProcessor` +... `CommonAnnotationBeanPostProcessor` -- 完成依赖注入。 +... `AutowiredAnnotationBeanPostProcessor` -- 完成依赖注入。 +.. `InstantiationAwareBeanPostProcessor#postProcessPropertyValues` -- 从 5.1 开始废弃,使用上面方法代替。 +.. `BeanPostProcessor#postProcessBeforeInitialization` -- 目前有 +... 用户手动添加的 `BeanPostProcessor` +... `ApplicationContextAwareProcessor` -- 完成如下六个 `Aware` 的注入: +.... `EnvironmentAware` +.... `EmbeddedValueResolverAware` +.... `ResourceLoaderAware` +.... `ApplicationEventPublisherAware` +.... `MessageSourceAware` +.... `ApplicationContextAware` +... `ApplicationListenerDetector` +... `ImportAwareBeanPostProcessor` +... `BeanPostProcessorChecker` +... `CommonAnnotationBeanPostProcessor` +... `AutowiredAnnotationBeanPostProcessor` +.. `init` +.. `BeanPostProcessor#postProcessAfterInitialization` 方法 +. 将 `AnnotationAwareAspectJAutoProxyCreator` 对象注册到容器中。以下会随着 `BeanPostProcessor` 的调用,也会被执行。 +. 重新添加 `ApplicationListenerDetector`,其实就是换了个位置,将其调整到了最后。 +. 到此为止,`registerBeanPostProcessors(beanFactory)` 方法调用完毕。 +. 调用 `initMessageSource()` 方法,注册 `MessageSource` Bean。 +. 调用 `initApplicationEventMulticaster()` 方法,注册 `SimpleApplicationEventMulticaster` 对象, +. 调用 `onRefresh()` 方法,这是空方法,方便做扩展。 +. 调用 `registerListeners()` 方法,但是似乎什么也没做。 +. 调用 `finishBeanFactoryInitialization(beanFactory)` 方法,这个方法中,最重要的一个操作就是实例化非懒加载的所有 Bean,在 `DefaultListableBeanFactory#preInstantiateSingletons` 中完成这些操作。目前,除了用户自己实现的,还有七个如下的 `BeanPostProcessor`: +.. `ApplicationContextAwareProcessor` +.. `ConfigurationClassPostProcessor` +.. `BeanPostProcessorChecker` +.. `AnnotationAwareAspectJAutoProxyCreator` +.. `CommonAnnotationBeanPostProcessor` +.. `AutowiredAnnotationBeanPostProcessor` +.. `ApplicationListenerDetector` ++ +这部分内容放在下一篇文章 https://www.diguage.com/post/spring-bean-lifecycle-overview/[Spring Bean 生命周期概述^] 再展开来讲。 ++ +. 调用 `finishRefresh()` -- 启动生命周期函数,广播刷新完成通知。具体如下: +.. 清理 `Resource` 缓存(也就是被扫描到的各种类,自定义类,以及相关父类和所实现的接口)。(像是在 `ImportSelector` 中声明的类。但是没有找到添加到缓存的地方?) +.. 注册 `LifecycleProcessor`,并通过它启动所有的 `LifecycleProcessor` 和它自身。没有看出来干什么用的? +.. 广播 `ContextRefreshedEvent` 事件。 +.. 将 `ConfigurableApplicationContext` 注册到 `LiveBeansView` 上,如果它存在的话。 +.. 清理各种缓存 +... 启动过程中的反射相关缓存,比如 `init-method`,`Aware` 相关的方法,注入需要的字段等等; +... `AnnotationFilter` 相关缓存; +... 注解元素缓存和生命周期函数(`Aware`、`InitializingBean`、`BeanFactoryPostProcessor`等)缓存清空 +... 解析类型缓存清空 +... 反省结果清空 + + +在下一篇文章 https://www.diguage.com/post/spring-bean-lifecycle-overview/[Spring Bean 生命周期概述^] 中,D瓜哥将针对 Spring Bean 的整个生命周期展开详细说明。 + +== 附录:启动日志 + +下面是启动日志。有删减,为了方便阅读,增加了序号和层次。 + +. 调用 `prepareRefresh()` 方法,初始化属性源(property source)配置。 +. 调用 `obtainFreshBeanFactory()` 获得 `ConfigurableListableBeanFactory` 对象。 +. 准备 `BeanFactory`,添加必要的 Bean,在 `prepareBeanFactory` 中完成。 +. 下面通过 `invokeBeanFactoryPostProcessors` 方法,开始执行 `BeanFactoryPostProcessor` 相关的方法 + +. `LogBeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry(DefaultListableBeanFactory)` -- 用户自己手动添加的 `BeanDefinitionRegistryPostProcessor` 实例 + +. 创建 `ConfigurationClassPostProcessor` Bean + +.. 构造函数 + +.. `ApplicationListenerDetector#postProcessMergedBeanDefinition(RootBeanDefinition, ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor)` -- `ApplicationListenerDetector` 实例是在 `prepareBeanFactory` 方法中,加入到容器中的。 + +.. `LogBeanPostProcessor#postProcessBeforeInitialization(ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor)` -- 用户自己手动添加 + +.. `LogDestructionAwareBeanPostProcessor#postProcessBeforeInitialization(ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor)` -- 用户自己手动添加,继承默认实现。 + +.. `ApplicationContextAwareProcessor#postProcessBeforeInitialization(ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor)` -- `ApplicationContextAwareProcessor` 实例是在 `prepareBeanFactory` 方法中,加入到容器中的。处理六种 `Aware` 注入。 + +.. `ApplicationListenerDetector#postProcessBeforeInitialization(ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor)` + +.. `LogBeanPostProcessor#postProcessAfterInitialization(ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor)` + +.. `LogDestructionAwareBeanPostProcessor#postProcessAfterInitialization(ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor)` -- 用户自己手动添加,继承默认实现,没有任何操作。 + +.. `ApplicationContextAwareProcessor#postProcessAfterInitialization(ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor)` -- 继承默认实现,没有任何操作。 + +.. `ApplicationListenerDetector#postProcessAfterInitialization(ConfigurationClassPostProcessor, org.springframework.context.annotation.internalConfigurationAnnotationProcessor)` + +. `ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry(DefaultListableBeanFactory)` -- 在这里,处理 `@Configuration`、`@Import`、 `@ImportResource`、 `@Bean` 和 。 + +. `LogBeanDefinitionRegistryPostProcessor#postProcessBeanFactory(DefaultListableBeanFactory)` + +. `ConfigurationClassPostProcessor#postProcessBeanFactory(DefaultListableBeanFactory)` -- 在这里给 `@Configuration` 标注的类,生成 cglib 增强后的代理类。注意:在这里,还增加了一个 `ImportAwareBeanPostProcessor` 后置处理器。 ++ +因为 `ConfigurationClassPostProcessor` 是一个 `InstantiationAwareBeanPostProcessor` 实例。所以,实例化 `ConfigurationClassPostProcessor` 对象并加入到容器后。__这句话啥意思?想想再补充一下。__ ++ + +. 创建 `EventListenerMethodProcessor` Bean, Name: `org.springframework.context.event.internalEventListenerProcessor` + +.. `ImportAwareBeanPostProcessor#postProcessBeforeInstantiation(EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)` + +.. 构造函数 + +.. `ApplicationListenerDetector#postProcessMergedBeanDefinition(RootBeanDefinition, EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)` + +.. `ImportAwareBeanPostProcessor#postProcessAfterInstantiation(EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)` + +.. `ImportAwareBeanPostProcessor#postProcessProperties(MutablePropertyValues, EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)` + +.. `LogBeanPostProcessor#postProcessBeforeInitialization(EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)` + +.. `LogDestructionAwareBeanPostProcessor#postProcessBeforeInitialization(EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)` + +.. `ApplicationContextAwareProcessor#postProcessBeforeInitialization(EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)` + +.. `ApplicationListenerDetector#postProcessBeforeInitialization(EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)` + +.. `ImportAwareBeanPostProcessor#postProcessBeforeInitialization(EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)` + +.. `LogBeanPostProcessor#postProcessAfterInitialization(EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)` + +.. `LogDestructionAwareBeanPostProcessor#postProcessAfterInitialization(EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)` + +.. `ApplicationContextAwareProcessor#postProcessAfterInitialization(EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)` + +.. `ApplicationListenerDetector#postProcessAfterInitialization(EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)` + +.. `ImportAwareBeanPostProcessor#postProcessAfterInitialization(EventListenerMethodProcessor, org.springframework.context.event.internalEventListenerProcessor)` + +. 创建自定义 `LogBeanFactoryPostProcessor`,通过上面 `LogBeanDefinitionRegistryPostProcessor` 的 `postProcessBeanDefinitionRegistry` 方法添加。在这一步创建用户通过 `BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry(DefaultListableBeanFactory)` 或者 `@Configuration` 添加的 `BeanFactoryPostProcessor`,以及 Spring 自己添加的 `BeanFactoryPostProcessor` 等类的相关 Bean。 + +.. `ImportAwareBeanPostProcessor#postProcessBeforeInstantiation(LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)` + +.. `ApplicationListenerDetector#postProcessMergedBeanDefinition(RootBeanDefinition, LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)` + +.. `ImportAwareBeanPostProcessor#postProcessAfterInstantiation(LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)` + +.. `ImportAwareBeanPostProcessor#postProcessProperties(MutablePropertyValues, LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)` + +.. `LogBeanPostProcessor#postProcessBeforeInitialization(LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)` + +.. `LogDestructionAwareBeanPostProcessor#postProcessBeforeInitialization(LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)` + +.. `ApplicationContextAwareProcessor#postProcessBeforeInitialization(LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)` + +.. `ApplicationListenerDetector#postProcessBeforeInitialization(LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)` + +.. `ImportAwareBeanPostProcessor#postProcessBeforeInitialization(LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)` + +.. `LogBeanPostProcessor#postProcessAfterInitialization(LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)` + +.. `LogDestructionAwareBeanPostProcessor#postProcessAfterInitialization(LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)` + +.. `ApplicationContextAwareProcessor#postProcessAfterInitialization(LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)` + +.. `ApplicationListenerDetector#postProcessAfterInitialization(LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)` + +.. `ImportAwareBeanPostProcessor#postProcessAfterInitialization(LogBeanFactoryPostProcessor, LogBeanFactoryPostProcessor)` + +. 这里会调用上一步创建的 `BeanFactoryPostProcessor` 对象的 `postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)` 方法。这里目前包含 `EventListenerMethodProcessor` 对象。`EventListenerMethodProcessor` 是 `AnnotationConfigApplicationContext()` 初始化时,创建 `new AnnotatedBeanDefinitionReader(this)` 对象时,通过调用 `AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry)` 方法注册到容器中的。 + +. `LogBeanFactoryPostProcessor#postProcessBeanFactory(DefaultListableBeanFactory)` + +. 到此为止,`invokeBeanFactoryPostProcessors(beanFactory)` 方法调用完毕。 + +. 下面开始调用 `registerBeanPostProcessors(beanFactory)` 方法。 + +. 添加 `PostProcessorRegistrationDelegate.BeanPostProcessorChecker` 实例,以下执行 `BeanPostProcessor` 方法时,都会带上。 + +. 创建 `AutowiredAnnotationBeanPostProcessor` Bean,Name: `org.springframework.context.annotation.internalAutowiredAnnotationProcessor` + +.. `ImportAwareBeanPostProcessor#postProcessBeforeInstantiation(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)` + +.. `ApplicationListenerDetector#postProcessMergedBeanDefinition(RootBeanDefinition, AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)` + +.. `ImportAwareBeanPostProcessor#postProcessAfterInstantiation(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)` + +.. `ImportAwareBeanPostProcessor#postProcessProperties(MutablePropertyValues, AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)` + +.. `AutowiredAnnotationBeanPostProcessor#setBeanFactory(DefaultListableBeanFactory)` + +.. `LogBeanPostProcessor#postProcessBeforeInitialization(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)` + +.. `LogDestructionAwareBeanPostProcessor#postProcessBeforeInitialization(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)` + +.. `ApplicationContextAwareProcessor#postProcessBeforeInitialization(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)` + +.. `ApplicationListenerDetector#postProcessBeforeInitialization(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)` + +.. `ImportAwareBeanPostProcessor#postProcessBeforeInitialization(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)` + +.. `BeanPostProcessorChecker#postProcessBeforeInitialization(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)` + +.. `LogBeanPostProcessor#postProcessAfterInitialization(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)` + +.. `LogDestructionAwareBeanPostProcessor#postProcessAfterInitialization(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)` + +.. `ApplicationContextAwareProcessor#postProcessAfterInitialization(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)` + +.. `ApplicationListenerDetector#postProcessAfterInitialization(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)` + +.. `ImportAwareBeanPostProcessor#postProcessAfterInitialization(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)` + +.. `BeanPostProcessorChecker#postProcessAfterInitialization(AutowiredAnnotationBeanPostProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor)` + +. 创建 `CommonAnnotationBeanPostProcessor` Bean,Name: `org.springframework.context.annotation.internalCommonAnnotationProcessor` + +.. `ImportAwareBeanPostProcessor#postProcessBeforeInstantiation(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)` + +.. `ApplicationListenerDetector#postProcessMergedBeanDefinition(RootBeanDefinition, CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)` + +.. `ImportAwareBeanPostProcessor#postProcessAfterInstantiation(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)` + +.. `ImportAwareBeanPostProcessor#postProcessProperties(MutablePropertyValues, CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)` + +.. `LogBeanPostProcessor#postProcessBeforeInitialization(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)` + +.. `LogDestructionAwareBeanPostProcessor#postProcessBeforeInitialization(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)` + +.. `ApplicationContextAwareProcessor#postProcessBeforeInitialization(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)` + +.. `ApplicationListenerDetector#postProcessBeforeInitialization(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)` + +.. `ImportAwareBeanPostProcessor#postProcessBeforeInitialization(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)` + +.. `BeanPostProcessorChecker#postProcessBeforeInitialization(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)` + +.. `LogBeanPostProcessor#postProcessAfterInitialization(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)` + +.. `LogDestructionAwareBeanPostProcessor#postProcessAfterInitialization(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)` + +.. `ApplicationContextAwareProcessor#postProcessAfterInitialization(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)` + +.. `ApplicationListenerDetector#postProcessAfterInitialization(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)` + +.. `ImportAwareBeanPostProcessor#postProcessAfterInitialization(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)` + +.. `BeanPostProcessorChecker#postProcessAfterInitialization(CommonAnnotationBeanPostProcessor, org.springframework.context.annotation.internalCommonAnnotationProcessor)` + +. 创建 `AnnotationAwareAspectJAutoProxyCreator`,Name: `org.springframework.aop.config.internalAutoProxyCreator`。也许是因为配置了 `@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)`。__这个再探究竟?__ + +.. `ImportAwareBeanPostProcessor#postProcessBeforeInstantiation(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)` + +.. `CommonAnnotationBeanPostProcessor#postProcessBeforeInstantiation(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)` + +.. `AutowiredAnnotationBeanPostProcessor#postProcessBeforeInstantiation(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)` + +.. `ApplicationListenerDetector#postProcessMergedBeanDefinition(RootBeanDefinition, AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)` + +.. `CommonAnnotationBeanPostProcessor#postProcessMergedBeanDefinition(RootBeanDefinition, AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)` + +.. `AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition(RootBeanDefinition, AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)` + +.. `ImportAwareBeanPostProcessor#postProcessAfterInstantiation(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)` + +.. `CommonAnnotationBeanPostProcessor#postProcessAfterInstantiation(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)` + +.. `AutowiredAnnotationBeanPostProcessor#postProcessAfterInstantiation(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)` + +.. `ImportAwareBeanPostProcessor#postProcessProperties(MutablePropertyValues, AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)` + +.. `CommonAnnotationBeanPostProcessor#postProcessProperties(MutablePropertyValues, AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)` + +.. `AutowiredAnnotationBeanPostProcessor#postProcessProperties(MutablePropertyValues, AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)` + +.. `LogBeanPostProcessor#postProcessBeforeInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)` + +.. `LogDestructionAwareBeanPostProcessor#postProcessBeforeInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)` + +.. `ApplicationContextAwareProcessor#postProcessBeforeInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)` + +.. `ApplicationListenerDetector#postProcessBeforeInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)` + +.. `ImportAwareBeanPostProcessor#postProcessBeforeInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)` + +.. `BeanPostProcessorChecker#postProcessBeforeInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)` + +.. `CommonAnnotationBeanPostProcessor#postProcessBeforeInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)` + +.. `AutowiredAnnotationBeanPostProcessor#postProcessBeforeInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)` + +.. `LogBeanPostProcessor#postProcessAfterInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)` + +.. `LogDestructionAwareBeanPostProcessor#postProcessAfterInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)` + +.. `ApplicationContextAwareProcessor#postProcessAfterInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)` + +.. `ApplicationListenerDetector#postProcessAfterInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)` + +.. `ImportAwareBeanPostProcessor#postProcessAfterInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)` + +.. `BeanPostProcessorChecker#postProcessAfterInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)` + +.. `CommonAnnotationBeanPostProcessor#postProcessAfterInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)` + +.. `AutowiredAnnotationBeanPostProcessor#postProcessAfterInitialization(AnnotationAwareAspectJAutoProxyCreator, org.springframework.aop.config.internalAutoProxyCreator)` + +. 预加载 `Config`、 `UserService` 等 Bean。下面以 `UserService` 为例: + +.. `ImportAwareBeanPostProcessor#postProcessBeforeInstantiation(UserService, UserService)` + +.. `AnnotationAwareAspectJAutoProxyCreator#postProcessBeforeInstantiation(UserService, UserService)` + +.. `CommonAnnotationBeanPostProcessor#postProcessBeforeInstantiation(UserService, UserService)` + +.. `AutowiredAnnotationBeanPostProcessor#postProcessBeforeInstantiation(UserService, UserService)` + +.. 构造函数 + +.. `CommonAnnotationBeanPostProcessor#postProcessMergedBeanDefinition(RootBeanDefinition, UserService, UserService)` + +.. `AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition(RootBeanDefinition, UserService, UserService)` + +.. `ApplicationListenerDetector#postProcessMergedBeanDefinition(RootBeanDefinition, UserService, UserService)` + +.. `ImportAwareBeanPostProcessor#postProcessAfterInstantiation(UserService, UserService)` + +.. `AnnotationAwareAspectJAutoProxyCreator#postProcessAfterInstantiation(UserService, UserService)` + +.. `CommonAnnotationBeanPostProcessor#postProcessAfterInstantiation(UserService, UserService)` + +.. `AutowiredAnnotationBeanPostProcessor#postProcessAfterInstantiation(UserService, UserService)` + +.. `ImportAwareBeanPostProcessor#postProcessProperties(MutablePropertyValues, UserService, UserService)` + +.. `AnnotationAwareAspectJAutoProxyCreator#postProcessProperties(MutablePropertyValues, UserService, UserService)` + +.. `AnnotationAwareAspectJAutoProxyCreator#postProcessPropertyValues(MutablePropertyValues, PropertyDescriptor[], UserService, UserService)` + +.. `CommonAnnotationBeanPostProcessor#postProcessProperties(MutablePropertyValues, UserService, UserService)` + +.. `AutowiredAnnotationBeanPostProcessor#postProcessProperties(MutablePropertyValues, UserService, UserService)` + +.. `UserService#setBeanFactory(DefaultListableBeanFactory)` + +.. `LogBeanPostProcessor#postProcessBeforeInitialization(UserService, UserService)` + +.. `LogDestructionAwareBeanPostProcessor#postProcessBeforeInitialization(UserService, UserService)` + +.. `ApplicationContextAwareProcessor#postProcessBeforeInitialization(UserService, UserService)` + +.. `UserService#setApplicationContext(AnnotationConfigApplicationContext)` + +.. `ImportAwareBeanPostProcessor#postProcessBeforeInitialization(UserService, UserService)` + +.. `BeanPostProcessorChecker#postProcessBeforeInitialization(UserService, UserService)` + +.. `AnnotationAwareAspectJAutoProxyCreator#postProcessBeforeInitialization(UserService, UserService)` + +.. `CommonAnnotationBeanPostProcessor#postProcessBeforeInitialization(UserService, UserService)` + +.. `AutowiredAnnotationBeanPostProcessor#postProcessBeforeInitialization(UserService, UserService)` + +.. `ApplicationListenerDetector#postProcessBeforeInitialization(UserService, UserService)` + +.. `UserService#afterPropertiesSet()` + +.. `UserService#init()` + +.. `LogBeanPostProcessor#postProcessAfterInitialization(UserService, UserService)` + +.. `LogDestructionAwareBeanPostProcessor#postProcessAfterInitialization(UserService, UserService)` + +.. `ApplicationContextAwareProcessor#postProcessAfterInitialization(UserService, UserService)` + +.. `ImportAwareBeanPostProcessor#postProcessAfterInitialization(UserService, UserService)` + +.. `BeanPostProcessorChecker#postProcessAfterInitialization(UserService, UserService)` + +.. `AnnotationAwareAspectJAutoProxyCreator#postProcessAfterInitialization(UserService, UserService)` + +.. `CommonAnnotationBeanPostProcessor#postProcessAfterInitialization(UserService, UserService)` + +.. `AutowiredAnnotationBeanPostProcessor#postProcessAfterInitialization(UserService, UserService)` + +.. `ApplicationListenerDetector#postProcessAfterInitialization(UserService, UserService)` + +. 销毁 Bean,`beanFactory.destroyBean(bean)` + +.. `LogDestructionAwareBeanPostProcessor#postProcessBeforeDestruction(UserService, UserService)` + +.. `UserService#destroy()` + +不知道有没有人关注这个附录日志,这里再重复一遍:在下一篇文章 https://www.diguage.com/post/spring-bean-lifecycle-overview/[Spring Bean 生命周期概述^] 中,D瓜哥将针对 Spring Bean 的整个生命周期展开详细说明。 + diff --git a/truman/src/docs/asciidoc/tables_mysql_innodb.sql b/truman/src/docs/asciidoc/tables_mysql_innodb.sql new file mode 100644 index 000000000000..c0696d93549d --- /dev/null +++ b/truman/src/docs/asciidoc/tables_mysql_innodb.sql @@ -0,0 +1,179 @@ +# +# In your Quartz properties file, you'll need to set +# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate +# +# +# By: Ron Cordell - roncordell +# I didn't see this anywhere, so I thought I'd post it here. This is the script from Quartz to create the tables in a MySQL database, modified to use INNODB instead of MYISAM. + +DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS; +DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE; +DROP TABLE IF EXISTS QRTZ_LOCKS; +DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_JOB_DETAILS; +DROP TABLE IF EXISTS QRTZ_CALENDARS; + +CREATE TABLE QRTZ_JOB_DETAILS( +SCHED_NAME VARCHAR(120) NOT NULL, +JOB_NAME VARCHAR(200) NOT NULL, +JOB_GROUP VARCHAR(200) NOT NULL, +DESCRIPTION VARCHAR(250) NULL, +JOB_CLASS_NAME VARCHAR(250) NOT NULL, +IS_DURABLE VARCHAR(1) NOT NULL, +IS_NONCONCURRENT VARCHAR(1) NOT NULL, +IS_UPDATE_DATA VARCHAR(1) NOT NULL, +REQUESTS_RECOVERY VARCHAR(1) NOT NULL, +JOB_DATA BLOB NULL, +PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)) +ENGINE=InnoDB; + +CREATE TABLE QRTZ_TRIGGERS ( +SCHED_NAME VARCHAR(120) NOT NULL, +TRIGGER_NAME VARCHAR(200) NOT NULL, +TRIGGER_GROUP VARCHAR(200) NOT NULL, +JOB_NAME VARCHAR(200) NOT NULL, +JOB_GROUP VARCHAR(200) NOT NULL, +DESCRIPTION VARCHAR(250) NULL, +NEXT_FIRE_TIME BIGINT(13) NULL, +PREV_FIRE_TIME BIGINT(13) NULL, +PRIORITY INTEGER NULL, +TRIGGER_STATE VARCHAR(16) NOT NULL, +TRIGGER_TYPE VARCHAR(8) NOT NULL, +START_TIME BIGINT(13) NOT NULL, +END_TIME BIGINT(13) NULL, +CALENDAR_NAME VARCHAR(200) NULL, +MISFIRE_INSTR SMALLINT(2) NULL, +JOB_DATA BLOB NULL, +PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), +FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) +REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)) +ENGINE=InnoDB; + +CREATE TABLE QRTZ_SIMPLE_TRIGGERS ( +SCHED_NAME VARCHAR(120) NOT NULL, +TRIGGER_NAME VARCHAR(200) NOT NULL, +TRIGGER_GROUP VARCHAR(200) NOT NULL, +REPEAT_COUNT BIGINT(7) NOT NULL, +REPEAT_INTERVAL BIGINT(12) NOT NULL, +TIMES_TRIGGERED BIGINT(10) NOT NULL, +PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), +FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) +REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)) +ENGINE=InnoDB; + +CREATE TABLE QRTZ_CRON_TRIGGERS ( +SCHED_NAME VARCHAR(120) NOT NULL, +TRIGGER_NAME VARCHAR(200) NOT NULL, +TRIGGER_GROUP VARCHAR(200) NOT NULL, +CRON_EXPRESSION VARCHAR(120) NOT NULL, +TIME_ZONE_ID VARCHAR(80), +PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), +FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) +REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)) +ENGINE=InnoDB; + +CREATE TABLE QRTZ_SIMPROP_TRIGGERS + ( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR(200) NOT NULL, + TRIGGER_GROUP VARCHAR(200) NOT NULL, + STR_PROP_1 VARCHAR(512) NULL, + STR_PROP_2 VARCHAR(512) NULL, + STR_PROP_3 VARCHAR(512) NULL, + INT_PROP_1 INT NULL, + INT_PROP_2 INT NULL, + LONG_PROP_1 BIGINT NULL, + LONG_PROP_2 BIGINT NULL, + DEC_PROP_1 NUMERIC(13,4) NULL, + DEC_PROP_2 NUMERIC(13,4) NULL, + BOOL_PROP_1 VARCHAR(1) NULL, + BOOL_PROP_2 VARCHAR(1) NULL, + PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), + FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) + REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)) +ENGINE=InnoDB; + +CREATE TABLE QRTZ_BLOB_TRIGGERS ( +SCHED_NAME VARCHAR(120) NOT NULL, +TRIGGER_NAME VARCHAR(200) NOT NULL, +TRIGGER_GROUP VARCHAR(200) NOT NULL, +BLOB_DATA BLOB NULL, +PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), +INDEX (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP), +FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) +REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)) +ENGINE=InnoDB; + +CREATE TABLE QRTZ_CALENDARS ( +SCHED_NAME VARCHAR(120) NOT NULL, +CALENDAR_NAME VARCHAR(200) NOT NULL, +CALENDAR BLOB NOT NULL, +PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)) +ENGINE=InnoDB; + +CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS ( +SCHED_NAME VARCHAR(120) NOT NULL, +TRIGGER_GROUP VARCHAR(200) NOT NULL, +PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)) +ENGINE=InnoDB; + +CREATE TABLE QRTZ_FIRED_TRIGGERS ( +SCHED_NAME VARCHAR(120) NOT NULL, +ENTRY_ID VARCHAR(95) NOT NULL, +TRIGGER_NAME VARCHAR(200) NOT NULL, +TRIGGER_GROUP VARCHAR(200) NOT NULL, +INSTANCE_NAME VARCHAR(200) NOT NULL, +FIRED_TIME BIGINT(13) NOT NULL, +SCHED_TIME BIGINT(13) NOT NULL, +PRIORITY INTEGER NOT NULL, +STATE VARCHAR(16) NOT NULL, +JOB_NAME VARCHAR(200) NULL, +JOB_GROUP VARCHAR(200) NULL, +IS_NONCONCURRENT VARCHAR(1) NULL, +REQUESTS_RECOVERY VARCHAR(1) NULL, +PRIMARY KEY (SCHED_NAME,ENTRY_ID)) +ENGINE=InnoDB; + +CREATE TABLE QRTZ_SCHEDULER_STATE ( +SCHED_NAME VARCHAR(120) NOT NULL, +INSTANCE_NAME VARCHAR(200) NOT NULL, +LAST_CHECKIN_TIME BIGINT(13) NOT NULL, +CHECKIN_INTERVAL BIGINT(13) NOT NULL, +PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)) +ENGINE=InnoDB; + +CREATE TABLE QRTZ_LOCKS ( +SCHED_NAME VARCHAR(120) NOT NULL, +LOCK_NAME VARCHAR(40) NOT NULL, +PRIMARY KEY (SCHED_NAME,LOCK_NAME)) +ENGINE=InnoDB; + +CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY ON QRTZ_JOB_DETAILS(SCHED_NAME,REQUESTS_RECOVERY); +CREATE INDEX IDX_QRTZ_J_GRP ON QRTZ_JOB_DETAILS(SCHED_NAME,JOB_GROUP); + +CREATE INDEX IDX_QRTZ_T_J ON QRTZ_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP); +CREATE INDEX IDX_QRTZ_T_JG ON QRTZ_TRIGGERS(SCHED_NAME,JOB_GROUP); +CREATE INDEX IDX_QRTZ_T_C ON QRTZ_TRIGGERS(SCHED_NAME,CALENDAR_NAME); +CREATE INDEX IDX_QRTZ_T_G ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP); +CREATE INDEX IDX_QRTZ_T_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE); +CREATE INDEX IDX_QRTZ_T_N_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_STATE); +CREATE INDEX IDX_QRTZ_T_N_G_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP,TRIGGER_STATE); +CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME ON QRTZ_TRIGGERS(SCHED_NAME,NEXT_FIRE_TIME); +CREATE INDEX IDX_QRTZ_T_NFT_ST ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE,NEXT_FIRE_TIME); +CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME); +CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_STATE); +CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_GROUP,TRIGGER_STATE); + +CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME); +CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME,REQUESTS_RECOVERY); +CREATE INDEX IDX_QRTZ_FT_J_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP); +CREATE INDEX IDX_QRTZ_FT_JG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_GROUP); +CREATE INDEX IDX_QRTZ_FT_T_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP); +CREATE INDEX IDX_QRTZ_FT_TG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_GROUP); + +commit; diff --git a/truman/src/docs/asciidoc/tag-resolve.adoc b/truman/src/docs/asciidoc/tag-resolve.adoc new file mode 100644 index 000000000000..14ffa77b1a07 --- /dev/null +++ b/truman/src/docs/asciidoc/tag-resolve.adoc @@ -0,0 +1,23 @@ +[#tag-resolve] += 标签解析 + +`` 的子标签 `` 会根据属性不同,被解析成不同的对象,例如 `TypedStringValue`、 `RuntimeBeanReference`,然后再被封装成 `PropertyValue` 对象,最后被存放在 `GenericBeanDefinition` 对象的 `MutablePropertyValues propertyValues` 属性(在 `AbstractBeanDefinition` 中声明)中。在首次获取对象时,根据这里的信息再逐步转化成不同对象。 + +这里还有一点需求说明: + +* `GenericBeanDefinition` 对象对应 XML 等原始配置文件中对 Bean 的定义和配置。 +* `RootBeanDefinition` 则代表生成实例时, Bean 对应的定义和配置。是根据 `GenericBeanDefinition` 的配置来生成的。如果 `GenericBeanDefinition` 定义的是子 Bean 的话,则会同时合并父类的相关属性。_这个合并怎么实现的?_ +* `BeanWrapperImpl` 对象是对创建后的实例对象的包装,被存储在 + + + + +* 对象之间的依赖关系存放在 `org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#dependentBeanMap` ( `Map>`,bean name --> Set of dependent bean names) 中。 而 `AbstractBeanFactory` 通过继承 `FactoryBeanRegistrySupport`, 而 `FactoryBeanRegistrySupport` 继承了 `DefaultSingletonBeanRegistry`,进而获得了对这个关系的访问。 +* `org.springframework.beans.factory.support.DefaultSingletonBeanRegistry` 中 `dependentBeanMap` 和 `dependenciesForBeanMap` 是什么关系? +** 例如 A ref B, A ref C,就是 类 A 中有属性 B 和 C。 +** 在 `dependenciesForBeanMap` 存到就是 A -> (B, C); +** 而 `dependentBeanMap` 中存的是 B -> A 和 C -> A。这个还要进一步确认。 + +疑问: + +* `org.springframework.beans.factory.support.ConstructorResolver#autowireConstructor` 中 `this.beanFactory.initBeanWrapper(bw)`,为什么把对 `BeanWrapper` 对初始化封装在 `BeanFactory` 而不是 `BeanWrapper` 内部? diff --git a/truman/src/docs/asciidoc/template-method-pattern.adoc b/truman/src/docs/asciidoc/template-method-pattern.adoc new file mode 100644 index 000000000000..986f2140bee8 --- /dev/null +++ b/truman/src/docs/asciidoc/template-method-pattern.adoc @@ -0,0 +1,8 @@ +[#template-method-pattern] += 瞎扯模板方法 + +模板方法模式中,会把公共部分提取到父类,一些细微的差别放在子类来实现。但是,如果不存在继承关系的类之间怎么重用这些共用代码?这就是 AOP 所需要解决的问题。 + +那么,这些问题怎么解决呢?如果接口是共用的,那么就可以使用代理模式。但是,静态代理模式的弊端就是需要各种子类,如果多层代理中的顺序发生改变,则对象的创建过程也需要跟着改变。为了解决这个问题,动态代理上场了。 + +从这个角度来说,D瓜哥觉得是可以从模板方法模式演进出代理模式。当然,这样搞就有点“曲线救国”,只是绕的道有点远。😆 diff --git a/truman/src/docs/asciidoc/tips.adoc b/truman/src/docs/asciidoc/tips.adoc new file mode 100644 index 000000000000..7f4e9676e6e0 --- /dev/null +++ b/truman/src/docs/asciidoc/tips.adoc @@ -0,0 +1,20 @@ +[#tips] +[appendix] += Spring 奇技淫巧 + +这里记录一些不太常见的使用技巧。 + +== 创建同步的 `Set` 实例 + +[{java_src_attr}] +---- +Set sets = Collections.newSetFromMap(new ConcurrentHashMap<>(256)); +---- + +include::{includedir}/inject-static-field.adoc[leveloffset=+1] + +include::{includedir}/performance-monitor.adoc[leveloffset=+1] + +include::{includedir}/lifecycle-callback.adoc[leveloffset=+1] + +include::{includedir}/scheduler.adoc[leveloffset=+1] diff --git a/truman/src/docs/asciidoc/todo.adoc b/truman/src/docs/asciidoc/todo.adoc new file mode 100644 index 000000000000..4cde0029d6b7 --- /dev/null +++ b/truman/src/docs/asciidoc/todo.adoc @@ -0,0 +1,6 @@ += 编程随想 + +== 2024-06-06 + +. `EventListenerMethodProcessor` 和 `DefaultEventListenerFactory` 是干嘛的?在 `AnnotationConfigUtils.registerAnnotationConfigProcessors` 方法中,注册了这两个类。 +. `@Order` 和 `Ordered` 在 Spring 内部处理时,有什么差别?各自又适用在什么场景下? \ No newline at end of file diff --git a/truman/src/docs/asciidoc/tools.adoc b/truman/src/docs/asciidoc/tools.adoc new file mode 100644 index 000000000000..6c50d3100bcf --- /dev/null +++ b/truman/src/docs/asciidoc/tools.adoc @@ -0,0 +1,14 @@ +[#tools] +[appendix] += 常用工具 + +. https://asciiflow.com/[ASCII diagrams--ASCIIFlow] -- 画 Ascii diagrams 图。画完 +后,通过 `asciidoctor-diagram` 的 `ditaaa` 转换成图片。 + +== Asciidoctor Diagram + +== Plant UML + +. https://plantuml.com/zh/commons[通用命令^] +. https://plantuml.com/zh/skinparam[Skinparam 命令^] +. https://plantuml.com/zh/creole[Creole 标记语言^] diff --git a/truman/src/docs/asciidoc/transaction.adoc b/truman/src/docs/asciidoc/transaction.adoc new file mode 100644 index 000000000000..d271fdb3ee66 --- /dev/null +++ b/truman/src/docs/asciidoc/transaction.adoc @@ -0,0 +1,187 @@ +[#transaction] += Spring 事务管理 + +事务是一组原子性的 SQL 查询,或者说是一个独立的工作单元。事务内的所有操作要么全部执行成功,要么全部执行失败。 + +== 四个基本特性 + +* **Atomicity(原子性)**:事务是一个不可分割的整体,事务内所有操作要么全部提交成功,要么全部失败回滚。 +* **Consistency(一致性)**:事务执行前后,数据从一个状态到另一个状态必须是一致的(A向B转账,不能出现A扣了钱,B却没收到)。 +* **Isolation(隔离性)**:多个并发事务之间相互隔离,不能互相干扰。或者说一个事务所做的修改在最终提交以前,对其他事务是不可见的。 +* **Durablity(持久性)**:事务完成后,对数据库的更改是永久保存的,不能回滚。 + +== 事务隔离级别 + +=== Read Uncommitted(未提交读) + +在 Read Uncommitted 级别,事务中的修改,即使没有提交,对其他事务也都是可见的。事务可以读取未提交的数据,这也被称为**脏读(Dirty Read)**。性能不会好太多,但是问题却一大堆,实际应用中一般很少使用。 + +=== Read Committed(提交读) + +大多数数据库系统的默认隔离级别都是 Read Committed。Read Committed 满足前面提到的隔离性的简单定义:一个事务开始时,只能“看见”已经提交的事务所做的修改。换句话说:一个事务从开始直到提交之前,所做的任何修改对其他事务都是不可见的。有时也叫不可重复读(Nonrepeatable Read)。 + +=== Repeatable Read(可重复读) + +Repeatable Read 解决了脏读的问题。但是还是无法解决领一个**幻读(Phantom Read)**问题。所谓幻读,指的是当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生幻行(Phantom Row)。InnoDB 和 XtraDB 存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)解决了幻读的问题。 + +=== Serializable(可串行化) + +Serializable 是最高的隔离级别。它通过强制事务串行执行,避免了前面说的幻读问题。简单来说,Serializable 会在读取的每一行数据上都加锁,所以导致大量的超时和锁争用的问题。实际中,极少使用。 + +Repeatable Read(可重复读) 是 MySQL 默认事务隔离级别。 + +== 常见错误 + +=== Phantom Read(幻读) + +B 事务读取了两次数据,在这两次的读取过程中A事务添加了数据,B 事务的这两次读取出来的集合不一样。幻读产生的流程如下: + +image::images/phantom-read-process.png[title="幻读处理流程", alt="幻读处理流程", width="95%", align="center"] + +这个流程看起来和不可重复读差不多,但幻读强调的集合的增减,而不是单独一条数据的修改。 + +=== NonRepeatable Read(不可重复读) + +B 事务读取了两次数据,在这两次的读取过程中 A 事务修改了数据,B 事务的这两次读取出来的数据不一样。B 事务这种读取的结果,即为不可重复读(Nonrepeatable Read)。相反,“可重复读”在同一个事务中多次读取数据时,能够保证所读数据一样,也就是后续读取不能读到另一个事务已提交的更新数据。不可重复读的产生的流程如下: + +image::images/non-repeatable-read-process.png[title="不可重复读处理流程", alt="不可重复读处理流程", width="95%", align="center"] + +=== Dirty Read(脏读) + +A 事务执行过程中,B 事务读取了A事务的修改。但是由于某些原因,A 事务可能没有完成提交,发生 RollBack 了操作,则B事务所读取的数据就会是不正确的。这个未提交数据就是脏读(Dirty Read)。 + +image::images/dirty-read-process.png[title="脏读处理流程", alt="脏读处理流程", width="95%", align="center"] + +=== Lost Update(第一类丢失更新) + +在完全未隔离事务的情况下,两个事务更新同一条数据资源,某一事务完成,另一事务异常终止,回滚造成第一个完成的更新也同时丢失 。这个问题现代关系型数据库已经不会发生。 + +=== Lost Update(第二类丢失更新) + +不可重复读有一种特殊情况,两个事务更新同一条数据资源,后完成的事务会造成先完成的事务更新丢失。这种情况就是大名鼎鼎的第二类丢失更新。主流的数据库已经默认屏蔽了第一类丢失更新问题(即:后做的事务撤销,发生回滚造成已完成事务的更新丢失),但我们编程的时候仍需要特别注意第二类丢失更新。它产生的流程如下: + +image::images/second-lost-update-process.png[title="Lost Update(第二类丢失更新)", alt="Lost Update(第二类丢失更新)", width="95%", align="center"] + +=== 小结 + +image::images/problem-reads.png[title="“读”之间的关系", alt="“读”之间的关系", align="center"] + +image::images/transactional-summary.png[title="数据库事务总结", alt="数据库事务总结", width="95%", align="center"] + +== Spring 中的隔离级别 + + +[cols="2,3"] +|=== +|常量名称 |解释说明 + +|ISOLATION_DEFAULT +|默认隔离级别,使用数据库默认的事务隔离级别。另外四个与 JDBC 的隔离级别相对应。 + +|ISOLATION_READ_UNCOMMITTED +|最低的事务隔离级别。它允许另外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻读。 + +|ISOLATION_READ_COMMITTED +|保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。 + +|ISOLATION_REPEATABLE_READ +|这种事务隔离级别可以防止脏读,不可重复读。但可能出现幻读。 + +|ISOLATION_SERIALIZABLE +|这是花费最高代价但是最可靠的事务隔离解绑。事务被处理为顺序执行。 +|=== + +---- +Spring 的事务隔离级别在 `TransactionDefinition` 中有定义。 +---- + +== Spring 的事务传播行为 + +[cols="2,3"] +|=== +|常量名称 |解释说明 + +|PROPAGATION_REQUIRED +|支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择,也是 Spring 默认的事务的传播。 + +|PROPAGATION_SUPPORTS +|支持当前事务,如果当前没有事务,就以非事务方式执行。 + +|PROPAGATION_MANDATORY +|支持当前事务,如果当前没有事务,就抛出异常。 + +|PROPAGATION_REQUIRES_NEW +|新建事务,如果当前存在事务,把当前事务挂起。新建的事务将和被挂起的事务没有任何关系,是两个独立的事务,外层事务失败回滚之后,不能回滚内层事务执行的结果内层事务失败抛出异常,外层事务捕获,也可以不处理回滚操作 + +|PROPAGATION_NOT_SUPPORTED +|以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 + +|PROPAGATION_NEVER +|以非事务方式执行,如果当前存在事务,则抛出异常。 + +|PROPAGATION_NESTED +|如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按 `REQUIRED` 属性执行。它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影影响。它只对 `DataSourceTransactionManaqer` 事务管理器起效。 +|=== + +---- +Spring 的事务传播行为在 `TransactionDefinition` 中有定义。 +---- + + +分析一下 Spring 中对事务支持的管理。同时,再分析一下 Java JTA 和 Java CMT。结合二者再来分析一下 Spring 中的事务管理。 + +`PROPAGATION_NOT_SUPPORTED` 以非事务方式运行,如果当前存在事务,则把当前事务挂起。 + +TODO 如何挂起事务? + +`org.springframework.transaction.support.TransactionTemplate.execute` 中,排除 `RuntimeException | Error` 异常就回滚。那么,在 `@Transactional(rollbackFor = Throwable.class)` 中指定了回滚异常,怎么生效? + +TODO: 增加创建用户并授权的 SQL 语句。 + +== `TransactionTemplate` 示例 + +[quote, Using the TransactionTemplate] +____ +https://docs.spring.io/spring-framework/docs/5.3.22/reference/html/data-access.html#tx-prog-template[Data Access: Using the TransactionTemplate^] + +The TransactionTemplate adopts the same approach as other Spring templates, such as the `JdbcTemplate`. It uses a callback approach (to free application code from having to do the boilerplate acquisition and release transactional resources) and results in code that is intention driven, in that your code focuses solely on what you want to do. +____ + +[#TransactionTemplateTest] +.TransactionTemplateTest +[{java_src_attr}] +---- +include::{truman_src_dir}/tx/TransactionTemplateTest.java[] +---- + +在 https://stackoverflow.com/a/51839282/951836[TransactionTemplate with rollbackFor - Stack Overflow^] 中介绍了一种通过在 `try`-`catch` 中调用 `org.springframework.transaction.TransactionExecution.setRollbackOnly()` 方法来实现回滚的操作。但是,时机并不需要。可以看一下 `org.springframework.transaction.support.TransactionTemplate.execute` 的代码就明白了: + +[{java_src_attr},highlight=25..30] +---- +include::{tx_src_dir}/transaction/support/TransactionTemplate.java[tag=execute] +---- + +代码中,直接 `catch (Throwable ex)`,相信大家都了解, `Throwable` 是 Java 异常类的基类,只要 `catch` 它,任何在前面没有处理的异常,都会在这里处理,这相当于使用了 `Throwable` 做了兜底。 + +当然,如果有异常需要特殊处理,还是可以这样做的。多说一句,由于会先执行这个回调函数,所以,在这里捕获异常,会优先处理。这里处理完,接着往外抛异常,`TransactionTemplate` 中的异常处理机制就可以处理异常了。 + + + +[#TxTest] +.TxTest +[{java_src_attr}] +---- +include::{truman_src_dir}/tx/TxTest.java[] +---- + + + +[#TxOnCloseTest] +.TxOnCloseTest +[{java_src_attr}] +---- +include::{truman_src_dir}/tx/TxOnCloseTest.java[] +---- + + + diff --git a/truman/src/docs/asciidoc/uml-class-diagram.adoc b/truman/src/docs/asciidoc/uml-class-diagram.adoc new file mode 100644 index 000000000000..4900131de2bc --- /dev/null +++ b/truman/src/docs/asciidoc/uml-class-diagram.adoc @@ -0,0 +1,166 @@ +[#class-diagram] += UML 类图 + +== 简介 + +== 要素 + +== 类之间的关系 + +UML类图几种关系的总结,泛化 = 实现 > 组合 > 聚合 > 关联 > 依赖 +在UML类图中,常见的有以下几种关系: 泛化(Generalization), 实现(Realization),关联(Association),聚合(Aggregation),组合(Composition),依赖(Dependency) + +image::images/relationships-between-classes.png[alt="类直接的关系",{image_attr}] + +=== 泛化(Generalization) + +泛化关系:: 是一种继承关系,表示一般与特殊的关系,它指定了子类如何特化父类的所有特征和行为。例如:老虎是动物的一种,即有老虎的特性也有动物的共性。 + +代码体现:: `extends` 关键字 + +箭头指向:: 带三角箭头的实线,箭头指向父类 + +[plantuml,{diagram_attr}] +.... +@startuml +skinparam classFontSize 30 +skinparam titleFontSize 30 +title **泛化(Generalization)** + +class Tiger extends Animal + +class ArrayList extends AbstractList + +skinparam footerFontSize 20 +footer ''地瓜哥''博客网 · https://www.diguage.com · 出品 +@enduml +.... + +=== 实现(Realization) + +实现关系:: 在这里插入图片描述是一种类与接口的关系,表示类是接口所有特征和行为的实现. + +代码体现:: `implements` 关键字 + +箭头指向:: 带三角箭头的虚线,箭头指向接口 + +[plantuml,{diagram_attr}] +.... +@startuml +skinparam classFontSize 30 +skinparam titleFontSize 30 +title **实现(Realization)** + +class Animal implements Movable + +class Thread implements Runnable + +skinparam footerFontSize 20 +footer ''地瓜哥''博客网 · https://www.diguage.com · 出品 +@enduml +.... + +=== 关联(Association) + +关联关系:: 是一种拥有的关系,它使一个类知道另一个类的属性和方法;如:老师与学生,丈夫与妻子关联可以是双向的,也可以是单向的。双向的关联可以有两个箭头或者没有箭头,单向的关联有一个箭头。 + +代码体现:: 成员变量 + +箭头指向:: 带普通箭头的实心线,指向被拥有者 + +[plantuml,{diagram_attr}] +.... +@startuml +skinparam classFontSize 30 +skinparam titleFontSize 30 +title **关联(Association)** + +class Teacher { + - students: Student +} + +class Student + +Teacher -right- Student + + +skinparam footerFontSize 20 +footer ''地瓜哥''博客网 · https://www.diguage.com · 出品 +@enduml +.... + +老师与学生是双向关联,老师有多名学生,学生也可能有多名老师。但学生与某课程间的关系为单向关联,一名学生可能要上多门课程,课程是个抽象的东西他不拥有学生。 + +=== 聚合(Aggregation) + +聚合关系:: 是整体与部分的关系,且部分可以离开整体而单独存在。如车和轮胎是整体和部分的关系,轮胎离开车仍然可以存在。 +聚合关系是关联关系的一种,是强的关联关系;关联和聚合在语法上无法区分,必须考察具体的逻辑关系。 + +代码体现:: 成员变量 + +箭头指向:: 带空心菱形的实心线,菱形指向整体 + +[plantuml,{diagram_attr}] +.... +@startuml +skinparam classFontSize 30 +skinparam titleFontSize 30 +title **聚合(Aggregation)** + +class Car { + - engine: Engine + - tire: Tire +} + +Car o-left- Engine + +Car o-right- Tire + + +skinparam footerFontSize 20 +footer ''地瓜哥''博客网 · https://www.diguage.com · 出品 +@enduml +.... + +小技巧:空心菱形表示聚合,好聚好散,所以生命周期可以不同。 + +=== 组合(Composition) + +组合关系:: 是整体与部分的关系,但部分不能离开整体而单独存在。如公司和部门是整体和部分的关系,没有公司就不存在部门。 ++ +组合关系是关联关系的一种,是比聚合关系还要强的关系,它要求普通的聚合关系中代表整体的对象负责代表部分的对象的生命周期。 + +代码体现:: 成员变量 + +箭头指向:: 带实心菱形的实线,菱形指向整体 + +[plantuml,{diagram_attr}] +.... +@startuml +skinparam classFontSize 30 +skinparam titleFontSize 30 +title **组合(Composition)** + +class Dog { + - head: Head + - tail: Tail + - ear: Ear +} + +Dog *-left- Head + +Dog "1" *-right- "1" Tail + +Dog "1" *-- "2" Ear + + +skinparam footerFontSize 20 +footer ''地瓜哥''博客网 · https://www.diguage.com · 出品 +@enduml +.... + +== 参考资料 + +. https://zhuanlan.zhihu.com/p/93289356[UML:类图关系总结 - 知乎^] +. http://jiagoushi.pro/book/export/html/1213[【软件设计】UML中关联,聚合和组合区别^] +. https://www.visual-paradigm.com/guide/uml-unified-modeling-language/uml-class-diagram-tutorial/[UML Class Diagram Tutorial^] \ No newline at end of file diff --git a/truman/src/docs/asciidoc/uml-sequence-diagram.adoc b/truman/src/docs/asciidoc/uml-sequence-diagram.adoc new file mode 100644 index 000000000000..a867411c89ad --- /dev/null +++ b/truman/src/docs/asciidoc/uml-sequence-diagram.adoc @@ -0,0 +1,10 @@ +[#sequence-diagram] += UML 序列图 + +== 简介 + +== 要素 + +== 类之间的关系 + +== 示例 diff --git a/truman/src/docs/asciidoc/uml.adoc b/truman/src/docs/asciidoc/uml.adoc new file mode 100644 index 000000000000..b167c7dd4128 --- /dev/null +++ b/truman/src/docs/asciidoc/uml.adoc @@ -0,0 +1,8 @@ +[#uml] +[appendix] += 常用 UML 图 + +include::{includedir}/uml-class-diagram.adoc[leveloffset=+1] + +include::{includedir}/uml-sequence-diagram.adoc[leveloffset=+1] + diff --git a/truman/src/docs/asciidoc/xml-dtd.adoc b/truman/src/docs/asciidoc/xml-dtd.adoc new file mode 100644 index 000000000000..caf33d416ef4 --- /dev/null +++ b/truman/src/docs/asciidoc/xml-dtd.adoc @@ -0,0 +1,5 @@ +[#xml-dtd] += DTD 文档类型定义 + +文档类型定义(DTD)可定义合法的XML文档构建模块。它使用一系列合法的元素来定义文档的结构。 +DTD 可被成行地声明于 XML 文档中,也可作为一个外部引用。 diff --git a/truman/src/docs/asciidoc/xml-schema.adoc b/truman/src/docs/asciidoc/xml-schema.adoc new file mode 100644 index 000000000000..83231173ca62 --- /dev/null +++ b/truman/src/docs/asciidoc/xml-schema.adoc @@ -0,0 +1,8 @@ +[#xml-Schema] += XML Schema 语言 + +XML Schema 是基于 XML 的 DTD 替代者。 + +XML Schema 描述 XML 文档的结构。 + +XML Schema 语言也称作 XML Schema 定义(XML Schema Definition,XSD)。 diff --git a/truman/src/docs/asciidoc/xml.adoc b/truman/src/docs/asciidoc/xml.adoc new file mode 100644 index 000000000000..1e058dd7316f --- /dev/null +++ b/truman/src/docs/asciidoc/xml.adoc @@ -0,0 +1,6 @@ +[#xml-language] += XML 扩展标记语言概述 + +XML 指扩展标记语言。 + +XML 被设计用来传输和存储数据。 diff --git a/truman/src/docs/asciidoc/xmls.adoc b/truman/src/docs/asciidoc/xmls.adoc new file mode 100644 index 000000000000..91a13eeeae27 --- /dev/null +++ b/truman/src/docs/asciidoc/xmls.adoc @@ -0,0 +1,9 @@ +[#xml] +[appendix] += XML 扩展标记语言 + +include::{includedir}/xml.adoc[leveloffset=+1] + +include::{includedir}/xml-dtd.adoc[leveloffset=+1] + +include::{includedir}/xml-schema.adoc[leveloffset=+1] diff --git a/truman/src/docs/color.html b/truman/src/docs/color.html new file mode 100755 index 000000000000..315b4690d175 --- /dev/null +++ b/truman/src/docs/color.html @@ -0,0 +1,70 @@ + + + + + 可续颜色 + + + + +
测试颜色-方便拾取颜色。
+ +

一、敌友识别色

+
    +
  1. #DEDEEE -- 我方,同一组内
  2. +
  3. #FBE12A -- 友军,同一个C3部门
  4. +
  5. #FF6666 -- 敌人,其他部门
  6. +
+

二、背景色

+

2.0 Draw 配色

+
    +
  1. #D5E8D4 -- 淡绿色
  2. +
  3. #DAE8FC -- 淡蓝色
  4. +
  5. #E1D5E7 -- 淡紫色
  6. +
  7. #F5F5F5 -- 淡灰色
  8. +
  9. #F8CECC -- 淡红色
  10. +
  11. #FFE6CC -- 淡黄色(泛红)
  12. +
  13. #FFF2CC -- 淡黄色
  14. +
  15. #FFFFFF -- 白色
  16. +
+

2.1、优选

+
    +
  1. #CFE5CE -- 淡绿色
  2. +
  3. #CEE7CA -- 淡绿色(差距很小)
  4. +
  5. #DAE3EE -- 淡蓝色
  6. +
  7. #DDFFFF -- 淡亮绿色
  8. +
  9. #F7C7C5 -- 淡红色
  10. +
  11. #F7CECD -- 淡红色(更淡)
  12. +
  13. #FDDEC8 -- 淡红色偏黄色
  14. +
  15. #FFFF7D -- 亮黄色
  16. +
+

2.2、备选项

+
    +
  1. #005500 -- 深绿色,更适合做前景色
  2. +
  3. #00B7FF , #DeepSkyBlue -- 深空蓝色
  4. +
  5. #00FFFF -- 亮蓝绿色
  6. +
  7. #118888 -- 深蓝绿色,感觉更适合做前景色,比如字体
  8. +
  9. #A3D2E2 , #LightBlue -- 淡蓝色
  10. +
  11. #A9DCDF -- 淡蓝色
  12. +
  13. #CFE5CE -- 淡绿色
  14. +
  15. #CEE7CA -- 淡绿色(差距很小)
  16. +
  17. #DAE3EE -- 淡蓝色
  18. +
  19. #DDFFFF -- 淡亮绿色
  20. +
  21. #EEEBDC -- 淡灰色
  22. +
  23. #E68B6F , #DarkSalmon -- 暗红色
  24. +
  25. #F7C7C5 -- 淡红色
  26. +
  27. #F7CECD -- 淡红色(更淡)
  28. +
  29. #FDDEC8 -- 淡红色偏黄色
  30. +
  31. #FF33FF -- 粉红色
  32. +
  33. #FFA0A0 -- 淡红色(更深)
  34. +
  35. #FFD100 , #Gold -- 金黄色
  36. +
  37. #FFB8C4 , #Pink -- 粉色
  38. +
  39. #FFBBBB -- 亮黄色
  40. +
  41. #FFFF7D -- 亮黄色
  42. +
+ + \ No newline at end of file diff --git a/truman/src/docs/dist/license.txt b/truman/src/docs/dist/license.txt new file mode 100644 index 000000000000..c68e1154b170 --- /dev/null +++ b/truman/src/docs/dist/license.txt @@ -0,0 +1,289 @@ + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +======================================================================= + +SPRING FRAMEWORK ${version} SUBCOMPONENTS: + +Spring Framework ${version} includes a number of subcomponents +with separate copyright notices and license terms. The product that +includes this file does not necessarily use all the open source +subcomponents referred to below. Your use of the source +code for these subcomponents is subject to the terms and +conditions of the following licenses. + + +>>> ASM 9.1 (org.ow2.asm:asm:9.1, org.ow2.asm:asm-commons:9.1): + +Copyright (c) 2000-2011 INRIA, France Telecom +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holders nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. + +Copyright (c) 1999-2009, OW2 Consortium + + +>>> CGLIB 3.3 (cglib:cglib:3.3): + +Per the LICENSE file in the CGLIB JAR distribution downloaded from +https://github.com/cglib/cglib/releases/download/RELEASE_3_3_0/cglib-3.3.0.jar, +CGLIB 3.3 is licensed under the Apache License, version 2.0, the text of which +is included above. + + +>>> Objenesis 3.1 (org.objenesis:objenesis:3.1): + +Per the LICENSE file in the Objenesis ZIP distribution downloaded from +http://objenesis.org/download.html, Objenesis 3.1 is licensed under the +Apache License, version 2.0, the text of which is included above. + +Per the NOTICE file in the Objenesis ZIP distribution downloaded from +http://objenesis.org/download.html and corresponding to section 4d of the +Apache License, Version 2.0, in this case for Objenesis: + +Objenesis +Copyright 2006-2019 Joe Walnes, Henri Tremblay, Leonardo Mesquita + + +=============================================================================== + +To the extent any open source components are licensed under the EPL and/or +other similar licenses that require the source code and/or modifications to +source code to be made available (as would be noted above), you may obtain a +copy of the source code corresponding to the binaries for such open source +components and modifications thereto, if any, (the "Source Files"), by +downloading the Source Files from https://spring.io/projects, Pivotal's website +at https://network.pivotal.io/open-source, or by sending a request, with your +name and address to: Pivotal Software, Inc., 875 Howard Street, 5th floor, San +Francisco, CA 94103, Attention: General Counsel. All such requests should +clearly specify: OPEN SOURCE FILES REQUEST, Attention General Counsel. Pivotal +can mail a copy of the Source Files to you on a CD or equivalent physical +medium. + +This offer to obtain a copy of the Source Files is valid for three years from +the date you acquired this Software product. Alternatively, the Source Files +may accompany the Software. diff --git a/truman/src/docs/spring-framework.png b/truman/src/docs/spring-framework.png new file mode 100644 index 000000000000..8945d6c6cbd7 Binary files /dev/null and b/truman/src/docs/spring-framework.png differ diff --git a/truman/src/jmh/java/com/diguage/truman/beans/BeanUtilsBenchTest.java b/truman/src/jmh/java/com/diguage/truman/beans/BeanUtilsBenchTest.java new file mode 100644 index 000000000000..31cfb1eb5e60 --- /dev/null +++ b/truman/src/jmh/java/com/diguage/truman/beans/BeanUtilsBenchTest.java @@ -0,0 +1,14 @@ +package com.diguage.truman.beans; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; + +@BenchmarkMode(Mode.Throughput) +@State(Scope.Benchmark) +public class BeanUtilsBenchTest { + // 跨版本的的测试,没办法在这里搞 + @Benchmark + public void test(Blackhole bh) { + bh.consume("https://www.diguage.com/"); + } +} diff --git a/truman/src/main/java/com/diguage/truman/aop/AopTest.java b/truman/src/main/java/com/diguage/truman/aop/AopTest.java new file mode 100644 index 000000000000..9071277c70d5 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/aop/AopTest.java @@ -0,0 +1,132 @@ +package com.diguage.truman.aop; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.Signature; +import org.aspectj.lang.annotation.After; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.annotation.Pointcut; +import org.junit.jupiter.api.Test; +import org.springframework.aop.framework.AopContext; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.ImportSelector; +import org.springframework.core.type.AnnotationMetadata; + +import jakarta.annotation.Resource; + +/** + * @author D瓜哥, https://www.diguage.com/ + * @since 2020-06-02 11:12 + */ +public class AopTest { + @Test + public void test() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(Config.class); + context.refresh(); + UserService bean = context.getBean(UserService.class); + bean.test(); + bean.getDesc(); + bean.setDesc("This is a test."); + + String user = bean.getById(119); + System.out.println(user); + + BeanDefinition definition = context.getBeanDefinition(UserService.class.getName()); + System.out.println(definition); + } + + @Configuration + @Import(AopImportSelector.class) + @EnableAspectJAutoProxy(exposeProxy = true) + public static class Config { + } + + // 使用 @Import 和 ImportSelector 搭配,就可以省去 XML 配置 + public static class AopImportSelector implements ImportSelector { + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + return new String[]{ + UserDao.class.getName(), + UserService.class.getName(), + TestAspect.class.getName() + }; + } + } + + @Aspect + public static class TestAspect { + @Pointcut("execution(* com.diguage.truman.aop.AopTest$UserService.test(..))") + public void test() { + } + + @Before("test()") + public void beforeTest() { + System.out.println("beforeTest"); + } + + @After("test()") + public void afterTest() { + System.out.println("afterTest"); + } + + @Around("test()") + public Object aroundTest(ProceedingJoinPoint pjp) { + System.out.println("aroundBefore1"); + Object restul = null; + Signature signature = pjp.getSignature(); + System.out.println(pjp.getKind()); + Object target = pjp.getTarget(); + System.out.println(target.getClass().getName() + "#" + signature.getName()); + try { + restul = pjp.proceed(); + } catch (Throwable throwable) { + throwable.printStackTrace(); + } + System.out.println("aroundAfter1"); + return restul; + } + } + + public static class UserDao { + public String getById(int id) { + return "diguage-" + id; + } + } + + public static class UserService { + private String desc = "testBean"; + + @Resource + private UserDao userDao; + + public String getDesc() { + System.out.println("getDesc"); + this.test(); + System.out.println("--this----------getDesc"); + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + // 使用 @EnableAspectJAutoProxy(exposeProxy = true) 打开 exposeProxy = true + // 则必须这样写,才能获取到当前的代理对象,然后调用的方法才是被 AOP 处理后的方法。 + // 使用 this.methodName() 调用,依然调用的是原始的、未经 AOP 处理的方法 + ((UserService) AopContext.currentProxy()).test(); + System.out.println("--AopContext----setDesc"); + } + + public void test() { + System.out.println("----------------test"); + } + + public String getById(int id) { + return userDao.getById(id); + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/aop/AspectAfterThrowingTest.java b/truman/src/main/java/com/diguage/truman/aop/AspectAfterThrowingTest.java new file mode 100644 index 000000000000..9a0dc3ce2d8e --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/aop/AspectAfterThrowingTest.java @@ -0,0 +1,70 @@ +package com.diguage.truman.aop; + +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.AfterThrowing; +import org.aspectj.lang.annotation.Aspect; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.*; +import org.springframework.core.type.AnnotationMetadata; + +import java.util.Arrays; + +/** + * 验证 @AfterThrowing 的属性 throwing + * + * @author D瓜哥, https://www.diguage.com/ + * @since 2021-07-19 23:28:27 + */ +@Slf4j +public class AspectAfterThrowingTest { + @Test + public void test() { + AnnotationConfigApplicationContext context + = new AnnotationConfigApplicationContext(); + context.register(Config.class); + context.refresh(); + + Movable bean = context.getBean(Movable.class); + bean.move("Henan"); + } + + @Configuration + @Import(AspectImportSelector.class) + @EnableAspectJAutoProxy + public static class Config { + } + + public static class AspectImportSelector implements ImportSelector { + @Override + public String[] selectImports(AnnotationMetadata metadata) { + return new String[]{ + Rabbit.class.getName(), + AfterThrowingAspect.class.getName() + }; + } + } + + public interface Movable { + void move(String target); + } + + public static class Rabbit implements Movable { + @Override + public void move(String target) { + log.info("Rabbit.move executing..."); + throw new RuntimeException("Rabbit throws an error."); + } + } + + @Aspect + public static class AfterThrowingAspect { + @AfterThrowing(pointcut = "execution(void *.move(String, ..))", + throwing = "e") + public void afterThrowing(JoinPoint joinPoint, RuntimeException e) { + Object[] args = joinPoint.getArgs(); + log.error("Target threw an error. args={}", + Arrays.toString(args), e); + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/aop/AspectAnnoArgsTest.java b/truman/src/main/java/com/diguage/truman/aop/AspectAnnoArgsTest.java new file mode 100644 index 000000000000..3b9a3311c56a --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/aop/AspectAnnoArgsTest.java @@ -0,0 +1,113 @@ +package com.diguage.truman.aop; + +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.*; +import org.springframework.core.type.AnnotationMetadata; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Arrays; + +/** + * 验证 @args 的匹配规则。 + * + * @author D瓜哥, https://www.diguage.com/ + * @since 2021-07-18 21:18:43 + */ +@Slf4j +public class AspectAnnoArgsTest { + + @Test + public void test() { + AnnotationConfigApplicationContext context + = new AnnotationConfigApplicationContext(); + context.register(Config.class); + context.refresh(); + + DiguageTask task = context.getBean(DiguageTask.class); + AnnoParam annoParam = new AnnoParam("AnnoParam"); + task.run(annoParam); + + NonAnnoParam nonAnnoParam = new NonAnnoParam("NonAnnoParam"); + task.run(nonAnnoParam); + } + + @Configuration + @Import(ArgsImportSelector.class) + @EnableAspectJAutoProxy // 注意:这行必须加 + public static class Config { + } + + public static class ArgsImportSelector implements ImportSelector { + @Override + public String[] selectImports(AnnotationMetadata metadata) { + return new String[]{ + DiguageTask.class.getName(), + TypeAspect.class.getName() + }; + } + } + + public static class DiguageTask { + public void run(Object param) { + log.info("Diguage.run executing.params[{}]", param); + } + } + + @Data + @NoArgsConstructor + @TypeAnnotation + public static class AnnoParam { + private String name; + + public AnnoParam(String name) { + this.name = name; + } + } + + /** + * 这个参数调用时,没有执行增强,所以只会对有注解的参数进行拦截。 + */ + @Data + @NoArgsConstructor + public static class NonAnnoParam { + private String name; + + public NonAnnoParam(String name) { + this.name = name; + } + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public @interface TypeAnnotation { + } + + @Aspect + public static class TypeAspect { + @Pointcut("@args(com.diguage.truman.aop." + + "AspectAnnoArgsTest.TypeAnnotation)") + public void doType() { + } + + @Around("doType()") + public Object around(ProceedingJoinPoint joinPoint) throws Throwable { + Object[] args = null; + try { + args = joinPoint.getArgs(); + return joinPoint.proceed(); + } finally { + log.info("TypeAspect executing. params[{}]", Arrays.toString(args)); + } + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/aop/AspectAnnoTargetTest.java b/truman/src/main/java/com/diguage/truman/aop/AspectAnnoTargetTest.java new file mode 100644 index 000000000000..748842777dba --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/aop/AspectAnnoTargetTest.java @@ -0,0 +1,111 @@ +package com.diguage.truman.aop; + +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.*; +import org.springframework.core.type.AnnotationMetadata; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 验证 @target 的匹配规则。 + * + * TODO 如何验证其动态匹配的特性? + * + * @author D瓜哥, https://www.diguage.com/ + * @since 2021-07-18 21:18:43 + */ +@Slf4j +public class AspectAnnoTargetTest { + @Test + public void test() { + AnnotationConfigApplicationContext context + = new AnnotationConfigApplicationContext(); + context.register(Config.class); + context.refresh(); + + DiguageTask task = context.getBean(DiguageTask.class); + task.run(); + } + + @Configuration + @Import(TargetImportSelector.class) + @EnableAspectJAutoProxy // 注意:这行必须加 + public static class Config { + } + + public static class TargetImportSelector implements ImportSelector { + @Override + public String[] selectImports(AnnotationMetadata metadata) { + return new String[]{ + DiguageTask.class.getName(), + TypeAspect.class.getName(), + MethodAspect.class.getName() + }; + } + } + + @TypeAnnotation + public static class DiguageTask { + @MethodAnnotation + public void run() { + log.info("Diguage.run executing..."); + } + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public @interface TypeAnnotation { + } + + @Aspect + public static class TypeAspect { + @Pointcut("@target(com.diguage.truman.aop." + + "AspectAnnoTargetTest.TypeAnnotation)") + public void doType() { + } + + @Around("doType()") + public Object around(ProceedingJoinPoint joinPoint) throws Throwable { + try { + return joinPoint.proceed(); + } finally { + log.info("TypeAspect executing..."); + } + } + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface MethodAnnotation { + } + + @Aspect + public static class MethodAspect { + @Pointcut("@target(com.diguage.truman.aop." + + "AspectAnnoTargetTest.MethodAnnotation)") + public void doMethod() { + } + + /** + * 这里的代码没有执行到,说明对于 @target 注解来说,只支持类注解。 + */ + @Around("doMethod()") + public Object around(ProceedingJoinPoint joinPoint) throws Throwable { + try { + return joinPoint.proceed(); + } finally { + log.info("MethodAspect executing..."); + } + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/aop/AspectAnnotationTest.java b/truman/src/main/java/com/diguage/truman/aop/AspectAnnotationTest.java new file mode 100644 index 000000000000..9236602cc067 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/aop/AspectAnnotationTest.java @@ -0,0 +1,107 @@ +package com.diguage.truman.aop; + +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.*; +import org.springframework.core.type.AnnotationMetadata; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author D瓜哥, https://www.diguage.com/ + * @since 2021-07-18 23:40:42 + */ +@Slf4j +public class AspectAnnotationTest { + + @Test + public void test() { + AnnotationConfigApplicationContext context + = new AnnotationConfigApplicationContext(); + context.register(Config.class); + context.refresh(); + + DiguageTask task = context.getBean(DiguageTask.class); + task.run(); + } + + @Configuration + @Import(AnnoImportSelector.class) + @EnableAspectJAutoProxy // 注意:这行必须加 + public static class Config { + } + + public static class AnnoImportSelector implements ImportSelector { + @Override + public String[] selectImports(AnnotationMetadata metadata) { + return new String[]{ + DiguageTask.class.getName(), + AnnoTypeAspect.class.getName(), + AnnoMethodAspect.class.getName() + }; + } + } + + @AnnoTypeAnnotation + public static class DiguageTask { + @AnnoMethodAnnotation + public void run() { + log.info("Diguage.run executing..."); + } + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface AnnoMethodAnnotation { + } + + @Aspect + public static class AnnoMethodAspect { + @Pointcut("@annotation(com.diguage.truman.aop." + + "AspectAnnotationTest.AnnoMethodAnnotation)") + public void annoMethod() { + } + + @Around("annoMethod()") + public Object around(ProceedingJoinPoint joinPoint) throws Throwable { + try { + return joinPoint.proceed(); + } finally { + log.info("AnnoMethodAspect.annoMethod executing..."); + } + } + } + + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public @interface AnnoTypeAnnotation { + } + + @Aspect + public static class AnnoTypeAspect { + @Pointcut("@annotation(com.diguage.truman.aop." + + "AspectAnnotationTest.AnnoTypeAnnotation)") + public void annoType() { + } + + /** + * 这里的代码没有执行到,说明对于 @annotation 注解来说,只支持方法注解。 + */ + @Around("annoType()") + public Object around(ProceedingJoinPoint joinPoint) throws Throwable { + try { + return joinPoint.proceed(); + } finally { + log.info("AnnoTypeAspect.annoType executing..."); + } + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/aop/AspectAopManualTest.java b/truman/src/main/java/com/diguage/truman/aop/AspectAopManualTest.java new file mode 100644 index 000000000000..1676d09e7b29 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/aop/AspectAopManualTest.java @@ -0,0 +1,61 @@ +package com.diguage.truman.aop; + +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.junit.jupiter.api.Test; +import org.springframework.aop.aspectj.annotation.AspectJProxyFactory; +import org.springframework.util.StopWatch; + +/** + * @author D瓜哥, https://www.diguage.com/ + * @since 2021-07-18 14:43:36 + */ +@Slf4j +public class AspectAopManualTest { + @Test + public void test() { + AspectJProxyFactory weaver = new AspectJProxyFactory(); + weaver.setProxyTargetClass(true); + weaver.setTarget(new Foo()); + weaver.addAspect(PerformanceTraceAspect.class); + Foo proxy = weaver.getProxy(); + proxy.method1(); + proxy.method2(); + } + + @Aspect + public static class PerformanceTraceAspect { + @Pointcut("execution(public void *.method1()) " + + "|| execution(public void *.method2())") + public void pointcutName() { + } + + @Around("pointcutName()") + public Object trace(ProceedingJoinPoint jp) throws Throwable { + StopWatch watch = new StopWatch(); + try { + watch.start(); + return jp.proceed(); + } finally { + watch.stop(); + if (log.isInfoEnabled()) { + log.info("PT in method[{}] {}", + jp.getSignature().getName(), watch); + } + } + } + } + + public class Foo { + public void method1() { + log.info("method1 execution."); + } + + public void method2() { + log.info("method2 execution."); + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/aop/AspectTargetClassTest.java b/truman/src/main/java/com/diguage/truman/aop/AspectTargetClassTest.java new file mode 100644 index 000000000000..22db4cf6fc92 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/aop/AspectTargetClassTest.java @@ -0,0 +1,79 @@ +package com.diguage.truman.aop; + +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.*; +import org.springframework.core.type.AnnotationMetadata; + +/** + * 验证 target 的匹配规则。 + * + * @author D瓜哥, https://www.diguage.com/ + * @since 2021-07-19 23:28:27 + */ +@Slf4j +public class AspectTargetClassTest { + @Test + public void test() { + AnnotationConfigApplicationContext context + = new AnnotationConfigApplicationContext(); + context.register(Config.class); + context.refresh(); + + Movable bean = context.getBean(Movable.class); + bean.move(); + } + + /** + * 无论 (proxyTargetClass = true/false),切面都会执行 + */ + @Configuration + @Import(TargetImportSelector.class) + @EnableAspectJAutoProxy + public static class Config { + } + + public static class TargetImportSelector implements ImportSelector { + @Override + public String[] selectImports(AnnotationMetadata metadata) { + return new String[]{ + Rabbit.class.getName(), + TargetAspect.class.getName() + }; + } + } + + public interface Movable { + void move(); + } + + public static class Rabbit implements Movable { + @Override + public void move() { + log.info("Rabbit.move executing..."); + } + } + + @Aspect + public static class TargetAspect { + @Pointcut("target(com.diguage.truman.aop.AspectTargetClassTest.Rabbit)") + public void doTargetInterface() { + } + + @Around("doTargetInterface()") + public Object around(ProceedingJoinPoint joinPoint) throws Throwable { + String typeName = joinPoint.getSignature().getDeclaringTypeName(); + String methodName = joinPoint.getSignature().getName(); + try { + return joinPoint.proceed(); + } finally { + log.info("aspect executing. pointcut={}.{}", + typeName, methodName); + } + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/aop/AspectTargetInterfaceTest.java b/truman/src/main/java/com/diguage/truman/aop/AspectTargetInterfaceTest.java new file mode 100644 index 000000000000..e4741a9ec0e9 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/aop/AspectTargetInterfaceTest.java @@ -0,0 +1,76 @@ +package com.diguage.truman.aop; + +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.*; +import org.springframework.core.type.AnnotationMetadata; + +/** + * 验证 target 的匹配规则。 + * + * @author D瓜哥, https://www.diguage.com/ + * @since 2021-07-19 23:28:27 + */ +@Slf4j +public class AspectTargetInterfaceTest { + @Test + public void test() { + AnnotationConfigApplicationContext context + = new AnnotationConfigApplicationContext(); + context.register(Config.class); + context.refresh(); + + Movable bean = context.getBean(Movable.class); + bean.move(); + } + + @Configuration + @Import(TargetImportSelector.class) + @EnableAspectJAutoProxy // 注意:这行必须加 + public static class Config { + } + + public static class TargetImportSelector implements ImportSelector { + @Override + public String[] selectImports(AnnotationMetadata metadata) { + return new String[]{ + Rabbit.class.getName(), + TargetAspect.class.getName() + }; + } + } + + public interface Movable { + void move(); + } + + public static class Rabbit implements Movable { + @Override + public void move() { + log.info("Rabbit.move executing..."); + } + } + + @Aspect + public static class TargetAspect { + @Pointcut("target(com.diguage.truman.aop.AspectTargetInterfaceTest.Movable)") + public void doTargetInterface() { + } + + @Around("doTargetInterface()") + public Object around(ProceedingJoinPoint joinPoint) throws Throwable { + String typeName = joinPoint.getSignature().getDeclaringTypeName(); + String methodName = joinPoint.getSignature().getName(); + try { + return joinPoint.proceed(); + } finally { + log.info("aspect executing. pointcut={}.{}", + typeName, methodName); + } + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/aop/AspectThisClassTest.java b/truman/src/main/java/com/diguage/truman/aop/AspectThisClassTest.java new file mode 100644 index 000000000000..feb913bf7411 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/aop/AspectThisClassTest.java @@ -0,0 +1,81 @@ +package com.diguage.truman.aop; + +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.*; +import org.springframework.core.type.AnnotationMetadata; + +/** + * 验证 this 的匹配规则。 + * + * @author D瓜哥, https://www.diguage.com/ + * @since 2021-07-19 21:27:47 + */ +@Slf4j +public class AspectThisClassTest { + @Test + public void test() { + AnnotationConfigApplicationContext context + = new AnnotationConfigApplicationContext(); + context.register(Config.class); + context.refresh(); + + // TODO 竟然不能写成 Rabbit.class。 为什么? + Movable bean = context.getBean(Movable.class); + bean.move(); + } + + /** + * 如果 (proxyTargetClass = true),则切面会执行; + * 如果 (proxyTargetClass = false)(默认如此),则切面会执行。 + */ + @Configuration + @Import(ThisImportSelector.class) + @EnableAspectJAutoProxy //(proxyTargetClass = true) + public static class Config { + } + + public static class ThisImportSelector implements ImportSelector { + @Override + public String[] selectImports(AnnotationMetadata metadata) { + return new String[]{ + Rabbit.class.getName(), + ThisAspect.class.getName() + }; + } + } + + public interface Movable { + void move(); + } + + public static class Rabbit implements Movable { + @Override + public void move() { + log.info("Rabbit.move executing..."); + } + } + + @Aspect + public static class ThisAspect { + @Pointcut("this(com.diguage.truman.aop.AspectThisClassTest.Rabbit)") + public void doThisInterface() { + } + + @Around("doThisInterface()") + public Object around(ProceedingJoinPoint joinPoint) throws Throwable { + String typeName = joinPoint.getSignature().getDeclaringTypeName(); + String methodName = joinPoint.getSignature().getName(); + try { + return joinPoint.proceed(); + } finally { + log.info("aspect executing. pointcut={}.{}", + typeName, methodName); + } + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/aop/AspectThisInterfaceTest.java b/truman/src/main/java/com/diguage/truman/aop/AspectThisInterfaceTest.java new file mode 100644 index 000000000000..235ebb72b02c --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/aop/AspectThisInterfaceTest.java @@ -0,0 +1,77 @@ +package com.diguage.truman.aop; + +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.*; +import org.springframework.core.type.AnnotationMetadata; + +/** + * 验证 this 的匹配规则。 + * + * @author D瓜哥, https://www.diguage.com/ + * @since 2021-07-19 21:27:47 + */ +@Slf4j +public class AspectThisInterfaceTest { + @Test + public void test() { + AnnotationConfigApplicationContext context + = new AnnotationConfigApplicationContext(); + context.register(Config.class); + context.refresh(); + + // TODO 竟然不能写成 Rabbit.class。 为什么? + Movable bean = context.getBean(Movable.class); + bean.move(); + } + + @Configuration + @Import(ThisImportSelector.class) + @EnableAspectJAutoProxy // 注意:这行必须加 + public static class Config { + } + + public static class ThisImportSelector implements ImportSelector { + @Override + public String[] selectImports(AnnotationMetadata metadata) { + return new String[]{ + Rabbit.class.getName(), + ThisAspect.class.getName() + }; + } + } + + public interface Movable { + void move(); + } + + public static class Rabbit implements Movable { + @Override + public void move() { + log.info("Rabbit.move executing..."); + } + } + + @Aspect + public static class ThisAspect { + @Pointcut("this(com.diguage.truman.aop.AspectThisInterfaceTest.Movable)") + public void doThisInterface() { + } + + @Around("doThisInterface()") + public Object around(ProceedingJoinPoint joinPoint) throws Throwable { + String typeName = joinPoint.getSignature().getDeclaringTypeName(); + String methodName = joinPoint.getSignature().getName(); + try { + return joinPoint.proceed(); + } finally { + log.info("aspect executing. pointcut={}.{}", + typeName, methodName); + } + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/aop/AspectWithinTest.java b/truman/src/main/java/com/diguage/truman/aop/AspectWithinTest.java new file mode 100644 index 000000000000..33de88bb8c45 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/aop/AspectWithinTest.java @@ -0,0 +1,106 @@ +package com.diguage.truman.aop; + +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.*; +import org.springframework.core.type.AnnotationMetadata; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author D瓜哥, https://www.diguage.com/ + * @since 2021-07-18 21:18:43 + */ +@Slf4j +public class AspectWithinTest { + + @Test + public void test() { + AnnotationConfigApplicationContext context + = new AnnotationConfigApplicationContext(); + context.register(Config.class); + context.refresh(); + + DiguageTask task = context.getBean(DiguageTask.class); + task.run(); + } + + @Configuration + @Import(WithinImportSelector.class) + @EnableAspectJAutoProxy // 注意:这行必须加 + public static class Config { + } + + public static class WithinImportSelector implements ImportSelector { + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + return new String[]{ + DiguageTask.class.getName(), + WithinTypeAspect.class.getName(), + WithinMethodAspect.class.getName() + }; + } + } + + @WithinTypeAnnotation + public static class DiguageTask { + @WithinMethodAnnotation + public void run() { + log.info("Diguage.run executing..."); + } + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public @interface WithinTypeAnnotation { + } + + @Aspect + public static class WithinTypeAspect { + @Pointcut("@within(com.diguage.truman.aop." + + "AspectWithinTest.WithinTypeAnnotation)") + public void withinType() { + } + + @Around("withinType()") + public Object around(ProceedingJoinPoint jp) throws Throwable { + try { + return jp.proceed(); + } finally { + log.info("WithinTypeAspect executing..."); + } + } + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface WithinMethodAnnotation { + } + + @Aspect + public static class WithinMethodAspect { + @Pointcut("@within(com.diguage.truman.aop." + + "AspectWithinTest.WithinMethodAnnotation)") + public void withinMethod() { + } + + /** + * 这里的代码没有执行到,说明对于 @Within 注解来说,只支持类注解。 + */ + @Around("withinMethod()") + public Object around(ProceedingJoinPoint jp) throws Throwable { + try { + return jp.proceed(); + } finally { + log.info("WithinTypeAspect executing..."); + } + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/aop/DeclareParentsAopTest.java b/truman/src/main/java/com/diguage/truman/aop/DeclareParentsAopTest.java new file mode 100644 index 000000000000..52cd2083a5b2 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/aop/DeclareParentsAopTest.java @@ -0,0 +1,167 @@ +package com.diguage.truman.aop; + +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.Signature; +import org.aspectj.lang.annotation.*; +import org.junit.jupiter.api.Test; +import org.springframework.aop.framework.AopContext; +import org.springframework.context.annotation.*; +import org.springframework.core.type.AnnotationMetadata; + +import jakarta.annotation.Resource; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author D瓜哥, https://www.diguage.com/ + * @since 2020-07-09 12:23 + */ +@Slf4j +public class DeclareParentsAopTest { + @Test + public void test() { + AnnotationConfigApplicationContext context + = new AnnotationConfigApplicationContext(); + context.register(Config.class); + context.refresh(); + UserService bean = context.getBean(UserService.class); + bean.test(); + + String user = bean.getById(119); + System.out.println(user); + if (bean instanceof UsageTracked) { + System.out.println("OKKK"); + } + + UsageTracked tracked = (UsageTracked) context.getBean(UserService.class); + System.out.println(tracked.incrementUseCount()); + } + + @Configuration + @Import(AopImportSelector.class) + @EnableAspectJAutoProxy(exposeProxy = true) + public static class Config { + @Bean + public UsageTracked usageTracked() { + return new DefaultUsageTracked(); + } + } + + public static class AopImportSelector implements ImportSelector { + @Override + public String[] selectImports(AnnotationMetadata metadata) { + return new String[]{ + UserDao.class.getName(), + UserService.class.getName(), + UserServiceAspect.class.getName(), + DeclareParentsAspect.class.getName(), + DefaultUsageTracked.class.getName() + }; + } + } + + @Aspect + public static class UserServiceAspect { + @Pointcut("execution(* com.diguage.truman.aop." + + "MoreAopTest$UserService.test(..))") + public void test() { + } + + @Before("test()") + public void beforeTest() { + System.out.println("beforeTest"); + } + + @After("test()") + public void afterTest() { + System.out.println("afterTest"); + } + + @Around("test()") + public Object aroundTest(ProceedingJoinPoint pjp) { + System.out.println("aroundBefore1"); + Object restul = null; + Signature signature = pjp.getSignature(); + System.out.println(pjp.getKind()); + Object target = pjp.getTarget(); + log.info(target.getClass().getName() + "#" + signature.getName()); + try { + restul = pjp.proceed(); + } catch (Throwable throwable) { + throwable.printStackTrace(); + } + System.out.println("aroundAfter1"); + return restul; + } + } + + @Aspect + public static class DeclareParentsAspect { + + // 目前这部分是 OK了。 + + + @DeclareParents(value = "com.diguage.truman.aop." + + "DeclareParentsAopTest.UserService+", + defaultImpl = DefaultUsageTracked.class) + public static UsageTracked mixin; + + // TODO 还不成功,纳闷,不知道该怎么弄? +// @Before("com.diguage.truman.aop.MoreAopTest$UserService.test() && this(usageTracked)") +// public void recordUsage(UsageTracked usageTracked) { +// usageTracked.incrementUseCount(); +// } + } + + public static interface UsageTracked { + int incrementUseCount(); + } + + + public static class DefaultUsageTracked implements UsageTracked { + private AtomicInteger count = new AtomicInteger(0); + + @Override + public int incrementUseCount() { + return count.incrementAndGet(); + } + } + + + public static class UserDao { + public String getById(int id) { + return "diguage-" + id; + } + } + + public static class UserService { + private String desc = "testBean"; + + @Resource + private UserDao userDao; + + public String getDesc() { + log.info("getDesc"); + this.test(); + log.info("--this----------getDesc"); + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + // 使用 @EnableAspectJAutoProxy(exposeProxy = true) 打开 exposeProxy = true + // 则必须这样写,才能获取到当前的代理对象,然后调用的方法才是被 AOP 处理后的方法。 + // 使用 this.methodName() 调用,依然调用的是原始的、未经 AOP 处理的方法 + ((UserService) AopContext.currentProxy()).test(); + log.info("--AopContext----setDesc"); + } + + public void test() { + log.info("----------------test"); + } + + public String getById(int id) { + return userDao.getById(id); + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/aop/FinalTest.java b/truman/src/main/java/com/diguage/truman/aop/FinalTest.java new file mode 100644 index 000000000000..e1fe8cd6ce5d --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/aop/FinalTest.java @@ -0,0 +1,131 @@ +package com.diguage.truman.aop; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.Signature; +import org.aspectj.lang.annotation.After; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.annotation.Pointcut; +import org.junit.jupiter.api.Test; +import org.springframework.aop.framework.AopContext; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.ImportSelector; +import org.springframework.core.type.AnnotationMetadata; + +import jakarta.annotation.Resource; + +/** + * @author D瓜哥, https://www.diguage.com/ + * @since 2020-06-02 11:12 + */ +public class FinalTest { + @Test + public void test() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(Config.class); + context.refresh(); + UserService bean = context.getBean(UserService.class); + bean.test(); + bean.getDesc(); + bean.setDesc("This is a test."); + + String user = bean.getById(119); + System.out.println(user); + + BeanDefinition definition = context.getBeanDefinition(UserService.class.getName()); + System.out.println(definition); + } + + @Configuration + @Import(AopImportSelector.class) + @EnableAspectJAutoProxy(exposeProxy = true) + public static class Config { + } + + public static class AopImportSelector implements ImportSelector { + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + return new String[]{ + UserDao.class.getName(), + UserService.class.getName(), + TestAspect.class.getName() + }; + } + } + + @Aspect + public static class TestAspect { + @Pointcut("execution(* com.diguage.truman.aop.FinalTest$UserService.test(..))") + public void test() { + } + + @Before("test()") + public void beforeTest() { + System.out.println("beforeTest"); + } + + @After("test()") + public void afterTest() { + System.out.println("afterTest"); + } + + @Around("test()") + public Object aroundTest(ProceedingJoinPoint pjp) { + System.out.println("aroundBefore1"); + Object restul = null; + Signature signature = pjp.getSignature(); + System.out.println(pjp.getKind()); + Object target = pjp.getTarget(); + System.out.println(target.getClass().getName() + "#" + signature.getName()); + try { + restul = pjp.proceed(); + } catch (Throwable throwable) { + throwable.printStackTrace(); + } + System.out.println("aroundAfter1"); + return restul; + } + } + + public static class UserDao { + public String getById(int id) { + return "diguage-" + id; + } + } + + public static final class UserService { + private String desc = "testBean"; + + @Resource + private UserDao userDao; + + public String getDesc() { + System.out.println("getDesc"); + this.test(); + System.out.println("--this----------getDesc"); + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + // 使用 @EnableAspectJAutoProxy(exposeProxy = true) 打开 exposeProxy = true + // 则必须这样写,才能获取到当前的代理对象,然后调用的方法才是被 AOP 处理后的方法。 + // 使用 this.methodName() 调用,依然调用的是原始的、未经 AOP 处理的方法 + ((UserService) AopContext.currentProxy()).test(); + System.out.println("--AopContext----setDesc"); + } + + public void test() { + System.out.println("----------------test"); + } + + public String getById(int id) { + return userDao.getById(id); + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/aop/HotSwappableTargetSourceTest.java b/truman/src/main/java/com/diguage/truman/aop/HotSwappableTargetSourceTest.java new file mode 100644 index 000000000000..5c7652d88757 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/aop/HotSwappableTargetSourceTest.java @@ -0,0 +1,137 @@ +package com.diguage.truman.aop; + +import lombok.Data; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.aop.MethodBeforeAdvice; +import org.springframework.aop.TargetSource; +import org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor; +import org.springframework.aop.framework.ProxyFactoryBean; +import org.springframework.aop.target.HotSwappableTargetSource; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; + +import jakarta.annotation.Resource; +import javax.sql.DataSource; +import java.lang.reflect.Method; +import java.sql.Connection; +import java.sql.SQLException; + +import static org.mockito.Mockito.when; + +/** + * 根据配置透明切换数据源的插件 + *

+ * https://afoo.me/posts/2005-08-10-improve-datasources-swap-solution.html + * + * @author D瓜哥, https://www.diguage.com + * @since 2021-07-16 23:59:56 + */ +public class HotSwappableTargetSourceTest { + private static final String PRIMARY_DATASOURCE = "primaryDatasource"; + private static final String SLAVE_DATASOURCE = "slaveDatasource"; + private static final String ADVISOR_NAME = "swapDataSourceAdvisor"; + private static boolean usePrimary = true; + + @Test + public void test() throws SQLException { + AnnotationConfigApplicationContext context + = new AnnotationConfigApplicationContext();context.register(Config.class); + context.refresh(); + + DataSource primaryDatasource = (DataSource) context.getBean(PRIMARY_DATASOURCE); + DataSource slaveDatasource = (DataSource) context.getBean(SLAVE_DATASOURCE); + + when(primaryDatasource.getConnection()) + .thenReturn(Mockito.mock(Connection.class, "primaryConnection")); + when(slaveDatasource.getConnection()) + .thenReturn(Mockito.mock(Connection.class, "slaveConnection")); + + DataSource dataSource = (DataSource) context.getBean("dataSource"); + + System.out.println(dataSource.getConnection()); + usePrimary = !usePrimary; + System.out.println(dataSource.getConnection()); + System.out.println(dataSource.getConnection()); + usePrimary = !usePrimary; + System.out.println(dataSource.getConnection()); + } + + @Configuration + @EnableAspectJAutoProxy + public static class Config { + @Bean(PRIMARY_DATASOURCE) + public DataSource primaryDatasource() { + return Mockito.mock(DataSource.class, PRIMARY_DATASOURCE); + } + + @Bean(SLAVE_DATASOURCE) + public DataSource slaveDatasource() { + return Mockito.mock(DataSource.class, SLAVE_DATASOURCE); + } + + @Bean + public HotSwappableTargetSource hotSwappableTargetSource( + @Qualifier(PRIMARY_DATASOURCE) DataSource dataSource) { + return new HotSwappableTargetSource(dataSource); + } + + @Bean + public SwapDataSourceAdvice swapDataSourceAdvice() { + return new SwapDataSourceAdvice(); + } + +// /** +// * 使用 Spring 自制的 Advisor +// */ +// @Bean(ADVISOR_NAME) +// public RegexpMethodPointcutAdvisor methodAdvisor(SwapDataSourceAdvice advice) { +// RegexpMethodPointcutAdvisor advisor = new RegexpMethodPointcutAdvisor(); +// advisor.setAdvice(advice); +// advisor.setPattern(".*getConnection.*"); +// return advisor; +// } + + /** + * 使用 AspectJ 表达式的 Advisor + */ + @Bean(ADVISOR_NAME) + public AspectJExpressionPointcutAdvisor methodAdvisor(SwapDataSourceAdvice advice) { + AspectJExpressionPointcutAdvisor advisor + = new AspectJExpressionPointcutAdvisor(); + advisor.setAdvice(advice); + advisor.setExpression("target(javax.sql.DataSource) " + + "&& execution(java.sql.Connection getConnection(..))"); + return advisor; + } + + @Bean("dataSource") + public ProxyFactoryBean getProxyFactoryBean(TargetSource targetSource) { + ProxyFactoryBean result = new ProxyFactoryBean(); + result.setTargetSource(targetSource); + result.setInterceptorNames(ADVISOR_NAME); + return result; + } + } + + @Data + public static class SwapDataSourceAdvice implements MethodBeforeAdvice { + @Resource(name = PRIMARY_DATASOURCE) + private DataSource primaryDatasource; + + @Resource(name = SLAVE_DATASOURCE) + private DataSource slaveDatasource; + + @Resource + private HotSwappableTargetSource targetSource; + + @Override + public void before(Method method, Object[] args, Object target) throws Throwable { + DataSource used = usePrimary ? primaryDatasource : slaveDatasource; + targetSource.swap(used); + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/aop/HotSwappableTargetSourceXmlTest.java b/truman/src/main/java/com/diguage/truman/aop/HotSwappableTargetSourceXmlTest.java new file mode 100644 index 000000000000..c01edf935422 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/aop/HotSwappableTargetSourceXmlTest.java @@ -0,0 +1,92 @@ +package com.diguage.truman.aop; + +import lombok.Data; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.aop.MethodBeforeAdvice; +import org.springframework.aop.target.HotSwappableTargetSource; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +import jakarta.annotation.Resource; +import javax.sql.DataSource; +import java.lang.reflect.Method; +import java.sql.Connection; +import java.sql.SQLException; + +import static com.diguage.truman.util.Constans.BASE_CLASS_PATH; +import static org.mockito.Mockito.when; + +/** + * 根据配置透明切换数据源的插件 + *

+ * https://afoo.me/posts/2005-08-10-improve-datasources-swap-solution.html + * + * @author D瓜哥, https://www.diguage.com + * @since 2021-07-16 23:59:56 + */ +public class HotSwappableTargetSourceXmlTest { + private static final String PRIMARY_DATASOURCE = "primaryDatasource"; + private static final String SLAVE_DATASOURCE = "slaveDatasource"; + private static boolean usePrimary = true; + + @Test + public void test() throws SQLException { + ApplicationContext context + = new ClassPathXmlApplicationContext( + BASE_CLASS_PATH + "/aop/HotSwappableTargetSource.xml"); + + DataSource primaryDatasource = (DataSource) context.getBean(PRIMARY_DATASOURCE); + DataSource slaveDatasource = (DataSource) context.getBean(SLAVE_DATASOURCE); + + when(primaryDatasource.getConnection()) + .thenReturn(Mockito.mock(Connection.class, "primaryConnection")); + when(slaveDatasource.getConnection()) + .thenReturn(Mockito.mock(Connection.class, "slaveConnection")); + + DataSource dataSource = (DataSource) context.getBean("dataSource"); + + System.out.println(dataSource.getConnection()); + usePrimary = !usePrimary; + System.out.println(dataSource.getConnection()); + System.out.println(dataSource.getConnection()); + usePrimary = !usePrimary; + System.out.println(dataSource.getConnection()); + } + + @Configuration + @EnableAspectJAutoProxy + public static class Config { + @Bean(PRIMARY_DATASOURCE) + public DataSource primaryDatasource() { + return Mockito.mock(DataSource.class, PRIMARY_DATASOURCE); + } + + @Bean(SLAVE_DATASOURCE) + public DataSource slaveDatasource() { + return Mockito.mock(DataSource.class, SLAVE_DATASOURCE); + } + } + + @Data + public static class SwapDataSourceAdvice implements MethodBeforeAdvice { + @Resource(name = PRIMARY_DATASOURCE) + private DataSource primaryDatasource; + + @Resource(name = SLAVE_DATASOURCE) + private DataSource slaveDatasource; + + @Resource + private HotSwappableTargetSource targetSource; + + + @Override + public void before(Method method, Object[] args, Object target) throws Throwable { + DataSource used = usePrimary ? primaryDatasource : slaveDatasource; + targetSource.swap(used); + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/aop/IntroductionTest.java b/truman/src/main/java/com/diguage/truman/aop/IntroductionTest.java new file mode 100644 index 000000000000..8f9461592e2d --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/aop/IntroductionTest.java @@ -0,0 +1,14 @@ +package com.diguage.truman.aop; + +import org.junit.jupiter.api.Test; + +/** + * @author D瓜哥, https://www.diguage.com/ + * @since 2020-08-09 12:13 + */ +public class IntroductionTest { + @Test + public void test() { + // 测试引介增强 + } +} diff --git a/truman/src/main/java/com/diguage/truman/aop/MoreAopTest.java b/truman/src/main/java/com/diguage/truman/aop/MoreAopTest.java new file mode 100644 index 000000000000..de8cc7634dfa --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/aop/MoreAopTest.java @@ -0,0 +1,246 @@ +package com.diguage.truman.aop; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.Signature; +import org.aspectj.lang.annotation.After; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.annotation.DeclareParents; +import org.aspectj.lang.annotation.Pointcut; +import org.junit.jupiter.api.Test; +import org.springframework.aop.framework.AopContext; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.ImportSelector; +import org.springframework.core.type.AnnotationMetadata; + +import jakarta.annotation.Resource; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author D瓜哥, https://www.diguage.com/ + * @since 2020-06-02 11:12 + */ +public class MoreAopTest { + @Test + public void test() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(Config.class); + context.refresh(); + UserService bean = context.getBean(UserService.class); + bean.test(); + bean.test(); + + String user = bean.getById(119); + System.out.println(user); + + BeanDefinition definition = context.getBeanDefinition(UserService.class.getName()); + System.out.println(definition); + + OrderServiceImpl orderService = context.getBean(OrderServiceImpl.class); + System.out.println(orderService.getById(120)); + + System.out.println(context.getBean(OrderServiceImpl.class).getById(122)); + } + + @Configuration + @Import(AopImportSelector.class) + @EnableAspectJAutoProxy(exposeProxy = true) + public static class Config { + @Bean + public UsageTracked usageTracked() { + return new DefaultUsageTracked(); + } + } + + public static class AopImportSelector implements ImportSelector { + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + return new String[]{ + UserDao.class.getName(), + UserService.class.getName(), + OrderServiceImpl.class.getName(), + UserServiceAspect.class.getName(), + UserServiceAspect2.class.getName(), + OrderServiceAspect.class.getName(), + UsageTrackingAspect.class.getName() + }; + } + } + + @Aspect + public static class UserServiceAspect { + @Pointcut("execution(* com.diguage.truman.aop.MoreAopTest$UserService.test(..))") + public void test() { + } + + @Before("test()") + public void beforeTest() { + System.out.println("beforeTest"); + } + + @After("test()") + public void afterTest() { + System.out.println("afterTest"); + } + + @Around("test()") + public Object aroundTest(ProceedingJoinPoint pjp) { + System.out.println("aroundBefore1"); + Object restul = null; + Signature signature = pjp.getSignature(); + System.out.println(pjp.getKind()); + Object target = pjp.getTarget(); + System.out.println(target.getClass().getName() + "#" + signature.getName()); + try { + restul = pjp.proceed(); + } catch (Throwable throwable) { + throwable.printStackTrace(); + } + System.out.println("aroundAfter1"); + return restul; + } + } + + @Aspect + public static class UserServiceAspect2 { + @Pointcut("execution(* com.diguage.truman.aop.MoreAopTest$UserService.test(..))") + public void test() { + } + + @Before("test()") + public void beforeTest() { + System.out.println("222-beforeTest"); + } + + @After("test()") + public void afterTest() { + System.out.println("222-afterTest"); + } + + @Around("test()") + public Object aroundTest(ProceedingJoinPoint pjp) { + System.out.println("222-aroundBefore1"); + Object restul = null; + Signature signature = pjp.getSignature(); + System.out.println(pjp.getKind()); + Object target = pjp.getTarget(); + System.out.println(target.getClass().getName() + "#" + signature.getName()); + try { + restul = pjp.proceed(); + } catch (Throwable throwable) { + throwable.printStackTrace(); + } + System.out.println("222-aroundAfter1"); + return restul; + } + } + + @Aspect + public static class UsageTrackingAspect { + + @DeclareParents(value = "com.diguage.truman.aop.*+", defaultImpl = DefaultUsageTracked.class) + public static UsageTracked mixin; + + @Before("execution(* com.diguage.truman.aop.MoreAopTest$OrderServiceImpl.getById(..)) && this(usageTracked)") + public void recordUsage(UsageTracked usageTracked) { + usageTracked.incrementUseCount(); + } + } + + public interface UsageTracked { + int incrementUseCount(); + } + + + public static class DefaultUsageTracked implements UsageTracked { + private AtomicInteger count = new AtomicInteger(0); + + @Override + public int incrementUseCount() { + return count.incrementAndGet(); + } + } + + @Aspect + public static class OrderServiceAspect { + @Pointcut("execution(* com.diguage.truman.aop.MoreAopTest$OrderService.getById(..))") + public void getById() { + } + + @Before("getById()") + public void beforeGetById() { + System.out.println("beforeTest"); + } + + @Around("getById()") + public Object aroundGetById(ProceedingJoinPoint pjp) { + System.out.println("aroundBefore1"); + Object restul = null; + Signature signature = pjp.getSignature(); + System.out.println(pjp.getKind()); + Object target = pjp.getTarget(); + System.out.println(target.getClass().getName() + "#" + signature.getName()); + try { + restul = pjp.proceed(); + } catch (Throwable throwable) { + throwable.printStackTrace(); + } + System.out.println("aroundAfter1"); + return restul; + } + } + + + public static class UserDao { + public String getById(int id) { + return "diguage-" + id; + } + } + + public static class UserService { + private String desc = "testBean"; + + @Resource + private UserDao userDao; + + public String getDesc() { + System.out.println("getDesc"); + this.test(); + System.out.println("--this----------getDesc"); + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + // 使用 @EnableAspectJAutoProxy(exposeProxy = true) 打开 exposeProxy = true + // 则必须这样写,才能获取到当前的代理对象,然后调用的方法才是被 AOP 处理后的方法。 + // 使用 this.methodName() 调用,依然调用的是原始的、未经 AOP 处理的方法 + ((UserService) AopContext.currentProxy()).test(); + System.out.println("--AopContext----setDesc"); + } + + public void test() { + System.out.println("----------------test"); + } + + public String getById(int id) { + return userDao.getById(id); + } + } + + public interface OrderService { + String getById(int id); + } + + public static class OrderServiceImpl implements OrderService { + public String getById(int id) { + return "Order-" + id; + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/aop/ProxyFactoryTest.java b/truman/src/main/java/com/diguage/truman/aop/ProxyFactoryTest.java new file mode 100644 index 000000000000..176142aa6a30 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/aop/ProxyFactoryTest.java @@ -0,0 +1,54 @@ +package com.diguage.truman.aop; + +import org.junit.jupiter.api.Test; +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.aop.interceptor.PerformanceMonitorInterceptor; +import org.springframework.aop.support.NameMatchMethodPointcutAdvisor; + +/** + * @author D瓜哥, https://www.diguage.com + */ +public class ProxyFactoryTest { + @Test + public void testJdkProxy() { + MockExecutable mockTask = new MockExecutable(); + ProxyFactory factory = new ProxyFactory(mockTask); + factory.setInterfaces(Executable.class); + NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor(); + advisor.setMappedName("execute"); + advisor.setAdvice(new PerformanceMonitorInterceptor()); + factory.addAdvisor(advisor); + Executable proxyExecutable = (Executable) factory.getProxy(); + System.out.println(proxyExecutable.getClass()); + proxyExecutable.execute(); + } + + public interface Executable { + void execute(); + } + + public static class MockExecutable implements Executable { + @Override + public void execute() { + System.out.println("MockTask.execute"); + } + } + + @Test + public void testCglibProxy() { + ProxyFactory factory = new ProxyFactory(new Task()); + NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor(); + advisor.setMappedName("execute"); + advisor.setAdvice(new PerformanceMonitorInterceptor()); + factory.addAdvisor(advisor); + Task proxyTask = (Task) factory.getProxy(); + System.out.println(proxyTask.getClass()); + proxyTask.execute(); + } + + public static class Task { + public void execute() { + System.out.println("Task.execute"); + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/aop/ProxyTargetClassTest.java b/truman/src/main/java/com/diguage/truman/aop/ProxyTargetClassTest.java new file mode 100644 index 000000000000..f2626a3e6058 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/aop/ProxyTargetClassTest.java @@ -0,0 +1,123 @@ +package com.diguage.truman.aop; + +import jakarta.annotation.Resource; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.Signature; +import org.aspectj.lang.annotation.*; +import org.junit.jupiter.api.Test; +import org.springframework.aop.framework.AopContext; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.*; +import org.springframework.core.type.AnnotationMetadata; + +/** + * @author D瓜哥, https://www.diguage.com + */ +public class ProxyTargetClassTest { + @Test + public void test() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(Config.class); + context.refresh(); + UserService bean = context.getBean(UserService.class); + bean.test(); + bean.getDesc(); + bean.setDesc("This is a test."); + + String user = bean.getById(119); + System.out.println(user); + + BeanDefinition definition = context.getBeanDefinition(UserService.class.getName()); + System.out.println(definition); + } + + @Configuration + @Import(AopImportSelector.class) + @EnableAspectJAutoProxy(exposeProxy = true, proxyTargetClass = true) + public static class Config { + } + + // 使用 @Import 和 ImportSelector 搭配,就可以省去 XML 配置 + public static class AopImportSelector implements ImportSelector { + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + return new String[]{ + UserDao.class.getName(), + UserService.class.getName(), + TestAspect.class.getName() + }; + } + } + + @Aspect + public static class TestAspect { + @Pointcut("execution(* com.diguage.truman.aop.ProxyTargetClassTest.UserService.test(..))") + public void pointcut() { + } + + @Before("pointcut()") + public void beforeTest() { + System.out.println("beforeTest"); + } + + @After("pointcut()") + public void afterTest() { + System.out.println("afterTest"); + } + + @Around("pointcut()") + public Object aroundTest(ProceedingJoinPoint pjp) { + System.out.println("aroundBefore1"); + Object restul = null; + Signature signature = pjp.getSignature(); + System.out.println(pjp.getKind()); + Object target = pjp.getTarget(); + System.out.println(target.getClass().getName() + "#" + signature.getName()); + try { + restul = pjp.proceed(); + } catch (Throwable throwable) { + throwable.printStackTrace(); + } + System.out.println("aroundAfter1"); + return restul; + } + } + + public static class UserDao { + public String getById(int id) { + return "diguage-" + id; + } + } + + public static class UserService { + private String desc = "testBean"; + + @Resource + private UserDao userDao; + + public String getDesc() { + System.out.println("getDesc"); + this.test(); + System.out.println("--this----------getDesc"); + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + // 使用 @EnableAspectJAutoProxy(exposeProxy = true) 打开 exposeProxy = true + // 则必须这样写,才能获取到当前的代理对象,然后调用的方法才是被 AOP 处理后的方法。 + // 使用 this.methodName() 调用,依然调用的是原始的、未经 AOP 处理的方法 + ((UserService) AopContext.currentProxy()).test(); + System.out.println("--AopContext----setDesc"); + } + + public void test() { + System.out.println("----------------test"); + } + + public String getById(int id) { + return userDao.getById(id); + } + } + +} diff --git a/truman/src/main/java/com/diguage/truman/aop/TargetSourceTest.java b/truman/src/main/java/com/diguage/truman/aop/TargetSourceTest.java new file mode 100644 index 000000000000..b44c934800b6 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/aop/TargetSourceTest.java @@ -0,0 +1,189 @@ +package com.diguage.truman.aop; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.Signature; +import org.aspectj.lang.annotation.After; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.annotation.Pointcut; +import org.junit.jupiter.api.Test; +import org.springframework.aop.TargetSource; +import org.springframework.aop.framework.AopContext; +import org.springframework.aop.framework.ProxyFactoryBean; +import org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator; +import org.springframework.aop.framework.autoproxy.TargetSourceCreator; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.ImportSelector; +import org.springframework.core.type.AnnotationMetadata; + +import java.util.Arrays; + + +/** + * @author D瓜哥, https://www.diguage.com/ + * @since 2020-06-21 11:03 + */ +public class TargetSourceTest { + @Test + public void test() throws Exception { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(Config.class); + context.refresh(); + String[] names = context.getBeanDefinitionNames(); + System.out.println(Arrays.toString(names).replaceAll(",", ",\n")); +// UserServiceTargetSource targetSource = context.getBean(UserServiceTargetSource.class); +// UserService bean = (UserService) targetSource.getTarget(); +// +// bean.test(); +// +// bean.getDesc(); +// bean.setDesc("This is a test."); + } + + @Configuration + @Import(AopImportSelector.class) + @EnableAspectJAutoProxy(exposeProxy = true) + public static class Config { + + public UserServiceTargetSource userServiceTargetSource(UserService userService) { + return new UserServiceTargetSource(userService); + } + + public TargetSourceCreator targetSourceCreator() { + return new TargetSourceCreator() { + @Override + public TargetSource getTargetSource(Class beanClass, String beanName) { + if (beanClass.equals(UserService.class)) { + return userServiceTargetSource((UserService) BeanUtils.instantiateClass(beanClass)); + } + return null; + } + }; + } + + @Bean + BeanNameAutoProxyCreator beanNameAutoProxyCreator() { + BeanNameAutoProxyCreator autoProxyCreator = new BeanNameAutoProxyCreator(); + autoProxyCreator.setBeanNames("*"); + autoProxyCreator.setCustomTargetSourceCreators(targetSourceCreator()); + return autoProxyCreator; + } + +// @Bean +// public ProxyFactoryBean getProxyFactoryBean(@Autowired UserServiceTargetSource userServiceTargetSource) { +// ProxyFactoryBean result = new ProxyFactoryBean(); +// result.setTargetSource(userServiceTargetSource); +// return result; +// } + } + + public static class AopImportSelector implements ImportSelector { + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + return new String[]{ + TestAspect.class.getName() + }; + } + } + + @Aspect + public static class TestAspect { + @Pointcut("execution(* com.diguage.truman.aop.TargetSourceTest$UserService.test(..))") + public void test() { + } + + @Before("test()") + public void beforeTest() { + System.out.println("beforeTest"); + } + + @After("test()") + public void afterTest() { + System.out.println("afterTest"); + } + + @Around("test()") + public Object aroundTest(ProceedingJoinPoint pjp) { + System.out.println("aroundBefore1"); + Object restul = null; + Signature signature = pjp.getSignature(); + System.out.println(pjp.getKind()); + Object target = pjp.getTarget(); + System.out.println(target.getClass().getName() + "#" + signature.getName()); + try { + restul = pjp.proceed(); + } catch (Throwable throwable) { + throwable.printStackTrace(); + } + System.out.println("aroundAfter1"); + return restul; + } + } + + public static class UserDao { + public String getById(int id) { + return "diguage-" + id; + } + } + + public static class UserService { + private String desc = "testBean"; + + + public String getDesc() { + System.out.println("getDesc"); + this.test(); + System.out.println("--this----------getDesc"); + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + // 使用 @EnableAspectJAutoProxy(exposeProxy = true) 打开 exposeProxy = true + // 则必须这样写,才能获取到当前的代理对象,然后调用的方法才是被 AOP 处理后的方法。 + // 使用 this.methodName() 调用,依然调用的是原始的、未经 AOP 处理的方法 + ((UserService) AopContext.currentProxy()).test(); + System.out.println("--AopContext----setDesc"); + } + + public void test() { + System.out.println("----------------test"); + } + } + + public static class UserServiceTargetSource implements TargetSource { + + private UserService userService; + + public UserServiceTargetSource(@Autowired UserService userService) { + this.userService = userService; + } + + @Override + public Class getTargetClass() { + return UserService.class; + } + + @Override + public boolean isStatic() { + return false; + } + + @Override + public Object getTarget() throws Exception { + return userService; + } + + @Override + public void releaseTarget(Object target) throws Exception { + } + } + +} diff --git a/truman/src/main/java/com/diguage/truman/aop/xml/AopXmlTest.java b/truman/src/main/java/com/diguage/truman/aop/xml/AopXmlTest.java new file mode 100644 index 000000000000..de5a8a3de59e --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/aop/xml/AopXmlTest.java @@ -0,0 +1,27 @@ +package com.diguage.truman.aop.xml; + +import org.junit.jupiter.api.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +import static com.diguage.truman.util.Constans.BASE_CLASS_PATH; + +/** + * @author D瓜哥 · https://www.diguage.com + * @since 2020-08-09 10:27 + */ +public class AopXmlTest { + @Test + public void test() { + ClassPathXmlApplicationContext context + = new ClassPathXmlApplicationContext(BASE_CLASS_PATH + "/aop/xml/aop-xml.xml"); + + UserService bean = context.getBean(UserService.class); + + bean.test(); + bean.getDesc(); + bean.setDesc("This is a test."); + + String user = bean.getById(119); + System.out.println(user); + } +} diff --git a/truman/src/main/java/com/diguage/truman/aop/xml/TestAdvice.java b/truman/src/main/java/com/diguage/truman/aop/xml/TestAdvice.java new file mode 100644 index 000000000000..115cfaa1061f --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/aop/xml/TestAdvice.java @@ -0,0 +1,35 @@ +package com.diguage.truman.aop.xml; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.Signature; + +/** + * @author D瓜哥, https://www.diguage.com/ + * @since 2020-08-09 10:40:17 + */ +public class TestAdvice { + + public void beforeTest() { + System.out.println("beforeTest"); + } + + public void afterTest() { + System.out.println("afterTest"); + } + + public Object aroundTest(ProceedingJoinPoint pjp) { + System.out.println("aroundBefore1"); + Object restul = null; + Signature signature = pjp.getSignature(); + System.out.println(pjp.getKind()); + Object target = pjp.getTarget(); + System.out.println(target.getClass().getName() + "#" + signature.getName()); + try { + restul = pjp.proceed(); + } catch (Throwable throwable) { + throwable.printStackTrace(); + } + System.out.println("aroundAfter1"); + return restul; + } +} diff --git a/truman/src/main/java/com/diguage/truman/aop/xml/UserDao.java b/truman/src/main/java/com/diguage/truman/aop/xml/UserDao.java new file mode 100644 index 000000000000..6fe3a8c3a71e --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/aop/xml/UserDao.java @@ -0,0 +1,11 @@ +package com.diguage.truman.aop.xml; + +/** + * @author D瓜哥, https://www.diguage.com/ + * @since 2020-08-09 10:30 + */ +public class UserDao { + public String getById(int id) { + return "diguage-" + id; + } +} diff --git a/truman/src/main/java/com/diguage/truman/aop/xml/UserService.java b/truman/src/main/java/com/diguage/truman/aop/xml/UserService.java new file mode 100644 index 000000000000..507a7ec14f76 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/aop/xml/UserService.java @@ -0,0 +1,41 @@ +package com.diguage.truman.aop.xml; + +import com.diguage.truman.aop.AopTest; +import org.springframework.aop.framework.AopContext; + +import jakarta.annotation.Resource; + +/** + * @author D瓜哥, https://www.diguage.com/ + * @since 2020-08-09 10:31 + */ +public class UserService { + private String desc = "testBean"; + + @Resource + private UserDao userDao; + + public String getDesc() { + System.out.println("getDesc"); + this.test(); + System.out.println("--this----------getDesc"); + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + // 使用 @EnableAspectJAutoProxy(exposeProxy = true) 打开 exposeProxy = true + // 则必须这样写,才能获取到当前的代理对象,然后调用的方法才是被 AOP 处理后的方法。 + // 使用 this.methodName() 调用,依然调用的是原始的、未经 AOP 处理的方法 + ((AopTest.UserService) AopContext.currentProxy()).test(); + System.out.println("--AopContext----setDesc"); + } + + public void test() { + System.out.println("----------------test"); + } + + public String getById(int id) { + return userDao.getById(id); + } +} diff --git a/truman/src/main/java/com/diguage/truman/beans/BeanUtilsTest.java b/truman/src/main/java/com/diguage/truman/beans/BeanUtilsTest.java new file mode 100644 index 000000000000..b903f3864025 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/beans/BeanUtilsTest.java @@ -0,0 +1,45 @@ +package com.diguage.truman.beans; + +import lombok.Data; +import lombok.NoArgsConstructor; +import org.junit.jupiter.api.Test; +import org.springframework.beans.BeanUtils; + +import java.math.BigDecimal; +import java.util.Date; + +public class BeanUtilsTest { + @Test + public void test() { + Person source = new Person(); + source.setFirstName("D瓜哥"); + source.setSecondName("diguage"); + source.setLastName("www.diguage.com"); + source.setBirthday(new Date()); + source.setSex(1); + source.setWeight(145.0F); + source.setHeight(175.0F); + source.setAddress("https://www.diguage.com"); + source.setSalary(BigDecimal.ZERO); + source.setCapital(BigDecimal.ONE); + + Person target = new Person(); + BeanUtils.copyProperties(source, target); + System.out.println(target); + } + + @Data + @NoArgsConstructor + public static class Person { + private String firstName; + private String secondName; + private String lastName; + private Date birthday; + private int sex; + private float weight; + private float height; + private String address; + private BigDecimal salary; + private BigDecimal capital; + } +} diff --git a/truman/src/main/java/com/diguage/truman/beans/FactoryBeanTest.java b/truman/src/main/java/com/diguage/truman/beans/FactoryBeanTest.java new file mode 100644 index 000000000000..80b32e665e7a --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/beans/FactoryBeanTest.java @@ -0,0 +1,83 @@ +package com.diguage.truman.beans; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.Arrays; + +/** + * FactoryBean 测试 + * + * @author D瓜哥 · https://www.diguage.com/ + * @since 2020-05-26 16:34 + */ +public class FactoryBeanTest { + private static final String FACTORY_BEAN_NAME = "userServiceFactoryBean"; + + @Test + public void test() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(Config.class); + context.refresh(); + + Object bean = context.getBean(FACTORY_BEAN_NAME); + System.out.println(bean.getClass().getName()); + + UserService userService = context.getBean(UserService.class); + System.out.println(userService.getById(119L)); + + // 实例不会缓存,每次调用 getBean 都会创建一个实例 + UserService userService2 = context.getBean(UserService.class); + System.out.println(userService == userService2); + + System.out.println("-↓----"); + // &userServiceFactoryBean = FactoryBeanTest$UserServiceFactoryBean@c260bdc + System.out.println("&userServiceFactoryBean = " + + context.getBean("&userServiceFactoryBean")); // <1> + + // userServiceFactoryBean = FactoryBeanTest$UserService@75e01201 + System.out.println(" userServiceFactoryBean = " + + context.getBean("userServiceFactoryBean")); // <2> + System.out.println("-↑----"); + + UserServiceFactoryBean factoryBean = context.getBean(UserServiceFactoryBean.class); + System.out.println(factoryBean); + System.out.println(Arrays.toString(context.getBeanDefinitionNames()) + .replaceAll(",", ",\n")); + } + + @Configuration + public static class Config { + @Bean(FACTORY_BEAN_NAME) + public UserServiceFactoryBean userServiceFactoryBean() { + return new UserServiceFactoryBean(); + } + } + + + public static class UserService { + public String getById(Long id) { + return "Name-" + id; + } + } + + public static class UserServiceFactoryBean implements FactoryBean { + @Override + public UserService getObject() throws Exception { + return new UserService(); + } + + @Override + public Class getObjectType() { + return UserService.class; + } + + @Override + public boolean isSingleton() { + return true; + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/beans/env/PropertyPlaceholderConfigurerTest.java b/truman/src/main/java/com/diguage/truman/beans/env/PropertyPlaceholderConfigurerTest.java new file mode 100644 index 000000000000..f94eb88f7677 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/beans/env/PropertyPlaceholderConfigurerTest.java @@ -0,0 +1,77 @@ +package com.diguage.truman.beans.env; + +import lombok.Data; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; +import org.springframework.core.io.ClassPathResource; + +import static com.diguage.truman.util.Constans.BASE_CLASS_PATH; + +/** + * 研究变量替换 + *

+ * {@link PropertyPlaceholderConfigurerTest} 被启用。启用 {@link PropertySourcesPlaceholderConfigurer}. + */ +public class PropertyPlaceholderConfigurerTest { + + + @Test + public void test() { + String config = BASE_CLASS_PATH + "/beans/env/" + + "PropertyPlaceholderConfigurerTest.xml"; + ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(config); +// PropertyPlaceholderConfigurer placeholderConfigurer = ctx.getBean(PropertyPlaceholderConfigurer.class); +// assertThat(placeholderConfigurer).isNotNull(); + + CfgOption dataSource = ctx.getBean(CfgOption.class); + System.out.println("cmdCfg=" + dataSource.getCmdCfg()); + System.out.println("fileCfg=" + dataSource.getFileCfg()); + System.out.println("envCfg=" + dataSource.getEnvCfg()); + } + + @Configuration + @EnableAspectJAutoProxy + public static class Config { +// @Bean +// public PropertyPlaceholderConfigurer getPropertyPlaceholderConfigurer() { +// String path = "com/diguage/truman/beans/env/" + +// "PropertyPlaceholderConfigurerTest.properties"; +// PropertyPlaceholderConfigurer result = new PropertyPlaceholderConfigurer(); +// result.setLocations(new ClassPathResource(path)); +// return result; +// } + + /** + * 如果不创建这个 Bean,则在 XML 中配置的 ${url} 和 ${javaHome} 就不会解析! + */ + @Bean + public PropertySourcesPlaceholderConfigurer getPSPC() { + PropertySourcesPlaceholderConfigurer result + = new PropertySourcesPlaceholderConfigurer(); + String path = "com/diguage/truman/beans/env/" + + "PropertyPlaceholderConfigurerTest.properties"; + result.setLocations(new ClassPathResource(path)); + return result; + } + } + + @Data + public static class CfgOption { + /** + * 命令行 -D 配置项 + */ + private String cmdCfg; + /** + * 配置文件配置项 + */ + private String fileCfg; + /** + * 环境变量配置项 + */ + private String envCfg; + } +} diff --git a/truman/src/main/java/com/diguage/truman/context/AnnoLookupTest.java b/truman/src/main/java/com/diguage/truman/context/AnnoLookupTest.java new file mode 100644 index 000000000000..fed995b31350 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/context/AnnoLookupTest.java @@ -0,0 +1,68 @@ +package com.diguage.truman.context; + +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Lookup; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.ImportSelector; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Repository; + +/** + * @author D瓜哥, https://www.diguage.com/ + * @since 2021-08-08 22:26:20 + */ +@Slf4j +public class AnnoLookupTest { + + @Test + public void test() { + AnnotationConfigApplicationContext context + = new AnnotationConfigApplicationContext(); + context.register(Config.class); + context.refresh(); + UserService service = context.getBean(UserService.class); + service.get(); + } + + @Configuration + @Import(LookupImportSelector.class) + public static class Config { + } + + public static class LookupImportSelector implements ImportSelector { + @Override + public String[] selectImports(AnnotationMetadata metadata) { + return new String[]{ + UserDao.class.getName(), + UserService.class.getName() + }; + } + } + + @Repository + public static class UserDao { + public String getUser() { + log.info("execute UserDao.getUser"); + return "D瓜哥"; + } + } + + @Component + public static abstract class UserService { + public void get() { + UserDao userDao = getUserDao(); + log.info("invoke userDao..."); + log.info("result: {}", userDao.getUser()); + } + + /** + * TODO 这里使用抽象方法,让人费解。 + */ + @Lookup + public abstract UserDao getUserDao(); + } +} diff --git a/truman/src/main/java/com/diguage/truman/context/ApplicationContextAwareTest.java b/truman/src/main/java/com/diguage/truman/context/ApplicationContextAwareTest.java new file mode 100644 index 000000000000..bd1964aa1165 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/context/ApplicationContextAwareTest.java @@ -0,0 +1,45 @@ +package com.diguage.truman.context; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.stereotype.Component; + +import java.util.Arrays; +import java.util.Objects; + +/** + * @author D瓜哥, https://www.diguage.com/ + * @since 2020-05-26 21:35 + */ +public class ApplicationContextAwareTest { + @Test + public void test() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(Config.class); + context.refresh(); + UserService service = context.getBean(UserService.class); + System.out.println(Arrays.toString(service.getBeanNames())); + } + + @Configuration + @Import(UserService.class) + public static class Config { + } + + @Component + public static class UserService { + @Autowired + ApplicationContext applicationContext; + + public String[] getBeanNames() { + if (Objects.nonNull(applicationContext)) { + return applicationContext.getBeanDefinitionNames(); + } + return new String[0]; + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/context/ApplicationEventTest.java b/truman/src/main/java/com/diguage/truman/context/ApplicationEventTest.java new file mode 100644 index 000000000000..f71637e03a18 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/context/ApplicationEventTest.java @@ -0,0 +1,57 @@ +package com.diguage.truman.context; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.stereotype.Component; + +public class ApplicationEventTest { + @Test + public void test() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); +// context.addApplicationListener(new LogApplicationListener()); + context.register(Config.class); + context.refresh(); + UserService service = context.getBean(UserService.class); + System.out.println(service); + } + + @Configuration + @Import({UserService.class, LogApplicationListener.class}) + public static class Config { + } + + @Component + public static class UserService { + public String getById(long id) { + return "id-" + id; + } + } + + public static class LogApplicationListener implements ApplicationListener { + @Override + public void onApplicationEvent(ApplicationEvent event) { + System.out.println(event); + Object source = event.getSource(); + if (source instanceof ConfigurableApplicationContext ctx) { + ConfigurableListableBeanFactory factory = ctx.getBeanFactory(); + String[] names = ctx.getBeanDefinitionNames(); + for (String name : names) { + BeanDefinition definition = factory.getBeanDefinition(name); + if (UserService.class.getName().equals(definition.getBeanClassName())) { + UserService bean = (UserService) ctx.getBean(name); + String username = bean.getById(System.currentTimeMillis()); + System.out.println(username); + } + } + } + } + } + +} diff --git a/truman/src/main/java/com/diguage/truman/context/ApplicationListenerParentTest.java b/truman/src/main/java/com/diguage/truman/context/ApplicationListenerParentTest.java new file mode 100644 index 000000000000..2d105d783600 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/context/ApplicationListenerParentTest.java @@ -0,0 +1,82 @@ +package com.diguage.truman.context; + +import lombok.Setter; +import org.junit.jupiter.api.Test; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.stereotype.Component; + +import java.util.Objects; + +import static com.diguage.truman.util.Constans.BASE_CLASS_PATH; + +/** + * @author D瓜哥, https://www.diguage.com/ + * @since 2020-05-26 19:33 + */ +public class ApplicationListenerParentTest { + @Test + public void test() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(Config.class); + context.refresh(); + UserService userService = LoaderListener.subcontext.getBean(UserService.class); + String user = userService.getUserById(119L); + System.out.println(user); + } + + @Configuration + @Import({UserDao.class, LoaderListener.class}) + public static class Config { + } + + @Component("userDao") + public static class UserDao { + public String getUserById(Long id) { + return "user-" + id + "@UserDao"; + } + } + + public static class LoaderListener implements ApplicationContextAware, ApplicationListener { + private static ApplicationContext context; + private static ApplicationContext subcontext; + private static boolean loaded = false; + + @Override + public void setApplicationContext(ApplicationContext ctx) throws BeansException { + if (Objects.isNull(LoaderListener.context)) { + LoaderListener.context = ctx; + } else { + boolean equals = Objects.equals(LoaderListener.context, ctx); + System.out.println("equals=" + equals); + } + } + + @Override + public void onApplicationEvent(ApplicationEvent event) { + System.out.println(event); + if (!LoaderListener.loaded) { + LoaderListener.loaded = true; + ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext( + new String[]{BASE_CLASS_PATH + "/context/ApplicationListenerParentTest.xml"}, LoaderListener.context); + LoaderListener.subcontext = ctx; + } + } + } + + public static class UserService { + @Setter + private UserDao userDao; + + public String getUserById(Long id) { + return userDao.getUserById(id); + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/context/ApplicationListenerTest.java b/truman/src/main/java/com/diguage/truman/context/ApplicationListenerTest.java new file mode 100644 index 000000000000..95c91451c6d5 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/context/ApplicationListenerTest.java @@ -0,0 +1,41 @@ +package com.diguage.truman.context; + +import org.junit.jupiter.api.Test; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.stereotype.Component; + +/** + * @author D瓜哥, https://www.diguage.com/ + * @since 2020-05-26 19:33 + */ +public class ApplicationListenerTest { + @Test + public void test() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.addApplicationListener(new LogApplicationListener()); + context.register(Config.class); + context.refresh(); + UserService service = context.getBean(UserService.class); + System.out.println(service); + } + + @Configuration + @Import(UserService.class) + public static class Config { + } + + @Component + public static class UserService { + } + + public static class LogApplicationListener implements ApplicationListener { + @Override + public void onApplicationEvent(ApplicationEvent event) { + System.out.println(event); + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/context/AsyncTest.java b/truman/src/main/java/com/diguage/truman/context/AsyncTest.java new file mode 100644 index 000000000000..bea6c9e863da --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/context/AsyncTest.java @@ -0,0 +1,78 @@ +package com.diguage.truman.context; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.stereotype.Service; + +import java.util.Date; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.LockSupport; + +/** + * TODO dgg 分析这篇文章 https://www.cnblogs.com/thisiswhy/p/16003571.html 的示例程序 + */ +@Slf4j +public class AsyncTest { + + @Test + public void test() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(Config.class); + ctx.refresh(); + UserService service = ctx.getBean(UserService.class); + log.info("start to invoke UserService.insert"); + User user = new User(); + user.setId(1L); + user.setName("D瓜哥"); + user.setBlog("https://www.diguage.com/"); + user.setBirthday(new Date()); + service.insert(user); + log.info("finish invoking UserService.insert"); + + // 防止退出 + LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(6)); + } + + @EnableAsync + @Configuration + @Import(UserService.class) + public static class Config { + @Bean(value = "diguage-executor") + public Executor getExecutor() { + return new ThreadPoolTaskExecutor(); + } + } + + // TODO dgg 这个 ${thread-pool.name} 变量无法解析,调试一下怎么回事 + @Service + public static class UserService { + @Async("${thread-pool.name}") + public void insert(User user) { + log.info("begin to insert user({})", user); + log.info("current thread: {}", Thread.currentThread().getName()); + try { + TimeUnit.SECONDS.sleep(5); + } catch (InterruptedException e) { + log.warn("lock was interrupted.", e); + } + log.info("finish inserting user."); + } + } + + @Data + public static class User { + private Long id; + private String name; + private String blog; + private Date birthday; + } +} diff --git a/truman/src/main/java/com/diguage/truman/context/BeanDefinitionRegistryPostProcessorTest.java b/truman/src/main/java/com/diguage/truman/context/BeanDefinitionRegistryPostProcessorTest.java new file mode 100644 index 000000000000..644d2b44181c --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/context/BeanDefinitionRegistryPostProcessorTest.java @@ -0,0 +1,52 @@ +package com.diguage.truman.context; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +import java.util.Arrays; + +/** + * @author D瓜哥, https://www.diguage.com/ + * @since 2020-05-26 17:29 + */ +public class BeanDefinitionRegistryPostProcessorTest { + @Test + public void test() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.addBeanFactoryPostProcessor(new LogBeanDefinitionRegistryPostProcessor()); + context.refresh(); + LogBeanFactoryPostProcessor processor = context.getBean(LogBeanFactoryPostProcessor.class); + System.out.println(processor); + } + + public static class LogBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor { + @Override + public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { + System.out.println("\nLogBeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry\n"); + RootBeanDefinition beanDefinition = new RootBeanDefinition(LogBeanFactoryPostProcessor.class); + registry.registerBeanDefinition(beanDefinition.getBeanClassName(), beanDefinition); + } + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + System.out.println("\nLogBeanDefinitionRegistryPostProcessor.postProcessBeanFactory\n"); + } + } + + public static class LogBeanFactoryPostProcessor implements BeanFactoryPostProcessor { + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + System.out.println("\nLogBeanFactoryPostProcessor.postProcessBeanFactory"); + System.out.println(Arrays.toString(beanFactory.getBeanDefinitionNames()).replaceAll(",", ",\n")); + System.out.println(); + } + } + + +} diff --git a/truman/src/main/java/com/diguage/truman/context/BeanFactoryPostProcessorAutowireTest.java b/truman/src/main/java/com/diguage/truman/context/BeanFactoryPostProcessorAutowireTest.java new file mode 100644 index 000000000000..2ac5a4ae19f1 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/context/BeanFactoryPostProcessorAutowireTest.java @@ -0,0 +1,91 @@ +package com.diguage.truman.context; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.ImportSelector; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.stereotype.Component; + +import java.util.Arrays; +import java.util.Objects; + +/** + * 实现 BeanFactoryPostProcessor 接口,可以在spring的bean创建之前修改bean的定义属性。 + * + * @author D瓜哥, https://www.diguage.com/ + * @since 2020-05-26 15:07 + */ +public class BeanFactoryPostProcessorAutowireTest { + @Test + public void test() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(Config.class); + context.addBeanFactoryPostProcessor(new LogBeanFactoryPostProcessor()); + context.refresh(); + UserService userService = context.getBean(UserService.class); + System.out.println(userService.getById(119L)); + } + + @Configuration + @Import(LogSelector.class) + public static class Config { + } + + public static class LogSelector implements ImportSelector { + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + return new String[]{ + UserService.class.getName(), + // 注意这里!应该整两个 BeanFactoryPostProcessor 实现类来对比 + LogBeanFactoryPostProcessor.class.getName() + }; + } + } + + @Component + public static class UserService { + public String getById(Long id) { + return "Name-" + id; + } + + public void init() { + System.out.println("start to invoke init method..."); + System.out.println("init"); + System.out.println(); + } + } + + @Test + void name() { + } + + @Component + public static class LogBeanFactoryPostProcessor implements BeanFactoryPostProcessor { + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + String[] names = beanFactory.getBeanDefinitionNames(); + System.out.println(); + System.out.println(Arrays.toString(names).replaceAll(",", ",\n")); + System.out.println(); + BeanDefinition definition = beanFactory.getBeanDefinition(UserService.class.getName()); + definition.getAttribute(""); + if (Objects.nonNull(definition)) { + definition.setScope(BeanDefinition.SCOPE_PROTOTYPE); + definition.setDescription("This is a dealed bean."); + definition.setInitMethodName("init"); + } + } + } + + /** + * BeanFactoryPostProcessor + * BeanDefinitionRegistryPostProcessor + * BeanPostProcessor + */ +} diff --git a/truman/src/main/java/com/diguage/truman/context/BeanFactoryPostProcessorFailTest.java b/truman/src/main/java/com/diguage/truman/context/BeanFactoryPostProcessorFailTest.java new file mode 100644 index 000000000000..af9115698b96 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/context/BeanFactoryPostProcessorFailTest.java @@ -0,0 +1,148 @@ +package com.diguage.truman.context; + +import com.diguage.truman.mybatis.Employees; +import com.diguage.truman.mybatis.EmployeesMapper; +import com.mysql.cj.jdbc.Driver; +import com.zaxxer.hikari.HikariDataSource; +import jakarta.annotation.Resource; +import org.apache.ibatis.session.Configuration; +import org.junit.jupiter.api.Test; +import org.mybatis.spring.SqlSessionFactoryBean; +import org.mybatis.spring.annotation.MapperScan; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.PropertySource; +import org.springframework.core.Ordered; +import org.springframework.core.PriorityOrdered; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.stereotype.Service; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; +import org.springframework.transaction.annotation.Transactional; + +import javax.sql.DataSource; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +import static com.diguage.truman.util.Constans.BASE_CLASS_PATH; + +/** + * @author D瓜哥 · https://www.diguage.com + * @since 2020-05-29 17:11 + */ +public class BeanFactoryPostProcessorFailTest { + private static final Logger logger = LoggerFactory.getLogger(BeanFactoryPostProcessorFailTest.class); + + @Test + public void testCacheQuery() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(Config.class); + context.register(PropertySourcesFactoryPostProcessor.class); + context.refresh(); + EmployeesMapper employeesMapper = context.getBean(EmployeesMapper.class); + Employees employees = employeesMapper.getById(10001); + System.out.println(employees); + System.out.println(employeesMapper.getById(10001)); + } + + @Test + public void testInsert() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(Config.class); + context.register(Config.class); + context.refresh(); + EmployeesService service = context.getBean(EmployeesService.class); + Employees employees = new Employees(); + employees.empNo = 123456789; + employees.birthDate = new Date(); + employees.firstName = "Dummy"; + employees.lastName = "Fake"; + employees.gender = "F"; + employees.hireDate = new Date(); + int insert = service.save(employees); + } + + @org.springframework.context.annotation.Configuration + @EnableTransactionManagement + @MapperScan(basePackages = "com.diguage.truman.mybatis") + @Import({EmployeesService.class, LoggerBeanFactoryPostProcessor.class}) + @PropertySource(BASE_CLASS_PATH + "/context/token.properties") + public static class Config { + @Bean + public DataSource dataSource() { + HikariDataSource dataSource = new HikariDataSource(); + dataSource.setUsername("root"); + dataSource.setPassword("123456"); + dataSource.setDriverClassName(Driver.class.getName()); + dataSource.setConnectionTimeout(TimeUnit.SECONDS.toMillis(5)); + + dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/employees?useUnicode=true" + + "&characterEncoding=utf-8&autoReconnectForPools=true&autoReconnect=true"); + return dataSource; + } + + @Bean + public PlatformTransactionManager transactionManager(DataSource dataSource) { + return new DataSourceTransactionManager(dataSource); + } + + @Bean + public SqlSessionFactoryBean sqlSessionFactory(@Autowired DataSource dataSource) { + SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); + factoryBean.setDataSource(dataSource); + Configuration configuration = new Configuration(); + configuration.setMapUnderscoreToCamelCase(true); + factoryBean.setConfiguration(configuration); + return factoryBean; + } + } + + @Service + public static class EmployeesService { + @Resource + private EmployeesMapper employeesMapper; + + @Transactional + public int save(Employees employees) { + return employeesMapper.insert(employees); + } + } + + private static boolean created = false; + + public static class PropertySourcesFactoryPostProcessor implements BeanFactoryPostProcessor, PriorityOrdered { + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + logger.info("PropertySourcesFactoryPostProcessor.postProcessBeanFactory run"); + logger.info("trigger some bean was created."); + created = true; + } + + @Override + public int getOrder() { + return Ordered.LOWEST_PRECEDENCE; + } + } + + public static class LoggerBeanFactoryPostProcessor implements BeanFactoryPostProcessor { + + @Value("${user.token}") + private String userToken; + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + logger.info("LoggerBeanFactoryPostProcessor.postProcessBeanFactory run, userToken={}", userToken); + if (created) { + logger.error("some bean was created, throw an error."); + } + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/context/BeanFactoryPostProcessorOkTest.java b/truman/src/main/java/com/diguage/truman/context/BeanFactoryPostProcessorOkTest.java new file mode 100644 index 000000000000..139d021c9e5e --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/context/BeanFactoryPostProcessorOkTest.java @@ -0,0 +1,153 @@ +package com.diguage.truman.context; + +import com.diguage.truman.mybatis.Employees; +import com.diguage.truman.mybatis.EmployeesMapper; +import com.mysql.cj.jdbc.Driver; +import com.zaxxer.hikari.HikariDataSource; +import jakarta.annotation.Resource; +import org.apache.ibatis.session.Configuration; +import org.junit.jupiter.api.Test; +import org.mybatis.spring.SqlSessionFactoryBean; +import org.mybatis.spring.annotation.MapperScan; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.EnvironmentAware; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.core.Ordered; +import org.springframework.core.PriorityOrdered; +import org.springframework.core.env.Environment; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.stereotype.Service; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; +import org.springframework.transaction.annotation.Transactional; + +import javax.sql.DataSource; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +/** + * @author D瓜哥 · https://www.diguage.com + * @since 2020-05-29 17:11 + */ +public class BeanFactoryPostProcessorOkTest { + private static final Logger logger = LoggerFactory.getLogger(BeanFactoryPostProcessorOkTest.class); + + @Test + public void testCacheQuery() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(Config.class); + context.refresh(); + EmployeesMapper employeesMapper = context.getBean(EmployeesMapper.class); + Employees employees = employeesMapper.getById(10001); + System.out.println(employees); + System.out.println(employeesMapper.getById(10001)); + } + + @Test + public void testInsert() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(Config.class); + context.refresh(); + EmployeesService service = context.getBean(EmployeesService.class); + Employees employees = new Employees(); + employees.empNo = 123456789; + employees.birthDate = new Date(); + employees.firstName = "Dummy"; + employees.lastName = "Fake"; + employees.gender = "F"; + employees.hireDate = new Date(); + int insert = service.save(employees); + } + + @org.springframework.context.annotation.Configuration + @EnableTransactionManagement + @MapperScan(basePackages = "com.diguage.truman.mybatis") + @Import({EmployeesService.class, PropertySourcesFactoryPostProcessor.class}) + public static class Config { + @Bean + public DataSource dataSource() { + HikariDataSource dataSource = new HikariDataSource(); + dataSource.setUsername("root"); + dataSource.setPassword("123456"); + dataSource.setDriverClassName(Driver.class.getName()); + dataSource.setConnectionTimeout(TimeUnit.SECONDS.toMillis(5)); + + dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/employees?useUnicode=true" + + "&characterEncoding=utf-8&autoReconnectForPools=true&autoReconnect=true"); + return dataSource; + } + + @Bean + public PlatformTransactionManager transactionManager(DataSource dataSource) { + return new DataSourceTransactionManager(dataSource); + } + + @Bean + public SqlSessionFactoryBean sqlSessionFactory(@Autowired DataSource dataSource) { + SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); + factoryBean.setDataSource(dataSource); + Configuration configuration = new Configuration(); + configuration.setMapUnderscoreToCamelCase(true); + factoryBean.setConfiguration(configuration); + return factoryBean; + } + } + + @Service + public static class EmployeesService { + @Resource + private EmployeesMapper employeesMapper; + + @Transactional + public int save(Employees employees) { + return employeesMapper.insert(employees); + } + } + + private static boolean created = false; + + public static class PropertySourcesFactoryPostProcessor implements BeanFactoryPostProcessor, PriorityOrdered { + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + logger.info("PropertySourcesFactoryPostProcessor.postProcessBeanFactory run"); + logger.info("trigger some bean was created."); + created = true; + } + + @Override + public int getOrder() { + return Ordered.LOWEST_PRECEDENCE; + } + } + + public static class LoggerBeanFactoryPostProcessor implements BeanFactoryPostProcessor, EnvironmentAware, PriorityOrdered { + + private Environment environment; + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + String userToken = this.environment.getProperty("user.token", String.class, "defaultToken"); + logger.info("LoggerBeanFactoryPostProcessor.postProcessBeanFactory run, userToken={}", userToken); + if (created) { + logger.error("some bean was created, throw an error."); + } + } + + @Override + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE; + } + + @Override + public void setEnvironment(Environment environment) { + this.environment = environment; + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/context/BeanFactoryPostProcessorOrderTest.java b/truman/src/main/java/com/diguage/truman/context/BeanFactoryPostProcessorOrderTest.java new file mode 100644 index 000000000000..a46a8942a293 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/context/BeanFactoryPostProcessorOrderTest.java @@ -0,0 +1,103 @@ +package com.diguage.truman.context; + +import com.diguage.truman.mybatis.Employees; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Import; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Service; + +/** + * @author D瓜哥 · https://www.diguage.com + * @since 2020-05-29 17:11 + */ +public class BeanFactoryPostProcessorOrderTest { + private static final Logger logger = LoggerFactory.getLogger(BeanFactoryPostProcessorOrderTest.class); + + @Test + public void test() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(Config.class); + context.refresh(); + EmployeesService service = context.getBean(EmployeesService.class); + service.save(new Employees()); + } + + + @org.springframework.context.annotation.Configuration + @Import({EmployeesService.class, + InterOrder1BeanFactoryPostProcessor.class, + InterOrder2BeanFactoryPostProcessor.class, + InterOrder3BeanFactoryPostProcessor.class, + AnnoOrder1BeanFactoryPostProcessor.class, + AnnoOrder2BeanFactoryPostProcessor.class}) + public static class Config { + } + + @Service + public static class EmployeesService { + public int save(Employees employees) { + logger.info("save employees: {}", employees); + return 1; + } + } + + + public static class InterOrder1BeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered { + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + logger.info("InterOrdered1FactoryPostProcessor.postProcessBeanFactory run"); + } + + @Override + public int getOrder() { + return 1; + } + } + + public static class InterOrder2BeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered { + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + logger.info("InterOrdered2FactoryPostProcessor.postProcessBeanFactory run"); + } + + @Override + public int getOrder() { + return 2; + } + } + + public static class InterOrder3BeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered { + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + logger.info("InterOrdered3FactoryPostProcessor.postProcessBeanFactory run"); + } + + @Override + public int getOrder() { + return 3; + } + } + + @Order(1) + public static class AnnoOrder1BeanFactoryPostProcessor implements BeanFactoryPostProcessor { + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + logger.info("AnnoOrder1BeanFactoryPostProcessor.postProcessBeanFactory run"); + } + } + + @Order(2) + public static class AnnoOrder2BeanFactoryPostProcessor implements BeanFactoryPostProcessor { + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + logger.info("AnnoOrder2BeanFactoryPostProcessor.postProcessBeanFactory run"); + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/context/BeanFactoryPostProcessorTest.java b/truman/src/main/java/com/diguage/truman/context/BeanFactoryPostProcessorTest.java new file mode 100644 index 000000000000..b699f711d590 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/context/BeanFactoryPostProcessorTest.java @@ -0,0 +1,92 @@ +package com.diguage.truman.context; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.stereotype.Component; + +import java.util.Arrays; +import java.util.Objects; + +/** + * 实现 BeanFactoryPostProcessor 接口,可以在spring的bean创建之前修改bean的定义属性。 + * + * @author D瓜哥, https://www.diguage.com/ + * @since 2020-05-26 15:07 + */ +public class BeanFactoryPostProcessorTest { + @Test + public void test() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(Config.class); + context.addBeanFactoryPostProcessor(new LogBeanFactoryPostProcessor()); + context.refresh(); + UserService userService = context.getBean(UserService.class); + System.out.println(userService.getById(119L)); + } + + @Configuration + @Import(UserService.class) + public static class Config { + } + + + @Component + public static class UserService { + + private String[] filters; + + public String[] getFilters() { + return filters; + } + + public void setFilters(String[] filters) { + this.filters = filters; + } + + public String getById(Long id) { + System.out.println(Arrays.toString(filters)); + return "Name-" + id; + } + + public void init() { + System.out.println("init"); + } + } + + public static class LogBeanFactoryPostProcessor implements BeanFactoryPostProcessor { + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + String[] names = beanFactory.getBeanDefinitionNames(); + System.out.println(); + System.out.println(Arrays.toString(names).replaceAll(",", ",\n")); + System.out.println(); + BeanDefinition definition = beanFactory.getBeanDefinition(UserService.class.getName()); + if (Objects.nonNull(definition)) { + // TODO 确认一下 getAttribute 中可以访问什么? + // 刚刚调试,就放了一个 ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE。 + // 跟代码发现,是在 ConfigurationClassPostProcessor.processConfigBeanDefinitions 中处理的。 + // 后续再跟一下代码。 + // definition.getAttribute(""); + + definition.setScope(BeanDefinition.SCOPE_PROTOTYPE); + definition.setDescription("This is a dealed bean."); + definition.setInitMethodName("init"); + + definition.getPropertyValues() + .addPropertyValue("filters", "com.diguage.filter.LogFilter"); + } + } + } + + /** + * BeanFactoryPostProcessor + * BeanDefinitionRegistryPostProcessor + * BeanPostProcessor + */ +} diff --git a/truman/src/main/java/com/diguage/truman/context/BeanPostProcessorAnnoBeanTest.java b/truman/src/main/java/com/diguage/truman/context/BeanPostProcessorAnnoBeanTest.java new file mode 100644 index 000000000000..7a2c6bc1db4a --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/context/BeanPostProcessorAnnoBeanTest.java @@ -0,0 +1,78 @@ +package com.diguage.truman.context; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.ImportSelector; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.stereotype.Component; + +import java.util.Objects; + +/** + * @author D瓜哥, https://www.diguage.com/ + * @since 2020-05-26 15:53 + */ +public class BeanPostProcessorAnnoBeanTest { + @Test + public void test() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(Config.class); + + context.refresh(); + UserService userService = context.getBean(UserService.class); + System.out.println(userService.getById(119L)); + } + + @Configuration + @Import(LogSelector.class) + public static class Config { + @Bean + public LogBeanPostProcessor logBeanPostProcessor() { + return new LogBeanPostProcessor(); + } + } + + public static class LogSelector implements ImportSelector { + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + return new String[]{ + UserService.class.getName() + }; + } + } + + @Component + public static class UserService implements InitializingBean { + public String getById(Long id) { + return "Name-" + id; + } + + @Override + public void afterPropertiesSet() throws Exception { + System.out.println("\nstart to invoke init method..."); + System.out.println("init"); + System.out.println(); + } + } + + @Component + public static class LogBeanPostProcessor implements BeanPostProcessor { + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + System.out.println("start to create [" + beanName + "]: isNull=" + Objects.isNull(bean)); + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + System.out.println("finish creating [" + beanName + "]: isNull=" + Objects.isNull(bean)); + return bean; + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/context/BeanPostProcessorAutowireTest.java b/truman/src/main/java/com/diguage/truman/context/BeanPostProcessorAutowireTest.java new file mode 100644 index 000000000000..6a1202305c4a --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/context/BeanPostProcessorAutowireTest.java @@ -0,0 +1,73 @@ +package com.diguage.truman.context; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.ImportSelector; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.stereotype.Component; + +import java.util.Objects; + +/** + * @author D瓜哥, https://www.diguage.com/ + * @since 2020-05-26 15:53 + */ +public class BeanPostProcessorAutowireTest { + @Test + public void test() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(Config.class); + context.refresh(); + UserService userService = context.getBean(UserService.class); + System.out.println(userService.getById(119L)); + } + + @Configuration + @Import(LogSelector.class) + public static class Config { + } + + public static class LogSelector implements ImportSelector { + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + return new String[]{ + UserService.class.getName(), + LogBeanPostProcessor.class.getName() + }; + } + } + + @Component + public static class UserService implements InitializingBean { + public String getById(Long id) { + return "Name-" + id; + } + + @Override + public void afterPropertiesSet() throws Exception { + System.out.println("\nstart to invoke init method..."); + System.out.println("init"); + System.out.println(); + } + } + + @Component + public static class LogBeanPostProcessor implements BeanPostProcessor { + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + System.out.println("start to create [" + beanName + "]: isNull=" + Objects.isNull(bean)); + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + System.out.println("finish creating [" + beanName + "]: isNull=" + Objects.isNull(bean)); + return bean; + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/context/BeanPostProcessorTest.java b/truman/src/main/java/com/diguage/truman/context/BeanPostProcessorTest.java new file mode 100644 index 000000000000..9cd5de353800 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/context/BeanPostProcessorTest.java @@ -0,0 +1,77 @@ +package com.diguage.truman.context; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.ImportSelector; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.stereotype.Component; + +import java.util.Objects; + +/** + * @author D瓜哥, https://www.diguage.com/ + * @since 2020-05-26 15:53 + */ +public class BeanPostProcessorTest { + @Test + public void test() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(Config.class); + + ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); + beanFactory.addBeanPostProcessor(new LogBeanPostProcessor()); + + context.refresh(); + UserService userService = context.getBean(UserService.class); + System.out.println(userService.getById(119L)); + } + + @Configuration + @Import(LogSelector.class) + public static class Config { + } + + public static class LogSelector implements ImportSelector { + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + return new String[]{ + UserService.class.getName() + }; + } + } + + @Component + public static class UserService implements InitializingBean { + public String getById(Long id) { + return "Name-" + id; + } + + @Override + public void afterPropertiesSet() throws Exception { + System.out.println("\nstart to invoke init method..."); + System.out.println("init"); + System.out.println(); + } + } + + @Component + public static class LogBeanPostProcessor implements BeanPostProcessor { + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + System.out.println("start to create [" + beanName + "]: isNull=" + Objects.isNull(bean)); + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + System.out.println("finish creating [" + beanName + "]: isNull=" + Objects.isNull(bean)); + return bean; + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/context/CircularDependenceConstructorTest.java b/truman/src/main/java/com/diguage/truman/context/CircularDependenceConstructorTest.java new file mode 100644 index 000000000000..67e77cc612e0 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/context/CircularDependenceConstructorTest.java @@ -0,0 +1,89 @@ +package com.diguage.truman.context; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.ImportSelector; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.stereotype.Component; + +/** + * @author D瓜哥, https://www.diguage.com/ + * @since 2020-05-24 13:02 + */ +public class CircularDependenceConstructorTest { + public static final Log log = LogFactory.getLog(CircularDependenceConstructorTest.class); + + @Test + public void test() { + log.debug("OK"); + /** + * 1. scan --- bd --- map + * 2. 遍历map + * 3. validate + * 4. 得到class + * 5. 推断构造方法 + * 6. 反射,实例化这个对象 + * 7. 合并 beanDefinition + * 8. 提前暴露一个bean工厂对象 + * 9. 填充属性---自动注入 + * 10. 部分aware接口 + * 11. 执行---部分aware接口,执行 Spring 生命周期回调方法 + */ + AnnotationConfigApplicationContext applicationContext + = new AnnotationConfigApplicationContext(); + applicationContext.register(Config.class); + applicationContext.refresh(); + + log.debug(applicationContext.getBean(A.class)); + log.debug(applicationContext.getBean(B.class)); + log.debug(applicationContext.getBean(C.class)); + } + + @Configuration + @Import(AbcImportSelector.class) + public static class Config { + } + + public static class AbcImportSelector implements ImportSelector { + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + return new String[]{ + A.class.getName(), + B.class.getName(), + C.class.getName()}; + } + } + + + @Component + public static class A { + B b; + + public A(@Autowired B b) { + this.b = b; + } + } + + @Component + public static class B { + C c; + + public B(@Autowired C c) { + this.c = c; + } + } + + @Component + public static class C { + A a; + + public C(@Autowired A a) { + this.a = a; + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/context/CircularDependencePrototypeTest.java b/truman/src/main/java/com/diguage/truman/context/CircularDependencePrototypeTest.java new file mode 100644 index 000000000000..61248fe5d9a0 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/context/CircularDependencePrototypeTest.java @@ -0,0 +1,99 @@ +package com.diguage.truman.context; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.ImportSelector; +import org.springframework.context.annotation.Scope; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.stereotype.Component; + +/** + * @author D瓜哥, https://www.diguage.com/ + * @since 2020-05-24 13:02 + */ +public class CircularDependencePrototypeTest { + public static final Log log = LogFactory.getLog(CircularDependencePrototypeTest.class); + + @Test + public void test() { + log.debug("OK"); + /** + * 1. scan --- bd --- map + * 2. 遍历map + * 3. validate + * 4. 得到class + * 5. 推断构造方法 + * 6. 反射,实例化这个对象 + * 7. 合并 beanDefinition + * 8. 提前暴露一个bean工厂对象 + * 9. 填充属性---自动注入 + * 10. 部分aware接口 + * 11. 执行---部分aware接口,执行 Spring 生命周期回调方法 + */ + AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); + applicationContext.register(Config.class); + applicationContext.refresh(); + + log.debug(applicationContext.getBean(A.class)); + log.debug(applicationContext.getBean(B.class)); + log.debug(applicationContext.getBean(C.class)); + + System.out.println("-A--------"); + A a = applicationContext.getBean(A.class); + log.debug(a); + log.debug(a.b); + System.out.println("-B--------"); + B b = applicationContext.getBean(B.class); + log.debug(b); + log.debug(b.c); + System.out.println("-C--------"); + C c = applicationContext.getBean(C.class); + log.debug(c); + log.debug(c.a); + log.debug(applicationContext.getBean(C.class)); + log.debug(applicationContext.getBean(C.class).a); + } + + @Configuration + @Import(AbcImportSelector.class) + public static class Config { + } + + public static class AbcImportSelector implements ImportSelector { + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + return new String[]{ + A.class.getName(), + B.class.getName(), + C.class.getName()}; + } + } + + + @Component + @Scope(BeanDefinition.SCOPE_PROTOTYPE) + public static class A { + @Autowired + B b; + } + + @Component + @Scope(BeanDefinition.SCOPE_PROTOTYPE) + public static class B { + @Autowired + C c; + } + + @Component +// @Scope(BeanDefinition.SCOPE_PROTOTYPE) + public static class C { + @Autowired + A a; + } +} diff --git a/truman/src/main/java/com/diguage/truman/context/CircularDependenceSingletonTest.java b/truman/src/main/java/com/diguage/truman/context/CircularDependenceSingletonTest.java new file mode 100644 index 000000000000..75cf3f5ac192 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/context/CircularDependenceSingletonTest.java @@ -0,0 +1,84 @@ +package com.diguage.truman.context; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.ImportSelector; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.stereotype.Component; + +import jakarta.annotation.Resource; + +/** + * @author D瓜哥, https://www.diguage.com/ + * @since 2020-05-24 13:02 + */ +public class CircularDependenceSingletonTest { + public static final Log log = LogFactory.getLog(CircularDependenceSingletonTest.class); + + @Test + public void test() { + log.info("OK"); + /** + * 1. scan --- bd --- map + * 2. 遍历map + * 3. validate + * 4. 得到class + * 5. 推断构造方法 + * 6. 反射,实例化这个对象 + * 7. 合并 beanDefinition + * 8. 提前暴露一个bean工厂对象 + * 9. 填充属性---自动注入 + * 10. 部分aware接口 + * 11. 执行---部分aware接口,执行 Spring 生命周期回调方法 + */ + AnnotationConfigApplicationContext applicationContext + = new AnnotationConfigApplicationContext(); + applicationContext.register(Config.class); + applicationContext.refresh(); + + log.info(applicationContext.getBean(A.class)); + log.info(applicationContext.getBean(B.class)); + + log.info("-A--------"); + A a = applicationContext.getBean(A.class); + log.info(a); + log.info(a.b); + + log.info("-B--------"); + B b = applicationContext.getBean(B.class); + log.info(b); + log.info(b.a); + } + + @Configuration + @Import(AbcImportSelector.class) + public static class Config { + } + + public static class AbcImportSelector implements ImportSelector { + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + return new String[]{ + A.class.getName(), + B.class.getName()}; + } + } + + + @Component + public static class A { + @Autowired + B b; + } + + @Component + public static class B { + @Resource + A a; + } +} diff --git a/truman/src/main/java/com/diguage/truman/context/InitializingBeanTest.java b/truman/src/main/java/com/diguage/truman/context/InitializingBeanTest.java new file mode 100644 index 000000000000..e3b19e055314 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/context/InitializingBeanTest.java @@ -0,0 +1,89 @@ +package com.diguage.truman.context; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.ImportSelector; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.stereotype.Repository; +import org.springframework.stereotype.Service; + +/** + * @author D瓜哥, https://www.diguage.com/ + * @since 2020-05-26 17:37 + */ +public class InitializingBeanTest { + @Test + public void test() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(Config.class); + context.refresh(); + + UserService service = context.getBean(UserService.class); + System.out.println(service.getCount()); + } + + @Configuration + @Import(DefaultImportSelector.class) + public static class Config { + } + + public static class DefaultImportSelector implements ImportSelector { + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + return new String[]{ + UserDao.class.getName(), + OrderDao.class.getName(), + UserService.class.getName() + }; + } + + } + + @Repository + public static class UserDao { + public String getById(Long id) { + return "Name-" + id; + } + } + + @Repository + public static class OrderDao { + public String getById(Long id) { + return "Order-" + id; + } + } + + @Service + public static class UserService implements InitializingBean { + @Autowired + UserDao userDao; + + @Autowired + OrderDao orderDao; + + static int count = 0; + + @Override + public void afterPropertiesSet() throws Exception { + // 根据最新代码测试来看,这里只会被执行一次 + System.out.println("\nuserDao =" + userDao + "\norderDao=" + orderDao); + count++; + } + + String getUserById(Long id) { + return userDao.getById(id); + } + + String getOrderById(Long id) { + return orderDao.getById(id); + } + + int getCount() { + return count; + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/context/InstantiationAwareBeanPostProcessorTest.java b/truman/src/main/java/com/diguage/truman/context/InstantiationAwareBeanPostProcessorTest.java new file mode 100644 index 000000000000..759d70e1cd6d --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/context/InstantiationAwareBeanPostProcessorTest.java @@ -0,0 +1,48 @@ +package com.diguage.truman.context; + +import org.springframework.beans.BeansException; +import org.springframework.beans.PropertyValues; +import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor; + +import java.beans.PropertyDescriptor; + +/** + * @author D瓜哥, https://www.diguage.com/ + * @since 2020-05-26 20:30 + */ +public class InstantiationAwareBeanPostProcessorTest { + // TODO + +// public static class LogInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor { +// @Override +// public Object postProcessBeforeInstantiation(Class beanClass, String beanName) throws BeansException { +// return null; +// } +// +// @Override +// public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException { +// return false; +// } +// +// @Override +// public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException { +// return null; +// } +// +// @Override +// public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException { +// return null; +// } +// +// @Override +// public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { +// return null; +// } +// +// @Override +// public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { +// return null; +// } +// } + +} diff --git a/truman/src/main/java/com/diguage/truman/context/LifecycleTest.java b/truman/src/main/java/com/diguage/truman/context/LifecycleTest.java new file mode 100644 index 000000000000..b6a60b317d66 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/context/LifecycleTest.java @@ -0,0 +1,295 @@ +package com.diguage.truman.context; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.BeansException; +import org.springframework.beans.PropertyValues; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor; +import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.context.annotation.ImportSelector; +import org.springframework.context.annotation.Scope; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Repository; +import org.springframework.stereotype.Service; + +import jakarta.annotation.Resource; + +/** + * @author D瓜哥, https://www.diguage.com/ + * @since 2020-05-26 19:49 + */ +public class LifecycleTest { + + static int sequence = 0; + + @Test + public void test() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.addBeanFactoryPostProcessor(new LogBeanDefinitionRegistryPostProcessor()); + ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); + beanFactory.addBeanPostProcessor(new LogBeanPostProcessor()); +// beanFactory.addBeanPostProcessor(new LogInstantiationAwareBeanPostProcessor()); + beanFactory.addBeanPostProcessor(new LogDestructionAwareBeanPostProcessor()); + + context.register(Config.class); + context.refresh(); + + ProtoService protoService = context.getBean(ProtoService.class); + System.out.println(protoService); + beanFactory.destroyBean(protoService); + + UserService userService = context.getBean(UserService.class); + System.out.println(userService.getById(119L)); + + BeanDefinition definition = context.getBeanDefinition(UserService.class.getName()); + System.out.println(definition.getClass().getName()); + System.out.println(definition); + +// context.close(); +// context.start(); + } + + @Configuration + @Import(LogImportSelector.class) + @EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true) + public static class Config { + } + + public static class LogImportSelector implements ImportSelector { + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + return new String[]{ +// BeanPostProcessorAspect.class.getName(), + UserService.class.getName(), + UserDao.class.getName(), + ProtoService.class.getName() + }; + } + } + + public static class LogImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, + BeanDefinitionRegistry registry) { + RootBeanDefinition definition = new RootBeanDefinition(UserService.class); + registry.registerBeanDefinition(UserService.class.getName(), definition); + } + } + + @Repository + public static class UserDao { + String getById(Long id) { + return "User-" + id; + } + } + + @Service + public static class UserService implements InitializingBean, BeanFactoryAware, ApplicationContextAware { + @Resource + UserDao userDao; + + public UserService() { + System.out.println(".. 构造函数\n"); + } + + @Override + public void afterPropertiesSet() throws Exception { + System.out.printf(".. %s#%s()%n%n", + getClass().getSimpleName(), + "afterPropertiesSet"); + } + + public void init() { + System.out.printf(".. %s#%s()%n%n", + getClass().getSimpleName(), + "init"); + } + + String getById(Long id) { + return userDao.getById(id); + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + System.out.printf(".. %s#%s(%s)%n%n", + getClass().getSimpleName(), + "setBeanFactory", + beanFactory.getClass().getSimpleName()); + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + System.out.printf(".. %s#%s(%s)%n%n", + getClass().getSimpleName(), + "setApplicationContext", + applicationContext.getClass().getSimpleName()); + } + } + + @Component + @Scope(BeanDefinition.SCOPE_PROTOTYPE) + public static class ProtoService implements DisposableBean { + + @Override + public void destroy() throws Exception { + System.out.printf(".. %s#%s()%n%n", + getClass().getSimpleName(), + "destroy"); + } + } + + + public static class LogBeanPostProcessor implements BeanPostProcessor { + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + System.out.printf(".. %s#%s(%s, %s)%n%n", + getClass().getSimpleName(), + "postProcessBeforeInitialization", + bean.getClass().getSimpleName(), + beanName); + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + System.out.printf(".. %s#%s(%s, %s)%n%n", + getClass().getSimpleName(), + "postProcessAfterInitialization", + bean.getClass().getSimpleName(), + beanName); + return bean; + } + } + + public static class LogBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor { + @Override + public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { + System.out.printf(". %s#%s(%s)%n%n", + getClass().getSimpleName(), + "postProcessBeanDefinitionRegistry", + registry.getClass().getSimpleName()); + + RootBeanDefinition beanDefinition = new RootBeanDefinition(LogBeanFactoryPostProcessor.class); + registry.registerBeanDefinition(beanDefinition.getBeanClassName(), beanDefinition); + } + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + System.out.printf(". %s#%s(%s)%n%n", + getClass().getSimpleName(), + "postProcessBeanFactory", + beanFactory.getClass().getSimpleName()); + } + } + + public static class LogBeanFactoryPostProcessor implements BeanFactoryPostProcessor { + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + System.out.printf(". %s#%s(%s)%n%n", + getClass().getSimpleName(), + "postProcessBeanFactory", + beanFactory.getClass().getSimpleName()); + + BeanDefinition definition = beanFactory.getBeanDefinition(UserService.class.getName()); + // 设置 init 方法 + definition.setInitMethodName("init"); + } + } + + public static class LogInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor { + @Override + public Object postProcessBeforeInstantiation(Class beanClass, String beanName) throws BeansException { + System.out.printf(".. %s#%s(%s, %s)%n%n", + getClass().getSimpleName(), + "postProcessBeforeInstantiation", + beanClass.getSimpleName(), + beanName); + + return null; + } + + @Override + public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException { + System.out.printf(".. %s#%s(%s, %s)%n%n", + getClass().getSimpleName(), + "postProcessAfterInstantiation", + bean.getClass().getSimpleName(), + beanName); + return true; + } + + @Override + public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException { + System.out.printf(".. %s#%s(%s, %s, %s)%n%n", + getClass().getSimpleName(), + "postProcessProperties", + pvs.getClass().getSimpleName(), + bean.getClass().getSimpleName(), + beanName); + + return pvs; + } + +// @Override +// public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException { +// System.out.println(bean.getClass().getName()); +// return pvs; +// } + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + System.out.printf(".. %s#%s(%s, %s)%n%n", + getClass().getSimpleName(), + "postProcessBeforeInitialization", + bean.getClass().getSimpleName(), + beanName); + + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + System.out.printf(".. %s#%s(%s, %s)%n%n", + getClass().getSimpleName(), + "postProcessAfterInitialization", + bean.getClass().getSimpleName(), + beanName); + + return bean; + } + } + + public static class LogDestructionAwareBeanPostProcessor implements DestructionAwareBeanPostProcessor { + @Override + public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException { + System.out.printf(".. %s#%s(%s, %s)%n%n", + getClass().getSimpleName(), + "postProcessBeforeDestruction", + bean.getClass().getSimpleName(), + beanName); + } + } + + + public static String getAndIncrement() { + return String.format("%n - %2d - ", ++sequence); + } +} diff --git a/truman/src/main/java/com/diguage/truman/context/ObjectFactoryTest.java b/truman/src/main/java/com/diguage/truman/context/ObjectFactoryTest.java new file mode 100644 index 000000000000..9a2d3e95bc13 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/context/ObjectFactoryTest.java @@ -0,0 +1,57 @@ +package com.diguage.truman.context; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.Arrays; + +/** + * @author D瓜哥, https://www.diguage.com/ + * @since 2020-05-27 20:05 + */ +public class ObjectFactoryTest { + @Test + public void test() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(Config.class); + context.refresh(); + + // TODO + +// UserService userService = context.getBean(UserService.class); +// System.out.println(userService.getById(119L)); +// +// System.out.println("-↓----"); +// System.out.println("&userServiceFactoryBean = " +// + context.getBean("&userServiceObjectFactory")); // <1> +// System.out.println(" userServiceFactoryBean = " +// + context.getBean("userServiceObjectFactory")); // <2> +// System.out.println("-↑----"); + } + + @Configuration + public static class Config { + @Bean + public UserServiceObjectFactory userServiceObjectFactory() { + return new UserServiceObjectFactory(); + } + } + + + public static class UserService { + public String getById(Long id) { + return "Name-" + id; + } + } + + public static class UserServiceObjectFactory implements ObjectFactory { + @Override + public UserService getObject() throws BeansException { + return new UserService(); + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/context/PlaceholderAnnoTest.java b/truman/src/main/java/com/diguage/truman/context/PlaceholderAnnoTest.java new file mode 100644 index 000000000000..05790579d584 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/context/PlaceholderAnnoTest.java @@ -0,0 +1,45 @@ +package com.diguage.truman.context; + +import lombok.Data; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.PropertySource; + +import static com.diguage.truman.util.Constans.BASE_CLASS_PATH; + +/** + * @author D瓜哥 · https://www.diguage.com + * @since 2023-05-02 10:23:49 + */ +public class PlaceholderAnnoTest { + @Test + public void test() { + AnnotationConfigApplicationContext context + = new AnnotationConfigApplicationContext(); + context.register(Config.class); + context.refresh(); + UserRpc userRpc = context.getBean(UserRpc.class); + System.out.println(userRpc.appId); + System.out.println(userRpc.token); + } + + @Configuration + @Import(UserRpc.class) + @PropertySource(BASE_CLASS_PATH + "/context/token.properties") + public static class Config { + } + + @Data + public static class UserRpc { + + @Value("${user.appId:defaultAppId}") + private String appId; + + @Value("${user.token:defaultAppToken}") + private String token; + + } +} diff --git a/truman/src/main/java/com/diguage/truman/context/PlaceholderTest.java b/truman/src/main/java/com/diguage/truman/context/PlaceholderTest.java new file mode 100644 index 000000000000..04afe67f8aa2 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/context/PlaceholderTest.java @@ -0,0 +1,34 @@ +package com.diguage.truman.context; + +import lombok.Data; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * @author D瓜哥 · https://www.diguage.com + * @since 2023-05-02 10:23:49 + */ +public class PlaceholderTest { + @Test + public void test() { + ClassPathXmlApplicationContext context + = new ClassPathXmlApplicationContext("classpath:com/" + + "diguage/truman/context/PlaceholderTest.xml"); + UserRpc userRpc = context.getBean(UserRpc.class); + System.out.println(userRpc.appId); + System.out.println(userRpc.token); + } + + @Data + public static class UserRpc { + + @Value("${user.appId}") + private String appId; + + // 这里不使用注解,而是使用 XML 配置 + // @Value("${user.token}") + private String token; + + } +} diff --git a/truman/src/main/java/com/diguage/truman/context/PropertyValuesTest.java b/truman/src/main/java/com/diguage/truman/context/PropertyValuesTest.java new file mode 100644 index 000000000000..7d7daacb21c1 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/context/PropertyValuesTest.java @@ -0,0 +1,84 @@ +package com.diguage.truman.context; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.junit.jupiter.api.Test; +import org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.ImportSelector; +import org.springframework.context.annotation.Lazy; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.stereotype.Component; + +/** + * @author D瓜哥, https://www.diguage.com/ + * @since 2020-08-05 10:15 + */ +public class PropertyValuesTest { + public static final Log log = LogFactory.getLog(PropertyValuesTest.class); + + @Test + public void test() { + AnnotationConfigApplicationContext applicationContext + = new AnnotationConfigApplicationContext(); + applicationContext.register(Config.class); + applicationContext.refresh(); + + // TODO 还没有成功 + // 查看如果处理这种依赖,如何进行实例化?会不会封装成 BeanDefinition ? + + BeanDefinition definition = applicationContext.getBeanDefinition(A.class.getName()); + MutablePropertyValues propertyValues = definition.getPropertyValues(); + propertyValues.add("name", "https://www.diguage.com"); +// propertyValues.add("b", "com.diguage.truman.context.PropertyValuesTest.B"); +// propertyValues.add("b", B.class); + propertyValues.add("b", new B()); + + A bean = applicationContext.getBean(A.class); + System.out.println(bean); + System.out.println(bean.b); + } + + @Configuration + @Import(AbcImportSelector.class) + public static class Config { + } + + public static class AbcImportSelector implements ImportSelector { + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + return new String[]{ + A.class.getName()}; + } + } + + + @Component + @Lazy + public static class A { + String name; + B b; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public B getB() { + return b; + } + + public void setB(B b) { + this.b = b; + } + } + + public static class B { + } +} diff --git a/truman/src/main/java/com/diguage/truman/context/ResourceLoaderTest.java b/truman/src/main/java/com/diguage/truman/context/ResourceLoaderTest.java new file mode 100644 index 000000000000..c49560e08b33 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/context/ResourceLoaderTest.java @@ -0,0 +1,56 @@ +package com.diguage.truman.context; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.core.io.ResourceLoader; +import org.springframework.stereotype.Component; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Resource; + +/** + * @author D瓜哥, https://www.diguage.com/ + * @since 2020-05-26 21:35 + */ +public class ResourceLoaderTest { + @Test + public void test() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(Config.class); + context.refresh(); + UserService service = context.getBean(UserService.class); + service.get(); + } + + @Configuration + @Import(UserService.class) + public static class Config { + } + + @Component + public static class UserService { + @Autowired + ResourceLoader resourceLoader; + + @Resource + private ApplicationContext applicationContext; + + @PostConstruct + public void init() { + System.out.println(resourceLoader); + System.out.println(applicationContext); + } + + public void get() { + // 由此证明,这两个对象可以直接注入进来的。 + // 而且,注入后的对象是同一个对象,就是上面实例化 + // 的 AnnotationConfigApplicationContext 对象 + System.out.println(resourceLoader); + System.out.println(applicationContext); + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/context/XmlApplicationContextTest.java b/truman/src/main/java/com/diguage/truman/context/XmlApplicationContextTest.java new file mode 100644 index 000000000000..91b4d1a3fbe4 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/context/XmlApplicationContextTest.java @@ -0,0 +1,26 @@ +package com.diguage.truman.context; + +import org.junit.jupiter.api.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * @author D瓜哥 · https://www.diguage.com + * @since 2022-10-27 22:46:56 + */ +public class XmlApplicationContextTest { + + @Test + public void test() { + ClassPathXmlApplicationContext context + = new ClassPathXmlApplicationContext("classpath:com/diguage/" + + "truman/context/XmlApplicationContextTest.xml"); + UserService userService = context.getBean(UserService.class); + System.out.println(userService.getUserById(119L)); + } + + public static class UserService { + public String getUserById(Long id) { + return "user-" + id; + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/core/PropertiesLoaderSupportTest.java b/truman/src/main/java/com/diguage/truman/core/PropertiesLoaderSupportTest.java new file mode 100644 index 000000000000..98ec419ed24c --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/core/PropertiesLoaderSupportTest.java @@ -0,0 +1,25 @@ +package com.diguage.truman.core; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +public class PropertiesLoaderSupportTest { + @Test + public void test() { + ClassPathXmlApplicationContext context + = new ClassPathXmlApplicationContext("classpath:com/diguage/" + + "truman/core/PropertiesApplicationContextTest.xml"); + UserService userService = context.getBean(UserService.class); + System.out.println(userService.getUserById(119L)); + } + + public static class UserService { + @Value("${user.appId}") + private String appId; + + public String getUserById(Long id) { + return "user-/" + appId + "/-" + id; + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/core/ReflectionTest.java b/truman/src/main/java/com/diguage/truman/core/ReflectionTest.java new file mode 100644 index 000000000000..bdb372c4e255 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/core/ReflectionTest.java @@ -0,0 +1,30 @@ +package com.diguage.truman.core; + +import org.junit.jupiter.api.Test; +import org.springframework.core.ResolvableType; +import org.springframework.util.ReflectionUtils; + +import java.lang.reflect.Method; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ReflectionTest { + interface SomeRepository { + T someMethod1(Class arg0, Class arg1, Class arg2); + } + + @Test + public void test() { + Method method = ReflectionUtils.findMethod(SomeRepository.class, "someMethod1", Class.class, Class.class, Class.class); + + ResolvableType returnType = ResolvableType.forMethodReturnType(method, SomeRepository.class); + + ResolvableType arg0 = ResolvableType.forMethodParameter(method, 0, SomeRepository.class); // generic[0]=T + ResolvableType arg1 = ResolvableType.forMethodParameter(method, 1, SomeRepository.class); // generic[0]=? + ResolvableType arg2 = ResolvableType.forMethodParameter(method, 2, SomeRepository.class); // generic[0]=java.lang.Object + + assertThat(returnType.isAssignableFrom(arg0.as(Class.class).getGeneric(0))).isTrue(); + assertThat(returnType.isAssignableFrom(arg1.as(Class.class).getGeneric(0))).isFalse(); + assertThat(returnType.isAssignableFrom(arg2.as(Class.class).getGeneric(0))).isFalse(); + } +} diff --git a/truman/src/main/java/com/diguage/truman/dubbo/Book.java b/truman/src/main/java/com/diguage/truman/dubbo/Book.java new file mode 100644 index 000000000000..4ccaa2934db4 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/dubbo/Book.java @@ -0,0 +1,12 @@ +package com.diguage.truman.dubbo; + +import lombok.Data; + +import java.util.Date; + +@Data +public class Book { + private Long id; + private String name; + private Date publishDate; +} diff --git a/truman/src/main/java/com/diguage/truman/dubbo/BookService.java b/truman/src/main/java/com/diguage/truman/dubbo/BookService.java new file mode 100644 index 000000000000..645fdcbf2402 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/dubbo/BookService.java @@ -0,0 +1,7 @@ +package com.diguage.truman.dubbo; + +public interface BookService { + Book getById(long id); + + Long save(Book book); +} diff --git a/truman/src/main/java/com/diguage/truman/dubbo/BookServiceImpl.java b/truman/src/main/java/com/diguage/truman/dubbo/BookServiceImpl.java new file mode 100644 index 000000000000..96f796e32d4c --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/dubbo/BookServiceImpl.java @@ -0,0 +1,22 @@ +package com.diguage.truman.dubbo; + +import org.apache.dubbo.config.annotation.DubboService; + +import java.util.Date; + +@DubboService +public class BookServiceImpl implements BookService { + @Override + public Book getById(long id) { + Book result = new Book(); + result.setId(id); + result.setName("diguage"); + result.setPublishDate(new Date()); + return result; + } + + @Override + public Long save(Book book) { + return System.currentTimeMillis(); + } +} diff --git a/truman/src/main/java/com/diguage/truman/dubbo/ProviderApplication.java b/truman/src/main/java/com/diguage/truman/dubbo/ProviderApplication.java new file mode 100644 index 000000000000..ea422ccb419f --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/dubbo/ProviderApplication.java @@ -0,0 +1,32 @@ +package com.diguage.truman.dubbo; + +import org.apache.dubbo.config.RegistryConfig; +import org.apache.dubbo.config.spring.context.annotation.EnableDubbo; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; + +import java.util.concurrent.locks.LockSupport; + +public class ProviderApplication { + public static void main(String[] args) { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(ProviderConfiguration.class); + context.refresh(); + context.start(); + LockSupport.park(); + } + + @Configuration + @EnableDubbo(scanBasePackages = "com.diguage.truman.dubbo") + @PropertySource("classpath:/dubbo/provider.properties") + static class ProviderConfiguration { + @Bean + public RegistryConfig registryConfig() { + RegistryConfig result = new RegistryConfig(); + result.setAddress("zookeeper://127.0.0.1:2181"); + return result; + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/ext/DggNamespaceHandler.java b/truman/src/main/java/com/diguage/truman/ext/DggNamespaceHandler.java new file mode 100644 index 000000000000..8b0d864f6aec --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/ext/DggNamespaceHandler.java @@ -0,0 +1,14 @@ +package com.diguage.truman.ext; + +import org.springframework.beans.factory.xml.NamespaceHandlerSupport; + +/** + * @author D瓜哥, https://www.diguage.com/ + * @since 2020-06-10 00:11 + */ +public class DggNamespaceHandler extends NamespaceHandlerSupport { + @Override + public void init() { + registerBeanDefinitionParser("user", new UserBeanDefinitionParser()); + } +} diff --git a/truman/src/main/java/com/diguage/truman/ext/ExtensionTest.java b/truman/src/main/java/com/diguage/truman/ext/ExtensionTest.java new file mode 100644 index 000000000000..22d96f5ce787 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/ext/ExtensionTest.java @@ -0,0 +1,20 @@ +package com.diguage.truman.ext; + +import org.junit.jupiter.api.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +import static com.diguage.truman.util.Constans.BASE_CLASS_PATH; + +/** + * @author D瓜哥, https://www.diguage.com/ + * @since 2020-06-10 00:26 + */ +public class ExtensionTest { + @Test + public void test() { + ClassPathXmlApplicationContext context + = new ClassPathXmlApplicationContext(BASE_CLASS_PATH + "/ext/dgg.xml"); + User user = context.getBean(User.class); + System.out.println(user.getUserName() + " : " + user.getEmail()); + } +} diff --git a/truman/src/main/java/com/diguage/truman/ext/User.java b/truman/src/main/java/com/diguage/truman/ext/User.java new file mode 100644 index 000000000000..891cc86cb56d --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/ext/User.java @@ -0,0 +1,26 @@ +package com.diguage.truman.ext; + +/** + * @author D瓜哥, https://www.diguage.com/ + * @since 2020-06-09 23:56 + */ +public class User { + private String userName; + private String email; + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } +} diff --git a/truman/src/main/java/com/diguage/truman/ext/UserBeanDefinitionParser.java b/truman/src/main/java/com/diguage/truman/ext/UserBeanDefinitionParser.java new file mode 100644 index 000000000000..8f95822764f9 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/ext/UserBeanDefinitionParser.java @@ -0,0 +1,29 @@ +package com.diguage.truman.ext; + +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; +import org.springframework.util.StringUtils; +import org.w3c.dom.Element; + +/** + * @author D瓜哥, https://www.diguage.com/ + * @since 2020-06-10 00:07 + */ +public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { + @Override + protected Class getBeanClass(Element element) { + return User.class; + } + + @Override + protected void doParse(Element element, BeanDefinitionBuilder builder) { + String userName = element.getAttribute("userName"); + String email = element.getAttribute("email"); + if (StringUtils.hasText(userName)) { + builder.addPropertyValue("userName", userName); + } + if (StringUtils.hasText(email)) { + builder.addPropertyValue("email", email); + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/jdbc/DataSourceTest.java b/truman/src/main/java/com/diguage/truman/jdbc/DataSourceTest.java new file mode 100644 index 000000000000..94e7b4edf6cb --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/jdbc/DataSourceTest.java @@ -0,0 +1,61 @@ +package com.diguage.truman.jdbc; + +import com.mysql.cj.jdbc.Driver; +import com.zaxxer.hikari.HikariDataSource; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.Statement; +import java.sql.Timestamp; +import java.util.concurrent.TimeUnit; + +public class DataSourceTest { + + private static final Logger logger = LoggerFactory.getLogger(DataSourceTest.class); + + @Test + public void testDataSource() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(Config.class); + context.refresh(); + DataSource dataSource = context.getBean(DataSource.class); + while (true) { + try { + Connection connection = dataSource.getConnection(); + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery("SELECT CURRENT_TIMESTAMP()"); + while (resultSet.next()) { + Timestamp date = resultSet.getTimestamp(1); + System.out.println(date.toInstant()); + } + connection.close(); + TimeUnit.SECONDS.sleep(3); + } catch (Throwable e) { + logger.error("query error", e); + } + } + } + + @Configuration + public static class Config { + @Bean + public DataSource dataSource() { + HikariDataSource dataSource = new HikariDataSource(); + dataSource.setUsername("root"); + dataSource.setPassword("123456"); + dataSource.setDriverClassName(Driver.class.getName()); + dataSource.setConnectionTimeout(TimeUnit.SECONDS.toMillis(5)); + + dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/employees?useUnicode=true" + + "&characterEncoding=utf-8&autoReconnectForPools=true&autoReconnect=true"); + return dataSource; + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/jdbc/Employees.java b/truman/src/main/java/com/diguage/truman/jdbc/Employees.java new file mode 100644 index 000000000000..bf4635aa5976 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/jdbc/Employees.java @@ -0,0 +1,28 @@ +package com.diguage.truman.jdbc; + +import java.util.Date; + +/** + * @author D瓜哥 · https://www.diguage.com + * @since 2020-05-29 17:24 + */ +public class Employees { + Integer empNo; + Date birthDate; + String firstName; + String lastName; + String gender; + Date hireDate; + + @Override + public String toString() { + return "Employees{" + + "empNo=" + empNo + + ", birthDate=" + birthDate + + ", firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", gender='" + gender + '\'' + + ", hireDate=" + hireDate + + '}'; + } +} diff --git a/truman/src/main/java/com/diguage/truman/jdbc/JdbcTest.java b/truman/src/main/java/com/diguage/truman/jdbc/JdbcTest.java new file mode 100644 index 000000000000..a110a20d8ad9 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/jdbc/JdbcTest.java @@ -0,0 +1,88 @@ +package com.diguage.truman.jdbc; + +import com.mysql.cj.jdbc.Driver; +import com.zaxxer.hikari.HikariDataSource; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; +import org.springframework.transaction.annotation.Transactional; + +import javax.sql.DataSource; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +/** + * JDBC 测试类 + * + * @author D瓜哥, https://www.diguage.com/ + * @since 2020-07-09 14:57 + */ +public class JdbcTest { + @Test + public void test() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(Config.class); + ctx.refresh(); + EmployeesService service = ctx.getBean(EmployeesService.class); + Employees employees = new Employees(); + employees.empNo = (int) System.currentTimeMillis(); + Date now = new Date(); + employees.birthDate = now; + employees.firstName = "Dummy"; + employees.lastName = "Fake"; + employees.gender = "F"; + employees.hireDate = now; + service.save(employees); + } + + + @Configuration + @EnableTransactionManagement + @Import(EmployeesService.class) + public static class Config { + @Bean + public DataSource dataSource() { + HikariDataSource dataSource = new HikariDataSource(); + dataSource.setUsername("root"); + dataSource.setPassword("123456"); + dataSource.setDriverClassName(Driver.class.getName()); + dataSource.setConnectionTimeout(TimeUnit.SECONDS.toMillis(5)); + + dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/employees?useUnicode=true" + + "&characterEncoding=utf-8&autoReconnectForPools=true&autoReconnect=true"); + return dataSource; + } + + @Bean + public PlatformTransactionManager transactionManager(DataSource dataSource) { + return new DataSourceTransactionManager(dataSource); + } + + @Bean + public JdbcTemplate getJdbcTemplate(DataSource dataSource) { + return new JdbcTemplate(dataSource); + } + } + + public static class EmployeesService { + @Autowired + private JdbcTemplate jdbcTemplate; + + @Transactional(rollbackFor = Throwable.class) + public boolean save(Employees emp) { + String sql = "INSERT INTO employees" + + "(emp_no, birth_date, first_name, last_name, gender, hire_date)" + + "VALUES(?, ?, ?, ?, ?, ?)"; + int count = jdbcTemplate.update(sql, emp.empNo, emp.birthDate, + emp.firstName, emp.lastName, emp.gender, emp.hireDate); + return count > 0; + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/jdbc/RoutingDataSourceTest.java b/truman/src/main/java/com/diguage/truman/jdbc/RoutingDataSourceTest.java new file mode 100644 index 000000000000..d1deac135038 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/jdbc/RoutingDataSourceTest.java @@ -0,0 +1,118 @@ +package com.diguage.truman.jdbc; + +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.HashMap; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * RoutingDataSource 测试类 + * + * @author D瓜哥, https://www.diguage.com/ + * @since 2022-02-04 22:57:14 + */ +public class RoutingDataSourceTest { + + private static final Logger log = LoggerFactory.getLogger(RoutingDataSourceTest.class); + + private static volatile boolean isMaster = true; + + private static final String MASTER_PREFIX = "master"; + private static final String SLAVE_PREFIX = "slave"; + + private static final String MASTER_DATA_SOURCE_NAME = "masterDataSource"; + private static final String SLAVE_DATA_SOURCE_NAME = "slaveDataSource"; + + // TODO 在注解中使用报错。 + // private static final String DATA_SOURCE_NAME = DataSource.class.getSimpleName(); + // private static final String MASTER_DATA_SOURCE_NAME = MASTER_PREFIX + DATA_SOURCE_NAME; + // private static final String SLAVE_DATA_SOURCE_NAME = SLAVE_PREFIX + DATA_SOURCE_NAME; + + private static final String CONNECTION_NAME = Connection.class.getSimpleName(); + private static final String MASTER_CONNECTION_NAME = MASTER_PREFIX + CONNECTION_NAME; + private static final String SLAVE_CONNECTION_NAME = SLAVE_PREFIX + CONNECTION_NAME; + + /** + * TODO 这个实验还不算成功。还需要再改进。 + */ + @Test + public void test() throws SQLException { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(Config.class); + ctx.refresh(); + + DataSource dataSource = ctx.getBean(DataSource.class); + Connection connection = dataSource.getConnection(); + assertThat(connection.toString()).isEqualTo(MASTER_CONNECTION_NAME); + + isMaster = false; + dataSource = ctx.getBean(DataSource.class); + connection = dataSource.getConnection(); + assertThat(connection.toString()).isEqualTo(SLAVE_CONNECTION_NAME); + } + + @Configuration + @EnableTransactionManagement + public static class Config { + @Bean(MASTER_DATA_SOURCE_NAME) + public DataSource masterDataSource() { + DataSource dataSource = mock(DataSource.class, MASTER_DATA_SOURCE_NAME); + try { + when(dataSource.getConnection()) + .thenReturn(mock(Connection.class, MASTER_CONNECTION_NAME)); + } catch (SQLException e) { + log.info("invoke getConnection error", e); + } + return dataSource; + } + + @Bean(SLAVE_DATA_SOURCE_NAME) + public DataSource slaveDataSource() { + DataSource dataSource = mock(DataSource.class, SLAVE_DATA_SOURCE_NAME); + try { + when(dataSource.getConnection()) + .thenReturn(mock(Connection.class, SLAVE_CONNECTION_NAME)); + } catch (SQLException e) { + log.info("invoke getConnection error", e); + } + return dataSource; + } + + @Bean + @Primary + public DataSource primaryDataSource( + @Autowired @Qualifier(MASTER_DATA_SOURCE_NAME) DataSource masterDataSource, + @Autowired @Qualifier(SLAVE_DATA_SOURCE_NAME) DataSource slaveDataSource) { + HotSwappableRoutingDataSource dataSource = new HotSwappableRoutingDataSource(); + HashMap dataSources = new HashMap<>(); + dataSources.put(MASTER_DATA_SOURCE_NAME, masterDataSource); + dataSources.put(SLAVE_DATA_SOURCE_NAME, slaveDataSource); + dataSource.setTargetDataSources(dataSources); + dataSource.setDefaultTargetDataSource(masterDataSource); + return dataSource; + } + } + + public static class HotSwappableRoutingDataSource extends AbstractRoutingDataSource { + @Override + protected Object determineCurrentLookupKey() { + return isMaster ? MASTER_DATA_SOURCE_NAME : SLAVE_DATA_SOURCE_NAME; + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/mybatis/Employees.java b/truman/src/main/java/com/diguage/truman/mybatis/Employees.java new file mode 100644 index 000000000000..6719323de446 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/mybatis/Employees.java @@ -0,0 +1,28 @@ +package com.diguage.truman.mybatis; + +import java.util.Date; + +/** + * @author D瓜哥 · https://www.diguage.com + * @since 2020-05-29 17:24 + */ +public class Employees { + public Integer empNo; + public Date birthDate; + public String firstName; + public String lastName; + public String gender; + public Date hireDate; + + @Override + public String toString() { + return "Employees{" + + "empNo=" + empNo + + ", birthDate=" + birthDate + + ", firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", gender='" + gender + '\'' + + ", hireDate=" + hireDate + + '}'; + } +} diff --git a/truman/src/main/java/com/diguage/truman/mybatis/EmployeesMapper.java b/truman/src/main/java/com/diguage/truman/mybatis/EmployeesMapper.java new file mode 100644 index 000000000000..100e31b4f898 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/mybatis/EmployeesMapper.java @@ -0,0 +1,22 @@ +package com.diguage.truman.mybatis; + +import org.apache.ibatis.annotations.Insert; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +/** + * @author D瓜哥 · https://www.diguage.com + * @since 2020-05-29 17:23 + */ +public interface EmployeesMapper { + + @MapperAop + @Select("SELECT * FROM employees WHERE emp_no = #{id}") + Employees getById(@Param("id") Integer id); + + @MapperAop + @Insert("INSERT INTO employees(emp_no, birth_date, first_name, last_name, gender, hire_date) " + + "VALUES(#{empNo}, #{birthDate, jdbcType=TIMESTAMP}, #{firstName}, #{lastName}, #{gender}, " + + " #{hireDate, jdbcType=TIMESTAMP})") + int insert(Employees employees); +} diff --git a/truman/src/main/java/com/diguage/truman/mybatis/MapperAop.java b/truman/src/main/java/com/diguage/truman/mybatis/MapperAop.java new file mode 100644 index 000000000000..301685dac153 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/mybatis/MapperAop.java @@ -0,0 +1,8 @@ +package com.diguage.truman.mybatis; + +/** + * 这里做AOP不生效,怎么办? + * 答:启用 @EnableAspectJAutoProxy 即可生效 + */ +public @interface MapperAop { +} diff --git a/truman/src/main/java/com/diguage/truman/mybatis/MybatisTest.java b/truman/src/main/java/com/diguage/truman/mybatis/MybatisTest.java new file mode 100644 index 000000000000..8728c9948a18 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/mybatis/MybatisTest.java @@ -0,0 +1,67 @@ +package com.diguage.truman.mybatis; + +import com.mysql.cj.jdbc.Driver; +import com.zaxxer.hikari.HikariDataSource; +import org.apache.ibatis.mapping.Environment; +import org.apache.ibatis.session.Configuration; +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.session.SqlSessionFactory; +import org.apache.ibatis.session.SqlSessionFactoryBuilder; +import org.apache.ibatis.transaction.TransactionFactory; +import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.sql.DataSource; +import java.util.concurrent.TimeUnit; + +/** + * @author D瓜哥, https://www.diguage.com/ + * @since 2020-05-29 17:11 + */ +public class MybatisTest { + private static final Logger logger = LoggerFactory.getLogger(MybatisTest.class); + + // tag::testCacheQuery[] + /** + * @author D瓜哥 · https://www.diguage.com + * @since 2022-07-03 09:47:37 + */ + @Test + public void testCacheQuery() { + DataSource dataSource = getDataSource(); + TransactionFactory transactionFactory = new JdbcTransactionFactory(); + Environment environment = + new Environment("development", transactionFactory, dataSource); + Configuration configuration = new Configuration(environment); + configuration.addMapper(EmployeesMapper.class); + configuration.setCacheEnabled(true); + configuration.setMapUnderscoreToCamelCase(true); + SqlSessionFactory sqlSessionFactory = + new SqlSessionFactoryBuilder().build(configuration); + SqlSession session = sqlSessionFactory.openSession(); + EmployeesMapper mapper = session.getMapper(EmployeesMapper.class); + System.out.println(mapper.getById(10001)); + System.out.println(mapper.getById(10001)); + } + // end::testCacheQuery[] + + // tag::getDataSource[] + /** + * @author D瓜哥 · https://www.diguage.com + * @since 2022-07-03 09:47:37 + */ + public DataSource getDataSource() { + HikariDataSource dataSource = new HikariDataSource(); + dataSource.setUsername("root"); + dataSource.setPassword("123456"); + dataSource.setDriverClassName(Driver.class.getName()); + dataSource.setConnectionTimeout(TimeUnit.SECONDS.toMillis(5)); + + dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/employees?useUnicode=true" + + "&characterEncoding=utf-8&autoReconnectForPools=true&autoReconnect=true"); + return dataSource; + } + // end::getDataSource[] +} diff --git a/truman/src/main/java/com/diguage/truman/mybatis/SpringMybatisTest.java b/truman/src/main/java/com/diguage/truman/mybatis/SpringMybatisTest.java new file mode 100644 index 000000000000..7909a5d6bc7a --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/mybatis/SpringMybatisTest.java @@ -0,0 +1,130 @@ +package com.diguage.truman.mybatis; + +import com.alibaba.fastjson2.JSON; +import com.mysql.cj.jdbc.Driver; +import com.zaxxer.hikari.HikariDataSource; +import jakarta.annotation.Resource; +import org.apache.ibatis.session.Configuration; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.After; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.junit.jupiter.api.Test; +import org.mybatis.spring.SqlSessionFactoryBean; +import org.mybatis.spring.annotation.MapperScan; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.context.annotation.Import; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.stereotype.Service; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; +import org.springframework.transaction.annotation.Transactional; + +import javax.sql.DataSource; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +/** + * @author D瓜哥 · https://www.diguage.com + * @since 2020-05-29 17:11 + */ +public class SpringMybatisTest { + private static final Logger logger = LoggerFactory.getLogger(SpringMybatisTest.class); + + // 测试一下 AOP 在 MyBATIS Mapper 接口上的效果。 + // 测试完毕,启用 AspectJ 后即可生效。 + + @Test + public void testCacheQuery() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(Config.class); + context.refresh(); + EmployeesMapper employeesMapper = context.getBean(EmployeesMapper.class); + Employees employees = employeesMapper.getById(10001); + System.out.println(employees); + System.out.println(employeesMapper.getById(10001)); + } + + @Test + public void testInsert() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(Config.class); + context.refresh(); + EmployeesService service = context.getBean(EmployeesService.class); + Employees employees = new Employees(); + employees.empNo = Math.toIntExact(System.currentTimeMillis() / 1000); + employees.birthDate = new Date(); + employees.firstName = "diguage"; + employees.lastName = "test"; + employees.gender = "F"; + employees.hireDate = employees.birthDate; + int insert = service.save(employees); + } + + @org.springframework.context.annotation.Configuration + @EnableTransactionManagement + @EnableAspectJAutoProxy + @MapperScan(basePackages = "com.diguage.truman.mybatis") + @Import({EmployeesService.class, MapperAspect.class}) + public static class Config { + @Bean + public DataSource dataSource() { + HikariDataSource dataSource = new HikariDataSource(); + dataSource.setUsername("root"); + dataSource.setPassword("123456"); + dataSource.setDriverClassName(Driver.class.getName()); + dataSource.setConnectionTimeout(TimeUnit.SECONDS.toMillis(5)); + + dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/employees?useUnicode=true" + + "&characterEncoding=utf-8&autoReconnectForPools=true&autoReconnect=true"); + return dataSource; + } + + @Bean + public PlatformTransactionManager transactionManager(DataSource dataSource) { + return new DataSourceTransactionManager(dataSource); + } + + @Bean + public SqlSessionFactoryBean sqlSessionFactory(@Autowired DataSource dataSource) { + SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); + factoryBean.setDataSource(dataSource); + Configuration configuration = new Configuration(); + configuration.setMapUnderscoreToCamelCase(true); + factoryBean.setConfiguration(configuration); + return factoryBean; + } + } + + @Service + public static class EmployeesService { + @Resource + private EmployeesMapper employeesMapper; + + @Transactional + public int save(Employees employees) { + return employeesMapper.insert(employees); + } + + public Employees getById(int id) { + return employeesMapper.getById(id); + } + } + + @Aspect + public static class MapperAspect { + @Pointcut("@annotation(com.diguage.truman.mybatis.MapperAop)") + public void pointcut() { + } + + @After("pointcut()") + public void doTask(JoinPoint joinPoint) { + logger.info("mapper method was called: {}", JSON.toJSON(joinPoint.getArgs())); + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/test/ContextConfigurationTest.java b/truman/src/main/java/com/diguage/truman/test/ContextConfigurationTest.java new file mode 100644 index 000000000000..d3045086dfca --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/test/ContextConfigurationTest.java @@ -0,0 +1,39 @@ +package com.diguage.truman.test; + +import lombok.AllArgsConstructor; +import lombok.Data; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static com.diguage.truman.util.Constans.BASE_CLASS_PATH; + + +@ExtendWith(SpringExtension.class) +@ContextConfiguration(BASE_CLASS_PATH + "/test/XmlApplicationContextTest.xml") +public class ContextConfigurationTest { + + @Autowired + private UserService userService; + + @Test + public void test() { + User user = userService.getById(1L); + System.out.println(user.toString()); + } + + @Data + @AllArgsConstructor + public static class User { + private long id; + private String name; + } + + public static class UserService { + public User getById(long id) { + return new User(id, "diguage-" + id); + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/tx/TransactionTemplateTest.java b/truman/src/main/java/com/diguage/truman/tx/TransactionTemplateTest.java new file mode 100644 index 000000000000..d7cd3386d8a6 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/tx/TransactionTemplateTest.java @@ -0,0 +1,115 @@ +package com.diguage.truman.tx; + +import com.mysql.cj.jdbc.Driver; +import com.zaxxer.hikari.HikariDataSource; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.stereotype.Service; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; +import org.springframework.transaction.support.TransactionTemplate; + +import javax.sql.DataSource; +import java.util.Date; + +/** + * @author D瓜哥, https://www.diguage.com/ + * @since 2020-09-11 10:20 + */ +public class TransactionTemplateTest { + + @Test + public void test() { + AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); + applicationContext.register(Config.class); + applicationContext.refresh(); + EmployeesService employeesService = applicationContext.getBean(EmployeesService.class); + Employees employees = new Employees(); +// employees.empNo = 5000000; + employees.birthDate = new Date(); + employees.firstName = "D"; + employees.gender = "M"; + employees.hireDate = new Date(); + employees.lastName = "瓜哥"; + employeesService.save(employees); + System.out.println(employees); + } + + @Configuration + @EnableTransactionManagement + @Import(EmployeesService.class) + public static class Config { + @Bean + public DataSource dataSource() { + HikariDataSource dataSource = new HikariDataSource(); + dataSource.setUsername("root"); + dataSource.setPassword("123456"); + dataSource.setDriverClassName(Driver.class.getName()); + dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/employees?useUnicode=true" + + "&characterEncoding=utf-8&autoReconnectForPools=true&autoReconnect=true"); + return dataSource; + } + + @Bean + public JdbcTemplate jdbcTemplate(DataSource dataSource) { + return new JdbcTemplate(dataSource); + } + + @Bean + public PlatformTransactionManager transactionManager(DataSource dataSource) { + return new DataSourceTransactionManager(dataSource); + } + } + + public static class Employees { + Integer empNo; + Date birthDate; + String firstName; + String lastName; + String gender; + Date hireDate; + + @Override + public String toString() { + return "Employees{" + + "empNo=" + empNo + + ", birthDate=" + birthDate + + ", firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", gender='" + gender + '\'' + + ", hireDate=" + hireDate + + '}'; + } + } + + @Service + public static class EmployeesService { + @Resource + private JdbcTemplate jdbcTemplate; + + private final TransactionTemplate transactionTemplate; + + public EmployeesService(PlatformTransactionManager transactionManager) { + transactionTemplate = new TransactionTemplate(transactionManager); + transactionTemplate.setTimeout(Integer.MAX_VALUE); + } + + public boolean save(Employees employees) { + Integer result = transactionTemplate.execute(status -> { + String sql = "INSERT INTO employees(emp_no, birth_date, first_name," + + " last_name, gender, hire_date) VALUES (?, ?, ?, ?, ?, ?)"; + int updatedCount = jdbcTemplate.update(sql, employees.empNo, + employees.birthDate, employees.firstName, employees.lastName, + employees.gender, employees.hireDate); + return updatedCount; + }); + return result == 1; + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/tx/TxOnCloseTest.java b/truman/src/main/java/com/diguage/truman/tx/TxOnCloseTest.java new file mode 100644 index 000000000000..12669c093fb8 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/tx/TxOnCloseTest.java @@ -0,0 +1,122 @@ +package com.diguage.truman.tx; + +import com.mysql.cj.jdbc.Driver; +import com.zaxxer.hikari.HikariDataSource; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.stereotype.Service; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.annotation.EnableTransactionManagement; +import org.springframework.transaction.support.DefaultTransactionDefinition; + +import javax.sql.DataSource; +import java.util.Date; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +/** + * @author D瓜哥, https://www.diguage.com/ + * @since 2020-09-11 10:20 + */ +public class TxOnCloseTest { + + /** + * TODO 这个实验还没搞好! + */ + @Test + public void test() { + AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); + applicationContext.register(Config.class); + applicationContext.refresh(); + EmployeesService employeesService = applicationContext.getBean(EmployeesService.class); + PlatformTransactionManager transactionManager = applicationContext.getBean(PlatformTransactionManager.class); + + Employees employees = new Employees(); + employees.empNo = (int) System.currentTimeMillis() / 1000; + employees.birthDate = new Date(); + employees.firstName = "trans"; + employees.lastName = "action"; + employees.gender = "F"; + employees.hireDate = new Date(); + TransactionStatus transactionStatus = transactionManager.getTransaction(new DefaultTransactionDefinition()); + employeesService.save(employees); + ExecutorService executorService = Executors.newFixedThreadPool(1); + executorService.execute(() -> applicationContext.close()); + try { + TimeUnit.SECONDS.sleep(300); + } catch (InterruptedException e) { + e.printStackTrace(); + } + transactionManager.commit(transactionStatus); + + System.out.println(employees); + } + + @Configuration + @EnableTransactionManagement + @Import(EmployeesService.class) + public static class Config { + @Bean + public DataSource dataSource() { + // TODO 设置超时时间,事务超时时间以及数据库里面的超时时间。 + HikariDataSource dataSource = new HikariDataSource(); + dataSource.setUsername("root"); + dataSource.setPassword("123456"); + dataSource.setDriverClassName(Driver.class.getName()); + dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/employees?useUnicode=true" + + "&characterEncoding=utf-8&autoReconnectForPools=true&autoReconnect=true"); + return dataSource; + } + + @Bean + public JdbcTemplate jdbcTemplate(DataSource dataSource) { + return new JdbcTemplate(dataSource); + } + + @Bean + public PlatformTransactionManager transactionManager(DataSource dataSource) { + return new DataSourceTransactionManager(dataSource); + } + } + + public static class Employees { + Integer empNo; + Date birthDate; + String firstName; + String lastName; + String gender; + Date hireDate; + + @Override + public String toString() { + return "Employees{" + + "empNo=" + empNo + + ", birthDate=" + birthDate + + ", firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", gender='" + gender + '\'' + + ", hireDate=" + hireDate + + '}'; + } + } + + @Service + public static class EmployeesService { + @Resource + private JdbcTemplate jdbcTemplate; + + public int save(Employees employees) { + String sql = "INSERT INTO employees(emp_no, birth_date, first_name," + + " last_name, gender, hire_date) VALUE (?, ?, ?, ?, ?, ?)"; + return jdbcTemplate.update(sql, employees.empNo, employees.birthDate, employees.firstName, employees.lastName, employees.gender, employees.hireDate); + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/tx/TxTest.java b/truman/src/main/java/com/diguage/truman/tx/TxTest.java new file mode 100644 index 000000000000..cbb4a68bb95f --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/tx/TxTest.java @@ -0,0 +1,178 @@ +package com.diguage.truman.tx; + +import com.mysql.cj.jdbc.Driver; +import com.zaxxer.hikari.HikariDataSource; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationEventPublisherAware; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.stereotype.Service; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; +import org.springframework.transaction.support.TransactionSynchronization; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +import javax.sql.DataSource; +import java.util.Date; + +/** + * @author D瓜哥, https://www.diguage.com/ + * @since 2020-09-11 10:20 + */ +public class TxTest { + + private static final Logger log = LoggerFactory.getLogger(TxTest.class); + + @Test + public void test() { + AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); + applicationContext.register(Config.class); + applicationContext.refresh(); + EmployeesService employeesService = applicationContext.getBean(EmployeesService.class); + + Employees newEmp = new Employees(); + newEmp.empNo = Math.toIntExact(System.currentTimeMillis() / 100000); + newEmp.birthDate = new Date(); + newEmp.firstName = "diguage"; + newEmp.lastName = "test"; + newEmp.hireDate = new Date(); + newEmp.gender = "F"; + employeesService.save(newEmp); + + Employees existEmp = employeesService.getById(newEmp.empNo); + System.out.println(existEmp); + } + + @Configuration + @EnableTransactionManagement + @Import({EmployeesService.class, TransactionPostTask.class}) + public static class Config { + @Bean + public DataSource dataSource() { + HikariDataSource dataSource = new HikariDataSource(); + dataSource.setUsername("root"); + dataSource.setPassword("123456"); + dataSource.setDriverClassName(Driver.class.getName()); + dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/employees?useUnicode=true" + + "&characterEncoding=utf-8&autoReconnectForPools=true&autoReconnect=true"); + return dataSource; + } + + @Bean + public JdbcTemplate jdbcTemplate(DataSource dataSource) { + return new JdbcTemplate(dataSource); + } + + @Bean + public PlatformTransactionManager transactionManager(DataSource dataSource) { + return new DataSourceTransactionManager(dataSource); + } + } + + public static class Employees { + Integer empNo; + Date birthDate; + String firstName; + String lastName; + String gender; + Date hireDate; + + @Override + public String toString() { + return "Employees{" + + "empNo=" + empNo + + ", birthDate=" + birthDate + + ", firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", gender='" + gender + '\'' + + ", hireDate=" + hireDate + + '}'; + } + } + + @Service + public static class EmployeesService implements ApplicationEventPublisherAware { + @Resource + private JdbcTemplate jdbcTemplate; + + private ApplicationEventPublisher applicationEventPublisher; + + @Transactional(readOnly = true, rollbackFor = Throwable.class) + public Employees getById(Integer id) { + String sql = "SELECT * FROM employees WHERE emp_no = ?"; + + return jdbcTemplate.queryForObject(sql, (rs, rowNum) -> { + Employees employee = new Employees(); + employee.empNo = rs.getInt("emp_no"); + employee.birthDate = rs.getDate("birth_date"); + employee.firstName = rs.getString("first_name"); + employee.lastName = rs.getString("last_name"); + employee.gender = rs.getString("gender"); + employee.hireDate = rs.getDate("hire_date"); + return employee; + }, new Object[]{id}); + } + + @Transactional(rollbackFor = Throwable.class) + public boolean save(Employees employee) { + + // :empNo, :birthDate, :firstName, :lastName, :gender, :hireDate + int updated = jdbcTemplate.update("INSERT INTO employees(emp_no, birth_date, first_name, last_name, gender, hire_date) " + + "VALUES(?, ?, ?, ?, ?, ?)", employee.empNo, employee.birthDate, employee.firstName, employee.lastName, employee.gender, employee.hireDate); + + // 以下是事务提交后置处理器 + // TransactionSynchronization 示例 + // TODO 如何封装一下?可以通过简单的注解或者配置完成这个事件处理。 + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { + @Override + public void afterCommit() { + // TODO 这里如何传递参数?直接引用上面的参数? + log.info("TransactionSynchronization.afterCommit was called"); + } + }); + + // @TransactionalEventListener 示例 + // TODO 如何封装一下?可以通过简单的注解或者配置完成这个事件处理。 + TransactionPostEvent event = new TransactionPostEvent<>(); + event.data = "This is a object"; + applicationEventPublisher.publishEvent(event); + + return updated > 0; + } + + @Override + public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { + this.applicationEventPublisher = applicationEventPublisher; + } + } + + + public static class TransactionPostEvent { + T data; + + @Override + public String toString() { + return "TransactionPostEvent{" + + "data=" + data + + '}'; + } + } + + public static class TransactionPostTask { + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT, classes = TransactionPostEvent.class) + public void doTask(TransactionPostEvent transactionPostEvent) { + log.info("TransactionPostTask doTask was called: {}", transactionPostEvent); + } + } +} diff --git a/truman/src/main/java/com/diguage/truman/util/Constans.java b/truman/src/main/java/com/diguage/truman/util/Constans.java new file mode 100644 index 000000000000..253fe842d667 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/util/Constans.java @@ -0,0 +1,5 @@ +package com.diguage.truman.util; + +public interface Constans { + String BASE_CLASS_PATH = "classpath:com/diguage/truman"; +} diff --git a/truman/src/main/java/com/diguage/truman/web/AppInitializer.java b/truman/src/main/java/com/diguage/truman/web/AppInitializer.java new file mode 100644 index 000000000000..41e4b372769d --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/web/AppInitializer.java @@ -0,0 +1,24 @@ +package com.diguage.truman.web; + +import org.springframework.web.WebApplicationInitializer; +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; +import org.springframework.web.servlet.DispatcherServlet; + +import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRegistration.Dynamic; + +/** + * @author D瓜哥, https://www.diguage.com + */ +public class AppInitializer implements WebApplicationInitializer { + @Override + public void onStartup(ServletContext servletContext) throws ServletException { + AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); + context.register(WebAppConfig.class); + context.setServletContext(servletContext); + Dynamic servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(context)); + servlet.addMapping("/"); + servlet.setLoadOnStartup(1); + } +} diff --git a/truman/src/main/java/com/diguage/truman/web/HomeServlet.java b/truman/src/main/java/com/diguage/truman/web/HomeServlet.java new file mode 100644 index 000000000000..1a6bd06a010c --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/web/HomeServlet.java @@ -0,0 +1,23 @@ +package com.diguage.truman.web; + +import jakarta.servlet.ServletException; +import jakarta.servlet.annotation.WebServlet; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; + +/** + * https://www.baeldung.com/java-web-app-without-web-xml + * + * @author D瓜哥, https://www.diguage.com + */ +@WebServlet(urlPatterns = "/about", name = "aboutServlet") +public class HomeServlet extends HttpServlet { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + PrintWriter writer = resp.getWriter(); + writer.println("This is the about page."); + } +} diff --git a/truman/src/main/java/com/diguage/truman/web/TraceFilter.java b/truman/src/main/java/com/diguage/truman/web/TraceFilter.java new file mode 100644 index 000000000000..f85fc6688e2f --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/web/TraceFilter.java @@ -0,0 +1,35 @@ +package com.diguage.truman.web; + +import jakarta.servlet.*; +import jakarta.servlet.annotation.WebFilter; +import jakarta.servlet.http.HttpServletRequest; +import java.io.IOException; + +/** + * @author D瓜哥, https://www.diguage.com + */ +@WebFilter(urlPatterns = "/*") +public class TraceFilter implements Filter { + @Override + public void init(FilterConfig filterConfig) throws ServletException { + + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + HttpServletRequest httpRequest = (HttpServletRequest) request; + String queryString = httpRequest.getQueryString(); + String url = httpRequest.getRequestURL().toString() + (queryString == null ? "" : queryString); +// String url = httpRequest.getScheme() + "://" + httpRequest.getServerName() + ":" + httpRequest.getServerPort() +// + httpRequest.getQueryString(); + + System.out.println(url + " Start"); + chain.doFilter(request, response); + System.out.println(url + " End"); + } + + @Override + public void destroy() { + + } +} diff --git a/truman/src/main/java/com/diguage/truman/web/WebAppConfig.java b/truman/src/main/java/com/diguage/truman/web/WebAppConfig.java new file mode 100644 index 000000000000..1fb399d3743b --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/web/WebAppConfig.java @@ -0,0 +1,23 @@ +package com.diguage.truman.web; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.view.InternalResourceViewResolver; + +/** + * @author D瓜哥, https://www.diguage.com + */ +@Configuration +@ComponentScan(basePackages = {"com.diguage.truman.web"}) +@EnableWebMvc +public class WebAppConfig { + @Bean + public InternalResourceViewResolver getInternalResourceViewResolver() { + InternalResourceViewResolver resolver = new InternalResourceViewResolver(); + resolver.setPrefix("/WEB-INF/views"); + resolver.setSuffix(".jsp"); + return resolver; + } +} diff --git a/truman/src/main/java/com/diguage/truman/web/undertow/MainWebAppInitializer.java b/truman/src/main/java/com/diguage/truman/web/undertow/MainWebAppInitializer.java new file mode 100644 index 000000000000..dbee791d521d --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/web/undertow/MainWebAppInitializer.java @@ -0,0 +1,23 @@ +package com.diguage.truman.web.undertow; + +import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRegistration; +import org.springframework.web.WebApplicationInitializer; +import org.springframework.web.context.ContextLoaderListener; +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; +import org.springframework.web.context.support.GenericWebApplicationContext; +import org.springframework.web.servlet.DispatcherServlet; + +public class MainWebAppInitializer implements WebApplicationInitializer { + @Override + public void onStartup(ServletContext servletContext) throws ServletException { + AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext(); + ctx.scan("com.diguage.truman.web.undertowv"); + servletContext.addListener(new ContextLoaderListener(ctx)); + ServletRegistration.Dynamic appServlet + = servletContext.addServlet("mvc", new DispatcherServlet(new GenericWebApplicationContext())); + appServlet.setLoadOnStartup(1); + appServlet.addMapping("/"); + } +} diff --git a/truman/src/main/java/com/diguage/truman/web/undertow/UndertowContainer.java b/truman/src/main/java/com/diguage/truman/web/undertow/UndertowContainer.java new file mode 100644 index 000000000000..da08af5b6182 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/web/undertow/UndertowContainer.java @@ -0,0 +1,43 @@ +package com.diguage.truman.web.undertow; + +import io.undertow.Handlers; +import io.undertow.Undertow; +import io.undertow.server.HttpHandler; +import io.undertow.server.handlers.PathHandler; +import io.undertow.servlet.Servlets; +import io.undertow.servlet.api.DeploymentInfo; +import io.undertow.servlet.api.DeploymentManager; +import org.springframework.web.servlet.DispatcherServlet; + +import static io.undertow.servlet.Servlets.defaultContainer; +import static io.undertow.servlet.Servlets.servlet; + + +public class UndertowContainer { + public static final String WEBMVC = "/webmvc"; + +// https://github.com/yarosla/spring-undertow/blob/master/src/main/java/ys/undertow/UndertowMain.java +// https://www.baeldung.com/java-web-app-without-web-xml +// TODO 未完成 + public static void main(String[] args) throws Throwable { + DeploymentInfo servletBuilder = Servlets.deployment() + .setClassLoader(UndertowContainer.class.getClassLoader()) + .setContextPath(WEBMVC) + .setDeploymentName("webmvc.war") + .addServlets( + servlet("DispatcherServlet", DispatcherServlet.class) + .addInitParam("message", "Hello D瓜哥") + .addMapping("/*")); + DeploymentManager manager = defaultContainer().addDeployment(servletBuilder); + manager.deploy(); + + HttpHandler servletHandler = manager.start(); + PathHandler path = Handlers.path(Handlers.redirect(WEBMVC)) + .addPrefixPath(WEBMVC, servletHandler); + Undertow server = Undertow.builder() + .addHttpListener(8080, "localhost") + .setHandler(path) + .build(); + server.start(); + } +} diff --git a/truman/src/main/java/com/diguage/truman/web/undertow/WebConfig.java b/truman/src/main/java/com/diguage/truman/web/undertow/WebConfig.java new file mode 100644 index 000000000000..8442a0bb965f --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/web/undertow/WebConfig.java @@ -0,0 +1,12 @@ +package com.diguage.truman.web.undertow; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@EnableWebMvc +@Configuration +@ComponentScan(basePackages = {"com.diguage.truman.web.undertow.rest"}) +public class WebConfig implements WebMvcConfigurer { +} diff --git a/truman/src/main/java/com/diguage/truman/web/undertow/rest/HelloController.java b/truman/src/main/java/com/diguage/truman/web/undertow/rest/HelloController.java new file mode 100644 index 000000000000..3d8a17931cd7 --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/web/undertow/rest/HelloController.java @@ -0,0 +1,12 @@ +package com.diguage.truman.web.undertow.rest; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class HelloController { + @RequestMapping("/hello") + public String hello() { + return "Hello, D瓜哥"; + } +} diff --git a/truman/src/main/java/com/diguage/truman/webflux/Test.java b/truman/src/main/java/com/diguage/truman/webflux/Test.java new file mode 100644 index 000000000000..f13a9d45988b --- /dev/null +++ b/truman/src/main/java/com/diguage/truman/webflux/Test.java @@ -0,0 +1,4 @@ +package com.diguage.truman.webflux; + +public class Test { +} diff --git a/truman/src/main/resources/META-INF/dgg.xsd b/truman/src/main/resources/META-INF/dgg.xsd new file mode 100644 index 000000000000..25d4ebf1fc02 --- /dev/null +++ b/truman/src/main/resources/META-INF/dgg.xsd @@ -0,0 +1,13 @@ + + + + + + + + + + \ No newline at end of file diff --git a/truman/src/main/resources/META-INF/spring.handlers b/truman/src/main/resources/META-INF/spring.handlers new file mode 100644 index 000000000000..4d9368562329 --- /dev/null +++ b/truman/src/main/resources/META-INF/spring.handlers @@ -0,0 +1 @@ +https\://www.diguage.com/schema/user=com.diguage.truman.ext.DggNamespaceHandler diff --git a/truman/src/main/resources/META-INF/spring.schemas b/truman/src/main/resources/META-INF/spring.schemas new file mode 100644 index 000000000000..058b3bcec5f1 --- /dev/null +++ b/truman/src/main/resources/META-INF/spring.schemas @@ -0,0 +1 @@ +https\://www.diguage.com/schema/user.xsd=META-INF/dgg.xsd diff --git a/truman/src/main/resources/com/diguage/truman/aop/HotSwappableTargetSource.xml b/truman/src/main/resources/com/diguage/truman/aop/HotSwappableTargetSource.xml new file mode 100644 index 000000000000..c38239facce3 --- /dev/null +++ b/truman/src/main/resources/com/diguage/truman/aop/HotSwappableTargetSource.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + swapDataSourceAdvisor + + + + + \ No newline at end of file diff --git a/truman/src/main/resources/com/diguage/truman/aop/xml/aop-xml.xml b/truman/src/main/resources/com/diguage/truman/aop/xml/aop-xml.xml new file mode 100644 index 000000000000..4398e536467e --- /dev/null +++ b/truman/src/main/resources/com/diguage/truman/aop/xml/aop-xml.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/truman/src/main/resources/com/diguage/truman/beans/env/PropertyPlaceholderConfigurerTest.properties b/truman/src/main/resources/com/diguage/truman/beans/env/PropertyPlaceholderConfigurerTest.properties new file mode 100644 index 000000000000..34a61a888067 --- /dev/null +++ b/truman/src/main/resources/com/diguage/truman/beans/env/PropertyPlaceholderConfigurerTest.properties @@ -0,0 +1 @@ +fileCfg=This is a file config option. \ No newline at end of file diff --git a/truman/src/main/resources/com/diguage/truman/beans/env/PropertyPlaceholderConfigurerTest.xml b/truman/src/main/resources/com/diguage/truman/beans/env/PropertyPlaceholderConfigurerTest.xml new file mode 100644 index 000000000000..5673bf521f2e --- /dev/null +++ b/truman/src/main/resources/com/diguage/truman/beans/env/PropertyPlaceholderConfigurerTest.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/truman/src/main/resources/com/diguage/truman/context/ApplicationListenerParentTest.xml b/truman/src/main/resources/com/diguage/truman/context/ApplicationListenerParentTest.xml new file mode 100644 index 000000000000..66edcb160c1e --- /dev/null +++ b/truman/src/main/resources/com/diguage/truman/context/ApplicationListenerParentTest.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/truman/src/main/resources/com/diguage/truman/context/PlaceholderTest.xml b/truman/src/main/resources/com/diguage/truman/context/PlaceholderTest.xml new file mode 100644 index 000000000000..96ac4cdbd50d --- /dev/null +++ b/truman/src/main/resources/com/diguage/truman/context/PlaceholderTest.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/truman/src/main/resources/com/diguage/truman/context/XmlApplicationContextTest.xml b/truman/src/main/resources/com/diguage/truman/context/XmlApplicationContextTest.xml new file mode 100644 index 000000000000..6e6c630ed5ef --- /dev/null +++ b/truman/src/main/resources/com/diguage/truman/context/XmlApplicationContextTest.xml @@ -0,0 +1,16 @@ + + + + + + + + + \ No newline at end of file diff --git a/truman/src/main/resources/com/diguage/truman/context/token.properties b/truman/src/main/resources/com/diguage/truman/context/token.properties new file mode 100644 index 000000000000..5b009149165a --- /dev/null +++ b/truman/src/main/resources/com/diguage/truman/context/token.properties @@ -0,0 +1,2 @@ +user.appId=dummyAppId +user.token=dummyToken diff --git a/truman/src/main/resources/com/diguage/truman/core/PropertiesApplicationContextTest.xml b/truman/src/main/resources/com/diguage/truman/core/PropertiesApplicationContextTest.xml new file mode 100644 index 000000000000..aa8023d7a60a --- /dev/null +++ b/truman/src/main/resources/com/diguage/truman/core/PropertiesApplicationContextTest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + classpath:com/diguage/truman/core/token.properties + + + + + + + + classpath:com/diguage/truman/core/token.properties + + + + \ No newline at end of file diff --git a/truman/src/main/resources/com/diguage/truman/core/token.properties b/truman/src/main/resources/com/diguage/truman/core/token.properties new file mode 100644 index 000000000000..5b009149165a --- /dev/null +++ b/truman/src/main/resources/com/diguage/truman/core/token.properties @@ -0,0 +1,2 @@ +user.appId=dummyAppId +user.token=dummyToken diff --git a/truman/src/main/resources/com/diguage/truman/ext/dgg.xml b/truman/src/main/resources/com/diguage/truman/ext/dgg.xml new file mode 100644 index 000000000000..5d4ca2c791c3 --- /dev/null +++ b/truman/src/main/resources/com/diguage/truman/ext/dgg.xml @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/truman/src/main/resources/com/diguage/truman/test/XmlApplicationContextTest.xml b/truman/src/main/resources/com/diguage/truman/test/XmlApplicationContextTest.xml new file mode 100644 index 000000000000..1b485d708176 --- /dev/null +++ b/truman/src/main/resources/com/diguage/truman/test/XmlApplicationContextTest.xml @@ -0,0 +1,16 @@ + + + + + + + + + \ No newline at end of file diff --git a/truman/src/main/resources/dubbo/consumer.properties b/truman/src/main/resources/dubbo/consumer.properties new file mode 100644 index 000000000000..2cc16e0c99c3 --- /dev/null +++ b/truman/src/main/resources/dubbo/consumer.properties @@ -0,0 +1,3 @@ +dubbo.application.name=dubbo-annotation-consumer +dubbo.registry.address=zookeeper://127.0.0.1:2181 +dubbo.protocol.port=-1 \ No newline at end of file diff --git a/truman/src/main/resources/dubbo/provider.properties b/truman/src/main/resources/dubbo/provider.properties new file mode 100644 index 000000000000..737cd22601f7 --- /dev/null +++ b/truman/src/main/resources/dubbo/provider.properties @@ -0,0 +1,3 @@ +dubbo.application.name=dubbo-annotation-provider +dubbo.protocol.name=dubbo +dubbo.protocol.port=-1 \ No newline at end of file diff --git a/truman/src/main/resources/log4j2.xml b/truman/src/main/resources/log4j2.xml new file mode 100644 index 000000000000..f58d9a7f7895 --- /dev/null +++ b/truman/src/main/resources/log4j2.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/truman/src/main/webapp/index.html b/truman/src/main/webapp/index.html new file mode 100644 index 000000000000..6a1cc80cf320 --- /dev/null +++ b/truman/src/main/webapp/index.html @@ -0,0 +1,10 @@ + + + + + 首页 + + +这是首页,测试页面! + + \ No newline at end of file diff --git a/truman/truman.gradle b/truman/truman.gradle new file mode 100644 index 000000000000..1e99140edf01 --- /dev/null +++ b/truman/truman.gradle @@ -0,0 +1,211 @@ +plugins { + id 'war' + id 'me.champeau.jmh' + id 'org.asciidoctor.jvm.convert' version '4.0.3' + id 'org.asciidoctor.jvm.pdf' version '4.0.3' + id 'org.asciidoctor.jvm.gems' version '4.0.3' + // id 'org.asciidoctor.jvm.revealjs' version '4.0.3' + id 'org.asciidoctor.editorconfig' version '4.0.3' +} + +configurations { + asciidoctorExt + asciidocExtensions +} + +dependencies { + asciidoctorGems "rubygems:rouge:4.3.0" + // asciidoctorGems "rubygems:rouge:3.30.0" + asciidoctorGems "rubygems:asciidoctor-multipage:0.0.19" + asciidoctorGems "rubygems:asciidoctor-comment-links:0.0.2" + + implementation(project(":spring-beans")) + implementation(project(":spring-context")) + implementation(project(":spring-jdbc")) + implementation(project(":spring-tx")) + implementation(project(":spring-webmvc")) + + implementation(project(":spring-test")) + + implementation("jakarta.annotation:jakarta.annotation-api") + implementation("com.fasterxml.jackson.core:jackson-databind") + + implementation("org.aspectj:aspectjweaver") + + implementation('org.projectlombok:lombok:1.18.32') + annotationProcessor 'org.projectlombok:lombok:1.18.32' + + implementation 'com.zaxxer:HikariCP:5.1.0' + implementation 'com.mysql:mysql-connector-j:8.4.0' + // implementation 'mysql:mysql-connector-java:5.1.49' + implementation 'org.mybatis:mybatis:3.5.16' + implementation 'org.mybatis:mybatis-spring:3.0.3' + + providedCompile("jakarta.servlet:jakarta.servlet-api:6.0.0") + implementation('io.undertow:undertow-servlet:2.3.13.Final') + implementation('io.undertow:undertow-websockets-jsr:2.3.13.Final') + + + // // 使用测试,注解总报错 + // // TODO 加了下面的依赖不报错了。新问题:明明没有使用 Kotlinx,为啥却要加这个依赖? + // implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core") + + jmh 'org.openjdk.jmh:jmh-core:1.37' + jmh 'org.openjdk.jmh:jmh-generator-annprocess:1.37' + jmh 'net.sf.jopt-simple:jopt-simple:5.0.4' + + implementation("org.slf4j:slf4j-api") + implementation("org.apache.logging.log4j:log4j-slf4j-impl") + implementation("org.apache.logging.log4j:log4j-1.2-api") + implementation("org.apache.logging.log4j:log4j-jcl") + implementation("org.apache.logging.log4j:log4j-jul") + implementation("org.apache.logging.log4j:log4j-core") + + implementation("org.junit.platform:junit-platform-launcher") + implementation("org.junit.jupiter:junit-jupiter-engine") + implementation("org.mockito:mockito-core") + implementation("org.mockito:mockito-junit-jupiter") + implementation("org.assertj:assertj-core") + + implementation platform("org.apache.dubbo:dubbo-bom:3.2.12") + implementation("org.apache.dubbo:dubbo-registry-multicast") + implementation("org.apache.dubbo:dubbo-registry-zookeeper") + implementation("org.apache.dubbo:dubbo-configcenter-zookeeper") + implementation("org.apache.dubbo:dubbo-metadata-report-zookeeper") + implementation("org.apache.dubbo:dubbo-rpc-dubbo") + implementation("org.apache.dubbo:dubbo-config-spring") + implementation("org.apache.dubbo:dubbo-remoting-netty4") + implementation("org.apache.dubbo:dubbo-serialization-hessian2") + + implementation 'javax.activation:activation:1.1.1' + implementation 'com.sun.activation:jakarta.activation:2.1.1' + implementation 'io.ratpack:ratpack-core:1.9.0' +} + +test { + // make sure the classes dir is used on the test classpath (required by ResourceTests) + // When test fixtures are involved, the JAR is used by default + classpath = sourceSets.main.output.classesDirs + classpath - files(jar.archiveFile) +} + +asciidoctorj { + requires "asciidoctor-comment-links" + // TODO 不生效 + // https://github.com/owenh000/asciidoctor-multipage +// requires "asciidoctor-multipage" +// attributes require: "asciidoctor-multipage", +// backend: "multipage_html5" +} + +asciidoctor { + // 加载必要的 Gem + dependsOn asciidoctorGemsPrepare + configurations 'asciidoctorExt' + configurations 'asciidoctorExtensions' + + baseDirFollowsSourceDir() + + sources { + include 'index.adoc' + } + outputDir "${project.getLayout().getBuildDirectory()}/docs/html5" + forkOptions { + jvmArgs += ["--add-opens", "java.base/sun.nio.ch=ALL-UNNAMED", + "--add-opens", "java.base/java.io=ALL-UNNAMED"] + } + logDocuments = true +} + +asciidoctorPdf { + baseDirFollowsSourceDir() + asciidoctorj { + attributes "pdf-fontsdir": "${project.projectDir}/cfg/fonts;GEM_FONTS_DIR;", + "pdf-themesdir": "${project.projectDir}/cfg/theme", + "pdf-theme": "Source" + } + sources { + include 'index.adoc' + } + outputDir "${project.getLayout().getBuildDirectory()}/docs/pdf" + forkOptions { + jvmArgs += ["--add-opens", "java.base/sun.nio.ch=ALL-UNNAMED", + "--add-opens", "java.base/java.io=ALL-UNNAMED"] + } + logDocuments = true + +} + +asciidoctorj { + def sourceAttr = 'linenums,indent=0,subs="attributes,verbatim"' + + version = '3.0.0' + jrubyVersion = '9.4.8.0' + fatalWarnings ".*" + options doctype: 'book', eruby: 'erubis' + attributes([ + author: 'D瓜哥', + email: 'https://www.diguage.com', + revnumber: project.version, + icons: 'font', + idprefix: '', + idseparator: '-', + docinfo: 'shared', + sectanchors: '', + sectnums: '', + 'source-highlighter': 'rouge', + 'rouge-style': 'github', // molokai, monokai, github, gruvbox + 'linkcss': 'true', + toc: 'left', + toclevels: 4, + sectnums: false, + sectnumlevels: 4, + sectanchors: true, + fontsDir: 'cfg/fonts', + graphvizdot: '/usr/local/bin/dot', + stylesdir: 'css', + homepage: 'https://www.diguage.com', + plantumlconfig: "${getProjectOperations().projectDir}/cfg/plantuml.cfg", + "scripts": "cjk", + + 'spring-version' : project.version, + 'source_attr' : sourceAttr, + 'java_src_attr' : "source%nowrap,java,${sourceAttr}", + 'xml_src_attr' : "source%nowrap,xml,${sourceAttr}", + 'image_attr' : 'align="center",width=98%', + 'diagram_attr' : 'format=svg,align="center",width=98%', + // 'rootDir' : project.rootDir, // = /path/to/spring-framework + // 'projectDir': project.projectDir, // = /path/to/spring-framework/truman + // 'buildDir' : project.buildDir, // = /path/to/spring-framework/truman/build + 'truman_src_dir' : "${getProjectOperations().projectDir}/src/main/java/com/diguage/truman", + 'truman_resource_dir': "${getProjectOperations().projectDir}/src/main/resources/com/diguage/truman", + 'aop_src_dir' : "${project.rootDir}/spring-aop/src/main/java/org/springframework/aop", + 'beans_src_dir' : "${project.rootDir}/spring-beans/src/main/java/org/springframework/beans", + 'context_src_dir' : "${project.rootDir}/spring-context/src/main/java/org/springframework", + 'core_src_dir' : "${project.rootDir}/spring-core/src/main/java/org/springframework", + 'jdbc_src_dir' : "${project.rootDir}/spring-jdbc/src/main/java/org/springframework/jdbc", + 'tx_src_dir' : "${project.rootDir}/spring-tx/src/main/java/org/springframework" + ]) + // attribute 'docinfo1', '' + modules { + pdf { + version '2.3.18' + } + epub { + version '2.1.3' + } + diagram { + version '2.3.1' + } + } +} + + +repositories { + mavenLocal() + mavenCentral() +// use the ruby.gems(), and throw an error +// ruby.gems() +// ruby { +// gems() +// } +}