青花小记

这个人很懒,只想写写代码做做饭

0%

本节将更详细地介绍如何使用 Spring Boot。 它涵盖了诸如构建系统、自动配置以及如何运行应用程序等内容。 此外,还介绍了一些 Spring Boot 的最佳实践。 尽管 Spring Boot 没有什么特别之处(它只是您可以使用的另一个库) ,但是有一些建议可以使您的开发过程更容易一些 。

如果你之前没有使用过 Spring Boot,那么在看这部分内容之前,你应该先阅读下入门指南

构建系统

强烈建议您选择一个支持依赖管理的构建系统,该系统可以使用发布到 Maven 中央库的依赖。 我们建议你选择 Maven 或 Gradle。Spring Boot 也支持其他构建系统,例如 Ant,但是它们并没有得到特别好的支持。

因为公司目前只用Maven,所以Gradle和Ant的方式不做翻译了

依赖管理

Spring Boot 的每个版本都提供了它所支持的经过精心策划的依赖项列表。 实际上,您不需要为构建配置中的任何依赖项提供版本信息,因为 Spring Boot 已经为您管理了这些依赖项。 当您升级 Spring Boot 时,这些依赖关系也会随之升级。

如果需要的话,您仍然可以指定一个版本并覆盖 Spring Boot 提供的版本。

这个精选列表包含所有可以用于 Spring Boot 的 Spring 模块,以及第三方库的精选列表。 该列表是一个可用于 Maven 和 Gradle的标准的材料账单(spring-boot-dependencies)。

Spring Boot 的每个版本都与 Spring 框架的基本版本高度关联, 建议您不要指定它的版本

Maven

Maven 用户可以通过继承 spring-boot-starter-parent 项目来获得合理的默认值。 父项目具有以下特点:

  • 默认的编译级别是 Java 1.8
  • 编码格式为 UTF-8
  • spring-boot-dependencies pom 继承而来的依赖管理部分,用于管理公共依赖项的版本。 这种依赖项管理允许您在自己的 pom 中引入依赖时忽略<version>标记。
  • 带有 repackage 执行 id 的 repackage 目标的执行
  • 可感知的资源过滤
  • 合理的插件配置( exec plugin, Git commit ID, shade
  • application.propertiesapplication.yml 进行合理的资源筛选,包括特定的配置文件(例如,application-dev.propertiesapplication-dev.yml )

注意,因为 application.propertiesapplication.yml 文件接受 Spring 风格的占位符( ${…} ) ,所以 Maven 过滤标识被更改为使用 @..@ 占位符 (可以通过设置一个名为 resource.delimiter的 Maven 属性来覆盖该属性)。

继承 Starter 父类

若要将项目配置为从 spring-boot-starter-parent 继承,请将 parent 设置如下:

1
2
3
4
5
6
<!-- Inherit defaults from Spring Boot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
</parent>

您应该只需要通过这个依赖指定 Spring Boot 版本号。如果导入其他 starter 依赖,您可以省略版本号

使用该设置,还可以通过重写自己项目中的属性来重写单个依赖项。 例如,要升级到另一个 Spring Data 版本,可以在 pom.xml 中添加以下内容:

1
2
3
<properties>
<spring-data-releasetrain.version>Fowler-SR2</spring-data-releasetrain.version>
</properties>

检查spring-boot-dependencies pom文件获取受支持的属性列表

使用没有父 POM 的 Spring Boot

并不是每个人都喜欢继承 spring-boot-starter-parent POM, 您可能需要使用自己的企业标准父级,或者您可能更喜欢显式声明所有 Maven 配置。

如果你不想使用 spring-boot-starter-parent ,你可以通过 scope=import 的方式来从依赖管理(不是插件管理)中受益,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
<dependencyManagement>
<dependencies>
<dependency>
<!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

如上所述,前面的示例程序不允许使用属性重写单个依赖项。 要实现相同的结果,您需要在 spring-boot-dependencies 依赖之前在项目的 dependencyManagement 中添加一个条目。 例如,要升级到另一个 Spring Data 发布系列,可以在 pom.xml 中添加以下元素:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<dependencyManagement>
<dependencies>
<!-- Override Spring Data release train provided by Spring Boot -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-releasetrain</artifactId>
<version>Fowler-SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

在前面的例子中,我们指定了一个BOM,任务依赖类型都可以以相同的方式来重写

使用 Spring Boot Maven 插件

Spring Boot 包含一个 Maven 插件,可以将项目打包为一个可执行 jar。 如果你想使用这个插件,可以把它添加到你的插件部分,如下面的例子所示:

1
2
3
4
5
6
7
8
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

如果你使用 Spring Boot starter 父 pom,你只需要添加插件,没有必要配置它,除非您想要更改在父级中定义的设置

Starter 命名规范

官方的 Starter 遵循 spring-boot-starter-* 的命名模式,如 spring-boot-starter-data-jpa 。第三方的 Starter 通常以自己的项目名称开始,例如,名为 thirdpartyproject 的第三方入门项目通常被命名为 thirdpartyproject-spring-boot-starter

自定义 Starter

创建 Starter 工程

使用 maven-archetype-quickstart 架手架创建一个 Maven 工程,artifactId 设置为 zombie-spring-boot-starter

pom.xml 文件中引入依赖的 jar 包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.1.7.RELEASE</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>2.1.7.RELEASE</version>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
</dependencies>

设置以 jar 包的方式打包:

1
<packaging>jar</packaging>

定义服务类

定义一个服务类 ZombieService :

1
2
3
4
5
6
7
8
9
public class ZombieService {

public Zombie setZombie(String name, Integer age) {
Zombie zombie = new Zombie();
zombie.setName(name);
zombie.setAge(age);
return zombie;
}
}

定义配置类

定义一个自动配置类,一般是以 *AutoConfiguration 的形式命名的。

1
2
3
4
5
6
7
8
9
10
@Configuration
@ConditionalOnClass(ZombieService.class)
public class ZombieAutoConfiguration {

@Bean
@ConditionalOnMissingBean(ZombieService.class)
public ZombieService zombieService() {
return new ZombieService();
}
}

常用的注解

  • @ConditionalOnClass:当类路径classpath下有指定的类的情况下进行自动配置

  • @ConditionalOnMissingClass:当类路径下没有指定的类的条件下

  • @ConditionalOnBean:当容器(Spring Context)中有指定的Bean的条件下

  • @ConditionalOnMissingBean:当容器(Spring Context)中没有指定Bean的情况下进行自动配置

配置 spring.factories

resources/META-INF/spring.factories 下配置自动配置类。如果没有该文件的话,需要手动创建。

1
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.qinghuazs.zombie.auto.ZombieAutoConfiguration

打包发布

在发布之前需要在 pom.xml 文件中配置仓库信息

1
2
3
4
5
6
7
8
9
10
11
12
<distributionManagement>
<repository>
<id>qinghuazs-release</id>
<name>qinghuazs-release</name>
<url>http://af.qinghuazs.com.cn:80/artifactory/maven-qinghuazs-release</url>
</repository>
<snapshotRepository>
<id>qinghuazs-snapshot</id>
<name>qinghuazs-snapshot</name>
<url>http://af.qinghuazs.com.cn:80/artifactory/maven-qinghuazs-snapshot</url>
</snapshotRepository>
</distributionManagement>

进行发布

1
2
# mvn clean install 
# mvn deploy

测试

新建测试项目,并在 pom.xml 中引入自定义的 Starter:

1
2
3
4
5
<dependency>
<groupId>com.qinghuazs</groupId>
<artifactId>zombie-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>

自建 Controller 类,并引入 Starter 中的类:

1
2
3
4
5
6
7
8
9
10
11
@RestController
public class TestController {

@Autowired
private ZombieService zombieService;

@GetMapping("/test")
public String test() {
return zombieService.setZombie("qinghuazs", 11).toString();
}
}

使用 Postman 测试,输出结果如下:

1
Zombie(name=qinghuazs, age=11)

本节主要内容是如何通过开发一个简单的”Hello World!” web应用突出Spring Boot 的一些关键特性。 我们使用 Maven 来构建这个项目,因为大多数 IDE 都支持它。

spring.io 网站包含了许多使用 Spring Boot 的”入门”指南。 如果你需要解决一个特定的问题,首先检查那里。

通过访问 start.spring.io 并从依赖项查询中选择”Web”starter,可以快捷地完成以下步骤。 这样做会生成一个新的项目结构,以便您可以立即开始编码。 查看 Spring Initializr 文档了解更多细节。

在我们开始之前,打开一个终端并运行以下命令,以确保你已经安装了 Java 和 Maven 的有效版本:

1
2
3
4
$ java -version
java version "1.8.0_102"
Java(TM) SE Runtime Environment (build 1.8.0_102-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.102-b14, mixed mode)
1
2
3
4
$ mvn -v
Apache Maven 3.5.4 (1edded0938998edf8bf061f1ceb3cfdeccf443fe; 2018-06-17T14:33:14-04:00)
Maven home: /usr/local/Cellar/maven/3.3.9/libexec
Java version: 1.8.0_102, vendor: Oracle Corporation

这个示例需要在它自己的文件夹中创建。 接下来的介绍假设你已经创建了一个合适的文件夹,并且这个文件夹就是你的工作目录。

创建 POM 文件

我们首先需要创建一个 Maven pom.xml 文件。pom.xml 是用于构建项目的配方。 打开文本编辑器,添加以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.example</groupId>
<artifactId>myproject</artifactId>
<version>0.0.1-SNAPSHOT</version>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
</parent>

<!-- Additional lines to be added here... -->

</project>

pom.xml 文件会为您提供一个工作构建。 您可以通过运行 mvn package 来测试它(现在,您可以先忽略 “jar will be empty - no content was marked for inclusion!” 的警告)。

现在,您可以将项目导入到 IDE 中(大多数Java IDE 都包含对 Maven 的内置支持)。 为了简单起见,我们继续使用纯文本编辑器。

添加 Classpath 依赖

Spring Boot 提供了很多 “Starter”,你可以将这些 jar 包添加到 classpath 中。在本例中,我们已经在 POM 文件的 parent 标签中使用了 spring-boot-starter-parentspring-boot-starter-parent 是一个特殊的 Starter,它提供了一些有用的Maven的默认值。同时也提供了一个 dependency-management 标签,这些你可以省略部分依赖的 version 标记。

其他 “Starter” 提供了在开发特定类型的应用程序时可能需要的依赖项。 因为我们正在开发一个 web 应用程序,所以我们添加了一个 spring-boot-starter-web 依赖项。 在此之前,我们可以通过运行以下命令查看我们当前已有的内容:

1
2
3
$ mvn dependency:tree

[INFO] com.example:myproject:jar:0.0.1-SNAPSHOT

mvn dependency:tree 命令以树的形式展示当前项目的依赖项。 您可以看到,spring-boot-starter-parent 本身并不提供任何依赖性。 要添加必要的依赖项,请编辑 pom.xml 并在 <parent> 节点下面添加 spring-boot-starter-web 依赖项:

1
2
3
4
5
6
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>

如果再次运行 mvn dependency:tree ,您会看到现在有许多附加的依赖项,包括 Tomcat web 服务器和 Spring Boot 本身。

编写代码

为了完成我们的应用程序,我们需要创建一个 Java 文件。 默认情况下,Maven 从 src/main/java 下编译源代码,因此需要创建该文件夹结构,然后添加一个名为 src/main/java/Examole.java 的文件,该文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.*;
import org.springframework.web.bind.annotation.*;

@RestController
@EnableAutoConfiguration
public class Example {

@RequestMapping("/")
String home() {
return "Hello World!";
}

public static void main(String[] args) {
SpringApplication.run(Example.class, args);
}

}

虽然这里没有很多代码,但是有很多事情正在进行。 在接下来的几节中,我们将逐步介绍重要的部分。

@RestController 和 @RequestMapping 注解

Example 类的第一个注解是 @RestController。 这是一个称为 stereotype 注解。 它为阅读代码的人提供了提示;对 Spring 来说,它提供了特殊的角色。 在这种情况下,我们的类是一个 web @Controller,因此 Spring 在处理传入的 web 请求时会考虑它。

@RequestMapping 注解提供”路由”信息。 它告诉 Spring,任何带有 / 路径的 HTTP 请求都应该映射到 home 方法。@RestController 注解告诉 Spring 将结果以字符串的形式直接返回给调用方。

@RestController @RequestMapping 注解是 Spring MVC 的注解。 (它们不是 Spring Boot 特有的。) 有关更多细节,请参见 Spring 参考文档中的 MVC 部分

@EnableAutoConfiguration 注解

第二个类级别的注解是 @EnableAutoConfiguration。 这个注解告诉 Spring Boot 根据你添加的 jar 依赖项”猜测”您希望如何配置 Spring。 由于 spring-boot-starter-web 添加了 Tomcat 和 Spring MVC,因此自动装配假定您正在开发 web 应用程序并相应地设置 Spring。

Starter 和 自动装配

自动装配被设计成与”Starter”一起工作,但是这两个概念并没有直接联系在一起。 您可以在 Starter 之外自由选择 jar 依赖项。 Spring Boot 仍然尽最大努力自动配置应用程序。

“main” 方法

我们应用的最后一部分是 main 方法。 这只是遵循 Java 约定的应用程序入口点的标准方法。 我们的主方法委托给Spring Boot 的 SpringApplication 类,通过调用 run 来实现。 SpringApplication 启动我们的应用程序,启动 Spring,然后启动自动配置的 Tomcat web 服务器。 我们需要将 Example.class 作为参数传递给 run 方法,以告诉 SpringApplication 哪个是主 Spring 组件。 args 数组暴露出来以接收命令行参数。

运行示例

此时,您的应用程序应该可以工作了。 由于使用了 spring-boot-starter-parent POM,因此可以使用一个有用的 run 目标来启动应用程序。 输入 mvn spring-boot:run 从根项目目录启动应用程序。 你应该看到类似下面的输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ mvn spring-boot:run

. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.1.5.RELEASE)
....... . . .
....... . . . (log output here)
....... . . .
........ Started Example in 2.222 seconds (JVM running for 6.514)

