Spring学习

你要问我Spring是什么,我不知道我认为是Bean工厂,利用IoC和AOP的概念,包含MVC框架.Spring Boot在Spring上进行了简化配置.Spring Cloud关注于全局的微服务整合和管理.

使用Spring+Spring MVC+Mybatis已经成了模板了,当然实际操作时可以直接上springboot-starter.

简介

Spring 是一个开源的轻量级 Java SE(Java 标准版本)/Java EE(Java 企业版本)开发应用框架,其目的是用于简化企业级应用程序开发。在传统应用程序开发中,一个完整的应用是由一组相互协作的对象组成的。所以开发一个应用除了要开发业务逻辑之外,最多的是关注使这些对象协作来完成所需功能的同时,实现低耦合、高内聚。所以,业务逻辑开发是不可避免的。如果有个框架可以帮我们来创建对象及管理这些对象之间的依赖关系,能通过配置方式来创建对象,管理对象之间依赖关系,我们不需要通过工厂和生成器来创建及管理对象之间的依赖关系,这样我们必然会减少许多工作量,加快开发。Spring 框架问世之初主要就是来完成这个功能

重点AOP IOC

Spring架构图

任何实际的应用程序都是由很多组件组成的,每个组件负责整个应用功能的一部分,这些组件需要与其他的应用元素进行协调以完成自己的任务。当应用程序运行时,需要以某种方式创建并引入这些组件。

介绍

Spring 的核心是提供了一个容器(container),通常称为 Spring 应用上下文(Spring application context),它们会创建和管理应用组件。这些组件也可以称为 bean,会在 Spring 应用上下文中装配在一起,从而形成一个完整的应用程序。这就像砖块、砂浆、木材、管道和电线组合在一起,形成一栋房子似的。

将 bean 装配在一起的行为是通过一种基于依赖注入(dependency injection,DI)的模式实现的。此时,组件不会再去创建它所依赖的组件并管理它们的生命周期,使用依赖注入的应用依赖于单独的实体(容器)来创建和维护所有的组件,并将其注入到需要它们的 bean 中。通常,这是通过构造器参数和属性访问方法来实现的。

在历史上,指导 Spring 应用上下文将 bean 装配在一起的方式是使用一个或多个 XML 文件

XML版本

1
2
3
4
5
6
7
<bean id="inventoryService"
class="com.example.InventoryService" />

<bean id="productService"
class="com.example.ProductService" />
<constructor-arg ref="inventoryService" />
</bean>

Java版本

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class ServiceConfiguration {
@Bean
public InventoryService inventoryService() {
return new InventoryService();
}

@Bean
public ProductService productService() {
return new ProductService(inventoryService());
}
}

@Configuration 注解会告知 Spring 这是一个配置类,会为 Spring 应用上下文提供 bean。这个配置类的方法使用 @Bean 注解进行了标注,表明这些方法所返回的对象会以 bean 的形式添加到 Spring 的应用上下文中(默认情况下,这些 bean 所对应的 bean ID 与定义它们的方法名称是相同的)。

相对于基于 XML 的配置方式,基于 Java 的配置会带来多项额外的收益,包括更强的类型安全性以及更好的重构能力。即便如此,不管是使用 Java 还是使用 XML 的显式配置,只有当 Spring 不能进行自动配置的时候才是必要的

在 Spring 技术中,自动配置起源于所谓的自动装配(autowiring)和组件扫描(component scanning)。借助组件扫描技术,Spring 能够自动发现应用类路径下的组件,并将它们创建成 Spring 应用上下文中的 bean。借助自动装配技术,Spring 能够自动为组件注入它们所依赖的其他 bean.

Spring MVC

diagram

Spring MVC 相关接口解释:

(1)DispatcherServlet

前端控制器,所有的请求都有经过它来统一分发,请求会被分发给对应的 Handler。

(2)HandlerMapping(处理器映射器)

解析请求链接,然后根据请求链接找到执行这个请求的类(HandlerMapping 所说的 handler)。

(3)HandlerAdapter(处理器适配器)

调用具体的方法对用户发来的请求来进行处理。

(4)Controller

Controller 将处理用户请求,Controller 处理完用户请求,则返回 ModelAndView 对象给 DispatcherServlet 前端控制器。

从宏观角度考虑,DispatcherServlet 是整个 Web 应用的控制器;从微观考虑,Controller 是单个 Http 请求处理过程中的控制器。

(5)ViewResolver(视图解析器)

解析 ModelMdoelAndView 中的逻辑视图名变为一个真正的 View 对象,并将 MdoelAndView 中的 Model 取出。

1
2
3
4
5
mvn archetype:generate \
-DgroupId=com.example \
-DartifactId=springboot \
-DarchetypeArtifactId=maven-archetype-quickstart \
-DinteractiveMode=false
  • Group Id:项目的组织机构,也是包的目录结构,一般都是域名的倒序,比如 com.sekyoro
  • Atifact Id:项目实际的名字,比如 SpringMVCTest
  • archetype Artifact Id:使用的 maven 骨架名称。

Spring Boot的引入

随着 Spring Boot 的引入,自动配置的能力已经远远超出了组件扫描和自动装配。Spring Boot 是 Spring 框架的扩展,提供了很多增强生产效率的方法。最为大家所熟知的增强方法就是自动配置(autoconfiguration),Spring Boot 能够基于类路径中的条目、环境变量和其他因素合理猜测需要配置的组件并将它们装配在一起。

