+
+
\ No newline at end of file
diff --git a/springboot-freemarker-sample/src/main/resources/templates/demo1.ftl b/springboot-freemarker-sample/src/main/resources/templates/demo1.ftl
new file mode 100644
index 0000000..1f919fc
--- /dev/null
+++ b/springboot-freemarker-sample/src/main/resources/templates/demo1.ftl
@@ -0,0 +1,21 @@
+
+
+
+
+ demo列表
+
+
+
+
+ 编号
+ 名称
+
+ <#list students as student>
+
+ ${student.idNo}
+ ${student.name}
+
+ #list>
+
+
+
\ No newline at end of file
diff --git a/springboot-freemarker-sample/src/test/java/com/ipman/freemarker/sample/SpringbootFreemarkerSampleApplicationTests.java b/springboot-freemarker-sample/src/test/java/com/ipman/freemarker/sample/SpringbootFreemarkerSampleApplicationTests.java
new file mode 100644
index 0000000..262dd9e
--- /dev/null
+++ b/springboot-freemarker-sample/src/test/java/com/ipman/freemarker/sample/SpringbootFreemarkerSampleApplicationTests.java
@@ -0,0 +1,13 @@
+package com.ipman.freemarker.sample;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class SpringbootFreemarkerSampleApplicationTests {
+
+ @Test
+ void contextLoads() {
+ }
+
+}
diff --git a/springboot-lettuce-sample/src/main/java/com/ipman/springboot/lettuce/sample/api/LettuceController.java b/springboot-lettuce-sample/src/main/java/com/ipman/springboot/lettuce/sample/api/LettuceController.java
index 88d2724..437e10b 100644
--- a/springboot-lettuce-sample/src/main/java/com/ipman/springboot/lettuce/sample/api/LettuceController.java
+++ b/springboot-lettuce-sample/src/main/java/com/ipman/springboot/lettuce/sample/api/LettuceController.java
@@ -2,6 +2,7 @@
import com.ipman.springboot.lettuce.sample.utils.LettuceUtil;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
diff --git a/springboot-netty-sample/.gitignore b/springboot-netty-sample/.gitignore
new file mode 100644
index 0000000..549e00a
--- /dev/null
+++ b/springboot-netty-sample/.gitignore
@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/springboot-netty-sample/.mvn/wrapper/maven-wrapper.jar b/springboot-netty-sample/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 0000000..c1dd12f
Binary files /dev/null and b/springboot-netty-sample/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/springboot-netty-sample/.mvn/wrapper/maven-wrapper.properties b/springboot-netty-sample/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..d8bc321
--- /dev/null
+++ b/springboot-netty-sample/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,2 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.3/apache-maven-3.8.3-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar
diff --git a/springboot-netty-sample/README.md b/springboot-netty-sample/README.md
new file mode 100644
index 0000000..d884eb3
--- /dev/null
+++ b/springboot-netty-sample/README.md
@@ -0,0 +1,314 @@
+### Netty 笔记
+
+#### 1.Netty对三种I/O模式的支持
+
+image-20220124110420220
+
+
+
+##### Netty并不是只支持过NIO,但是不建议(depercate)阻塞I/O(BIO/OIO)
+
+- 连接数高的情况下:阻塞 -> 消耗源、效率低
+
+##### Netty也不建议(depercate)使用AIO
+
+- AIO在Windows 下比较成熟,但是很少用来做服务器
+- Linux 常用来做服务器,但是AIO实现不够成熟
+- Linux 的AIO相比NIO的性能没有显著的提升,反而会为开发者带来高额的维护成本
+
+##### Netty和JDK NIO在Linux下,都是基于epoll实现,为什么要用Netty?
+
+- Netty 暴露了更多的可用参数,如:
+ - JDK 的 NIO 默认实现是水平触发
+ - Netty 是边缘触发(默认)和水平触发可以切换
+- Netty 实现的垃圾回收更少、性能更好
+
+
+
+#### 2.Netty NIO 中的Reactor 开发模式
+
+Netty 三种开发模式版本
+
+BIO 下是 Thread-Per-Connection
+
+image-20220124112504631
+
+
+
+***Thread-Per-Connection:对应每个连接都有1个线程处理,1个线程同时处理:读取、解码、计算、编码、发送***
+
+
+
+NIO 下是 Reactor
+
+image-20220124112753682
+
+
+
+***Reactor 多线程模式,由多个线程负责:读取、发送,由线程池负责处理:解码、计算、编码***
+
+***Reactor 主从多线程模式,由单独mainReactor 单线程负责接收请求,subReactor和 Reactor 多线程模式一致***
+
+
+
+AIO 下是 Proactor
+
+Reactor 是一种开发模式,模式的核心流程:
+
+> 注册感兴趣的事件 -> 扫描是否有感兴趣的事件发生 -> 事件发生后做出相应的处理
+
+
+
+##### Netty下使用 NIO 示范例
+
+- Reactor 单线程模式
+
+```java
+//线程数1
+EventLoopGroup eventGroup = new NioEventLoopGroup(1);
+
+ServerBootStrap serverBootStrap = new ServerBootStrap();
+serverBootStrap.group(eventGroup);
+```
+
+- Reactor 多线程模式
+
+```java
+//多线程,不传具体的线程数时,Netty会根据CPU核心数分配
+EventLoopGroup eventGroup = new NioEventLoopGroup();
+
+ServerBootStrap serverBootStrap = new ServerBootStrap();
+serverBootStrap.group(serverBootStrap);
+```
+
+- Reactor 主从多线程模式
+
+```java
+// 主线程负责接收请求 acceptor,是单线程
+EventLoopGroup bossGroup = new NioEventLoopGroup();
+// 从线程负责:读取、解码、计算、编码、发送,是多线程
+EventLoopGroup workerGroup = new NioEventLoopGroup();
+
+SeverBootStrap serverBootStrap = new ServerBootStrap();
+serverBootStrap.group(bossGroup, workerGroup);
+```
+
+
+
+##### Netty 支持主从 Reactor 源码分析
+
+1.初始化 Main EventLoopGroup
+
+```java
+public abstract class AbstractBootstrap, C extends Channel> implements Cloneable {
+
+ // main Event Loop Group
+ volatile EventLoopGroup group;
+
+ ....
+
+ // 初始化 mian Event Loop Group 方法
+ public B group(EventLoopGroup group) {
+ ObjectUtil.checkNotNull(group, "group");
+ if (this.group != null) {
+ throw new IllegalStateException("group set already");
+ }
+ this.group = group;
+ return self();
+ }
+
+ ....
+}
+```
+
+2. 初始化 Worker EventLoopGroup
+
+```java
+public class ServerBootstrap extends AbstractBootstrap {
+
+ // woker Events Loop Group
+ private volatile EventLoopGroup childGroup;
+ .....
+
+ public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
+ super.group(parentGroup);
+ ObjectUtil.checkNotNull(childGroup, "childGroup");
+ if (this.childGroup != null) {
+ throw new IllegalStateException("childGroup set already");
+ }
+ // 初始化 worker Event Loop Group 方法
+ this.childGroup = childGroup;
+ return this;
+ }
+
+ ....
+}
+```
+
+3. MainEventLoopGroup 和 WorkerEventLoop 绑定# bind(),并实现新建和初始化 SocketChannel 绑定到 MainEventLoopGroup中
+
+```java
+// 绑定 地址:端口
+public ChannelFuture bind(SocketAddress localAddress) {
+ validate();
+ return doBind(ObjectUtil.checkNotNull(localAddress, "localAddress"));
+}
+
+// 绑定逻辑
+private ChannelFuture doBind(final SocketAddress localAddress) {
+ // 初始化 & 注册 MainEventLoopGroup
+ final ChannelFuture regFuture = initAndRegister();
+ final Channel channel = regFuture.channel();
+ ....
+}
+
+// 初始化 & 注册 MainEventLoopGroup
+final ChannelFuture initAndRegister() {
+ Channel channel = null;
+ try {
+ // 创建新的 ServerSocketChannel
+ channel = channelFactory.newChannel();
+ // 初始化 ServerSocketChannel 中的 Handler
+ init(channel);
+ } catch (Throwable t) {
+ if (channel != null) {
+ // channel can be null if newChannel crashed (eg SocketException("too many open files"))
+ channel.unsafe().closeForcibly();
+ // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
+ return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
+ }
+ // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
+ return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
+ }
+
+ // 将 ServerSocketChannel 注册到 MainEventLoop 中
+ // 因为端口和地址 只有1个,channel只能被注册一次,所以 MainEventLoopGroup 是单线程的
+ ChannelFuture regFuture = config().group().register(channel);
+ if (regFuture.cause() != null) {
+ if (channel.isRegistered()) {
+ channel.close();
+ } else {
+ channel.unsafe().closeForcibly();
+ }
+ }
+ ...
+}
+
+```
+
+4. WorkerEventLoopGroup 和 SocketChannel 绑定关系
+
+```java
+private static class ServerBootstrapAcceptor extends ChannelInboundHandlerAdapter {
+ @Override
+ @SuppressWarnings("unchecked")
+ public void channelRead(ChannelHandlerContext ctx, Object msg) {
+ // 每次读取都是一个 SocketChannel
+ final Channel child = (Channel) msg;
+
+ child.pipeline().addLast(childHandler);
+
+ setChannelOptions(child, childOptions, logger);
+
+ for (Entry, Object> e: childAttrs) {
+ child.attr((AttributeKey) e.getKey()).set(e.getValue());
+ }
+
+ try {
+ // 将 SocketChannel 注册到 workerEventLoopGroup中
+ childGroup.register(child).addListener(new ChannelFutureListener() {
+ @Override
+ public void operationComplete(ChannelFuture future) throws Exception {
+ if (!future.isSuccess()) {
+ forceClose(child, future.cause());
+ }
+ }
+ });
+ } catch (Throwable t) {
+ forceClose(child, t);
+ }
+ }
+}
+```
+
+
+
+
+
+#### 3.Netty 粘包/半包解决方案
+
+关于半包的主要原因:
+
+- 发送写入数据> 套接字缓冲区大小
+- 发送的数据大于协议 MTU(Maximum Transmission Unit,最大传输单元),必须拆包
+
+image-20220124144456949
+
+
+
+
+
+关于粘包的主要原因:
+
+- 发送方每次写入数据> 套接字缓冲区大小
+- 接收方读取套接字缓冲区不够及时
+
+
+
+换个角度看原因:
+
+- 收发时:一个发送可能多次接收,多个发送可能被一次接收
+- 传输时:一个发送可能占用多个传输包,多个发送可能公用一个传输包
+
+
+
+导致粘包/半包的根本原因:
+
+TCP 是流式协议,消息无边界
+
+> 提醒:UDP 像邮寄的包裹,虽然一次运输多个,但每个包裹都是 "界限",一个一个签收,所以无粘包、半包问题
+
+
+
+***解决粘包和半包的手段: 找出消息的边界***
+
+- 方式一:***短连接***(不推荐)
+ - 手段:TCP 连接改成短连接,一个请求一个短连接,建立连接到释放连接之间的信息即为传输信息
+ - 优点:简单
+ - 缺点:效率低下
+
+- 方式二:***固定长度***(不推荐)
+ - 手段:满足固定长度即可
+ - 优点:简单
+ - 缺点:浪费空间
+
+- 方式三:***分隔符***(推荐)
+ - 手段:用确定分隔符切割
+ - 优点:空间不浪费,也比较简单
+ - 缺点:内容本身出现分隔符时需要转义,所以需要扫描内容
+
+- 方式四:***固定长度字段存个内容的长度信息***(推荐+)
+ - 手段:先解析固定长度的字段获取长度,然后读取后续的内容
+ - 优点:精确定位用户数据,内容也不用转义
+ - 缺点:长度理论上是有限制,需要提前预知可能的最大长度从而定义长度占用字节数
+
+- 方式五:***序列化方式***(根据场景衡量)
+ - 手段:每种都不同,例如JSON 可以看{} 是否应己成对
+ - 优缺点:衡量实际场景,很多是对现有协议的支持
+
+
+
+##### Netty 粘包/半包 解决方案
+
+- 固定长度
+ - 解码:FixedLengthFrameDecoder
+- 分隔符
+ - 解码:DelimiterBasedFrameDecoder
+- 固定长度存个内容长度字段
+ - 解码:LengthFieldBasedFrameDecoder
+ - 编码:LengthFieldPerpender
+
+
+
+
+
diff --git a/springboot-netty-sample/mvnw b/springboot-netty-sample/mvnw
new file mode 100755
index 0000000..8a8fb22
--- /dev/null
+++ b/springboot-netty-sample/mvnw
@@ -0,0 +1,316 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /usr/local/etc/mavenrc ] ; then
+ . /usr/local/etc/mavenrc
+ fi
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - 0ドル may be a link to maven's home
+ PRG="0ドル"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*'> /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="`\\unset -f command; \\command -v java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly.">&2
+ echo " We cannot execute $JAVACMD">&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "1ドル" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="1ドル"
+ wdir="1ドル"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "1ドル" ]; then
+ echo "$(tr -s '\n' ' ' < "1ドル")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget> /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ else
+ wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ fi
+ elif command -v curl> /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl -o "$wrapperJarPath" "$jarUrl" -f
+ else
+ curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+ fi
+
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaClass=`cygpath --path --windows "$javaClass"`
+ fi
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ $MAVEN_DEBUG_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" \
+ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/springboot-netty-sample/mvnw.cmd b/springboot-netty-sample/mvnw.cmd
new file mode 100644
index 0000000..1d8ab01
--- /dev/null
+++ b/springboot-netty-sample/mvnw.cmd
@@ -0,0 +1,188 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
+if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment.>&2
+echo Please set the JAVA_HOME variable in your environment to match the>&2
+echo location of your Java installation.>&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory.>&2
+echo JAVA_HOME = "%JAVA_HOME%">&2
+echo Please set the JAVA_HOME variable in your environment to match the>&2
+echo location of your Java installation.>&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% ^
+ %JVM_CONFIG_MAVEN_PROPS% ^
+ %MAVEN_OPTS% ^
+ %MAVEN_DEBUG_OPTS% ^
+ -classpath %WRAPPER_JAR% ^
+ "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
+ %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
+if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%"=="on" pause
+
+if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
+
+cmd /C exit /B %ERROR_CODE%
diff --git a/springboot-netty-sample/pom.xml b/springboot-netty-sample/pom.xml
new file mode 100644
index 0000000..d27b746
--- /dev/null
+++ b/springboot-netty-sample/pom.xml
@@ -0,0 +1,52 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.4.1
+
+
+ com.example
+ springboot-netty-sample
+ 0.0.1-SNAPSHOT
+ springboot-netty-sample
+ Demo project for Spring Boot
+
+
+ 1.8
+ 1.8
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+ io.netty
+ netty-all
+ 4.1.39.Final
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/springboot-netty-sample/src/main/java/com/ipman/netty/springboot/sample/SpringbootNettySampleApplication.java b/springboot-netty-sample/src/main/java/com/ipman/netty/springboot/sample/SpringbootNettySampleApplication.java
new file mode 100644
index 0000000..6b1ac93
--- /dev/null
+++ b/springboot-netty-sample/src/main/java/com/ipman/netty/springboot/sample/SpringbootNettySampleApplication.java
@@ -0,0 +1,13 @@
+package com.ipman.netty.springboot.sample;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class SpringbootNettySampleApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SpringbootNettySampleApplication.class, args);
+ }
+
+}
diff --git a/springboot-netty-sample/src/main/java/com/ipman/netty/springboot/sample/echo/EchoClient.java b/springboot-netty-sample/src/main/java/com/ipman/netty/springboot/sample/echo/EchoClient.java
new file mode 100644
index 0000000..6318b40
--- /dev/null
+++ b/springboot-netty-sample/src/main/java/com/ipman/netty/springboot/sample/echo/EchoClient.java
@@ -0,0 +1,48 @@
+package com.ipman.netty.springboot.sample.echo;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.*;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.logging.LogLevel;
+import io.netty.handler.logging.LoggingHandler;
+
+/**
+ * Created by ipipman on 2022年1月19日.
+ *
+ * @version V1.0
+ * @Package com.ipman.netty.springboot.sample.echo
+ * @Description: (用一句话描述该文件做什么)
+ * @date 2022年1月19日 9:26 下午
+ */
+public class EchoClient {
+
+ public static void main(String[] args) {
+ EventLoopGroup group = new NioEventLoopGroup();
+ EchoClientHandler clientHandler = new EchoClientHandler();
+ try {
+ Bootstrap b = new Bootstrap();
+ b.group(group)
+ .channel(NioSocketChannel.class)
+ .option(ChannelOption.TCP_NODELAY, true)
+ .handler(new ChannelInitializer() {
+ @Override
+ public void initChannel(SocketChannel ch) throws Exception {
+ ChannelPipeline p = ch.pipeline();
+ p.addLast(new LoggingHandler(LogLevel.INFO));
+ p.addLast(clientHandler);
+ }
+ });
+ // 连接server端
+ ChannelFuture cf = b.connect("127.0.0.1", 8090).sync();
+ // 等待连接关闭
+ cf.channel().closeFuture().sync();
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ group.shutdownGracefully();
+ }
+
+ }
+}
diff --git a/springboot-netty-sample/src/main/java/com/ipman/netty/springboot/sample/echo/EchoClientHandler.java b/springboot-netty-sample/src/main/java/com/ipman/netty/springboot/sample/echo/EchoClientHandler.java
new file mode 100644
index 0000000..6289138
--- /dev/null
+++ b/springboot-netty-sample/src/main/java/com/ipman/netty/springboot/sample/echo/EchoClientHandler.java
@@ -0,0 +1,78 @@
+package com.ipman.netty.springboot.sample.echo;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelHandler.Sharable;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Created by ipipman on 2022年1月19日.
+ *
+ * @version V1.0
+ * @Package com.ipman.netty.springboot.sample.echo
+ * @Description: (用一句话描述该文件做什么)
+ * @date 2022年1月19日 9:26 下午
+ */
+@Sharable
+public class EchoClientHandler extends ChannelInboundHandlerAdapter {
+
+ private final ByteBuf firstMsg;
+
+ public EchoClientHandler() {
+ firstMsg = Unpooled.wrappedBuffer("Hello Codeing".getBytes(StandardCharsets.UTF_8));
+ }
+
+ /**
+ * 通道活跃时
+ *
+ * @param ctx
+ * @throws Exception
+ */
+ @Override
+ public void channelActive(ChannelHandlerContext ctx) throws Exception {
+ // 第一次传输
+ ctx.writeAndFlush(firstMsg);
+ }
+
+ /**
+ * 读取数据时
+ *
+ * @param ctx
+ * @param msg
+ * @throws Exception
+ */
+ @Override
+ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+ ctx.write(msg);
+ }
+
+ /**
+ * 读取完毕时
+ *
+ * @param ctx
+ * @throws Exception
+ */
+ @Override
+ public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
+ // 延迟3秒
+ TimeUnit.SECONDS.sleep(3);
+ ctx.flush();
+ }
+
+ /**
+ * 捕获到异常时
+ *
+ * @param ctx
+ * @param cause
+ * @throws Exception
+ */
+ @Override
+ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+ cause.printStackTrace();
+ ctx.close();
+ }
+}
diff --git a/springboot-netty-sample/src/main/java/com/ipman/netty/springboot/sample/echo/EchoServer.java b/springboot-netty-sample/src/main/java/com/ipman/netty/springboot/sample/echo/EchoServer.java
new file mode 100644
index 0000000..bb03580
--- /dev/null
+++ b/springboot-netty-sample/src/main/java/com/ipman/netty/springboot/sample/echo/EchoServer.java
@@ -0,0 +1,52 @@
+package com.ipman.netty.springboot.sample.echo;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.logging.LogLevel;
+import io.netty.handler.logging.LoggingHandler;
+
+/**
+ * Created by ipipman on 2022年1月19日.
+ *
+ * @version V1.0
+ * @Package com.ipman.netty.springboot.sample.http
+ * @Description: (用一句话描述该文件做什么)
+ * @date 2022年1月19日 9:09 下午
+ */
+public class EchoServer {
+
+ public static void main(String[] args) {
+ // 创建Server端
+ EventLoopGroup workGroup = new NioEventLoopGroup();
+ final EchoServerHandler serverHandler = new EchoServerHandler();
+ try {
+ ServerBootstrap b = new ServerBootstrap();
+ b.group(workGroup)
+ .channel(NioServerSocketChannel.class)
+ .handler(new LoggingHandler(LogLevel.INFO))
+ .childHandler(new ChannelInitializer() {
+ @Override
+ public void initChannel(SocketChannel ch) throws Exception {
+ ChannelPipeline p = ch.pipeline();
+ p.addLast(new LoggingHandler(LogLevel.INFO));
+ p.addLast(serverHandler);
+ }
+ });
+ // 绑定端口
+ ChannelFuture f = b.bind(8090).sync();
+ // 等待连接关闭
+ f.channel().closeFuture().sync();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } finally {
+ // 关闭所有线程
+ workGroup.shutdownGracefully();
+ }
+ }
+}
diff --git a/springboot-netty-sample/src/main/java/com/ipman/netty/springboot/sample/echo/EchoServerHandler.java b/springboot-netty-sample/src/main/java/com/ipman/netty/springboot/sample/echo/EchoServerHandler.java
new file mode 100644
index 0000000..c58b990
--- /dev/null
+++ b/springboot-netty-sample/src/main/java/com/ipman/netty/springboot/sample/echo/EchoServerHandler.java
@@ -0,0 +1,54 @@
+package com.ipman.netty.springboot.sample.echo;
+
+import io.netty.channel.ChannelHandler.Sharable;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+
+/**
+ * Created by ipipman on 2022年1月19日.
+ *
+ * @version V1.0
+ * @Package com.ipman.netty.springboot.sample.http
+ * @Description: (用一句话描述该文件做什么)
+ * @date 2022年1月19日 9:11 下午
+ */
+@Sharable
+public class EchoServerHandler extends ChannelInboundHandlerAdapter {
+
+
+ /**
+ * 读取
+ *
+ * @param ctx
+ * @param msg
+ * @throws Exception
+ */
+ @Override
+ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+ ctx.write(msg);
+ }
+
+ /**
+ * 读取完毕时
+ *
+ * @param ctx
+ * @throws Exception
+ */
+ @Override
+ public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
+ ctx.flush();
+ }
+
+ /**
+ * 抓住异常
+ *
+ * @param ctx
+ * @param cause
+ * @throws Exception
+ */
+ @Override
+ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+ cause.printStackTrace();
+ ctx.close();
+ }
+}
diff --git a/springboot-netty-sample/src/main/java/com/ipman/netty/springboot/sample/http/HttpServer.java b/springboot-netty-sample/src/main/java/com/ipman/netty/springboot/sample/http/HttpServer.java
new file mode 100644
index 0000000..dcb2d67
--- /dev/null
+++ b/springboot-netty-sample/src/main/java/com/ipman/netty/springboot/sample/http/HttpServer.java
@@ -0,0 +1,55 @@
+package com.ipman.netty.springboot.sample.http;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.*;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.codec.http.HttpServerCodec;
+import io.netty.handler.codec.http.HttpServerExpectContinueHandler;
+import io.netty.handler.logging.LogLevel;
+import io.netty.handler.logging.LoggingHandler;
+
+/**
+ * Created by ipipman on 2022年1月19日.
+ *
+ * @version V1.0
+ * @Package com.ipman.netty.springboot.sample.http
+ * @Description: (用一句话描述该文件做什么)
+ * @date 2022年1月19日 10:00 下午
+ */
+public class HttpServer {
+
+ public static void main(String[] args) {
+ // 主从模式
+ EventLoopGroup bossGroup = new NioEventLoopGroup(1);
+ EventLoopGroup workerGroup = new NioEventLoopGroup();
+
+ try {
+ ServerBootstrap b = new ServerBootstrap();
+ b.group(bossGroup, workerGroup)
+ .channel(NioServerSocketChannel.class)
+ .handler(new LoggingHandler(LogLevel.INFO))
+ .childHandler(new ChannelInitializer() {
+ @Override
+ protected void initChannel(SocketChannel ch) throws Exception {
+ // HTTP 模式
+ ChannelPipeline p = ch.pipeline();
+ p.addLast(new HttpServerCodec());
+ p.addLast(new HttpServerExpectContinueHandler());
+ p.addLast(new HttpServerHandler());
+ }
+ });
+ ChannelFuture ch = b.bind(8099).sync();
+
+ System.out.println("Open Http Server : http://127.0.0.1:8099");
+ ch.channel().closeFuture().sync();
+
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ } finally {
+ workerGroup.shutdownGracefully();
+ bossGroup.shutdownGracefully();
+ }
+ }
+}
diff --git a/springboot-netty-sample/src/main/java/com/ipman/netty/springboot/sample/http/HttpServerHandler.java b/springboot-netty-sample/src/main/java/com/ipman/netty/springboot/sample/http/HttpServerHandler.java
new file mode 100644
index 0000000..d0e4bf8
--- /dev/null
+++ b/springboot-netty-sample/src/main/java/com/ipman/netty/springboot/sample/http/HttpServerHandler.java
@@ -0,0 +1,60 @@
+package com.ipman.netty.springboot.sample.http;
+
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelHandler.Sharable;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.SimpleChannelInboundHandler;
+import io.netty.handler.codec.http.*;
+
+import java.nio.charset.StandardCharsets;
+
+import static io.netty.handler.codec.http.HttpResponseStatus.OK;
+import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
+import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH;
+import static io.netty.handler.codec.http.HttpHeaderValues.TEXT_PLAIN;
+
+
+/**
+ * Created by ipipman on 2022年1月19日.
+ *
+ * @version V1.0
+ * @Package com.ipman.netty.springboot.sample.http
+ * @Description: (用一句话描述该文件做什么)
+ * @date 2022年1月19日 9:50 下午
+ */
+@Sharable
+public class HttpServerHandler extends SimpleChannelInboundHandler {
+
+ private static final byte[] CONTEXT = "hello coding".getBytes(StandardCharsets.UTF_8);
+
+ /**
+ * 当读取完毕时
+ *
+ * @param ctx
+ * @throws Exception
+ */
+ @Override
+ public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
+ ctx.flush();
+ }
+
+ @Override
+ protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
+ if (msg instanceof HttpRequest) {
+ HttpRequest req = (HttpRequest) msg;
+ FullHttpResponse response = new DefaultFullHttpResponse(req.protocolVersion(), OK, Unpooled.wrappedBuffer(CONTEXT));
+ response.headers()
+ .set(CONTENT_TYPE, TEXT_PLAIN)
+ .set(CONTENT_LENGTH, response.content().readableBytes());
+
+ ChannelFuture f = ctx.write(response);
+ }
+ }
+
+ @Override
+ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+ cause.printStackTrace();
+ ctx.close();
+ }
+}
diff --git a/springboot-netty-sample/src/main/resources/application.properties b/springboot-netty-sample/src/main/resources/application.properties
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/springboot-netty-sample/src/main/resources/application.properties
@@ -0,0 +1 @@
+
diff --git a/springboot-netty-sample/src/test/java/com/ipman/netty/springboot/sample/SpringbootNettySampleApplicationTests.java b/springboot-netty-sample/src/test/java/com/ipman/netty/springboot/sample/SpringbootNettySampleApplicationTests.java
new file mode 100644
index 0000000..c9f8c24
--- /dev/null
+++ b/springboot-netty-sample/src/test/java/com/ipman/netty/springboot/sample/SpringbootNettySampleApplicationTests.java
@@ -0,0 +1,13 @@
+package com.ipman.netty.springboot.sample;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class SpringbootNettySampleApplicationTests {
+
+ @Test
+ void contextLoads() {
+ }
+
+}
diff --git "a/springboot-source-code-analysis/(1)346円241円206円346円236円266円346円225円264円344円275円223円345円220円257円345円212円250円346円265円201円347円250円213円.md" "b/springboot-source-code-analysis/(1)346円241円206円346円236円266円346円225円264円344円275円223円345円220円257円345円212円250円346円265円201円347円250円213円.md"
new file mode 100644
index 0000000..d4c7c72
--- /dev/null
+++ "b/springboot-source-code-analysis/(1)346円241円206円346円236円266円346円225円264円344円275円223円345円220円257円345円212円250円346円265円201円347円250円213円.md"
@@ -0,0 +1,68 @@
+## SpringBoot 2.1.x 框架整体启动流程
+
+### 1、框架初始化步骤
+
+#### 框架初始化步骤-脑图
+
+image-20210605194919123
+
+#### 框架初始化步骤-源代码
+
+> SpringBoog启动类:org.springframework.boot.SpringApplication
+
+```java
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public SpringApplication(ResourceLoader resourceLoader, Class>... primarySources) {
+ // 配置资源加载器
+ this.resourceLoader = resourceLoader;
+ Assert.notNull(primarySources, "PrimarySources must not be null");
+
+ // 配置primarySources
+ this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
+
+ // 应用环境检测
+ this.webApplicationType = WebApplicationType.deduceFromClasspath();
+
+ // 配置系统初始化器
+ setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
+
+ // 配置应用监听器
+ setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
+
+ // 配置main方法所在的类
+ this.mainApplicationClass = deduceMainApplicationClass();
+ }
+```
+
+
+
+### 2、启动框架
+
+#### 启动框架-流程
+
+image-20210605200051981
+
+
+
+### 3、框架自动化装配步骤
+
+#### 自动化装配-脑图
+
+image-20210605200241401
+
+
+
+
+
+### 4、SpringBoot上下文
+
+#### 准备上下文流程
+
+image-20210605200740395
+
+
+
+#### 刷新上下文流程
+
+image-20210605201154951
+
diff --git "a/springboot-source-code-analysis/(10)XML346円226円271円345円274円217円351円205円215円347円275円256円Bean345円256円236円346円210円230円.md" "b/springboot-source-code-analysis/(10)XML346円226円271円345円274円217円351円205円215円347円275円256円Bean345円256円236円346円210円230円.md"
new file mode 100644
index 0000000..e9a14bd
--- /dev/null
+++ "b/springboot-source-code-analysis/(10)XML346円226円271円345円274円217円351円205円215円347円275円256円Bean345円256円236円346円210円230円.md"
@@ -0,0 +1,262 @@
+### XML方式配置Bean实战
+
+
+
+#### 配置方式
+
+##### 无参构造
+
+> 定义一个普通对象
+
+```java
+public class Student {
+
+ private String name;
+ private Integer age;
+ private List classList;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Integer getAge() {
+ return age;
+ }
+
+ public void setAge(Integer age) {
+ this.age = age;
+ }
+
+ public List getClassList() {
+ return classList;
+ }
+
+ public void setClassList(List classList) {
+ this.classList = classList;
+ }
+
+ @Override
+ public String toString() {
+ return "Student{" +
+ "name='" + name + '\'' +
+ ", age=" + age +
+ ", classList=" + String.join(",", classList) +
+ '}';
+ }
+}
+```
+
+> 无参构造器xml定义
+
+```java
+
+
+
+
+
+ math
+ english
+
+
+
+
+```
+
+
+
+##### 有参构造
+
+>定义个有构造器的普通类
+
+```java
+public class Student {
+
+ private String name;
+ private Integer age;
+ private List classList;
+
+ /**
+ * 使用构造器注入
+ *
+ * @param name
+ * @param age
+ */
+ public Student(String name, Integer age) {
+ this.name = name;
+ this.age = age;
+ }
+
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Integer getAge() {
+ return age;
+ }
+
+ public void setAge(Integer age) {
+ this.age = age;
+ }
+
+ public List getClassList() {
+ return classList;
+ }
+
+ public void setClassList(List classList) {
+ this.classList = classList;
+ }
+
+ @Override
+ public String toString() {
+ return "Student{" +
+ "name='" + name + '\'' +
+ ", age=" + age +
+ ", classList=" + String.join(",", classList) +
+ '}';
+ }
+}
+```
+
+> 定义有参数构造器xml配置
+
+```java
+
+
+
+
+
+
+
+
+ math
+ english
+
+
+
+```
+
+
+
+##### 静态工厂方法 和 实例工厂方法
+
+> 定义一个普通的抽象类 和 继承类
+
+```java
+public abstract class Animal {
+
+ abstract String getName();
+}
+```
+
+```java
+public class Cat extends Animal{
+ @Override
+ String getName() {
+ return "Cat";
+ }
+}
+```
+
+```java
+public class Dog extends Animal{
+ @Override
+ String getName() {
+ return "Dog";
+ }
+}
+```
+
+
+
+> 定义一个抽象工厂类
+
+```java
+public class AnimalFactory {
+
+ // 静态工厂
+ public static Animal getAnimal(String type) {
+ if ("Dog".equals(type)) {
+ return new Dog();
+ } else {
+ return new Cat();
+ }
+ }
+
+ // 实例工厂
+ public Animal getAnimal1(String type) {
+ if ("Dog".equals(type)) {
+ return new Dog();
+ } else {
+ return new Cat();
+ }
+ }
+}
+```
+
+
+
+> 定义一个服务类
+
+```
+public class HelloService {
+
+ private Student student;
+
+ private Animal animal;
+
+ public Animal getAnimal() {
+ return animal;
+ }
+
+ public void setAnimal(Animal animal) {
+ this.animal = animal;
+ }
+
+ public Student getStudent() {
+ return student;
+ }
+
+ public void setStudent(Student student) {
+ this.student = student;
+ }
+
+ public String hello() {
+ //return student.toString();
+ return animal.getName();
+ }
+
+}
+```
+
+
+
+> 静态工厂方法 和 实例工厂方法 XML 配置
+
+```java
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
diff --git "a/springboot-source-code-analysis/(11)351円200円232円350円277円207円Java351円205円215円347円275円256円Bean347円232円204円345円207円240円347円247円215円346円226円271円345円274円217円.md" "b/springboot-source-code-analysis/(11)351円200円232円350円277円207円Java351円205円215円347円275円256円Bean347円232円204円345円207円240円347円247円215円346円226円271円345円274円217円.md"
new file mode 100644
index 0000000..98ea91c
--- /dev/null
+++ "b/springboot-source-code-analysis/(11)351円200円232円350円277円207円Java351円205円215円347円275円256円Bean347円232円204円345円207円240円347円247円215円346円226円271円345円274円217円.md"
@@ -0,0 +1,98 @@
+### 通过Java配置Bean的几种方式
+
+#### 1.通过@Configuration注解注入Bean的方式
+
+```java
+@Configuration
+public class MyBeanConfiguration {
+
+ /**
+ * 通过 @Configuration注解 注入一个Bean
+ */
+ @Bean("dog")
+ public Animal getDog() {
+ return new Dog();
+ }
+
+}
+```
+
+
+
+#### 2.通过实现FactoryBean>接口的方式注入Bean
+
+```java
+@Component
+public class MyFactoryBean implements FactoryBean {
+
+ /**
+ * 返回要注入的类
+ */
+ @Override
+ public Animal getObject() throws Exception {
+ return new Cat();
+ }
+
+ /**
+ * 获取要注入类的类型
+ */
+ @Override
+ public Class> getObjectType() {
+ return Animal.class;
+ }
+
+ @Override
+ public boolean isSingleton() {
+ return FactoryBean.super.isSingleton();
+ }
+
+}
+```
+
+
+
+#### 3.通过实现BeanDefinitionRegistryPostProcessor接口的方式注入Bean
+
+```java
+@Component
+public class MyBeanDefinitionRegistry implements BeanDefinitionRegistryPostProcessor {
+
+ /**
+ * 通过BeanDefinitionRegistryPostProcessor
+ */
+ @Override
+ public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
+ RootBeanDefinition rootBeanDefinition = new RootBeanDefinition();
+ rootBeanDefinition.setBeanClass(Monkey.class);
+ beanDefinitionRegistry.registerBeanDefinition("monkey", rootBeanDefinition);
+ }
+
+ @Override
+ public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
+
+ }
+}
+```
+
+
+
+#### 4.通过实现ImportBeanDefinitionRegistrar接口的方式注入Bean
+
+```java
+public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
+
+ /**
+ * 通过ImportBeanDefinitionRegistrar注入Bean
+ */
+ @Override
+ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
+ RootBeanDefinition rootBeanDefinition = new RootBeanDefinition();
+ rootBeanDefinition.setBeanClass(Bird.class);
+ registry.registerBeanDefinition("bird", rootBeanDefinition);
+ }
+}
+
+```
+
+> 使用时:用 @Import(MyImportBeanDefinitionRegistrar.class) 进行注入
+
diff --git "a/springboot-source-code-analysis/(12)346円241円206円346円236円266円Refresh346円226円271円346円263円225円350円247円243円346円236円220円344円270円200円.md" "b/springboot-source-code-analysis/(12)346円241円206円346円236円266円Refresh346円226円271円346円263円225円350円247円243円346円236円220円344円270円200円.md"
new file mode 100644
index 0000000..507ca80
--- /dev/null
+++ "b/springboot-source-code-analysis/(12)346円241円206円346円236円266円Refresh346円226円271円346円263円225円350円247円243円346円236円220円344円270円200円.md"
@@ -0,0 +1,276 @@
+
+
+### 框架Refresh方法解析一
+
+
+
+#### 1.Refresh方法简介
+
+- Bean配置读取加载入口
+- Spring框架的启动流程
+
+
+
+#### 2.Refresh方法的执行步骤
+
+image-20211017144834689
+
+```java
+ @Override
+ public void refresh() throws BeansException, IllegalStateException {
+ synchronized (this.startupShutdownMonitor) {
+ // Prepare this context for refreshing.
+ // 准备此上下文
+ prepareRefresh();
+
+ // Tell the subclass to refresh the internal bean factory.
+ // 获取BeanFacotry -> DefaultListablesBeanFacotory
+ ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
+
+ // Prepare the bean factory for use in this context.
+ // 准备此类上下文中使用的Bean工厂
+ prepareBeanFactory(beanFactory);
+
+ try {
+ // Allows post-processing of the bean factory in context subclasses.
+ // 允许在上下文子类中对Bean工厂进行后处理
+ postProcessBeanFactory(beanFactory);
+
+ // Invoke factory processors registered as beans in the context.
+ // 调用在上下文中注册为Bean的工厂处理器
+ invokeBeanFactoryPostProcessors(beanFactory);
+
+ // Register bean processors that intercept bean creation.
+ // 注册拦截Bean创建的Bean处理器
+ registerBeanPostProcessors(beanFactory);
+
+ // Initialize message source for this context.
+ // 为此上下文初始化消息源
+ initMessageSource();
+
+ // Initialize event multicaster for this context.
+ // 为此上下文初始化事件广播器
+ initApplicationEventMulticaster();
+
+ // Initialize other special beans in specific context subclasses.
+ // 初始化特定上下文子类中的其他特殊Bean
+ onRefresh();
+
+ // Check for listener beans and register them.
+ // 检查监听器,并注册
+ registerListeners();
+
+ // Instantiate all remaining (non-lazy-init) singletons.
+ // 实例化所有剩余的(非延迟初始化)单例。
+ finishBeanFactoryInitialization(beanFactory);
+
+ // Last step: publish corresponding event.
+ // 最后一步:发布相应的事件
+ finishRefresh();
+ }
+
+ catch (BeansException ex) {
+ if (logger.isWarnEnabled()) {
+ logger.warn("Exception encountered during context initialization - " +
+ "cancelling refresh attempt: " + ex);
+ }
+
+ // Destroy already created singletons to avoid dangling resources.
+ // 销毁已经创建的单例以避免浪费资源
+ destroyBeans();
+
+ // Reset 'active' flag.
+ // 重置active标志
+ cancelRefresh(ex);
+
+ // Propagate exception to caller.
+ throw ex;
+ }
+
+ finally {
+ // Reset common introspection caches in Spring's core, since we
+ // might not ever need metadata for singleton beans anymore...
+ // 重置 Spring 核心中的常见内省缓存,因为我们可能不再需要单例 bean 的元数据...
+ resetCommonCaches();
+ }
+ }
+ }
+```
+
+
+
+#### 3.准备上下文:prepareRefersh#方法
+
+- prepareRefersh#方法简介
+ - 清除本地元数据缓存
+ - 准备框架上下文
+ - 设置框架启动时间
+ - 设置框架 active 启动状态为true
+ - 如果是Debug模式打印Debug日志
+ - 初始化环境配置
+ - 获取当前环境配置
+ - 如果是Web环境,配置ServletContext 和 ServletConfig
+ - 检查环境配置中的必备属性是否存在
+ - 如果没有早期的事件监听器,就注册应用监听器:applicationListeners
+
+- prepareRefresh#方法
+
+```java
+ @Override
+ protected void prepareRefresh() {
+ // 清除元数据缓存
+ this.scanner.clearCache();
+ // 准备上下文
+ super.prepareRefresh();
+ }
+```
+
+- clearCache#方法
+
+```java
+ /**
+ * Clear the local metadata cache, if any, removing all cached class metadata.
+ * 清除本地元数据缓存(如果有),删除所有缓存的类元数据。
+ */
+ public void clearCache() {
+ if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
+ // Clear cache in externally provided MetadataReaderFactory; this is a no-op
+ // for a shared cache since it'll be cleared by the ApplicationContext.
+ // 清除外部提供的 MetadataReaderFactory 中的缓存;这是一个无操作用于共享缓存,因为它将被 ApplicationContext 清除。
+ ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
+ }
+ }
+```
+
+- super.prepareRefresh方法
+
+```java
+ /**
+ * Prepare this context for refreshing, setting its startup date and
+ * active flag as well as performing any initialization of property sources.
+ * 准备此上下文以进行刷新、设置其启动日期和* active 标志以及执行属性源的任何初始化
+ */
+ protected void prepareRefresh() {
+ // Switch to active.
+ // 设置启动时间
+ this.startupDate = System.currentTimeMillis();
+ this.closed.set(false);
+ // 设置 active 标志状态为 true
+ this.active.set(true);
+
+ // 如果当前日志状态是Debug模式的话,会打印一段话
+ if (logger.isDebugEnabled()) {
+ if (logger.isTraceEnabled()) {
+ logger.trace("Refreshing " + this);
+ }
+ else {
+ logger.debug("Refreshing " + getDisplayName());
+ }
+ }
+
+ // Initialize any placeholder property sources in the context environment.
+ // 初始化环境配置
+ initPropertySources();
+
+ // Validate that all properties marked as required are resolvable:
+ // see ConfigurablePropertyResolver#setRequiredProperties
+ // 检查环境属性中必备的环境数据,如果没有就 throw MissingRequiredPropertiesException 异常
+ getEnvironment().validateRequiredProperties();
+
+ // Store pre-refresh ApplicationListeners...
+ // 将系统的事件监听器进行注册
+ if (this.earlyApplicationListeners == null) {
+ this.earlyApplicationListeners = new LinkedHashSet(this.applicationListeners);
+ }
+ else {
+ // Reset local application listeners to pre-refresh state.
+ this.applicationListeners.clear();
+ this.applicationListeners.addAll(this.earlyApplicationListeners);
+ }
+
+ // Allow for the collection of early ApplicationEvents,
+ // to be published once the multicaster is available...
+ this.earlyApplicationEvents = new LinkedHashSet();
+ }
+
+```
+
+- initPropertySources#方法
+
+```java
+ protected void initPropertySources() {
+ // 获取环境配置上下文
+ ConfigurableEnvironment env = this.getEnvironment();
+ // 如果当前环境是Web环境,将servletContext 和 ServletConfig 加载到环境配置上下文中
+ if (env instanceof ConfigurableWebEnvironment) {
+ ((ConfigurableWebEnvironment)env).initPropertySources(this.servletContext, (ServletConfig)null);
+ }
+ }
+```
+
+
+
+
+
+#### 获取新的Bean工厂:obtainFreshBeanFacotry#方法
+
+- obtainFreshBeanFacotry#方法简介
+ - 设置当前上下文刷新状态为true
+ - 这是BeanFactory序列化ID(默认是application)
+ - 返回BeanFacotry
+
+- obtainFreshBeanFactory#方法
+
+```java
+ /**
+ * Tell the subclass to refresh the internal bean factory.
+ * 告诉子类刷新内部 bean 工厂
+ * @return the fresh BeanFactory instance
+ * @see #refreshBeanFactory()
+ * @see #getBeanFactory()
+ */
+ protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
+ // 设置上下文刷新状态和Beanfacotory的序列化ID
+ refreshBeanFactory();
+ // 获取BeanFactory(默认是DefaultListableBeanFacotry)
+ return getBeanFactory();
+ }
+
+```
+
+- refreshBeanFacoty#方法
+
+```java
+ /**
+ * Do nothing: We hold a single internal BeanFactory and rely on callers
+ * to register beans through our public methods (or the BeanFactory's).
+ * @see #registerBeanDefinition
+ */
+ @Override
+ protected final void refreshBeanFactory() throws IllegalStateException {
+ // 设置当前上下文刷新状态:true
+ if (!this.refreshed.compareAndSet(false, true)) {
+ throw new IllegalStateException(
+ "GenericApplicationContext does not support multiple refresh attempts: just call 'refresh' once");
+ }
+ // 设置BeanFacotry序列化ID:默认是application
+ this.beanFactory.setSerializationId(getId());
+ }
+```
+
+- getBeanFacotory#方法
+
+```java
+/**
+ * Return the single internal BeanFactory held by this context
+ * (as ConfigurableListableBeanFactory).
+ */
+ @Override
+ public final ConfigurableListableBeanFactory getBeanFactory() {
+ // 返回默认的 DefaultListableBeanFacotry
+ return this.beanFactory;
+ }
+```
+
+
+
diff --git "a/springboot-source-code-analysis/(13)346円241円206円346円236円266円Refresh346円226円271円346円263円225円350円247円243円346円236円220円344円272円214円.md" "b/springboot-source-code-analysis/(13)346円241円206円346円236円266円Refresh346円226円271円346円263円225円350円247円243円346円236円220円344円272円214円.md"
new file mode 100644
index 0000000..c3b2180
--- /dev/null
+++ "b/springboot-source-code-analysis/(13)346円241円206円346円236円266円Refresh346円226円271円346円263円225円350円247円243円346円236円220円344円272円214円.md"
@@ -0,0 +1,390 @@
+### 框架Refresh方法解析二
+
+
+
+#### 1.配置工厂标准上下文特征:prepareBeanFacotry()
+
+- 作用
+ - 设置beanFacotry一些属性
+ - 添加后置处理器
+ - 设置忽略的自动装配接口
+ - 注册一些组件
+
+```java
+ /**
+ * Configure the factory's standard context characteristics,
+ * such as the context's ClassLoader and post-processors.
+ * @param beanFactory the BeanFactory to configure
+ * 配置工厂的标准上下文特征,比如上下文的ClassLoader和post-processors
+ */
+ protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
+ // Tell the internal bean factory to use the context's class loader etc.
+ // 告知内部的Bean工厂,使用上下文的类加载器
+ beanFactory.setBeanClassLoader(getClassLoader());
+
+ // 设置Bean表达式解析器
+ // 默认是spring el 表达式
+ beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
+
+ // 注册上下文enviroment配置的引用
+ // 用于做属性转换
+ beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));
+
+ // Configure the bean factory with context callbacks.
+ // 使用上下文回调配置 Bean 工厂
+ // 在工厂的 beanPostProcessor 属性中添加Bean的后置处理器,beanPostProcessor是一个ArrayList
+ beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
+ // 在工厂的忽略依赖接口ignoreDependencyInterface列表中添加Aware系列的接口
+ // 以下Aware接口由 ApplicationContextAwareProcessor 后置处理器处理
+ beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
+ beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
+ beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
+ beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
+ beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
+ beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);
+
+ // BeanFactory interface not registered as resolvable type in a plain factory.
+ // MessageSource registered (and found for autowiring) as a bean.
+ // 在普通的工厂中,BeanFactory接口并没有按照resolvable类型进行注册
+ // MessageSource被注册成一个Bean(并被自动注入)
+
+ //BeanFactory.class为key,beanFactory为value放入到了beanFactory的resolvableDependencies属性中
+ //resolvableDependencies是一个ConcurrentHashMap,映射依赖类型和对应的被注入的value
+ //这样的话BeanFactory/ApplicationContext虽然没有以bean的方式被定义在工厂中,
+ //但是也能够支持自动注入,因为他处于resolvableDependencies属性中
+
+ // 向BeanFacotry添加一些依赖,需要解析依赖的时候指向自身
+ beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
+ // 再将上下文的一些接口与上下文本身做映射,一一放入到resolvableDependencies中
+ beanFactory.registerResolvableDependency(ResourceLoader.class, this);
+ beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
+ beanFactory.registerResolvableDependency(ApplicationContext.class, this);
+
+ // Register early post-processor for detecting inner beans as ApplicationListeners.
+ // 将用于检测内部 bean 的早期后处理器注册为 ApplicationListeners
+ beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));
+
+ // Detect a LoadTimeWeaver and prepare for weaving, if found.
+ // 检测LoadTimeWeaver,如果有就准备织入
+ if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
+ // 如果有LoadTimeWeaver,加入bean后处理器
+ beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
+ // Set a temporary ClassLoader for type matching.
+ // 为匹配类型设置一个临时的ClassLoader
+ beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
+ }
+
+ // Register default environment beans.
+ // 注册默认的environment beans
+ if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {
+ //虽然XmlWebApplicationContext中持有默认实现的StandardServletEnvironment
+ //但是没有注册到beanFactory中,通过getEnvironment方法拿到持有的引用
+ //2.注册environment单例
+ beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
+ }
+ if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {
+ //注册systemProperties单例
+ beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());
+ }
+ if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {
+ ///注册systemEnvironment单例
+ beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());
+ }
+ }
+```
+
+
+
+#### 2.重写BeanFactory,在BeanFactory创建后进一步设置:postProcessBeanFactory()
+
+- 作用是
+ - 留给开发者的扩展点,通过子类重写,在BeanFactory完成创建后做进一步设置
+ - 如添加WebApplicationContextServletContextAwareProcessor后置处理器,给Web环境下的Servlet作用域进行设置:Request、Session
+
+- postProcessBeanFacotory()方法
+
+```java
+ protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
+ // 调用父类的GenericWebApplicationContext#postProcessBeanFacotory方法
+ super.postProcessBeanFactory(beanFactory);
+ if (!ObjectUtils.isEmpty(this.basePackages)) {
+ this.scanner.scan(this.basePackages);
+ }
+
+ if (!this.annotatedClasses.isEmpty()) {
+ this.reader.register(ClassUtils.toClassArray(this.annotatedClasses));
+ }
+
+ }
+```
+
+- 在Web环境下注册WebApplicationContextServletContextAwareProcessor后置处理器
+
+```java
+ /**
+ * Register ServletContextAwareProcessor.
+ * @see ServletContextAwareProcessor
+ */
+ @Override
+ protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
+ // 添加WebApplicationContextServletContextAwareProcessor后置处理器
+ beanFactory.addBeanPostProcessor(new WebApplicationContextServletContextAwareProcessor(this));
+ // 忽略ServletContextAware
+ beanFactory.ignoreDependencyInterface(ServletContextAware.class);
+ // 定义Web环境下的Servlet作用域,比如:Request、Session
+ registerWebApplicationScopes();
+ }
+
+```
+
+
+
+
+
+#### 3.调用所有注册的BeanFactoryPostProcessor的Bean
+
+- 作用
+
+ - 框架自身的BeanFactoryPostProcessor或BeanDefinitionRegistoryPostProcessor初始化Bean的定义或属性
+
+ - 调用BeanDefinitionRegistryPostProcessor实现向容器内添加Bean的定义
+
+ - ```java
+ // 调用时机:在BeanFactory标准初始化之后调用,这时所有的Bean定义已经保存加载到BeanFactory,但是Bean的实例还未创建
+ // 作用:来定制和修改BeanFactory内容,如添加一个Bean
+ @Component
+ public class MyBeanDefinitionRegistry implements BeanDefinitionRegistryPostProcessor {
+
+ /**
+ * 通过BeanDefinitionRegistryPostProcessor
+ */
+ @Override
+ public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
+ RootBeanDefinition rootBeanDefinition = new RootBeanDefinition();
+ rootBeanDefinition.setBeanClass(Monkey.class);
+ beanDefinitionRegistry.registerBeanDefinition("monkey", rootBeanDefinition);
+ }
+
+ @Override
+ public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
+
+ }
+ }
+ ```
+
+ - 调用BeanFacotryPostProcessor实现容器内Bean的定义添加属性
+
+ - ```java
+ // 调用时机:在BeanFactory标准初始化之后调用,这时所有的Bean定义已经保存加载到BeanFactory,但是Bean的实例还未创建
+ // 作用:来定制和修改BeanFactory内容,如覆盖或添加属性
+ @Component
+ public class MyBeanFacotoryPostProcessor implements BeanFactoryPostProcessor {
+
+ @Override
+ public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
+ // 给DemoBean添加属性
+ BeanDefinition beanDemo = configurableListableBeanFactory.getBeanDefinition("demoBean");
+ MutablePropertyValues propertyValues = beanDemo.getPropertyValues();
+ propertyValues.addPropertyValue("name", "ipman");
+ }
+ }
+ ```
+
+- 步骤
+
+ - 步骤一
+
+ image-20211017195022460
+
+
+
+ - 步骤二
+
+ image-20211017195333372
+
+
+
+ - 步骤三
+
+ image-20211017195520176
+
+
+
+ - 步骤四
+
+ [画像:image-20211017195711612]
+
+- invokeBeanFacotoryPostProcessors()方法
+
+```java
+public static void invokeBeanFactoryPostProcessors(
+ ConfigurableListableBeanFactory beanFactory, List beanFactoryPostProcessors) {
+
+ // 保存所有调用过的PostProcessor的beanName
+ Set processedBeans = new HashSet();
+
+ if (beanFactory instanceof BeanDefinitionRegistry) {
+ BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
+ // 这两个list主要用来分别收集BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor
+ List regularPostProcessors = new ArrayList();
+ List registryProcessors = new ArrayList();
+
+ for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
+ // 对已注册在DefaultListBeanFactory的BeanFactoryPostProcessor进行分类
+ if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
+ BeanDefinitionRegistryPostProcessor registryProcessor =
+ (BeanDefinitionRegistryPostProcessor) postProcessor;
+ // 已注册在DefaultListBeanFactory的BeanDefinitionRegistryPostProcessor优先级最高
+ // 如果postProcessor是BeanDefinitionRegistryPostProcessor的实例
+ // 执行postProcessor的postProcessBeanDefinitionRegistry
+ // 为什么执行完之后还要保存到List中呢?
+ // 因为这里只是执行完了BeanDefinitionRegistryPostProcessor的回调
+ // 父类BeanFactoryPostProcessor的方法还没有进行回调
+ registryProcessor.postProcessBeanDefinitionRegistry(registry);
+ registryProcessors.add(registryProcessor);
+ }
+ else {
+ // 如果不是BeanDefinitionRegistryPostProcessor的实例
+ // 则是BeanFactoryPostProcessor的实例,存放在List中,后面会进行回调
+ regularPostProcessors.add(postProcessor);
+ }
+ }
+
+ // 定义了一个集合来存放当前需要执行的BeanDefinitionRegistryPostProcessor
+ List currentRegistryProcessors = new ArrayList();
+
+ // 首先执行实现了PriorityOrdered的BeanDefinitionRegistryPostProcessor
+ // 这里只能获取到Spring内部注册的BeanDefinitionRegistryPostProcessor,因为到这里spring还没有去扫描Bean,获取不到我们 // 通过@Component标志的自定义的BeanDefinitionRegistryPostProcessor
+ // 一般默认情况下,这里只有一个beanName,
+ // org.springframework.context.annotation.internalConfigurationAnnotationProcessor
+ // 对应的BeanClass:ConfigurationClassPostProcessor
+ String[] postProcessorNames =
+ beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
+ for (String ppName : postProcessorNames) {
+ if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
+ currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
+ // 保存调用过的beanName
+ processedBeans.add(ppName);
+ }
+ }
+ // 排序
+ sortPostProcessors(currentRegistryProcessors, beanFactory);
+ // registryProcessors存放的是BeanDefinitionRegistryPostProcessor
+ registryProcessors.addAll(currentRegistryProcessors);
+ // 执行BeanDefinitionRegistryPostProcessor,一般默认情况下,只有ConfigurationClassPostProcessor
+ // ConfigurationClassPostProcessor的具体作用后面再讲,这里先认为它执行完扫描,并且注册BeanDefinition
+ invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
+ // 清空临时变量,后面再使用
+ currentRegistryProcessors.clear();
+
+ // 第二步执行实现了Ordered的BeanDefinitionRegistryPostProcessor
+ // 这里为什么要再一次从beanFactory中获取所有的BeanDefinitionRegistryPostProcessor,是因为上面的操作有可能注册了
+ // 新的BeanDefinitionRegistryPostProcessor,所以再获取一次
+ postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
+ for (String ppName : postProcessorNames) {
+ if (!processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) {
+ currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
+ // 保存调用过的beanName
+ processedBeans.add(ppName);
+ }
+ }
+ sortPostProcessors(currentRegistryProcessors, beanFactory);
+ registryProcessors.addAll(currentRegistryProcessors);
+ invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
+ currentRegistryProcessors.clear();
+
+
+ // 上面两步的套路是一模一样的,唯一不一样的地方是第一步执行的是实现了PriorityOrdered的
+ // BeanDefinitionRegistryPostProcessor,第二步是执行的是实现了Ordered的
+ // BeanDefinitionRegistryPostProcessor
+
+
+ // 最后一步,执行没有实现PriorityOrdered或者Ordered的BeanDefinitionRegistryPostProcessor
+ // 比较不一样的是,这里有个while循环,是因为在实现BeanDefinitionRegistryPostProcessor的方法的过程中有可能会注册新的
+ // BeanDefinitionRegistryPostProcessor,所以需要处理,直到不会出现新的BeanDefinitionRegistryPostProcessor为止
+ boolean reiterate = true;
+ while (reiterate) {
+ reiterate = false;
+ postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
+ for (String ppName : postProcessorNames) {
+ if (!processedBeans.contains(ppName)) {
+ // 发现还有未处理过的BeanDefinitionRegistryPostProcessor,按照套路放进list中
+ // reiterate标记为true,后面还要再执行一次这个循环,因为执行新的BeanDefinitionRegistryPostProcessor有可能会
+ // 注册新的BeanDefinitionRegistryPostProcessor
+ currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
+ processedBeans.add(ppName);
+ reiterate = true;
+ }
+ }
+ sortPostProcessors(currentRegistryProcessors, beanFactory);
+ registryProcessors.addAll(currentRegistryProcessors);
+ invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
+ currentRegistryProcessors.clear();
+ }
+ // 方法开头前几行分类的两个list就是在这里调用
+ // BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor
+ // 刚刚执行了BeanDefinitionRegistryPostProcessor的方法
+ // 现在要执行父类BeanFactoryPostProcessor的方法
+ invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);
+ invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory);
+ }
+
+ else {
+ // Invoke factory processors registered with the context instance.
+ invokeBeanFactoryPostProcessors(beanFactoryPostProcessors, beanFactory);
+ }
+
+
+ // 上面BeanFactoryPostProcessor的回调可能又注册了一些类,下面需要再走一遍之前的逻辑
+ String[] postProcessorNames =
+ beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);
+
+ // 根据不同的优先级,分成三类
+ List priorityOrderedPostProcessors = new ArrayList();
+ List orderedPostProcessorNames = new ArrayList();
+ List nonOrderedPostProcessorNames = new ArrayList();
+ for (String ppName : postProcessorNames) {
+ // 上面处理BeanDefinitionRegistryPostProcessor的时候已经处理过,这里不再重复处理
+ // 因为BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor
+ if (processedBeans.contains(ppName)) {
+ // skip - already processed in first phase above
+ }
+ else if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
+ priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class));
+ }
+ else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
+ orderedPostProcessorNames.add(ppName);
+ }
+ else {
+ nonOrderedPostProcessorNames.add(ppName);
+ }
+ }
+
+ // First, invoke the BeanFactoryPostProcessors that implement PriorityOrdered.
+ // 下面的逻辑跟上面是一致的,先处理实现了PriorityOrdered接口的
+ // 再处理实现了Ordered接口的
+ // 最后处理普通的BeanFactoryPostProcessor
+ sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
+ invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);
+
+ // Next, invoke the BeanFactoryPostProcessors that implement Ordered.
+ List orderedPostProcessors = new ArrayList();
+ for (String postProcessorName : orderedPostProcessorNames) {
+ orderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
+ }
+ sortPostProcessors(orderedPostProcessors, beanFactory);
+ invokeBeanFactoryPostProcessors(orderedPostProcessors, beanFactory);
+
+ // Finally, invoke all other BeanFactoryPostProcessors.
+ List nonOrderedPostProcessors = new ArrayList();
+ for (String postProcessorName : nonOrderedPostProcessorNames) {
+ nonOrderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
+ }
+ invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory);
+
+ // 因为各种BeanFactoryPostProcessor可能修改了BeanDefinition
+ // 所以这里需要清除缓存,需要的时候再通过merge的方式获取
+ beanFactory.clearMetadataCache();
+}
+```
+
diff --git "a/springboot-source-code-analysis/(2)346円241円206円346円236円266円345円210円235円345円247円213円345円214円226円345円231円250円345円256円236円346円210円230円.md" "b/springboot-source-code-analysis/(2)346円241円206円346円236円266円345円210円235円345円247円213円345円214円226円345円231円250円345円256円236円346円210円230円.md"
new file mode 100644
index 0000000..a516601
--- /dev/null
+++ "b/springboot-source-code-analysis/(2)346円241円206円346円236円266円345円210円235円345円247円213円345円214円226円345円231円250円345円256円236円346円210円230円.md"
@@ -0,0 +1,149 @@
+## 框架初始化器实战
+
+### 思路
+
+添加一个框架初始化器,在Springboot初始化阶段中添加一个自定义的环境属性配置
+
+- 添加一个自定义的框架初始化类,实现 ApplicationContextInitializer 接口
+ - 重写 initialize(ConfigurableApplicationContext configurableApplicationContext) 方法
+ - 获取系统属性 ConfigurableEnvirnoment env = configurableApplicationContext.getEnvironment();
+ - 添加系统属性 env.getPropertySource().addLast(XXX);
+- 加载这个自定义框架初始化类
+ - 方式一
+ - 在 /META/spring.factories 中配置 org.springframe.context.ApplicationContextInitializer = 自定义框架初始化器
+ - 方式二
+ - 在springApplication.run()前,通过springApplicaiton.addInitializers()方法,自定义框架初始化器
+ - 方式三
+ - 在application.properties 中配置 context.initializer.classes = 自定义框架初始化器
+
+
+
+### 方式一代码
+
+#### 添加自定义初始化器
+
+```java
+@Order(1) // 设置执行顺序
+public class FirstInitializer implements ApplicationContextInitializer {
+
+ private static final Logger logger = LoggerFactory.getLogger(FirstInitializer.class);
+
+ @Override
+ public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
+ // 获取系统属性
+ ConfigurableEnvironment environment =
+ configurableApplicationContext.getEnvironment();
+
+ // 设置自定义属性
+ Map attributeMap = new HashMap();
+ attributeMap.put("firstKey", "firstValue");
+
+ // 添加自定义属性
+ MapPropertySource mapPropertySource = new MapPropertySource("firstInitializer", attributeMap);
+ environment.getPropertySources().addLast(mapPropertySource);
+ logger.info("设置框架初始化器【ApplicationContextInitializer】成功 : run firstInitializer");
+ }
+}
+```
+
+
+
+#### 在 /META/spring.factories 中配置 自定义初始化器类的Reference
+
+```java
+# 通过系统初始化器,设置自定义环境属性
+org.springframework.context.ApplicationContextInitializer=\
+ com.example.springboot.source.code.analysis.initializer.FirstInitializer
+```
+
+
+
+### 方式二代码
+
+#### 添加自定义初始化器
+
+```java
+@Order(2) // 设置执行顺序
+public class SecondInitializer implements ApplicationContextInitializer {
+
+ private static final Logger logger = LoggerFactory.getLogger(SecondInitializer.class);
+
+ @Override
+ public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
+ // 获取系统属性
+ ConfigurableEnvironment environment =
+ configurableApplicationContext.getEnvironment();
+
+ // 设置自定义属性
+ Map attributeMap = new HashMap();
+ attributeMap.put("secondKey", "secondValue");
+
+ // 添加自定义属性
+ MapPropertySource mapPropertySource = new MapPropertySource("secondInitializer", attributeMap);
+ environment.getPropertySources().addLast(mapPropertySource);
+ logger.info("设置框架初始化器【ApplicationContextInitializer】成功 : run secondInitializer");
+ }
+}
+```
+
+
+
+#### 在 springApplication#run方法执行前,加载自定义初始化器
+
+```java
+@SpringBootApplication
+public class SpringbootSourceCodeAnalysisApplication {
+
+ public static void main(String[] args) {
+ // SpringApplication.run(SpringbootSourceCodeAnalysisApplication.class, args);
+ SpringApplication springApplication = new SpringApplication(SpringbootSourceCodeAnalysisApplication.class);
+
+ // 手动添加一个框架的初始化器
+ springApplication.addInitializers(new SecondInitializer());
+
+ springApplication.run();
+
+ }
+}
+```
+
+
+
+### 方式三代码
+
+#### 添加自定义初始化器
+
+```java
+@Order(3) // 设置执行顺序
+public class ThirdInitializer implements ApplicationContextInitializer {
+
+ private static final Logger logger = LoggerFactory.getLogger(ThirdInitializer.class);
+
+ @Override
+ public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
+ // 获取系统属性
+ ConfigurableEnvironment environment =
+ configurableApplicationContext.getEnvironment();
+
+ // 设置自定义属性
+ Map attributeMap = new HashMap();
+ attributeMap.put("thirdKey", "thirdValue");
+
+ // 添加自定义属性
+ MapPropertySource mapPropertySource = new MapPropertySource("thirdInitializer", attributeMap);
+ environment.getPropertySources().addLast(mapPropertySource);
+ logger.info("设置框架初始化器【ApplicationContextInitializer】成功 : run thirdInitializer");
+ }
+}
+```
+
+
+
+#### 在 application.property 配置文件中 添加自定义初始器
+
+```java
+# 通过系统初始化器,设置环境属性
+context.initializer.classes=\
+ com.example.springboot.source.code.analysis.initializer.ThirdInitializer
+```
+
diff --git "a/springboot-source-code-analysis/(3)345円267円245円345円216円202円345円212円240円350円275円275円346円234円272円345円210円266円350円247円243円346円236円220円.md" "b/springboot-source-code-analysis/(3)345円267円245円345円216円202円345円212円240円350円275円275円346円234円272円345円210円266円350円247円243円346円236円220円.md"
new file mode 100644
index 0000000..9dc5a54
--- /dev/null
+++ "b/springboot-source-code-analysis/(3)345円267円245円345円216円202円345円212円240円350円275円275円346円234円272円345円210円266円350円247円243円346円236円220円.md"
@@ -0,0 +1,186 @@
+ ## 工厂加载机制解析
+
+### SpringFactoriesLoader 介绍
+
+> org.springframework.core.io.support.SpringFactoriesLoader
+
+```java
+/**
+ * General purpose factory loading mechanism for internal use within the framework.
+ *
+ *
{@code SpringFactoriesLoader} {@linkplain #loadFactories loads} and instantiates
+ * factories of a given type from {@value #FACTORIES_RESOURCE_LOCATION} files which
+ * may be present in multiple JAR files in the classpath. The {@code spring.factories}
+ * file must be in {@link Properties} format, where the key is the fully qualified
+ * name of the interface or abstract class, and the value is a comma-separated list of
+ * implementation class names. For example:
+ *
+ *
Typically used within web applications that require some programmatic initialization
+ * of the application context. For example, registering property sources or activating
+ * profiles against the {@linkplain ConfigurableApplicationContext#getEnvironment()
+ * context's environment}. See {@code ContextLoader} and {@code FrameworkServlet} support
+ * for declaring a "contextInitializerClasses" context-param and init-param, respectively.
+ *
+ *
As of Spring 3.0, an ApplicationListener can generically declare the event type
+ * that it is interested in. When registered with a Spring ApplicationContext, events
+ * will be filtered accordingly, with the listener getting invoked for matching event
+ * objects only.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @param the specific ApplicationEvent subclass to listen to
+ * @see org.springframework.context.event.ApplicationEventMulticaster
+ */
+@FunctionalInterface
+public interface ApplicationListener extends EventListener {
+
+ /**
+ * Handle an application event.
+ * @param event the event to respond to
+ */
+ void onApplicationEvent(E event);
+}
+```
+
+- SpringBoot中可以通过实现ApplicationListener接口类,来完成系统事件的监听
+- 这个监听器是机遇java.util.EventListener标准来实现的,是一个ObServer观察者模式
+- 从Spring3.0开始,ApplicationListener可以通过声明感兴趣的事件进行触发
+
+
+
+### SpringBoot 系统广播器介绍
+
+ ```java
+ /**
+ * Interface to be implemented by objects that can manage a number of
+ * {@link ApplicationListener} objects, and publish events to them.
+ *
+ *
An {@link org.springframework.context.ApplicationEventPublisher}, typically
+ * a Spring {@link org.springframework.context.ApplicationContext}, can use an
+ * ApplicationEventMulticaster as a delegate for actually publishing events.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @author Stephane Nicoll
+ */
+ public interface ApplicationEventMulticaster {
+ ```
+
+- 用来管理ApplicationListener事件监听器,比如事件监听器的添加、删除等
+- 用来遍历所有的ApplicationListener事件监听器,触发ApplicationEvent事件
+
+
+
+#### (1)Spring 系统广播器的具体方法介绍
+
+```java
+public interface ApplicationEventMulticaster {
+
+ // 添加一个 ApplicationListener 事件监听器
+ void addApplicationListener(ApplicationListener> listener);
+
+ // 删除一个 ApplicationListener 事件监听器
+ void removeApplicationListener(ApplicationListener> listener);
+
+ // 删除所有事件监听器
+ void removeAllListeners();
+
+ // 广播 ApplicationEvent 事件
+ void multicastEvent(ApplicationEvent event);
+}
+```
+
+
+
+### Spring 系统事件介绍
+
+image-20210803171306277
+
+- EventObject:具体的事件描述
+- ApplicationEvent:应用事件描述
+- SpringApplicationEvent:框架应用事件描述
+- ApplicationContextInitializedEvent:框架应用上下文初始化阶段的事件
+- ApplicationEnvironmentPreparedEvent:框架应用环境属性准备好阶段的事件
+- ApplicationFeiledEvent:框架启动失败时的事件
+- ApplicationPreparedEvent:框架准备阶段的事件
+- ApplicationReadyEvent:框架准备好时的事件
+- ApplicationStartedEvent:框架启动完成时的事件
+- ApplicationStartingEvent:框架启动中的事件
+
+
+
+### Spring 事件发送的顺序
+
+image-20210803172047686
+
+- 框架准备启动
+- 触发ApplicationStaringEvent事件,告知框架已开始启动
+- 触发ApplicationEnvironmentPreparedEvent事件,告知框架环境属性、我们指定的一些属性已经准备完成
+- 触发ApplicationContextInitializedEvent事件,告知框架ApplicationContextInitializer的系统上下文已经初始化好
+- Bean的准备、启动、准备好阶段,会分别触发ApplicationPreparedEvent、ApplicationStartedEvent、ApplicationReadyEvent事件;
+- 整个SpringBoot启动失败时,会触发ApplicationFailedEvent事件;
+- 框架启动完毕
+
+
+
+### 监听器注册
+
+image-20210809153150882
+
+- 定义SpringApplicationListener接口实现类
+- 将SpringApplicationListeners接口实现类配置到spring.factories配置文件中
+- 通过SpringFactoriesLoader系统工程加载类加载spring.factories配置文件中的SpringApplicationListeners接口实现类
+- 将SpringApplicationListeners初始化后放入this.listeners属性中进行排序
+
diff --git "a/springboot-source-code-analysis/(7)344円272円213円344円273円266円347円233円221円345円220円254円345円231円250円350円247円246円345円217円221円346円234円272円345円210円266円350円247円243円346円236円220円.md" "b/springboot-source-code-analysis/(7)344円272円213円344円273円266円347円233円221円345円220円254円345円231円250円350円247円246円345円217円221円346円234円272円345円210円266円350円247円243円346円236円220円.md"
new file mode 100644
index 0000000..de9c491
--- /dev/null
+++ "b/springboot-source-code-analysis/(7)344円272円213円344円273円266円347円233円221円345円220円254円345円231円250円350円247円246円345円217円221円346円234円272円345円210円266円350円247円243円346円236円220円.md"
@@ -0,0 +1,372 @@
+### 事件监听器触发机制解析
+
+
+
+#### 以SpringBoot Staring事件具例子
+
+- SpringContext#run方法中的,listeners.staring()方法
+
+```java
+ /**
+ * Run the Spring application, creating and refreshing a new
+ * {@link ApplicationContext}.
+ * @param args the application arguments (usually passed from a Java main method)
+ * @return a running {@link ApplicationContext}
+ */
+ public ConfigurableApplicationContext run(String... args) {
+ StopWatch stopWatch = new StopWatch();
+ stopWatch.start();
+ ConfigurableApplicationContext context = null;
+ Collection exceptionReporters = new ArrayList();
+ configureHeadlessProperty();
+ SpringApplicationRunListeners listeners = getRunListeners(args);
+ // 告知框架已经开始启动
+ listeners.starting();
+ try {
+```
+
+
+
+- SpringApplicationRunListeners#starting() 内部方法
+
+```java
+ public void starting() {
+ // 调用 SpringApplicationRunListenser#staring() 方法
+ for (SpringApplicationRunListener listener : this.listeners) {
+ listener.starting();
+ }
+ }
+```
+
+
+
+- SpringApplicationRunListener方法,定义了框架各个阶段的事件监听方法
+
+```java
+/**
+ * Listener for the {@link SpringApplication} {@code run} method.
+ * {@link SpringApplicationRunListener}s are loaded via the {@link SpringFactoriesLoader}
+ * and should declare a public constructor that accepts a {@link SpringApplication}
+ * instance and a {@code String[]} of arguments. A new
+ * {@link SpringApplicationRunListener} instance will be created for each run.
+ *
+ * @author Phillip Webb
+ * @author Dave Syer
+ * @author Andy Wilkinson
+ * @since 1.0.0
+ */
+public interface SpringApplicationRunListener {
+
+ /**
+ * Called immediately when the run method has first started. Can be used for very
+ * early initialization.
+ */
+ void starting();
+
+ /**
+ * Called once the environment has been prepared, but before the
+ * {@link ApplicationContext} has been created.
+ * @param environment the environment
+ */
+ void environmentPrepared(ConfigurableEnvironment environment);
+
+ /**
+ * Called once the {@link ApplicationContext} has been created and prepared, but
+ * before sources have been loaded.
+ * @param context the application context
+ */
+ void contextPrepared(ConfigurableApplicationContext context);
+
+ /**
+ * Called once the application context has been loaded but before it has been
+ * refreshed.
+ * @param context the application context
+ */
+ void contextLoaded(ConfigurableApplicationContext context);
+
+ /**
+ * The context has been refreshed and the application has started but
+ * {@link CommandLineRunner CommandLineRunners} and {@link ApplicationRunner
+ * ApplicationRunners} have not been called.
+ * @param context the application context.
+ * @since 2.0.0
+ */
+ void started(ConfigurableApplicationContext context);
+
+ /**
+ * Called immediately before the run method finishes, when the application context has
+ * been refreshed and all {@link CommandLineRunner CommandLineRunners} and
+ * {@link ApplicationRunner ApplicationRunners} have been called.
+ * @param context the application context.
+ * @since 2.0.0
+ */
+ void running(ConfigurableApplicationContext context);
+
+ /**
+ * Called when a failure occurs when running the application.
+ * @param context the application context or {@code null} if a failure occurred before
+ * the context was created
+ * @param exception the failure
+ * @since 2.0.0
+ */
+ void failed(ConfigurableApplicationContext context, Throwable exception);
+
+}
+```
+
+
+
+- SpringApplicationRunListener#starting() 方法,通过 SimpleApplicationEventMulticaster 广播器发送 ApplicationStartingEvent 事件
+
+```java
+/**
+ * {@link SpringApplicationRunListener} to publish {@link SpringApplicationEvent}s.
+ *
+ * Uses an internal {@link ApplicationEventMulticaster} for the events that are fired
+ * before the context is actually refreshed.
+ *
+ * @author Phillip Webb
+ * @author Stephane Nicoll
+ * @author Andy Wilkinson
+ * @author Artsiom Yudovin
+ * @since 1.0.0
+ */
+public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
+
+ private final SpringApplication application;
+
+ private final String[] args;
+
+ private final SimpleApplicationEventMulticaster initialMulticaster;
+
+ public EventPublishingRunListener(SpringApplication application, String[] args) {
+ this.application = application;
+ this.args = args;
+ this.initialMulticaster = new SimpleApplicationEventMulticaster();
+ for (ApplicationListener> listener : application.getListeners()) {
+ this.initialMulticaster.addApplicationListener(listener);
+ }
+ }
+
+ @Override
+ public void starting() {
+ // 通过广播器,发送ApplicationStaringEvent事件
+ this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
+ }
+
+```
+
+
+
+- SimpleApplicationEventMulticaster#multicastEvent() 广播器发送方法
+
+```java
+public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
+
+ @Override
+ public void multicastEvent(ApplicationEvent event) {
+ multicastEvent(event, resolveDefaultEventType(event));
+ }
+
+ @Override
+ public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
+ ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
+ Executor executor = getTaskExecutor();
+ // 通过AbstractApplicationEventMulticaster#getApplicationListeners() 方法获得对该事件感兴趣的监听器列表
+ for (ApplicationListener> listener : getApplicationListeners(event, type)) {
+ if (executor != null) {
+ executor.execute(() -> invokeListener(listener, event));
+ }
+ else {
+ invokeListener(listener, event);
+ }
+ }
+ }
+```
+
+
+
+- AbstractApplicationEventMulticaster#getApplicationListeners() 方法,获得对该事件感兴趣的监听器列表
+
+```java
+ /**
+ * Return a Collection of ApplicationListeners matching the given
+ * event type. Non-matching listeners get excluded early.
+ * @param event the event to be propagated. Allows for excluding
+ * non-matching listeners early, based on cached matching information.
+ * @param eventType the event type
+ * @return a Collection of ApplicationListeners
+ * @see org.springframework.context.ApplicationListener
+ */
+ protected Collection> getApplicationListeners(
+ ApplicationEvent event, ResolvableType eventType) {
+
+ Object source = event.getSource();
+ Class> sourceType = (source != null ? source.getClass() : null);
+ ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);
+
+ // Quick check for existing entry on ConcurrentHashMap...
+ ListenerRetriever retriever = this.retrieverCache.get(cacheKey);
+ if (retriever != null) {
+ return retriever.getApplicationListeners();
+ }
+
+ if (this.beanClassLoader == null ||
+ (ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
+ (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
+ // Fully synchronized building and caching of a ListenerRetriever
+ // 避免其它线程操作ListenersCache缓存
+ synchronized (this.retrievalMutex) {
+ retriever = this.retrieverCache.get(cacheKey);
+ // 双重检测锁
+ if (retriever != null) {
+ return retriever.getApplicationListeners();
+ }
+ retriever = new ListenerRetriever(true);
+ // 通过AbstractApplicationEventMulicaster#retrieveApplicationListeners()获取感兴趣的事件监听列表
+ Collection> listeners =
+ retrieveApplicationListeners(eventType, sourceType, retriever);
+ this.retrieverCache.put(cacheKey, retriever);
+ return listeners;
+ }
+ }
+ else {
+ // No ListenerRetriever caching -> no synchronization necessary
+ return retrieveApplicationListeners(eventType, sourceType, null);
+ }
+ }
+```
+
+
+
+- 如果 this.retrieverCache 缓存获取不到 监听器列表,就从AbstractApplicationEventMulticaster#retrieveApplicationListeners()中获取
+
+```java
+/**
+ * Actually retrieve the application listeners for the given event and source type.
+ * @param eventType the event type
+ * @param sourceType the event source type
+ * @param retriever the ListenerRetriever, if supposed to populate one (for caching purposes)
+ * @return the pre-filtered list of application listeners for the given event and source type
+ */
+ private Collection> retrieveApplicationListeners(
+ ResolvableType eventType, @Nullable Class> sourceType, @Nullable ListenerRetriever retriever) {
+
+ List> allListeners = new ArrayList();
+ Set> listeners;
+ Set listenerBeans;
+ // 通过互斥锁初始化Set对象
+ synchronized (this.retrievalMutex) {
+ listeners = new LinkedHashSet(this.defaultRetriever.applicationListeners);
+ listenerBeans = new LinkedHashSet(this.defaultRetriever.applicationListenerBeans);
+ }
+ /**
+ 获取所有事件监听器
+ 在 spring.factories 中定义的实践监听器
+ # Application Listeners
+ org.springframework.context.ApplicationListener=\
+ org.springframework.boot.ClearCachesApplicationListener,\
+ org.springframework.boot.builder.ParentContextCloserApplicationListener,\
+ org.springframework.boot.context.FileEncodingApplicationListener,\
+ org.springframework.boot.context.config.AnsiOutputApplicationListener,\
+ org.springframework.boot.context.config.ConfigFileApplicationListener,\
+ org.springframework.boot.context.config.DelegatingApplicationListener,\
+ org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
+ org.springframework.boot.context.logging.LoggingApplicationListener,\
+ org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
+ */
+ for (ApplicationListener> listener : listeners) {
+ // 判断是否对该事件感兴趣
+ if (supportsEvent(listener, eventType, sourceType)) {
+ if (retriever != null) {
+ retriever.applicationListeners.add(listener);
+ }
+ // 感兴趣就加入到集合当中
+ allListeners.add(listener);
+ }
+ }
+ if (!listenerBeans.isEmpty()) {
+ BeanFactory beanFactory = getBeanFactory();
+ for (String listenerBeanName : listenerBeans) {
+ try {
+ Class> listenerType = beanFactory.getType(listenerBeanName);
+ if (listenerType == null || supportsEvent(listenerType, eventType)) {
+ ApplicationListener> listener =
+ beanFactory.getBean(listenerBeanName, ApplicationListener.class);
+ if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {
+ if (retriever != null) {
+ if (beanFactory.isSingleton(listenerBeanName)) {
+ retriever.applicationListeners.add(listener);
+ }
+ else {
+ retriever.applicationListenerBeans.add(listenerBeanName);
+ }
+ }
+ allListeners.add(listener);
+ }
+ }
+ }
+ catch (NoSuchBeanDefinitionException ex) {
+ // Singleton listener instance (without backing bean definition) disappeared -
+ // probably in the middle of the destruction phase
+ }
+ }
+ }
+ // 监听器排序
+ AnnotationAwareOrderComparator.sort(allListeners);
+ if (retriever != null && retriever.applicationListenerBeans.isEmpty()) {
+ retriever.applicationListeners.clear();
+ retriever.applicationListeners.addAll(allListeners);
+ }
+ return allListeners;
+ }
+```
+
+
+
+- 通过AbstractApplicationEventMuliticaster#supportEvent() 方法,判断该事件监听器,是否对该事件感兴趣?
+
+```java
+ /**
+ * Determine whether the given listener supports the given event.
+ *
The default implementation detects the {@link SmartApplicationListener}
+ * and {@link GenericApplicationListener} interfaces. In case of a standard
+ * {@link ApplicationListener}, a {@link GenericApplicationListenerAdapter}
+ * will be used to introspect the generically declared type of the target listener.
+ * @param listener the target listener to check
+ * @param eventType the event type to check against
+ * @param sourceType the source type to check against
+ * @return whether the given listener should be included in the candidates
+ * for the given event type
+ */
+ protected boolean supportsEvent(
+ ApplicationListener> listener, ResolvableType eventType, @Nullable Class> sourceType) {
+
+ // 判断当前事件监听器是否是GenericApplicationListerner接口类的实现类,如果不是的话,就做个委派,方便获取该事件监听器感兴趣事件
+ GenericApplicationListener smartListener = (listener instanceof GenericApplicationListener ?
+ (GenericApplicationListener) listener : new GenericApplicationListenerAdapter(listener));
+ // 判断当前监听器是否感兴趣该事件
+ return (smartListener.supportsEventType(eventType) && smartListener.supportsSourceType(sourceType));
+ }
+```
+
+
+
+
+
+#### 获取监听器列表逻辑
+
+image-20210920200242597
+
+- 通过AbstractApplicationEventMulticaster#getApplicationListeners()方法获取感兴趣事件监听器列表
+- 判断当前SpringBoot缓存中是否存在该事件感兴趣的监听器列表
+- 如果没有,调用通过AbstractApplicationEventMulicaster#retrieveApplicationListener()方法,获取所有事件监听器列表
+- 遍历所有事件监听器列表
+- 通过AbstractApplicationEventMuliticaster#supportsEvent() 方法查找出对该事件感兴趣的事件监听器
+- 将感兴趣的事件监听器列表加入SpirngBoot缓存中,并返回给事件触发器
+
+
+
+#### AbstractApplicationEventMuliticaster#supportEvent() 事件监听器匹配器触发条件
+
+image-20210920201046825
\ No newline at end of file
diff --git "a/springboot-source-code-analysis/(8)350円207円252円345円256円232円344円271円211円347円233円221円345円220円254円345円231円250円345円256円236円346円210円230円.md" "b/springboot-source-code-analysis/(8)350円207円252円345円256円232円344円271円211円347円233円221円345円220円254円345円231円250円345円256円236円346円210円230円.md"
new file mode 100644
index 0000000..748b4c4
--- /dev/null
+++ "b/springboot-source-code-analysis/(8)350円207円252円345円256円232円344円271円211円347円233円221円345円220円254円345円231円250円345円256円236円346円210円230円.md"
@@ -0,0 +1,155 @@
+### 自定义监听器实战
+
+#### 实现方式一
+
+- 实现ApplicationListener接口,ApplicationListener声明感兴趣的事件
+
+```java
+@Order(1)
+public class FirstListener implements ApplicationListener {
+
+ private final static Logger logger = LoggerFactory.getLogger(FirstListener.class);
+
+ @Override
+ public void onApplicationEvent(ApplicationStartedEvent event) {
+ logger.info("设置框架事件监听器【ApplicationListener】成功 : run firstListener");
+
+ }
+}
+
+```
+
+- 在spring.factories 配置文件中配置FirstListener事件监听器,key值为org.springframework.context.ApplicationListener
+
+```java
+# 通过系统事件监听器,设置自定义事件监听器
+org.springframework.context.ApplicationListener=\
+ com.example.springboot.source.code.analysis.listener.FirstListener
+```
+
+
+
+#### 实现方式三
+
+- 实现ApplicationListener接口,声明感兴趣的事件ApplicationListener
+
+```java
+@Order(3)
+public class ThirdListener implements ApplicationListener {
+
+ private final static Logger logger = LoggerFactory.getLogger(ThirdListener.class);
+
+ @Override
+ public void onApplicationEvent(ApplicationStartedEvent event) {
+ logger.info("设置框架事件监听器【ApplicationListener】成功 : run thirdListener");
+ }
+}
+```
+
+- 在application.properties 内配置事件监听器,key值为context.listener.classes
+
+```java
+# 通过系统事件监听器,监听事件
+context.listener.classes=\
+ com.example.springboot.source.code.analysis.listener.ThirdListener,\
+```
+
+> application.properties 内配置的 conext.listener.classes 事件监听器执行优先级最高
+>
+> ```java
+> // context.listener.classes 是通过 DelegatingApplicationListener 委派给 ApplicationListener
+> public class DelegatingApplicationListener implements ApplicationListener, Ordered {
+> private static final String PROPERTY_NAME = "context.listener.classes";
+> // 在委派过程中,DelegatingApplicationListener 会将 order 优先级设置为最高
+> private int order = 0;
+> private SimpleApplicationEventMulticaster multicaster;
+>
+> ```
+
+
+
+#### 实现方式四
+
+- 实现SmartApplicationListener接口
+
+```java
+@Order(4)
+public class FourthListener implements SmartApplicationListener {
+
+ private final static Logger logger = LoggerFactory.getLogger(FourthListener.class);
+
+ // 通过重写 supportEventType()方法 判断该监听器对哪些事件感兴趣
+ @Override
+ public boolean supportsEventType(Class extends ApplicationEvent> eventType) {
+ return ApplicationStartedEvent.class.isAssignableFrom(eventType)
+ || ApplicationPreparedEvent.class.isAssignableFrom(eventType);
+ }
+
+ // 编写事件触发逻辑
+ @Override
+ public void onApplicationEvent(ApplicationEvent event) {
+ logger.info("设置框架事件监听器【SmartApplicationListener】成功 : run fourthListener");
+ }
+}
+
+```
+
+- 可以通过spring.factories 或 application.properties 或 new SpringApplication().addListeners() 三种方式注入该事件
+
+
+
+#### 通过 ContextRefershedEvent 获取 Spirng 上下文
+
+- 实现ApplicationListener接口,关注ContextRefershedEvent事件
+
+```java
+@Component
+@Order(1)
+public class ApplicationContextContainer implements ApplicationListener {
+
+ private ApplicationContext applicationContext;
+
+ private static ApplicationContextContainer applicationContextContainer;
+
+ // 当容器上下文刷新完成后,会触发此方法
+ @Override
+ public void onApplicationEvent(@Nullable ContextRefreshedEvent event) {
+ // 双重检测锁,获取当前容器上下文
+ if (event != null && applicationContext == null) {
+ synchronized (ApplicationContextContainer.class) {
+ if (applicationContext == null) {
+ applicationContext = event.getApplicationContext();
+ }
+ }
+ }
+ applicationContextContainer = this;
+ }
+
+ // 返回当前自定义容器类的实例
+ public static ApplicationContextContainer getInstance() {
+ return applicationContextContainer;
+ }
+
+ // 从 Spring 容器中获取 Bean
+ public T getBean(Class clazz) {
+ return applicationContext.getBean(clazz);
+ }
+}
+```
+
+- 测试,通过普通方法获取Spring容器中的Bean
+
+```java
+@SpringBootTest(classes = SpringbootSourceCodeAnalysisApplication.class)
+@RunWith(SpringJUnit4ClassRunner.class)
+public class SpringbootSourceCodeAnalysisApplicationTests {
+
+ // 测试ContextRefreshEvent事件
+ @Test
+ public void testContextRefreshEvent() {
+ WeatherRunListener weatherRunListener = ApplicationContextContainer.getInstance().getBean(WeatherRunListener.class);
+ weatherRunListener.snow();
+ weatherRunListener.rain();
+ }
+```
+
diff --git "a/springboot-source-code-analysis/(9)IoC346円200円235円346円203円263円.md" "b/springboot-source-code-analysis/(9)IoC346円200円235円346円203円263円.md"
new file mode 100644
index 0000000..f6d2ee9
--- /dev/null
+++ "b/springboot-source-code-analysis/(9)IoC346円200円235円346円203円263円.md"
@@ -0,0 +1,15 @@
+### IoC思想
+
+
+
+image-20210921195432177
+
+
+
+- 松耦合
+ - 降低强耦合关系,对象与对象之间的依赖不用new,而是通过Spring容器@Autowired注入完成
+- 灵活性
+ - 不需要初始化使用类的构造方法,而是通过Spring容器完成初始化工作
+- 可维护
+ - 通过Spring xml或注解 的方式,可以清晰的了解类与类之间的依赖关系,提升可读性
+
diff --git a/springboot-source-code-analysis/.gitignore b/springboot-source-code-analysis/.gitignore
new file mode 100644
index 0000000..549e00a
--- /dev/null
+++ b/springboot-source-code-analysis/.gitignore
@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/springboot-source-code-analysis/.mvn/wrapper/MavenWrapperDownloader.java b/springboot-source-code-analysis/.mvn/wrapper/MavenWrapperDownloader.java
new file mode 100644
index 0000000..a45eb6b
--- /dev/null
+++ b/springboot-source-code-analysis/.mvn/wrapper/MavenWrapperDownloader.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2007-present the original author or authors.
+ *
+ * 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.
+ */
+
+import java.net.*;
+import java.io.*;
+import java.nio.channels.*;
+import java.util.Properties;
+
+public class MavenWrapperDownloader {
+
+ private static final String WRAPPER_VERSION = "0.5.6";
+ /**
+ * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
+ */
+ private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
+ + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
+
+ /**
+ * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
+ * use instead of the default one.
+ */
+ private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
+ ".mvn/wrapper/maven-wrapper.properties";
+
+ /**
+ * Path where the maven-wrapper.jar will be saved to.
+ */
+ private static final String MAVEN_WRAPPER_JAR_PATH =
+ ".mvn/wrapper/maven-wrapper.jar";
+
+ /**
+ * Name of the property which should be used to override the default download url for the wrapper.
+ */
+ private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
+
+ public static void main(String args[]) {
+ System.out.println("- Downloader started");
+ File baseDirectory = new File(args[0]);
+ System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
+
+ // If the maven-wrapper.properties exists, read it and check if it contains a custom
+ // wrapperUrl parameter.
+ File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
+ String url = DEFAULT_DOWNLOAD_URL;
+ if (mavenWrapperPropertyFile.exists()) {
+ FileInputStream mavenWrapperPropertyFileInputStream = null;
+ try {
+ mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
+ Properties mavenWrapperProperties = new Properties();
+ mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
+ url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
+ } catch (IOException e) {
+ System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
+ } finally {
+ try {
+ if (mavenWrapperPropertyFileInputStream != null) {
+ mavenWrapperPropertyFileInputStream.close();
+ }
+ } catch (IOException e) {
+ // Ignore ...
+ }
+ }
+ }
+ System.out.println("- Downloading from: " + url);
+
+ File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
+ if (!outputFile.getParentFile().exists()) {
+ if (!outputFile.getParentFile().mkdirs()) {
+ System.out.println(
+ "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
+ }
+ }
+ System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
+ try {
+ downloadFileFromURL(url, outputFile);
+ System.out.println("Done");
+ System.exit(0);
+ } catch (Throwable e) {
+ System.out.println("- Error downloading");
+ e.printStackTrace();
+ System.exit(1);
+ }
+ }
+
+ private static void downloadFileFromURL(String urlString, File destination) throws Exception {
+ if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
+ String username = System.getenv("MVNW_USERNAME");
+ char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
+ Authenticator.setDefault(new Authenticator() {
+ @Override
+ protected PasswordAuthentication getPasswordAuthentication() {
+ return new PasswordAuthentication(username, password);
+ }
+ });
+ }
+ URL website = new URL(urlString);
+ ReadableByteChannel rbc;
+ rbc = Channels.newChannel(website.openStream());
+ FileOutputStream fos = new FileOutputStream(destination);
+ fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
+ fos.close();
+ rbc.close();
+ }
+
+}
diff --git a/springboot-source-code-analysis/.mvn/wrapper/maven-wrapper.jar b/springboot-source-code-analysis/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 0000000..2cc7d4a
Binary files /dev/null and b/springboot-source-code-analysis/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/springboot-source-code-analysis/.mvn/wrapper/maven-wrapper.properties b/springboot-source-code-analysis/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..ffdc10e
--- /dev/null
+++ b/springboot-source-code-analysis/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,2 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.1/apache-maven-3.8.1-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
diff --git a/springboot-source-code-analysis/mvnw b/springboot-source-code-analysis/mvnw
new file mode 100755
index 0000000..a16b543
--- /dev/null
+++ b/springboot-source-code-analysis/mvnw
@@ -0,0 +1,310 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - 0ドル may be a link to maven's home
+ PRG="0ドル"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*'> /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="`which java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly.">&2
+ echo " We cannot execute $JAVACMD">&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "1ドル" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="1ドル"
+ wdir="1ドル"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "1ドル" ]; then
+ echo "$(tr -s '\n' ' ' < "1ドル")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget> /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget "$jarUrl" -O "$wrapperJarPath"
+ else
+ wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
+ fi
+ elif command -v curl> /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl -o "$wrapperJarPath" "$jarUrl" -f
+ else
+ curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+ fi
+
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaClass=`cygpath --path --windows "$javaClass"`
+ fi
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/springboot-source-code-analysis/mvnw.cmd b/springboot-source-code-analysis/mvnw.cmd
new file mode 100644
index 0000000..c8d4337
--- /dev/null
+++ b/springboot-source-code-analysis/mvnw.cmd
@@ -0,0 +1,182 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment.>&2
+echo Please set the JAVA_HOME variable in your environment to match the>&2
+echo location of your Java installation.>&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory.>&2
+echo JAVA_HOME = "%JAVA_HOME%">&2
+echo Please set the JAVA_HOME variable in your environment to match the>&2
+echo location of your Java installation.>&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+
+FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%
diff --git a/springboot-source-code-analysis/pom.xml b/springboot-source-code-analysis/pom.xml
new file mode 100644
index 0000000..ce53e12
--- /dev/null
+++ b/springboot-source-code-analysis/pom.xml
@@ -0,0 +1,50 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.1.7.RELEASE
+
+
+ com.ipman.source.code.analysis.springboot
+ springboot-source-code-analysis
+ 0.0.1-SNAPSHOT
+ springboot-source-code-analysis
+ Demo project for Spring Boot
+
+ 1.8
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ junit
+ junit
+ 4.12
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/SpringbootSourceCodeAnalysisApplication.java b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/SpringbootSourceCodeAnalysisApplication.java
new file mode 100644
index 0000000..af01364
--- /dev/null
+++ b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/SpringbootSourceCodeAnalysisApplication.java
@@ -0,0 +1,24 @@
+package com.example.springboot.source.code.analysis;
+
+import com.example.springboot.source.code.analysis.initializer.SecondInitializer;
+import com.example.springboot.source.code.analysis.listener.SecondListener;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class SpringbootSourceCodeAnalysisApplication {
+
+ public static void main(String[] args) {
+ // SpringApplication.run(SpringbootSourceCodeAnalysisApplication.class, args);
+ SpringApplication springApplication = new SpringApplication(SpringbootSourceCodeAnalysisApplication.class);
+
+ // 手动添加一个框架的初始化器
+ springApplication.addInitializers(new SecondInitializer());
+
+ // 手动添加一个框架事件监听器
+ springApplication.addListeners(new SecondListener());
+
+ springApplication.run();
+
+ }
+}
diff --git a/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/controller/InitializerController.java b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/controller/InitializerController.java
new file mode 100644
index 0000000..98366d8
--- /dev/null
+++ b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/controller/InitializerController.java
@@ -0,0 +1,54 @@
+package com.example.springboot.source.code.analysis.controller;
+
+import com.example.springboot.source.code.analysis.service.TestFirstInitializerService;
+import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * InitializerController 测试框架初始化器 ApplicationContextInitializer
+ *
+ * @author ipipman
+ * @version V1.0
+ * @date 2021年6月5日
+ * @date 2021年6月5日 8:45 下午
+ */
+@RequestMapping("/initializer")
+@RestController
+public class InitializerController {
+ /**
+ * 测试框架初始化器 ApplicationContextInitializer
+ */
+ @Autowired
+ private TestFirstInitializerService firstInitializerService;
+
+ /**
+ * 测试框架初始化器 ApplicationContextInitializer
+ * 通过在 META-INF/spring.factories 中配置 ApplicationContextInitializer 的方式
+ */
+ @RequestMapping(value = "first", method = RequestMethod.GET)
+ @ResponseBody
+ public String testFirstInitializer() {
+ return firstInitializerService.getCustomEnvironmentProperty("firstKey");
+ }
+
+ /**
+ * 测试框架初始化器 ApplicationContextInitializer
+ * 通过 springApplication.addInitializers(new SecondInitializer()) 的方式
+ */
+ @RequestMapping(value = "second", method = RequestMethod.GET)
+ @ResponseBody
+ public String testSecondInitializer() {
+ return firstInitializerService.getCustomEnvironmentProperty("secondKey");
+ }
+
+ /**
+ * 测试框架初始化器 ApplicationContextInitializer
+ * 通过在 application.property 中配置 context.initializer.classes 的方式
+ */
+ @RequestMapping(value = "third", method = RequestMethod.GET)
+ @ResponseBody
+ public String testThirdInitializer() {
+ return firstInitializerService.getCustomEnvironmentProperty("thirdKey");
+ }
+}
diff --git a/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/event/AbstractEventMulticaster.java b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/event/AbstractEventMulticaster.java
new file mode 100644
index 0000000..b2a865c
--- /dev/null
+++ b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/event/AbstractEventMulticaster.java
@@ -0,0 +1,55 @@
+package com.example.springboot.source.code.analysis.event;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Created by ipipman on 2021年8月3日.
+ *
+ * @version V1.0
+ * @Package com.example.springboot.source.code.analysis.event
+ * @Description: (用一句话描述该文件做什么)
+ * @date 2021年8月3日 3:20 下午
+ */
+
+
+// 抽象的事件广播器
+@Component
+public abstract class AbstractEventMulticaster implements EventMulticaster {
+
+ // 事件监听器列表
+ @Autowired
+ private List listenerList;
+
+ @Override
+ public void multicastEvent(WeatherEvent event) {
+ // 广播所有事件监听器
+ listenerList.forEach(listener -> {
+ doStart();
+ // 触发
+ listener.onWeatherEvent(event);
+ doEnd();
+ });
+ }
+
+ // 添加事件监听器
+ @Override
+ public void addListener(WeatherListener listener) {
+ listenerList.add(listener);
+ }
+
+ // 删除事件监听器
+ @Override
+ public void removeListener(WeatherListener listener) {
+ listenerList.remove(listener);
+ }
+
+
+ // 模版方法
+ abstract void doStart();
+
+ abstract void doEnd();
+}
diff --git a/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/event/EventMulticaster.java b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/event/EventMulticaster.java
new file mode 100644
index 0000000..e94fc66
--- /dev/null
+++ b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/event/EventMulticaster.java
@@ -0,0 +1,23 @@
+package com.example.springboot.source.code.analysis.event;
+
+/**
+ * Created by ipipman on 2021年8月3日.
+ *
+ * @version V1.0
+ * @Package com.example.springboot.source.code.analysis.event
+ * @Description: (用一句话描述该文件做什么)
+ * @date 2021年8月3日 3:04 下午
+ */
+
+// 事件广播器
+public interface EventMulticaster {
+
+ // 广播事件
+ void multicastEvent(WeatherEvent event);
+
+ // 添加监听器
+ void addListener(WeatherListener listener);
+
+ // 删除监听器
+ void removeListener(WeatherListener listener);
+}
diff --git a/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/event/RainEvent.java b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/event/RainEvent.java
new file mode 100644
index 0000000..a6f127e
--- /dev/null
+++ b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/event/RainEvent.java
@@ -0,0 +1,19 @@
+package com.example.springboot.source.code.analysis.event;
+
+/**
+ * Created by ipipman on 2021年8月3日.
+ *
+ * @version V1.0
+ * @Package com.example.springboot.source.code.analysis.event
+ * @Description: (用一句话描述该文件做什么)
+ * @date 2021年8月3日 2:52 下午
+ */
+
+// 下雨事件
+public class RainEvent extends WeatherEvent {
+
+ @Override
+ public String getWeather() {
+ return "rain";
+ }
+}
diff --git a/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/event/RainListener.java b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/event/RainListener.java
new file mode 100644
index 0000000..37ca02a
--- /dev/null
+++ b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/event/RainListener.java
@@ -0,0 +1,25 @@
+package com.example.springboot.source.code.analysis.event;
+
+import org.springframework.stereotype.Component;
+
+/**
+ * Created by ipipman on 2021年8月3日.
+ *
+ * @version V1.0
+ * @Package com.example.springboot.source.code.analysis.event
+ * @Description: (用一句话描述该文件做什么)
+ * @date 2021年8月3日 3:00 下午
+ */
+
+
+// 下雨监听器
+@Component
+public class RainListener implements WeatherListener {
+
+ @Override
+ public void onWeatherEvent(WeatherEvent event) {
+ if (event instanceof RainEvent) {
+ System.out.println("rain event ..." + event.getWeather());
+ }
+ }
+}
diff --git a/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/event/SnowEvent.java b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/event/SnowEvent.java
new file mode 100644
index 0000000..c12c5ca
--- /dev/null
+++ b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/event/SnowEvent.java
@@ -0,0 +1,19 @@
+package com.example.springboot.source.code.analysis.event;
+
+/**
+ * Created by ipipman on 2021年8月3日.
+ *
+ * @version V1.0
+ * @Package com.example.springboot.source.code.analysis.event
+ * @Description: (用一句话描述该文件做什么)
+ * @date 2021年8月3日 2:51 下午
+ */
+
+// 下雪事件
+public class SnowEvent extends WeatherEvent {
+
+ @Override
+ public String getWeather() {
+ return "snow";
+ }
+}
diff --git a/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/event/SnowListener.java b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/event/SnowListener.java
new file mode 100644
index 0000000..579ee36
--- /dev/null
+++ b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/event/SnowListener.java
@@ -0,0 +1,24 @@
+package com.example.springboot.source.code.analysis.event;
+
+import org.springframework.stereotype.Component;
+
+/**
+ * Created by ipipman on 2021年8月3日.
+ *
+ * @version V1.0
+ * @Package com.example.springboot.source.code.analysis.event
+ * @Description: (用一句话描述该文件做什么)
+ * @date 2021年8月3日 3:02 下午
+ */
+
+// 下雪监听器
+@Component
+public class SnowListener implements WeatherListener {
+
+ @Override
+ public void onWeatherEvent(WeatherEvent event) {
+ if (event instanceof SnowEvent) {
+ System.out.println("snow event.." + event.getWeather());
+ }
+ }
+}
diff --git a/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/event/Test.java b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/event/Test.java
new file mode 100644
index 0000000..0aca9cc
--- /dev/null
+++ b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/event/Test.java
@@ -0,0 +1,32 @@
+package com.example.springboot.source.code.analysis.event;
+
+/**
+ * Created by ipipman on 2021年8月3日.
+ *
+ * @version V1.0
+ * @Package com.example.springboot.source.code.analysis.event
+ * @Description: (用一句话描述该文件做什么)
+ * @date 2021年8月3日 3:29 下午
+ */
+
+
+public class Test {
+
+ public static void main(String[] args) {
+ // 创建事件广播器
+ WeatherEventMulticaster multicaster = new WeatherEventMulticaster();
+
+ // 添加事件监听器
+ WeatherListener listener1 = new SnowListener();
+ WeatherListener listener2 = new RainListener();
+ multicaster.addListener(listener1);
+ multicaster.addListener(listener2);
+
+ // 广播事件
+ WeatherEvent event1 = new RainEvent();
+ WeatherEvent event2 = new SnowEvent();
+ multicaster.multicastEvent(event1);
+ multicaster.multicastEvent(event2);
+ }
+
+}
diff --git a/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/event/WeatherEvent.java b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/event/WeatherEvent.java
new file mode 100644
index 0000000..8238dcc
--- /dev/null
+++ b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/event/WeatherEvent.java
@@ -0,0 +1,19 @@
+package com.example.springboot.source.code.analysis.event;
+
+/**
+ * Created by ipipman on 2021年8月3日.
+ *
+ * @version V1.0
+ * @Package com.example.springboot.source.code.analysis.event
+ * @Description: (用一句话描述该文件做什么)
+ * @date 2021年8月3日 2:50 下午
+ */
+
+
+// 天气事件抽象类
+public abstract class WeatherEvent {
+
+ // 获取当前天气的事件
+ public abstract String getWeather();
+
+}
diff --git a/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/event/WeatherEventMulticaster.java b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/event/WeatherEventMulticaster.java
new file mode 100644
index 0000000..11bbfff
--- /dev/null
+++ b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/event/WeatherEventMulticaster.java
@@ -0,0 +1,27 @@
+package com.example.springboot.source.code.analysis.event;
+
+import org.springframework.stereotype.Component;
+
+/**
+ * Created by ipipman on 2021年8月3日.
+ *
+ * @version V1.0
+ * @Package com.example.springboot.source.code.analysis.event
+ * @Description: (用一句话描述该文件做什么)
+ * @date 2021年8月3日 3:27 下午
+ */
+
+// 天气事件广播器
+@Component
+public class WeatherEventMulticaster extends AbstractEventMulticaster {
+
+ @Override
+ public void doStart() {
+ System.out.println("multicaster event begin ...");
+ }
+
+ @Override
+ public void doEnd() {
+ System.out.println("multicaster evnet end ....");
+ }
+}
diff --git a/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/event/WeatherListener.java b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/event/WeatherListener.java
new file mode 100644
index 0000000..ff237ce
--- /dev/null
+++ b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/event/WeatherListener.java
@@ -0,0 +1,17 @@
+package com.example.springboot.source.code.analysis.event;
+
+/**
+ * Created by ipipman on 2021年8月3日.
+ *
+ * @version V1.0
+ * @Package com.example.springboot.source.code.analysis.event
+ * @Description: (用一句话描述该文件做什么)
+ * @date 2021年8月3日 2:58 下午
+ */
+
+// 天气监听器
+public interface WeatherListener {
+
+ // 监听天气事件
+ void onWeatherEvent(WeatherEvent event);
+}
diff --git a/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/event/WeatherRunListener.java b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/event/WeatherRunListener.java
new file mode 100644
index 0000000..a7a1530
--- /dev/null
+++ b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/event/WeatherRunListener.java
@@ -0,0 +1,35 @@
+package com.example.springboot.source.code.analysis.event;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * Created by ipipman on 2021年8月9日.
+ *
+ * @version V1.0
+ * @Package com.example.springboot.source.code.analysis.event
+ * @Description: (用一句话描述该文件做什么)
+ * @date 2021年8月9日 3:46 下午
+ */
+
+@Component
+public class WeatherRunListener {
+
+ @Autowired
+ private WeatherEventMulticaster weatherEventMulticaster;
+
+ // 下雪事件
+ public void snow(){
+ weatherEventMulticaster.multicastEvent(new SnowEvent());
+ }
+
+ // 下雨事件
+ public void rain(){
+ weatherEventMulticaster.multicastEvent(new RainEvent());
+ }
+
+ // 天津监听器
+ public void addListener(WeatherListener listener){
+ weatherEventMulticaster.addListener(listener);
+ }
+}
diff --git a/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/initializer/FirstInitializer.java b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/initializer/FirstInitializer.java
new file mode 100644
index 0000000..38edbf3
--- /dev/null
+++ b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/initializer/FirstInitializer.java
@@ -0,0 +1,45 @@
+package com.example.springboot.source.code.analysis.initializer;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.core.annotation.Order;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.core.env.MapPropertySource;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * FirstInitializer 系统初始化器
+ *
+ * @author ipipman
+ * @version V1.0
+ * @date 2021年6月5日
+ * @date 2021年6月5日 8:20 下午
+ */
+@Order(1) // 设置执行顺序
+public class FirstInitializer implements ApplicationContextInitializer {
+
+ private static final Logger logger = LoggerFactory.getLogger(FirstInitializer.class);
+
+ @Override
+ public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
+ // 获取系统属性
+ ConfigurableEnvironment environment =
+ configurableApplicationContext.getEnvironment();
+
+ // 设置必填属性
+ environment.setRequiredProperties("app.enviroment");
+
+ // 设置自定义属性
+ Map attributeMap = new HashMap();
+ attributeMap.put("firstKey", "firstValue");
+
+ // 添加自定义属性
+ MapPropertySource mapPropertySource = new MapPropertySource("firstInitializer", attributeMap);
+ environment.getPropertySources().addLast(mapPropertySource);
+ logger.info("设置框架初始化器【ApplicationContextInitializer】成功 : run firstInitializer");
+ }
+}
diff --git a/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/initializer/SecondInitializer.java b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/initializer/SecondInitializer.java
new file mode 100644
index 0000000..149ea8a
--- /dev/null
+++ b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/initializer/SecondInitializer.java
@@ -0,0 +1,42 @@
+package com.example.springboot.source.code.analysis.initializer;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.core.annotation.Order;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.core.env.MapPropertySource;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * FirstInitializer 系统初始化器
+ *
+ * @author ipipman
+ * @version V1.0
+ * @date 2021年6月5日
+ * @date 2021年6月5日 8:20 下午
+ */
+@Order(2) // 设置执行顺序
+public class SecondInitializer implements ApplicationContextInitializer {
+
+ private static final Logger logger = LoggerFactory.getLogger(SecondInitializer.class);
+
+ @Override
+ public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
+ // 获取系统属性
+ ConfigurableEnvironment environment =
+ configurableApplicationContext.getEnvironment();
+
+ // 设置自定义属性
+ Map attributeMap = new HashMap();
+ attributeMap.put("secondKey", "secondValue");
+
+ // 添加自定义属性
+ MapPropertySource mapPropertySource = new MapPropertySource("secondInitializer", attributeMap);
+ environment.getPropertySources().addLast(mapPropertySource);
+ logger.info("设置框架初始化器【ApplicationContextInitializer】成功 : run secondInitializer");
+ }
+}
diff --git a/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/initializer/ThirdInitializer.java b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/initializer/ThirdInitializer.java
new file mode 100644
index 0000000..62f062b
--- /dev/null
+++ b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/initializer/ThirdInitializer.java
@@ -0,0 +1,42 @@
+package com.example.springboot.source.code.analysis.initializer;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.core.annotation.Order;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.core.env.MapPropertySource;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * FirstInitializer 系统初始化器
+ *
+ * @author ipipman
+ * @version V1.0
+ * @date 2021年6月5日
+ * @date 2021年6月5日 8:20 下午
+ */
+@Order(3) // 设置执行顺序
+public class ThirdInitializer implements ApplicationContextInitializer {
+
+ private static final Logger logger = LoggerFactory.getLogger(ThirdInitializer.class);
+
+ @Override
+ public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
+ // 获取系统属性
+ ConfigurableEnvironment environment =
+ configurableApplicationContext.getEnvironment();
+
+ // 设置自定义属性
+ Map attributeMap = new HashMap();
+ attributeMap.put("thirdKey", "thirdValue");
+
+ // 添加自定义属性
+ MapPropertySource mapPropertySource = new MapPropertySource("thirdInitializer", attributeMap);
+ environment.getPropertySources().addLast(mapPropertySource);
+ logger.info("设置框架初始化器【ApplicationContextInitializer】成功 : run thirdInitializer");
+ }
+}
diff --git a/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/ioc/ann/MyBeanConfiguration.java b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/ioc/ann/MyBeanConfiguration.java
new file mode 100644
index 0000000..547e15c
--- /dev/null
+++ b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/ioc/ann/MyBeanConfiguration.java
@@ -0,0 +1,27 @@
+package com.example.springboot.source.code.analysis.ioc.ann;
+
+import com.example.springboot.source.code.analysis.ioc.pojo.Animal;
+import com.example.springboot.source.code.analysis.ioc.pojo.Dog;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Created by ipipman on 2021年10月16日.
+ *
+ * @version V1.0
+ * @Package com.example.springboot.source.code.analysis.ioc.ann
+ * @Description: (通过 @Configuration注解 注入一个Bean)
+ * @date 2021年10月16日 12:04 下午
+ */
+@Configuration
+public class MyBeanConfiguration {
+
+ /**
+ * 通过 @Configuration注解 注入一个Bean
+ */
+ @Bean("dog")
+ public Animal getDog() {
+ return new Dog();
+ }
+
+}
diff --git a/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/ioc/ann/MyBeanDefinitionRegistry.java b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/ioc/ann/MyBeanDefinitionRegistry.java
new file mode 100644
index 0000000..ab84f26
--- /dev/null
+++ b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/ioc/ann/MyBeanDefinitionRegistry.java
@@ -0,0 +1,37 @@
+package com.example.springboot.source.code.analysis.ioc.ann;
+
+import com.example.springboot.source.code.analysis.ioc.pojo.Monkey;
+import org.springframework.beans.BeansException;
+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.stereotype.Component;
+import org.springframework.stereotype.Service;
+
+/**
+ * Created by ipipman on 2021年10月16日.
+ *
+ * @version V1.0
+ * @Package com.example.springboot.source.code.analysis.ioc.ann
+ * @Description: (通过BeanDefinitionRegistryPostProcessor 进行Bean的注入)
+ * @date 2021年10月16日 3:18 下午
+ */
+@Component
+public class MyBeanDefinitionRegistry implements BeanDefinitionRegistryPostProcessor {
+
+ /**
+ * 通过BeanDefinitionRegistryPostProcessor
+ */
+ @Override
+ public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
+ RootBeanDefinition rootBeanDefinition = new RootBeanDefinition();
+ rootBeanDefinition.setBeanClass(Monkey.class);
+ beanDefinitionRegistry.registerBeanDefinition("monkey", rootBeanDefinition);
+ }
+
+ @Override
+ public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
+
+ }
+}
diff --git a/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/ioc/ann/MyBeanFacotoryPostProcessor.java b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/ioc/ann/MyBeanFacotoryPostProcessor.java
new file mode 100644
index 0000000..a08d25e
--- /dev/null
+++ b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/ioc/ann/MyBeanFacotoryPostProcessor.java
@@ -0,0 +1,32 @@
+package com.example.springboot.source.code.analysis.ioc.ann;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.MutablePropertyValues;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.beans.factory.support.RootBeanDefinition;
+import org.springframework.stereotype.Component;
+
+/**
+ * Created by ipipman on 2021年10月17日.
+ *
+ * @version V1.0
+ * @Package com.example.springboot.source.code.analysis.ioc.ann
+ * @Description: (用一句话描述该文件做什么)
+ * @date 2021年10月17日 6:47 下午
+ */
+
+// 调用时机:在BeanFactory标准初始化之后调用,这时所有的Bean定义已经保存加载到BeanFactory,但是Bean的实例还未创建
+// 作用:来定制和修改BeanFactory内容,如覆盖或添加属性
+@Component
+public class MyBeanFacotoryPostProcessor implements BeanFactoryPostProcessor {
+
+ @Override
+ public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
+ // 给DemoBean添加属性
+ BeanDefinition beanDemo = configurableListableBeanFactory.getBeanDefinition("demoBean");
+ MutablePropertyValues propertyValues = beanDemo.getPropertyValues();
+ propertyValues.addPropertyValue("name", "ipman");
+ }
+}
diff --git a/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/ioc/ann/MyFactoryBean.java b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/ioc/ann/MyFactoryBean.java
new file mode 100644
index 0000000..486a909
--- /dev/null
+++ b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/ioc/ann/MyFactoryBean.java
@@ -0,0 +1,41 @@
+package com.example.springboot.source.code.analysis.ioc.ann;
+
+import com.example.springboot.source.code.analysis.ioc.pojo.Animal;
+import com.example.springboot.source.code.analysis.ioc.pojo.Cat;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.stereotype.Component;
+
+/**
+ * Created by ipipman on 2021年10月16日.
+ *
+ * @version V1.0
+ * @Package com.example.springboot.source.code.analysis.ioc.ann
+ * @Description: (通过实现 FactoryBean < ?> 接口实现Bean的注入)
+ * @date 2021年10月16日 12:15 下午
+ */
+@Component
+public class MyFactoryBean implements FactoryBean {
+
+ /**
+ * 返回要注入的类
+ */
+ @Override
+ public Animal getObject() throws Exception {
+ return new Cat();
+ }
+
+ /**
+ * 获取要注入类的类型
+ */
+ @Override
+ public Class> getObjectType() {
+ return Animal.class;
+ }
+
+ @Override
+ public boolean isSingleton() {
+ return FactoryBean.super.isSingleton();
+ }
+
+}
diff --git a/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/ioc/ann/MyImportBeanDefinitionRegistrar.java b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/ioc/ann/MyImportBeanDefinitionRegistrar.java
new file mode 100644
index 0000000..f04aca8
--- /dev/null
+++ b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/ioc/ann/MyImportBeanDefinitionRegistrar.java
@@ -0,0 +1,28 @@
+package com.example.springboot.source.code.analysis.ioc.ann;
+
+import com.example.springboot.source.code.analysis.ioc.pojo.Bird;
+import org.springframework.beans.factory.support.BeanDefinitionRegistry;
+import org.springframework.beans.factory.support.RootBeanDefinition;
+import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
+import org.springframework.core.type.AnnotationMetadata;
+
+/**
+ * Created by ipipman on 2021年10月16日.
+ *
+ * @version V1.0
+ * @Package com.example.springboot.source.code.analysis.ioc.ann
+ * @Description: (通过ImportBeanDefinitionRegistrar注入Bean)
+ * @date 2021年10月16日 3:32 下午
+ */
+public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
+
+ /**
+ * 通过ImportBeanDefinitionRegistrar注入Bean
+ */
+ @Override
+ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
+ RootBeanDefinition rootBeanDefinition = new RootBeanDefinition();
+ rootBeanDefinition.setBeanClass(Bird.class);
+ registry.registerBeanDefinition("bird", rootBeanDefinition);
+ }
+}
diff --git a/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/ioc/pojo/Animal.java b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/ioc/pojo/Animal.java
new file mode 100644
index 0000000..74d5d78
--- /dev/null
+++ b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/ioc/pojo/Animal.java
@@ -0,0 +1,14 @@
+package com.example.springboot.source.code.analysis.ioc.pojo;
+
+/**
+ * Created by ipipman on 2021年10月16日.
+ *
+ * @version V1.0
+ * @Package com.example.springboot.source.code.analysis.ioc.pojo
+ * @Description: (用一句话描述该文件做什么)
+ * @date 2021年10月16日 12:06 下午
+ */
+public abstract class Animal {
+
+ public abstract String getName();
+}
diff --git a/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/ioc/pojo/Bird.java b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/ioc/pojo/Bird.java
new file mode 100644
index 0000000..e65bf5e
--- /dev/null
+++ b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/ioc/pojo/Bird.java
@@ -0,0 +1,16 @@
+package com.example.springboot.source.code.analysis.ioc.pojo;
+
+/**
+ * Created by ipipman on 2021年10月16日.
+ *
+ * @version V1.0
+ * @Package com.example.springboot.source.code.analysis.ioc.pojo
+ * @Description: (用一句话描述该文件做什么)
+ * @date 2021年10月16日 3:35 下午
+ */
+public class Bird extends Animal{
+ @Override
+ public String getName() {
+ return "Bird";
+ }
+}
diff --git a/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/ioc/pojo/Cat.java b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/ioc/pojo/Cat.java
new file mode 100644
index 0000000..79bb1ab
--- /dev/null
+++ b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/ioc/pojo/Cat.java
@@ -0,0 +1,16 @@
+package com.example.springboot.source.code.analysis.ioc.pojo;
+
+/**
+ * Created by ipipman on 2021年10月16日.
+ *
+ * @version V1.0
+ * @Package com.example.springboot.source.code.analysis.ioc.pojo
+ * @Description: (用一句话描述该文件做什么)
+ * @date 2021年10月16日 12:09 下午
+ */
+public class Cat extends Animal{
+ @Override
+ public String getName() {
+ return "Cat";
+ }
+}
diff --git a/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/ioc/pojo/DemoBean.java b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/ioc/pojo/DemoBean.java
new file mode 100644
index 0000000..72f65ee
--- /dev/null
+++ b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/ioc/pojo/DemoBean.java
@@ -0,0 +1,27 @@
+package com.example.springboot.source.code.analysis.ioc.pojo;
+
+import org.springframework.stereotype.Component;
+
+/**
+ * Created by ipipman on 2021年10月17日.
+ *
+ * @version V1.0
+ * @Package com.example.springboot.source.code.analysis.ioc.pojo
+ * @Description: (用一句话描述该文件做什么)
+ * @date 2021年10月17日 6:53 下午
+ */
+@Component
+public class DemoBean {
+
+ private String name;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+
+}
diff --git a/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/ioc/pojo/Dog.java b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/ioc/pojo/Dog.java
new file mode 100644
index 0000000..5dcd836
--- /dev/null
+++ b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/ioc/pojo/Dog.java
@@ -0,0 +1,16 @@
+package com.example.springboot.source.code.analysis.ioc.pojo;
+
+/**
+ * Created by ipipman on 2021年10月16日.
+ *
+ * @version V1.0
+ * @Package com.example.springboot.source.code.analysis.ioc.pojo
+ * @Description: (用一句话描述该文件做什么)
+ * @date 2021年10月16日 12:08 下午
+ */
+public class Dog extends Animal{
+ @Override
+ public String getName() {
+ return "Dog";
+ }
+}
diff --git a/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/ioc/pojo/Monkey.java b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/ioc/pojo/Monkey.java
new file mode 100644
index 0000000..7e922ad
--- /dev/null
+++ b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/ioc/pojo/Monkey.java
@@ -0,0 +1,16 @@
+package com.example.springboot.source.code.analysis.ioc.pojo;
+
+/**
+ * Created by ipipman on 2021年10月16日.
+ *
+ * @version V1.0
+ * @Package com.example.springboot.source.code.analysis.ioc.pojo
+ * @Description: (用一句话描述该文件做什么)
+ * @date 2021年10月16日 12:25 下午
+ */
+public class Monkey extends Animal {
+ @Override
+ public String getName() {
+ return "Monkey";
+ }
+}
diff --git a/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/ioc/service/HelloService.java b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/ioc/service/HelloService.java
new file mode 100644
index 0000000..ec6f7d0
--- /dev/null
+++ b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/ioc/service/HelloService.java
@@ -0,0 +1,37 @@
+package com.example.springboot.source.code.analysis.ioc.service;
+
+import com.example.springboot.source.code.analysis.ioc.pojo.Animal;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Component;
+
+import javax.swing.event.AncestorEvent;
+
+/**
+ * Created by ipipman on 2021年10月16日.
+ *
+ * @version V1.0
+ * @Package com.example.springboot.source.code.analysis.ioc.service
+ * @Description: (用一句话描述该文件做什么)
+ * @date 2021年10月16日 12:10 下午
+ */
+
+@Component
+public class HelloService {
+
+ /**
+ * animal
+ */
+ @Autowired
+ // 如果有多个同类型的Bean,那么用@Qualifier指定Bean的名称后进行注入
+ //@Qulifier("dog") // 通过@Configuration注解进行Bean的注入
+ //@Qualifier("myFactoryBean") // 通过FacotryBean>接口进行Bean的注入
+ //@Qualifier("monkey") // 通过BeanDefinitionRegistryPostProcessor接口进行Bean的注入
+ @Qualifier("bird") // 通过ImpoortBeanDefinitionRegistrar接口进行Bean的注入
+ private Animal animal;
+
+
+ public String hello() {
+ return animal.getName();
+ }
+}
diff --git a/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/listener/ApplicationContextContainer.java b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/listener/ApplicationContextContainer.java
new file mode 100644
index 0000000..adaea1d
--- /dev/null
+++ b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/listener/ApplicationContextContainer.java
@@ -0,0 +1,45 @@
+package com.example.springboot.source.code.analysis.listener;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.event.ContextRefreshedEvent;
+import org.springframework.core.annotation.Order;
+import org.springframework.lang.Nullable;
+import org.springframework.stereotype.Component;
+
+/**
+ * Created by ipipman on 2021年9月21日.
+ *
+ * @version V1.0
+ * @Package com.example.springboot.source.code.analysis.listener
+ * @Description: (用一句话描述该文件做什么)
+ * @date 2021年9月21日 5:49 下午
+ */
+@Component
+@Order(1)
+public class ApplicationContextContainer implements ApplicationListener {
+
+ private ApplicationContext applicationContext;
+
+ private static ApplicationContextContainer applicationContextContainer;
+
+ @Override
+ public void onApplicationEvent(@Nullable ContextRefreshedEvent event) {
+ if (event != null && applicationContext == null) {
+ synchronized (ApplicationContextContainer.class) {
+ if (applicationContext == null) {
+ applicationContext = event.getApplicationContext();
+ }
+ }
+ }
+ applicationContextContainer = this;
+ }
+
+ public static ApplicationContextContainer getInstance() {
+ return applicationContextContainer;
+ }
+
+ public T getBean(Class clazz) {
+ return applicationContext.getBean(clazz);
+ }
+}
diff --git a/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/listener/FirstListener.java b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/listener/FirstListener.java
new file mode 100644
index 0000000..29be824
--- /dev/null
+++ b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/listener/FirstListener.java
@@ -0,0 +1,28 @@
+package com.example.springboot.source.code.analysis.listener;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.context.event.ApplicationStartedEvent;
+import org.springframework.context.ApplicationListener;
+import org.springframework.core.annotation.Order;
+
+/**
+ * Created by ipipman on 2021年9月21日.
+ *
+ * @version V1.0
+ * @Package com.example.springboot.source.code.analysis.listener
+ * @Description: (用一句话描述该文件做什么)
+ * @date 2021年9月21日 4:12 下午
+ */
+
+@Order(1)
+public class FirstListener implements ApplicationListener {
+
+ private final static Logger logger = LoggerFactory.getLogger(FirstListener.class);
+
+ @Override
+ public void onApplicationEvent(ApplicationStartedEvent event) {
+ logger.info("设置框架事件监听器【ApplicationListener】成功 : run firstListener");
+
+ }
+}
diff --git a/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/listener/FourthListener.java b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/listener/FourthListener.java
new file mode 100644
index 0000000..6b8eaba
--- /dev/null
+++ b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/listener/FourthListener.java
@@ -0,0 +1,34 @@
+package com.example.springboot.source.code.analysis.listener;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.context.event.ApplicationPreparedEvent;
+import org.springframework.boot.context.event.ApplicationStartedEvent;
+import org.springframework.context.ApplicationEvent;
+import org.springframework.context.event.SmartApplicationListener;
+import org.springframework.core.annotation.Order;
+
+/**
+ * Created by ipipman on 2021年9月21日.
+ *
+ * @version V1.0
+ * @Package com.example.springboot.source.code.analysis.listener
+ * @Description: (用一句话描述该文件做什么)
+ * @date 2021年9月21日 4:26 下午
+ */
+@Order(4)
+public class FourthListener implements SmartApplicationListener {
+
+ private final static Logger logger = LoggerFactory.getLogger(FourthListener.class);
+
+ @Override
+ public boolean supportsEventType(Class extends ApplicationEvent> eventType) {
+ return ApplicationStartedEvent.class.isAssignableFrom(eventType)
+ || ApplicationPreparedEvent.class.isAssignableFrom(eventType);
+ }
+
+ @Override
+ public void onApplicationEvent(ApplicationEvent event) {
+ logger.info("设置框架事件监听器【SmartApplicationListener】成功 : run fourthListener");
+ }
+}
diff --git a/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/listener/SecondListener.java b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/listener/SecondListener.java
new file mode 100644
index 0000000..dd54f03
--- /dev/null
+++ b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/listener/SecondListener.java
@@ -0,0 +1,26 @@
+package com.example.springboot.source.code.analysis.listener;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.context.event.ApplicationStartedEvent;
+import org.springframework.context.ApplicationListener;
+import org.springframework.core.annotation.Order;
+
+/**
+ * Created by ipipman on 2021年9月21日.
+ *
+ * @version V1.0
+ * @Package com.example.springboot.source.code.analysis.listener
+ * @Description: (用一句话描述该文件做什么)
+ * @date 2021年9月21日 4:19 下午
+ */
+@Order(2)
+public class SecondListener implements ApplicationListener {
+
+ private static final Logger logger = LoggerFactory.getLogger(SecondListener.class);
+
+ @Override
+ public void onApplicationEvent(ApplicationStartedEvent event) {
+ logger.info("设置框架事件监听器【ApplicationListener】成功 : run secondListener");
+ }
+}
diff --git a/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/listener/ThirdListener.java b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/listener/ThirdListener.java
new file mode 100644
index 0000000..1c69a4b
--- /dev/null
+++ b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/listener/ThirdListener.java
@@ -0,0 +1,27 @@
+package com.example.springboot.source.code.analysis.listener;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.context.event.ApplicationStartedEvent;
+import org.springframework.context.ApplicationListener;
+import org.springframework.core.annotation.Order;
+
+
+/**
+ * Created by ipipman on 2021年9月21日.
+ *
+ * @version V1.0
+ * @Package com.example.springboot.source.code.analysis.listener
+ * @Description: (用一句话描述该文件做什么)
+ * @date 2021年9月21日 4:21 下午
+ */
+@Order(3)
+public class ThirdListener implements ApplicationListener {
+
+ private final static Logger logger = LoggerFactory.getLogger(ThirdListener.class);
+
+ @Override
+ public void onApplicationEvent(ApplicationStartedEvent event) {
+ logger.info("设置框架事件监听器【ApplicationListener】成功 : run thirdListener");
+ }
+}
diff --git a/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/service/TestFirstInitializerService.java b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/service/TestFirstInitializerService.java
new file mode 100644
index 0000000..1a62f1b
--- /dev/null
+++ b/springboot-source-code-analysis/src/main/java/com/example/springboot/source/code/analysis/service/TestFirstInitializerService.java
@@ -0,0 +1,45 @@
+package com.example.springboot.source.code.analysis.service;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.lang.Nullable;
+import org.springframework.stereotype.Component;
+
+import java.util.Optional;
+
+/**
+ * 测试框架初始化器,获取自定义的环境属性
+ *
+ * @author ipipman
+ * @version V1.0
+ * @date 2021年6月5日
+ * @date 2021年6月5日 8:35 下午
+ */
+@Component
+public class TestFirstInitializerService implements ApplicationContextAware {
+
+ private static final Logger logger = LoggerFactory.getLogger(TestFirstInitializerService.class);
+
+ /**
+ * 框架应用上下文
+ */
+ private ApplicationContext applicationContext;
+
+ /**
+ * 获取当前框架上下文
+ */
+ @Override
+ public void setApplicationContext(@Nullable ApplicationContext applicationContext) throws BeansException {
+ this.applicationContext = applicationContext;
+ }
+
+ /**
+ * 获取在框架初始化器阶段设置的配置信息
+ */
+ public String getCustomEnvironmentProperty(final String propertyKey) {
+ return Optional.ofNullable(applicationContext.getEnvironment().getProperty(propertyKey)).orElse(null);
+ }
+}
diff --git a/springboot-source-code-analysis/src/main/resources/META-INF/spring.factories b/springboot-source-code-analysis/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000..b386fd2
--- /dev/null
+++ b/springboot-source-code-analysis/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,8 @@
+# 通过系统初始化器,设置自定义环境属性
+org.springframework.context.ApplicationContextInitializer=\
+ com.example.springboot.source.code.analysis.initializer.FirstInitializer
+
+
+# 通过系统事件监听器,设置自定义事件监听器
+org.springframework.context.ApplicationListener=\
+ com.example.springboot.source.code.analysis.listener.FirstListener
\ No newline at end of file
diff --git a/springboot-source-code-analysis/src/main/resources/application.properties b/springboot-source-code-analysis/src/main/resources/application.properties
new file mode 100644
index 0000000..856742a
--- /dev/null
+++ b/springboot-source-code-analysis/src/main/resources/application.properties
@@ -0,0 +1,12 @@
+# 通过系统初始化器,设置环境属性
+context.initializer.classes=\
+ com.example.springboot.source.code.analysis.initializer.ThirdInitializer
+
+# 通过系统事件监听器,监听事件
+context.listener.classes=\
+ com.example.springboot.source.code.analysis.listener.ThirdListener,\
+ com.example.springboot.source.code.analysis.listener.FourthListener
+
+
+# 通过ConfigurableEnviroment.requiredProperty()方法设置的必填属性
+app.enviroment=ipman
\ No newline at end of file
diff --git a/springboot-source-code-analysis/src/test/java/com/example/springboot/source/code/analysis/SpringbootSourceCodeAnalysisApplicationTests.java b/springboot-source-code-analysis/src/test/java/com/example/springboot/source/code/analysis/SpringbootSourceCodeAnalysisApplicationTests.java
new file mode 100644
index 0000000..b78658e
--- /dev/null
+++ b/springboot-source-code-analysis/src/test/java/com/example/springboot/source/code/analysis/SpringbootSourceCodeAnalysisApplicationTests.java
@@ -0,0 +1,68 @@
+package com.example.springboot.source.code.analysis;
+
+import com.example.springboot.source.code.analysis.event.RainListener;
+import com.example.springboot.source.code.analysis.event.WeatherRunListener;
+import com.example.springboot.source.code.analysis.ioc.ann.MyImportBeanDefinitionRegistrar;
+import com.example.springboot.source.code.analysis.ioc.pojo.DemoBean;
+import com.example.springboot.source.code.analysis.ioc.service.HelloService;
+import com.example.springboot.source.code.analysis.listener.ApplicationContextContainer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+@SpringBootTest(classes = SpringbootSourceCodeAnalysisApplication.class)
+@RunWith(SpringJUnit4ClassRunner.class)
+// 通过XML注入Bean
+//@ContextConfiguration(locations = "classpath:ioc/demo.xml")
+// 通过ImportBeanDefinitionRegistrar进行Bean的注入
+@Import(MyImportBeanDefinitionRegistrar.class)
+public class SpringbootSourceCodeAnalysisApplicationTests {
+
+ // 引入事件监听器
+ @Autowired
+ private WeatherRunListener weatherRunListener;
+
+ // 测试事件监听器
+ @Test
+ public void testEvent() {
+ weatherRunListener.addListener(new RainListener());
+ weatherRunListener.snow();
+ weatherRunListener.rain();
+ }
+
+ // 测试ContextRefreshEvent事件
+ @Test
+ public void testContextRefreshEvent() {
+ WeatherRunListener weatherRunListener = ApplicationContextContainer.getInstance().getBean(WeatherRunListener.class);
+ weatherRunListener.snow();
+ weatherRunListener.rain();
+ }
+
+
+ @Autowired
+ HelloService helloService;
+
+ @Test
+ public void testHello() {
+ System.out.println(helloService.hello());
+ }
+
+
+ @Test
+ public void contextLoads() {
+ }
+
+
+ @Autowired
+ DemoBean demoBean;
+
+ @Test
+ public void sayDemoBean(){
+ System.out.println(demoBean.getName());
+ }
+
+
+}
diff --git a/springboot-swagger-sample/.gitignore b/springboot-swagger-sample/.gitignore
new file mode 100644
index 0000000..549e00a
--- /dev/null
+++ b/springboot-swagger-sample/.gitignore
@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/springboot-swagger-sample/.mvn/wrapper/MavenWrapperDownloader.java b/springboot-swagger-sample/.mvn/wrapper/MavenWrapperDownloader.java
new file mode 100644
index 0000000..a45eb6b
--- /dev/null
+++ b/springboot-swagger-sample/.mvn/wrapper/MavenWrapperDownloader.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2007-present the original author or authors.
+ *
+ * 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.
+ */
+
+import java.net.*;
+import java.io.*;
+import java.nio.channels.*;
+import java.util.Properties;
+
+public class MavenWrapperDownloader {
+
+ private static final String WRAPPER_VERSION = "0.5.6";
+ /**
+ * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
+ */
+ private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
+ + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
+
+ /**
+ * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
+ * use instead of the default one.
+ */
+ private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
+ ".mvn/wrapper/maven-wrapper.properties";
+
+ /**
+ * Path where the maven-wrapper.jar will be saved to.
+ */
+ private static final String MAVEN_WRAPPER_JAR_PATH =
+ ".mvn/wrapper/maven-wrapper.jar";
+
+ /**
+ * Name of the property which should be used to override the default download url for the wrapper.
+ */
+ private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
+
+ public static void main(String args[]) {
+ System.out.println("- Downloader started");
+ File baseDirectory = new File(args[0]);
+ System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
+
+ // If the maven-wrapper.properties exists, read it and check if it contains a custom
+ // wrapperUrl parameter.
+ File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
+ String url = DEFAULT_DOWNLOAD_URL;
+ if (mavenWrapperPropertyFile.exists()) {
+ FileInputStream mavenWrapperPropertyFileInputStream = null;
+ try {
+ mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
+ Properties mavenWrapperProperties = new Properties();
+ mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
+ url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
+ } catch (IOException e) {
+ System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
+ } finally {
+ try {
+ if (mavenWrapperPropertyFileInputStream != null) {
+ mavenWrapperPropertyFileInputStream.close();
+ }
+ } catch (IOException e) {
+ // Ignore ...
+ }
+ }
+ }
+ System.out.println("- Downloading from: " + url);
+
+ File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
+ if (!outputFile.getParentFile().exists()) {
+ if (!outputFile.getParentFile().mkdirs()) {
+ System.out.println(
+ "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
+ }
+ }
+ System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
+ try {
+ downloadFileFromURL(url, outputFile);
+ System.out.println("Done");
+ System.exit(0);
+ } catch (Throwable e) {
+ System.out.println("- Error downloading");
+ e.printStackTrace();
+ System.exit(1);
+ }
+ }
+
+ private static void downloadFileFromURL(String urlString, File destination) throws Exception {
+ if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
+ String username = System.getenv("MVNW_USERNAME");
+ char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
+ Authenticator.setDefault(new Authenticator() {
+ @Override
+ protected PasswordAuthentication getPasswordAuthentication() {
+ return new PasswordAuthentication(username, password);
+ }
+ });
+ }
+ URL website = new URL(urlString);
+ ReadableByteChannel rbc;
+ rbc = Channels.newChannel(website.openStream());
+ FileOutputStream fos = new FileOutputStream(destination);
+ fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
+ fos.close();
+ rbc.close();
+ }
+
+}
diff --git a/springboot-swagger-sample/.mvn/wrapper/maven-wrapper.jar b/springboot-swagger-sample/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 0000000..2cc7d4a
Binary files /dev/null and b/springboot-swagger-sample/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/springboot-swagger-sample/.mvn/wrapper/maven-wrapper.properties b/springboot-swagger-sample/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..abd303b
--- /dev/null
+++ b/springboot-swagger-sample/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,2 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.2/apache-maven-3.8.2-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
diff --git a/springboot-swagger-sample/mvnw b/springboot-swagger-sample/mvnw
new file mode 100755
index 0000000..a16b543
--- /dev/null
+++ b/springboot-swagger-sample/mvnw
@@ -0,0 +1,310 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - 0ドル may be a link to maven's home
+ PRG="0ドル"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*'> /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="`which java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly.">&2
+ echo " We cannot execute $JAVACMD">&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "1ドル" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="1ドル"
+ wdir="1ドル"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "1ドル" ]; then
+ echo "$(tr -s '\n' ' ' < "1ドル")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget> /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget "$jarUrl" -O "$wrapperJarPath"
+ else
+ wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
+ fi
+ elif command -v curl> /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl -o "$wrapperJarPath" "$jarUrl" -f
+ else
+ curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+ fi
+
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaClass=`cygpath --path --windows "$javaClass"`
+ fi
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/springboot-swagger-sample/mvnw.cmd b/springboot-swagger-sample/mvnw.cmd
new file mode 100644
index 0000000..c8d4337
--- /dev/null
+++ b/springboot-swagger-sample/mvnw.cmd
@@ -0,0 +1,182 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment.>&2
+echo Please set the JAVA_HOME variable in your environment to match the>&2
+echo location of your Java installation.>&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory.>&2
+echo JAVA_HOME = "%JAVA_HOME%">&2
+echo Please set the JAVA_HOME variable in your environment to match the>&2
+echo location of your Java installation.>&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+
+FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%
diff --git a/springboot-swagger-sample/pom.xml b/springboot-swagger-sample/pom.xml
new file mode 100644
index 0000000..ef7743a
--- /dev/null
+++ b/springboot-swagger-sample/pom.xml
@@ -0,0 +1,81 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.5.4
+
+
+ com.ipman.swagger.sample
+ springboot-swagger-sample
+ 0.0.1-SNAPSHOT
+ springboot-swagger-sample
+ Demo project for Spring Boot
+
+ 1.8
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
+ org.springframework.boot
+ spring-boot-devtools
+ runtime
+ true
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ io.springfox
+ springfox-swagger2
+ 2.9.2
+
+
+
+ io.springfox
+ springfox-swagger-ui
+ 2.9.2
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.projectlombok
+ lombok
+ 1.18.2
+ provided
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+ com.ipman.swagger.sample.SpringbootSwaggerSampleApplication
+
+
+
+
+
+
diff --git a/springboot-swagger-sample/readme.md b/springboot-swagger-sample/readme.md
new file mode 100644
index 0000000..056622f
--- /dev/null
+++ b/springboot-swagger-sample/readme.md
@@ -0,0 +1,143 @@
+### SpringBoot集成Swagger用例
+
+
+
+#### 什么是 Swagger 2
+
+**Swagger 2** 是一个开源软件框架,可以帮助开发人员设计、构建、记录和使用 **RESTful Web** 服务,它将代码和文档融为一体,可以完美解决文档编写繁琐、维护不方便等问题。使得开发人员可以将大部分精力集中到业务中,而不是繁杂琐碎的文档中。
+
+
+
+ #### 在Maven工程中引入Swagger和Swagger UI
+
+```java
+
+ io.springfox
+ springfox-swagger2
+ 2.9.2
+
+
+
+ io.springfox
+ springfox-swagger-ui
+ 2.9.2
+
+```
+
+
+
+#### 配置Swagger Bean
+
+```java
+@Configuration
+@EnableSwagger2
+public class SwaggerConfig {
+
+ /**
+ * 注入SwaggerDocketBean
+ */
+ @Bean
+ public Docket docket() {
+ return new Docket(DocumentationType.SWAGGER_2)
+ .apiInfo(apiInfo())
+ .select()
+ .apis(RequestHandlerSelectors.basePackage("com.ipman.swagger.sample.controller"))
+ .paths(PathSelectors.any())
+ .build();
+ }
+
+ /**
+ * 配置Swagger文档说明
+ */
+ private ApiInfo apiInfo() {
+ return new ApiInfoBuilder()
+ .title("API测试文档")
+ .description("DEMO项目的接口测试文档")
+ .termsOfServiceUrl("http://www.hangge.com")
+ .version("1.0")
+ .contact(new Contact("ipman",
+ "http://www.ipman.com",
+ "ipipman@163.com"))
+ .build();
+ }
+}
+```
+
+
+
+#### 编写Controller,配置Swagger接口文档说明
+
+```java
+@RestController
+@Api(tags = "用户数据接口") // @Api 注解标注在类上用来描述整个 Controller 信息。
+public class UserController {
+
+ /**
+ * @ApiOperation 注解标注在方法上,用来描述一个方法的基本信息。
+ */
+ @ApiOperation(value = "修改用户", notes = "传入用户信息进行更新修改")
+ @PutMapping("/user")
+ public String updateUser(@RequestBody User user){
+ return user.toString();
+ }
+
+ /**
+ * @ApiImplicitParam 注解标注在方法上,用来描述方法的参数。其中 paramType 是指方法参数的类型,有如下可选值:
+ * path:参数获取方式为 @PathVariable
+ * query:参数获取方式为 @RequestParam
+ * header:参数获取方式为 @RequestHeader
+ * body
+ * form
+ */
+ @ApiOperation(value ="查询用户", notes = "根据 id 查询用户")
+ @ApiImplicitParam(paramType = "path", name = "id", value = "用户 id", required = true)
+ @GetMapping("/user/{id}")
+ public String getUserById(@PathVariable Integer id){
+ return "查找的用户id是:" + id;
+ }
+
+ /**
+ * 如果有多个参数,可以将多个参数的 @ApiImplicitParam 注解放到 @ApiImplicitParams 中
+ */
+ @ApiOperation(value = "新增用户", notes = "根据传入的用户名和密码添加一个新用户")
+ @ApiImplicitParams({
+ @ApiImplicitParam(paramType = "query", name = "username",
+ value = "用户名", required = true, defaultValue = "test"),
+ @ApiImplicitParam(paramType = "query", name = "password",
+ value = "密码", required = true, defaultValue = "123")
+ })
+ @PostMapping("/user")
+ public String addUser(@RequestParam String username, @RequestParam String password) {
+ return "新增用户:" + username + " " + password;
+ }
+
+ /**
+ * @ApiResponse 是对响应结果的描述。code 表示响应码,message 为相应的描述信息。如果有多个 @ApiResponse,则放在一个 @ApiResponses 中
+ */
+ @ApiOperation(value = "删除用户", notes = "根据 id 删除用户")
+ @ApiResponses({
+ @ApiResponse(code = 200, message = "删除成功!"),
+ @ApiResponse(code = 500, message = "删除失败!")
+ })
+ @DeleteMapping("/user/{id}")
+ public Integer deleteUserById(@PathVariable Integer id) {
+ return id;
+ }
+
+ /**
+ * @ApiIgnore 注解表示不对某个接口生成文档。
+ */
+ @ApiIgnore
+ @GetMapping("/user/test")
+ public String test() {
+ return "这是一个测试接口,不需要在api文档中显示。";
+ }
+}
+```
+
+
+
+#### 启动项目,访问Swagger-UI:http://127.0.0.1:8080/swagger-ui.html#/%E7%94%A8%E6%88%B7%E6%95%B0%E6%8D%AE%E6%8E%A5%E5%8F%A3
+
+image-20210922203731622
+
diff --git a/springboot-swagger-sample/src/main/java/com/ipman/swagger/sample/SpringbootSwaggerSampleApplication.java b/springboot-swagger-sample/src/main/java/com/ipman/swagger/sample/SpringbootSwaggerSampleApplication.java
new file mode 100644
index 0000000..fae8037
--- /dev/null
+++ b/springboot-swagger-sample/src/main/java/com/ipman/swagger/sample/SpringbootSwaggerSampleApplication.java
@@ -0,0 +1,13 @@
+package com.ipman.swagger.sample;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class SpringbootSwaggerSampleApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SpringbootSwaggerSampleApplication.class, args);
+ }
+
+}
diff --git a/springboot-swagger-sample/src/main/java/com/ipman/swagger/sample/config/SwaggerConfig.java b/springboot-swagger-sample/src/main/java/com/ipman/swagger/sample/config/SwaggerConfig.java
new file mode 100644
index 0000000..d7293da
--- /dev/null
+++ b/springboot-swagger-sample/src/main/java/com/ipman/swagger/sample/config/SwaggerConfig.java
@@ -0,0 +1,53 @@
+package com.ipman.swagger.sample.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.service.ApiInfo;
+import springfox.documentation.service.Contact;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+import springfox.documentation.swagger2.annotations.EnableSwagger2;
+
+/**
+ * @author ipipman
+ * @version V1.0
+ * @date 2021年9月22日
+ * @Package com.ipman.swagger.sample.config
+ * @Description: (Swagger配置)
+ * @date 2021年9月22日 6:17 下午
+ */
+@Configuration
+@EnableSwagger2
+public class SwaggerConfig {
+
+ /**
+ * 注入SwaggerDocketBean
+ */
+ @Bean
+ public Docket docket() {
+ return new Docket(DocumentationType.SWAGGER_2)
+ .apiInfo(apiInfo())
+ .select()
+ .apis(RequestHandlerSelectors.basePackage("com.ipman.swagger.sample.controller"))
+ .paths(PathSelectors.any())
+ .build();
+ }
+
+ /**
+ * 配置Swagger文档说明
+ */
+ private ApiInfo apiInfo() {
+ return new ApiInfoBuilder()
+ .title("API测试文档")
+ .description("DEMO项目的接口测试文档")
+ .termsOfServiceUrl("http://www.hangge.com")
+ .version("1.0")
+ .contact(new Contact("ipman",
+ "http://www.ipman.com",
+ "ipipman@163.com"))
+ .build();
+ }
+}
diff --git a/springboot-swagger-sample/src/main/java/com/ipman/swagger/sample/controller/UserController.java b/springboot-swagger-sample/src/main/java/com/ipman/swagger/sample/controller/UserController.java
new file mode 100644
index 0000000..2b02396
--- /dev/null
+++ b/springboot-swagger-sample/src/main/java/com/ipman/swagger/sample/controller/UserController.java
@@ -0,0 +1,80 @@
+package com.ipman.swagger.sample.controller;
+
+import com.ipman.swagger.sample.entity.User;
+import io.swagger.annotations.*;
+import org.springframework.web.bind.annotation.*;
+import springfox.documentation.annotations.ApiIgnore;
+
+/**
+ * Created by ipipman on 2021年9月22日.
+ *
+ * @version V1.0
+ * @Package com.ipman.swagger.sample.controller
+ * @Description: (用一句话描述该文件做什么)
+ * @date 2021年9月22日 6:29 下午
+ */
+@RestController
+@Api(tags = "用户数据接口") // @Api 注解标注在类上用来描述整个 Controller 信息。
+public class UserController {
+
+ /**
+ * @ApiOperation 注解标注在方法上,用来描述一个方法的基本信息。
+ */
+ @ApiOperation(value = "修改用户", notes = "传入用户信息进行更新修改")
+ @PutMapping("/user")
+ public String updateUser(@RequestBody User user){
+ return user.toString();
+ }
+
+ /**
+ * @ApiImplicitParam 注解标注在方法上,用来描述方法的参数。其中 paramType 是指方法参数的类型,有如下可选值:
+ * path:参数获取方式为 @PathVariable
+ * query:参数获取方式为 @RequestParam
+ * header:参数获取方式为 @RequestHeader
+ * body
+ * form
+ */
+ @ApiOperation(value ="查询用户", notes = "根据 id 查询用户")
+ @ApiImplicitParam(paramType = "path", name = "id", value = "用户 id", required = true)
+ @GetMapping("/user/{id}")
+ public String getUserById(@PathVariable Integer id){
+ return "查找的用户id是:" + id;
+ }
+
+ /**
+ * 如果有多个参数,可以将多个参数的 @ApiImplicitParam 注解放到 @ApiImplicitParams 中
+ */
+ @ApiOperation(value = "新增用户", notes = "根据传入的用户名和密码添加一个新用户")
+ @ApiImplicitParams({
+ @ApiImplicitParam(paramType = "query", name = "username",
+ value = "用户名", required = true, defaultValue = "test"),
+ @ApiImplicitParam(paramType = "query", name = "password",
+ value = "密码", required = true, defaultValue = "123")
+ })
+ @PostMapping("/user")
+ public String addUser(@RequestParam String username, @RequestParam String password) {
+ return "新增用户:" + username + " " + password;
+ }
+
+ /**
+ * @ApiResponse 是对响应结果的描述。code 表示响应码,message 为相应的描述信息。如果有多个 @ApiResponse,则放在一个 @ApiResponses 中
+ */
+ @ApiOperation(value = "删除用户", notes = "根据 id 删除用户")
+ @ApiResponses({
+ @ApiResponse(code = 200, message = "删除成功!"),
+ @ApiResponse(code = 500, message = "删除失败!")
+ })
+ @DeleteMapping("/user/{id}")
+ public Integer deleteUserById(@PathVariable Integer id) {
+ return id;
+ }
+
+ /**
+ * @ApiIgnore 注解表示不对某个接口生成文档。
+ */
+ @ApiIgnore
+ @GetMapping("/user/test")
+ public String test() {
+ return "这是一个测试接口,不需要在api文档中显示。";
+ }
+}
diff --git a/springboot-swagger-sample/src/main/java/com/ipman/swagger/sample/entity/User.java b/springboot-swagger-sample/src/main/java/com/ipman/swagger/sample/entity/User.java
new file mode 100644
index 0000000..da75542
--- /dev/null
+++ b/springboot-swagger-sample/src/main/java/com/ipman/swagger/sample/entity/User.java
@@ -0,0 +1,29 @@
+package com.ipman.swagger.sample.entity;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.*;
+
+/**
+ * Created by ipipman on 2021年9月22日.
+ *
+ * @version V1.0
+ * @Package com.ipman.swagger.sample.entity
+ * @Description: (用一句话描述该文件做什么)
+ * @date 2021年9月22日 6:37 下午
+ */
+@Getter
+@Setter
+@ToString
+@ApiModel(value = "用户实体类", description = "用户信息描述类")
+public class User {
+
+ @ApiModelProperty(value = "用户id")
+ private Integer id;
+
+ @ApiModelProperty(value = "用户名")
+ private String username;
+
+ @ApiModelProperty(value = "用户密码")
+ private String password;
+}
\ No newline at end of file
diff --git a/springboot-swagger-sample/src/main/resources/application.properties b/springboot-swagger-sample/src/main/resources/application.properties
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/springboot-swagger-sample/src/main/resources/application.properties
@@ -0,0 +1 @@
+
diff --git a/springboot-swagger-sample/src/test/java/com/ipman/swagger/sample/SpringbootSwaggerSampleApplicationTests.java b/springboot-swagger-sample/src/test/java/com/ipman/swagger/sample/SpringbootSwaggerSampleApplicationTests.java
new file mode 100644
index 0000000..dd489b9
--- /dev/null
+++ b/springboot-swagger-sample/src/test/java/com/ipman/swagger/sample/SpringbootSwaggerSampleApplicationTests.java
@@ -0,0 +1,13 @@
+package com.ipman.swagger.sample;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class SpringbootSwaggerSampleApplicationTests {
+
+ @Test
+ void contextLoads() {
+ }
+
+}
diff --git a/springboot-transactional-sample/.gitignore b/springboot-transactional-sample/.gitignore
new file mode 100644
index 0000000..549e00a
--- /dev/null
+++ b/springboot-transactional-sample/.gitignore
@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/springboot-transactional-sample/.mvn/wrapper/MavenWrapperDownloader.java b/springboot-transactional-sample/.mvn/wrapper/MavenWrapperDownloader.java
new file mode 100644
index 0000000..e76d1f3
--- /dev/null
+++ b/springboot-transactional-sample/.mvn/wrapper/MavenWrapperDownloader.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2007-present the original author or authors.
+ *
+ * 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.
+ */
+import java.net.*;
+import java.io.*;
+import java.nio.channels.*;
+import java.util.Properties;
+
+public class MavenWrapperDownloader {
+
+ private static final String WRAPPER_VERSION = "0.5.6";
+ /**
+ * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
+ */
+ private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
+ + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
+
+ /**
+ * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
+ * use instead of the default one.
+ */
+ private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
+ ".mvn/wrapper/maven-wrapper.properties";
+
+ /**
+ * Path where the maven-wrapper.jar will be saved to.
+ */
+ private static final String MAVEN_WRAPPER_JAR_PATH =
+ ".mvn/wrapper/maven-wrapper.jar";
+
+ /**
+ * Name of the property which should be used to override the default download url for the wrapper.
+ */
+ private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
+
+ public static void main(String args[]) {
+ System.out.println("- Downloader started");
+ File baseDirectory = new File(args[0]);
+ System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
+
+ // If the maven-wrapper.properties exists, read it and check if it contains a custom
+ // wrapperUrl parameter.
+ File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
+ String url = DEFAULT_DOWNLOAD_URL;
+ if(mavenWrapperPropertyFile.exists()) {
+ FileInputStream mavenWrapperPropertyFileInputStream = null;
+ try {
+ mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
+ Properties mavenWrapperProperties = new Properties();
+ mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
+ url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
+ } catch (IOException e) {
+ System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
+ } finally {
+ try {
+ if(mavenWrapperPropertyFileInputStream != null) {
+ mavenWrapperPropertyFileInputStream.close();
+ }
+ } catch (IOException e) {
+ // Ignore ...
+ }
+ }
+ }
+ System.out.println("- Downloading from: " + url);
+
+ File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
+ if(!outputFile.getParentFile().exists()) {
+ if(!outputFile.getParentFile().mkdirs()) {
+ System.out.println(
+ "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
+ }
+ }
+ System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
+ try {
+ downloadFileFromURL(url, outputFile);
+ System.out.println("Done");
+ System.exit(0);
+ } catch (Throwable e) {
+ System.out.println("- Error downloading");
+ e.printStackTrace();
+ System.exit(1);
+ }
+ }
+
+ private static void downloadFileFromURL(String urlString, File destination) throws Exception {
+ if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
+ String username = System.getenv("MVNW_USERNAME");
+ char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
+ Authenticator.setDefault(new Authenticator() {
+ @Override
+ protected PasswordAuthentication getPasswordAuthentication() {
+ return new PasswordAuthentication(username, password);
+ }
+ });
+ }
+ URL website = new URL(urlString);
+ ReadableByteChannel rbc;
+ rbc = Channels.newChannel(website.openStream());
+ FileOutputStream fos = new FileOutputStream(destination);
+ fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
+ fos.close();
+ rbc.close();
+ }
+
+}
diff --git a/springboot-transactional-sample/.mvn/wrapper/maven-wrapper.jar b/springboot-transactional-sample/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 0000000..2cc7d4a
Binary files /dev/null and b/springboot-transactional-sample/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/springboot-transactional-sample/.mvn/wrapper/maven-wrapper.properties b/springboot-transactional-sample/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..642d572
--- /dev/null
+++ b/springboot-transactional-sample/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,2 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
diff --git a/springboot-transactional-sample/README.md b/springboot-transactional-sample/README.md
new file mode 100644
index 0000000..3c52024
--- /dev/null
+++ b/springboot-transactional-sample/README.md
@@ -0,0 +1,41 @@
+# Spring的事务7种传播机制和隔离级别
+
+### Propagation(事务传播属性)
+Propagation 属性确定代理应该对那些方法增加事务行为,这样属性最重要的部分是传播行为:
+> - **PROPAGATION_REQUIRED** ----> 支持当前事务,如果当前没有事务,则新建一个事务,这是最常见的选择,也是 Spring 默认的一个事务传播行为;
+> - **PROPAGATION_SUPPORTS** ----> 支持当前事务,如果当前没有事务,则以非事务的方式执行;
+> - **PROPAGATION_MANDATORY** ----> 支持当前事务,如果当前没有事务,则抛出异常;
+> - **PROPAGATION_REQUIRES_NEW** ----> 新建事务,如果当前存在事务,把当前事务挂起;
+> - **PROPAGATION_NOT_SUPPORTED** ----> 以非事务的方式执行操作,如果当前存在事务,就把当前事务挂起;
+> - **PROPAGATION_NEVER** ----> 以非事务方式执行,如果当前存在事务,则抛出异常;
+> - **PROPAGATION_NESTED** ----> Nested的事务和它的父事务是相依的,它的提交是等它的父事务一块提交的;
+
+
+#### (一)PROPAGATION_REQUIRED
+注解用法比如:@Transactional(propagation = Propagation.REQUIRED)
+默认的 spring 事务传播级别,使用该级别的特点是:如果上下文中已经存在事务,那么就加入到事务中执行,如果当前上下文不存在事务,则新建事务执行
+在大多数业务场景下通常都能满足
+
+#### (二)PROPAGATION_SUPPORTS
+该传播级别的特点是:如果上下文存在事务,则支持事务加入事务,如果没有事务,则使用非事务的方式执行。所以说,并非所有的包在transactionTemplate.execute中的代码都会有事务支持。这个通常是用来处理那些非原子性的非核心业务逻辑操作。
+应用场景较少
+
+#### (三)PROPAGATION_MANDATORY
+该级别的事务要求上下文中必须要存在事务,否则就会抛出异常
+配置改方法的传播级别是有效的控制上下文调用代码遗漏添加事务控制的保证手段
+比如一段代码不能单独被调用执行,但是一旦被调用,就必须有事务包含的情况,就可以使用这个传播级别
+
+#### (四)PROPAGATION_REQUIRES_NEW
+该传播级别的特点是,每次都会新建一个事务,并且同时将上下文中的事务挂起,执行当前新建事务完成以后,上下文事务恢复再执行
+
+#### (五)PROPAGATION_NOT_SUPPORTED
+该传播属性不支持事务,该级别的特点就上下文中存在事务,则挂起事务,执行当前逻辑,结束后恢复上下文的事务
+好处:可以帮助我们把事务缩小范围。因为一个事务越大,它存在的风险也就越多。所以在处理事务过程中,要保证尽可能的缩小范围
+
+#### (六)PROPAGATION_NEVER
+不能在事务中运行,该事务传播级别要求中不能存在事务,一旦有事务,就抛出runtime异常,强制停止执行
+
+#### (七)PROPAGATION_NESTED
+理解Nested的关键是savepoint
+它与PROPAGATION_REQUIRES_NEW的区别是,PROPAGATION_REQUIRES_NEW另起一个事务,将会与它的父事务相互独立
+而Nested的事务和它的父事务是相依的,它的提交是要等和它的父事务一块提交的。也就是说,如果父事务最后回滚,它也要回滚的
\ No newline at end of file
diff --git a/springboot-transactional-sample/mvnw b/springboot-transactional-sample/mvnw
new file mode 100755
index 0000000..a16b543
--- /dev/null
+++ b/springboot-transactional-sample/mvnw
@@ -0,0 +1,310 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - 0ドル may be a link to maven's home
+ PRG="0ドル"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*'> /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="`which java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly.">&2
+ echo " We cannot execute $JAVACMD">&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "1ドル" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="1ドル"
+ wdir="1ドル"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "1ドル" ]; then
+ echo "$(tr -s '\n' ' ' < "1ドル")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget> /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget "$jarUrl" -O "$wrapperJarPath"
+ else
+ wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
+ fi
+ elif command -v curl> /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl -o "$wrapperJarPath" "$jarUrl" -f
+ else
+ curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+ fi
+
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaClass=`cygpath --path --windows "$javaClass"`
+ fi
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/springboot-transactional-sample/mvnw.cmd b/springboot-transactional-sample/mvnw.cmd
new file mode 100644
index 0000000..c8d4337
--- /dev/null
+++ b/springboot-transactional-sample/mvnw.cmd
@@ -0,0 +1,182 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment.>&2
+echo Please set the JAVA_HOME variable in your environment to match the>&2
+echo location of your Java installation.>&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory.>&2
+echo JAVA_HOME = "%JAVA_HOME%">&2
+echo Please set the JAVA_HOME variable in your environment to match the>&2
+echo location of your Java installation.>&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+
+FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%
diff --git a/springboot-transactional-sample/pom.xml b/springboot-transactional-sample/pom.xml
new file mode 100644
index 0000000..8268374
--- /dev/null
+++ b/springboot-transactional-sample/pom.xml
@@ -0,0 +1,110 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.4.4
+
+
+ com.ipman.springboot.transactional.sample
+ springboot-transactional-sample
+ 0.0.1-SNAPSHOT
+ springboot-transactional-sample
+ Demo project for Spring Boot
+
+ 1.8
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+ org.mybatis.spring.boot
+ mybatis-spring-boot-starter
+ 2.1.0
+
+
+
+
+ com.baomidou
+ mybatis-plus
+ 3.3.0
+
+
+ mybatis-spring
+ org.mybatis
+
+
+ mybatis
+ org.mybatis
+
+
+ jsqlparser
+ com.github.jsqlparser
+
+
+
+
+
+ com.baomidou
+ mybatis-plus-generator
+ 3.1.0
+
+
+ mybatis-plus-extension
+ com.baomidou
+
+
+
+
+
+
+ mysql
+ mysql-connector-java
+ 5.1.49
+
+
+
+ com.alibaba
+ druid
+ 1.0.15
+
+
+
+ com.github.pagehelper
+ pagehelper
+ 4.1.6
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ org.mybatis.generator
+ mybatis-generator-maven-plugin
+ 1.3.2
+
+ ${project.basedir}/src/main/resources/generatorConfig.xml
+ true
+ true
+
+
+
+
+
+
diff --git a/springboot-transactional-sample/src/main/java/com/ipman/springboot/transactional/sample/SpringbootTransactionalSampleApplication.java b/springboot-transactional-sample/src/main/java/com/ipman/springboot/transactional/sample/SpringbootTransactionalSampleApplication.java
new file mode 100644
index 0000000..02aaf20
--- /dev/null
+++ b/springboot-transactional-sample/src/main/java/com/ipman/springboot/transactional/sample/SpringbootTransactionalSampleApplication.java
@@ -0,0 +1,13 @@
+package com.ipman.springboot.transactional.sample;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class SpringbootTransactionalSampleApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SpringbootTransactionalSampleApplication.class, args);
+ }
+
+}
diff --git a/springboot-transactional-sample/src/main/java/com/ipman/springboot/transactional/sample/common/domain/model/User1.java b/springboot-transactional-sample/src/main/java/com/ipman/springboot/transactional/sample/common/domain/model/User1.java
new file mode 100644
index 0000000..cdddd23
--- /dev/null
+++ b/springboot-transactional-sample/src/main/java/com/ipman/springboot/transactional/sample/common/domain/model/User1.java
@@ -0,0 +1,23 @@
+package com.ipman.springboot.transactional.sample.common.domain.model;
+
+public class User1 {
+ private Integer id;
+
+ private String name;
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name == null ? null : name.trim();
+ }
+}
\ No newline at end of file
diff --git a/springboot-transactional-sample/src/main/java/com/ipman/springboot/transactional/sample/common/domain/model/User2.java b/springboot-transactional-sample/src/main/java/com/ipman/springboot/transactional/sample/common/domain/model/User2.java
new file mode 100644
index 0000000..bc03bb3
--- /dev/null
+++ b/springboot-transactional-sample/src/main/java/com/ipman/springboot/transactional/sample/common/domain/model/User2.java
@@ -0,0 +1,23 @@
+package com.ipman.springboot.transactional.sample.common.domain.model;
+
+public class User2 {
+ private Integer id;
+
+ private String name;
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name == null ? null : name.trim();
+ }
+}
\ No newline at end of file
diff --git a/springboot-transactional-sample/src/main/java/com/ipman/springboot/transactional/sample/config/MybatisConfig.java b/springboot-transactional-sample/src/main/java/com/ipman/springboot/transactional/sample/config/MybatisConfig.java
new file mode 100644
index 0000000..99a1c95
--- /dev/null
+++ b/springboot-transactional-sample/src/main/java/com/ipman/springboot/transactional/sample/config/MybatisConfig.java
@@ -0,0 +1,125 @@
+package com.ipman.springboot.transactional.sample.config;
+
+import com.alibaba.druid.pool.DruidDataSource;
+import com.baomidou.mybatisplus.core.MybatisConfiguration;
+import com.baomidou.mybatisplus.core.parser.ISqlParser;
+import com.baomidou.mybatisplus.extension.parsers.BlockAttackSqlParser;
+import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.pagination.optimize.JsqlParserCountOptimize;
+import com.github.pagehelper.PageHelper;
+import net.sf.jsqlparser.statement.delete.Delete;
+import net.sf.jsqlparser.statement.update.Update;
+import org.apache.ibatis.logging.stdout.StdOutImpl;
+import org.apache.ibatis.session.SqlSessionFactory;
+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.Qualifier;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import org.springframework.core.env.StandardEnvironment;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.core.io.support.ResourcePatternResolver;
+import org.springframework.jdbc.datasource.DataSourceTransactionManager;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+import org.springframework.util.ClassUtils;
+
+import javax.sql.DataSource;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+@Configuration
+@EnableTransactionManagement
+@MapperScan(basePackages = "com.ipman.springboot.transactional.sample.core.dao")
+public class MybatisConfig {
+
+ private static final Logger logger = LoggerFactory.getLogger(MybatisConfig.class);
+
+ @Bean(name = "dataSource")
+ @ConfigurationProperties(prefix = "datasource")
+ public DataSource sspSource() {
+ return new DruidDataSource();
+ }
+
+
+ private Resource[] getResource(String basePackage, String pattern) throws IOException {
+ String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
+ + ClassUtils.convertClassNameToResourcePath(new StandardEnvironment()
+ .resolveRequiredPlaceholders(basePackage)) + "/" + pattern;
+ Resource[] resources = new PathMatchingResourcePatternResolver().getResources(packageSearchPath);
+ return resources;
+ }
+
+ //配置事务管理器
+ @Bean(name = "transactionManager")
+ public DataSourceTransactionManager transactionManager(@Qualifier("dataSource") DataSource dspSource) {
+ return new DataSourceTransactionManager(dspSource);
+ }
+
+ //获取mybatis-config配置文件
+ private Resource getMybatisConfig(String pattern) throws IOException {
+ String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
+ + "/" + pattern;
+ Resource[] resources = new PathMatchingResourcePatternResolver().getResources(packageSearchPath);
+ return resources[0];
+ }
+
+ @Bean
+ @Primary
+ public SqlSessionFactory sqlSessionFactory(DataSource ds) throws Exception {
+ logger.info("> sqlSessionFactory");
+ final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
+ sessionFactory.setDataSource(ds);
+ sessionFactory.setMapperLocations(getResource("mapper", "**/*.xml"));
+ //日志打印控制台
+ MybatisConfiguration configuration = new MybatisConfiguration();
+ configuration.setLogImpl(StdOutImpl.class);
+ sessionFactory.setConfiguration(configuration);
+ //防注入插件
+ sessionFactory.setPlugins(paginationInterceptor());
+ //分页插件
+ sessionFactory.setPlugins(pageHelper());
+ return sessionFactory.getObject();
+ }
+
+ private PageHelper pageHelper() {
+ PageHelper pageHelper = new PageHelper();
+ Properties props = new Properties();
+ props.setProperty("reasonable", "true");
+ props.setProperty("supportMethodsArguments", "true");
+ props.setProperty("returnPageInfo", "check");
+ props.setProperty("params", "count=countSql");
+ pageHelper.setProperties(props);
+ return pageHelper;
+ }
+
+ private PaginationInterceptor paginationInterceptor() {
+ PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
+ // 开启 count 的 join 优化,只针对 left join !!!
+ paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
+ paginationInterceptor.setDialectType("mysql");
+ List sqlParserList = new ArrayList();
+ // 攻击 SQL 阻断解析器、加入解析链
+ sqlParserList.add(new BlockAttackSqlParser() {
+ // 防止delete全表操作
+ @Override
+ public void processDelete(Delete delete) {
+ super.processDelete(delete);
+ }
+
+ // 防止update全表操作
+ @Override
+ public void processUpdate(Update update) {
+ super.processUpdate(update);
+ }
+ });
+ paginationInterceptor.setSqlParserList(sqlParserList);
+ return paginationInterceptor;
+ }
+}
diff --git a/springboot-transactional-sample/src/main/java/com/ipman/springboot/transactional/sample/core/dao/User1Mapper.java b/springboot-transactional-sample/src/main/java/com/ipman/springboot/transactional/sample/core/dao/User1Mapper.java
new file mode 100644
index 0000000..aed02ee
--- /dev/null
+++ b/springboot-transactional-sample/src/main/java/com/ipman/springboot/transactional/sample/core/dao/User1Mapper.java
@@ -0,0 +1,17 @@
+package com.ipman.springboot.transactional.sample.core.dao;
+
+import com.ipman.springboot.transactional.sample.common.domain.model.User1;
+
+public interface User1Mapper {
+ int deleteByPrimaryKey(Integer id);
+
+ int insert(User1 record);
+
+ int insertSelective(User1 record);
+
+ User1 selectByPrimaryKey(Integer id);
+
+ int updateByPrimaryKeySelective(User1 record);
+
+ int updateByPrimaryKey(User1 record);
+}
\ No newline at end of file
diff --git a/springboot-transactional-sample/src/main/java/com/ipman/springboot/transactional/sample/core/dao/User2Mapper.java b/springboot-transactional-sample/src/main/java/com/ipman/springboot/transactional/sample/core/dao/User2Mapper.java
new file mode 100644
index 0000000..55b739f
--- /dev/null
+++ b/springboot-transactional-sample/src/main/java/com/ipman/springboot/transactional/sample/core/dao/User2Mapper.java
@@ -0,0 +1,17 @@
+package com.ipman.springboot.transactional.sample.core.dao;
+
+import com.ipman.springboot.transactional.sample.common.domain.model.User2;
+
+public interface User2Mapper {
+ int deleteByPrimaryKey(Integer id);
+
+ int insert(User2 record);
+
+ int insertSelective(User2 record);
+
+ User2 selectByPrimaryKey(Integer id);
+
+ int updateByPrimaryKeySelective(User2 record);
+
+ int updateByPrimaryKey(User2 record);
+}
\ No newline at end of file
diff --git a/springboot-transactional-sample/src/main/java/com/ipman/springboot/transactional/sample/core/service/User1Service.java b/springboot-transactional-sample/src/main/java/com/ipman/springboot/transactional/sample/core/service/User1Service.java
new file mode 100644
index 0000000..b0c1b8b
--- /dev/null
+++ b/springboot-transactional-sample/src/main/java/com/ipman/springboot/transactional/sample/core/service/User1Service.java
@@ -0,0 +1,22 @@
+package com.ipman.springboot.transactional.sample.core.service;
+
+import com.ipman.springboot.transactional.sample.common.domain.model.User1;
+
+/**
+ * Created by ipipman on 2021年4月2日.
+ *
+ * @version V1.0
+ * @Package com.ipman.springboot.transactional.sample.core.service
+ * @Description: (用一句话描述该文件做什么)
+ * @date 2021年4月2日 11:49 上午
+ */
+public interface User1Service {
+
+ /**
+ * Propagation.REQUIRED
+ * 如果当前没有事务,就创建一个事务
+ * 如果已经存在一个事务中,加入这个事务中
+ *
+ */
+ void addRequired(User1 user1);
+}
diff --git a/springboot-transactional-sample/src/main/java/com/ipman/springboot/transactional/sample/core/service/User2Service.java b/springboot-transactional-sample/src/main/java/com/ipman/springboot/transactional/sample/core/service/User2Service.java
new file mode 100644
index 0000000..90a8dc1
--- /dev/null
+++ b/springboot-transactional-sample/src/main/java/com/ipman/springboot/transactional/sample/core/service/User2Service.java
@@ -0,0 +1,29 @@
+package com.ipman.springboot.transactional.sample.core.service;
+
+import com.ipman.springboot.transactional.sample.common.domain.model.User2;
+
+/**
+ * Created by ipipman on 2021年4月2日.
+ *
+ * @version V1.0
+ * @Package com.ipman.springboot.transactional.sample.core.service
+ * @Description: (用一句话描述该文件做什么)
+ * @date 2021年4月2日 11:50 上午
+ */
+public interface User2Service {
+
+
+ /**
+ * Propagation.REQUIRED
+ * 如果当前没有事务,就创建一个事务
+ * 如果已经存在一个事务中,加入这个事务中
+ */
+ void addRequired(User2 user2);
+
+ /**
+ * Propagation.REQUIRED
+ * 如果当前没有事务,就创建一个事务
+ * 如果已经存在一个事务中,加入这个事务中
+ */
+ void addRequiredException(User2 user2);
+}
diff --git a/springboot-transactional-sample/src/main/java/com/ipman/springboot/transactional/sample/core/service/impl/User1ServiceImpl.java b/springboot-transactional-sample/src/main/java/com/ipman/springboot/transactional/sample/core/service/impl/User1ServiceImpl.java
new file mode 100644
index 0000000..37feac3
--- /dev/null
+++ b/springboot-transactional-sample/src/main/java/com/ipman/springboot/transactional/sample/core/service/impl/User1ServiceImpl.java
@@ -0,0 +1,38 @@
+package com.ipman.springboot.transactional.sample.core.service.impl;
+
+import com.ipman.springboot.transactional.sample.common.domain.model.User1;
+import com.ipman.springboot.transactional.sample.core.dao.User1Mapper;
+import com.ipman.springboot.transactional.sample.core.service.User1Service;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+
+/**
+ * Created by ipipman on 2021年4月2日.
+ *
+ * @version V1.0
+ * @Package com.ipman.springboot.transactional.sample.core.service.impl
+ * @Description: (用一句话描述该文件做什么)
+ * @date 2021年4月2日 11:49 上午
+ */
+@Service
+public class User1ServiceImpl implements User1Service {
+
+ @Resource
+ private User1Mapper user1Mapper;
+
+ /**
+ * Propagation.REQUIRED
+ * 如果当前没有事务,就创建一个事务
+ * 如果已经存在一个事务中,加入这个事务中
+ */
+ @Override
+ @Transactional(value = "transactionManager", propagation = Propagation.REQUIRED)
+ public void addRequired(User1 user1) {
+ user1Mapper.insert(user1);
+ }
+
+
+}
diff --git a/springboot-transactional-sample/src/main/java/com/ipman/springboot/transactional/sample/core/service/impl/User2ServiceImpl.java b/springboot-transactional-sample/src/main/java/com/ipman/springboot/transactional/sample/core/service/impl/User2ServiceImpl.java
new file mode 100644
index 0000000..c0d2de2
--- /dev/null
+++ b/springboot-transactional-sample/src/main/java/com/ipman/springboot/transactional/sample/core/service/impl/User2ServiceImpl.java
@@ -0,0 +1,49 @@
+package com.ipman.springboot.transactional.sample.core.service.impl;
+
+import com.ipman.springboot.transactional.sample.common.domain.model.User2;
+import com.ipman.springboot.transactional.sample.core.dao.User2Mapper;
+import com.ipman.springboot.transactional.sample.core.service.User2Service;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+
+/**
+ * Created by ipipman on 2021年4月2日.
+ *
+ * @version V1.0
+ * @Package com.ipman.springboot.transactional.sample.core.service.impl
+ * @Description: (用一句话描述该文件做什么)
+ * @date 2021年4月2日 11:50 上午
+ */
+@Service
+public class User2ServiceImpl implements User2Service {
+
+ @Resource
+ private User2Mapper user2Mapper;
+
+ /**
+ * Propagation.REQUIRED
+ * 如果当前没有事务,就创建一个事务
+ * 如果已经存在一个事务中,加入这个事务中
+ */
+ @Override
+ @Transactional(value = "transactionManager", propagation = Propagation.REQUIRED)
+ public void addRequired(User2 user2){
+ user2Mapper.insert(user2);
+ }
+
+ /**
+ * Propagation.REQUIRED
+ * 如果当前没有事务,就创建一个事务
+ * 如果已经存在一个事务中,加入这个事务中
+ */
+ @Override
+ @Transactional(value = "transactionManager", propagation = Propagation.REQUIRED)
+ public void addRequiredException(User2 user2){
+ user2Mapper.insert(user2);
+ throw new RuntimeException();
+ }
+
+}
diff --git a/springboot-transactional-sample/src/main/resources/application.properties b/springboot-transactional-sample/src/main/resources/application.properties
new file mode 100644
index 0000000..4ba7044
--- /dev/null
+++ b/springboot-transactional-sample/src/main/resources/application.properties
@@ -0,0 +1,17 @@
+#datasource
+datasource.type= com.alibaba.druid.pool.DruidDataSource
+datasource.driver-class-name= com.mysql.jdbc.Driver
+datasource.url= jdbc:mysql://127.0.0.1:3306/stx
+datasource.username= root
+datasource.initialSize= 5
+datasource.minIdle= 5
+datasource.maxActive= 30
+datasource.maxWait= 60000
+datasource.timeBetweenEvictionRunsMillis= 60000
+datasource.minEvictableIdleTimeMillis= 30000
+datasource.validationQuery= select 'x'
+datasource.testWhileIdle= true
+datasource.testOnBorrow= true
+datasource.testOnReturn= false
+datasource.poolPreparedStatements= false
+datasource.maxPoolPreparedStatementPerConnectionSize= 20
diff --git a/springboot-transactional-sample/src/main/resources/generatorConfig.xml b/springboot-transactional-sample/src/main/resources/generatorConfig.xml
new file mode 100644
index 0000000..15e4dfc
--- /dev/null
+++ b/springboot-transactional-sample/src/main/resources/generatorConfig.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/springboot-transactional-sample/src/main/resources/mapper/User1Mapper.xml b/springboot-transactional-sample/src/main/resources/mapper/User1Mapper.xml
new file mode 100644
index 0000000..cda2bc6
--- /dev/null
+++ b/springboot-transactional-sample/src/main/resources/mapper/User1Mapper.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+ id, `name`
+
+
+
+ delete from user1
+ where id = #{id,jdbcType=INTEGER}
+
+
+
+ SELECT LAST_INSERT_ID()
+
+ insert into user1 (id, `name`)
+ values (#{id,jdbcType=INTEGER}, #{name,jdbcType=VARCHAR})
+
+
+
+ SELECT LAST_INSERT_ID()
+
+ insert into user1
+
+ id,
+
+ `name`,
+
+
+
+ #{id,jdbcType=INTEGER},
+
+ #{name,jdbcType=VARCHAR},
+
+
+
+
+ update user1
+
+
+ `name` = #{name,jdbcType=VARCHAR},
+
+
+ where id = #{id,jdbcType=INTEGER}
+
+
+ update user1
+ set `name` = #{name,jdbcType=VARCHAR}
+ where id = #{id,jdbcType=INTEGER}
+
+
\ No newline at end of file
diff --git a/springboot-transactional-sample/src/main/resources/mapper/User2Mapper.xml b/springboot-transactional-sample/src/main/resources/mapper/User2Mapper.xml
new file mode 100644
index 0000000..43ea38f
--- /dev/null
+++ b/springboot-transactional-sample/src/main/resources/mapper/User2Mapper.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+ id, `name`
+
+
+
+ delete from user2
+ where id = #{id,jdbcType=INTEGER}
+
+
+
+ SELECT LAST_INSERT_ID()
+
+ insert into user2 (id, `name`)
+ values (#{id,jdbcType=INTEGER}, #{name,jdbcType=VARCHAR})
+
+
+
+ SELECT LAST_INSERT_ID()
+
+ insert into user2
+