打开web 浏览器访问 localhost: 8080,可以看到如下输出:

1
Hello World!

可以按 ctrl-c退出应用程序。

创建一个可执行 Jar

我们通过创建一个完全自包含的可执行 jar 文件来完成示例,该文件可以在生产环境中运行。 可执行的 jar (有时称为”fat jars”)是存档文件,其中包含您编译的类以及您的代码需要运行的所有 jar 依赖项。

可执行的 jar 和 Java
Java 没有提供加载嵌套 jar 文件(jar 文件本身包含在 jar 中)的标准方法。 如果您希望分发一个自包含的应用程序,这可能会有问题。
为了解决这个问题,许多开发人员使用”超级”罐子(uber jar)。 一个 uber jar 将应用程序的所有依赖项的所有类打包到一个归档文件中。这种方法的问题在于,很难看到应用程序中有哪些库。如果在多个 jar 中使用相同的文件名(但内容不同) ,也会出现问题。
Spring Boot 采用了一种不同的方法,它可以让您直接嵌套 jar。

要创建一个可执行的 jar,我们需要将 spring-boot-maven-plugin 添加到 pom.xml 中。 为此,在 dependencies 部分下面插入以下行:

1
2
3
4
5
6
7
8
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

spring-boot-starter-parent POM 包括 <executions> 配置用于绑定 repackage 目标。 如果不使用父 POM,则需要自己声明此配置。 有关详细信息,请参阅插件文档

保存 pom.xml 并从命令行运行 mvn package,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ mvn package

[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building myproject 0.0.1-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] .... ..
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ myproject ---
[INFO] Building jar: /Users/developer/example/spring-boot-example/target/myproject-0.0.1-SNAPSHOT.jar
[INFO]
[INFO] --- spring-boot-maven-plugin:2.1.5.RELEASE:repackage (default) @ myproject ---
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

如果查看 target 目录,应该会看到 myproject-0.0.1-SNAPSHOT.jar。 文件大小应该在10MB 左右。 如果你想查看jar 包内部的信息,你可以使用 jar tvf,如下所示:

1
$ jar tvf target/myproject-0.0.1-SNAPSHOT.jar

target 目录下,您还应该看到一个更小的文件,名为 myproject-0.0.1-SNAPSHOT.jar.original。 这是 Maven 在被 Spring Boot 重新打包之前创建的原始 jar 文件。