Spring Boot 极大地改善了 Spring 的开发,因此很难想象在没有它的情况下如何开发 Spring 应用

Spring XML 配置是一种过时的方式,主要关注 Spring 基于 Java 的配置。

实战

使用 Spring Initializr 初始化应用

Spring Initializr 是一个基于浏览器的 Web 应用,同时也是一个 REST API,能够生成一个 Spring 项目结构的骨架,我们还可以使用各种想要的功能来填充它

使用 Spring Initializr 的几种方式如下:

  • 通过地址为 https://start.spring.io/ 的 Web 应用;
  • 在命令行中使用 curl 命令;
  • 在命令行中使用 Spring Boot 命令行接口;
  • 在 Spring Tool Suite 中创建新项目;
  • 在 IntelliJ IDEA 中创建新项目;
  • 在 NetBeans 中创建新项目。

image-20230127132557990

通过curl命令创建项目 https://start.spring.io/starter.tgz

1
2
3
4
5
6
7
8
9
10
11
12
curl https://start.spring.io/starter.tgz \
-d groupId=sia \
-d artifactId=taco-cloud \
-d packaging=jar \
-d name=taco-cloud \
-d packageName=tacos \
-d dependencies=web,devtools,thymeleaf \
-d type=maven-project \
-d baseDir=taco-cloud \
-d bootVersion=2.4.0 \
-d javaVersion=1.8 \
| tar -xzvf -

-d 指定额外的参数。

  • groupId:指定 groupId
  • artifactId:指定 artifactId
  • packaging:指定打包方式。
  • name:指定项目名,如果没有指定 applicationName,则将其作为 Application Name。
  • packageName:指定项目的包名。
  • dependencies:指定项目的依赖。
  • type:指定项目的类型(使用哪种构建工具)。
  • baseDir:指定项目的根目录名称。
  • bootVersion:指定 SpringBoot 的版本

image-20230128210555390

  • mvnwmvnw.cmd:这是 Maven 包装器(wrapper)脚本。借助这些脚本,即便你的机器上没有安装 Maven,也可以构建项目。
  • pom.xml:这是 Maven 构建规范,随后我们将会深入介绍该文件。
  • TacoCloudApplication.java:这是 Spring Boot 主类,它会启动该项目。随后,我们会详细介绍这个类。
  • application.properties:这个文件起初是空的,但是它为我们提供了指定配置属性的地方。在本章中,我们会稍微修改一下这个文件
  • static:在这个文件夹下,你可以存放任意为浏览器提供服务的静态内容(图片、样式表、JavaScript 等),该文件夹初始为空。
  • templates:这个文件夹中存放用来渲染内容到浏览器的模板文件。这个文件夹初始是空的,不过我们很快就会往里面添加 Thymeleaf 模板。
  • TacoCloudApplicationTests.java:这是一个简单的测试类,它能确保 Spring 应用上下文可以成功加载。在开发应用的过程中,我们会将更多的测试添加进来。

POM文件

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
<?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
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>sia</groupId>
<artifactId>taco-cloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>taco-cloud</name>
<description>Taco Cloud Example</description>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
<project.build.sourceEncoding>
UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>
UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>htmlunit-driver</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

项目属性

1
2
3
4
5
6
<modelVersion>4.0.0</modelVersion>
<groupId>sia</groupId>
<artifactId>taco-cloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>taco-cloud</name>
<description>Demo project for Spring Boot</description>

modelVersion 描述这个POM文件是遵从哪个版本的项目描述符

modelVersion:指定了当前POM模型的版本,对于Maven2及Maven 3来说,它只能是4.0.0;

<parent> 元素,更具体来说是它的 <version> 子元素。这表明我们的项目要以 spring-boot-starter-parent 作为其父 POM。除了其他的一些功能之外,这个父 POM 为 Spring 项目常用的一些库提供了依赖管理,现在你不需要指定它们的版本,因为这是通过父 POM 来管理的

1
2
3
4
5
6
   <parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<parent> 元素,更具体来说是它的 <version> 子元素。这表明我们的项目要以 spring-boot-starter-parent 作为其父 POM。除了其他的一些功能之外,这个父 POM 为 Spring 项目常用的一些库提供了依赖管理,现在你不需要指定它们的版本,因为这是通过父 POM 来管理的

2.0.4.RELEASE 表明要使用 Spring Boot 2.0.4,所以会根据这个版本的 Spring Boot 定义来继承依赖管理

<dependencies> 元素下声明了 4 个依赖。在某种程度上,你可能会对前两个更熟悉一些。它们直接对应我们使用 curl 创建新项目中所指定的 WebThymeleaf 依赖。第四个依赖 test 提供了很多有用的测试功能。我们没有必要专门指定它

4 个依赖的 Artifact ID 中有 3 个都有 starter 这个单词。Spring Boot starter 依赖的特别之处在于它们本身并不包含库代码,而是传递性地拉取其他的库。这种 starter 依赖主要有 3 个好处。

  • 构建文件会显著减小并且更易于管理,因为这样不必为每个所需的依赖库都声明依赖。
  • 我们能够根据它们所提供的功能来思考依赖,而不是根据库的名称。如果是开发 Web 应用,那么你只需要添加 web starter 就可以了,而不必添加一堆单独的库再编写 Web 应用。
  • 我们不必再担心库版本的问题。你可以直接相信给定版本的 Spring Boot,传递性引入的库的版本是兼容的。现在,你只需要关心使用的是哪个版本的 Spring Boot 就可以了。

