admin管理员组

文章数量:1627747

1.Spring Boot 简介

1.1Spring Boot 的起源和发展历程

1.1.1Spring Boot 是如何简化传统 Spring 应用程序开发的过程的

Spring Boot通过多种方式显著简化了Spring应用的初始化和开发过程,让开发者能够更快速、更高效地构建出高质量的应用程序。以下是一些Spring Boot如何简化Spring应用初始化和开发过程的关键点:

自动化配置:

Spring Boot的核心思想之一是“约定优于配置”。它提供了大量的默认配置,这些配置基于开发者的常见需求和使用习惯。这意味着开发者在很多情况下无需手动配置,Spring Boot会自动处理这些配置。这不仅减少了配置错误的可能性,还大大提高了开发效率。

starter依赖:

Spring Boot提供了大量的“starter”依赖,这些依赖包含了开发某个特定功能所需的所有库和配置。例如,开发者只需添加Spring Boot Web Starter依赖,就可以快速搭建一个Web应用程序,而无需手动添加和配置相关的库和组件。

内嵌Web服务器:

Spring Boot支持内嵌的Web服务器,如Tomcat、Jetty等。这意味着开发者无需在外部部署Web服务器,只需通过Spring Boot即可快速启动和运行Web应用程序。这大大简化了应用的部署过程。

属性文件配置:

Spring Boot支持使用属性文件(如application.properties或application.yml)进行配置。开发者可以在这些文件中定义应用的各种属性和设置,Spring Boot会自动加载这些配置并应用到应用中。这种方式比传统的XML配置更加直观和易于管理。

命令行工具:

Spring Boot还提供了命令行工具,帮助开发者快速创建和管理Spring Boot项目。通过命令行工具,开发者可以生成基本的项目结构、添加依赖、运行应用等,进一步简化了开发流程。

健康检查和监控:

Spring Boot提供了Actuator模块,用于监控和管理Spring Boot应用程序。开发者可以使用Actuator来查看应用的健康状态、性能指标、环境信息等,从而更好地了解和优化应用。

原文链接:https://blog.csdn/m0_60521228/article/details/136791283

1.2Spring Boot 的特点和优势

1.2.1Spring Boot 的自动配置如何减少了开发人员的配置工作量

Spring Boot的自动配置机制是通过在类路径下寻找合适的配置文件来简化配置的。开发者只需要引入相关的Spring Boot starter依赖,这些依赖中通常会包含足够的自动配置,以便开发者能够快速地开始构建一个生产级别的应用。

例如,如果你想要构建一个使用Spring Data JPA的应用,你只需要添加spring-boot-starter-data-jpa依赖,Spring Boot会自动配置数据源、实体管理器等,大大减少了开发者需要手动配置的内容。
Spring Boot 自动配置(Auto-Configuration)是其最重要的特性之一,通过智能地配置 Spring 应用程序所需的各种组件和库,极大地减少了开发人员的配置工作量。以下是 Spring Boot 自动配置如何实现这一目标的详细解释:

1. 自动配置的原理

Spring Boot 自动配置基于一组预定义的配置类,这些类会根据应用的类路径、定义的 Bean 以及各种属性文件中的设置来有条件地配置 Spring 应用。具体来说,Spring Boot 自动配置依赖于以下机制:

条件注解(Conditional Annotations):如 @ConditionalOnClass、@ConditionalOnMissingBean、@ConditionalOnProperty 等,用于在特定条件满足时进行相应配置。例如,如果类路径中存在某个类,则自动配置相应的 Bean。

2. 减少配置工作的方式

以下是 Spring Boot 自动配置通过多种方式减少开发人员配置工作量的具体示例:

2.1 自动配置常见组件
Spring Boot 提供了对许多常见组件的自动配置支持,例如:

数据源配置:自动配置 DataSource,当类路径中存在数据库驱动时,自动创建 DataSource Bean。
JPA 配置:当类路径中存在 Hibernate 时,自动配置 EntityManagerFactory 和 TransactionManager。
Web 配置:自动配置内嵌的 Tomcat、Jetty 或 Undertow 服务器,并根据应用需求配置 Spring MVC。
2.2 简化的属性配置
Spring Boot 提供了一种简单的方式,通过 application.properties 或 application.yml 文件来配置应用程序,而不是编写大量的 Java 配置或 XML 配置。例如:

spring.datasource.url=jdbc:mysql://localhost/test
spring.datasource.username=root
spring.datasource.password=password

上述配置可以自动配置数据源,无需开发人员编写复杂的 Bean 定义。

2.3 预定义的配置模板
Spring Boot 提供了一些 Starter POMs,它们是 Maven 或 Gradle 项目的起点,包含了一组依赖项和自动配置类。例如,spring-boot-starter-web 包含了 Spring MVC、Tomcat 和相关的自动配置,开发人员只需添加这个依赖,即可快速构建一个 Web 应用:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
3.示例分析

以一个简单的 Web 应用为例:

传统 Spring 应用:

配置 Servlet 容器。
配置 DispatcherServlet。
配置 Web 应用上下文和各种 Bean。
配置数据源和事务管理。
这些配置通常需要数十行甚至上百行的 XML 或 Java 配置代码。

Spring Boot 应用:

通过依赖管理引入 spring-boot-starter-web。
编写一个主类,使用 @SpringBootApplication 注解。

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

在 application.properties 中进行少量配置(如数据源配置)。

spring.datasource.url=jdbc:mysql://localhost/test
spring.datasource.username=root
spring.datasource.password=password

通过这些步骤,Spring Boot 自动配置会自动完成 Servlet 容器、DispatcherServlet、Web 应用上下文、数据源和事务管理的配置工作。

4.自定义和扩展

尽管 Spring Boot 自动配置极大地减少了配置工作量,但它仍然允许开发人员进行自定义和扩展。例如,通过 @Bean 注解手动定义 Bean,或通过 @Configuration 类完全接管某些配置。

1.3Spring Boot 在现代 Java 开发中的地位和作用

1.3.1 Spring Boot 如何帮助开发者快速构建现代化的企业级应用程序

1在企业级应用开发中快速开发和迭代是至关重要的,这就需要我们掌握一些关键技术和方法。在这里,我们主要介绍以下四个方面:

  1. 快速搭建Spring Boot应用的基本流程和方法
    1.1 Spring Boot基本流程
    下面是一个简单的Spring Boot应用的创建流程:

创建Spring Boot项目并配置依赖
编写应用程序的入口类
添加Controller和Service
运行Spring Boot应用程序
1.2 Spring Boot快速搭建方法
我们可以使用Spring Initializr快速搭建一个基础骨架配置相关依赖和插件,然后在应用程序逻辑和业务代码中编写应用程序的业务逻辑即可。具体步骤如下:

使用Spring Initializr创建基础骨架。
通过配置文件或者注解等方式配置相关依赖和插件。
编写应用程序的各个模块的业务逻辑(例如Controller、Service等)。
2. 集成常用组件和中间件
在企业级应用中通常需要访问数据库、使用缓存和消息队列等中间件。Spring Boot对这些中间件都提供了集成支持。

2.1 数据库访问
Spring Boot支持JDBC、JPA、Hibernate和MyBatis ORM框架的集成,同时也支持MongoDB等NoSQL数据库的集成。

2.2 缓存
Spring Boot默认可以支持基于JCache、EhCache、Redis和Guava等缓存框架。集成时只需相应添加相关依赖和配置即可。

2.3 消息队列
Spring Boot支持JMS、AMQP(如RabbitMQ)和Kafka等消息队列框架完成异步消息传递。可以使用Spring Boot提供的JMS、AMQP和Kafka等starter集成组件。

  1. 构建RESTful服务和Web应用与前端的集成开发
    在当今互联网开发领域RESTFul服务和Web应用的开发已经成为了标配。Spring Boot对这两方面也有很好的支持。

3.1 RESTful服务构建
Spring Boot提供了Spring MVC和Spring WebFlux等模块来构建RESTful服务,并充分利用Spring Boot的自动配置功能。

3.2 Web应用与前端的集成开发
Spring Boot提供了对Webjars和Thymeleaf等静态资源管理框架的支持,可以很简单地将前端部分集成到Spring Boot的Web应用中。

  1. 优化和监控应用性能
    在部署应用后通常需要进行应用性能的调优和监控,保证应用的性能和可靠性。

4.1 调优技巧
对于数据库,使用连接池和优化SQL语句以提高查询性能。
合理使用线程池,避免线程阻塞和等待,使用异步编程方式提高吞吐量。
按需加载资源,减少应用启动时间,提高性能。
4.2 监控工具的使用
Spring Boot提供了Actuator和Spring Boot Admin等监控工具可以查看各种应用程序信息,包括请求次数、线程池情况、资源使用情况等。

原文链接:https://blog.csdn/u010349629/article/details/130673293

2.Spring Boot 的核心原理

2.1自动配置(Auto-Configuration)

2.1.1使用简单的例子说明 Spring Boot 是如何根据项目的依赖自动配置应用程序的

依赖配置
本例子使用Maven来做包的依赖管理,在pom.xml文件中我们需要添加Spring boot依赖:

org.springframework.boot spring-boot-starter-parent 2.2.2.RELEASE

同时我们要构建一个web应用程序,所以需要添加web依赖:

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

OOM框架,我们使用spring自带的jpa,数据库使用内存数据库H2:

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>

main程序配置
接下来我们需要创建一个应用程序的主类:

@SpringBootApplication
public class App {

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

}

这里我们使用了注解: @SpringBootApplication。 它等同于三个注解:@Configuration, @EnableAutoConfiguration, 和 @ComponentScan同时使用。

最后,我们需要在resources目录中添加属性文件:application.properties。 在其中我们定义程序启动的端口:

server.port=8081

MVC配置
spring MVC可以配合很多模板语言使用,这里我们使用Thymeleaf。

首先需要添加依赖:

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

然后在application.properties中添加如下配置:

spring.thymeleaf.cache=false
spring.thymeleaf.enabled=true
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
 
spring.application.name=Bootstrap Spring Boot

然后创建一个home页面:

<html>
<head><title>Home Page</title></head>
<body>
<h1>Hello !</h1>
<p>Welcome to <span th:text="${appName}">Our App</span></p>
</body>
</html>

最后创建一个Controller指向这个页面:

@Controller
public class SimpleController {
    @Value("${spring.application.name}")
    String appName;
 
    @GetMapping("/")
    public String homePage(Model model) {
        model.addAttribute("appName", appName);
        return "home";
    }
}

安全配置
本例主要是搭一个基本完整的框架,所以必须的安全访问控制也是需要的。我们使用Spring Security来做安全控制,加入依赖如下:

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

当spring-boot-starter-security加入依赖之后,应用程序所有的入库会被默认加入权限控制,在本例中,我们还用不到这些权限控制,所以需要自定义SecurityConfig,放行所有的请求:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .anyRequest()
            .permitAll()
            .and().csrf().disable();
    }
}

原文链接:https://blog.csdn/superfjj/article/details/104059204

2.2起步依赖(Starter Dependencies)

2.2.1举例介绍一些常用的起步依赖,如 Web、JPA、Security 等,并解释其作用

一、WEB 相关

  1. web
    用于web开发场景,包含了 RESTful 和 Spring MVC,并且默认使用了内置的Tomcat。
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>
  1. aop
    使用 Spring AOP 和 AspectJ 的面向切面编程场景。
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
  1. thymeleaf
    thymeleaf 是一个很强大的视图解析工具。
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
  1. test
    用于测试,提供了多个测试库,包括 JUnit Jupiter、Hamcrest 和 Mockito。
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-test</artifactId>
</dependency>
  1. security
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
</dependency>
  1. swagger
<!-- swagger-ui -->
<dependency>
	<groupId>io.springfox</groupId>
	<artifactId>springfox-swagger-ui</artifactId>
	<version>2.9.2</version>
</dependency>
 
<!-- swagger2 -->
<dependency>
	<groupId>io.springfox</groupId>
	<artifactId>springfox-swagger2</artifactId>
	<version>2.9.2</version>
</dependency>

二、DB 相关

  1. mybatis
<dependency>
	<groupId>org.mybatis.spring.boot</groupId>
	<artifactId>mybatis-spring-boot-starter</artifactId>
	<version>2.2.2</version>
</dependency>

<!-- mybatis-plus -->
<dependency>
	<groupId>com.baomidou</groupId>
	<artifactId>mybatis-plus-boot-starter</artifactId>
	<version>3.4.3.4</version>
</dependency>
  1. jdbc
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
  1. mysql
<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
	<scope>runtime</scope>
</dependency>
  1. redis
<dependency>
	<groupId>org.springframework.data</groupId>
	<artifactId>spring-data-redis</artifactId>
	<version>3.1.2</version>
</dependency>

<dependency>
	<groupId>redis.clients</groupId>
	<artifactId>jedis</artifactId>
	<version>3.10.0</version>
</dependency>

<!-- redisson分布式锁 -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.16.6</version>
</dependency>
  1. mongodb
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

三、辅助依赖

  1. lombok
<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<optional>true</optional>
</dependency>
  1. log
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
  1. mail
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
  1. commons-lang3
    commons-lang3是一个小而全的Java工具类库,类里面有很多对时间、数组、数字、字符串等的处理。
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.12.0</version>
</dependency>
  1. guava
<dependency>
	<groupId>com.google.guava</groupId>
	<artifactId>guava</artifactId>
	<version>22.0</version>
 </dependency>
  1. aviator
<!-- 规则引擎 -->
<dependency>
	<groupId>com.googlecode.aviator</groupId>
	<artifactId>aviator</artifactId>
	<version>5.2.1</version>
</dependency>
  1. codec
md5加密…

<dependency>
	<groupId>commons-codec</groupId>
	<artifactId>commons-c</artifactId>
	<version>1.11</version>
</dependency>

四、配置文件
application.properties 配置文件的内容