要运行该应用程序,请使用 java-jar 命令,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ java -jar target/myproject-0.0.1-SNAPSHOT.jar

. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.1.5.RELEASE)
....... . . .
....... . . . (log output here)
....... . . .
........ Started Example in 2.536 seconds (JVM running for 2.864)

可以按ctrl-c 退出应用程序。

Spring Boot 可以与”经典” Java 开发工具一起使用,也可以作为命令行工具安装。 无论哪种方式,您都需要 JDK1.8 或更高版本。 在开始之前,你应该使用以下命令检查你当前安装的JDK的版本:

1
$ java -version

Java开发人员的安装说明

您可以以与任何标准 Java 库相同的方式使用 Spring Boot。 所以,请在 classpath 中包含适当的 spring-boot-*.jar。 Spring Boot 不需要任何特殊的工具集成,因此您可以使用任何 IDE 或文本编辑器。 此外,Spring Boot 应用程序没有什么特别之处,您可以像运行其他 Java 程序一样运行和调试 Spring Boot 应用程序。

虽然可以拷贝 Spring Boot 的 jar 包,但我们通常建议您使用支持依赖项管理的构建工具(例如 Maven 或 Gradle)。

Maven 安装

Spring Boot 兼容 Apache Maven 3.3 或更高版本,如果你还没有安装 Maven,可以按照 maven.apache.org 的说明进行操作。

在许多操作系统上,Maven 可以通过一个包管理器进行安装。 如果你使用 OSX 自制程序,可以运行 brew install maven 指令。 Ubuntu 用户可以运行 sudo apt-get install maven 指令。 使用 Chocolatey 的 Windows 用户可以在 cmd 下运行 choco install maven

Spring Boot 依赖项使用 org.springframework.boot 作为 groupId。 通常,您的 Maven POM 文件继承自 spring-boot-starter-parent 项目,并声明对一个或多个 “Starters” 的依赖。 Spring Boot 还提供了一个可选的  Maven plugin 来创建可执行的 jar 文件。

下面是一个典型的 pom.xml 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.example</groupId>
<artifactId>myproject</artifactId>
<version>0.0.1-SNAPSHOT</version>

<!-- Inherit defaults from Spring Boot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
</parent>

<!-- Add typical dependencies for a web application -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>

<!-- Package as an executable jar -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

spring-boot-starter-parent 是使用 Spring Boot 的一个很好的方法,但它不可能在所有的时候都适用。 有时您可能需要从不同的父 POM 继承,或者您可能不喜欢我们的默认设置。 在这些情况下,请参阅第13.2.2节”在不使用父 POM 的情况下使用 Spring Boot”,以获得使用 import 范围的替代解决方案。

安装Spring Boot CLI

Spring Boot CLI (命令行接口)是一个命令行工具,您可以使用它来快速构建 Spring 的样例。 支持运行 Groovy 脚本,这代表你可以使用类似 Java 的语法,而不需要太多的样板代码。

您不需要使用 CLI 来处理 Spring Boot,但它绝对是获得 Spring 应用程序的最快方法。

手动安装

你可以从 Spring 软件资源库下载 Spring CLI 发行版:

也可以使用最新的快照版本(snapshot distributions )

下载完成后,请按照未打包的归档文件中的 INSTALL.txt 说明进行操作。 一般情况下,.zip 文件中有一个 bin 文件夹,在该文件夹下存放了一个 spring 脚本(spring.bat ,Windows平台下使用)。 或者,在遇到 .jar 时您可以使用 java -jar (该脚本可以帮助您确保classpath设置正确)。

安装 SDKMAN

SDKMAN!(The Software Development Kit Manager)能帮您管理各种版本的二进制 SDK,包括 Groovy 和 Spring Boot CLI。从 sdkman.io 可以获取 SDKMAN! ,然后运行以下命令来安装 Spring Boot:

1
2
3
$ sdk install springboot
$ spring --version
Spring Boot v2.1.5.RELEASE

如果您为 CLI 开发特性并希望方便地访问您构建的版本,请使用以下命令:

1
2
3
4
$ sdk install springboot dev /path/to/spring-boot/spring-boot-cli/target/spring-boot-cli-2.1.5.RELEASE-bin/spring-2.1.5.RELEASE/
$ sdk default springboot dev
$ spring --version
Spring CLI v2.1.5.RELEASE

通过上面的指令安装一个 spring 的本地实例,称为 dev 实例。 它指向您的目标构建位置,因此每次重新构建 Spring Boot 时,Spring 都是最新的。

你可以通过运行以下命令来查看它:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ sdk ls springboot

================================================================================
Available Springboot Versions
================================================================================
> + dev
* 2.1.5.RELEASE

================================================================================
+ - local version
* - installed
> - currently in use
================================================================================

OSX Homebrew安装

如果你在 Mac 电脑上使用 Homebrew,则可以使用以下指令安装 Spring Boot CLI:

1
2
$ brew tap pivotal/tap
$ brew install springboot

Homebrew 会将 Spring 安装到 /usr/local/bin 目录。

如果您没有看到该公式,您安装的 brew 可能已经过时了。 在这种情况下,运行 brew update, 然后再试一次。

MacPorts 安装

如果你在 Mac 上使用 MacPorts,你可以使用以下命令安装 Spring Boot CLI:

1
$ sudo port install spring-boot-cli

命令行补全

Spring Boot CLI 中包含了为 BASH 和 zsh 提供命令补全的脚本。 您可以在任何 shell 中 source 这些脚本(也称为 spring ) ,或者将其放入个人或系统范围的 bash 补全初始化中。 在 Debian 系统上,系统范围的脚本在 /shell-completion/bash 中,当新的 shell 启动时,该目录中的所有脚本都将执行。 例如,如果您已经使用 SDKMAN 安装了脚本,那么可以使用以下命令手动运行该脚本:

1
2
3
$ . ~/.sdkman/candidates/springboot/current/shell-completion/bash/spring
$ spring <HIT TAB HERE>
grab help jar run test version

如果你使用 Homebrew 或者 MacPorts 来安装 Spring Boot CLI,命令行补全脚本会自动注册到你的 shell 中。

快速启用 Spring CLI 示例

您可以使用以下 web 应用程序来测试您的安装是否成功。 首先,创建一个 app.groovy 文件,如下所示:

1
2
3
4
5
6
7
8
@RestController
class ThisWillActuallyRun {

@RequestMapping("/")
String home() {
"Hello World!"
}
}

然后以 shell 形式运行,如下所示:

1
$ spring run app.groovy

应用程序的第一次运行速度很慢,因为要下载依赖项。 后续的运行速度要快得多。

在浏览器中打开 localhost: 8080 ,您应该看到以下输出:

1
Hello World!

SpringBoot 版本升级

如果您正在从早期版本的 Spring Boot 升级,请查看项目 wiki 上的迁移指南,该指南提供了详细的升级说明。 还要检查发行说明以获得每个版本的新的和值得注意的特性列表。

当升级到新的特性版本时,一些属性可能已被重命名或删除。 Spring Boot 提供了一种在启动时分析应用程序环境和打印诊断信息的方法,而且还可以在运行时为您临时迁移属性。 要启用该特性,请在项目中添加以下依赖项:

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-properties-migrator</artifactId>
<scope>runtime</scope>
</dependency>

后期添加到环境中的属性(例如在使用 @PropertySource 时)将不会被考虑。

迁移完成后,请确保从项目的依赖项中删除此模块。

要升级已安装的 CLI ,请使用适当的包管理器命令(例如,brew upgrade) ,或者,如果您手动安装了 CLI,请按照标准说明进行升级,记住更新 PATH 环境变量以删除任何旧的引用。

Spring Boot 2.1.5.RELEASE 需要 JDK8 并且兼容 JDK11。Spring Framework 5.1.7.RELEASE 或更高的版本。

为以下构建工具提供了明确的构建支持:

构建工具 版本
Maven 3.3+
Gradle 4.4+

Servlet 容器

Spring Boot 支持以下嵌入的Servlet容器:

容器 Servlet版本
Tomcat 9.0 4.0
Jetty 9.4 3.1
Undertow 2.0 4.0

您还可以将 Spring Boot 应用程序部署到任何兼容 Servlet 3.1以上的容器中。