构建规范还包含一个 Spring Boot 插件。这个插件提供了一些重要的功能。

  • 它提供了一个 Maven goal,允许我们使用 Maven 来运行应用。在后面小节,我们将会尝试这个 goal。
  • 它会确保依赖的所有库都会包含在可执行 JAR 文件中,并且能够保证它们在运行时类路径下是可用的。
  • 它会在 JAR 中生成一个 manifest 文件,将引导类(在我们的场景中,也就是 TacoCloudApplication)声明为可执行 JAR 的主类。

主类

1
2
3
4
5
6
7
8
9
10
11
12
13
package tacos;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class TacoCloudApplication {

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

}
  • @SpringBootConfiguration:将该类声明为配置类。尽管这个类目前还没有太多的配置,但是后续我们可以按需添加基于 Java 的 Spring 框架配置。这个注解实际上是 @Configuration 注解的特殊形式。
  • @EnableAutoConfiguration:启用 Spring Boot 的自动配置。我们随后会介绍自动配置的更多功能。就现在来说,我们只需要知道这个注解会告诉 Spring Boot 自动配置它认为我们会用到的组件。
  • @ComponentScan:启用组件扫描。这样我们能够通过像 @Component@Controller@Service 这样的注解声明其他类,Spring 会自动发现它们并将它们注册为 Spring 应用上下文中的组件

它的 main() 方法。这是 JAR 文件执行的时候要运行的方法。在大多数情况下,这个方法都是样板代码,我们编写的每个 Spring Boot 应用都会有一个类似或完全相同的方法(类名不同则另当别论)。

这个 main() 方法会调用 SpringApplication 中静态的 run() 方法,后者会真正执行应用的引导过程,也就是创建 Spring 的应用上下文。在传递给 run() 的两个参数中,一个是配置类,另一个是命令行参数。尽管传递给 run() 的配置类不一定要和引导类相同,但这是最便利和最典型的做法。

你可能并不需要修改引导类中的任何内容。对于简单的应用程序来说,你可能会发现在引导类中配置一两个组件是非常方便的,但是对于大多数应用来说,最好还是要为没有实现自动配置的功能创建一个单独的配置类。在本课程的整个过程中,我们将会创建多个配置类,所以请继续关注后续的细

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package tacos;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class) // <--- 使用Spring的运行器
@SpringBootTest // <--- Spring Boot测试
public class TacoCloudApplicationTests {

@Test // <--- 测试方法
public void contextLoads() {
}

}

这个类中只有一个空的测试方法。即便如此,这个测试类还是会执行必要的检查,确保 Spring 应用上下文能够成功加载。如果你所做的变更导致 Spring 应用上下文无法创建,那么这个测试将会失败,你就可以做出反应来解决相关的问题了

处理Web请求

Spring 自带了一个强大的 Web 框架,名为 Spring MVC。Spring MVC 的核心是控制器(controller)的理念。控制器是处理请求并以某种方式进行信息响应的类。在面向浏览器的应用中,控制器会填充可选的数据模型并将请求传递给一个视图,以便于生成返回给浏览器的 HTML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package tacos;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller // <--- 控制器
public class HomeController {

@GetMapping("/") // <--- 处理对根路径“/”的请求
public String home() {
return "home"; // <--- 返回视图名
}

}

可以看到,这个类带有 @Controller。就其本身而言,@Controller 并没有做太多的事情。它的主要目的是让组件扫描将这个类识别为一个组件。因为 HomeController 带有 @Controller,所以 Spring 的组件扫描功能会自动发现它,并创建一个 HomeController 实例作为 Spring 应用上下文中的 bean

有一些其他的注解与 @Controller 有着类似的目的(包括 @Component@Service@Repository)。你可以为 HomeController 添加上述的任意其他注解,其作用是完全相同的。但是,在这里选择使用 @Controller 更能描述这个组件在应用中的角色

“home”这个值将会被解析为视图的逻辑名。视图如何实现取决于多个因素,但是因为 Thymeleaf 位于类路径中,所以我们可以使用 Thymeleaf 来定义模板.

src/main/resources/templates 目录下创建 home.html 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<title>Taco Cloud</title>
</head>

<body>
<h1>Welcome to...</h1>
<img th:src="@{/images/TacoCloud.png}" />

<a th:href="@{/design}" id="design">Design a taco</a>
</body>
</html>

唯一需要注意的一行代码是用于展现 Taco Cloud Logo 的 <img> 标签。它使用了 Thymeleaf 的 th:src 属性和 @{...} 表达式,以便于引用相对于上下文路径的图片。除此之外,它就是一个 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
package tacos;

import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

@RunWith(SpringRunner.class)
@WebMvcTest(HomeController.class) // <--- 针对HomeController的Web测试
public class HomeControllerTest {

@Autowired
private MockMvc mockMvc; // <--- 注入MockMvc

@Test
public void testHomePage() throws Exception {
mockMvc.perform(get("/")) // <--- 发起对“/”的GET

.andExpect(status().isOk()) // <--- 期望得到HTTP 200

.andExpect(view().name("home")) // <--- 期望得到home视图

.andExpect(content().string( // <--- 期望包含“Welcome to...”
containsString("Welcome to...")));
}

}

运行

1
2
# 测试
mvn test
1
mvn clean spring-boot:run

Spring Boot DevTools