spring.datasource.url = jdbc:mysql://IP地址:数据库端口号/数据库名称?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8
spring.datasource.username = 数据库登陆用户
spring.datasource.password =  登陆密码
spring.datasource.driverClassName = com.mysql.cj.jdbc.Driver
server.port=9990 #项目启动后的端口号,springboot自带tomcat容器
mybatis.mapper-locations: classpath*:mapper/*.xml #扫描mapper文件也就是mybatis映射的文件

原文链接:https://blog.csdn/qq_40213825/article/details/124082688

2.3Spring Boot 启动过程分析

2.3.1使用图示和步骤详细说明 Spring Boot 应用程序的启动过程

SpringBoot启动过程
springboot的启动经过了一些一系列的处理,我们先看看整体过程的流程图

你别说,步骤还挺多,但是不要紧,为了帮助大家理解,接下来将上面的编号一个个地讲解,以通俗易懂的方式告诉大家,里面都做了什么事情,废话不多说,开整

1、运行 SpringApplication.run() 方法
可以肯定的是,所有的标准的springboot的应用程序都是从run方法开始的

代码语言:javascript
复制
package com.spring;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class App {

public static void main(String[] args) {

// 启动springboot
ConfigurableApplicationContext run = SpringApplication.run(App.class, args);
}
}
进入run方法后,会 new 一个SpringApplication 对象,创建这个对象的构造函数做了一些准备工作,编号第2~5步就是构造函数里面所做的事情

代码语言:javascript
复制
/** * Static helper that can be used to run a {@link SpringApplication} from the * specified sources using default settings and user supplied arguments. * @param primarySources the primary sources to load * @param args the application arguments (usually passed from a Java main method) * @return the running {@link ApplicationContext} */
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {

return new SpringApplication(primarySources).run(args);
}
另外在说明一下,springboot启动有三种方式,其他的启动方式可参照我的另一个帖子: SpringBoot的三种启动方式

2、确定应用程序类型
在SpringApplication的构造方法内,首先会通过 WebApplicationType.deduceFromClasspath(); 方法判断当前应用程序的容器,默认使用的是Servlet 容器,除了servlet之外,还有NONE 和 REACTIVE (响应式编程);

在这里插入图片描述
在这里插入图片描述
3、加载所有的初始化器
这里加载的初始化器是springboot自带初始化器,从从 META-INF/spring.factories 配置文件中加载的,那么这个文件在哪呢?自带有2个,分别在源码的jar包的 spring-boot-autoconfigure 项目 和 spring-boot 项目里面各有一个

在这里插入图片描述
在这里插入图片描述

spring.factories文件里面,看到开头是 org.springframework.context.ApplicationContextInitializer 接口就是初始化器了 ,

在这里插入图片描述
在这里插入图片描述

当然,我们也可以自己实现一个自定义的初始化器:实现 ApplicationContextInitializer接口既可

MyApplicationContextInitializer.java

代码语言:javascript
复制
package com.spring.application;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
/** * 自定义的初始化器 */
public class MyApplicationContextInitializer implements ApplicationContextInitializer {

@Override
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {

System.out.println(“我是初始化的 MyApplicationContextInitializer…”);
}
}
在resources目录下添加 META-INF/spring.factories 配置文件,内容如下,将自定义的初始化器注册进去;

代码语言:javascript
复制
org.springframework.context.ApplicationContextInitializer=
com.spring.application.MyApplicationContextInitializer
在这里插入图片描述
在这里插入图片描述

启动springboot后,就可以看到控制台打印的内容了,在这里我们可以很直观的看到它的执行顺序,是在打印banner的后面执行的;

在这里插入图片描述
在这里插入图片描述
4、加载所有的监听器
加载监听器也是从 META-INF/spring.factories 配置文件中加载的,与初始化不同的是,监听器加载的是实现了 ApplicationListener 接口的类

在这里插入图片描述
在这里插入图片描述

自定义监听器也跟初始化器一样,依葫芦画瓢就可以了,这里不在举例;

5、设置程序运行的主类
deduceMainApplicationClass(); 这个方法仅仅是找到main方法所在的类,为后面的扫包作准备,deduce是推断的意思,所以准确地说,这个方法作用是推断出主方法所在的类;

在这里插入图片描述
在这里插入图片描述
6、开启计时器
程序运行到这里,就已经进入了run方法的主体了,第一步调用的run方法是静态方法,那个时候还没实例化SpringApplication对象,现在调用的run方法是非静态的,是需要实例化后才可以调用的,进来后首先会开启计时器,这个计时器有什么作用呢?顾名思义就使用来计时的嘛,计算springboot启动花了多长时间;关键代码如下:

代码语言:javascript
复制
// 实例化计时器
StopWatch stopWatch = new StopWatch();
// 开始计时
stopWatch.start();
run方法代码段截图

在这里插入图片描述
在这里插入图片描述
7、将java.awt.headless设置为true
这里将java.awt.headless设置为true,表示运行在服务器端,在没有显示器器和鼠标键盘的模式下照样可以工作,模拟输入输出设备功能。

做了这样的操作后,SpringBoot想干什么呢?其实是想设置该应用程序,即使没有检测到显示器,也允许其启动.对于服务器来说,是不需要显示器的,所以要这样设置.

方法主体如下:

代码语言:javascript
复制
private void configureHeadlessProperty() {

System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, System.getProperty(
SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}
通过方法可以看到,setProperty()方法里面又有个getProperty();这不多此一举吗?其实getProperty()方法里面有2个参数, 第一个key值,第二个是默认值,意思是通过key值查找属性值,如果属性值为空,则返回默认值 true;保证了一定有值的情况;

8、获取并启用监听器
这一步 通过监听器来实现初始化的的基本操作,这一步做了2件事情

创建所有 Spring 运行监听器并发布应用启动事件
启用监听器

在这里插入图片描述
在这里插入图片描述
9、设置应用程序参数
将执行run方法时传入的参数封装成一个对象

在这里插入图片描述
在这里插入图片描述

仅仅是将参数封装成对象,没啥好说的,对象的构造函数如下

代码语言:javascript
复制
public DefaultApplicationArguments(String[] args) {

Assert.notNull(args, “Args must not be null”);
this.source = new Source(args);
this.args = args;
}
那么问题来了,这个参数是从哪来的呢?其实就是main方法里面执行静态run方法传入的参数,

在这里插入图片描述
在这里插入图片描述
10、准备环境变量
准备环境变量,包含系统属性和用户配置的属性,执行的代码块在 prepareEnvironment 方法内

在这里插入图片描述
在这里插入图片描述

打了断点之后可以看到,它将maven和系统的环境变量都加载进来了

在这里插入图片描述
在这里插入图片描述
11、忽略bean信息
这个方法configureIgnoreBeanInfo() 这个方法是将 spring.beaninfo.ignore 的默认值值设为true,意思是跳过beanInfo的搜索,其设置默认值的原理和第7步一样;

代码语言:javascript
复制
private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {

if (System.getProperty(
CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) {

Boolean ignore = environment.getProperty(“spring.beaninfo.ignore”,
Boolean.class, Boolean.TRUE);
System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME,
ignore.toString());
}
}
当然也可以在配置文件中添加以下配置来设为false

代码语言:javascript
复制
spring.beaninfo.ignore=false
目前还不知道这个配置的具体作用,待作者查明后在补上

12、打印 banner 信息
显而易见,这个流程就是用来打印控制台那个很大的spring的banner的,就是下面这个东东

在这里插入图片描述
在这里插入图片描述

那他在哪里打印的呢?他在 SpringBootBanner.java 里面打印的,这个类实现了Banner 接口,

而且banner信息是直接在代码里面写死的;

在这里插入图片描述
在这里插入图片描述

有些公司喜欢自定义banner信息,如果想要改成自己喜欢的图标该怎么办呢,其实很简单,只需要在resources目录下添加一个 banner.txt 的文件即可,txt文件内容如下

代码语言:javascript
复制
_ _
() | |
_ _ _____ ___ _ __ _| | ___ _ __ __ _
| | | |/ _ \ / / | ’
\ / |/ _ \| '_ \ / _ |
| |
| | __/> <| | | | | (
| | () | | | | (| |
_, |___//__|| ||_,|__/|| ||_, |
/ | / |
|
/ |
/
:: yexindong::
一定要添加到resources目录下,别加错了

在这里插入图片描述
在这里插入图片描述

只需要加一个文件即可,其他什么都不用做,然后直接启动springboot,就可以看到效果了

在这里插入图片描述
在这里插入图片描述
13、创建应用程序的上下文
实例化应用程序的上下文, 调用 createApplicationContext() 方法,这里就是用反射创建对象,没什么好说的;

在这里插入图片描述
在这里插入图片描述
14、实例化异常报告器
异常报告器是用来捕捉全局异常使用的,当springboot应用程序在发生异常时,异常报告器会将其捕捉并做相应处理,在spring.factories 文件里配置了默认的异常报告器,

在这里插入图片描述
在这里插入图片描述

需要注意的是,这个异常报告器只会捕获启动过程抛出的异常,如果是在启动完成后,在用户请求时报错,异常报告器不会捕获请求中出现的异常,

在这里插入图片描述
在这里插入图片描述

了解原理了,接下来我们自己配置一个异常报告器来玩玩;

MyExceptionReporter.java 继承 SpringBootExceptionReporter 接口

代码语言:javascript
复制
package com.spring.application;
import org.springframework.boot.SpringBootExceptionReporter;
import org.springframework.context.ConfigurableApplicationContext;
public class MyExceptionReporter implements SpringBootExceptionReporter {

private ConfigurableApplicationContext context;
// 必须要有一个有参的构造函数,否则启动会报错
MyExceptionReporter(ConfigurableApplicationContext context) {

this.context = context;
}
@Override
public boolean reportException(Throwable failure) {

System.out.println(“进入异常报告器”);
failure.printStackTrace();
// 返回false会打印详细springboot错误信息,返回true则只打印异常信息
return false;
}
}
在 spring.factories 文件中注册异常报告器

代码语言:javascript
复制

Error Reporters 异常报告器

org.springframework.boot.SpringBootExceptionReporter=
com.spring.application.MyExceptionReporter
在这里插入图片描述
在这里插入图片描述

接着我们在application.yml 中 把端口号设置为一个很大的值,这样肯定会报错,

代码语言:javascript
复制
server:
port: 80828888
启动后,控制台打印如下图

在这里插入图片描述
在这里插入图片描述
15、准备上下文环境
这里准备的上下文环境是为了下一步刷新做准备的,里面还做了一些额外的事情;

在这里插入图片描述
在这里插入图片描述
15.1、实例化单例的beanName生成器
在 postProcessApplicationContext(context); 方法里面。使用单例模式创建 了BeanNameGenerator 对象,其实就是beanName生成器,用来生成bean对象的名称

15.2、执行初始化方法
初始化方法有哪些呢?还记得第3步里面加载的初始化器嘛?其实是执行第3步加载出来的所有初始化器,实现了ApplicationContextInitializer 接口的类

15.3、将启动参数注册到容器中
这里将启动参数以单例的模式注册到容器中,是为了以后方便拿来使用,参数的beanName 为 :springApplicationArguments

16、刷新上下文
刷新上下文已经是spring的范畴了,自动装配和启动 tomcat就是在这个方法里面完成的,还有其他的spring自带的机制在这里就不一一细说了,

在这里插入图片描述
在这里插入图片描述
17、刷新上下文后置处理
afterRefresh 方法是启动后的一些处理,留给用户扩展使用,目前这个方法里面是空的,

代码语言:javascript
复制
/** * Called after the context has been refreshed. * @param context the application context * @param args the application arguments */
protected void afterRefresh(ConfigurableApplicationContext context,
ApplicationArguments args) {

}
18、结束计时器
到这一步,springboot其实就已经完成了,计时器会打印启动springboot的时长

在这里插入图片描述
在这里插入图片描述

在控制台看到启动还是挺快的,不到2秒就启动完成了;

在这里插入图片描述
在这里插入图片描述
19、发布上下文准备就绪事件
告诉应用程序,我已经准备好了,可以开始工作了

在这里插入图片描述
在这里插入图片描述
20、执行自定义的run方法
这是一个扩展功能,callRunners(context, applicationArguments) 可以在启动完成后执行自定义的run方法;有2中方式可以实现:

实现 ApplicationRunner 接口
实现 CommandLineRunner 接口
接下来我们验证一把,为了方便代码可读性,我把这2种方式都放在同一个类里面

代码语言:javascript
复制
package com.spring.init;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
/** * 自定义run方法的2种方式 */
@Component
public class MyRunner implements ApplicationRunner, CommandLineRunner {

@Override
public void run(ApplicationArguments args) throws Exception {

System.out.println(" 我是自定义的run方法1,实现 ApplicationRunner 接口既可运行" );
}
@Override
public void run(String… args) throws Exception {

System.out.println(" 我是自定义的run方法2,实现 CommandLineRunner 接口既可运行" );
}
}
启动springboot后就可以看到控制台打印的信息了

在这里插入图片描述
在这里插入图片描述
https://cloud.tencent/developer/article/2131866

2.4应用程序结构和配置文件

2.4.1展示一个简单的 Spring Boot 项目结构,解释每个文件的作用,并举例说明如何配置应用程序

2.SpringBoot项目创建
(1)创建一个 Project,选择类型为Spring Booot 快速构建
默认Server URL 是https://start.spring.io java版本不支持8,需要修改Server URL为https://start.aliyun
原因:
spring2.X版本在2023年11月24日停止维护了,因此创建spring项目时不再有2.X版本的选项,只能从3.1.X版本开始选择
而Spring3.X版本不支持JDK8,JDK11,最低支持JDK17,因此JDK8也无法选择了
当然,停止维护只代表我们无法用idea主动创建spring2.X版本的项目了,不代表我们无法使用,该使用依然能使用,丝毫不受影响
目前阿里云还是支持创建Spring2.X版本的项目的

(2)选择 Spring Boot 版本及依赖
spring boot版本一般选择最新的就行,勾选spring web ,不勾选项目结构中会缺少resources需要自己配置

(3)项目结构如下:

static:存放静态资源。如图片、CSS、JavaScript 等

templates:存放 Web 页面的模板文件

application.properties/application.yml 用于存放程序的各种依赖模块的配置信息,
比如 服务端口,数据库连接配置等

.gitignore:使用版本控制工具 git 的时候,设置一些忽略提交的内容

Application.java:SpringBoot 程序执行的入口,执行该程序中的 main 方法,
启动当前SpringBoot项目。
(4)对pom.xml文件进行解释

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache/POM/4.0.0" xmlns:xsi="http://www.w3/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache/POM/4.0.0 https://maven.apache/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>demo</description>
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.6.13</spring-boot.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <configuration>
                    <mainClass>com.example.demo.DemoApplication</mainClass>
                    <skip>true</skip>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

2.2 创建一个 Spring MVC 的 Spring BootController
注意:新创建的类一定要位于 Application 同级目录或者下级目录,否则 SpringBoot 加载不到。

package com.example.demo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class SpringBootController {
    @RequestMapping(value = "/hello")
    public @ResponseBody String helloh() {
        return "Hello World";
    }
}

(2)启动Application类中的main方法
1.修改配置配置上main方法
通过在控制台的输出,可以看到启动 SpringBoot 框架,会启动一个内嵌的 tomcat,端
口号为 8080,上下文根为空 。

(3)SpringBoot项目快速启动
1)对SpringBoot项目打包(执行Maven构建指令package)
mvn clean package

或者

https://blog.csdn/weixin_46453070/article/details/135861278
2) 执行启动指令,执行指令之前看一下maven配置的对不对

(3)在浏览器中输入 http://localhost:8022/hellow进行访问

注解:@SpringBootApplication、@Controller、@RequestMapping 和 @ResponseBody

1.@SpringBootApplication 注解
这是 Spring Boot 项目的核心注解。
主要作用是开启 Spring 的自动配置。
如果在 Application 类上去掉这个注解,Spring Boot 应用将不会启动。
2. @Controller
定义:
@Controller 是 Spring MVC 的注解,用于定义控制器类。
作用:
标识一个类作为 Spring MVC 的控制器。
控制器类处理 HTTP 请求并返回视图名称。
3. @RequestMapping
定义:
@RequestMapping 是用于映射请求 URL 到处理方法的注解。
作用:
可以作用于类或方法上,用于指定请求路径和请求方法。
@RequestMapping(“/hellow”) 将该控制器的所有请求路径前缀设置为
/hellow。
4. @ResponseBody
定义:
@ResponseBody 注解用于将控制器方法的返回值直接写入 HTTP 响应体中。
作用:
通常用于返回 JSON 或 XML 数据,而不是视图名称。
5. @RestController
定义:
@RestController 是
@Controller 和
@ResponseBody 的组合注解,通常用于构建 RESTful web 服务。
作用:
标识一个类作为 RESTful web 服务的控制器。
控制器中的每个方法返回的数据会直接写入 HTTP 响应体中。
2.4 核心配置文件格式
(1).properties 文件(默认采用该文件)
通过修改 application.properties 配置文件,修改默认 tomcat 端口号及项目上下文件根:
#设置内嵌 Tomcat 端口号
server.port=9090
#配置项目上下文根
server.servlet.context-path=/003-springboot-port-context-path

页面显示结果:

(2) .yml 文件 :

yml 是一种 yaml 格式的配置文件,主要采用一定的空格、换行等格式排版进行配置。它能够直观的被计算机识别数据序列化格式,容易被人类阅读,yaml 类似于 xml,但是语法比 xml 简洁很多,值与前面的冒号配置项必须要有一个空格, yml 后缀也可以使用 yaml 后缀 。

    注意:当两种格式配置文件同时存在时,使用的是.properties 配置文件。

(3)多环境配置(.properties方式)
在实际开发的过程中,我们的项目会经历很多的阶段(开发->测试->上线),每个阶段
的配置也会不同,例如:端口、上下文根、数据库等,那么这个时候为了方便在不同的环境
之间切换,SpringBoot 提供了多环境配置,具体步骤如下 :
为每个环境创建一个配置文件,命名必须为 application-环境标识.properties / yml

application-dev.properties

#开发环境

#设置内嵌 Tomcat 默认端口号
server.port=8001

application-product.properties

#正式环境

#设置内嵌 Tomcat 默认端口号
server.port=8002

在总配置文件 application.properties 进行环境的激活

#SpringBoot 的总配置文件

#激活开发环境
#spring.profiles.active=dev

#激活生产环境
spring.profiles.active=prod

yml:

application-dev.yml

#测试环境
server:
  port: 8022
  servlet:
    context-path: /appy

application-prod.yml

#正式环境
server:
  port: 8023
  servlet:
    context-path: /appy

在总配置文件 application.yml 进行环境的激活

#SpringBoot 的总配置文件

#激活开发环境
#spring.profiles.active=dev

#激活生产环境
spring.profiles.active=prod
其他常用注解
Spring核心注解

@Component
用途:标记一个类为Spring管理的Bean。
使用场景:任何不属于特定层的组件。
java
复制代码

@Component
public class MyComponent {
// 业务逻辑
}

@Service
用途:特殊的@Component,表示该类是服务层组件。
使用场景:用于业务逻辑层的类。
java
复制代码

@Service
public class MyService {
// 业务逻辑
}

@Repository
用途:特殊的@Component,表示该类是数据访问层组件,通常用于DAO类。
使用场景:用于数据访问逻辑。
java
复制代码

@Repository
public class MyRepository {
// 数据访问逻辑
}

@Controller
用途:特殊的@Component,表示该类是控制器层组件,处理HTTP请求。
使用场景:用于处理Web请求的类。
java
复制代码

@Controller
public class MyController {
@GetMapping("/hello")
public String hello() {
return "hello";
}
}

@RestController
用途:组合注解,相当于@Controller和@ResponseBody,表示该类是RESTful控制器,返回的结果直接写入HTTP响应体。
使用场景:用于REST API控制器。
java
复制代码

@RestController
public class MyRestController {
@GetMapping("/hello")
public String hello() {
return "hello";
}
}

@Configuration
用途:标记一个类为配置类,Spring会根据它来生成和管理Bean。
使用场景:定义应用的配置类。
java
复制代码

@Configuration
public class AppConfig {
@Bean
public MyBean myBean() {
return new MyBean();
}
}

@Bean
用途:在@Configuration类中使用,定义一个Spring管理的Bean。
使用场景:创建和配置Spring Bean。
java
复制代码

@Configuration
public class AppConfig {
@Bean
public MyBean myBean() {
return new MyBean();
}
}

@Override
用途:用于表明一个方法重写了父类或接口中的方法。
使用场景:在子类或实现接口的方法上使用,确保方法正确地重写了父类或接口中的方法。
java
复制代码

public class Animal {
public void makeSound() {
System.out.println("Animal makes a sound");
}
}
public class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Dog barks");
}
}