Spring Boot 使您可以轻松地创建可独立运行的、生产级的基于 Spring 的应用程序。 我们对 Spring 平台和第三方库进行了封装,因此您可以轻松上手。 大多数 Spring Boot 应用程序只需要很少的 Spring 配置。

您可以使用 Spring Boot 创建 Java 应用程序,这些应用程序可以通过使用 java -jar 或更传统的 war 包形式部署启动。 我们还提供了一个命令行工具来运行 “spring 脚本”。

我们的主要目标是:

  • 为所有 Spring 开发提供快速且广泛的合适的入门体验
  • 开箱即用,但是当默认值不能满足需求时,需要修改配置
  •  提供大型项目所共有的一系列非功能特性(如嵌入式服务器、安全性、度量、健康检查和外部化配置)
  •  绝对没有代码生成,也不需要 XML 配置

近期使用绿盟扫描工具对 Web 系统进行扫描,出现了检测到目标服务器启用了 TRACE 方法的高级漏洞,TRACE 是 HTTP 协议定义的一种请求方法,该方法会使服务器原样返回任意客户端请求的任何内容,因此可以用来进行跨站点脚本攻击(XSS 攻击),这种攻击方式又称为跨站跟踪攻击(XST)。

绿盟的报告中给出了相应的方案:

  1. 2.0.55 以上版本的 Apache 服务器,可以在httpd.conf的尾部添加:
1
TraceEnable off

2.如果你使用的是 Apache:

  • 确认 rewrite 模块激活(httpd.conf)
1
LoadModule rewrite_module modules/mod_rewrite.so
  • 在各虚拟主机的配置文件里添加如下语句:
1
RewriteEngine On RewriteCond %{REQUEST_METHOD} ^TRACE RewriteRule .*