DevTools 为 Spring 开发人员提供了一些便利的开发期工具,其中包括:

  • 代码变更后应用会自动重启;
  • 当面向浏览器的资源(如模板、JavaScript、样式表)等发生变化时,会自动刷新浏览器(实验楼 WebIDE 中需要手动刷新 Web 服务的页面);
  • 自动禁用模板缓存;
  • 如果使用 H2 数据库的话,内置了 H2 控制台

Overview

pom.xml 文件中,我们声明了对 Web 和 Thymeleaf starter 的依赖。这两项依赖会传递引入大量其他的依赖,包括:

  • Spring 的 MVC 框架;
  • 嵌入式的 Tomcat;
  • Thymeleaf 和 Thymeleaf 布局方言;

它还引入了 Spring Boot 的自动配置库。当应用启动的时候,Spring Boot 的自动配置将会探测到这些库,并自动完成如下功能:

  • 在 Spring 应用上下文中配置 bean 以启用 Spring MVC;
  • 在 Spring 应用上下文中配置嵌入式的 Tomcat 服务器;
  • 配置 Thymeleaf 视图解析器,以便于使用 Thymeleaf 模板渲染 Spring MVC 视图。

创建控制类

Spring MVC 请求映射注解

新的请求映射注解具有和 @RequestMapping 完全相同的属性,所以我们可以在使用 @RequestMapping 的任何地方使用它们。

通常,只在类级别上使用 @RequestMapping,以便于指定基本路径。在每个处理器方法上,我会使用更具体的 @GetMapping@PostMapping 等注解。

1
2
3
4
5
@GetMapping
public String showDesignForm(Model model) {
model.addAttribute("design", new Taco());
return "design";
}

showDesignForm() 方法接下来的几行代码会根据配料类型过滤列表。配料类型的列表会作为属性添加到 Model 对象上,这个对象是以参数的形式传递给 showDesignForm() 方法的。Model 对象负责在控制器和展现数据的视图之间传递数据。实际上,放到 Model 属性中的数据将会复制到 Servlet Response 的属性中,这样视图就能在这里找到它们了。showDesignForm() 方法最后返回 design,这是视图的逻辑名称,会用来将模型渲染到视图上.

项目启动

Main() 方法启动

与普通的 Web 项目相比,Spring Boot 启动项目减少了几个中间步骤,不用去配置 Servlet 容器,也不用打包并且发布到 Servlet 容器再去启动,而是直接运行主方法即可启动项目,开发调试都十分方便也节省开发时间。在你的本机上开发项目时,可以直接在 Eclipse 或者 IDEA 中运行 Spring Boot 主程序类即可,比如现在的项目中有 Application 类,可以直接运行它的 run() 方法,项目就能够正常启动了。

Maven 插件启动

由于 pom.xml 文件中引入了 spring-boot-maven-plugin 插件依赖,也可以直接使用 Maven 命令来启动 Spring Boot 项目。插件配置如下,如果 pom.xml 文件中没有该 Maven 插件,是无法通过这种方式启动 Spring Boot 项目的,这一点需要注意。

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>

启动过程过程如下图所示,首先点击下方工具栏中的 Terminal 打开命令行窗口,之后在命令行中输入命令,首先是切换到 lou-springboot 目录,命令为 cd lou-springboot,之后执行命令 mvn spring-boot:run 即可启动项目,如下图所示,Spring Boot 项目启动成功。

图片描述

java -jar 命令启动

项目初始化时我们选择的打包方式为 Jar ,因此项目开发完成进行打包时的结果是一个 Jar 包, Java 运行 Jar 包的命令为 java -jar xxx.jar ,结合以上两个原因我们可以使用这种方式启动 Spring Boot 项目,接下来我们来演示这一过程。

  • 首先,点击下方工具栏中的 Terminal 打开命令行窗口,切换到 lou-springboot 目录,命令为 cd lou-springboot,这一步跟 Maven 命令启动一样
  • 之后使用 Maven 命令将项目打包,执行命令为:mvn clean package -Dmaven.test.skip=true,等待打包结果即可
  • 打包成功后进入 target 目录,cd target
  • 最后就是启动已经生成的 Jar 包,执行命令为 java -jar springboot-demo-0.0.1-SNAPSHOT.jar

业务层/类与对象

Controller
Service
Mapper(DAO)
Entity(Model,POJO)
Query
POJO PO VO DAO DTO
  • POJO 是一种简单的 Java 类,它不继承或实现任何特殊的接口或抽象类。
  • POJO 通常只包含数据属性和基本的 getter/setter 方法,没有任何业务逻辑。
  • POJO 是最基本的 Java 对象,用于在应用程序中传递和存储数据

  • PO 是持久化对象,通常用于与数据库进行交互。

  • PO 通常与数据库表结构一一对应,用于表示数据库中的一条记录。
  • PO 通常包含与数据库表字段一一对应的属性,以及基本的 CRUD (创建、读取、更新、删除) 操作

  • VO 是值对象,用于在应用程序的层与层之间传递数据。

  • VO 通常是 POJO 类,但可能会包含一些业务逻辑,如数据验证、格式化等。
  • VO 通常用于展示层和业务层之间的数据传输,以及不同模块之间的数据交互

  • DAO 是数据访问对象,用于封装与数据库的交互逻辑。

  • DAO 通常包含 CRUD 操作,并且隔离了数据访问的具体实现细节。
  • DAO 提供了一个统一的接口,使得业务逻辑层可以方便地访问数据库。

  • DTO 是数据传输对象,用于在应用程序的不同层级之间传递数据。

  • DTO 通常是一个 POJO 类,但不包含任何业务逻辑,只有数据属性和 getter/setter 方法。
  • DTO 的主要目的是减少在各层之间传输数据时的网络开销,以及隐藏内部实现细节。