@RefreshScope
用途:用于刷新Spring容器中Bean的配置。
使用场景:在需要动态更新配置的Bean类上使用,当配置发生变化时,可以重新加载Bean的配置。
java
复制代码

@RestController
@RefreshScope
public class MyController {
@Value("${app.message}")
private String message;
@GetMapping("/message")
public String getMessage() {
    return message;
}

}

@Documented
用途:标记注解是否应该包含在Java文档中。
使用场景:用于自定义注解,希望在Java文档中显示该注解的信息。
java
复制代码

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
// 自定义注解属性和方法
}
日志相关

@Slf4j
用途:引入SLF4J日志框架,提供日志记录功能。
使用场景:需要日志记录时使用。
java
复制代码

@Slf4j
public class MyService {
public void performAction() {
log.info("Action performed");
}
}
依赖注入相关

@Autowired
用途:自动注入依赖对象,默认按类型注入。
使用场景:用于字段、构造函数或方法上,自动注入Bean。
java
复制代码

@Service
public class MyService {
@Autowired
private MyRepository myRepository;
}

@Qualifier
用途:与@Autowired结合使用,用于按名称注入。
使用场景:在有多个同类型Bean时指定注入哪个Bean。
java
复制代码

@Service
public class MyService {
@Autowired
@Qualifier("specificBean")
private MyBean myBean;
}

@Resource
用途:JDK提供的注解,类似@Autowired,但默认按名称注入。
使用场景:需要按名称注入时使用。
java
复制代码

@Service
public class MyService {
@Resource(name = "myBean")
private MyBean myBean;
}

@Primary
用途:标记为主要Bean,当有多个候选Bean时,优先选择。
使用场景:多个同类型Bean时,指定优先选择的Bean。
java
复制代码

@Service
@Primary
public class PrimaryService implements MyService {
// 主要服务实现
}
Lombok相关

@Data
用途:Lombok注解,自动生成getter、setter、toString等方法。
使用场景:需要自动生成常用方法时使用。
java
复制代码

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

@Getter
用途:Lombok注解,自动生成getter方法。
使用场景:需要自动生成getter方法时使用。
java
复制代码

@Getter
public class User {
private Long id;
}

@Setter
用途:Lombok注解,自动生成setter方法。
使用场景:需要自动生成setter方法时使用。
java
复制代码

@Setter
public class User {
private String username;
}

@Builder
用途:Lombok注解,自动生成构建器模式代码。
使用场景:需要使用Builder模式创建对象时使用。
java
复制代码

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

@AllArgsConstructor
用途:Lombok注解,自动生成包含所有字段的构造函数。
使用场景:需要全参构造函数时使用。
java
复制代码

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

@NoArgsConstructor
用途:Lombok注解,自动生成无参构造函数。
使用场景:需要无参构造函数时使用。
java
复制代码

@NoArgsConstructor
public class User {
private Long id;
private String username;
}
Spring Boot相关

@SpringBootApplication
用途:组合注解,包括@Configuration、@EnableAutoConfiguration和@ComponentScan,用于启动Spring Boot应用。
使用场景:主应用类,启动Spring Boot应用。
java
复制代码

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

@EnableDiscoveryClient
用途:启用服务发现功能(如Eureka)。
使用场景:需要注册到服务注册中心时使用。
java
复制代码

@SpringBootApplication
@EnableDiscoveryClient
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

@EnableFeignClients
用途:启用Feign客户端,用于简化HTTP调用。
使用场景:需要通过Feign进行HTTP调用时使用。
java
复制代码

@SpringBootApplication
@EnableFeignClients
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

@NacosConfigurationProperties
用途:用于从Nacos配置中心加载配置属性。
使用场景:使用Nacos作为配置中心时使用。
java
复制代码

@NacosConfigurationProperties(dataId = "example", autoRefreshed = true)
public class ConfigProperties {
// 配置属性
}
数据访问相关

@Mapper
用途:MyBatis的注解,标识一个Mapper接口,映射SQL语句。
使用场景:MyBatis的Mapper接口。
java
复制代码

@Mapper
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{id}")
User findById(int id);
}

@Param
用途:MyBatis注解,指定Mapper方法参数名称。
使用场景:在Mapper方法中使用,传递参数。
java
复制代码

@Mapper
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{id}")
User findById(@Param("id") int id);
}

@MapKey
用途:MyBatis注解,用于将查询结果映射为Map。
使用场景:将结果映射为Map时使用。
java
复制代码

@Mapper
public interface UserMapper {
@MapKey("id")
@Select("SELECT * FROM users")
Map<Integer, User> findAll();
}
异步处理

@Async
用途:标记一个方法为异步执行。
使用场景:需要异步执行方法时使用。
java
复制代码

@Service
public class AsyncService {
@Async
public void executeAsync() {
// 异步任务
}
}
配置属性

@ConfigurationProperties
用途:将配置文件中的属性映射到Java类。
使用场景:需要绑定外部配置到Java对象时使用。
java
复制代码

@ConfigurationProperties(prefix = "app")
public class AppConfig {
private String name;
private int version;
}

@Value
用途:注入配置文件中的属性值。
使用场景:需要从配置文件注入属性值时使用。
java
复制代码

@Component
public class MyComponent {
@Value("${my.property}")
private String myProperty;
}
格式化和校验

@DateTimeFormat
用途:格式化日期时间类型数据。
使用场景:在日期时间字段上,指定格式化模式。
java
复制代码

public class Event {
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate date;
}

Spring Cloud
@EnableCircuitBreaker
用途:启用断路器功能,管理服务调用的容错。
使用场景:需要断路器机制时使用。
java
复制代码

@SpringBootApplication
@EnableCircuitBreaker
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

@FeignClient
用途:声明一个Feign客户端,用于服务间通信。
使用场景:定义Feign客户端接口。
java
复制代码

@FeignClient(name = "service-name")
public interface MyFeignClient {
@GetMapping("/api/resource")
String getResource();
}
AOP相关

@Aspect
用途:声明一个切面类,定义横切关注点。
使用场景:定义AOP切面类。
java
复制代码

@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example..*(..))")
public void logBefore(JoinPoint joinPoint) {
// 切面逻辑
}
}

@Pointcut
用途:定义一个切点,指定切面逻辑应用的连接点。
使用场景:定义AOP切点。
java
复制代码

@Aspect
public class LoggingAspect {
@Pointcut("execution(* com.example..*(..))")
public void applicationMethod() {
// 切点逻辑
}
}

@Around
用途:声明环绕通知,在方法执行前后添加逻辑。
使用场景:需要在方法执行前后添加逻辑时使用。
java
复制代码

@Aspect
public class LoggingAspect {
@Around("execution(* com.example..*(..))")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
// 方法执行前逻辑
Object result = joinPoint.proceed();
// 方法执行后逻辑
return result;
}
}

@After
用途:声明后置通知,在方法执行后添加逻辑。
使用场景:需要在方法执行后添加逻辑时使用。
java
复制代码

@Aspect
public class LoggingAspect {
@After("execution(* com.example..*(..))")
public void logAfter(JoinPoint joinPoint) {
// 方法执行后逻辑
}
}

@Before
用途:声明前置通知,在方法执行前添加逻辑。
使用场景:需要在方法执行前添加逻辑时使用。
java
复制代码

@Aspect
public class LoggingAspect {
@Before("execution(* com.example..*(..))")
public void logBefore(JoinPoint joinPoint) {
// 方法执行前逻辑
}
}

@AfterReturning
用途:声明返回通知,在方法成功返回后添加逻辑。
使用场景:需要在方法成功返回后添加逻辑时使用。
java
复制代码