我们的项目是基于 SpringBoot的单机web项目,并没有使用到Apache服务器。查询相关资料,得知在Tomcat的web.xml文件中可以配置HTTP的请求方式,禁止不安全的请求类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<security-constraint>  
<web-resource-collection>
<url-pattern>/*</url-pattern>
<http-method>PUT</http-method>
<http-method>DELETE</http-method>
<http-method>HEAD</http-method>
<http-method>OPTIONS</http-method>
<http-method>TRACE</http-method>
</web-resource-collection>
<auth-constraint>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
</login-config>

如果使用的是SpringBoot内置的Tomcat容器的话,可以配置Tomcat的配置类,将配置注入到Spring容器中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Configuration
public class TomcatConfig {
@Bean
public TomcatServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory tomcatServletContainerFactory = new TomcatServletWebServerFactory();
tomcatServletContainerFactory.addContextCustomizers(new TomcatContextCustomizer(){
@Override
public void customize(Context context) {
SecurityConstraint constraint = new SecurityConstraint();
SecurityCollection collection = new SecurityCollection();
//http方法
collection.addMethod("PUT");
collection.addMethod("DELETE");
collection.addMethod("HEAD");
collection.addMethod("OPTIONS");
collection.addMethod("TRACE");
//url匹配表达式
collection.addPattern("/*");
constraint.addCollection(collection);
constraint.setAuthConstraint(true);
context.addConstraint(constraint );

//设置使用httpOnly
context.setUseHttpOnly(true);
}
});
return tomcatServletContainerFactory;
}
}

如果 SpringBoot 的版本低于2.0,可以将 TomcatServletWebServerFactory替换成 EmbeddedServletContainerFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Configuration
public class TomcatConfig {
@Bean
public TomcatServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() {
@Override
protected void postProcessContext(Context context) {
SecurityConstraint securityConstraint = new SecurityConstraint();
securityConstraint.setUserConstraint("CONFIDENTIAL");
SecurityCollection collection = new SecurityCollection();
collection.addPattern("/*");
collection.addMethod("HEAD");
collection.addMethod("PUT");
collection.addMethod("DELETE");
collection.addMethod("OPTIONS");
collection.addMethod("TRACE");
collection.addMethod("COPY");
collection.addMethod("SEARCH");
collection.addMethod("PROPFIND");
securityConstraint.addCollection(collection);
context.addConstraint(securityConstraint);
}
};
//如果需要禁用TRACE请求,需添加以下代码:
tomcat.addConnectorCustomizers(connector -> {
connector.setAllowTrace(true);
});
return tomcat;
}
}

Java反射机制是在运行过程中,对于任意一个类,能够动态的获取对象的属性和方法

作用

  1. 获取类的变量,调用类的私有方法
  2. 增加代码的灵活性

获取类的Class对象

通过对象的getClass()方法

1
2
3
String str = "Hello World";
Class<?> clazz = str.getClass();
System.out.println(clazz);

输入结果

1
class java.lang.String

通过类的.class属性

1
2
Class<?> clazz2 = String.class;
System.out.println(clazz2);

输出结果

1
class java.lang.String

通过Class类的静态方法forName(String className)

1
2
Class<?> clazz3 = Class.forName("java.lang.String");
System.out.println(clazz3);

输出结果

1
class java.lang.String

注意:使用这种方法,需要写上包名,不需要写.class

应用场景

先创建一个实体类User,具体内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class User {

private int id;
private String name;
private String password;
private int level;
private String phone;

public User() {
}

public User(int id, String name, String password, int level, String phone) {
super();
this.id = id;
this.name = name;
this.password = password;
this.level = level;
this.phone = phone;
}

public User(int id, String name, String password) {
super();
this.id = id;
this.name = name;
this.password = password;
}

private User(int id, String name) {
super();
this.id = id;
this.name = name;
}
}

获取所有的构造方法

  • public Constructor<?>[] getConstructors()

获得所有 public 访问权限的构造方法

1
2
3
4
5
6
7
Class<?> clazz = Class.forName("org.spirit.lemon.reflect.User");

System.out.println("获取所有的公共构造方法:");
Constructor<?>[] constructors = clazz.getConstructors();
for (int i = 0; i < constructors.length; i++) {
System.out.println(constructors[i]);
}

输出结果

1
2
3
4
获取所有的公共构造方法:
public org.spirit.lemon.reflect.User(java.lang.Integer,java.lang.String,java.lang.String)
public org.spirit.lemon.reflect.User(java.lang.Integer,java.lang.String,java.lang.String,int,java.lang.String)
public org.spirit.lemon.reflect.User()

可以看出,只输出了三个public修饰的构造方法,私有的构造方法并未输出

  • public Constructor getConstructor(Class<?>… parameterTypes)

获得指定的构造方法,注意只能获得 public 权限的构造方法,其他访问权限的获取不到

1
2
3
4
5
6
7
8
System.out.println("获取指定的公共构造方法:");
Constructor<?> constructor = clazz.getConstructor(Integer.class, String.class, String.class);
System.out.println(constructor);
System.out.println();

System.out.println("私有构造方法测试:");
constructor = clazz.getConstructor(Integer.class, String.class);
System.out.println(constructor);

输出结果

1
2
3
4
5
6
7
8
获取指定的公共构造方法:
public org.spirit.lemon.reflect.User(java.lang.Integer,java.lang.String,java.lang.String)

私有构造方法测试:
Exception in thread "main" java.lang.NoSuchMethodException: org.spirit.lemon.reflect.User.<init>(java.lang.Integer, java.lang.String)
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getConstructor(Class.java:1825)
at org.spirit.lemon.reflect.Test.main(Test.java:37)

可以看到,只能获取public修饰的构造方法,在获取private修饰的构造方法时,抛出了异常信息

  • public Constructor<?>[] getDeclaredConstructors()

获得所有的构造方法,包括(public, private,protected,默认权限的)

1
2
3
4
5
System.out.println("获取所有的公共构造方法(包括私有构造方法):");
Constructor[] constructors = clazz.getDeclaredConstructors();
for (int i = 0; i < constructors.length; i++) {
System.out.println(constructors[i]);
}

输出结果

1
2
3
4
5
获取所有的公共构造方法(包括私有构造方法):
private org.spirit.lemon.reflect.User(java.lang.Integer,java.lang.String)
public org.spirit.lemon.reflect.User(java.lang.Integer,java.lang.String,java.lang.String)
public org.spirit.lemon.reflect.User(java.lang.Integer,java.lang.String,java.lang.String,int,java.lang.String)
public org.spirit.lemon.reflect.User()

不仅输出了public修饰的构造方法,private的构造方法也有输出

  • public Constructor getDeclaredConstructor(Class<?>… parameterTypes)

获得指定的构造方法,注意可以获取到任何访问权限的构造方法。

1
2
3
System.out.println("获取指定的构造方法(私有构造方法也可以获取):");
Constructor constructor = clazz.getDeclaredConstructor(Integer.class, String.class);
System.out.println(constructor);

输出结果

1
2
获取指定的构造方法(私有构造方法也可以获取):
private org.spirit.lemon.reflect.User(java.lang.Integer,java.lang.String)

可以看出,private修饰的构造方法也可以获取到

通过构造方法构建实例对象

获取到构造方法Constructor的对象之后,可以通过Constructor对象创建类的实例

1
2
3
Constructor constructor = clazz.getDeclaredConstructor(Integer.class, String.class, String.class);
User user = (User)constructor.newInstance(1, "lemon", "a12345+");
System.out.println(user.toString());

输出结果

1
User [id=1, name=lemon, password=a12345+, level=null, phone=null]

不过私有构造方法好像是不能通过这个方式创建对象的

1
2
3
Constructor constructor = clazz.getDeclaredConstructor(Integer.class, String.class);
User user = (User)constructor.newInstance(1, "lemon");
System.out.println(user.toString());

输出结果

1
2
3
4
5
6
Exception in thread "main" java.lang.IllegalAccessException: Class org.spirit.lemon.reflect.Test can not access a member of class org.spirit.lemon.reflect.User with modifiers "private"
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
at java.lang.reflect.Constructor.newInstance(Constructor.java:413)
at org.spirit.lemon.reflect.Test.main(Test.java:55)

抛出异常,对象创建失败

通过Class对象创建实例对象

1
2
User user2 = (User)clazz.newInstance(); //User.class.newInstance()也可以实现
System.out.println(user2.toString());

输出结果

1
User [id=0, name=null, password=null, level=null, phone=null]

更多创建对象的方法可以参考Java 创建对象的几种方式

获取属性

添加Student类,继承User的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class Student extends  User{

private String cardNo;
private Integer profession;
public String cls;

public Student() {

}

public Student(String cardNo, Integer profession) {
super();
this.cardNo = cardNo;
this.profession = profession;
}

public String getCardNo() {
return cardNo;
}

public void setCardNo(String cardNo) {
this.cardNo = cardNo;
}

public Integer getProfession() {
return profession;
}

public void setProfession(Integer profession) {
this.profession = profession;
}

public String getCls() {
return cls;
}

public void setCls(String cls) {
this.cls = cls;
}

@Override
public String toString() {
return "Student [cardNo=" + cardNo + ", profession=" + profession + ", cls=" + cls + ", id=" + id + ", name=" + name
+ ", password=" + password + ", level=" + level + ", phone=" + phone + "]";
}
}
  • public Field[] getDeclaredFields()

返回 Field 对象的一个数组,包括public、private、protected和default属性,但是不包括继承的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Class<?> clazz = Student.class;	

Student student = new Student();
student.setCardNo("1001");
student.setProfession(11055225);
student.setCls("数媒2班");

Field[] fields = clazz.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
field.setAccessible(true); //设置属性可访问,否则私有属性无法获取值,抛出IllegalAccessException

System.out.println(field);//属性字段对象

System.out.println(field.getName());//属性名
System.out.println(field.getType());//属性的类型

System.out.println(field.get(student));//获取对象指定属性的值

System.out.println("-------------------------");
}

输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private java.lang.String org.spirit.lemon.reflect.Student.cardNo
cardNo
class java.lang.String
1001
-------------------------
private java.lang.Integer org.spirit.lemon.reflect.Student.profession
profession
class java.lang.Integer
11055225
-------------------------
public java.lang.String org.spirit.lemon.reflect.Student.cls
cls
class java.lang.String
数媒2
-------------------------
  • public Field[] getFields()

获取类的公共属性,包括继承的属性;私有属性无法获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Field[] fields = clazz.getFields();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
field.setAccessible(true);

System.out.println(field);

System.out.println(field.getName());
System.out.println(field.getType());

System.out.println(field.get(student));

System.out.println("-------------------------");
}

输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public java.lang.String org.spirit.lemon.reflect.Student.cls
cls
class java.lang.String
数媒2
-------------------------
public int org.spirit.lemon.reflect.User.id
id
int
0
-------------------------
public java.lang.String org.spirit.lemon.reflect.User.name
name
class java.lang.String
null
-------------------------
public java.lang.String org.spirit.lemon.reflect.User.password
password
class java.lang.String
null
-------------------------
public java.lang.Integer org.spirit.lemon.reflect.User.level
level
class java.lang.Integer
null
-------------------------
public java.lang.String org.spirit.lemon.reflect.User.phone
phone
class java.lang.String
null
-------------------------

可以看出,cardNo和profession两个私有属性没有输出

  • public Field getDeclaredField(String name)

获取指定属性的Field对象,包括private属性;不可以获取继承自父类的属性

1
2
3
4
5
6
7
8
9
Field field = clazz.getDeclaredField("cls");
field.setAccessible(true);

System.out.println(field);

System.out.println(field.getName());
System.out.println(field.getType());

System.out.println(field.get(student));

输出结果

1
2
3
4
public java.lang.String org.spirit.lemon.reflect.Student.cls
cls
class java.lang.String
数媒2班
  • public Field getField(String name)

获取指定属性的Field对象,包括继承自父类的属性;只能获取public修饰的属性

1
2
3
4
5
6
7
8
9
Field field = clazz.getField("cls");
field.setAccessible(true);

System.out.println(field);

System.out.println(field.getName());
System.out.println(field.getType());

System.out.println(field.get(student));

输出结果

1
2
3
4
public java.lang.String org.spirit.lemon.reflect.User.name
name
class java.lang.String
null

获取方法

在User类中添加方法

1
2
3
public void test() {
System.out.println("User");
}

在Student类中添加私有方法

1
2
3
private void test2() {
System.out.println("Student");
}
  • public Method[] getDeclaredMethods()

获取所有的方法,包括private方法,但是不包括继承自父类的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Method[] methods = clazz.getDeclaredMethods();
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
System.out.println(method);
System.out.println(method.getName());//方法名
System.out.println(method.getReturnType());//返回值

if (method.getName().contains("test2")) {
method.setAccessible(true);//设置可访问,否则私有方法执行报错
System.out.println("执行test2方法:");
method.invoke(student, args);//执行方法
}
System.out.println("---------分割线----------");
}

输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public java.lang.String org.spirit.lemon.reflect.Student.toString()
toString
class java.lang.String
---------分割线----------
public void org.spirit.lemon.reflect.Student.setCls(java.lang.String)
setCls
void
---------分割线----------
private void org.spirit.lemon.reflect.Student.test2()
test2
void
执行test2方法:
Student
---------分割线----------
public void org.spirit.lemon.reflect.Student.setCardNo(java.lang.String)
setCardNo
void
---------分割线----------
public void org.spirit.lemon.reflect.Student.setProfession(java.lang.Integer)
setProfession
void
---------分割线----------
public java.lang.String org.spirit.lemon.reflect.Student.getCardNo()
getCardNo
class java.lang.String
---------分割线----------
public java.lang.String org.spirit.lemon.reflect.Student.getCls()
getCls
class java.lang.String
---------分割线----------
public java.lang.Integer org.spirit.lemon.reflect.Student.getProfession()
getProfession
class java.lang.Integer
---------分割线----------

可以看出,User的方法test并没有获取到

  • public Method[] getMethods()

获取类的方法,包括继承自父类的方法;但是只能获取public修饰的方法,私有方法不能获取

1
2
3
4
5
6
7
8
9
Method[] methods = clazz.getMethods();
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
System.out.println(method);
System.out.println(method.getName());//方法名
System.out.println(method.getReturnType());//返回值

System.out.println("---------分割线----------");
}

输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public java.lang.String org.spirit.lemon.reflect.Student.toString()
toString
class java.lang.String
---------分割线----------
public void org.spirit.lemon.reflect.Student.setProfession(java.lang.Integer)
setProfession
void
---------分割线----------
public void org.spirit.lemon.reflect.Student.setCardNo(java.lang.String)
setCardNo
void
---------分割线----------
public void org.spirit.lemon.reflect.Student.setCls(java.lang.String)
setCls
void
---------分割线----------
public java.lang.String org.spirit.lemon.reflect.Student.getCardNo()
getCardNo
class java.lang.String
---------分割线----------
public java.lang.Integer org.spirit.lemon.reflect.Student.getProfession()
getProfession
class java.lang.Integer
---------分割线----------
public java.lang.String org.spirit.lemon.reflect.Student.getCls()
getCls
class java.lang.String
---------分割线----------
public void org.spirit.lemon.reflect.User.test()
test
void
---------分割线----------
public final void java.lang.Object.wait() throws java.lang.InterruptedException
wait
void
---------分割线----------
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
wait
void
---------分割线----------
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
wait
void
---------分割线----------
public boolean java.lang.Object.equals(java.lang.Object)
equals
boolean
---------分割线----------
public native int java.lang.Object.hashCode()
hashCode
int
---------分割线----------
public final native java.lang.Class java.lang.Object.getClass()
getClass
class java.lang.Class
---------分割线----------
public final native void java.lang.Object.notify()
notify
void
---------分割线----------
public final native void java.lang.Object.notifyAll()
notifyAll
void
---------分割线----------

可以看到Student的私有方法test2()并没有打印出来

  • public Method getDeclaredMethod(String name, Class<?>… parameterTypes)

获取本类指定的方法,包括私有方法;但是不能获取继承自父类的方法

1
2
3
4
5
6
7
Method method = clazz.getDeclaredMethod("test2", null);
System.out.println(method);
System.out.println(method.getName());//方法名
System.out.println(method.getReturnType());//返回值

method.setAccessible(true);
method.invoke(student, args);

输出结果

1
2
3
4
private void org.spirit.lemon.reflect.Student.test2()
test2
void
Student
  • public Method getMethod(String name, Class<?>… parameterTypes)

获取指定的方法,包括继承自父类的方法,但是只能获取public方法

1
2
3
4
5
Method method = clazz.getMethod("test", null);
System.out.println(method);
System.out.println(method.getName());//方法名
System.out.println(method.getReturnType());//返回值
method.invoke(student, args);

输出结果

1
2
3
4
public void org.spirit.lemon.reflect.User.test()
test
void
User

获取注解

通过Class对象也可以获取对应的类的注解信息。自定义注解就可以通过获取类/方法/属性的注解信息来判定是否需要进行必要的逻辑处理。

在类Student的声明上添加两个注解,如下:

1
2
3
4
5
@Description("test")
@Deprecated
public class Student extends User{

}
  • public Annotation[] getAnnotations()

获取类的注解信息,返回注解类的对象数组;可以获取到继承自父类的注解信息(@Inherited修饰的注解才行)

1
2
3
4
5
Annotation[] annotations = clazz.getAnnotations();
for (int i = 0; i < annotations.length; i++) {
Annotation annotation = annotations[i];
System.out.println(annotation);
}

输出结果

1
2
@com.sun.org.glassfish.gmbal.Description(key=, value=test)
@java.lang.Deprecated()

可以看出,获取到了类的注解信息

  • public Annotation[] getDeclaredAnnotations()

获取直接作用在类上的注解信息,不包含继承的注解

获取类的指定类型的注解信息,如果该类配置了指定注解,返回注解的对象信息,否则返回null

1
2
3
4
5
Annotation annotation = clazz.getDeclaredAnnotation(Deprecated.class);
System.out.println(annotation);

annotation = clazz.getDeclaredAnnotation(Documented.class);
System.out.println(annotation);

输出结果

1
2
@java.lang.Deprecated()
null

Student声明了Deprecated注解,未声明Documented注解,所以第二行输出null

其他

也可以通过Class类获取包(Package)、修饰符、父类、泛型等其他信息,此处不做介绍了。

总结

个人在开发过程中用到反射的地方并不是很多,但是使用的框架中(如Spring)大量的应用了反射技术,了解反射的使用方法有助于了解这些框架的实现;

此外,反射会额外的消耗系统资源,效率上较直接创建对象、方法调用较差,但是更加灵活、方便。

待补充

  • 反射的实现原理

在浏览器中输入网址并回车后发生了什么?

在浏览器中输入网址并回车后,浏览器先在本地 hosts 文件中查询域名和 IP 的对应关系,如果有,则向 IP 地址发送请求,如果没有,则去 DNS 服务器查询。

DispatcherServlet

DispatcherServlet 是 SpringMVC 的核心控制器,用于处理 HTTP 请求处理和控制。

dispatcher,即调度员

特点

  • 基于 JavaBean 配置机制
  • 可以使用任何的 HandlerMapping 实现来控制对处理程序对象的请求的路由,默认是 BeanNameUrlHandlerMapping 和 RequestMappingHandlerMapping
  • TODO

主要流程:

1、文件上传解析,如果请求类型是multipart将通过MultipartResolver进行文件上传解析;

2、通过HandlerMapping,将请求映射到处理器(返回一个HandlerExecutionChain,它包括一个处理器、多个HandlerInterceptor拦截器);

3、通过HandlerAdapter支持多种类型的处理器(HandlerExecutionChain中的处理器);

4、通过ViewResolver解析逻辑视图名到具体视图实现;

5、本地化解析;

6、渲染具体的视图等;

7、如果执行过程中遇到异常将交给HandlerExceptionResolver来解析。

最终调用的方法为 doDispatch。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;

WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

try {
ModelAndView mv = null;
Exception dispatchException = null;

try {
//检查是否是文件上传请求
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);

// 获取当期请求的HandlerExecutionChain
mappedHandler = getHandler(processedRequest);
//返回null时。会调用noHandlerFound,抛出异常或返回请求找不到的错误信息
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}

// 获取当前请求的处理器适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}

if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}

// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

if (asyncManager.isConcurrentHandlingStarted()) {
return;
}

applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}

checkMultipart 检查是否是文件上传请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, " +
"this typically results from an additional MultipartFilter in web.xml");
}
//文件解析异常
else if (hasMultipartException(request) ) {
logger.debug("Multipart resolution failed for current request before - " +
"skipping re-resolution for undisturbed error rendering");
}
else {
try {
//请求转化
return this.multipartResolver.resolveMultipart(request);
}
catch (MultipartException ex) {
if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
logger.debug("Multipart resolution failed for error dispatch", ex);
// Keep processing error dispatch with regular request handle below
}
else {
throw ex;
}
}
}
}
// 如遇异常或非文件上传请求,则返回原始请求
return request;
}

getHandler

实现如下,返回当前请求对应的 HandlerExecutionChain。

1
2
3
4
5
6
7
8
9
10
11
12
13
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
//省略日志信息

//根据请求去获取对应的处理器执行链
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
//没有找到对应的处理器执行链,则返回null
return null;
}

HandlerMapping 都存放在 handlerMappings 变量中,

1
private List<HandlerMapping> handlerMappings;

在 initStrategies 方法中进行 handlerMappings 的初始化

1
2
3
4
5
6
7
8
9
10
11
12
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
//初始化handlerMappings
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}

initHandlerMappings 的作用是初始化所有的HandlerMapping,具体实现如下:

ancestor 祖先

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;

//是否加载所有的 HandlerMapping
if (this.detectAllHandlerMappings) {
// 找到ApplicationContext中的所有HandlerMapping,包括上级的上下文
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
// 对HandlerMapping进行排序
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else {
//加载单一的HandlerMapping
try {
//public static final String HANDLER_MAPPING_BEAN_NAME = "handlerMapping";
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
}

// 注册一个默认的HandlerMapping,以保证至少有一个HandlerMapping存在
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
}
}
}

注册默认的HandlerMapping

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
String key = strategyInterface.getName();
String value = defaultStrategies.getProperty(key);
if (value != null) {
String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
List<T> strategies = new ArrayList<T>(classNames.length);
for (String className : classNames) {
try {
Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
Object strategy = createDefaultStrategy(context, clazz);
strategies.add((T) strategy);
}
catch (ClassNotFoundException ex) {
throw new BeanInitializationException(
"Could not find DispatcherServlet's default strategy class [" + className +
"] for interface [" + key + "]", ex);
}
catch (LinkageError err) {
throw new BeanInitializationException(
"Error loading DispatcherServlet's default strategy class [" + className +
"] for interface [" + key + "]: problem with class file or dependent class", err);
}
}
return strategies;
}
else {
return new LinkedList<T>();
}
}

找不到请求对应的处理器映射器时,执行noHandlerFound方法,响应结束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (pageNotFoundLogger.isWarnEnabled()) {
pageNotFoundLogger.warn("No mapping found for HTTP request with URI [" + getRequestUri(request) +
"] in DispatcherServlet with name '" + getServletName() + "'");
}

if (this.throwExceptionIfNoHandlerFound) {
//抛出异常
throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request),
new ServletServerHttpRequest(request).getHeaders());
}
else {
//返回HttpServletResponse.SC_NOT_FOUND,即404
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}

获取处理器适配器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
//handlerAdapters 处理器适配器集合,存放所有的处理器适配器
for (HandlerAdapter ha : this.handlerAdapters) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler adapter [" + ha + "]");
}
// 检查适配器是否支持给定的处理器
if (ha.supports(handler)) {
return ha;
}
}
// 找不到对应的适配器则抛出异常
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

HandlerMapping

接口

请求到处理器的映射,如果映射成功返回一个 HandlerExecutionChain 对象(包含一个 Handler 处理器(页面控制器)对象、多个 HandlerInterceptor 拦截器)对象;如 BeanNameUrlHandlerMapping 将 URL 与 Bean 名字映射,映射成功的 Bean 就是此处的处理器。

handler 处理器,其实就是 Controller

former 前面的,前者

wrapped 包裹,包含

optionally 可选

accompanied 伴随着

parameterize 参数

unusual 特殊的,不同寻常的

flexible 灵活的

custom 定制的

开发人员可以实现该接口,但是并没有太大的必要,因为 SpringMVC 提供了两个处理器:BeanNameUrlHandlerMapping 和 DefaultAnnotationHandlerMapping,默认的处理器是 BeanNameUrlHandlerMapping 。

HandlerMapping 实现类可以提供匹配的拦截器,但是这些可选的,并不是必须要提供。处理器始终在 HandlerExecutionChain 实例中,可选地伴随着某些 HandlerInterceptor 实例 。 DispatcherServlet 将以指定的顺讯先调用每个 HandlerInterceptor 的 preHandle 方法, 如果每个 preHandle 方法都返回 true, 才会执行处理器的方法。

参数化映射的能力是 SpringMVC 框架提供的强大的不同寻常的功能。例如,开发人员可以根据 session 状态、cookie 状态或其他变量去编写一个自定义的映射器。其他的 MVC 框架并没有这么灵活。

实现类可以去实现 org.springframework.core.Ordered 接口, 指定排序,以保证 DispatcherServlet 的优先级。无序的实例以最低优先级对待。

使用了模板方法模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public interface HandlerMapping {

/**
* HttpServletRequest中包含处理器映射路径的属性的名称
* 模式匹配和完整的相关 URI(通常在 DispatcherServlet 的映射中)例外
* relevant 相应的
* scenarios 场景
* 注意:该属性对所有 HandlerMapping 实现来说不是必需的。
* 基于 URL 的 HandlerMappings 通常会支持它,但处理程序不要求在所有场景中都存在此请求属性。
*/
String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";