常用注解

SpringBootApplication

包含三个注释

  • @EnableAutoConfiguration: 启用 Spring Boot 的自动配置机制
  • @ComponentScan:在应用程序所在的软件包上启用 @Component 扫描(请参阅最佳实践)
  • @SpringBootConfiguration:启用在上下文中注册额外的 Bean 或导入额外的配置类。它是 Spring 标准 @Configuration 的替代方案,有助于在集成测试中进行配置检测。

这是Spring Boot应用程序的主注解,用于标注主类

@Configuration:
标注一个类作为配置类,用于定义Spring应用程序上下文中的bean。

通常与@Bean注解一起使用,用于声明bean实例。

@EnableAutoConfiguration:

  • 告诉Spring Boot根据添加的jar依赖猜测您想如何配置Spring。
  • 比如添加了spring-boot-starter-web依赖,它会自动配置Tomcat和Spring MVC.

如果 @ComponentScan 注解没有指定 basePackagesbasePackageClasses 属性,那么它会默认扫描当前类所在的包及其子包

ComponentScan MapperScan

ComponentScan

  • 告诉Spring在com/example包(应用程序主类的所在包)及其子包中扫描@Component注解的类。
  • 这样可以自动发现和注册各种Spring组件,如@Controller@Service@Repository

    MapperScan

  • 如果 @MapperScan 注解指定了 basePackagesbasePackageClasses 属性,那么它会扫描这些指定的基础包及其子包。如果 @MapperScan 注解没有指定 basePackagesbasePackageClasses 属性,那么它会默认扫描当前类所在的包及其子包。

  1. 扫描和注册Mapper接口:
    • @MapperScan注解可以指定扫描的包路径,Spring Boot会自动扫描并注册这些包下的所有Mapper接口。
    • 这样就不需要手动在Spring容器中注册每个Mapper接口的bean。
  2. 自动关联Mapper XML文件:
    • 当Mapper接口和对应的Mapper XML文件位于同一个包结构下时,Spring Boot会自动关联并使用这些XML文件。
    • 开发者只需要编写Mapper接口和XML文件,不需要额外的配置。
  3. 支持自定义Mapper扫描规则:
    • @MapperScan注解支持多种属性配置,如basePackagesannotationClassmarkerInterface等,可以自定义Mapper接口的扫描范围。

RestController

  • 标注一个控制器类,用于处理RESTful API请求
  • 相当于@Controller@ResponseBody的结合

RequestMapping GetMapping PostMapping

映射URL

在类级别或方法级别使用@RequestMapping注解可以指定一个或多个URL路径,将HTTP请求映射到相应的控制器方法上

指定请求方法

@RequestMapping注解还可以指定请求方法,如GET、POST、PUT、DELETE等。这使得我们可以根据不同的HTTP方法来处理不同的业务逻辑

RequestParam

@RequestParam是Spring MVC中另一个重要的注解,它的主要作用是绑定HTTP请求参数到控制器方法的参数上

@RequestParam注解可以将HTTP请求参数绑定到控制器方法的参数上。当客户端发送请求时,Spring MVC会自动将请求参数的值赋值给对应的方法参数

当使用 @RequestParam 注解接受参数时,被注解的参数类型不需要实现任何特殊的序列化方法。

@RequestParam 注解的作用是将 HTTP 请求参数绑定到方法参数上,而不是将整个请求体绑定到一个对象上(如使用 @RequestBody 注解)。

对于使用 @RequestParam 注解的方法参数,只需要满足以下条件即可:

  1. 基本数据类型:
    • 参数可以是基本数据类型,如 intlongStringboolean 等。
    • 这些基本数据类型不需要实现任何特殊的序列化方法。
  2. POJO 类:
    • 参数也可以是一个 POJO 类。
    • 这个 POJO 类不需要实现任何特殊的序列化接口,如 Serializable 接口。
    • 类中的属性需要有公开的 getter 和 setter 方法,以便 Spring MVC 能够正确地读取和写入数据。

Configuration

Configuration

  • 标注一个类作为配置类,用于定义Spring应用程序上下文中的bean。
  • 通常与@Bean注解一起使用,用于声明bean实例

Service Component Repository

  1. Service:
    • 服务层组件,用于封装业务逻辑。
    • 通常包含复杂的业务规则、算法和计算。
    • 提供给上层(如 Controller) 调用的接口。
    • 示例: UserService, OrderService 等。
  2. Component:
    • 通用组件,用于封装通用功能和逻辑。
    • 可重用的功能模块,如日志记录器、缓存管理器等。
    • 被其他组件(如 Service 和 Repository) 依赖和使用。
    • 示例: EmailSender, SecurityManager 等。
  3. Repository:
    • 数据访问层组件,用于封装与数据库的交互。
    • 提供面向对象的数据访问接口,隐藏数据访问的细节。
    • 通常与 DAO (Data Access Object) 或 Mapper 等概念相关。
    • 示例: UserRepository, OrderRepository 等。

Resource Autowired

@Resource 注解:

  • 来源: 属于 Java EE 规范的一部分,不是 Spring 特有的。
  • 注入方式: 按照名称进行自动装配。
  • 使用方式:
    • 可以用于注入 Bean 属性、Setter 方法和构造函数参数。
    • 如果没有指定 name 属性,则默认使用属性名或方法名作为 Bean 名称。