@Aspect
public class LoggingAspect {
@AfterReturning(pointcut = "execution(* com.example..*(..))", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
// 方法成功返回后逻辑
}
}

@AfterThrowing
用途:声明异常通知,在方法抛出异常后添加逻辑。
使用场景:需要在方法抛出异常后添加逻辑时使用。
java
复制代码

@Aspect
public class LoggingAspect {
@AfterThrowing(pointcut = "execution(* com.example..*(..))", throwing = "error")
public void logAfterThrowing(JoinPoint joinPoint, Throwable error) {
// 方法抛出异常后逻辑
}
}
Spring生命周期管理

@PostConstruct
用途:在依赖注入完成后自动调用,用于初始化。
使用场景:需要在Bean初始化后执行逻辑时使用。
java
复制代码

@Component
public class MyComponent {
@PostConstruct
public void init() {
// 初始化逻辑
}
}

@PreDestroy
用途:在Bean销毁前自动调用,用于清理资源。
使用场景:需要在Bean销毁前执行逻辑时使用。
java
复制代码

@Component
public class MyComponent {
@PreDestroy
public void cleanup() {
// 清理资源逻辑
}
}
Swagger相关

@EnableSwagger2
用途:启用Swagger2,用于生成API文档。
使用场景:需要生成API文档时使用。
java
复制代码

@SpringBootApplication
@EnableSwagger2
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

@ApiModelProperty
用途:用于Swagger,描述Model属性。
使用场景:在Model类的字段上,描述字段信息。
java
复制代码

public class User {
@ApiModelProperty(value = "用户ID")
private Long id;
@ApiModelProperty(value = "用户名")
private String username;

}
事务管理

@Transactional
用途:声明事务,管理事务边界。
使用场景:需要事务管理时使用。
java
复制代码

@Service
@Transactional
public class MyService {
public void performAction() {
// 业务逻辑
}
}
数据源管理

@DataSource
用途:配置数据源,通常用于多数据源配置。
使用场景:需要配置多数据源时使用。
java
复制代码