/**
* HttpServletRequest包含处理程序映射中最佳匹配模式的属性的名称。
* 注意:该属性对所有 HandlerMapping 实现来说不是必需的。
* 基于 URL 的 HandlerMappings 通常会支持它,但处理程序不要求在所有场景中都存在此请求属性。
*/
String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";

/**
* 布尔 HttpServletRequest 属性的名称,指示是否应检查类型级别映射。
* indicate 指示
* 注意: 该属性对所有 HandlerMapping 实现来说不是必需的。
*/
String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";

/**
* HttpServletRequest 包含 URI 模板映射的属性的名称,将变量名称映射到值。
* 注意:该属性对所有 HandlerMapping 实现来说不是必需的。
* 基于 URL 的 HandlerMappings 通常会支持它,但处理程序不要求在所有场景中都存在此请求属性。
*/
String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";

/**
* HttpServletRequest 包含具有 URI 变量名称的映射的属性的名称
* 注意:所有 HandlerMapping 实现都不需要此属性,也可能不存在此属性,
* 具体取决于 HandlerMapping 是否配置为保持矩阵变量内容
*/
String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables";

/**
* HttpServletRequest 包含适用于映射处理程序的可生成 MediaType 集的属性的名称
* 注意:该属性对所有 HandlerMapping 实现来说不是必需的。
* 基于 URL 的 HandlerMappings 通常会支持它,但处理程序不要求在所有场景中都存在此请求属性。
*/
String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";