@Autowired 注解:

  • 来源: 属于 Spring 框架特有的注解。
  • 注入方式: 按照类型进行自动装配。
  • 使用方式:
    • 可以用于注入 Bean 属性、Setter 方法和构造函数参数。
    • 如果存在多个匹配的 Bean,可以使用 @Qualifier 注解进行限定。

PathVariable

@PathVariable 注解的作用是从 URL 路径中提取参数值,并将其绑定到方法参数上。与 @RequestBody 不同的是,@PathVariable 是用于处理 URL 路径中的参数,而不是请求体。

1
2
3
4
5
6
7
8
9
@Controller
public class UserController {
@GetMapping("/users/{userId}")
@ResponseBody
public User getUser(@PathVariable("userId") Long userId) {
User user = userService.getUserById(userId);
return user;
}
}

RequestBody RequestParam RequestHeader

@RequestBody

  • 作用: 用于将 HTTP 请求体映射到方法参数上。
  • 使用场景: 当客户端以 JSON、XML 等格式发送请求体数据时,可以使用该注解将请求体数据绑定到方法参数上

@RequestBody 注解的作用是将 HTTP 请求体绑定到方法参数上,通常用于处理 JSON 或 XML 格式的请求数据。

实现类序列化的方法

除了实现 Serializable 接口之外,还有其他几种方式可以让参数类型满足 @RequestBody 注解的序列化和反序列化要求:

  1. 使用 Jackson 注解:
    • 可以使用 Jackson 库提供的一些注解,如 @JsonCreator@JsonProperty 等,来自定义序列化和反序列化的行为。
    • 这些注解可以帮助 Spring MVC 更好地将请求体转换为 Java 对象。
  2. 实现自定义的序列化和反序列化逻辑:
    • 如果需要更复杂的序列化和反序列化逻辑,可以自己实现 JsonSerializerJsonDeserializer 接口。
    • 然后在参数类型上使用 @JsonSerialize@JsonDeserialize 注解来指定自定义的序列化和反序列化器。
  3. 使用 Lombok 库:
    • Lombok 是一个第三方库,它可以通过注解的方式自动生成 POJO 类的样板代码,包括构造函数、getter/setter 方法等。
    • 使用 Lombok 可以大大简化 POJO 类的定义,同时也满足 @RequestBody 注解的要求。
  4. 使用 Spring 的 @JsonView 注解:
    • Spring 提供了 @JsonView 注解,可以用于控制哪些属性在序列化时应该被包含。
    • 这在某些情况下可能更灵活,比如不同的视图需要不同的属性集合

@RequestParam

  • 作用: 用于将 HTTP 请求参数绑定到方法参数上。
  • 使用场景: 当客户端以 URL 参数的形式传递数据时,可以使用该注解将请求参数绑定到方法参数上

@RequestHeader

  • 作用: 用于将 HTTP 请求头映射到方法参数上。
  • 使用场景: 当需要获取客户端请求头信息时,可以使用该注解将请求头数据绑定到方法参数上。

ResponseBody

当我们在控制器方法上使用@ResponseBody注解时,Spring MVC会将方法的返回值直接写入HTTP响应正文中,而不是渲染一个视图.

这在处理RESTful API时非常有用,因为我们通常希望将JSON或XML格式的数据直接返回给客户端,而不是渲染一个HTML页面.

  • 将方法返回值直接写入HTTP响应正文中
  • 支持返回各种数据类型

当使用 @ResponseBody 注解返回一个类时,这个类不需要实现任何特殊的序列化方法。

@ResponseBody 注解的作用是将方法的返回值直接写入 HTTP 响应体中,而不是渲染视图.Spring MVC 会自动将返回的 Java 对象转换为 HTTP 响应体的内容,通常是 JSON 或 XML 格式.

Param

@Param 注解是 Mybatis 框架中使用的一个注解,主要用于解决方法参数与 SQL 语句占位符之间的映射关系。

  • 用于标注方法参数,指定参数在 SQL 语句中的占位符名称。
  • 解决方法参数与 SQL 语句占位符之间的映射关系。

使用场景:

  • 在 Mybatis 的 Mapper 接口方法中使用。
  • 当方法参数太多或参数名称不直观时,可以使用 @Param 注解进行命名