@Bean(name = "primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
自定义注解

@TradeMsgAspectAnnotation
用途:示例自定义注解,通常用于标识特殊的业务逻辑。
使用场景:在自定义AOP中使用,标识特定逻辑。
java
复制代码

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TradeMsgAspectAnnotation {
// 自定义属性
}

XXL-JOB相关
@XxlJobBeanManage
用途:用于分布式任务调度的注解。
使用场景:在XXL-JOB任务调度中使用。
java
复制代码

@Component
@XxlJobBeanManage
public class MyJob {
// 任务调度逻辑
}

@XxlJob
用途:XXL-JOB任务调度注解。
使用场景:定义XXL-JOB任务。
java
复制代码

@XxlJob("myJobHandler")
public void myJobHandler() {
// 任务逻辑
}
MyBatis相关

@Intercepts
用途:MyBatis插件注解,用于拦截SQL执行。
使用场景:自定义MyBatis插件。
java
复制代码

@Intercepts({
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class MyPlugin implements Interceptor {
// 插件逻辑
}

@Signature
用途:MyBatis插件注解,定义拦截的方法。
使用场景:在MyBatis插件中使用。
java
复制代码

@Intercepts({
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class MyPlugin implements Interceptor {
// 插件逻辑
}
其它注解

@EnableScheduling
用途:启用Spring的计划任务。
使用场景:需要使用@Scheduled时使用。
java

复制代码
@SpringBootApplication
@EnableScheduling
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

@Scheduled
用途:定义计划任务,按照固定频率或时间点执行。
使用场景:定义需要定期执行的任务。
java
复制代码

@Component
public class ScheduledTasks {
@Scheduled(cron = "0 0 * * * ?")
public void performTask() {
// 任务逻辑
}
}

@Retryable
用途:定义可重试的操作。
使用场景:需要重试逻辑时使用。
java
复制代码

@Service
public class MyService {
@Retryable(value = Exception.class, maxAttempts = 3)
public void performAction() {
// 可能抛出异常的操作
}
}

@Recover
用途:定义重试失败后的恢复逻辑。
使用场景:与@Retryable结合使用。
java
复制代码

@Service
public class MyService {
@Retryable(value = Exception.class, maxAttempts = 3)
public void performAction() {
// 可能抛出异常的操作
}
@Recover
public void recover(Exception e) {
    // 恢复逻辑
}

}

@Cacheable
用途:启用缓存功能,将方法结果缓存。
使用场景:需要缓存方法结果时使用。
java
复制代码

@Service
public class MyService {
@Cacheable("items")
public Item getItem(Long id) {
// 获取Item逻辑
}
}

@CacheEvict
用途:清除缓存。
使用场景:需要清除缓存时使用。
java
复制代码

@Service
public class MyService {
@CacheEvict(value = "items", allEntries = true)
public void clearCache() {
// 清除缓存逻辑
}
}

@CachePut
用途:更新缓存。
使用场景:需要更新缓存时使用。
java
复制代码

@Service
public class MyService {
@CachePut(value = "items", key = "#item.id")
public Item updateItem(Item item) {
// 更新Item逻辑
}
}

@ConditionalOnProperty
用途:根据配置属性值,决定是否启用某个Bean。
使用场景:有条件地创建Bean。
java
复制代码

@Configuration
public class MyConfig {
@Bean
@ConditionalOnProperty(name = "feature.enabled", havingValue = "true")
public MyFeature myFeature() {
return new MyFeature();
}
}

@ConditionalOnClass
用途:当类路径中存在指定类时,启用某个Bean。
使用场景:有条件地创建Bean。
java
复制代码

@Configuration
@ConditionalOnClass(name = "com.example.SomeClass")
public class MyConfig {
@Bean
public MyFeature myFeature() {
return new MyFeature();
}
}

@ConditionalOnMissingBean
用途:当Spring上下文中缺少指定Bean时,创建某个Bean。
使用场景:有条件地创建Bean。
java
复制代码

@Configuration
public class MyConfig {
@Bean
@ConditionalOnMissingBean
public MyFeature myFeature() {
return new MyFeature();
}
}

数据库连接增删改查
redis连接增删改查
mq连接
定时任务相关

3.Spring Boot 中的关键组件

3.1 Spring MVC

3.1.1用一个简单的控制器例子解释 MVC 架构模式的基本概念和 Spring MVC 的工作原理

11、MVC基本概念
MVC全名是Model View Controller。

是模型(model)-视图(view)-控制器(controller)的缩写。
是一种使用mvc设计创建 Web 应用程序的模式。
是一种软件设计典范。
是软件工程中的一种软件架构模式。
MVC将业务逻辑、数据、界面显示分离的方法组织代码,在改变其中一层时,另外两层可能不需要改变,或作较少的修改即可。

Model(模型)
表示应用程序核心。

是应用程序中用于处理应用程序数据逻辑的部分。

通常模型对象负责在数据库中存取数据。

View(视图)
显示数据(数据库记录)。

是应用程序中处理数据显示的部分。

通常视图是依据模型数据创建的。

Controller(控制器)
是应用程序中处理用户交互的部分。通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。

2.MVC原理
MVC原理是View接收用户输入,发送请求给Controller,Controller调用Module完成具体操作。Module从数据库获取数据并进行业务逻辑判断,然后触发事件也就是间接返回数据给View。

为了加深大家的印象,我们引入MVP与MVC进行对比记忆。是MVC中的C改为P,英文单词全拼为Presenter,读作协调器。MVP即为即模型-视图-协调器。

下图是两种模式的对比

原文链接:https://blog.csdn/2301_77609559/article/details/134323575

3.2Spring Data

3.2.2通过一个简单的实体类和仓库接口演示 Spring Data JPA 的基本用法

1SpringDataJpa框架使用文档
一、什么是 Jpa ?
jpa 的全称是 Java Persistence API , 中文的字面意思就是 java 的持久层 API , jpa 就是定义了一系列标准,让实体类和数据库中的表建立一个对应的关系,当我们在使用 java 操作实体类的时候能达到操作数据库中表的效果(不用写sql ,就可以达到效果),jpa 的实现思想即是 ORM (Object Relation Mapping),对象关系映射,用于在关系型数据库和业务实体对象之间作一个映射。

jpa 并不是一个框架,是一类框架的总称,持久层框架 Hibernate 是 jpa 的一个具体实现,本文要谈的 spring data jpa 又是在 Hibernate 的基础之上的封装实现。

使用 jpa 是可以解决一些我们写 sql 语句的烦恼,相反另一个数据库层的框架 mybatis是专注 sql 语句的;

二、SpringDataJpa常用的 jpa 的配置

项目依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

application.properties 配置

复制代码
#项目端口的常用配置
server.port=8081

数据库连接的配置

spring.datasource.url=jdbc:mysql:///jpa?useSSL=false
spring.datasource.username=root
spring.datasource.password=zempty123
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

#数据库连接池的配置,hikari 连接池的配置
spring.datasource.hikari.idle-timeout=30000
spring.datasource.hikari.connection-timeout=10000
spring.datasource.hikari.maximum-pool-size=15
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.auto-commit=true

#通过 jpa 自动生成数据库中的表

spring.jpa.hibernate.ddl-auto=update
#该配置比较常用,当服务首次启动会在数据库中生成相应表,后续启动服务时如果实体类有增加属性会在数据中添加相应字段,
#原来数据仍在,该配置除了 update ,
#还有其他配置值,
#create :该值慎用,每次重启项目的时候都会删除表结构,重新生成,原来数据会丢失不见。
#create-drop :慎用,当项目关闭,数据库中的表会被删掉。
#validate : 验证数据库和实体类的属性是否匹配,不匹配将会报错。

spring.jpa.show-sql=true
#该配置当在执行数据库操作的时候会在控制台打印 sql 语句,方便我们检查排错等。

spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
#数据库的方言配置。
复制代码
三、类映射到数据库表的常用注解

1、@Entity

用来注解该类是一个实体类用来进行和数据库中的表建立关联关系,首次启动项目的时候,默认会在数据中生成一个同实体类相同名字的表(table),也可以通过注解中的 name 属性来修改表(table)名称, 如@Entity(name=“stu”) , 这样数据库中表的名称则是 stu 。 该注解十分重要,如果没有该注解首次启动项目的时候你会发现数据库没有生成对应的表。

2、@Id

@Id 类的属性注解,该注解表明该属性字段是一个主键,该属性必须具备,不可缺少。

3、@GeneratedValue

该注解通常和 @Id 主键注解一起使用,用来定义主键的呈现形式,

该注解通常有多种使用策略:

@GeneratedValue(strategy= GenerationType.IDENTITY) 该注解由数据库自动生成,主键自增型,在 mysql 数据库中使用最频繁,oracle 不支持。
@GeneratedValue(strategy= GenerationType.AUTO) 主键由程序控制,默认的主键生成策略,oracle 默认是序列化的方式,mysql 默认是主键自增的方式。
@GeneratedValue(strategy= GenerationType.SEQUENCE) 根据底层数据库的序列来生成主键,条件是数据库支持序列,Oracle支持,Mysql不支持。
@GeneratedValue(strategy= GenerationType.TABLE) 使用一个特定的数据库表格来保存主键,较少使用。
以上的主键生成策略当中,在数据库 mysql 当中 IDENTITY 和 AUTO 用的较多,二者当中 IDENTITY 用的多些

4、@Column

是一个类的属性注解,该注解可以定义一个字段映射到数据库属性的具体特征,比如字段长度,映射到数据库时属性的具体名字等。

5、@Transient

是一个属性注解,该注解标注的字段不会被应射到数据库当中。

实例代码:

复制代码
@Setter//lombok 的注解
@Getter//lombok 的注解
@Accessors(chain = true) //lombok 的注解
@Entity(name = “stu”)
//@Table
public class Student {
@Id
@GeneratedValue(strategy= GenerationType.TABLE)
private long id;

@Column(length = 100)
private String name;

@Transient
private String test;

private int age;

private LocalTime onDuty;

private LocalDate onPosition;

private LocalDateTime birthdayTime;

}
复制代码
四、使用SpringDataJpa进行增删改查
定义Dao层

定义一个 Student 的 dao 层,这样我们的增删改查就已经有了

public interface StudentRepository extends JpaRepository<Student,Integer> {
}
在 spring boot 项目中在 dao 层我们不需要写 @Repository 注解 ,我们在使用的时候直接注入使用就好,这里需要说明一点, 我们在更新数据的时候,可以先查询,然后更改属性,使用 save 方法保存就好。

使用关键字自定义查询

我们可以使用 jpa 提供的 find 和 get 关键字完成常规的查询操作,使用 delete 关键字完成删除,使用 count 关键字完成统计等

复制代码
public interface StudentRepository extends JpaRepository<Student,Integer> {

// 查询数据库中指定名字的学生
List<Student> findByName(String name);

// 根据名字和年龄查询学生
List<Student> getByNameAndAge(String name, Integer age);

//删除指定名字的学生,删除时需要在Service层调用是添加事务注解@Transactional
Long deleteByName(String name);

// 统计指定名字学生的数量
Long countByName(String name);

}
复制代码
jpa关键词查询是通过方法名称关键字的搭配,底层生成sql的方式来实现与数据库的交互,其关键词的搭配又很多方式,基本能覆盖表查询的所又情况,其余关键字查询可以自行百度;

3、使用 sql 增删改查

jpa同样支持写sql语句来操作数据,sql 有两种呈现形式:

JPQL 形式的 sql 语句,from 后面是以类名呈现的。
原生的 sql 语句,需要使用 nativeQuery = true 指定使用原生 sql
示例代码
复制代码
public interface ClassRoomRepository extends JpaRepository<ClassRoom,Integer> {

//使用的 JPQL 的 sql 形式 from 后面是类名
// ?1 代表是的是方法中的第一个参数
@Query("select s from ClassRoom s where s.name =?1")
List<ClassRoom> findClassRoom1(String name);

//这是使用正常的 sql 语句去查询
// :name 是通过 @Param 注解去确定的
@Query(nativeQuery = true,value = "select * from class_room c where c.name =:name")
List<ClassRoom> findClassRoom2(@Param("name")String name);

}
复制代码
sql 中的参数传递也有两种形式:

使用问号 ?,紧跟数字序列,数字序列从1 开始,如 ?1 接收第一个方法参数的值。

使用冒号:,紧跟参数名,参数名是通过@Param 注解来确定。

使用 Sort 来对数据进行一个排序

实例化Sort

复制代码
public List getTeachers(String subject) {
// 第一种方法实例化出 Sort 类,根据年龄进行升序排列
Sort sort1 = Sort.by(Sort.Direction.ASC, “age”);

    //定义多个字段的排序
    Sort sort2 = Sort.by(Sort.Direction.DESC, "id", "age");

    // 通过实例化 Sort.Order 来排序多个字段
    List<Sort.Order> orders = new ArrayList<>();
    Sort.Order order1 = new Sort.Order(Sort.Direction.DESC, "id");
    Sort.Order order2 = new Sort.Order(Sort.Direction.DESC, "age");
    orders.add(order1);
    orders.add(order2);
    Sort sort3 = Sort.by(orders);

    //可以传不同的 sort1,2,3 去测试效果
    return teacherRepositoty.getTeachers(subject, sort1);
}

复制代码
Dao层多传一个参数Sort

复制代码
public interface TeacherRepositoty extends JpaRepository<Teacher,Integer> {

// 正常使用,只是多加了一个 sort 参数而已
@Query("select t from Teacher t where t.subject = ?1")
List<Teacher> getTeachers(String subject, Sort sort);

}
复制代码
jpa 的分页操作

实例化 PageRequest (PageRequest是Pageable接口的实现类)

复制代码
public Page getPage(@PathVariable(“subject”) String subject) {
// 第一种方法实例化 Pageable
Pageable pageable1 = PageRequest.of(1, 2);

    //第二种实例化 Pageable
    Sort sort = Sort.by(Sort.Direction.ASC, "age");
    Pageable pageable2 = PageRequest.of(1, 2, sort);

    //第三种实例化 Pageable
    Pageable pageable3 = PageRequest.of(1, 2, Sort.Direction.DESC, "age");

    //可以传入不同的 Pageable,测试效果
    Page page = teacherRepositoty.getPage(subject, pageable3);
    System.out.println(page.getTotalElements());
    System.out.println(page.getTotalPages());
    System.out.println(page.hasNext());
    System.out.println(page.hasPrevious());
    System.out.println(page.getNumberOfElements());
    System.out.println(page.getSize());
    return page;
}

复制代码
PageRequest 一共有三个可以实例化的静态方法:

public static PageRequest of(int page, int size)
public static PageRequest of(int page, int size, Sort sort) 分页的同时还可以针对分页后的结果进行一个排序。
public static PageRequest of(int page, int size, Direction direction, String… properties) 直接针对字段进行排序。
Dao层多传一个参数Pageable

复制代码
public interface TeacherRepositoty extends JpaRepository<Teacher,Integer> {

//正常使用,只是多加了一个 Pageable 参数而已
@Query("select t from Teacher t where t.subject = :subject")
Page<Teacher> getPage(@Param("subject") String subject, Pageable pageable);

}
复制代码
五、jpa 使用 Specification
接口继承JpaSpecificationExecutor

复制代码
public interface TeacherRepositoty extends JpaRepository<Teacher,Integer> , JpaSpecificationExecutor {
}

//JpaSpecificationExecutor提供了如下的几个方法供我们使用 方法参数:Specification
public interface JpaSpecificationExecutor {

Optional<T> findOne(@Nullable Specification<T> var1);

List<T> findAll(@Nullable Specification<T> var1);

Page<T> findAll(@Nullable Specification<T> var1, Pageable var2);

List<T> findAll(@Nullable Specification<T> var1, Sort var2);

long count(@Nullable Specification<T> var1);

}
复制代码
实例化 Specification

Specification 是一个函数式接口,里面有一个抽象的方法:

Predicate toPredicate(Root var1, CriteriaQuery<?> var2, CriteriaBuilder var3);
参数说明:

Predicate 是用来建立 where 后的查寻条件的相当于上述sql语句的 age > 20。
Root 使用来定位具体的查询字段,比如 root.get(“age”) ,定位 age字段,
CriteriaBuilder是用来构建一个字段的范围,相当于 > ,= ,<,and …. 等等
CriteriaQuery 可以用来构建整个 sql 语句,可以指定sql 语句中的 select 后的查询字段,也可以拼接 where , groupby 和 having 等复杂语句。
代码示例

复制代码
//多条件查询并排序
public List<Entity类> queryBanner(Integer location) {
return findAll(
//实例化 Specification 类
(root, query, criteriaBuilder) -> {
// 构建查询条件
Predicate predicate = criteriaBuilder.equal(root.get(“数据库字段”),与数据库字段比较的参数 );
// 使用 and 连接上一个条件
predicate = criteriaBuilder.and(
predicate,
criteriaBuilder.equal(root.get(数据库字段), 与数据库字段比较的参数));
return predicate;
},
//传入Sort排序参数进行排序
Sort.by(Sort.Order.desc(“数据库字段”)));
}
复制代码
Jpa的Projection (投影映射)

作用:将从查询到的数据封装自定一的接口中;

自定义接口:

复制代码
public interface TeacherProjection {

String getName();

Integer getAge();

@Value("#{target.name +' and age is' + target.age}")
String getTotal();

}
复制代码
接口中的方法以 get 开头 + 属性名,属性名首字母大写, 例如 getName(), 也可以通过 @Value 注解中使用 target.属性名获取属性,也可以把多个属性值拼接成一个字符串。

定义好一个接口后,在查询方法中指定返回接口类型的数据即可;

1.核心方法

查询所有数据 findAll()
修改 添加数据 S save(S entity)
分页查询 Page findAll(Example example, Pageable pageable)
根据id查询 findOne()
根据实体类属性查询: findByProperty (type Property); 例如:findByAge(int age)
删除 void delete(T entity)
计数 查询 long count() 或者 根据某个属性的值查询总数 countByAge(int age)
是否存在 boolean existsById(ID primaryKey)
2.查询关键字

复制代码
-and

And 例如:findByUsernameAndPassword(String user, Striang pwd);

-or
Or 例如:findByUsernameOrAddress(String user, String addr);

-between
Between 例如:SalaryBetween(int max, int min);

-“<”
LessThan 例如: findBySalaryLessThan(int max);

-“>”
GreaterThan 例如: findBySalaryGreaterThan(int min);

-is null
IsNull 例如: findByUsernameIsNull();

-is not null
IsNotNull NotNull 与 IsNotNull 等价 例如: findByUsernameIsNotNull();

-like
Like 例如: findByUsernameLike(String user);

-not like
NotLike 例如: findByUsernameNotLike(String user);

-order by
OrderBy 例如: findByUsernameOrderByNameAsc(String user);直接通过name正序排序

-“!=”
Not 例如: findByUsernameNot(String user);

-in
In 例如: findByUsernameIn(Collection userList) ,方法的参数可以是 Collection 类型,也可以是数组或者不定长参数;

-not in
NotIn 例如: findByUsernameNotIn(Collection userList) ,方法的参数可以是 Collection 类型,也可以是数组或者不定长参数;

-Top/Limit
查询方法结果的数量可以通过关键字来限制,first 或者 top都可以使用。top/first加数字可以指定要返回最大结果的大小 默认为1
复制代码
例如:

复制代码
User findFirstByOrderByLastnameAsc();
User findTopByOrderByAgeDesc();
Page queryFirst10ByLastname(String lastname, Pageable pageable);
Slice findTop3ByLastname(String lastname, Pageable pageable);
List findFirst10ByLastname(String lastname, Sort sort);
List findTop10ByLastname(String lastname, Pageable pageable);
复制代码
详细查询语法
关键词 示例 对应的sql片段
And

findByLastnameAndFirstname

… where x.lastname = ?1 and x.firstname = ?2

Or

findByLastnameOrFirstname

… where x.lastname = ?1 or x.firstname = ?2

Is,Equals

findByFirstname,findByFirstnameIs,findByFirstnameEquals

… where x.firstname = ?1

Between

findByStartDateBetween

… where x.startDate between ?1 and ?2

LessThan

findByAgeLessThan

… where x.age < ?1

LessThanEqual

findByAgeLessThanEqual

… where x.age <= ?1

GreaterThan

findByAgeGreaterThan

… where x.age > ?1

GreaterThanEqual

findByAgeGreaterThanEqual

… where x.age >= ?1

After

findByStartDateAfter

… where x.startDate > ?1

Before

findByStartDateBefore

… where x.startDate < ?1

IsNull

findByAgeIsNull

… where x.age is null

IsNotNull,NotNull

findByAge(Is)NotNull

… where x.age not null

Like

findByFirstnameLike

… where x.firstname like ?1

NotLike

findByFirstnameNotLike

… where x.firstname not like ?1

StartingWith

findByFirstnameStartingWith

… where x.firstname like ?1 (parameter bound with appended %)

EndingWith

findByFirstnameEndingWith

… where x.firstname like ?1 (parameter bound with prepended %)

Containing

findByFirstnameContaining

… where x.firstname like ?1 (parameter bound wrapped in %)

OrderBy

findByAgeOrderByLastnameDesc

… where x.age = ?1 order by x.lastname desc

Not

findByLastnameNot

… where x.lastname <> ?1

In

findByAgeIn(Collection ages)

… where x.age in ?1

NotIn

findByAgeNotIn(Collection ages)

… where x.age not in ?1

True

findByActiveTrue()

… where x.active = true

False

findByActiveFalse()

… where x.active = false

IgnoreCase

findByFirstnameIgnoreCase

… where UPPER(x.firstame) = UPPER(?1)

3.内置方法

Sort_排序
Sort sort =new Sort(Sort.Direction.ASC,“id”);
//其中第一个参数表示是降序还是升序(此处表示升序)
//第二个参数表示你要按你的 entity(记住是entity中声明的变量,不是数据库中表对应的字段)中的那个变量进行排序
PageRequest_分页
PageRequest pageRequest = new PageRequest(index, num, sort);
//index偏移量 num查询数量 sort排序
分页+排序实现:

复制代码
DemoBean demoBean = new DemoBean();
demoBean.setAppId(appId); //查询条件
//创建查询参数
Example example = Example.of(demoBean);
//获取排序对象
Sort sort = new Sort(Sort.Direction.DESC, “id”);
//创建分页对象
PageRequest pageRequest = new PageRequest(index, num, sort);
//分页查询
return demoRepository.findAll(example, pageRequest).getContent();
复制代码
Example_实例查询
创建一个ExampleMatcher对象,最后再用Example的of方法构造相应的Example对象并传递给相关查询方法。我们看看Spring的例子。

复制代码
Person person = new Person();
person.setFirstname(“Dave”); //Firstname = ‘Dave’

ExampleMatcher matcher = ExampleMatcher.matching()
.withMatcher(“name”, GenericPropertyMatchers.startsWith()) //姓名采用“开始匹配”的方式查询
.withIgnorePaths(“int”); //忽略属性:是否关注。因为是基本类型,需要忽略掉

Example example = Example.of(person, matcher); //Example根据域对象和配置创建一个新的ExampleMatcher
复制代码
ExampleMatcher用于创建一个查询对象,上面的代码就创建了一个查询对象。withIgnorePaths方法用来排除某个属性的查询。withIncludeNullValues方法让空值也参与查询,就是我们设置了对象的姓,而名为空值.

1、概念定义:

上面例子中,是这样创建“实例”的:Example<Customer> ex = Example.of(customer, matcher);我们看到,Example对象,由customer和matcher共同创建。

A、实体对象:在持久化框架中与Table对应的域对象,一个对象代表数据库表中的一条记录,如上例中Customer对象。在构建查询条件时,一个实体对象代表的是查询条件中的“数值”部分。如:要查询名字是“Dave”的客户,实体对象只能存储条件值“Dave”。

B、匹配器:ExampleMatcher对象,它是匹配“实体对象”的,表示了如何使用“实体对象”中的“值”进行查询,它代表的是“查询方式”,解释了如何去查的问题。如:要查询FirstName是“Dave”的客户,即名以“Dave"开头的客户,该对象就表示了“以什么开头的”这个查询方式,如上例中:withMatcher("name", GenericPropertyMatchers.startsWith())

C、实例:即Example对象,代表的是完整的查询条件。由实体对象(查询条件值)和匹配器(查询方式)共同创建。

再来理解“实例查询”,顾名思义,就是通过一个例子来查询。要查询的是Customer对象,查询条件也是一个Customer对象,通过一个现有的客户对象作为例子,查询和这个例子相匹配的对象。

2、特点及约束(局限性):

A、支持动态查询。即支持查询条件个数不固定的情况,如:客户列表中有多个过滤条件,用户使用时在“地址”查询框中输入了值,就需要按地址进行过滤,如果没有输入值,就忽略这个过滤条件。对应的实现是,在构建查询条件Customer对象时,将address属性值置具体的条件值或置为null。

B、不支持过滤条件分组。即不支持过滤条件用 or(或) 来连接,所有的过滤查件,都是简单一层的用 and(并且) 连接。

C、仅支持字符串的开始/包含/结束/正则表达式匹配 和 其他属性类型的精确匹配。查询时,对一个要进行匹配的属性(如:姓名 name),只能传入一个过滤条件值,如以Customer为例,要查询姓“刘”的客户,“刘”这个条件值就存储在表示条件对象的Customer对象的name属性中,针对于“姓名”的过滤也只有这么一个存储过滤值的位置,没办法同时传入两个过滤值。正是由于这个限制,有些查询是没办法支持的,例如要查询某个时间段内添加的客户,对应的属性是 addTime,需要传入“开始时间”和“结束时间”两个条件值,而这种查询方式没有存两个值的位置,所以就没办法完成这样的查询。

3、ExampleMatcher的使用 :

一些问题:
(1)Null值的处理。当某个条件值为Null,是应当忽略这个过滤条件呢,还是应当去匹配数据库表中该字段值是Null的记录?
(2)基本类型的处理。如客户Customer对象中的年龄age是int型的,当页面不传入条件值时,它默认是0,是有值的,那是否参与查询呢?
(3)忽略某些属性值。一个实体对象,有许多个属性,是否每个属性都参与过滤?是否可以忽略某些属性?
(4)不同的过滤方式。同样是作为String值,可能“姓名”希望精确匹配,“地址”希望模糊匹配,如何做到?

(5)大小写匹配。字符串匹配时,有时可能希望忽略大小写,有时则不忽略,如何做到?

一些方法:
1、关于基本数据类型。
实体对象中,避免使用基本数据类型,采用包装器类型。如果已经采用了基本类型,

而这个属性查询时不需要进行过滤,则把它添加到忽略列表(ignoredPaths)中。

2、Null值处理方式。

默认值是 IGNORE(忽略),即当条件值为null时,则忽略此过滤条件,一般业务也是采用这种方式就可满足。当需要查询数据库表中属性为null的记录时,可将值设为INCLUDE,这时,对于不需要参与查询的属性,都必须添加到忽略列表(ignoredPaths)中,否则会出现查不到数据的情况。

3、默认配置、特殊配置。

默认创建匹配器时,字符串采用的是精确匹配、不忽略大小写,可以通过操作方法改变这种默认匹配,以满足大多数查询条件的需要,如将“字符串匹配方式”改为CONTAINING(包含,模糊匹配),这是比较常用的情况。对于个别属性需要特定的查询方式,可以通过配置“属性特定查询方式”来满足要求。

4、非字符串属性

如约束中所谈,非字符串属性均采用精确匹配,即等于。

5、忽略大小写的问题。

忽略大小的生效与否,是依赖于数据库的。例如 MySql 数据库中,默认创建表结构时,字段是已经忽略大小写的,所以这个配置与否,都是忽略的。如果业务需要严格区分大小写,可以改变数据库表结构属性来实现,具体可百度。

一些例子:
综合使用:

复制代码
//创建查询条件数据对象
Customer customer = new Customer();
customer.setName(“zhang”);
customer.setAddress(“河南省”);
customer.setRemark(“BB”);

//创建匹配器,即如何使用查询条件
ExampleMatcher matcher = ExampleMatcher.matching() //构建对象
.withStringMatcher(StringMatcher.CONTAINING) //改变默认字符串匹配方式:模糊查询
.withIgnoreCase(true) //改变默认大小写忽略方式:忽略大小写
.withMatcher(“address”, GenericPropertyMatchers.startsWith()) //地址采用“开始匹配”的方式查询
.withIgnorePaths(“focus”); //忽略属性:是否关注。因为是基本类型,需要忽略掉

//创建实例
Example ex = Example.of(customer, matcher);

//查询
List ls = dao.findAll(ex);
复制代码
查询null值:

复制代码
//创建查询条件数据对象
Customer customer = new Customer();

//创建匹配器,即如何使用查询条件
ExampleMatcher matcher = ExampleMatcher.matching() //构建对象
.withIncludeNullValues() //改变“Null值处理方式”:包括
.withIgnorePaths(“id”,“name”,“sex”,“age”,“focus”,“addTime”,“remark”,“customerType”); //忽略其他属性

//创建实例
Example ex = Example.of(customer, matcher);

//查询
List ls = dao.findAll(ex);
复制代码
三.Spring data jpa 注解
1.Repository注解

@Modifying //做update操作时需要添加

@Query // 自定义Sql

@Query(value = “SELECT * FROM USERS WHERE X = ?1”, nativeQuery = true)
User findByEmailAddress(String X);
@Query(“select u from User u where u.firstname = :firstname”) //不加nativeQuery应使用HQL
User findByLastnameOrFirstname(@Param(“lastname”) String lastname);
@Transactional //事务

@Async //异步操作

2.Entity注解

复制代码
@Entity //不写@Table默认为user
@Table(name=“t_user”) //自定义表名
public class user {

@Id //主键
@GeneratedValue(strategy = GenerationType.AUTO)//采用数据库自增方式生成主键
//JPA提供的四种标准用法为TABLE,SEQUENCE,IDENTITY,AUTO.
//TABLE:使用一个特定的数据库表格来保存主键。
//SEQUENCE:根据底层数据库的序列来生成主键,条件是数据库支持序列。
//IDENTITY:主键由数据库自动生成(主要是自动增长型)
//AUTO:主键由程序控制。

@Transient //此字段不与数据库关联
@Version//此字段加上乐观锁
//字段为name,不允许为空,用户名唯一
@Column(name = "name", unique = true, nullable = false)
private String name;

@Temporal(TemporalType.DATE)//生成yyyy-MM-dd类型的日期
//出参时间格式化
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
//入参时,请求报文只需要传入yyyymmddhhmmss字符串进来,则自动转换为Date类型数据
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm")
private Date createTime;

public String getName() {
    return name;
}
public void setName(String name) {
    this.name = name;
}

}
复制代码
四.继承JpaSpecificationExecutor接口进行复杂查询
spring data jpa 通过创建方法名来做查询,只能做简单的查询,那如果我们要做复杂一些的查询呢,多条件分页怎么办,这里,spring data jpa为我们提供了JpaSpecificationExecutor接口,只要简单实现toPredicate方法就可以实现复杂的查询

参考:https://wwwblogs/happyday56/p/4661839.html

1.首先让我们的接口继承于JpaSpecificationExecutor

public interface TaskDao extends JpaSpecificationExecutor{
}
2.JpaSpecificationExecutor提供了以下接口

复制代码
public interface JpaSpecificationExecutor {

T findOne(Specification<T> spec);

List<T> findAll(Specification<T> spec);

Page<T> findAll(Specification<T> spec, Pageable pageable);

List<T> findAll(Specification<T> spec, Sort sort);

long count(Specification<T> spec);

}

//其中Specification就是需要我们传入查询方法的参数,它是一个接口

public interface Specification {

Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);

}
复制代码
提供唯一的一个方法toPredicate,我们只要按照JPA 2.0 criteria api写好查询条件就可以了,关于JPA 2.0 criteria api的介绍和使用,欢迎参考
http://blog.csdn/dracotianlong/article/details/28445725

http://developer.51cto/art/200911/162722.htm

3.接下来我们在service bean

复制代码
@Service
public class TaskService {

@Autowired TaskDao taskDao ;


/**
 * 复杂查询测试
 * @param page
 * @param size
 * @return
 */
public Page<Task> findBySepc(int page, int size){

    PageRequest pageReq = this.buildPageRequest(page, size);
    Page<Task> tasks = this.taskDao.findAll(new MySpec(), pageReq);
    //传入了new MySpec() 既下面定义的匿名内部类 其中定义了查询条件
    return tasks;

}

 /**
  * 建立分页排序请求 
  * @param page
  * @param size
  * @return
  */
 private PageRequest buildPageRequest(int page, int size) {
       Sort sort = new Sort(Direction.DESC,"createTime");
       return new PageRequest(page,size, sort);
 }

/**
 * 建立查询条件
 */
private class MySpec implements Specification<Task>{

    @Override
    public Predicate toPredicate(Root<Task> root, CriteriaQuery<?> query, CriteriaBuilder cb) {

 //1.混合条件查询
      Path<String> exp1 = root.get("taskName");
        Path<Date>  exp2 = root.get("createTime");
        Path<String> exp3 = root.get("taskDetail");
        Predicate predicate = cb.and(cb.like(exp1, "%taskName%"),cb.lessThan(exp2, new Date()));
        return cb.or(predicate,cb.equal(exp3, "kkk"));

       /* 类似的sql语句为:
        Hibernate: 
            select
                count(task0_.id) as col_0_0_ 
            from
                tb_task task0_ 
            where
                (
                    task0_.task_name like ?
                ) 
                and task0_.create_time<? 
                or task0_.task_detail=?
        */

//2.多表查询
    Join<Task,Project> join = root.join("project", JoinType.INNER);
        Path<String> exp4 = join.get("projectName");
        return cb.like(exp4, "%projectName%");

       /* Hibernate: 
        select
            count(task0_.id) as col_0_0_ 
        from
            tb_task task0_ 
        inner join
            tb_project project1_ 
                on task0_.project_id=project1_.id 
        where
            project1_.project_name like ?*/ 
       return null ;  
    }
}

}
复制代码

4.实体类task代码如下

复制代码
@Entity
@Table(name = “tb_task”)
public class Task {

private Long id ;
private String taskName ;
private Date createTime ;
private Project project;
private String taskDetail ;

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
public Long getId() {
    return id;
}
public void setId(Long id) {
    this.id = id;
}

@Column(name = "task_name")
public String getTaskName() {
    return taskName;
}
public void setTaskName(String taskName) {
    this.taskName = taskName;
}

@Column(name = "create_time")
@DateTimeFormat(pattern = "yyyy-MM-dd hh:mm:ss")
public Date getCreateTime() {
    return createTime;
}
public void setCreateTime(Date createTime) {
    this.createTime = createTime;
}


@Column(name = "task_detail")
public String getTaskDetail() {
    return taskDetail;
}
public void setTaskDetail(String taskDetail) {
    this.taskDetail = taskDetail;
}

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "project_id")
public Project getProject() {
    return project;
}
public void setProject(Project project) {
    this.project = project;
} 

}
复制代码
通过重写toPredicate方法,返回一个查询 Predicate,spring data jpa会帮我们进行查询。

也许你觉得,每次都要写一个类来实现Specification很麻烦,那或许你可以这么写

复制代码
public class TaskSpec {

public static Specification<Task> method1(){

    return new Specification<Task>(){
        @Override
        public Predicate toPredicate(Root<Task> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
            return null;
        } 
    };
}

public static Specification<Task> method2(){

    return new Specification<Task>(){
        @Override
        public Predicate toPredicate(Root<Task> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
            return null;
        } 
    };
} 

}
复制代码
那么用的时候,我们就这么用

Page tasks = this.taskDao.findAll(TaskSpec.method1(), pageReq);
五.Spring data jpa + QueryDSL 进行复杂查询
QueryDSL仅仅是一个通用的查询框架,专注于通过Java API构建类型安全的SQL查询。
Querydsl可以通过一组通用的查询API为用户构建出适合不同类型ORM框架或者是SQL的查询语句,也就是说QueryDSL是基于各种ORM框架以及SQL之上的一个通用的查询框架。
借助QueryDSL可以在任何支持的ORM框架或者SQL平台上以一种通用的API方式来构建查询。目前QueryDSL支持的平台包括JPA,JDO,SQL,Java Collections,RDF,Lucene,Hibernate Search。
P.s.配置可以根据官网介绍来配置

1 实体类

城市类:

复制代码
@Entity
@Table(name = “t_city”, schema = “test”, catalog = “”)
public class TCity {
//省略JPA注解标识
private int id;
private String name;
private String state;
private String country;
private String map;
}
复制代码
旅馆类:

复制代码
@Entity
@Table(name = “t_hotel”, schema = “test”, catalog = “”)
public class THotel {
//省略JPA注解标识
private int id;
private String name;
private String address;
private Integer city;//保存着城市的id主键
}
复制代码
2 单表动态分页查询

Spring Data JPA中提供了QueryDslPredicateExecutor接口,用于支持QueryDSL的查询操作

public interface tCityRepository extends JpaRepository<TCity, Integer>, QueryDslPredicateExecutor {
}
这样的话单表动态查询就可以参考如下代码:

复制代码
//查找出Id小于3,并且名称带有shanghai的记录.

//动态条件
QTCity qtCity = QTCity.tCity; //SDL实体类
//该Predicate为querydsl下的类,支持嵌套组装复杂查询条件
Predicate predicate = qtCity.id.longValue().lt(3).and(qtCity.name.like(“shanghai”));
//分页排序
Sort sort = new Sort(new Sort.Order(Sort.Direction.ASC,“id”));
PageRequest pageRequest = new PageRequest(0,10,sort);
//查找结果
Page tCityPage = tCityRepository.findAll(predicate,pageRequest);
复制代码
3 多表动态查询

QueryDSL对多表查询提供了一个很好地封装,看下面代码:

复制代码
/**
* 关联查询示例,查询出城市和对应的旅店
* @param predicate 查询条件
* @return 查询实体
*/
@Override
public List findCityAndHotel(Predicate predicate) {
JPAQueryFactory queryFactory = new JPAQueryFactory(em);
JPAQuery jpaQuery = queryFactory.select(QTCity.tCity,QTHotel.tHotel)
.from(QTCity.tCity)
.leftJoin(QTHotel.tHotel)
.on(QTHotel.tHotel.city.longValue().eq(QTCity.tCity.id.longValue()));
//添加查询条件
jpaQuery.where(predicate);
//拿到结果
return jpaQuery.fetch();
}
复制代码
城市表左连接旅店表,当该旅店属于这个城市时查询出两者的详细字段,存放到一个Tuple的多元组中.相比原生sql,简单清晰了很多.
那么该怎么调用这个方法呢?

复制代码
Test
public void findByLeftJoin(){
QTCity qtCity = QTCity.tCity;
QTHotel qtHotel = QTHotel.tHotel;
//查询条件
Predicate predicate = qtCity.name.like(“shanghai”);
//调用
List result = tCityRepository.findCityAndHotel(predicate);
//对多元组取出数据,这个和select时的数据相匹配
for (Tuple row : result) {
System.out.println(“qtCity:”+row.get(qtCity));
System.out.println(“qtHotel:”+row.get(qtHotel));
System.out.println(“--------------------”);
}
System.out.println(result);
}
复制代码
这样做的话避免了返回Object[]数组,下面是自动生成的sql语句:

复制代码
select
tcity0_.id as id1_0_0_,
thotel1_.id as id1_1_1_,
tcity0_.country as country2_0_0_,
tcity0_.map as map3_0_0_,
tcity0_.name as name4_0_0_,
tcity0_.state as state5_0_0_,
thotel1_.address as address2_1_1_,
thotel1_.city as city3_1_1_,
thotel1_.name as name4_1_1_
from
t_city tcity0_
left outer join
t_hotel thotel1_
on (
cast(thotel1_.city as signed)=cast(tcity0_.id as signed)
)
where
tcity0_.name like ? escape ‘!’
复制代码

4 多表动态分页查询

分页查询对于queryDSL无论什么样的sql只需要写一遍,会自动转换为相应的count查询,也就避免了文章开始的问题4,下面代码是对上面的查询加上分页功能:

复制代码
@Override
public QueryResults findCityAndHotelPage(Predicate predicate,Pageable pageable) {
JPAQueryFactory queryFactory = new JPAQueryFactory(em);
JPAQuery jpaQuery = queryFactory.select(QTCity.tCity.id,QTHotel.tHotel)
.from(QTCity.tCity)
.leftJoin(QTHotel.tHotel)
.on(QTHotel.tHotel.city.longValue().eq(QTCity.tCity.id.longValue()))
.where(predicate)
.offset(pageable.getOffset())
.limit(pageable.getPageSize());
//拿到分页结果
return jpaQuery.fetchResults();
复制代码
和上面不同之处在于这里使用了offset和limit限制查询结果.并且返回一个QueryResults,该类会自动实现count查询和结果查询,并进行封装.调用形式如下:

复制代码
@Test
public void findByLeftJoinPage(){
QTCity qtCity = QTCity.tCity;
QTHotel qtHotel = QTHotel.tHotel;
//条件
Predicate predicate = qtCity.name.like(“shanghai”);
//分页
PageRequest pageRequest = new PageRequest(0,10);
//调用查询
QueryResults result = tCityRepository.findCityAndHotelPage(predicate,pageRequest);
//结果取出
for (Tuple row : result.getResults()) {
System.out.println(“qtCity:”+row.get(qtCity));
System.out.println(“qtHotel:”+row.get(qtHotel));
System.out.println(“--------------------”);
}
//取出count查询总数
System.out.println(result.getTotal());
}
复制代码
生成的原生count查询sql,当该count查询结果为0的话,则直接返回,并不会再进行具体数据查询:

复制代码
select
count(tcity0_.id) as col_0_0_
from
t_city tcity0_
left outer join
t_hotel thotel1_
on (
cast(thotel1_.city as signed)=cast(tcity0_.id as signed)
)
where
tcity0_.name like ? escape ‘!’
复制代码
生成的原生查询sql:

复制代码
select
tcity0_.id as id1_0_0_,
thotel1_.id as id1_1_1_,
tcity0_.country as country2_0_0_,
tcity0_.map as map3_0_0_,
tcity0_.name as name4_0_0_,
tcity0_.state as state5_0_0_,
thotel1_.address as address2_1_1_,
thotel1_.city as city3_1_1_,
thotel1_.name as name4_1_1_
from
t_city tcity0_
left outer join
t_hotel thotel1_
on (
cast(thotel1_.city as signed)=cast(tcity0_.id as signed)
)
where
tcity0_.name like ? escape ‘!’ limit ?
复制代码
查看打印,可以发现对应的city也都是同一个对象,hotel是不同的对象.

5 改造
有了上面的经验,改造就变得相当容易了.
首先前面的一堆sql可以写成如下形式,无非是多了一些select和left join

复制代码
JPAQueryFactory factory = new JPAQueryFactory(entityManager);
factory.select( . p c a r d C a r d O r d e r ) . s e l e c t ( .pcardCardOrder) .select( .pcardCardOrder).select(.pcardVcardMake.vcardMakeDes)
.select( . p c a r d V t y p e . c a r d n u m R u l e I d , .pcardVtype.cardnumRuleId, .pcardVtype.cardnumRuleId,.pcardVtype.vtypeNm)
.select( . p c a r d C a r d b i n ) . l e f t J o i n ( .pcardCardbin) .leftJoin( .pcardCardbin).leftJoin(.pcardVcardMake).on( . p c a r d C a r d O r d e r . m a k e I d . e q ( .pcardCardOrder.makeId.eq( .pcardCardOrder.makeId.eq(.pcardVcardMake.vcardMakeId))
//…省略
复制代码
查询条件使用Predicate代替,放在service拼接,或者写一个生产条件的工厂都可以.

jpaQuery.where(predicate);
最后的分页处理就和之前的一样了

jpaQuery.offset(pageable.getOffset()).limit(pageable.getPageSize());
return jpaQuery.fetchResults();
写在最后:

个人认为jpa的意义就在于少用原生sql 为了方便开发 封装已经是在所难免了. 推荐多使用简单查询,需要使用动态查询的时候推荐使用JpaSpecificationExecutor个人认为比较好用.

虽然我还是喜欢原生的写法...

另外很多时候简单的条件可以在server层进行判断调用不同的Dao层方法就可以。
原文:https://wwwblogs/yayuya/p/17946288

3.3Spring Security

3.3.1举例说明如何配置一个简单的基于用户名和密码的认证和授权功能

1SpringSecurity 框架简介
概要
Spring 是非常流行和成功的 Java 应用开发框架,Spring Security 正是 Spring 家族中的 成员。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方 案。 正如你可能知道的关于安全方面的两个主要区域是“认证”和“授权”(或者访问控 制),一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权 (Authorization)两个部分,这两点也是 Spring Security 重要核心功能。

(1)用户认证指的是:验证某个用户是否为系统中的合法主体,也就是说用户能否访问 该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认 证过程。通俗点说就是系统认为用户是否能登录

(2)用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户 所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以 进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的 权限。通俗点讲就是系统判断用户是否有权限去做某些事情。
SpringSecurity 入门案例
创建一个项目


添加一个配置类

@Configuration
public class SecurityConfigextends1 extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin() // 表单登录
                .and()
                .authorizeRequests() // 认证配置
                .anyRequest() // 任何请求
                .authenticated(); // 都需要身份验证
    }
}

运行这个项目
访问 localhost:8080===》配置的端口

默认的用户名:user

密码在项目启动的时候在控制台会打印,注意每次启动的时候密码都回发生变化!

输入用户名,密码,这样表示可以访问了,404 表示我们没有这个控制器,但是我们可以 访问了。

原文链接:https://blog.csdn/m0_62436868/article/details/130651434

4.Spring Boot 的开发工具

4.1Spring Boot DevTools

4.1.1介绍 DevTools 的常见功能,如热部署、自动重启等,并演示如何在开发中使用

1Spring Boot DevTools简介
Spring Boot DevTools是Spring Boot提供的一个开发工具,旨在提高开发人员在开发阶段的效率。它提供了许多有用的功能,包括热部署、自动重启、自动配置等。

Spring Boot DevTools是一个为开发人员设计的工具,它提供了一系列功能来加速开发流程。其中最常用的功能是热部署,可以在应用程序运行时自动加载修改后的类,无需重启应用程序。此外,它还提供了自动重启、自动配置、内嵌的开发服务器等功能。

Spring Boot DevTools原理
Spring Boot DevTools的热部署功能是通过在开发环境中使用两个类加载器实现的。一个类加载器用于加载不会经常发生变化的类,如依赖库和框架类,而另一个类加载器用于加载开发者编写的应用程序类。当类文件发生变化时,只需要重新加载应用程序类加载器即可,而无需重启整个应用程序。
spring Boot Devtools优缺点
优点:

提高开发效率:在修改代码后无需重启应用程序即可看到变化,加快开发调试的速度。
自动重启:当应用程序的依赖关系发生变化时,Spring Boot DevTools会自动重新启动应用程序。
自动配置:DevTools可以根据不同的开发环境自动配置应用程序,无需手动修改配置文件。
内嵌的开发服务器:DevTools提供内嵌的开发服务器,可以在开发环境中轻松运行应用程序。
缺点:

部分配置可能会被忽略:由于DevTools的自动配置特性,可能会导致一些配置在开发环境中被忽略。
需要额外的依赖:使用DevTools需要将其添加为项目的依赖,增加项目的依赖复杂性。
Spring Boot DevTools集成步骤
第一步:添加maven依赖
自动方式:在创建项目的时候勾选Spring Boot DevTools工具依赖

手动方式:手动在项目的pom.xml文件中添加对spring-boot-devtools依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
</dependency>

第二步:IDEA热部署配置
IDEA2021版本前后的运行时自动编译配置位置有所改动,这里以IDEA2022为例进行演示,IDEA2021及之前的版本,请参考百度教程
勾选自动构建选项

勾选允许运行自启动选项

重新启动应用程序时,Spring Boot DevTools会自动应用热部署功能,默认保存文件后5秒生效,也可以手动进行热部署构建

原文链接:https://blog.csdn/yang2330648064/article/details/136069933

4.2Spring Boot Actuator

4.2.1用实际的案例说明如何配置 Actuator 来监控应用程序的健康状态和性能指标

1集成Spring Boot Actuator来监控和管理应用程序的健康状况、性能指标等
集成Spring Boot Actuator可以让您轻松地监控和管理应用程序的健康状况、性能指标等。Spring Boot Actuator提供了一组RESTful端点,用于暴露应用程序的各种信息,包括健康状况、运行时指标、配置信息等。以下是一个简单的示例,演示如何在Spring Boot应用程序中集成Spring Boot Actuator:

添加Spring Boot Actuator依赖:

首先,您需要添加Spring Boot Actuator依赖到您的Spring Boot项目中。

Maven依赖:

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

Gradle依赖:

implementation 'org.springframework.boot:spring-boot-starter-actuator'

配置Spring Boot Actuator端点:

在application.properties中配置Spring Boot Actuator端点的访问路径和权限等信息。

management.endpoints.web.base-path=/actuator
management.endpoints.web.exposure.include=*

在上面的示例中,我们将Actuator端点的访问路径设置为/actuator,并将所有端点都包含在内。

启动应用程序:

启动您的Spring Boot应用程序,Spring Boot Actuator将自动暴露端点,并通过HTTP暴露给外部。

访问Actuator端点:

您可以通过浏览器或HTTP客户端访问Actuator端点来获取应用程序的各种信息。

健康状况:http://localhost:8080/actuator/health
信息:http://localhost:8080/actuator/info
运行时指标:http://localhost:8080/actuator/metrics
除此之外,Actuator还提供了许多其他端点,如/actuator/env用于查看应用程序的环境变量,/actuator/loggers用于查看和修改日志配置等。

原文链接:https://blog.csdn/qiuyehuanghun/article/details/137062327

5.准备进行 Code Diff 的关键知识

5.1如何配置和构建 Spring Boot 项目

5.1.1通过 Maven 或 Gradle 构建项目,并演示如何添加起步依赖

1

5.2使用版本控制工具(如Git)管理代码

5.2.1详细说明如何使用 Git 进行基本的提交、分支管理和合并代码

11.1 了解Git
Git是一个免费的、开源的分布式版本控制系统,可以高速处理从小型到大型的各种项目
版本控制:是一种记录文件内容变化,以便将来查阅特定版本修订情况的系统
了解一下:集中式与分布式版本控制工具

     -- 集中式版本控制工具:如CVS、`SVN`等,都有一个单一的几种管理服务器,保存所有文件的修订版本,而协同工作的人通过客户端连接到这台服务器,从而取出最新的文件或者提交更新。缺点:中央服务器的单点故障;多(程序员)对一(中央服务器)

     -- 分布式版本控制工具:如git,客户端取的不是最新的文件快照,而是把代码仓库完整的镜像下来到本地库(克隆/备份)

工作机制:

1.2 Git安装
官方网址: https://git-scm/

找到对应电脑系统的网址:

配置选择,基本上一直下一步即可:

选择第一个就够使用:

Git常用命令
2.1 常用命令
git config --global user.name 用户名

设置用户签名

git config --global user.email 邮箱

设置用户签名

git init

初始化本地库

git status

查看本地库状态

git add 文件名

添加到暂存区

git commit-m “日志信息” 文件名

提交到本地库

git reflog/git log

查看历史记录

git reset --hard 版本号

版本穿梭

2.2 基本操作
2.2.1 打开git后端
(1)鼠标右键 --> Git Bash Here

找到自己新建的文件夹,然后鼠标右键 --> Git Bash Here(需要初始化文件)

2.2.2 设置用户签名

-git config --global user.name 浅风    设置用户签名
-git config --global user.email 480364454@qq.com     设置用户签名

只需要首次配置就好了,如何查看配置成功,根据下面路径查看对应文件夹显示即可:

2.2.3 初始化本地库

git init


2.2.4 查看本地状态

  git status
![在这里插入图片描述](https://img-blog.csdnimg/direct/f87b6b26527643c98b5cbfd48ec99a7f.png)

2.2.5 添加到暂存区

git add 需要添加到暂存库的文件名


2.2.6 提交到本地库

git commit -m "日志信息(名字可以任意取)" 暂存区需要添加到工作区的文件名


2.2.7 查看历史记录

git reflog
git log



2.2.8 版本穿梭

git reset --hard 版本号(查看历史记录时候给的版本号)

修改文件:


切换版本:

2.3 分支操作
2.3.1 分支的好处
同时并进行多个功能开发,提高了开发效率
各个分支再开发过程中,如果某个分支开发失败,不会对其他分支有任何影响,失败的分支删除重新开始即可
2.3.2 分支操作常用命令

git branch分支名创建分支
git branch -v查看分支
git checkout 分支名切换分支
git merge需要合并的分支名把指定的分支合并到当前分支上
2.3.3 查看分支
git branch -v


2.3.4 创建分支

git branch 分支名  (相当于对主线分支的复制)


2.3.5 切换分支
git checkout 分支名

切换后所在分支:


查看分支内容->修改分支内容->该分支状态依旧从工作区到暂存区到本地库


2.3.6 合并分支(正常合并)

git merge 需要合并的分支名  //把指定的分支合并到当前分支上

该合并就是对比当前支线与需要合并的支线内容,将不同的内容合并一起 ;这种合并最重要的就是原分支不要做任何修改,只对需要合并的分支修改就好。

查看分支和主线的内容:

合并后当前分支的内容:


2.3.7 合并分支(合并冲突)
注意事项就是当前分支和合并的分支都不要同时修改,多人合作时,商量好,最好是等到上一个人合并好之后,下一个人再合并进去/提取出来

需要手动合并——

查看内容:

手动合并,将更新的地方修改,然后放到本地库:

从远程库拉取文件
对需要上传的文件内容,右键 --> Git Bush Here
输入初始化命令 git init 回车

git init


拉取所有文件到项目中来,git pull origin master
git pull origin master
得到的效果:


(以上几步是从远程仓库上拉取文件到工作区)
4、将文件上传到远程库
在命令行中继续输入代码执行 git add . (add空格后有个点别忘了,表示需要将所有的文件提交到暂存区) :

git add . (这一步是先添加到暂存区)

从暂存区添加到远程库,说明自己为什么要上传,方便以后自己查阅 git commit -m “第一次上传”

git commit -m "第一次上传"

提交到远程仓库上面,git push origin master

git push origin (master/创建分支的名字)


上传成功的效果:

原文链接:https://blog.csdn/qq_45796592/article/details/128953729

5.3了解应用程序的结构和依赖关系

5.3.1介绍项目的主要文件和目录,以及如何解决依赖冲突和版本兼容性问题

1
依赖传递引起的版本冲突通常发生在多个库或模块之间,导致项目中的代码不能正常运行。解决这类问题的方法有以下几种:

使用版本控制系统:使用像Git这样的版本控制系统可以帮助追踪和管理依赖项的版本。你可以将依赖项的版本控制添加到你的Git仓库,以确保项目使用的所有依赖项都遵循同一版本。
检查你的构建系统:确保你的构建系统能够正确地解析依赖项。检查项目的依赖项配置,并确保所有的库都被正确地链接到项目。
检查和更新依赖项:在引入新版本的依赖项时,应检查新版本是否包含不兼容的更改。如果有不兼容的更改,可能需要修改代码以适应新版本。
更新你的依赖项管理工具:如果使用的是某个特定的包管理器(如npm、Maven或Gradle),那么考虑升级到最新版本,看看是否解决了冲突。
重新安装依赖项:有时,重新安装依赖项可能有助于解决版本冲突。
检查代码和配置:查看项目中的代码和配置文件,以确保没有错误的依赖关系或配置。例如,如果两个库需要相同的版本,那么可能会出现冲突。
咨询社区或专业论坛:如果你在解决冲突上遇到困难,可以向专业的开发者社区或论坛求助。这可能会帮助你找到解决方案。
通过上述方法,你应该能够有效地解决由依赖传递引起的版本冲突。但请注意,这些只是一般性的建议,具体解决方案可能因项目和具体情况而异。

原文链接:https://blog.csdn/qq_33240556/article/details/134870940## 6.Code Diff 的实践指南

6.1Code Diff 的基本原理和方法

6.1.1使用图示和简单的示例解释 Code Diff 是如何比较两个版本之间的代码差异的

1一、什么是codediff?
codediff为比较代码差异,有以下两种形式:
•code review:一般是dev开发人员进行,用于比较master与branch的差异,以判断代码规范是否合理、需求设计是否正确
code diff: 一般是qa进行,用于保证自己的checklist是否覆盖完全(还要注意代码改动影响的范围)

二、codediff的作用
•1、新增代码不符合代码编写规范
•2、防止在QA不知情下开发偷偷搭车
•3、加深对技术实现上的理解,特别是核心业务逻辑,更有利于精准设计case、补充测试点
•4、提前发现问题(通过逻辑实现直接判断是否存在严重bug, 而不是经过3个小时测试后才发现问题)
•5、评估代码改动带来的影响面(A依赖B,如果B接口发生改动,如果只对B进行接口测试,则A很有可能挂掉)
•6、开发merge过程中暴力解冲突,导致线上代码丢掉
7、其他。。。

三、codediff的时机
1、提测之后,提测分支与线上基线分支对比
2、bug修复后提测,提测分支与上一个版本对比
3、上线前(目前应该无法实现,目前灰度和线上为同一分支)

四、codediff前的准备工作
•熟悉需求的业务目标:需求文档和业务目标
•熟悉需求的技术实现:开发设计文档
•了解系统整体结构以及调用关系
•了解数据的流向
根据习惯选择diff工具, 推荐使用IDEA的git diff工具

五、如何做codediff
入口1:通过idea进行diff
2.1、首先拉取提测分支最新代码:
mkdir projectname //创建目录,用于拉取代码
git init //初始化
git remote add origin //拉取仓库代码
git fetch //从远程仓库获取最新分支
git pull origin dev20230704 //拉取对应的提测分支代码
git checkout prod_release //切换到某个分支

2.2、使用idea打开所在项目:
确定当前所在分支,点击idea底部–>“terminal”,执行 git branch,显示当前所在分支。

如果不在提测分支,执行git checkout 分支名,切换到提测分支。
2.3、选择基线,比对差异代码
idea中选中项目,右键–>Git–>compare with Branch

选择对应要对比的基线分支,即需要当前提测分支和哪个分支进行对比

找到差异代码:

入口2:通过gitlab进行diff

选择基线分支、被测分支后,点击“Compare”

六、codediff主要关注哪些问题?
6.1、代码是否和提测内容一致
1、首先检查是否有和本次项目不相关的代码,比如未开发完的需求部分代码、偷偷修改bug等,防止搭车;
2、不允许开发大量的进行代码格式化,导致变更的代码量很大,影响对测试范围的评估

3、逻辑是否都是完整闭合的,对状态处理考虑:初始、成功、失败以及处理中怎么处理,考虑所有异常分支,要多想else是否缺了;
4、是否有多余的接口调用、代码等等;

6.2、日志、业务监控是否完善
1、业务监控:通常在关键业务节点上添加,比如下载账单异常、提现异常
2、日志分级:日志分级别不合理,调试使用debug,运行时日志使用info,系统遇到问题使用warn,系统遇到异常使用error
3、异常日志:需要在diff过程中明确catch住可能存在异常的部分;如果是正常业务的异常分支,那么就需要有明确的业务输出或记录,而不是抛出exception
4、日志不可以使用System.out输出
5、日志需要符合易读的原则,要输出定位问题需要的关键信息

6.3、代码功能类
1、for,while循环,要检查好退出条件,避免死循环,检查好循环内是否有调用方法,评估调用外部系统的性能。
2、循环结构、递归调用,要检查退出条件,避免死循环
3、条件判断语句参数是否发生空指针异常
4、分支if else多了后考虑是否需要使用switch替代
5、在一个 switch 块内,每个 case 要么通过 break来终止,要么注释说明程序将继续执行到哪一个 case 为止;在一个 switch 块内,都必须包含一个 default 语句并且 放在最后,即使空代码
6、在 if/else/for/while/do 语句中必须使用大括号。即使只有一行代码,避免采用单行的编码方式:if (condition) statements;
7、表达异常的分支避免使用if else;通过使用if(异常){return} 后再处理业务逻辑
8、运算表达式,需注意精度计算问题
9、工具类添加对应的单测
10、枚举新增字段,是否所有的调用方都考虑到了,是否可以回滚
11、避免修改:改动需要调用方配合修改一起上线,否则会造成影响

6.4、连接池、线程池、锁资源释放
1、线程池、锁资源未释放

6.5、代码异常处理类
1、catch住不稳定代码,并尽可能区分异常,再做对应的异常处理
2、捕获异常需要处理,或者抛给调用者最外层,必须处理并且需要转换为用户可以接受的
3、不要catch住异常,然后do nothing,除非有十足合理的理由
4、try块中放了事务代码,catch异常后,需要手动回滚事务
5、检查是否可能存在NPE(空指针)
6、数据库查询结果有可能为null
7、集合里的元素isNotEmpty,取出的数据元素有可能为null
8、远程调用返回对象,需要NPE(空指针异常)检查
9、级联调用,一连串的调用容易产生NPE(空指针异常),.getA().getB().getC()

6.6、数据库、sql相关(mapper文件)
1、建库/表语句
2、是否符合公司规范。
3、注释、字段类型、字段默认值是否合理
4、索引设计是否合理
5、查询语句是否走了索引

6、修改表结构:新旧数据兼容,测试范围补充
7、修改表数据:是否存在语法错误 是否符合业务逻辑 执行过程中是否会锁表
8、sql改了,有哪些功能受影响?修改sql需要克隆线上非敏感数据进行验证。
9、sql代码方面
10、禁止使用 select *
11、代码的字段类型和最大长度,与数据库一致
12、新增查询语句,是否需要添加索引
13、是否需要分表
14、新增字段是否在某些语句中漏写
15、注意查询条件的边界,比如日期是否包含两端的时间点

6.7、消息队列相关
○生产者:
1、发送的mq消息内容是否正确
2、发送失败是否重试
3、数据的新增、修改、删除时的发送是否正常

○消费者:
1、mq消费者的配置,新的group消费策略
2、失败和处理异常时的处理逻辑,重试、是否返回失败、忽略、回滚
3、重复消费的幂等
4、消息无序
5、消息的延迟性
6、消息的积压
7、对消息的顺序是否有依赖
8、是否需要事务

七、codediff结果必须归档,入库,并及时总结分享

八、常见问题代码
1、判空、执行逻辑问题

2、类和函数没有注释
推荐做法:

使用Java标准注释:
/**
* 函数功能说明
* @author wuyanfei
* @date 18-01-02 10:38:07
*/

3、对函数或Rpc调用的返回结果,缺少失败判断,且缺少log::warn日志
例如:对getOrderBestVoucherByOrderId() 调用的返回,没做失败判断,结果可能返回null,导致写入null到cache。

OrderBestVoucher orderBestVoucher = couponDao.getOrderBestVoucherByOrderId(param);
couponCache.setOrderBestVoucherCache(orderBestVoucher, userId, orderId);

推荐做法:

OrderBestVoucher orderBestVoucher = couponDao.getOrderBestVoucherByOrderId(param);
if (orderBestVoucher != null) {
    couponCache.setOrderBestVoucherCache(orderBestVoucher, userId, orderId);
} else {
    Log::warn("call getOrderBestVoucherByOrderId fail");
}

4、硬编码
错误做法:把数字、字符串等常量硬编码在代码里面。

if("1".equals(orderBestVoucherStr)) {
   ...
}

推荐做法:
把常量提取到某个类静态常量,并起一个可读性强的名字。
public static final String SUCCESS = 1;

...
if(SUCCESS.equals(orderBestVoucherStr)) {
   ...
}

5、函数参数太多,需要收敛到domain类
错误做法:

public JSONObject getProductPriceForInvite(String sku, int price, int vipPrice, String platform,boolean isGroupPurchase, int color, long userId){
    ......
}

推荐做法:
(1)定义一个domain类:

import lombok.Data;
import java.io.Serializable;
@Data
public class ProductPriceRequest {
    private String sku;
    private int price;
    private int vipPrice;
    private String platform;
    private boolean isGroupPurchase;
    private int color;
    private long userId;   
}

(2)仅使用一个 ProductPriceRequest 参数

public JSONObject getProductPriceForInvite(ProductPriceRequest request) {
    ......
}

6、服务调用的返回值在使用前,中间有不相关的return逻辑。
错误做法:

String shipping_area_skus = productCenterService.getProductSkusByShippingAreaId();

List<Map<Object, Object>> productDetailList = productCenterService.getProductDetailListFromRedisForPromotion(skuList);
if(CollectionUtils.isEmptyCollection(productDetailList)){
    return productInfoList; /如果提前返回,导致 getProductSkusByShippingAreaId 调用是浪费的。
}

if (StringUtil.isNotBlank(shipping_area_skus)) {
    ......
}

正确做法:

List<Map<Object, Object>> productDetailList = productCenterService.getProductDetailListFromRedisForPromotion(skuList);
if(CollectionUtils.isEmptyCollection(productDetailList)){
    return productInfoList;
}

String shipping_area_skus = productCenterService.getProductSkusByShippingAreaId();
if (StringUtil.isNotBlank(shipping_area_skus)) {
    ......
}

指导思想:变量或逻辑调用,在真正使用前再发起定义或发起调用。

7、不要手工写实体类的 setter 和 getter 方法
通过lombok的 @Data 注解,可以省去setter和getter方法,代码会简洁很多。用法如下:
(1)引入jar包:

<properties>
    <lombok.version>1.16.10</lombok.version>
</properties>
 
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>${lombok.version}</version>
</dependency>

(2)在类前面加入@Data注解,这样就可以正常的调用相应属性的get和set方法了(lombok已经帮我们自动注入了set和get方法)

@Data
public class ManyParamRequest implements Serializable{
    private static final long serialVersionUID = -1662585918043190201L;
    private Short aShort;
    private Integer aInteger;
    private Float aFloat;
    private Double aDouble;
}

(3) 在idea里面添加 lombok 插件,使idea支持lombok的getter和setter方法调用。

8、sql没有做判空拦截,导致某些情况下入参为空,
导致查询全量数据到jvm内存,导致oom,真实P0故障案例:从美团过来的数据在某些情况下入参会传空,导致sql全量订单查询select * from pop_club_meituan_order


优化后的代码:

9、使用了java的显示锁,读锁没释放,导致内存泄漏

package com.culiuj.search.system.monitor.stat;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
/**
 * 统计对象基类
 */
public class AStatistics {
    /** 请求成功次数 */
    private int successCount = 0;
    /** 请求失败次数 */
    private int errorCount = 0;
    /** 错误代码集合 */
    private Set<Integer> errorCodeSet = new HashSet<>();
    /** 锁对象 */
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public int getTotal() {
        ReadLock lock = this.getReadLock();
        lock.lock();
        try {
            int total = successCount + errorCount;
            return total;
        } finally {
        }
    }

正确的代码:

10、接口间调用判断是否成功没有使用标准格式,使用了map为空来判断,而不是规范中的isSuccess()方式

6.2如何使用工具进行 Code Diff

6.2.1推荐一些简单易用的代码比较工具,并提供步骤演示如何使用

1

6.3识别代码变更的常见技巧和注意事项

6.3.1解释如何识别和理解不同类型的代码变更,并提供一些常见问题的解决方法1

6.4处理 Code Diff 中的常见问题和挑战

6.4.1提供一些实用的技巧和建议,帮助新手解决在 Code Diff 过程中遇到的各种问题和挑战1

本文标签: 中非提建议最终版SpringCode