/**
* 返回一个 HandlerExecutionChain 对象,该对象中包含了该请求的处理器和所有的拦截器。
* 可以根据请求的 URL, session 的状态,或实现类选择的任何因素进行选择。
* HandlerExecutionChain 对象中包含的处理器是 Object 类型的,而不是一个标记接口,所以处理器不会受到任何限制。
* 比如,可以编写一个 HandlerAdapter 允许使用另一个框架的处理器。
* 如果没有找到匹配的处理器,会返回 null,这并不是错误。
* DispatcherServlet 会查询所有注册的 HandlerMapping 以找到对应的匹配项,如果没有找到处理器,才会判定出错
* 内部的
* @param request 当前的HTTP请求
* @return 包含处理器对象和所有拦截器的 HandlerExecutionChain 对象,如果没找到,返回 null
* @throws 如果发生一个内部的错误,则抛出异常
*/
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

}

Ordered

接口。

Ordered 是一个接口,可以被可比较的对象实现,比如 Collection 类。

actual 实际

interpreted 解释执行,解读

prioritization 优先

实际的排序可以被解读为优先级,值越小,优先级越高,getOrder() 方法可以获取到具体的排序值。

此接口还有一个优先级的标记:PriorityOrdered,PriorityOrdered 对象表示的排序值始终在普通 Ordered对象表示的相同排序值之前起应用。

Consult 参考

precedence 优先权,位次

arbitrary 随意的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public interface Ordered {

/**
* 最高的优先级
*/
int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;

/**
* 最低的优先级
*/
int LOWEST_PRECEDENCE = Integer.MAX_VALUE;


/**
* 获取此对象的排序值
* 值越小优先级越高,值越大优先级越低
* 相同的排序值,将导致受影响的元素随意的排序
*/
int getOrder();

}

PriorityOrdered

Ordered 接口的扩展,表示优先级排序:

PriorityOrdered 对象表示的顺序值始终在普通 Ordered 对象表示的相同顺序值之前应用。

这主要是一个专用接口,用于首先识别优先级对象尤为重要的对象,甚至不需要获取剩余的对象。一个典型的例子:Spring中的优先级后处理器 ApplicationContext。

注意: PriorityOrdered 后处理器 bean 在特殊阶段初始化,超过其他后处理器 bean。这巧妙地影响了它们的自动装配行为:它们只会针对不需要急切初始化类型匹配的 bean 进行自动装配。

extension 扩展

recognize 识别

phase 阶段

1
2
3
public interface PriorityOrdered extends Ordered {

}

Order

注解类,定义了排序值

1
2
3
4
5
6
7
8
9
10
11
12
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Documented
public @interface Order {

/**
* 排序值
* 默认值为Ordered.LOWEST_PRECEDENCE,最低优先级
* @see Ordered#getOrder()
*/
int value() default Ordered.LOWEST_PRECEDENCE;
}

value 是可选的,作用和 Ordered 接口中的排序值一样,value 越小,优先级越高,默认值是 Ordered.LOWEST_PRECEDENCE,最低优先级。

从 Spring 4.0 开始,Spring 中的很多组件都支持基于 Order 注解的排序,即使目标组件的排序值(目标类或 @Bean 方法)考虑的集合注入也是如此。虽然此类排序值可能会影响注入点的优先级,但请注意它们不会影响单例启动顺序,这是由依赖关系和 @DependsOn(影响运行时确定的依赖关系)声明确定的正交关注点。

从 Spring 4.1 开始,标准 Priority 注解可以用作排序方案中该注解的替代品。请注意, @Priority 当必须选择单个元素是,可能会有其他语义。具体参见 AnnotationAwareOrderComparator#getPriority

也可以通过 Ordered 接口基于每个实例确定排序值,允许配置确定的实例值而不是附加到特定类的硬编码值。

standard 标准

semantics 语义