Validated

  • 启用数据验证:

    • 当在 Bean 类或其方法参数上使用 @Validated 注解时,Spring 会自动启用数据验证功能。
  • 指定验证组:

    • @Validated 注解可以指定一个或多个验证组,用于选择应该应用哪些验证规则。
    • 验证组是一种逻辑分组,可以根据不同的场景应用不同的验证规则。

    通常情况下,@Validated 注解会与 Bean Validation API 中的各种验证注解(如 @NotNull@Email@Min 等)一起使用,以定义数据的验证规则.

    一种方法是在定义类时,声明验证规则和验证组,然后在controller或service层进行验证

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class User {
    @NotNull(groups = CreateUser.class)
    private String name;

    @Email(groups = {CreateUser.class, UpdateUser.class})
    private String email;

    @Min(value = 18, groups = CreateUser.class)
    private int age;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @RestController
    @RequestMapping("/users")
    public class UserController {
    @PostMapping
    public ResponseEntity<User> createUser(@Validated(CreateUser.class) @RequestBody User user) {
    // 调用 UserService 的 createUser() 方法
    return ResponseEntity.ok(user);
    }

    @PutMapping("/{id}")
    public ResponseEntity<User> updateUser(@PathVariable Long id,
    @Validated(UpdateUser.class) @RequestBody User user) {
    // 调用 UserService 的 updateUser() 方法
    return ResponseEntity.ok(user);
    }
    }
    • @Validated: 主要用于在 Service 层或控制器层的方法参数和类上应用验证。
    • @Valid: 通常用于在实体类的字段上定义验证规则。

    • @Validated: 可以指定验证分组,从而针对不同的场景应用不同的验证规则。

    • @Valid: 无法指定验证分组,只能应用默认的验证规则.

Data

使用@Data注解的类,会自动生成以下方法:

  1. 无参构造方法
  2. 全参构造方法
  3. Getter和Setter方法
  4. equals()和hashCode()方法
  5. toString()方法
1
2
3
4
5
6
7
8
import lombok.Data;

@Data
public class User {
private Long id;
private String username;
private String email;
}

自己实现一个注解

在JDK中提供了4个标准的用来对注解类型进行注解的注解类(元注解),他们分别是:

  • @Target 注解用于什么地方

  • 类或接口:ElementType.TYPE

  • 字段:ElementType.FIELD
  • 方法:ElementType.METHOD
  • 构造方法:ElementType.CONSTRUCTOR
  • 方法参数:ElementType.PARAMETER

  • @Retention 定义该注解的生命周期

  • 仅编译期:RetentionPolicy.SOURCE

  • 仅class文件:RetentionPolicy.CLASS
  • 运行期:RetentionPolicy.RUNTIME

  • @Documented 一个简单的Annotations 标记注解,表示是否将注解信息添加在java 文档中

  • @Inherited 定义该注释和子类的关系,当一个类被标记为 @Inherited 时,如果它的子类没有被注解标记,那么子类也会继承该注解
1
2
3
4
5
6
7
8
9
10
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogExecutionTime {
// 可以添加属性,如果没有属性,可以省略括号
}

注解类的定义使用 @interface 关键字。

  • @Retention(RetentionPolicy.RUNTIME): 指定注解的保留策略为运行时,这意味着该注解可以在运行时被反射获取和使用。
  • @Target(ElementType.METHOD): 指定该注解可以应用于方法上。

@Retention(RetentionPolicy.RUNTIME)是定义注解所必须的。
@Retention是注解的注解,称为注解的元注解。括号里有一个枚举类型的值,即为注解内部定义的值

1
2
3
4
5
6
7
8
9
@RestController
public class UserController {
@LogExecutionTime
@GetMapping("/users")
public List<User> getUsers() {
// 获取用户列表的逻辑
return userService.getAllUsers();
}
}

添加注解属性

1
2
3
4
@Retention(RetentionPolicy.RUNTIME)
public @interface CarNameTarget {
String name() default "";
}
1
2
3
4
public class BnechCar {
@CarNameTarget(name="奔驰")
private String name;
}

使用注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class UserService {

@MyLogger("getUserById")
public User getUserById(Long id) {
// 获取用户的逻辑
return userRepository.findById(id);
}

@MyLogger
public List<User> getAllUsers() {
// 获取所有用户的逻辑
return userRepository.findAll();
}
}

可以编写一个切面 (Aspect) 来处理被 注解标记的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect {
@Around("@annotation(MyLogger)")
public Object logMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable {
MyLogger myLogger = joinPoint.getTarget().getClass().getMethod(joinPoint.getSignature().getName()).getAnnotation(MyLogger.class);
String logMessage = myLogger.value().isEmpty() ? joinPoint.getSignature().getName() : myLogger.value();
System.out.println("Executing method: " + logMessage);
Object result = joinPoint.proceed();
System.out.println("Method execution completed.");
return result;
}
}
  • 我们使用 @Around 注解来拦截被 @MyLogger 注解标记的方法。
  • 在切面方法中, 我们首先获取方法上的 @MyLogger 注解实例,并根据注解的 value 属性构建日志消息。
  • 然后,我们在方法执行前后分别打印日志消息。

  • Object[] getArgs:获取目标方法的参数数组

  • Signature getSignature:获取目标方法的签名
  • Object getTarget:获取被植入增强处理的目标对象
  • Object getThis:获取AOP框架为目标对象生成的代理对象

处理注解的通用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Range {
int min = 1;
int max = 7;
String msg = "长度不能小于" + Range.min + "或者大于" + Range.max;

String message() default msg;

int min() default min;

int max() default max;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Person {
@Range
public String name;

@Range(min = 22, max = 50, message = "年龄不能小于22或者大于50")
public Integer age;

@Range(min = 3, max = 20, message = "长度不能小于3或者大于20")
public String city;

public Person() {}

public Person(String name, Integer age, String city) {
this.name = name;
this.age = age;
this.city = city;
}

// 此处省略 get/set ...
}

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
import java.lang.reflect.Field;
// RangeCheck对象传入person对象或其他有注解的对象.
public class RangeCheck {

public static void check(Person person) throws IllegalAccessException, NullPointerException, IllegalArgumentException {
// 使用反射获取所有字段并遍历
for (Field field : person.getClass().getFields()) {
// 获取字段上的注解
Range range = field.getAnnotation(Range.class);
// 判断该字段是否使用了注解
if (range != null) {
// 获取字段的值, 这里可能会抛出 IllegalAccessException 访问权限异常
Object value = field.get(person);
System.out.println(field.getName() + " value--> " + value);

// 判断参数是否为空
if (value == null) {
throw new NullPointerException(field.getName() + "不能为空");
}
// String类型校验
else if (value instanceof String) {
// 如果是String类型就强转
String s = (String) value;
// 判断该值是否满足 @Range的min/max
if (s.length() < range.min() || s.length() > range.max()) {
throw new IllegalArgumentException("参数异常: " + field.getName() + range.message());
}
}
// Integer 类型校验
else if (value instanceof Integer) {
Integer i = (Integer) value;
if (i < range.min() || i > range.max()) {
throw new IllegalArgumentException("参数异常: " + field.getName() + range.message());
}
}
}
}
}
}

杂项

mvn项目管理

maven 命令的格式为 mvn [plugin-name]:[goal-name],可以接受的参数如下,
-D 指定参数,如 -Dmaven.test.skip=true 跳过单元测试;
-P 指定 Profile 配置,可以用于区分环境;
-e 显示maven运行出错的信息;
-o 离线执行命令,即不去远程仓库更新包;
-X 显示maven允许的debug信息;
-U 强制去远程更新snapshot的插件或依赖,默认每天只更新一次。

常用maven命令

  • 创建maven项目:mvn archetype:create
    指定 group: -DgroupId=packageName
    指定 artifact:-DartifactId=projectName
    创建web项目:-DarchetypeArtifactId=maven-archetype-webapp
  • 创建maven项目:mvn archetype:generate
  • 验证项目是否正确:mvn validate
  • maven 打包:mvn package
  • 只打jar包:mvn jar:jar
  • 生成源码jar包:mvn source:jar
  • 产生应用需要的任何额外的源代码:mvn generate-sources
  • 编译源代码: mvn compile
  • 编译测试代码:mvn test-compile
  • 运行测试:mvn test
  • 运行检查:mvn verify
  • 清理maven项目:mvn clean
  • 生成eclipse项目:mvn eclipse:eclipse
  • 清理eclipse配置:mvn eclipse:clean
  • 生成idea项目:mvn idea:idea
  • 安装项目到本地仓库:mvn install
  • 发布项目到远程仓库:mvn:deploy
  • 在集成测试可以运行的环境中处理和发布包:mvn integration-test
  • 显示maven依赖树:mvn dependency:tree
  • 显示maven依赖列表:mvn dependency:list
  • 下载依赖包的源码:mvn dependency:sources
  • 安装本地jar到本地仓库:mvn install:install-file -DgroupId=packageName -DartifactId=projectName -Dversion=version -Dpackaging=jar -Dfile=path

web项目相关命令

  • 启动tomcat:mvn tomcat:run

  • 启动jetty:mvn jetty:run

  • 运行打包部署:mvn tomcat:deploy

  • 撤销部署:mvn tomcat:undeploy

  • 启动web应用:mvn tomcat:start

  • 停止web应用:mvn tomcat:stop

  • 重新部署:mvn tomcat:redeploy

  • 部署展开的war文件:mvn war:exploded tomcat:exploded

    maven 命令的格式为 mvn [plugin-name]:[goal-name],可以接受的参数如下,
    -D 指定参数,如 -Dmaven.test.skip=true 跳过单元测试;
    -P 指定 Profile 配置,可以用于区分环境;
    -e 显示maven运行出错的信息;
    -o 离线执行命令,即不去远程仓库更新包;
    -X 显示maven允许的debug信息;
    -U 强制去远程更新snapshot的插件或依赖,默认每天只更新一次。

常用maven命令

  • 创建maven项目:mvn archetype:create
    指定 group: -DgroupId=packageName
    指定 artifact:-DartifactId=projectName
    创建web项目:-DarchetypeArtifactId=maven-archetype-webapp
  • 创建maven项目:mvn archetype:generate
  • 验证项目是否正确:mvn validate
  • maven 打包:mvn package
  • 只打jar包:mvn jar:jar
  • 生成源码jar包:mvn source:jar
  • 产生应用需要的任何额外的源代码:mvn generate-sources
  • 编译源代码: mvn compile
  • 编译测试代码:mvn test-compile
  • 运行测试:mvn test
  • 运行检查:mvn verify
  • 清理maven项目:mvn clean
  • 生成eclipse项目:mvn eclipse:eclipse
  • 清理eclipse配置:mvn eclipse:clean
  • 生成idea项目:mvn idea:idea
  • 安装项目到本地仓库:mvn install
  • 发布项目到远程仓库:mvn:deploy
  • 在集成测试可以运行的环境中处理和发布包:mvn integration-test
  • 显示maven依赖树:mvn dependency:tree
  • 显示maven依赖列表:mvn dependency:list
  • 下载依赖包的源码:mvn dependency:sources
  • 安装本地jar到本地仓库:mvn install:install-file -DgroupId=packageName -DartifactId=projectName -Dversion=version -Dpackaging=jar -Dfile=path

web项目相关命令

  • 启动tomcat:mvn tomcat:run
  • 启动jetty:mvn jetty:run
  • 运行打包部署:mvn tomcat:deploy
  • 撤销部署:mvn tomcat:undeploy
  • 启动web应用:mvn tomcat:start
  • 停止web应用:mvn tomcat:stop
  • 重新部署:mvn tomcat:redeploy
  • 部署展开的war文件:mvn war:exploded tomcat:exploded

参考资料

  1. java注解:如何实现和使用一个自定义注解?_java自定义注解如何实现功能-CSDN博客

  2. Java使用自定义注解并处理注解_自定义注解 去掉某个注解-CSDN博客

-------------本文结束感谢您的阅读-------------
感谢阅读.

欢迎关注我的其它发布渠道