Order 注解可以用于类注解、方法注解、字段注解。

OrderUtils

用于根据类型声明确定对象顺序的常规实用程序 , 用于处理 Spring 的 Order 注解和 Java 的 Priority 注解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
public abstract class OrderUtils {

/**
* 优先级注解类型,使用 javax.annotation.Priority
*/
private static Class<? extends Annotation> priorityAnnotationType = null;

static {
try {
//兼容不同的 JDK 版本
priorityAnnotationType = (Class<? extends Annotation>)
ClassUtils.forName("javax.annotation.Priority", OrderUtils.class.getClassLoader());
}
catch (Throwable ex) {
// javax.annotation.Priority 无效,或者存在但是不可装载(JDK6)
}
}


/**
* 返回指定类型的排序值
* <p>Takes care of {@link Order @Order} and {@code @javax.annotation.Priority}.
* @param type 要处理的类型,即被注解的类
* @return 排序值,如果没有找到对应的类型,返回null
* @see #getPriority(Class)
*/
public static Integer getOrder(Class<?> type) {
return getOrder(type, null);
}

/**
* 返回指定类型的排序值,如果没有指定的类型,则返回默认的排序值
* <p>Takes care of {@link Order @Order} and {@code @javax.annotation.Priority}.
* @param type the type to handle
* @return the priority value, or the specified default order if none can be found
* @see #getPriority(Class)
*/
public static Integer getOrder(Class<?> type, Integer defaultOrder) {
//优先获取 Order 注解的值
Order order = AnnotationUtils.findAnnotation(type, Order.class);
if (order != null) {
return order.value();
}
// 如果没有声明 Order 注解, 返回 Priority 注解的值
Integer priorityOrder = getPriority(type);
if (priorityOrder != null) {
return priorityOrder;
}
// 如果两个注解都为 null, 则返回传入的默认排序值 defaultOrder
return defaultOrder;
}

/**
* 返回 {@code javax.annotation.Priority} 注解的值,如果类型上没有声明 Priority 注解,则返回 null
* @param type the type to handle
* @return the priority value if the annotation is declared, or {@code null} if none
*/
public static Integer getPriority(Class<?> type) {
if (priorityAnnotationType != null) {
// 获取类上面的 Priority 注解类对象
Annotation priority = AnnotationUtils.findAnnotation(type, priorityAnnotationType);
if (priority != null) {
// 获取注解类的 value 值
return (Integer) AnnotationUtils.getValue(priority);
}
}
//如果 Priority 注解没有加载,则直接返回 null
return null;
}

}

AbstractHandlerMapping

抽象类,实现了 HandlerMapping 和 Ordered 接口,同时继承了 WebApplicationObjectSupport 类。

支持排序,拥有一个默认的处理器,一系列处理器拦截器,包括路径模式映射的处理器拦截器。

该基础类不支持暴露 PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE。对此属性的支持取决于具体的子类,通常基于请求 URL 映射。

常用的子类有两个:AbstractHandlerMethodMapping 和 AbstractUrlHandlerMapping 。

AbstractHandlerMethodMapping 将具体的 Method 作为 Handler使用

AbstractUrlHandlerMapping 通过 URL 来进行匹配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//默认的处理器
private Object defaultHandler;

//UrlPathHelper 对象,进行 URL 路径匹配
private UrlPathHelper urlPathHelper = new UrlPathHelper();

//路径匹配器
private PathMatcher pathMatcher = new AntPathMatcher();

//拦截器列表
private final List<Object> interceptors = new ArrayList<Object>();

//拦截器处理器列表
private final List<HandlerInterceptor> adaptedInterceptors = new ArrayList<HandlerInterceptor>();

//
private final UrlBasedCorsConfigurationSource globalCorsConfigSource = new UrlBasedCorsConfigurationSource();

//CORS处理器
private CorsProcessor corsProcessor = new DefaultCorsProcessor();

//排序值,指定HandlerMapping实现类的执行顺序,默认为 Ordered.LOWEST_PRECEDENCE,最低优先级
private int order = Ordered.LOWEST_PRECEDENCE;

实现了 HandlerMapping 接口的 getHandler 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//获取当前请求的处理器,如果没找到对应的处理器,则返回默认的处理器
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}

// 返回的是Bean Name,则通过bean name创建处理器对象
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}

//获取执行链
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

// CORS 跨越资源共享 判断是否为跨域请求 跨越请求需特殊处理
if (CorsUtils.isCorsRequest(request)) {
CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}

getHandlerInternal 方法是一个抽象的方法,由具体的子类来实现,获取当前请求的处理器,如果没找到对应的处理器,则返回null。

1
protected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception;

getDefaultHandler 方法即返回默认的处理器

1
2
3
public Object getDefaultHandler() {
return this.defaultHandler;
}

getHandlerExecutionChain 方法用于获取请求的执行链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
//如果不存在,则创建一个HandlerExecutionChain对象
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

//获取请求的路径,相对路径,即Controller中方法配置的请求路径
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
//组装拦截器列表
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
if (interceptor instanceof MappedInterceptor) {
//自定义拦截器是否与当前路径匹配,如果相匹配,则加入到拦截器列表中
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
else {
chain.addInterceptor(interceptor);
}
}
return chain;
}

initApplicationContext() 方法,初始化时调用,初始化拦截器信息

HandlerInterceptor

分类

  • JDK
  • CGLIB
  • Javassist
  • ASM

JDK 动态代理

在 JDK 动态代理中,要实现代理逻辑类必须去实现 java.lang.reflect.InvocationHandler 接口

必须提供接口和实现

接口

1
2
3
public interface Hello {
void say();
}

实现类

1
2
3
4
5
6
7
public class HelloImpl implements Hello{

@Override
public void say() {
System.out.println("hello world!");
}
}

代理对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class HelloProxy implements InvocationHandler {

//真实对象
private Object target = null;

/**
* 建立代理对象和真实对象的代理关系,并返回代理对象
* @param target 真实对象
* @return 代理对象
*/
public Object bind(Object target) {
this.target = target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
System.out.println("进入代理逻辑方法");
System.out.println("在调度真实对象之前的服务");
Object obj = method.invoke(target, args);//相当于调用say方法
System.out.println("在调度真实对象之后的服务");
return obj;
}
}

Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this)

  • 第一个参数是类加载器
  • 第二个参数是把生成的动态代理对象挂在那些接口下
  • 第三个参数是定义实现方法逻辑的代理类,这里是当前对象,必须实现 InvocationHandler 的 invoke 方法

测试

1
2
3
4
5
6
7
public class Test {
public static void main(String[] args) throws Exception{
HelloProxy helloProxy = new HelloProxy();
Hello proxy = (Hello) helloProxy.bind(new HelloImpl());
proxy.say();
}
}

结果

1
2
3
4
进入代理逻辑方法
在调度真实对象之前的服务
hello world!
在调度真实对象之后的服务

CGLIB动态代理

优势在于不需要提供接口,只要一个非抽象类就能实现动态代理

被代理类

1
2
3
4
5
6
public class HelloImpl2 {

public void say() {
System.out.println("hello world!");
}
}

代理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class CglibProxy implements MethodInterceptor {

/**
* 生成CGLIB代理对象
* @param clazz Class类
* @return 代理对象
*/
public Object getProxy(Class clazz) {
//CGLIB enhancer 增强类对象
Enhancer enhancer = new Enhancer();
//设置增强类型
enhancer.setSuperclass(clazz);
//定义代理逻辑对象为当前对象,要求当前对象实现MethodInterceptor的intercept方法
enhancer.setCallback(this);
//生成并返回代理对象
return enhancer.create();
}

/**
* 代理逻辑方法
* @param proxy 代理对象
* @param method 方法
* @param args 方法参数
* @param methodProxy 方法代理
* @return 代理逻辑返回
* @throws Throwable
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("调用真实对象前");
Object result = methodProxy.invokeSuper(proxy, args);
System.out.println("调用真实对象后");
return result;
}
}

测试

1
2
3
4
5
6
7
public class Test {
public static void main(String[] args) throws Exception{
CglibProxy proxy = new CglibProxy();
HelloImpl2 obj = (HelloImpl2) proxy.getProxy(HelloImpl2.class);
obj.say();
}
}

结果

1
2
3
调用真实对象前
hello world!
调用真实对象后