• [技术干货] Springboot入门
    1. Spring Boot是什么 我们知道,从 2002 年开始,Spring 一直在飞速的发展,如今已经成为了在Java EE(Java Enterprise Edition)开发中真正意义上的标准,但是随着技术的发展,Java EE使用 Spring 逐渐变得笨重起来,大量的 XML 文件存在于项目之中。繁琐的配置,整合第三方框架的配置问题,导致了开发和部署效率的降低。  2012 年 10 月,Mike Youngstrom 在 Spring jira 中创建了一个功能请求,要求在 Spring 框架中支持无容器 Web 应用程序体系结构。他谈到了在主容器引导 spring 容器内配置 Web 容器服务。这是 jira 请求的摘录:  我认为 Spring 的 Web 应用体系结构可以大大简化,如果它提供了从上到下利用 Spring 组件和配置模型的工具和参考体系结构。在简单的 main()方法引导的 Spring 容器内嵌入和统一这些常用Web 容器服务的配置。  这一要求促使了 2013 年初开始的 Spring Boot 项目的研发,到今天,Spring Boot 的版本已经到了 2.0.3 RELEASE。Spring Boot 并不是用来替代 Spring 的解决方案,而是和 Spring 框架紧密结合用于提升 Spring 开发者体验的工具。  它集成了大量常用的第三方库配置,Spring Boot应用中这些第三方库几乎可以是零配置的开箱即用(out-of-the-box),大部分的 Spring Boot 应用都只需要非常少量的配置代码(基于 Java 的配置),开发者能够更加专注于业务逻辑。  2. 为什么学习Spring Boot 2.1 从Spring官方来看 我们打开 Spring 的官方网站,可以看到下图: 我们可以看到图中官方对 Spring Boot 的定位:Build Anything, Build任何东西。Spring Boot旨在尽可能快地启动和运行,并且只需最少的 Spring 前期配置。 同时我们也来看一下官方对后面两个的定位:  SpringCloud:Coordinate Anything,协调任何事情; SpringCloud Data Flow:Connect everything,连接任何东西。  仔细品味一下,Spring 官网对 Spring Boot、SpringCloud 和 SpringCloud Data Flow三者定位的措辞非常有味道,同时也可以看出,Spring 官方对这三个技术非常重视,是现在以及今后学习的重点(SpringCloud 相关达人课课程届时也会上线)。 2.2 从Spring Boot的优点来看 Spring Boot 有哪些优点?主要给我们解决了哪些问题呢?我们以下图来说明: 2.2.1 良好的基因  Spring Boot 是伴随着 Spring 4.0 诞生的,从字面理解,Boot是引导的意思,因此 Spring Boot 旨在帮助开发者快速搭建 Spring 框架。Spring Boot 继承了原有 Spring 框架的优秀基因,使 Spring 在使用中更加方便快捷。 2.2.2 简化编码  举个例子,比如我们要创建一个 web 项目,使用 Spring 的朋友都知道,在使用 Spring 的时候,需要在 pom 文件中添加多个依赖,而 Spring Boot 则会帮助开发着快速启动一个 web 容器,在 Spring Boot 中,我们只需要在 pom 文件中添加如下一个 starter-web 依赖即可。 我们点击进入该依赖后可以看到,Spring Boot 这个 starter-web 已经包含了多个依赖,包括之前在 Spring 工程中需要导入的依赖,我们看一下其中的一部分,如下: 由此可以看出,Spring Boot 大大简化了我们的编码,我们不用一个个导入依赖,直接一个依赖即可。  2.2.3 简化配置  Spring 虽然使Java EE轻量级框架,但由于其繁琐的配置,一度被人认为是“配置地狱”。各种XML、Annotation配置会让人眼花缭乱,而且配置多的话,如果出错了也很难找出原因。Spring Boot更多的是采用 Java Config 的方式,对 Spring 进行配置。举个例子:  我新建一个类,但是我不用 @Service注解,也就是说,它是个普通的类,那么我们如何使它也成为一个 Bean 让 Spring 去管理呢?只需要@Configuration 和@Bean两个注解即可,如下:  @Configuration表示该类是个配置类,@Bean表示该方法返回一个 Bean。这样就把TestService作为 Bean 让 Spring 去管理了,在其他地方,我们如果需要使用该 Bean,和原来一样,直接使用@Resource注解注入进来即可使用,非常方便。 @Resource private TestService testService; 另外,部署配置方面,原来 Spring 有多个 xml 和 properties配置,在 Spring Boot 中只需要个 application.yml即可。 2.2.4 简化部署 在使用 Spring 时,项目部署时需要我们在服务器上部署 tomcat,然后把项目打成 war 包扔到 tomcat里,在使用 Spring Boot 后,我们不需要在服务器上去部署 tomcat,因为 Spring Boot 内嵌了 tomcat,我们只需要将项目打成 jar 包,使用 java -jar xxx.jar一键式启动项目。 另外,也降低对运行环境的基本要求,环境变量中有JDK即可。 2.2.5 简化监控 我们可以引入 spring-boot-start-actuator 依赖,直接使用 REST 方式来获取进程的运行期性能参数,从而达到监控的目的,比较方便。但是 Spring Boot 只是个微框架,没有提供相应的服务发现与注册的配套功能,没有外围监控集成方案,没有外围安全管理方案,所以在微服务架构中,还需要 Spring Cloud 来配合一起使用。 2.3 从未来发展的趋势来看 微服务是未来发展的趋势,项目会从传统架构慢慢转向微服务架构,因为微服务可以使不同的团队专注于更小范围的工作职责、使用独立的技术、更安全更频繁地部署。而 继承了 Spring 的优良特性,与 Spring 一脉相承,而且 支持各种REST API 的实现方式。Spring Boot 也是官方大力推荐的技术,可以看出,Spring Boot 是未来发展的一个大趋势。  3. 本课程能学到什么 本课程使用目前 Spring Boot 最新版本2.0.3 RELEASE,课程文章均为作者在实际项目中剥离出来的场景和demo,目标是带领学习者快速上手 Spring Boot,将 Spring Boot 相关技术点快速运用在微服务项目中。全篇分为两部分:基础篇和进阶篇。 基础篇(01—10课)主要介绍 Spring Boot 在项目中最常使用的一些功能点,旨在带领学习者快速掌握 Spring Boot 在开发时需要的知识点,能够把 Spring Boot 相关技术运用到实际项目架构中去。该部分以 Spring Boot 框架为主线,内容包括Json数据封装、日志记录、属性配置、MVC支持、在线文档、模板引擎、异常处理、AOP 处理、持久层集成等等。 进阶篇(11—17课)主要是介绍 Spring Boot 在项目中拔高一些的技术点,包括集成的一些组件,旨在带领学习者在项目中遇到具体的场景时能够快速集成,完成对应的功能。该部分以 Spring Boot 框架为主线,内容包括拦截器、监听器、缓存、安全认证、分词插件、消息队列等等。  认真读完该系列文章之后,学习者会快速了解并掌握 Spring Boot 在项目中最常用的技术点,作者课程的最后,会基于课程内容搭建一个 Spring Boot 项目的空架构,该架构也是从实际项目中剥离出来,学习者可以运用该架构于实际项目中,具备使用 Spring Boot 进行实际项目开发的能力。  5. 本课程开发环境和插件 第01课:Spring Boot开发环境搭建和项目启动 上一节对 SpringBoot 的特性做了一个介绍,本节主要对 jdk 的配置、Spring Boot工程的构建和项目的启动、Spring Boot 项目工程的结构做一下讲解和分析。  1. jdk 的配置 本课程是使用 IDEA 进行开发,在IDEA 中配置 jdk 的方式很简单,打开File->Project Structure 选择 SDKs 在 JDK home path 中选择本地 jdk 的安装目录 在 Name 中为 jdk 自定义名字 通过以上三步骤,即可导入本地安装的 jdk。如果是使用 STS 或者 eclipse 的朋友,可以通过两步骤添加:  window->preference->java->Instralled JRES来添加本地 jdk。 window-->preference-->java-->Compiler选择 jre,和 jdk 保持一致。 2. Spring Boot 工程的构建 2.1 IDEA 快速构建 IDEA 中可以通过File->New->Project来快速构建 Spring Boot 工程。如下,选择 Spring Initializr,在 Project SDK 中选择刚刚我们导入的 jdk,点击 Next,到了项目的配置信息。  Group:填企业域名,本课程使用com.itcodai Artifact:填项目名称,本课程中每一课的工程名以course+课号命令,这里使用 course01 Dependencies:可以添加我们项目中所需要的依赖信息,根据实际情况来添加,本课程只需要选择 Web 即可。 2.2 官方构建 第二种方式可以通过官方构建,步骤如下: 访问 http://start.spring.io/。 在页面上输入相应的 Spring Boot 版本、Group 和 Artifact 信息以及项目依赖,然后创建项目。  解压后,使用 IDEA 导入该 maven 工程:File->New->Model from Existing Source,然后选择解压后的项目文件夹即可。如果是使用 eclipse 的朋友,可以通过Import->Existing Maven Projects->Next,然后选择解压后的项目文件夹即可。 2.3 maven配置 创建了 Spring Boot 项目之后,需要进行 maven 配置。打开File->settings,搜索 maven,配置一下本地的 maven 信息。如下: 在 Maven home directory 中选择本地 Maven 的安装路径;在 User settings file 中选择本地 Maven 的配置文件所在路径。在配置文件中,我们配置一下国内阿里的镜像,这样在下载 maven 依赖时,速度很快。 如果是使用 eclipse 的朋友,可以通过window-->preference-->Maven-->User Settings来配置,配置方式和上面一致。 2.4 编码配置 同样地,新建项目后,我们一般都需要配置编码,这点非常重要,很多初学者都会忘记这一步,所以要养成良好的习惯。 IDEA 中,仍然是打开File->settings,搜索 encoding,配置一下本地的编码信息。如下: 如果是使用 eclipse 的朋友,有两个地方需要设置一下编码: window–> perferences–>General–>Workspace,将Text file encoding改成utf-8 window–>perferences–>General–>content types,选中Text,将Default encoding填入utf-8 OK,编码设置完成即可启动项目工程了。  3. Spring Boot 项目工程结构 Spring Boot 项目总共有三个模块src/main/java路径:主要编写业务程序 src/main/resources路径:存放静态文件和配置文件 src/test/java路径:主要编写测试程序 默认情况下,如上图所示会创建一个启动类 Course01Application,该类上面有个@SpringBootApplication注解,该启动类中有个 main 方法,没错,Spring Boot 启动只要运行该 main 方法即可,非常方便。另外,Spring Boot 内部集成了 tomcat,不需要我们人为手动去配置 tomcat,开发者只需要关注具体的业务逻辑即可。  到此为止,Spring Boot 就启动成功了,为了比较清楚的看到效果,我们写一个 Controller 来测试一下,如下: 4. 总结 本节我们快速学习了如何在 IDEA 中导入 jdk,以及使用 IDEA 如何配置 maven 和编码,如何快速的创建和启动 Spring Boot 工程。IDEA 对 Spring Boot 的支持非常友好,建议大家使用 IDEA 进行 Spring Boot 的开发,从下一课开始,我们真正进入 Spring Boot 的学习中。  学习源码链接点击这里    第02课:Spring Boot返回Json数据及数据封装 在项目开发中,接口与接口之间,前后端之间数据的传输都使用 Json 格式,在 Spring Boot 中,接口返回 Json 格式的数据很简单,在 Controller 中使用@RestController注解即可返回 Json 格式的数据,@RestController也是 Spring Boot 新增的一个注解,我们点进去看一下该注解都包含了哪些东西。   可以看出, @RestController 注解包含了原来的 @Controller 和 @ResponseBody 注解,使用过 Spring 的朋友对 @Controller 注解已经非常了解了,这里不再赘述, @ResponseBody 注解是将返回的数据结构转换为 Json 格式。所以在默认情况下,使用了 @RestController 注解即可将返回的数据结构转换成 Json 格式,Spring Boot 中默认使用的 Json 解析技术框架是 jackson。我们点开 pom.xml 中的 spring-boot-starter-web 依赖,可以看到一个 spring-boot-starter-json 依赖:   Spring Boot 中对依赖都做了很好的封装,可以看到很多 spring-boot-starter-xxx 系列的依赖,这是 Spring Boot 的特点之一,不需要人为去引入很多相关的依赖了,starter-xxx 系列直接都包含了所必要的依赖,所以我们再次点进去上面这个 spring-boot-starter-json 依赖,可以看到: 到此为止,我们知道了 Spring Boot 中默认使用的 json 解析框架是 jackson。下面我们看一下默认的 jackson 框架对常用数据类型的转 Json 处理。  1. Spring Boot 默认对Json的处理 在实际项目中,常用的数据结构无非有类对象、List对象、Map对象,我们看一下默认的 jackson 框架对这三个常用的数据结构转成 json 后的格式如何。  1.1 创建 User 实体类 为了测试,我们需要创建一个实体类,这里我们就用 User 来演示。 1.2 创建Controller类 然后我们创建一个 Controller,分别返回 User对象、List 和 Map。   1.3 测试不同数据类型返回的json OK,写好了接口,分别返回了一个 User 对象、一个 List 集合和一个 Map 集合,其中 Map 集合中的 value 存的是不同的数据类型。接下来我们依次来测试一下效果。  在浏览器中输入:localhost:8080/json/user 返回 json 如下:  {"id":1,"username":"测试","password":"123456"} 在浏览器中输入:localhost:8080/json/list 返回 json 如下:  [{"id":1,"username":"测试","password":"123456"},{"id":2,"username":"测试课","password":"123456"}] 在浏览器中输入:localhost:8080/json/map 返回 json 如下: {"作者信息":{"id":1,"username":"测试","password":"123456"},"CSDN地址":"http://blog.csdn.net/eson_15","测试数量":001,"博客地址":"http://blog.itcodai.com"} 可以看出,map 中不管是什么数据类型,都可以转成相应的 json 格式,这样就非常方便。  1.4 jackson 中对null的处理 在实际项目中,我们难免会遇到一些 null 值出现,我们转 json 时,是不希望有这些 null 出现的,比如我们期望所有的 null 在转 json 时都变成 “” 这种空字符串,那怎么做呢?在 Spring Boot 中,我们做一下配置即可,新建一个 jackson 的配置类: 然后我们修改一下上面返回 map 的接口,将几个值改成 null 测试一下: 重启项目,再次输入:localhost:8080/json/map,可以看到 jackson 已经将所有 null 字段转成了空字符串了。  {"作者信息":{"id":1,"username":"测试","password":""},"CSDN地址":"","测试数量":001,"博客地址":"http://blog.itcodai.com"} 2. 使用阿里巴巴FastJson的设置 2.1 jackson 和 fastJson 的对比 有很多朋友习惯于使用阿里巴巴的 fastJson 来做项目中 json 转换的相关工作,目前我们项目中使用的就是阿里的 fastJson,那么 jackson 和 fastJson 有哪些区别呢?根据网上公开的资料比较得到下表。  选项    fastJson    jackson 上手难易程度    容易    中等 高级特性支持    中等    丰富 官方文档、Example支持    中文    英文 处理json速度    略快    快 关于 fastJson 和 jackson 的对比,网上有很多资料可以查看,主要是根据自己实际项目情况来选择合适的框架。从扩展上来看,fastJson 没有 jackson 灵活,从速度或者上手难度来看,fastJson 可以考虑,我们项目中目前使用的是阿里的 fastJson,挺方便的。  2.2 fastJson依赖导入 使用 fastJson 需要导入依赖,本课程使用 1.2.35 版本,依赖如下:   2.2 使用 fastJson 处理 null 使用 fastJson 时,对 null 的处理和 jackson 有些不同,需要继承 WebMvcConfigurationSupport 类,然后覆盖 configureMessageConverters 方法,在方法中,我们可以选择对要实现 null 转换的场景,配置好即可。如下:   3. 封装统一返回的数据结构 以上是 Spring Boot 返回 json 的几个代表的例子,但是在实际项目中,除了要封装数据之外,我们往往需要在返回的 json 中添加一些其他信息,比如返回一些状态码 code ,返回一些 msg 给调用者,这样调用者可以根据 code 或者 msg 做一些逻辑判断。所以在实际项目中,我们需要封装一个统一的 json 返回结构存储返回信息。 3.1 定义统一的 json 结构 由于封装的 json 数据的类型不确定,所以在定义统一的 json 结构时,我们需要用到泛型。统一的 json 结构中属性包括数据、状态码、提示信息即可,构造方法可以根据实际业务需求做相应的添加即可,一般来说,应该有默认的返回结构,也应该有用户指定的返回结构。如下:   3.2 修改 Controller 中的返回值类型及测试 由于 JsonResult 使用了泛型,所以所有的返回值类型都可以使用该统一结构,在具体的场景将泛型替换成具体的数据类型即可,非常方便,也便于维护。在实际项目中,还可以继续封装,比如状态码和提示信息可以定义一个枚举类型,以后我们只需要维护这个枚举类型中的数据即可(在本课程中就不展开了)。根据以上的 JsonResult,我们改写一下 Controller,如下:   我们重新在浏览器中输入:localhost:8080/jsonresult/user 返回 json 如下: {"code":"0","data":{"id":1,"password":"123456","username":"测试"},"msg":"操作成功!"} 输入:localhost:8080/jsonresult/list,返回 json 如下:  {"code":"0","data":[{"id":1,"password":"123456","username":"测试"},{"id":2,"password":"123456","username":"达人课"}],"msg":"获取用户列表成功"} 输入:localhost:8080/jsonresult/map,返回 json 如下: {"code":"0","data":{"作者信息":{"id":1,"password":"","username":"测试"},"CSDN地址":null,"粉丝数量":4153,"博客地址":"http://blog.itcodai.com"},"msg":"操作成功!"} 通过封装,我们不但将数据通过 json 传给前端或者其他接口,还带上了状态码和提示信息,这在实际项目场景中应用非常广泛。  4. 总结 本节主要对 Spring Boot 中 json 数据的返回做了详细的分析,从 Spring Boot 默认的 jackson 框架到阿里巴巴的 fastJson 框架,分别对它们的配置做了相应的讲解。另外,结合实际项目情况,总结了实际项目中使用的 json 封装结构体,加入了状态码和提示信息,使得返回的 json 数据信息更加完整。   第03课:Spring Boot使用slf4j进行日志记录 在开发中,我们经常使用 System.out.println() 来打印一些信息,但是这样不好,因为大量的使用 System.out 会增加资源的消耗。我们实际项目中使用的是 slf4j 的 logback 来输出日志,效率挺高的,Spring Boot 提供了一套日志系统,logback 是最优的选择。  1. slf4j 介绍 引用百度百科里的一段话: SLF4J,即简单日志门面(Simple Logging Facade for Java),不是具体的日志解决方案,它只服务于各种各样的日志系统。按照官方的说法,SLF4J是一个用于日志系统的简单Facade,允许最终用户在部署其应用时使用其所希望的日志系统。 这段的大概意思是:你只需要按统一的方式写记录日志的代码,而无需关心日志是通过哪个日志系统,以什么风格输出的。因为它们取决于部署项目时绑定的日志系统。例如,在项目中使用了 slf4j 记录日志,并且绑定了 log4j(即导入相应的依赖),则日志会以 log4j 的风格输出;后期需要改为以 logback 的风格输出日志,只需要将 log4j 替换成 logback 即可,不用修改项目中的代码。这对于第三方组件的引入的不同日志系统来说几乎零学习成本,况且它的优点不仅仅这一个而已,还有简洁的占位符的使用和日志级别的判断。  正因为 sfl4j 有如此多的优点,阿里巴巴已经将 slf4j 作为他们的日志框架了。在《阿里巴巴Java开发手册(正式版)》中,日志规约一项第一条就强制要求使用 slf4j:  1.【强制】应用中不可直接使用日志系统(Log4j、Logback)中的API,而应依赖使用日志框架SLF4J中的API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。  “强制”两个字体现出了 slf4j 的优势,所以建议在实际项目中,使用 slf4j 作为自己的日志框架。使用 slf4j 记录日志非常简单,直接使用 LoggerFactory 创建即可。   2. application.yml 中对日志的配置 Spring Boot 对 slf4j 支持的很好,内部已经集成了 slf4j,一般我们在使用的时候,会对slf4j 做一下配置。application.yml 文件是 Spring Boot 中唯一一个需要配置的文件,一开始创建工程的时候是 application.properties 文件,个人比较细化用 yml 文件,因为 yml 文件的层次感特别好,看起来更直观,但是 yml 文件对格式要求比较高,比如英文冒号后面必须要有个空格,否则项目估计无法启动,而且也不报错。用 properties 还是 yml 视个人习惯而定,都可以。本课程使用 yml。 我们看一下 application.yml 文件中对日志的配置:  logging:   config: logbck.xml   level:     com.itcodai.course03.dao: trace logging.config 是用来指定项目启动的时候,读取哪个配置文件,这里指定的是日志配置文件是根路径下的 logback.xml 文件,关于日志的相关配置信息,都放在 logback.xml 文件中了。logging.level 是用来指定具体的 mapper 中日志的输出级别,上面的配置表示 com.itcodai.course03.dao 包下的所有 mapper 日志输出级别为 trace,会将操作数据库的 sql 打印出来,开发时设置成 trace 方便定位问题,在生产环境上,将这个日志级别再设置成 error 级别即可(本节课不讨论 mapper 层,在后面 Spring Boot 集成 MyBatis 时再详细讨论)。 常用的日志级别按照从高到低依次为:ERROR、WARN、INFO、DEBUG。  3. logback.xml 配置文件解析 在上面 application.yml 文件中,我们指定了日志配置文件 logback.xml,logback.xml 文件中主要用来做日志的相关配置。在 logback.xml 中,我们可以定义日志输出的格式、路径、控制台输出格式、文件大小、保存时长等等。下面来分析一下:  3.1 定义日志输出格式和存储路径 <configuration>     <property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" />     <property name="FILE_PATH" value="D:/logs/course03/demo.%d{yyyy-MM-dd}.%i.log" /> </configuration> 我们来看一下这个定义的含义:首先定义一个格式,命名为 “LOG_PATTERN”,该格式中 %date 表示日期,%thread 表示线程名,%-5level 表示级别从左显示5个字符宽度,%logger{36} 表示 logger 名字最长36个字符,%msg 表示日志消息,%n 是换行符。  然后再定义一下名为 “FILE_PATH” 文件路径,日志都会存储在该路径下。%i 表示第 i 个文件,当日志文件达到指定大小时,会将日志生成到新的文件里,这里的 i 就是文件索引,日志文件允许的大小可以设置,下面会讲解。这里需要注意的是,不管是 windows 系统还是 Linux 系统,日志存储的路径必须要是绝对路径。  3.2 定义控制台输出 <configuration>     <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">         <encoder>             <!-- 按照上面配置的LOG_PATTERN来打印日志 -->             <pattern>${LOG_PATTERN}</pattern>         </encoder>     </appender> </configuration> 使用  节点设置个控制台输出(class="ch.qos.logback.core.ConsoleAppender")的配置,定义为 “CONSOLE”。使用上面定义好的输出格式(LOG_PATTERN)来输出,使用 ${} 引用进来即可。  3.3 定义日志文件的相关参数 使用  定义一个名为 “FILE” 的文件配置,主要是配置日志文件保存的时间、单个日志文件存储的大小、以及文件保存的路径和日志的输出格式。  3.4 定义日志输出级别 有了上面那些定义后,最后我们使用  来定义一下项目中默认的日志输出级别,这里定义级别为 INFO,然后针对 INFO 级别的日志,使用  引用上面定义好的控制台日志输出和日志文件的参数。这样 logback.xml 文件中的配置就设置完了。  4. 使用Logger在项目中打印日志 在代码中,我们一般使用 Logger 对象来打印出一些 log 信息,可以指定打印出的日志级别,也支持占位符,很方便。  启动该项目,在浏览器中输入 localhost:8080/test/log 后可以看到控制台的日志记录:  ======测试日志info级别打印===== =====测试日志error级别打印==== ======测试日志warn级别打印===== ======测试:blog.csdn.net/eson_15 因为 INFO 级别比 DEBUG 级别高,所以 debug 这条没有打印出来,如果将 logback.xml 中的日志级别设置成 DEBUG,那么四条语句都会打印出来,这个大家自己去测试了。同时可以打开 D:\logs\course03\ 目录,里面有刚刚项目启动,以后后面生成的所有日志记录。在项目部署后,我们大部分都是通过查看日志文件来定位问题。  5. 总结 本节课主要对 slf4j 做了一个简单的介绍,并且对 Spring Boot 中如何使用 slf4j 输出日志做了详细的说明,着重分析了 logback.xml 文件中对日志相关信息的配置,包括日志的不同级别。最后针对这些配置,在代码中使用 Logger 打印出一些进行测试。在实际项目中,这些日志都是排查问题的过程中非常重要的资料。  第04课:Spring Boot中的项目属性配置 我们知道,在项目中,很多时候需要用到一些配置的信息,这些信息可能在测试环境和生产环境下会有不同的配置,后面根据实际业务情况有可能还会做修改,针对这种情况,我们不能将这些配置在代码中写死,最好就是写到配置文件中。比如可以把这些信息写到 application.yml 文件中。 1. 少量配置信息的情形 举个例子,在微服务架构中,最常见的就是某个服务需要调用其他服务来获取其提供的相关信息,那么在该服务的配置文件中需要配置被调用的服务地址,比如在当前服务里,我们需要调用订单微服务获取订单相关的信息,假设 订单服务的端口号是 8002,那我们可以做如下配置:  server:   port: 8001   # 配置微服务的地址 url:   # 订单微服务的地址   orderUrl: http://localhost:8002 然后在业务代码中如何获取到这个配置的订单服务地址呢?我们可以使用 @Value 注解来解决。在对应的类中加上一个属性,在属性上使用 @Value 注解即可获取到配置文件中的配置信息,如下:  @Value 注解上通过 ${key} 即可获取配置文件中和 key 对应的 value 值。我们启动一下项目,在浏览器中输入 localhost:8080/test/config 请求服务后,可以看到控制台会打印出订单服务的地址:  =====获取的订单服务地址为:http://localhost:8002 说明我们成功获取到了配置文件中的订单微服务地址,在实际项目中也是这么用的,后面如果因为服务器部署的原因,需要修改某个服务的地址,那么只要在配置文件中修改即可。  2. 多个配置信息的情形 这里再引申一个问题,随着业务复杂度的增加,一个项目中可能会有越来越多的微服务,某个模块可能需要调用多个微服务获取不同的信息,那么就需要在配置文件中配置多个微服务的地址。可是,在需要调用这些微服务的代码中,如果这样一个个去使用 @Value 注解引入相应的微服务地址的话,太过于繁琐,也不科学。  所以,在实际项目中,业务繁琐,逻辑复杂的情况下,需要考虑封装一个或多个配置类。举个例子:假如在当前服务中,某个业务需要同时调用订单微服务、用户微服务和购物车微服务,分别获取订单、用户和购物车相关信息,然后对这些信息做一定的逻辑处理。那么在配置文件中,我们需要将这些微服务的地址都配置好:  # 配置多个微服务的地址 url:   # 订单微服务的地址   orderUrl: http://localhost:8002   # 用户微服务的地址   userUrl: http://localhost:8003   # 购物车微服务的地址   shoppingUrl: http://localhost:8004 也许实际业务中,远远不止这三个微服务,甚至十几个都有可能。对于这种情况,我们可以先定义一个 MicroServiceUrl 类来专门保存微服务的 url,如下:   细心的朋友应该可以看到,使用 @ConfigurationProperties 注解并且使用 prefix 来指定一个前缀,然后该类中的属性名就是配置中去掉前缀后的名字,一一对应即可。即:前缀名 + 属性名就是配置文件中定义的 key。同时,该类上面需要加上 @Component 注解,把该类作为组件放到Spring容器中,让 Spring 去管理,我们使用的时候直接注入即可。 需要注意的是,使用 @ConfigurationProperties 注解需要导入它的依赖:  OK,到此为止,我们将配置写好了,接下来写个 Controller 来测试一下。此时,不需要在代码中一个个引入这些微服务的 url 了,直接通过 @Resource 注解将刚刚写好配置类注入进来即可使用了,非常方便。如下:  再次启动项目,请求一下可以看到,控制台打印出如下信息,说明配置文件生效,同时正确获取配置文件内容: =====获取的订单服务地址为:http://localhost:8002 =====获取的订单服务地址为:http://localhost:8002 =====获取的用户服务地址为:http://localhost:8003 =====获取的购物车服务地址为:http://localhost:8004 3. 指定项目配置文件 我们知道,在实际项目中,一般有两个环境:开发环境和生产环境。开发环境中的配置和生产环境中的配置往往不同,比如:环境、端口、数据库、相关地址等等。我们不可能在开发环境调试好之后,部署到生产环境后,又要将配置信息全部修改成生产环境上的配置,这样太麻烦,也不科学。 最好的解决方法就是开发环境和生产环境都有一套对用的配置信息,然后当我们在开发时,指定读取开发环境的配置,当我们将项目部署到服务器上之后,再指定去读取生产环境的配置。 我们新建两个配置文件: application-dev.yml 和 application-pro.yml,分别用来对开发环境和生产环境进行相关配置。这里为了方便,我们分别设置两个访问端口号,开发环境用 8001,生产环境用 8002.  # 开发环境配置文件 server:   port: 8001 # 开发环境配置文件 server:   port: 8002 然后在 application.yml 文件中指定读取哪个配置文件即可。比如我们在开发环境下,指定读取 applicationn-dev.yml 文件,如下: spring:   profiles:     active:     - dev 这样就可以在开发的时候,指定读取 application-dev.yml 文件,访问的时候使用 8001 端口,部署到服务器后,只需要将 application.yml 中指定的文件改成 application-pro.yml 即可,然后使用 8002 端口访问,非常方便。  4. 总结 本节课主要讲解了 Spring Boot 中如何在业务代码中读取相关配置,包括单一配置和多个配置项,在微服务中,这种情况非常常见,往往会有很多其他微服务需要调用,所以封装一个配置类来接收这些配置是个很好的处理方式。除此之外,例如数据库相关的连接参数等等,也可以放到一个配置类中,其他遇到类似的场景,都可以这么处理。最后介绍了开发环境和生产环境配置的快速切换方式,省去了项目部署时,诸多配置信息的修改。 ———————————————— 原文链接:https://blog.csdn.net/cuiqwei/article/details/118188540 
  • [技术干货] SpringBoot常用注解
    1 @SpringBootApplication @SpringBootApplication是 Spring Boot 项目的基石,创建 SpringBoot 项目之后会默认在主类加上;可以把 @SpringBootApplication看作是@EnableAutoConfiguration、@ComponentScan、@Configuration注解的集合; @EnableAutoConfiguration:启用 SpringBoot 的自动配置机制; @ComponentScan: 扫描被@Component (@Service,@Controller)注解的 bean,注解默认会扫描该类所在的包下所有的类; @Configuration:允许在 Spring 上下文中注册额外的 bean 或导入其他配置类; 2 Spring Bean相关 2.1 @Autowired 自动导入对象到类中,被注入进的类同样要被 Spring 容器管理。比如:Service 类注入到 Controller 类中。  2.2 @Component/@Controller/@Service/@Repository 一般使用 @Autowired 让 Spring 容器帮我们自动装配 bean。要想把类标识成可用于 @Autowired 注解自动装配的 bean 的类,可以采用以下注解实现:  @Component :通用注解,可标注任意类为 Spring 组件。如果一个 Bean 不知道属于哪个层,可以使用@Component注解标注; @Controller : 对应 Spring MVC 控制层,主要用于接受用户请求并调用 Service 层返回数据给前端页面; @Service : 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层; @Repository : 对应持久层即 Dao 层,主要用于数据库相关操作; 2.3 @RestController @RestController是@Controller和@ResponseBody的合集,表示这是个控制器 bean,并且是将函数的返回值直接填入 HTTP 响应体中,是 REST 风格的控制器; 单独使用@Controller不加@ResponseBody一般使用在要返回一个视图的情况,这种情况属于比较传统的Spring MVC的应用,对应于前后端不分离的情况; @Controller+@ResponseBody 返回JSON或XML形式数据; 2.4 @Scope 声明 Spring Bean 的作用域。  @Bean @Scope("singleton") public Person personSingleton() {     return new Person(); } 四种常见的 Spring Bean 的作用域: singleton : 唯一 bean 实例,Spring 中的 bean 默认都是单例的; prototype : 每次请求都会创建一个新的 bean 实例; request : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP request 内有效; session : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP session 内有效。 2.5 @Configuration 一般用来声明配置类,可以使用@Component注解替代,不过使用@Configuration注解声明配置类更加语义化。  @Configuration public class AppConfig {     @Bean     public TransferService transferService() {         return new TransferServiceImpl();     } } 3 处理常见的 HTTP 请求类型 GET :请求从服务器获取特定资源。举个例子:GET /users(获取所有学生) POST :在服务器上创建一个新的资源。举个例子:POST /users(创建学生) PUT :更新服务器上的资源(客户端提供更新后的整个资源)。举个例子:PUT /users/12(更新编号为 12 的学生) DELETE :从服务器删除特定的资源。举个例子:DELETE /users/12(删除编号为 12 的学生) 3.1 @GetMapping @GetMapping(“users”) 等价于@RequestMapping(value=“/users”,method=RequestMethod.GET) @GetMapping("/users") public ResponseEntity<List<User>> getAllUsers() {  return userRepository.findAll(); } 3.2 @PostMapping @PostMapping(“users”) 等价于@RequestMapping(value=“/users”,method=RequestMethod.POST) @PostMapping("/users") public ResponseEntity<User> createUser(@Valid @RequestBody UserCreateRequest userCreateRequest) {  return userRespository.save(user); } 3.3 @PutMapping @PutMapping(“/users/{userId}”) 等价于@RequestMapping(value=“/users/{userId}”,method=RequestMethod.PUT)  @PutMapping("/users/{userId}") public ResponseEntity<User> updateUser(@PathVariable(value = "userId") Long userId,   @Valid @RequestBody UserUpdateRequest userUpdateRequest) {   ...... } 3.3 @DeleteMapping @DeleteMapping(“/users/{userId}”)等价于@RequestMapping(value=“/users/{userId}”,method=RequestMethod.DELETE)  @DeleteMapping("/users/{userId}") public ResponseEntity deleteUser(@PathVariable(value = "userId") Long userId){   ...... } 4 前后端传值 4.1 @PathVariable和@RequestParam @PathVariable用于获取路径参数,@RequestParam用于获取查询参数。 例:  @GetMapping("/klasses/{klassId}/teachers") public List<Teacher> getKlassRelatedTeachers(          @PathVariable("klassId") Long klassId,          @RequestParam(value = "type", required = false) String type ) { ... } 如果我们请求的 url 是:/klasses/{123456}/teachers?type=web,那么我们服务获取到的数据就是:klassId=123456,type=web。 4.2 @RequestBody 用于读取 Request 请求(可能是 POST,PUT,DELETE,GET 请求)的 body 部分并且Content-Type 为 application/json 格式的数据,接收到数据之后会自动将数据绑定到 Java 对象上去。系统会使用HttpMessageConverter或者自定义的HttpMessageConverter将请求的 body 中的 json 字符串转换为 java 对象。 例: @PostMapping("/sign-up") public ResponseEntity signUp(@RequestBody @Valid UserRegisterRequest userRegisterRequest) {   userService.save(userRegisterRequest);   return ResponseEntity.ok().build(); } UserRegisterRequest对象:  @Data @AllArgsConstructor @NoArgsConstructor public class UserRegisterRequest {     @NotBlank     private String userName;     @NotBlank     private String password;     @NotBlank     private String fullName; } 我们发送 post 请求到这个接口,并且 body 携带 JSON 数据: {"userName":"coder","fullName":"shuangkou","password":"123456"} 这样后端就可以直接把 json 格式的数据映射到UserRegisterRequest类上。 一个请求方法只可以有一个@RequestBody,但是可以有多个@RequestParam和@PathVariable。 5 读取配置信息 很多时候我们需要将一些常用的配置信息比如阿里云 oss、发送短信、微信认证的相关配置信息等等放到配置文件中。 Spring 为我们提供了以下几种方式帮助我们从配置文件中读取这些配置信息。 数据源application.yml: wuhan:     2020: 2020年初武汉爆发了新型冠状病毒,疫情严重,但是,我相信一切都会过去!武汉加油!中国加油! my-profile:   name: Guide哥   email: koushuangbwcx@163.com library:   location: 湖北武汉加油中国加油   books:     - name: 天才基本法       description: 二十二岁的林朝夕在父亲确诊阿尔茨海默病这天,得知自己暗恋多年的校园男神裴之即将出国深造的消息——对方考取的学校,恰是父亲当年为她放弃的那所。     - name: 时间的秩序       description: 为什么我们记得过去,而非未来?时间“流逝”意味着什么?是我们存在于时间之内,还是时间存在于我们之中?卡洛·罗韦利用诗意的文字,邀请我们思考这一亘古难题——时间的本质。     - name: 了不起的我       description: 如何养成一个新习惯?如何让心智变得更成熟?如何拥有高质量的关系? 如何走出人生的艰难时刻? 5.1 @value(常用) 使用 @Value(“${property}”) 读取比较简单的配置信息: @Value("${wuhan.2020}") String wuhan2020; 5.2 @ConfigurationProperties(常用) 通过@ConfigurationProperties读取配置信息并与 bean 绑定。 @Data @Component @ConfigurationProperties(prefix = "library") public class LibraryProperties {     @NotEmpty     private String location;     private List<Book> books;     @Setter     @Getter     @ToString     public static class Book {         String name;         String description;     } } 可以像使用普通的 Spring bean 一样,将其注入到类中使用。 5.3 @PropertySource(不常用) @PropertySource读取指定 properties 文件 @Data @Component @PropertySource("classpath:website.properties") class WebSite {     @Value("${url}")     private String url; } 6 参数校验 数据的校验非常重要,即使在前端对数据进行校验的情况下,我们还是要对传入后端的数据再进行一遍校验,避免用户绕过浏览器直接通过一些 HTTP 工具直接向后端请求一些违法数据; JSR(Java Specification Requests) 是一套 JavaBean 参数校验的标准,它定义了很多常用的校验注解,我们可以直接将这些注解加在我们 JavaBean 的属性上面,这样就可以在需要校验的时候进行校验了; 校验的时候我们实际用的是 Hibernate Validator 框架。Hibernate Validator 是 Hibernate 团队最初的数据校验框架,Hibernate Validator 4.x 是 Bean Validation 1.0(JSR 303)的参考实现,Hibernate Validator 5.x 是 Bean Validation 1.1(JSR 349)的参考实现,目前最新版的 Hibernate Validator 6.x 是 Bean Validation 2.0(JSR 380)的参考实现; SpringBoot 项目的 spring-boot-starter-web 依赖中已经有 hibernate-validator 包,不需要引用相关依赖。如下图所示(通过 idea 插件—Maven Helper 生成): 非 SpringBoot 项目需要自行引入相关依赖包; 所有的注解,推荐使用 JSR 注解,即javax.validation.constraints,而不是org.hibernate.validator.constraints; 6.1 一些常用的字段验证的注解 @NotEmpty(message=):被注释的字符串的不能为 null 也不能为空,即长度必须大于0; @NotBlank(message=):被注释的字符串非 null,并且必须包含一个非空白字符,即调用trim()后,长度必须大于0; @Null(message=):被注释的元素必须为 null; @NotNull(message=):被注释的元素必须不为 null; @AssertTrue:被注释的元素必须为 true; @AssertFalse:被注释的元素必须为 false; @Pattern(regexp=,message=):被注释的元素必须符合指定的正则表达式; @Email:被注释的元素必须是 Email 格式; @Min(value):被注释的元素必须是一个数字,其值必须大于等于指定的最小值; @Max(value):被注释的元素必须是一个数字,其值必须小于等于指定的最大值; @DecimalMin(value):被注释的元素必须是一个数字,其值必须大于等于指定的最小值; @DecimalMax(value):被注释的元素必须是一个数字,其值必须小于等于指定的最大值; @Size(max=, min=):被注释的字符串、Collection、Map、数组等的大小必须在指定的范围内; @Range(min=最小值, max=最大值):被注释的BigDecimal,BigInteger,CharSequence, byte, short, int, long等原子类型和包装类型在最小值和最大值之间; @Digits (integer=整数位数, fraction=小数位数):被注释的元素必须是一个数字,其值必须在可接受的范围内; @Past:被注释的元素必须是一个过去的日期; @Future:被注释的元素必须是一个将来的日期; 6.2 验证请求体(RequestBody) @Data @AllArgsConstructor @NoArgsConstructor public class Person {     @NotNull(message = "classId不能为空")     private String classId;     @Size(max=33)     @NotNull(message = "name不能为空")     private String name;     @Pattern(regexp ="((^Man$|^Woman$|^UGM$))", message = "sex值不在可选范围")     @NotNull(message = "sex不能为空")     private String sex;     @Email(message = "email格式不正确")     @NotNull(message = "email不能为空")     private String email;  } 在需要验证的参数上加上@Valid注解,如果验证失败,它将抛出MethodArgumentNotValidException。 @RestController @RequestMapping("/api") public class PersonController {      @PostMapping("/person")     public ResponseEntity<Person> getPerson(@RequestBody @Valid Person person) {         return ResponseEntity.ok().body(person);     } } 6.3 验证请求参数(Path Variables 和 Request Parameters) 需要在类上加上 Validated 注解,它可以告诉 Spring 去校验方法参数。 @RestController @RequestMapping("/api") @Validated public class PersonController {      @GetMapping("/person/{id}")     public ResponseEntity<Integer> getPersonByID(@Valid @PathVariable("id") @Max(value = 5,message = "超过 id 的范围了") Integer id) {         return ResponseEntity.ok().body(id);     } } 7 全局处理 Controller 层异常 @ControllerAdvice :注解定义全局异常处理类 @ExceptionHandler :注解声明异常处理方法 拿参数校验来举例。如果方法参数不对的话就会抛出MethodArgumentNotValidException。 @ControllerAdvice @ResponseBody public class GlobalExceptionHandler {      /**      * 请求参数异常处理      */     @ExceptionHandler(MethodArgumentNotValidException.class)     public ResponseEntity<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, HttpServletRequest request) {        ......     } } 8 json 数据处理 8.1 过滤 json 数据 @JsonIgnoreProperties 作用在类上用于过滤掉特定字段不返回或者不解析; //生成json时将userRoles属性过滤 @JsonIgnoreProperties({"userRoles"}) public class User {      private String userName;     private String fullName;     private String password;     private List<UserRole> userRoles = new ArrayList<>(); } @JsonIgnore一般用于类的属性上,作用和上面的@JsonIgnoreProperties 一样; public class User {     private String userName;     private String fullName;     private String password;    //生成json时将userRoles属性过滤     @JsonIgnore     private List<UserRole> userRoles = new ArrayList<>(); } 8.2 格式化 json 数据 @JsonFormat一般用来格式化 json 数据。 例 @JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone="GMT") private Date date; 8.3 扁平化对象 @Data public class Account {     private Location location;     private PersonInfo personInfo;    @Data   public static class Location {      private String provinceName;      private String countyName;   }   @Data   public static class PersonInfo {     private String userName;     private String fullName;   } } 未扁平化之前:  {     "location": {         "provinceName":"湖北",         "countyName":"武汉"     },     "personInfo": {         "userName": "coder1234",         "fullName": "shaungkou"     } } 使用@JsonUnwrapped 扁平对象之后: @Data public class Account {     @JsonUnwrapped     private Location location;     @JsonUnwrapped     private PersonInfo personInfo;     ...... } {   "provinceName":"湖北",   "countyName":"武汉",   "userName": "coder1234",   "fullName": "shaungkou" } ———————————————— 原文链接:https://blog.csdn.net/sc179/article/details/128376337 
  • [技术干货] SpringBoot 常用注解汇总
    Spring Boot 常用注解汇总 一、启动注解 @SpringBootApplication 查看源码可发现,@SpringBootApplication是一个复合注解,包含了@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan这三个注解 @SpringBootConfiguration 注解,继承@Configuration注解,主要用于加载配置文件 @SpringBootConfiguration继承自@Configuration,二者功能也一致,标注当前类是配置类, 并会将当前类内声明的一个或多个以@Bean注解标记的方法的实例纳入到spring容器中,并且实例名就是方法名。 @EnableAutoConfiguration 注解,开启自动配置功能 @EnableAutoConfiguration可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器。借助于Spring框架原有的一个工具类:SpringFactoriesLoader的支持,@EnableAutoConfiguration可以智能的自动配置功效才得以大功告成 @ComponentScan 注解,主要用于组件扫描和自动装配 @ComponentScan的功能其实就是自动扫描并加载符合条件的组件或bean定义,最终将这些bean定义加载到容器中。我们可以通过basePackages等属性指定@ComponentScan自动扫描的范围,如果不指定,则默认Spring框架实现从声明@ComponentScan所在类的package进行扫描,默认情况下是不指定的,所以SpringBoot的启动类最好放在root package下。 二、Controller 相关注解 @Controller 控制器,处理http请求。 @RestController 复合注解 查看@RestController源码 从源码我们知道,@RestController注解相当于@ResponseBody+@Controller合在一起的作用,RestController使用的效果是将方法返回的对象直接在浏览器上展示成json格式. @RequestBody 通过HttpMessageConverter读取Request Body并反序列化为Object(泛指)对象 @RequestMapping @RequestMapping 是 Spring Web 应用程序中最常被用到的注解之一。这个注解会将 HTTP 请求映射到 MVC 和 REST 控制器的处理方法上 @GetMapping用于将HTTP get请求映射到特定处理程序的方法注解 GetMapping源码 是@RequestMapping(method = RequestMethod.GET)的缩写 @PostMapping用于将HTTP post请求映射到特定处理程序的方法注解 是@RequestMapping(method = RequestMethod.POST)的缩写 三、取请求参数值 @PathVariable:获取url中的数据 请求示例:http://localhost:8080/User/getUser/123 @RequestParam:获取请求参数的值 请求示例:http://localhost:8080/User/getUser?uid=123 @RequestHeader 把Request请求header部分的值绑定到方法的参数上 @CookieValue 把Request header中关于cookie的值绑定到方法的参数上 四、注入bean相关 @Repository DAO层注解,DAO层中接口继承JpaRepository<T,ID extends Serializable>,需要在build.gradle中引入相关jpa的一个jar自动加载。 Repository注解源码 @Service是@Component注解的一个特例,作用在类上 @Service注解作用域默认为单例 使用注解配置和类路径扫描时,被@Service注解标注的类会被Spring扫描并注册为Bean @Service用于标注服务层组件,表示定义一个bean @Service使用时没有传参数,Bean名称默认为当前类的类名,首字母小写 @Service(“serviceBeanId”)或@Service(value=”serviceBeanId”)使用时传参数,使用value作为Bean名字 @Scope作用域注解 @Scope作用在类上和方法上,用来配置 spring bean 的作用域,它标识 bean 的作用域 @Scope源码 属性介绍 @Entity实体类注解 @Table(name =“数据库表名”),这个注解也注释在实体类上,对应数据库中相应的表。 @Id、@Column注解用于标注实体类中的字段,pk字段标注为@Id,其余@Column。 @Bean产生一个bean的方法 @Bean明确地指示了一种方法,产生一个bean的方法,并且交给Spring容器管理。支持别名@Bean(“xx-name”) @Autowired 自动导入 @Autowired注解作用在构造函数、方法、方法参数、类字段以及注解上 @Autowired注解可以实现Bean的自动注入 @Component 把普通pojo实例化到spring容器中,相当于配置文件中的 虽然有了@Autowired,但是我们还是要写一堆bean的配置文件,相当麻烦,而@Component就是告诉spring,我是pojo类,把我注册到容器中吧,spring会自动提取相关信息。那么我们就不用写麻烦的xml配置文件了 五、导入配置文件 @Import 导入额外的配置信息 功能类似XML配置的,用来导入配置类,可以导入带有@Configuration注解的配置类或实现了ImportSelector/ImportBeanDefinitionRegistrar。 使用示例 六、事务注解 @Transactional 在Spring中,事务有两种实现方式,分别是编程式事务管理和声明式事务管理两种方式 编程式事务管理: 编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。 声明式事务管理: 建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务,通过@Transactional就可以进行事务操作,更快捷而且简单。推荐使用 七、全局异常处理 @ControllerAdvice 统一处理异常 @ControllerAdvice 注解定义全局异常处理类 @ExceptionHandler 注解声明异常处理方法 ———————————————— 原文链接:https://blog.csdn.net/m0_67401153/article/details/125243438 
  • [技术干货] SpringBoot最常用的50个注解
    SpringBoot最常用的50个注解 ​ SpringBoot提供了很多注解,可以帮助我们快速构建应用程序。以下是SpringBoot最常用的50个注解: (1)@SpringBootApplication 作用:这是一个组合注解,包括了@Configuration、@EnableAutoConfiguration和@ComponentScan三个注解。用于标识SpringBoot应用程序的入口类。 @Configuration:指示这个类是一个配置类,它定义了一个或多个@Bean方法,用于创建和配置Spring应用程序上下文中的Bean。 @EnableAutoConfiguration:启用Spring Boot的自动配置机制,它会自动添加所需的依赖项和配置,以使应用程序能够运行。 @ComponentScan:指示Spring Boot扫描当前包及其子包中的所有@Component、@Service、@Repository和@Controller注解的类,并将它们注册为Spring Bean。 @SpringBootApplication注解通常被用于Spring Boot应用程序的入口类上,用于启动Spring Boot应用程序。它可以简化Spring应用程序的配置和启动过程。 用例: @SpringBootApplication public class MyApplication {      public static void main(String[] args) {         SpringApplication.run(MyApplication.class, args);     } } (2)@RestController 作用:与@Controller类似,但是@RestController会自动将返回值转换为JSON格式。 @RestController是Spring Framework 4.0版本引入的一个注解,它是@Controller和@ResponseBody的组合注解。它用于标注一个类,表示这个类是一个RESTful风格的控制器,可以处理HTTP请求并返回JSON/XML格式的响应。 @RestController注解用于替代原来的@Controller注解,它默认情况下会将控制器方法的返回值转换为JSON格式,并以HTTP响应的方式返回给客户端。如果需要返回XML格式的响应,可以使用其他注解,如@Produces和@Consumes。 用例: @RestController public class HelloController {      @GetMapping("/hello")     public String hello() {         return "Hello, World!";     } } (3)@RequestMapping 作用:用于映射请求URL和处理方法。@RequestMapping是Spring MVC框架中的一个核心注解,它用于映射HTTP请求和控制器方法之间的关系。它可以用于类级别和方法级别,用于指定请求URL和HTTP方法(GET、POST、PUT、DELETE等)。 用例: @RestController @RequestMapping("/api") public class UserController {      @GetMapping("/users")     public List<User> getUsers() {         // 获取用户列表     }      @PostMapping("/users")     public void createUser(@RequestBody User user) {         // 创建新用户     }      @GetMapping("/users/{id}")     public User getUserById(@PathVariable Long id) {         // 根据ID获取用户信息     }      @PutMapping("/users/{id}")     public void updateUser(@PathVariable Long id, @RequestBody User user) {         // 更新用户信息     }      @DeleteMapping("/users/{id}")     public void deleteUser(@PathVariable Long id) {         // 根据ID删除用户     } } (4)@GetMapping 作用:用于映射HTTP GET请求。 用例: @RestController @RequestMapping("/api") public class UserController {      @GetMapping("/users")     public List<User> getUsers() {         // 获取用户列表     }      @GetMapping("/users/{id}")     public User getUserById(@PathVariable Long id) {         // 根据ID获取用户信息     } } (5)@PostMapping 作用:用于映射HTTP POST请求。 用例: @RestController @RequestMapping("/api") public class UserController {      @PostMapping("/users")     public void createUser(@RequestBody User user) {         // 创建新用户     } } (6)@PutMapping 作用:用于映射HTTP PUT请求。 用例: @RestController @RequestMapping("/api") public class UserController {      @PutMapping("/users/{id}")     public void updateUser(@PathVariable Long id, @RequestBody User user) {         // 更新用户信息     } } (7)@DeleteMapping 作用:用于映射HTTP DELETE请求。 用例: @RestController @RequestMapping("/api") public class UserController {      @DeleteMapping("/users/{id}")     public void deleteUser(@PathVariable Long id) {         // 根据ID删除用户     } } (8)@RequestParam 作用:用于获取请求参数的值。 用例: @RestController @RequestMapping("/api") public class UserController {      @GetMapping("/users")     public List<User> getUsers(@RequestParam("page") int page, @RequestParam("size") int size) {         // 分页获取用户列表     } } (9)@PathVariable 作用:用于获取URL中的参数值。@PathVariable是Spring MVC框架中的一个注解,用于将HTTP请求路径中的变量绑定到控制器方法的参数上。 用例:  @RestController @RequestMapping("/api") public class UserController {      @GetMapping("/users/{id}")     public User getUser(@PathVariable Long id) {         // 根据ID获取用户信息     } } (10)@RequestBody 作用:用于将HTTP请求的主体转换为方法的参数。@RequestBody是Spring MVC框架中的一个注解,用于将HTTP请求体中的数据绑定到控制器方法的参数上。 用例: @RestController @RequestMapping("/api") public class UserController {      @PostMapping("/users")     public User createUser(@RequestBody User user) {         // 创建用户     } } (11)@ResponseBody 作用:用于将方法的返回值转换为HTTP响应的主体。@ResponseBody是Spring MVC框架中的一个注解,用于将控制器方法的返回值转换为HTTP响应体中的数据。 用例: @RestController public class UserController {      @GetMapping("/users/{id}")     @ResponseBody     public User getUser(@PathVariable int id) {         // 从数据库或其他地方获取用户数据         User user = userService.getUserById(id);         return user;     } } (12)@Autowired 作用:用于自动装配Spring容器中的Bean。 用例: @Service public class UserServiceImpl implements UserService {      @Autowired     private UserRepository userRepository;      // 实现UserService接口中的方法 } (13)@Component 作用:用于标识一个类是Spring容器中的组件。@Component是Spring框架中的一个通用注解,用于标注一个类作为Spring Bean。 用例: @Component public class UserServiceImpl implements UserService {      // 实现UserService接口中的方法 } (14)@Service 作用:用于标识一个类是Spring容器中的服务组件。@Service是Spring框架中的一个注解,用于标注一个类作为服务类(Service)。 用例: @Service public class UserServiceImpl implements UserService {      // 实现UserService接口中的方法 } (15)@Repository 作用:用于标识一个类是Spring容器中的数据访问组件。@Repository是Spring框架中的一个注解,用于标注一个类作为数据访问对象(DAO)。 用例: @Repository public class UserRepositoryImpl implements UserRepository {      // 实现UserRepository接口中的方法 } (16)@Configuration 作用:用于标识一个类是Spring的配置类。@Configuration是Spring框架中的一个注解,用于标注一个类作为配置类。 用例: @Configuration public class AppConfig {      @Bean     public UserService userService() {         return new UserServiceImpl();     }      @Bean     public UserRepository userRepository() {         return new UserRepositoryImpl();     } } (17)@Value 作用:用于获取配置文件中的属性值。@Value是Spring框架中的一个注解,用于将配置文件中的属性值注入到Bean对象中。 用例: @Component public class MyComponent {      @Value("${my.property}")     private String myProperty;      // 其他方法 } 这个类使用@Component注解标注,表示这个类是一个Spring Bean,可以被其他的Spring Bean自动装配。 在属性级别上,@Value注解指定了需要注入的属性值,这个属性值可以通过${...}的方式引用配置文件中的属性值。 在这个例子中,MyComponent类中的myProperty属性使用@Value注解指定了需要注入的属性值,Spring会自动将配置文件中名为my.property的属性值注入到这个属性中。 @Value注解用于注入配置文件中的属性值,使得开发者可以方便地从配置文件中获取属性值,并将其注入到Bean对象中。同时,使用@Value注解还可以方便地处理不同环境下的配置文件,如开发环境和生产环境的配置文件。 @Value注解是Spring框架中比较常用的注解之一,可以让开发者更加专注于业务逻辑的实现,而不必关心属性值的获取和注入细节。 (18)@Bean 作用:用于将一个方法返回的对象注册到Spring容器中。@Bean是Spring框架中的一个注解,用于将一个方法返回的对象注册为一个Spring Bean。 用例: @Configuration public class AppConfig {      @Bean     public UserService userService() {         return new UserServiceImpl();     }      @Bean     public UserRepository userRepository() {         return new UserRepositoryImpl();     } } (19)@Import 作用:用于导入其他配置类或Bean。 用例: @Configuration @Import({AppConfig1.class, AppConfig2.class}) public class AppConfig {      // 其他方法 } (20)@Conditional 作用:用于根据条件判断是否创建Bean或执行配置。 用例: @Configuration public class AppConfig {      @Bean     @Conditional(DatabaseTypeCondition.class)     public UserRepository userRepository() {         return new UserRepositoryImpl();     }      // 其他方法 } (21)@Profile 作用:用于指定配置的环境,如开发环境、测试环境或生产环境。 用例:  @Configuration public class AppConfig {      @Bean     @Profile("dev")     public UserService userServiceDev() {         return new UserServiceDevImpl();     }      @Bean     @Profile("prod")     public UserService userServiceProd() {         return new UserServiceProdImpl();     }      // 其他方法 } (22)@PropertySource 作用:用于指定配置文件的位置。@PropertySource是Spring框架中的一个注解,用于指定一组属性文件的位置,从而可以在Spring应用程序中使用这些属性。 用例:  @Configuration @PropertySource("classpath:application.properties") public class AppConfig {      @Autowired     private Environment environment;      @Bean     public UserService userService() {         return new UserServiceImpl(environment.getProperty("userService.name"));     }      // 其他方法 } 这个类使用@Configuration注解标注,表示这个类是一个配置类,用于配置应用程序的Bean对象。 在类级别上,使用@PropertySource注解可以指定一个属性文件的位置。在这个例子中,使用@PropertySource注解指定了一个名为application.properties的属性文件,它位于classpath下。 在方法级别上,使用@Bean注解标注方法,表示这个方法返回一个Bean对象。在这个例子中,使用Environment对象从属性文件中读取属性值,并将这些属性值传递给UserService实例的构造方法。 @PropertySource注解用于指定一组属性文件的位置,使得开发者可以在Spring应用程序中使用这些属性。同时,使用Environment对象可以方便地读取属性文件中的属性值,并将这些属性值传递给Bean对象的构造方法或属性。 @PropertySource注解是Spring框架中比较常用的注解之一,可以让开发者更加灵活地管理和配置Spring Bean。 (23)@Qualifier 作用:用于指定注入的Bean的名称。 用例:  @Component public class UserServiceImpl implements UserService {      @Autowired     @Qualifier("userRepositoryImpl")     private UserRepository userRepository;      // 其他方法 } (24)@ExceptionHandler 作用:用于处理异常。 用例: @ControllerAdvice public class GlobalExceptionHandler {      @ExceptionHandler(Exception.class)     public ModelAndView handleException(Exception ex) {         ModelAndView modelAndView = new ModelAndView();         modelAndView.addObject("errorMessage", ex.getMessage());         modelAndView.setViewName("error");         return modelAndView;     } }  这个类使用@ControllerAdvice注解标注,表示这个类是一个全局异常处理器。在方法级别上,使用@ExceptionHandler注解可以指定一个方法来处理控制器中抛出的异常。 在这个例子中,使用@ExceptionHandler注解指定了一个名为handleException的方法,它处理所有类型的异常。当控制器中抛出异常时,会调用这个方法,并将异常对象作为参数传递给这个方法。 在这个方法中,使用ModelAndView对象来封装错误信息,并将视图名称设置为error。最后,返回这个ModelAndView对象,将错误信息显示到用户界面上。 @ExceptionHandler注解用于处理控制器中抛出的异常,使得开发者可以根据需要灵活地处理异常。同时,使用@ControllerAdvice注解可以将这个异常处理器应用于所有的控制器中。 @ExceptionHandler注解是Spring框架中比较常用的注解之一,可以让开发者更加灵活地处理控制器中的异常。 (25)@ResponseStatus 作用:用于指定异常的HTTP响应状态码。 用例: @Controller public class UserController {      @GetMapping("/user/{id}")     @ResponseBody     @ResponseStatus(HttpStatus.OK)     public UserDetails getUserDetails(@PathVariable("id") Long id) {         // 查询用户信息         UserDetails userDetails = userService.getUserDetails(id);         if (userDetails == null) {             throw new UserNotFoundException("User not found");         }         return userDetails;     }      @ExceptionHandler(UserNotFoundException.class)     @ResponseStatus(HttpStatus.NOT_FOUND)     @ResponseBody     public String handleUserNotFoundException(UserNotFoundException ex) {         return ex.getMessage();     } } (26)@ControllerAdvice 作用:用于全局处理异常。 @ControllerAdvice是Spring框架中的一个注解,用于定义全局控制器通知。 在Spring MVC框架中,控制器通知是一些特殊的组件,它们可以在控制器方法执行前、执行后或抛出异常时执行一些额外的逻辑处理。使用@ControllerAdvice注解可以定义全局控制器通知,它可以应用于所有的控制器。 用例:  @ControllerAdvice public class GlobalControllerAdvice {      @ModelAttribute("currentUser")     public User getCurrentUser() {         // 获取当前登录用户信息         User currentUser = userService.getCurrentUser();         return currentUser;     }      @InitBinder     public void initBinder(WebDataBinder binder) {         // 注册自定义的属性编辑器         binder.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), true));     }      @ExceptionHandler(Exception.class)     public ModelAndView handleException(Exception ex) {         ModelAndView modelAndView = new ModelAndView();         modelAndView.addObject("errorMessage", ex.getMessage());         modelAndView.setViewName("error");         return modelAndView;     } }  这个类使用@ControllerAdvice注解标注,表示这个类是一个全局控制器通知。在方法级别上,使用@ModelAttribute注解标注方法,表示这个方法会在所有控制器方法执行前执行,用于将当前登录用户信息添加到模型中。 使用@InitBinder注解标注方法,表示这个方法会在所有控制器方法执行前执行,用于注册自定义的属性编辑器。 使用@ExceptionHandler注解标注方法,表示这个方法会在控制器中抛出异常时执行,用于处理控制器方法中抛出的异常。 @ControllerAdvice注解用于定义全局控制器通知,使得开发者可以在所有控制器方法执行前、执行后或抛出异常时执行一些额外的逻辑处理。同时,使用@ModelAttribute注解可以将一些公共的模型数据添加到模型中,使用@InitBinder注解可以注册自定义的属性编辑器,使用@ExceptionHandler注解可以处理控制器方法中抛出的异常。 @ControllerAdvice注解是Spring框架中比较常用的注解之一,可以让开发者更加灵活地定义全局控制器通知。 (27)@CrossOrigin 作用:用于解决跨域问题。 @CrossOrigin是Spring框架中的一个注解,用于解决跨域资源共享(CORS)问题。 跨域资源共享是浏览器安全策略的一部分,它限制了浏览器在不同域名之间发送和接收HTTP请求。使用@CrossOrigin注解可以指定允许跨域访问的域名和HTTP方法。 用例:  @RestController @RequestMapping("/api") @CrossOrigin(origins = "http://localhost:8080", methods = {RequestMethod.GET, RequestMethod.POST}) public class ApiController {      @GetMapping("/users")     public List<User> getUsers() {         // 查询用户信息         List<User> users = userService.getUsers();         return users;     } }  这个类使用@RestController注解标注,表示这个类是一个RESTful风格的控制器。在类级别上,使用@RequestMapping注解指定控制器处理的请求路径为/api。同时,使用@CrossOrigin注解可以指定允许跨域访问的域名和HTTP方法。 在这个例子中,使用@CrossOrigin注解指定允许来自http://localhost:8080域名的GET和POST请求访问该控制器中的方法。这意味着,在http://localhost:8080域名下的网页可以通过XMLHttpRequest对象发送GET和POST请求,访问该控制器中的方法。 @CrossOrigin注解用于解决跨域资源共享(CORS)问题,使得开发者可以更加灵活地控制允许跨域访问的域名和HTTP方法。它是一种简单但非常有效的解决方案,可以使得前端开发者更加轻松地开发跨域应用程序。 @CrossOrigin注解是Spring框架中比较常用的注解之一,可以让开发者更加灵活地解决跨域资源共享(CORS)问题。 (28)@Async 作用:用于将方法标记为异步执行。 在Spring框架中,如果一个方法需要执行一些比较耗时的操作,如果这个方法是在主线程中执行,就会导致主线程被阻塞,用户界面无法响应用户的操作。使用@Async注解可以将这个方法的执行异步化,让主线程继续执行其他任务,提高应用程序的响应性能。 用例:  @Service public class UserService {      @Async     public CompletableFuture<UserDetails> getUserDetailsAsync(Long id) {         // 查询用户信息         UserDetails userDetails = userRepository.getUserDetails(id);         return CompletableFuture.completedFuture(userDetails);     } } 这个类使用@Service注解标注,表示这个类是一个服务。在方法级别上,使用@Async注解标注方法,表示这个方法需要异步执行。 在这个例子中,getUserDetailsAsync方法使用@Async注解标注,表示这个方法需要异步执行。查询用户信息的操作在异步线程中执行,不会阻塞主线程。同时,这个方法返回一个CompletableFuture对象,表示异步执行的结果。 @Async注解用于异步执行方法,可以提高应用程序的响应性能。它是一种简单但非常有效的解决方案,可以使得开发者更加轻松地编写并发应用程序。 @Async注解是Spring框架中比较常用的注解之一,可以让开发者更加灵活地异步执行方法。需要注意的是,异步执行的方法必须在一个独立的线程中执行,因此需要使用线程池来管理异步线程的执行。 (29)@Cacheable 作用:用于缓存方法的返回值。 在Spring框架中,如果一个方法的返回结果是固定的,而且这个方法的执行比较耗时,我们可以使用@Cacheable注解将这个方法的返回结果缓存起来,下次执行这个方法时直接从缓存中获取结果,避免重复执行。  用例: @Service public class UserService {      @Cacheable("userCache")     public User getUser(Long id) {         // 查询用户信息         User user = userRepository.getUser(id);         return user;     } } 这个类使用@Service注解标注,表示这个类是一个服务。在方法级别上,使用@Cacheable注解标注方法,表示这个方法返回的结果可以被缓存起来。 在这个例子中,getUser方法使用@Cacheable注解标注,表示这个方法的返回结果可以被缓存起来。查询用户信息的操作在第一次执行时会被执行,返回结果会被缓存到名为"userCache"的缓存中。下次执行getUser方法时,如果缓存中已经存在这个结果,就直接从缓存中获取结果,不需要再次执行查询操作。 @Cacheable注解用于缓存方法的返回结果,可以提高应用程序的执行效率。它是一种简单但非常有效的解决方案,可以使得开发者更加灵活地使用缓存来优化应用程序的性能。 @Cacheable注解是Spring框架中比较常用的注解之一,可以让开发者更加轻松地使用缓存来提高应用程序的性能。需要注意的是,使用缓存需要考虑缓存的生命周期和缓存的一致性,必要时需要使用缓存失效机制和缓存更新机制来维护缓存的一致性。 (30)@CacheEvict 作用:用于清除缓存。 @CacheEvict是Spring框架中的一个注解,用于清空缓存中的数据。 在Spring框架中,如果一个方法的执行会导致缓存数据的失效,我们可以使用@CacheEvict注解将这个方法的缓存数据清空,这样下次执行这个方法时就会重新查询数据并缓存起来。 用例: @Service public class UserService {      @Cacheable("userCache")     public User getUser(Long id) {         // 查询用户信息         User user = userRepository.getUser(id);         return user;     }          @CacheEvict("userCache")     public void clearCache() {         // 清空缓存     } } 这个类使用@Service注解标注,表示这个类是一个服务。在方法级别上,使用@Cacheable注解标注getUser方法,表示这个方法的返回结果可以被缓存起来。同时,使用@CacheEvict注解标注clearCache方法,表示这个方法会清空名为"userCache"的缓存。 在这个例子中,getUser方法使用@Cacheable注解标注,表示这个方法的返回结果可以被缓存起来。查询用户信息的操作在第一次执行时会被执行,返回结果会被缓存到名为"userCache"的缓存中。下次执行getUser方法时,如果缓存中已经存在这个结果,就直接从缓存中获取结果,不需要再次执行查询操作。 当调用clearCache方法时,@CacheEvict注解会清空名为"userCache"的缓存,下次执行getUser方法时,就需要重新查询数据并缓存起来。 @CacheEvict注解用于清空缓存中的数据,可以使得开发者更加灵活地控制缓存的生命周期和缓存的一致性。它是一种简单但非常有效的解决方案,可以使得开发者更加轻松地使用缓存来提高应用程序的性能。 @CacheEvict注解是Spring框架中比较常用的注解之一,可以让开发者更加灵活地控制缓存的生命周期和缓存的一致性。需要注意的是,清空缓存需要谨慎操作,必要时需要考虑缓存的失效机制和缓存更新机制来维护缓存的一致性。 (31)@CachePut 作用:用于更新缓存中的数据。 @CachePut是Spring框架中的一个注解,用于更新或添加缓存中的数据。 在Spring框架中,如果一个方法的执行会导致缓存数据的更新或添加,我们可以使用@CachePut注解将这个方法的返回结果更新或添加到缓存中。 用例: @Service public class UserService {      @Cacheable("userCache")     public User getUser(Long id) {         // 查询用户信息         User user = userRepository.getUser(id);         return user;     }          @CachePut("userCache")     public User updateUser(Long id, User user) {         // 更新用户信息         User updatedUser = userRepository.updateUser(id, user);         return updatedUser;     } } 这个类使用@Service注解标注,表示这个类是一个服务。在方法级别上,使用@Cacheable注解标注getUser方法,表示这个方法的返回结果可以被缓存起来。同时,使用@CachePut注解标注updateUser方法,表示这个方法会更新或添加名为"userCache"的缓存。 在这个例子中,getUser方法使用@Cacheable注解标注,表示这个方法的返回结果可以被缓存起来。查询用户信息的操作在第一次执行时会被执行,返回结果会被缓存到名为"userCache"的缓存中。下次执行getUser方法时,如果缓存中已经存在这个结果,就直接从缓存中获取结果,不需要再次执行查询操作。 当调用updateUser方法时,@CachePut注解会更新或添加名为"userCache"的缓存,下次执行getUser方法时,就可以从缓存中获取更新后的用户信息。 @CachePut注解用于更新或添加缓存中的数据,可以使得开发者更加灵活地控制缓存的生命周期和缓存的一致性。它是一种简单但非常有效的解决方案,可以使得开发者更加轻松地使用缓存来提高应用程序的性能。 @CachePut注解是Spring框架中比较常用的注解之一,可以让开发者更加灵活地控制缓存的生命周期和缓存的一致性。需要注意的是,更新或添加缓存需要谨慎操作,必要时需要考虑缓存的失效机制和缓存更新机制来维护缓存的一致性。 (32)@Transactional 作用:用于指定事务的范围。 用例: (33)@EnableTransactionManagement 作用:用于启用事务管理功能。 @Transactional是Spring框架中的一个注解,用于标识一个方法或类需要使用事务进行操作。 在Spring框架中,如果一个方法需要对数据库进行操作,我们可以使用@Transactional注解来确保这个操作在一个事务中进行,从而保证操作的原子性、一致性、隔离性和持久性。 用例:  @Service @Transactional public class UserService {      @Autowired     private UserRepository userRepository;      public void createUser(User user) {         userRepository.save(user);     }          public void updateUser(Long id, User user) {         User existingUser = userRepository.findById(id);                  if (existingUser != null) {             existingUser.setName(user.getName());             existingUser.setEmail(user.getEmail());             userRepository.save(existingUser);         }     } }  这个类使用@Service注解标注,表示这个类是一个服务。同时,在类级别上使用@Transactional注解标注,表示这个类中的所有方法都需要使用事务进行操作。 在这个例子中,createUser和updateUser方法都需要对数据库进行操作,因此使用userRepository来保存或更新用户信息。由于这个类使用了@Transactional注解来标识,因此userRepository的操作都在一个事务中进行,从而保证操作的原子性、一致性、隔离性和持久性。 @Transactional注解用于标识一个方法或类需要使用事务进行操作,可以使得开发者更加灵活地控制事务的使用。它是一种简单但非常有效的解决方案,可以使得开发者更加轻松地使用事务来提高应用程序的性能和数据一致性。 @Transactional注解是Spring框架中比较常用的注解之一,可以让开发者更加灵活地控制事务的使用。需要注意的是,事务的使用需要谨慎操作,必要时需要考虑事务的隔离级别、超时时间和回滚机制等来维护数据的一致性和应用程序的性能。 (34)@EnableAspectJAutoProxy 作用:用于启用AOP功能。 @EnableAspectJAutoProxy是Spring框架中的一个注解,用于启用自动代理功能,以便使用AOP(面向切面编程)进行编程。 在Spring框架中,如果需要使用AOP来实现某些功能,我们可以使用@EnableAspectJAutoProxy注解来启用自动代理功能,从而在运行时自动为我们生成代理对象,以便进行切面编程。 用例:  @Configuration @EnableAspectJAutoProxy public class AppConfig {      @Bean     public MyAspect myAspect() {         return new MyAspect();     }          @Bean     public UserService userService() {         return new UserService();     } }  这个类使用@Configuration注解标注,表示这个类是一个配置类。同时,在类级别上使用@EnableAspectJAutoProxy注解标注,表示这个配置类需要启用自动代理功能。 在这个例子中,我们定义了一个MyAspect类来实现某些功能的切面编程。为了让Spring框架能够自动为我们生成代理对象,我们需要将MyAspect类加入到Spring容器中,并且使用@Bean注解标注。另外,我们还定义了一个UserService类来实现某些业务功能。 @EnableAspectJAutoProxy注解用于启用自动代理功能,可以使得开发者更加方便地使用AOP来实现某些功能。它是一种简单但非常有效的解决方案,可以让开发者更加轻松地使用切面编程来提高应用程序的性能和可维护性。  @EnableAspectJAutoProxy注解是Spring框架中比较常用的注解之一,可以让开发者更加方便地使用AOP来实现某些功能。需要注意的是,AOP的使用需要谨慎操作,必要时需要考虑AOP的切面逻辑、切入点和通知类型等来维护应用程序的性能和可维护性。 (35)@Aspect 作用:用于定义切面。 @Aspect是Spring框架中的一个注解,用于标识一个类为切面类,从而可以在该类中定义切面逻辑以实现AOP(面向切面编程)。 在Spring框架中,如果需要使用AOP来实现某些功能,我们可以使用@Aspect注解来标识一个类为切面类。在切面类中,我们可以定义切面逻辑,包括切入点、通知类型和切面顺序等,从而实现AOP编程的功能。 用例:  @Aspect @Component public class MyAspect {      @Before("execution(* com.example.UserService.*(..))")     public void beforeAdvice() {         System.out.println("Before advice is executed.");     }          @After("execution(* com.example.UserService.*(..))")     public void afterAdvice() {         System.out.println("After advice is executed.");     } }  这个类使用@Aspect注解标识,表示这个类是一个切面类。同时,我们还使用@Component注解标识这个类,以便Spring框架能够自动将它加入到Spring容器中。 在这个例子中,我们定义了一个MyAspect类来实现某些功能的切面编程。在这个类中,我们定义了两个通知类型,即@Before和@After,分别表示在目标方法执行前和执行后执行某些操作。这些通知类型的执行条件是通过切入点表达式来定义的。 @Aspect注解用于标识一个类为切面类,可以使得开发者更加方便地使用AOP来实现某些功能。它是一种简单但非常有效的解决方案,可以让开发者更加轻松地使用切面编程来提高应用程序的性能和可维护性。 @Aspect注解是Spring框架中比较常用的注解之一,用于标识一个类为切面类。需要注意的是,AOP的使用需要谨慎操作,必要时需要考虑切入点、通知类型和切面顺序等来维护应用程序的性能和可维护性。 (36)@Pointcut 作用:用于定义切点。 @Pointcut是Spring框架中的一个注解,用于定义一个切入点,从而可以在该切入点上定义通知类型以实现AOP(面向切面编程)。 在Spring框架中,如果需要使用AOP来实现某些功能,我们可以使用@Pointcut注解来定义一个切入点。在切入点上,我们可以定义切面逻辑,包括通知类型和切面顺序等,从而实现AOP编程的功能。 用例:  @Aspect @Component public class MyAspect {      @Pointcut("execution(* com.example.UserService.*(..))")     public void userServicePointcut() {}          @Before("userServicePointcut()")     public void beforeAdvice() {         System.out.println("Before advice is executed.");     }          @After("userServicePointcut()")     public void afterAdvice() {         System.out.println("After advice is executed.");     } }  这个类使用@Aspect注解标识,表示这个类是一个切面类。同时,我们还使用@Component注解标识这个类,以便Spring框架能够自动将它加入到Spring容器中。 在这个例子中,我们定义了一个MyAspect类来实现某些功能的切面编程。在这个类中,我们使用@Pointcut注解定义了一个切入点,即userServicePointcut()方法。在这个切入点上,我们定义了两个通知类型,即@Before和@After,分别表示在目标方法执行前和执行后执行某些操作。 @Pointcut注解用于定义一个切入点,可以使得开发者更加方便地使用AOP来实现某些功能。它是一种简单但非常有效的解决方案,可以让开发者更加轻松地使用切面编程来提高应用程序的性能和可维护性。  @Pointcut注解是Spring框架中比较常用的注解之一,用于定义一个切入点。需要注意的是,AOP的使用需要谨慎操作,必要时需要考虑切入点、通知类型和切面顺序等来维护应用程序的性能和可维护性。 (37)@Before 作用:用于在方法执行前执行通知。 @Before是Spring框架中的一个注解,用于定义在目标方法执行前执行的通知类型,以实现AOP(面向切面编程)。 在Spring框架中,如果需要在目标方法执行前执行某些操作,我们可以使用@Before注解来定义一个通知类型。在这个通知类型中,我们可以编写自己的逻辑代码,从而实现AOP编程的功能。 用例:  @Aspect @Component public class MyAspect {      @Before("execution(* com.example.UserService.*(..))")     public void beforeAdvice() {         System.out.println("Before advice is executed.");     } } (38)@After 作用:用于在方法执行后执行通知。 @After是Spring框架中的一个注解,用于定义在目标方法执行后执行的通知类型,以实现AOP(面向切面编程)。 在Spring框架中,如果需要在目标方法执行后执行某些操作,我们可以使用@After注解来定义一个通知类型。在这个通知类型中,我们可以编写自己的逻辑代码,从而实现AOP编程的功能。 用例:  @Aspect @Component public class MyAspect {      @After("execution(* com.example.UserService.*(..))")     public void afterAdvice() {         System.out.println("After advice is executed.");     } } (39)@Around 作用:用于在方法执行前后执行通知。 @Around是Spring框架中的一个注解,用于定义在目标方法执行前后执行的通知类型,以实现AOP(面向切面编程)。 在Spring框架中,如果需要在目标方法执行前后执行某些操作,我们可以使用@Around注解来定义一个通知类型。在这个通知类型中,我们可以编写自己的逻辑代码,从而实现AOP编程的功能。 用例: @Aspect @Component public class MyAspect {      @Around("execution(* com.example.UserService.*(..))")     public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {         System.out.println("Before advice is executed.");         Object result = joinPoint.proceed();         System.out.println("After advice is executed.");         return result;     } } (40)@AfterReturning 作用:用于在方法返回结果后执行通知。  @AfterReturning是Spring框架中的一个注解,用于定义在目标方法返回结果后执行的通知类型,以实现AOP(面向切面编程)。 在Spring框架中,如果需要在目标方法返回结果后执行某些操作,我们可以使用@AfterReturning注解来定义一个通知类型。在这个通知类型中,我们可以编写自己的逻辑代码,从而实现AOP编程的功能。 用例:  @Aspect @Component public class MyAspect {      @AfterReturning(pointcut = "execution(* com.example.UserService.*(..))", returning = "result")     public void afterReturningAdvice(Object result) {         System.out.println("After returning advice is executed. Result is " + result);     } } (41)@AfterThrowing 作用:用于在方法抛出异常后执行通知。 @AfterThrowing是Spring框架中的一个注解,用于定义在目标方法抛出异常后执行的通知类型,以实现AOP(面向切面编程)。 在Spring框架中,如果需要在目标方法抛出异常后执行某些操作,我们可以使用@AfterThrowing注解来定义一个通知类型。在这个通知类型中,我们可以编写自己的逻辑代码,从而实现AOP编程的功能。 用例:  @Aspect @Component public class MyAspect {      @AfterThrowing(pointcut = "execution(* com.example.UserService.*(..))", throwing = "ex")     public void afterThrowingAdvice(Exception ex) {         System.out.println("After throwing advice is executed. Exception is " + ex);     } } (42)@Order 作用:用于指定切面的执行顺序。 @Order是Spring框架中的一个注解,用于定义切面的执行顺序。 在Spring框架中,如果有多个切面类需要对同一个方法进行切面处理,那么这些切面类的执行顺序可能会影响到最终的结果。为了控制这些切面类的执行顺序,我们可以使用@Order注解来定义它们的执行顺序。 @Order注解可以应用在切面类上,用于指定切面执行的顺序。它的参数为一个整数,数值越小表示优先级越高,数值相同时按照类名的自然顺序进行排序。 用例:  @Aspect @Component @Order(1) public class MyAspect1 {      @Before("execution(* com.example.UserService.*(..))")     public void beforeAdvice() {         System.out.println("Before advice from MyAspect1 is executed.");     } }  @Aspect @Component @Order(2) public class MyAspect2 {      @Before("execution(* com.example.UserService.*(..))")     public void beforeAdvice() {         System.out.println("Before advice from MyAspect2 is executed.");     } } (43)@Slf4j 作用:用于简化日志记录。 @Slf4j是Lombok框架中的一个注解,用于在Java类中自动生成日志记录器。 在Java开发中,日志记录是非常重要的一环,可以帮助我们更好地了解程序的运行情况,从而更好地进行调试和优化。通常情况下,我们需要手动引入日志框架(如Log4j、SLF4J等)并编写相应的日志记录代码。这些代码可能会比较繁琐,而且容易出现错误。为了简化这个过程,Lombok框架提供了一个@Slf4j注解,可以在Java类中自动生成日志记录器。 使用@Slf4j注解非常简单,只需要在Java类中添加这个注解即可。在使用时,我们可以直接使用log变量来记录日志,而不需要再引入其他的日志框架 用例:  @Slf4j public class MyService {      public void doSomething() {         log.debug("This is a debug message.");         log.info("This is an info message.");         log.error("This is an error message.");     } }  在这个例子中,我们定义了一个MyService类,并使用@Slf4j注解来自动生成日志记录器。然后,在doSomething()方法中,我们直接使用log变量来记录日志,而不需要再引入其他的日志框架。 需要注意的是,使用@Slf4j注解需要在编译器中安装Lombok插件,否则可能会出现编译错误。另外,虽然@Slf4j注解非常方便,但在实际应用中,我们还需要根据实际情况选择合适的日志框架,并编写相应的日志记录代码。 总之,@Slf4j是Lombok框架中的一个注解,可以在Java类中自动生成日志记录器,从而简化日志记录的过程。它是一种极为方便的解决方案,可以提高应用程序的可维护性和可读性。 (44)@Data 作用:用于自动生成JavaBean的getters、setters、toString、hashCode和equals方法。 @Data是Lombok框架中的一个注解,可以自动生成Java类的getter、setter、equals、hashCode和toString等方法。 在Java开发中,我们经常需要编写一些POJO类来表示数据结构。这些类通常包含一些成员变量,并且需要编写相应的getter、setter、equals、hashCode和toString等方法。这些方法通常是相似的,而且比较繁琐。为了简化这个过程,Lombok框架提供了一个@Data注解,可以自动生成这些方法。 使用@Data注解非常简单,只需要在Java类上添加这个注解即可。在使用时,我们可以直接访问类的成员变量,并且可以自动生成相应的getter、setter、equals、hashCode和toString等方法。 用例:  @Data public class User {     private Long id;     private String name;     private Integer age; } (45)@NoArgsConstructor 作用:用于生成无参构造函数。 @NoArgsConstructor是Lombok框架中的一个注解,用于自动生成一个无参构造方法。  在Java开发中,我们经常需要编写一些POJO类来表示数据结构。这些类通常包含一些成员变量,并且需要编写相应的构造方法。在某些情况下,我们可能需要编写一个无参构造方法,用于创建一个对象的实例。这个构造方法通常是简单的、无需参数的。为了简化这个过程,Lombok框架提供了一个@NoArgsConstructor注解,可以自动生成一个无参构造方法。 使用@NoArgsConstructor注解非常简单,只需要在Java类上添加这个注解即可。在使用时,我们可以直接创建对象的实例,而不需要手动编写无参构造方法。 用例:  @NoArgsConstructor public class User {     private Long id;     private String name;     private Integer age; } 在这个例子中,我们定义了一个User类,并使用@NoArgsConstructor注解来自动生成一个无参构造方法。然后,在其他的Java类中,我们可以直接创建User对象的实例,而不需要手动编写无参构造方法。  需要注意的是,使用@NoArgsConstructor注解需要在编译器中安装Lombok插件,否则可能会出现编译错误。另外,虽然@NoArgsConstructor注解非常方便,但在实际应用中,我们还需要根据实际情况选择合适的构造方法,并编写相应的代码。 总之,@NoArgsConstructor是Lombok框架中的一个注解,用于自动生成一个无参构造方法,从而简化Java开发的过程。它是一种极为方便的解决方案,可以提高应用程序的可维护性和可读性。 (46)@AllArgsConstructor 作用:用于生成全参构造函数。 @AllArgsConstructor是Lombok框架中的一个注解,用于自动生成一个全参构造方法。 在Java开发中,我们经常需要编写一些POJO类来表示数据结构。这些类通常包含一些成员变量,并且需要编写相应的构造方法。在某些情况下,我们可能需要编写一个全参构造方法,用于初始化所有成员变量。这个构造方法通常包含所有成员变量作为参数。为了简化这个过程,Lombok框架提供了一个@AllArgsConstructor注解,可以自动生成一个全参构造方法。 使用@AllArgsConstructor注解非常简单,只需要在Java类上添加这个注解即可。在使用时,我们可以直接创建对象的实例,并传入相应的参数,而不需要手动编写全参构造方法。 用例:  @AllArgsConstructor public class User {     private Long id;     private String name;     private Integer age; }  在这个例子中,我们定义了一个User类,并使用@AllArgsConstructor注解来自动生成一个全参构造方法。然后,在其他的Java类中,我们可以直接创建User对象的实例,并传入相应的参数,而不需要手动编写全参构造方法。  需要注意的是,使用@AllArgsConstructor注解需要在编译器中安装Lombok插件,否则可能会出现编译错误。另外,虽然@AllArgsConstructor注解非常方便,但在实际应用中,我们还需要根据实际情况选择合适的构造方法,并编写相应的代码。 总之,@AllArgsConstructor是Lombok框架中的一个注解,用于自动生成一个全参构造方法,从而简化Java开发的过程。它是一种极为方便的解决方案,可以提高应用程序的可维护性和可读性。 (47)@Builder 作用:用于生成Builder模式的构造函数。 @Builder是Lombok框架中的一个注解,用于自动生成一个Builder模式的构造器。 在Java开发中,我们经常需要编写一些POJO类来表示数据结构。这些类通常包含一些成员变量,并且需要编写相应的构造方法。在某些情况下,我们可能需要编写一个Builder模式的构造器,用于方便地创建对象实例。Builder模式是一种创建对象的设计模式,它可以通过链式调用的方式设置对象的属性,并最终创建一个不可变的对象。为了简化这个过程,Lombok框架提供了一个@Builder注解,可以自动生成一个Builder模式的构造器。 使用@Builder注解非常简单,只需要在Java类上添加这个注解即可。在使用时,我们可以使用链式调用的方式设置对象的属性,并最终创建一个不可变的对象。 用例: @Builder public class User {     private Long id;     private String name;     private Integer age; } 在这个例子中,我们定义了一个User类,并使用@Builder注解来自动生成一个Builder模式的构造器。然后,在其他的Java类中,我们可以使用链式调用的方式设置User对象的属性,并最终创建一个不可变的对象。 需要注意的是,使用@Builder注解需要在编译器中安装Lombok插件,否则可能会出现编译错误。另外,虽然@Builder注解非常方便,但在实际应用中,我们还需要根据实际情况选择合适的构造方法,并编写相应的代码。 总之,@Builder是Lombok框架中的一个注解,用于自动生成一个Builder模式的构造器,从而简化Java开发的过程。它是一种极为方便的解决方案,可以提高应用程序的可维护性和可读性。 (48)@EqualsAndHashCode 作用:用于生成hashCode和equals方法。 @EqualsAndHashCode是Lombok框架中的一个注解,用于自动生成equals()和hashCode()方法。 在Java开发中,我们经常需要比较两个对象是否相等,并且需要根据对象的属性生成一个hashCode值。为了简化这个过程,Lombok框架提供了一个@EqualsAndHashCode注解,可以自动生成equals()和hashCode()方法。 使用@EqualsAndHashCode注解非常简单,只需要在Java类上添加这个注解即可。在使用时,Lombok会根据类的属性自动生成equals()和hashCode()方法。如果两个对象的所有属性都相等,那么它们的equals()方法返回true,并且它们的hashCode()方法返回相同的值。 用例:  @EqualsAndHashCode public class User {     private Long id;     private String name;     private Integer age; } (49)@ToString 作用:用于生成toString方法。 @ToString是Lombok框架中的一个注解,用于自动生成toString()方法。 在Java开发中,我们经常需要将对象转换为字符串,以便于输出或日志记录。为了简化这个过程,Lombok框架提供了一个@ToString注解,可以自动生成toString()方法。 使用@ToString注解非常简单,只需要在Java类上添加这个注解即可。在使用时,Lombok会根据类的属性自动生成toString()方法,这个方法将输出类的名称和所有属性的名称和值。如果需要排除某些属性,可以使用exclude属性来指定排除的属性。 用例:  @ToString(exclude = "password") public class User {     private Long id;     private String name;     private String password; } (50)@Getter 作用:用于生成getters方法。 @Getter是Lombok框架中的一个注解,用于自动生成getter方法。 在Java开发中,我们经常需要为类的属性编写getter和setter方法。为了简化这个过程,Lombok框架提供了一个@Getter注解,可以自动生成getter方法。 使用@Getter注解非常简单,只需要在Java类上添加这个注解即可。在使用时,Lombok会根据类的属性自动生成对应的getter方法。如果需要生成setter方法,可以使用@Setter注解。 用例: @Getter public class User {     private Long id;     private String name;     private Integer age; } ———————————————— 原文链接:https://blog.csdn.net/qq_46138492/article/details/129476788 
  • [技术干货] Java 算法篇-深入理解递归(递归实现:青蛙爬楼梯)-转载
       1.0 递归的说明         递归就是在一个函数中调用自身。这样做可以让我们解决一些问题,比如计算斐波那契数列、阶乘等。          递归函数一般包括两部分:基本情况和递归情况。基本情况是指当问题变得很小,可以直接得到答案时,递归就可以停止了。递归情况是指在解决问题的过程中,需要不断地调用自身来解决更小规模的问题。          对于递归这个算法,简单的来说,方法自身调用自身的时候,需要有终止的条件,在运行过程中不断的趋向终止条件。还有递归总的来说有两个动作:第一个动作是递出,方法不断的在栈区中创建出来,直到达到了条件就会停止。第二个动作,达到条件停止了,就会回归,指方法在栈区中依次执行完后就销毁。          2.0 用递归来实现相关问题         以下的问题都较为简单,采取直接用代码来演示。          2.1 递归 - 阶乘 代码如下:      //阶乘     public static void main(String[] args) {         System.out.println(fun(5));     }          public static int fun(int n) {         if (n == 1) {             return 1;         }         return n * fun(n-1);     } } 运行结果为:           2.2 递归 - 反向打印字符串 代码如下:      //反向打印字符串     public static void main(String[] args) {         String str = "lisi";         fun2(str,0);     }       public static void fun2 (String s, int n) {           if (n == s.length()) {             return;         }         fun2(s,n + 1);         System.out.println(s.charAt(n));     } 运行结果:           2.3 递归 - 二分查找 代码如下:      //二分查找     public static void main(String[] args) {         int[] arr = {1,3,5,7,9,10,13};         System.out.println(fun3(arr, 0, arr.length - 1, 4));     }       public static int fun3 (int[] arr, int left, int right, int target) {         int mid = (left + right) >>> 1;         if (left > right) {             return -1;         }         if(arr[mid] < target) {             return fun3(arr, mid + 1,right,target);         } else if (target < arr[mid]) {             return fun3(arr,left,right - 1,target);         }else {             return mid;         }     }    运行结果如下:           没有找到就返回 - 1          2.4 递归 - 冒泡排序 代码如下:      //冒泡排序     public static void main(String[] args) {         int[] arr = {1,5,2,4,9,1,3};         fun4(arr, arr.length - 1);         System.out.println(Arrays.toString(arr));     }     public static void fun4 (int[] arr, int n) {         if (n == 0) {             return;         }         for (int i = 0; i < n; i++) {             if (arr[i] > arr[i + 1]) {                 int temp = arr[i];                 arr[i] = arr[i+1];                 arr[i+1] = temp;             }         }         fun4(arr,n-1);     }  运行结果如下:           2.5 递归 - 冒泡排序2.0         对冒泡排序进行升级,假如 int[] arr = {2,1,1,3,4,5,9},这种只需要遍历一遍即可,但是对与已经用递归实现的冒泡不止遍历一次。因此,需要得到升级版冒泡排序。          思路为:对于后续的元素已经是排好序了,就不用再遍历了。每一次交换完元素之后记下来 i 索引,i 之后的元素已经是排好序的,i 之前的元素还需要继续遍历,看是否还需要交换。  代码如下:      //冒泡排序升级版     public static void main(String[] args) {         int[] arr = {1,3,2,4,9,10,13};         fun4(arr, arr.length - 1);         System.out.println(Arrays.toString(arr));     }     public static void fun4 (int[] arr, int n) {         if (n == 0) {             return;         }         int j = 0;         for (int i = 0; i < n; i++) {             if (arr[i] > arr[i + 1]) {                 int temp = arr[i];                 arr[i] = arr[i+1];                 arr[i+1] = temp;                 j = i;             }         }         fun4(arr,j);     }          如果还不是很清晰的话,可以一步步来调试一下,来对比两种冒泡的执行过程。          2.6 递归 - 插入排序         思路:假设第一个元素已经排序好了的,在已经排好的元素的后一个元素记录为 low,这个 low 索引对应的元素需要用临时变量来接受,只要找到比这个索引对应的元素小的值,就可以插入到比它小的值的后一个索引位置了,当然,每一次对比之后,都需要往后移一个位置,以便直接插入。当 low 一直每一个加 1 ,当 low 等于数组的长度时,就该停止了继续递归下去了。  代码如下:  public class Recursion {     // 插入排序     public static void main(String[] args) {         int[] arr = {1,3,2,4,9,10,13};         fun5(arr,1);         System.out.println(Arrays.toString(arr));     }       public static void fun5 (int[] arr,int low) {         if (low == arr.length) {             return;         }         int temp = arr[low];         int i = low - 1;         while (arr[i] > temp) {             arr[i + 1] = arr[i];             i--;         }         arr[i + 1] = temp;         fun5(arr,low + 1);     }  运行结果如下:           2.7 递归 - 斐波那契 代码如下:      //斐波那契     public static void main(String[] args) {         System.out.print(fun6(1) +" ");         System.out.print(fun6(2) +" ");         System.out.print(fun6(3) +" ");         System.out.print(fun6(4) +" ");         System.out.print(fun6(5) +" ");         System.out.print(fun6(6) +" ");     }       public static int fun6 (int n) {         if (n == 0) {             return 0;         }         if (n == 1 || n == 2) {             return 1;         }           return fun6(n-1) + fun6(n - 2);     }  运行结果如下:           2.8 递归 - 兔子问题         一个斐波那契的变体问题。          思路:观察第六个月的兔子个数,是否等于第四个月的兔子的总数加上第五个月的兔子总数;类推,第五个月的兔子个数,是否等于第四个月的兔子的总数加上第三个月的兔子总数;以此类推,是符合斐波那契逻辑的。   代码如下:      //兔子问题     public static void main(String[] args) {         System.out.print(fun7(1) + " ");         System.out.print(fun7(2) + " ");         System.out.print(fun7(3) + " ");         System.out.print(fun7(4) + " ");         System.out.print(fun7(5) + " ");     }          public static int fun7 (int n) {         if (n == 1) {             return 1;         }         if (n == 0) {             return 0;         }         return fun7(n -1) + fun7(n - 2);     }  运行结果如下:           2.9 递归 - 青蛙爬楼梯         一个斐波那契的变体问题。  题目如下:   列举一下:           实现思路: 一个阶梯一种跳法,两个阶梯两种跳法。重点,如果有四个阶梯,从后往前分析,分两种情况;第一种,从第二个台阶直接一下子跳两阶上来。第二种,从第三个台阶跳一阶上来。那么从考虑第一种情况,前面两阶是不是就是只有两种方法。考虑第二种情况,前面的三个台阶是不是就是前面已经算出来的方式跳法个数了。因此,这就是一个斐波那契的变体问题。  代码如下:      //青蛙问题     public static void main(String[] args) {         System.out.print(fun8(1) + " ");         System.out.print(fun8(2) + " ");         System.out.print(fun8(3) + " ");         System.out.print(fun8(4) + " ");         System.out.print(fun8(5) + " ");         System.out.print(fun8(6) + " ");     }       public static int fun8 (int n) {         if (n == 1) {             return 1;         }         if (n == 2) {             return 2;         }         return fun8(n-1) +fun8(n-2);     }  运行结果如下:  ———————————————— 版权声明:本文为CSDN博主「小扳」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/Tingfeng__/article/details/134325079 
  • JAVA面向对象之封装&amp;多态
    一、封装性.1.1封装概述1、是面向对象三大特征之一(封装,继承,多态)2、是面向对象编程语言对现实世界的模拟,现实世界里成员变量和对象都是一个整体,归属者是对象,外界需要通过对象的同意才能对其进行相应操作1.2封装原则1.2.1.封装提高了数据的安全性      将类的某些信息隐藏在类的内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问            成员变量private,提供对应的getXxx()/setXxx()方法比如:public class student{        private string name;        public string getName(){        }               pub1ic void setName(string name)i           this.name=name;       }      private int age;        public int getAge(){       }          public void setAge(int age){              this.age=age;       } }1.2.2把代码用方法进行封装(可维护性和条理性)      把代码按照功能模块抽取成一个个方法,然后在main方法里调用就可以了,这样做,我们main方法里的代码实现了哪些功能一眼就能看出来,都是一个个功能的调用,没有冗余的代码,比较直观,方便阅读和后期的维护1.2.3隐藏了实现,操作简单(复用性)      在其他也有用到这个功能的地方,可以不用在写,直接调用方法,不用过于关注过程,达到了复用的效果,让事情变得更简单二、多态.2.1多态的概念多态:同一个事物,表现出来的多种形态:多态需要满足的前提条件:1、要有继承或实现关系2、要有方法的重写3、要有父类接收子类对象比如:public class Car {      public void weight(){            system.out.print1n("普通车重量...");       } } class Big extends Car { //重写父类的方法         pub1ic void weight(){             system. out.println("大车又高又大,而且重量.....");         } } class Small extends Car{ //重写父类的方法          public void weight({                 system. out.println("小车又小又快,而且重量.....");           } } /** 1、要有继承或实现关系 2、要有方法的重写 3、要有父类接收子类对象 **/ public class BigDemo {       public static void main(string[] args) {           //多态的表达形式:向上造型           Car c = new Big();        } }2.2 多态的好处与弊端好处: 提高代码的扩展性; 定义一个方法的时候,以父类作为参数,在调用时,传入子类进行 具体的操作;弊端:不能访问子类特有的方法;(可以通过类型强转来实现)2.3 多态的转型1、向上转型:参考多态的优点扩展---设计中的一个基本准则:里氏代换原则      在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,如果 一个软件实体使用的是一个子类对象的话,那么它不一定能够使用基类对象。      里氏代换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此在 程 序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。2、向下转型         子类 子类名称 = (子类)父类名称;
  • SSH 组合框架模式小知识分享
    SSH=Struts+Spring+Hibernate      集成SSH框架的系统从职责上分为四层:表示层、业务逻辑层、数据持久层和域模块层,以帮助开发人员在短期内搭建结构清晰、可复用性好、维护方便的Web应用程序。其中使用Struts作为系统的整体基础架构,负责MVC的分离,在Struts框架的模型部分,控制业务跳转,利用Hibernate框架对持久层提供支持,Spring做管理,管理struts和hibernate。      Struts 是一个很好的MVC框架,主要技术是Servlet和Jsp。Struts的MVC设计模式可以使我们的逻辑变得很清晰,让我们写的程序层次分明。基于Struts开发可以简化开发难度,提高开发效率。  Spring 提供了管理业务对象的一致方法,并鼓励注入对接口编程而不是对类编程的良好习惯,使我们的产品在最大程度上解耦。Hibernate 是用来持久化数据的,提供了完全面向对象的数据库操作。Hibernate对JDBC进行了非常轻量级的封装,它使得与关系型数据库打交道变得非常轻松。在Struts+Spring+Hibernate系统中,对象之间的调用流程如下: Struts——>Spring——>Hibernate  JSP——>Action——>Service——>DAO——>Hibernate比如:1.Spring的配置文件bean.xml<?xml version="1.0" encoding="UTF-8"?><beans         xmlns="http://www.springframework.org/schema/beans"         xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"         xmlns:tx="http://www.springframework.org/schema/tx">     <bean id="dataSource"           class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">         <property name="jdbcUrl"                   value="jdbc:mysql://localhost:3306/samblog?useUnicode=true&amp;characterEncoding=UTF-8&amp;autoReconnect=true">         </property>         <property name="user" value="root"></property>         <property name="password" value="123456"></property>         <property name="driverClass" value="org.gjt.mm.mysql.Driver"/>     </bean>     <bean id="sessionFactory"           class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">         <property name="dataSource">             <ref bean="dataSource"/>         </property>         <property name="hibernateProperties">             <value>                 hibernate.dialect=org.hibernate.dialect.MySQL5Dialect                 hibernate.hbm2ddl.auto=update                 hibernate.show_sql=false                 hibernate.format_sql=false            </value>         </property>         <property name="mappingResources">             <list>                 <value>site/sambloger/domain/Users.hbm.xml</value>                 <value>site/sambloger/domain/Blog.hbm.xml</value>                 <value>site/sambloger/domain/Category.hbm.xml</value>                 <value>site/sambloger/domain/Comment.hbm.xml</value>             </list>         </property>     </bean>     <bean id="transactionManager"           class="org.springframework.orm.hibernate5.HibernateTransactionManager">         <property name="sessionFactory" ref="sessionFactory"/>     </bean>     <tx:annotation-driven transaction-manager="transactionManager"/>     <!-- 配置Blog  spring进行管理  服务层直接调用实现与数据库的CRUD-->     <bean id="blogDao" class="site.sambloger.dao.impl.BlogDAOImpl">         <property name="sessionFactory" ref="sessionFactory"/>     </bean>     <bean id="blogService" class="site.sambloger.service.impl.BlogServiceImpl" scope="prototype">         <property name="blogDao" ref="blogDao"/>     </bean>     <bean id="blogAction" class="site.sambloger.action.BlogAction">         <property name="blogService" ref="blogService"/>         <property name="commentService" ref="commentService"/>     </bean>     <!-- 配置Comment -->     <bean id="commentDao" class="site.sambloger.dao.impl.CommentDAOImpl">         <property name="sessionFactory" ref="sessionFactory"/>     </bean>     <bean id="commentService" class="site.sambloger.service.impl.CommentServiceImpl" scope="prototype">         <property name="commentDao" ref="commentDao"/>     </bean>     <bean id="commentAction" class="site.sambloger.action.CommentAction">         <property name="commentService" ref="commentService"/>         <property name="blogService" ref="blogService"/>     </bean>     <!-- 配置Users -->     <bean id="usersDao" class="site.sambloger.dao.impl.UsersDAOImpl">         <property name="sessionFactory" ref="sessionFactory"></property>     </bean>     <bean id="usersService" class="site.sambloger.service.impl.UsersServiceImpl" scope="prototype">         <property name="usersDao" ref="usersDao"/>     </bean>     <bean id="usersAction" class="site.sambloger.action.UsersAction">         <property name="userService" ref="usersService"></property>     </bean></beans>2.Struts的配置文件 struts.xml<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.1//EN" "http://struts.apache.org/dtds/struts-2.1.dtd"><struts>     <package name="samblog" extends="struts-default" namespace="/">              <action name="init" class="blogAction" method="init">                     <result name="success">/bloglist.jsp</result>             </action>             <action name="getBlog" class="blogAction" method="getBlog">                     <result name="success">/displayBlog.jsp</result>             </action>              <action name="getAllNote" class="blogAction" method="getAllNote">                 <result name="success">/notelist.jsp</result>             </action>             <action name="addComment" class="commentAction" method="addComment">                 <result name="success"  type="redirect">/getBlog</result>             </action>     </package></struts>3.Hibernate其中的一个配置文件:<?xml version="1.0" encoding="utf-8"?><!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN""http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"><!--      Mapping file autogenerated by MyEclipse Persistence Tools--><hibernate-mapping>     <class name="site.sambloger.domain.Blog" table="blog">         <!--id标签表示映射到数据库中是作为主键 其他property表示普通键-->         <id name="id" type="java.lang.Integer">             <column name="id" />             <generator class="increment" />         </id><!--该标签加N方 会有一个字段叫category_id作为外键参照1(Category)的主键字段 并且用来存储这个主键的信息-->         <many-to-one name="category" class="site.sambloger.domain.Category"  lazy="false" cascade="all">             <column name="category_id" not-null="true" />         </many-to-one>         <property name="title" type="java.lang.String">             <column name="title" length="400" not-null="true" />         </property>         <property name="content" type="java.lang.String">             <column name="content" length="4000" not-null="true" />         </property>         <property name="createdTime" type="java.util.Date">             <column name="created_time" length="10" not-null="true" />         </property><!--在一对多的关联中,在一的一方(Blog)设置inverse=”true”让多的一方来维护关联关系更有助于优化,因为可以减少执行update语句-->         <set name="comments" inverse="true">             <key>                 <column name="blog_id" not-null="true" />             </key>             <one-to-many class="site.sambloger.domain.Comment" />         </set>     </class></hibernate-mapping>Spring框架的作用和好处:    Spring框架提供了一个容器,该容器可以管理应用程序的组件,还提供了IoC和AoP机制,实现组件之间解耦,提高程序结构的灵活性,增强系统的可维护和可扩展性。     在SSH整合开发中,利用Spring管理Service、DAO等组件,利用IoC机制实现Action和Service,Service和DAO之间低耦合调用。利用AoP机制实现事务管理、以及共通功能的切入等。     功能是整合,好处是解耦。Hibernate中操作并发处理(乐观锁和悲观锁)    Hibernate框架可以使用锁的机制来解决操作并发。    a.悲观锁         在数据查询出来时,就给数据加一个锁,锁定。这样其他用户再执行删、改操作时不允许。当占用着事务结束,锁会自动解除。          Hibernate采用的是数据库锁机制实现悲观锁控制。        缺点:将并发用户操作同步开,一个一个处理。当一个用户处理时间比较长时,效率会比较低。      b.乐观锁         允许同时更新提交,但是最快的会成功,慢的失败。         在记录中追加一个字段值,用该字段值当做版本。当最先提交者提交后,会自动将版本字段值提升,这样其他用户提交,会发现版本低于数据库记录目前版本,因此抛出异常提示失败。    特点:允许用户同时处理,但只能有一个成功,其他失败,以异常方式提示。SSH工作流程       a.启动服务器,加载工程以及web.xml.           (实例化Lisener,Filter等组件,将Spring容器和Struts2控制创建)       b.客户端发送请求,所有请求进入Struts2控制器。控制器根据请求类型不同,分别处理。           (action请求,*.action会进入struts.xml寻找<action>配置.            其他请求,*.jsp会直接调用请求资源,生成响应信息)       c.Struts2控制器根据<action>配置调用一个Action对象处理。         整合方法一:将Action交给Spring容器          (Action对象由struts2-spring-plugin.jar插件提供的                      StrutsSpringObjectFactory负责去Spring容器获取)         整合方法二:将Action置于Spring容器之外          (Action对象由struts2-spring-plugin.jar插件提供的                      StrutsSpringObjectFactory负责创建,然后到Spring容器中寻找与Action属性匹配的Bean对象,给Action对象注入。(默认采用名称匹配规则)       d.Struts2控制器执行defaultStack拦截器、Action对象、Result等组件处理.       e.执行Action的execute业务方法时,如果使用Service或DAO采用Spring的IoC机制调用。       f.执行Result生成响应信息,执行后续拦截器处理       g.将响应信息输出。
  • [技术干货] 如何动态更新SpringBoot中的yml文件
    前言在系统运行过程中,可能由于一些配置项的简单变动需要重新打包启停项目,这对于在运行中的项目会造成数据丢失,客户操作无响应等情况发生,针对这类情况对开发框架进行升级提供yml文件实时修改更新功能。项目依赖项目基于的是2.0.0.RELEASE版本,所以snakeyaml需要单独引入,高版本已包含在内。 codeduidaima.com<dependency><groupId>org.yaml</groupId><artifactId>snakeyaml</artifactId><version>1.23</version></dependency>网上大多数方法是引入spring-cloud-context配置组件调用ContextRefresher的refresh方法达到同样的效果,考虑以下两点未使用:1.开发框架使用了logback日志,引入spring-cloud-context会造成日志配置读取错误。2.引入spring-cloud-context会同时引入spring-boot-starter-actuator组件,会开放一些健康检查路由及端口,需要对框架安全方面进行额外控制。YML文件内容获取读取resource文件下的文件需要使用ClassPathResource获取InputStream。 codeduidaima.compublic String getTotalYamlFileContent() throws Exception {// 堆代码 duidaima.comString fileName = "application.yml";return getYamlFileContent(fileName);}public String getYamlFileContent(String fileName) throws Exception {ClassPathResource classPathResource = new ClassPathResource(fileName);return onvertStreamToString(classPathResource.getInputStream());}public static String convertStreamToString(InputStream inputStream) throws Exception{return IOUtils.toString(inputStream, "utf-8");}YML文件内容更新我们获取到yml文件内容后可视化显示到前台进行展示修改,将修改后的内容通过yaml.load方法转换成Map结构,再使用yaml.dumpAsMap转换为流写入到文件。 codeduidaima.compublic void updateTotalYamlFileContent(String content) throws Exception {String fileName = "application.yml";updateYamlFileContent(fileName, content);}public void updateYamlFileContent(String fileName, String content) throws Exception {Yaml template = new Yaml();Map<String, Object> yamlMap = template.load(content);ClassPathResource classPathResource = new ClassPathResource(fileName);Yaml yaml = new Yaml();//字符输出FileWriter fileWriter = new FileWriter(classPathResource.getFile());//用yaml方法把map结构格式化为yaml文件结构fileWriter.write(yaml.dumpAsMap(yamlMap));//刷新fileWriter.flush();//关闭流fileWriter.close();}YML属性刷新yml属性在程序中读取使用一般有三种:1.使用Value注解 codeduidaima.com@Value("${system.systemName}")private String systemName;2.通过enviroment注入读取 codeduidaima.com@Autowiredprivate Environment environment;environment.getProperty("system.systemName")3.使用ConfigurationProperties注解读取 codeduidaima.com@Component@ConfigurationProperties(prefix = "system")public class SystemConfig {private String systemName;}Property刷新我们通过environment.getProperty方法读取的配置集合实际是存储在PropertySources中的,我们只需要把键值对全部取出存储在propertyMap中,将更新后的yml文件内容转换成相同格式的ymlMap,两个Map进行合并,调用PropertySources的replace方法进行整体替换即可。但是yaml.load后的ymlMap和PropertySources取出的propertyMap两者数据解构是不同的,需要进行手动转换。propertyMap集合就是单纯的key,value键值对,key是properties形式的名称,例如system.systemName=>xxxxx集团管理系统。ymlMap集合是key,LinkedHashMap的嵌套层次结构,例如system=>(systemName=>xxxxx集团管理系统)。转换方法如下: codeduidaima.compublic HashMap<String, Object> convertYmlMapToPropertyMap(Map<String, Object> yamlMap) {HashMap<String, Object> propertyMap = new HashMap<String, Object>();for (String key : yamlMap.keySet()) {String keyName = key;Object value = yamlMap.get(key);if (value != null && value.getClass() == LinkedHashMap.class) {convertYmlMapToPropertyMapSub(keyName, ((LinkedHashMap<String, Object>) value), propertyMap);} else {propertyMap.put(keyName, value);}}return propertyMap;}private void convertYmlMapToPropertyMapSub(String keyName, LinkedHashMap<String, Object> submMap, Map<String, Object> propertyMap) {for (String key : submMap.keySet()) {String newKey = keyName + "." + key;Object value = submMap.get(key);if (value != null && value.getClass() == LinkedHashMap.class) {convertYmlMapToPropertyMapSub(newKey, ((LinkedHashMap<String, Object>) value), propertyMap);} else {propertyMap.put(newKey, value);}}}刷新方法如下 codeduidaima.comString name = "applicationConfig: [classpath:/" + fileName + "]";MapPropertySource propertySource = (MapPropertySource) environment.getPropertySources().get(name);Map<String, Object> source = propertySource.getSource();Map<String, Object> map = new HashMap<>(source.size());map.putAll(source);Map<String, Object> propertyMap = convertYmlMapToPropertyMap(yamlMap);for (String key : propertyMap.keySet()) {Object value = propertyMap.get(key);map.put(key, value);}environment.getPropertySources().replace(name, new MapPropertySource(name, map));注解刷新不论是Value注解还是ConfigurationProperties注解,实际都是通过注入Bean对象的属性方法使用的,我们先自定注解RefreshValue来修饰属性所在Bean的class。通过实现InstantiationAwareBeanPostProcessorAdapter接口在系统启动时过滤筛选对应的Bean存储下来,在更新yml文件时通过spring的event通知更新对应bean的属性即可。注册事件使用EventListener注解: codeduidaima.com@EventListenerpublic void updateConfig(ConfigUpdateEvent configUpdateEvent) {if(mapper.containsKey(configUpdateEvent.key)){List<FieldPair> fieldPairList = mapper.get(configUpdateEvent.key);if(fieldPairList.size()>0){for (FieldPair fieldPair:fieldPairList) {fieldPair.updateValue(environment);}}}}通知触发事件使用ApplicationContext的publishEvent方法: codeduidaima.com@Autowiredprivate ApplicationContext applicationContext;for (String key : propertyMap.keySet()) {applicationContext.publishEvent(new YamlConfigRefreshPostProcessor.ConfigUpdateEvent(this, key));}YamlConfigRefreshPostProcessor的完整代码如下: codeduidaima.com@Componentpublic class YamlConfigRefreshPostProcessor extends InstantiationAwareBeanPostProcessorAdapter implements EnvironmentAware {private Map<String, List<FieldPair>> mapper = new HashMap<>();private Environment environment;@Overridepublic boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {processMetaValue(bean);return super.postProcessAfterInstantiation(bean, beanName);}@Overridepublic void setEnvironment(Environment environment) {this.environment = environment;}private void processMetaValue(Object bean) {Class clz = bean.getClass();if (!clz.isAnnotationPresent(RefreshValue.class)) {return;}if (clz.isAnnotationPresent(ConfigurationProperties.class)) {//@ConfigurationProperties注解ConfigurationProperties config = (ConfigurationProperties) clz.getAnnotation(ConfigurationProperties.class);for (Field field : clz.getDeclaredFields()) {String key = config.prefix() + "." + field.getName();if(mapper.containsKey(key)){mapper.get(key).add(new FieldPair(bean, field, key));}else{List<FieldPair> fieldPairList = new ArrayList<>();fieldPairList.add(new FieldPair(bean, field, key));mapper.put(key, fieldPairList);}}} else {//@Valuez注解try {for (Field field : clz.getDeclaredFields()) {if (field.isAnnotationPresent(Value.class)) {Value val = field.getAnnotation(Value.class);String key = val.value().replace("${", "").replace("}", "");if(mapper.containsKey(key)){mapper.get(key).add(new FieldPair(bean, field, key));}else{List<FieldPair> fieldPairList = new ArrayList<>();fieldPairList.add(new FieldPair(bean, field, key));mapper.put(key, fieldPairList);}}}} catch (Exception e) {e.printStackTrace();System.exit(-1);}}}public static class FieldPair {private static PropertyPlaceholderHelper propertyPlaceholderHelper = new PropertyPlaceholderHelper("${", "}",":", true);private Object bean;private Field field;private String value;public FieldPair(Object bean, Field field, String value) {this.bean = bean;this.field = field;this.value = value;}public void updateValue(Environment environment) {boolean access = field.isAccessible();if (!access) {field.setAccessible(true);}try {if (field.getType() == String.class) {String updateVal = environment.getProperty(value);field.set(bean, updateVal);}else if (field.getType() == Integer.class) {Integer updateVal = environment.getProperty(value,Integer.class);field.set(bean, updateVal);}else if (field.getType() == int.class) {int updateVal = environment.getProperty(value,int.class);field.set(bean, updateVal);}else if (field.getType() == Boolean.class) {Boolean updateVal = environment.getProperty(value,Boolean.class);field.set(bean, updateVal);}else if (field.getType() == boolean.class) {boolean updateVal = environment.getProperty(value,boolean.class);field.set(bean, updateVal);}else {String updateVal = environment.getProperty(value);field.set(bean, JSONObject.parseObject(updateVal, field.getType()));}} catch (IllegalAccessException e) {e.printStackTrace();}field.setAccessible(access);}public Object getBean() {return bean;}public void setBean(Object bean) {this.bean = bean;}public Field getField() {return field;}public void setField(Field field) {this.field = field;}public String getValue() {return value;}public void setValue(String value) {this.value = value;}}public static class ConfigUpdateEvent extends ApplicationEvent {String key;public ConfigUpdateEvent(Object source, String key) {super(source);this.key = key;}}@EventListenerpublic void updateConfig(ConfigUpdateEvent configUpdateEvent) {if(mapper.containsKey(configUpdateEvent.key)){List<FieldPair> fieldPairList = mapper.get(configUpdateEvent.key);if(fieldPairList.size()>0){for (FieldPair fieldPair:fieldPairList) {fieldPair.updateValue(environment);}}}}}转载自cid:link_0Group/Topic/JAVA/18012
  • [技术干货] 如何使用Spring Validation实现数据校验功能
    背景Spring 框架,广泛应用于 JAVA 企业级开发中,包含了一套实用的字段校验机制: Spring Validation。这个机制融合了 JSR380 规范,即 Bean Validation 2.0。本文将介绍 Spring Validation 的使用方法,包括基础注解的应用以及进阶使用技巧。常用注解Bean Validation 2.0 注解校验空值@Null:验证对象是否为 null@NotNull:验证对象是否不为 null@NotEmpty:验证对象不为 null,且长度(数组、集合、字符串等)大于 0@NotBlank:验证字符串不为 null,且去除两端空白字符后长度大于 0校验大小@Size(min=, max=):验证对象(数组、集合、字符串等)长度是否在给定的范围之内@Min(value):验证数值(整数或浮点数)是否大于等于指定的最小值@Max(value):验证数值是否小于等于指定的最大值校验布尔值@AssertTrue:验证 Boolean 对象是否为 true@AssertFalse:验证 Boolean 对象是否为 false校验日期和时间@Past:验证 Date 和 Calendar 对象是否在当前时间之前@Future:验证 Date 和 Calendar 对象是否在当前时间之后@PastOrPresent:验证日期是否是过去或现在的时间@FutureOrPresent:验证日期是否是现在或将来的时间正则表达式@Pattern(regexp=, flags=):验证 String 对象是否符合正则表达式的规则Hibernate Validation 拓展@Length(min=, max=):验证字符串的大小是否在指定的范围内@Range(min=, max=):验证数值是否在合适的范围内@UniqueElements:校验集合中的值是否唯一,依赖于 equals 方法@ScriptAssert:利用脚本进行校验@Valid 和 @Validated这两个注解是校验的入口,作用相似但用法上存在差异。  codeduidaima.com@Validated// 用于类/接口/枚举,方法以及参数@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Validated {// 校验时启动的分组Class<?>[] value() default {};}@Valid// 用于方法,字段,构造函数,参数,以及泛型类型@Target({ METHOD, FIELD, CONSTRUCTOR, PARAMETER, TYPE_USE })@Retention(RUNTIME)@Documentedpublic @interface Valid {// 未提供其他属性}作用范围不同:@Validated 无法作用在于字段, @Valid 无法作用于类;注解中的属性不同:@Validated 中提供了指定校验分组的属性,而 @Valid 没有这个功能,因为 @Valid 不能进行分组校验。字段校验场景及使用示例常见的校验场景有三种: Controller 层的校验、编程式校验、 Dubbo 接口校验。Controller层 的校验使用方式当方法入参为 @RequestBody 注解的 JavaBean,可在入参前使用 @Validated 或 @Valid 注解开启校验。 codeduidaima.com@PostMapping("/save")public Response<Boolean> saveNotice(@Validated @RequestBody NoticeDTO noticeDTO) {// noticeDTO中各字段校验通过,才会执行后续业务逻辑return Response.ok(true);}当方法入参为 @PathVariable、 @RequestParam 注解的简单参数时,需要在 Controller 加上 @Validated 注解开启校验。 codeduidaima.com@RequestMapping("/notice")@RestController// 必须加上该注解@Validatedpublic class UserController {// 路径变量@GetMapping("{id}")public Reponse<NoticeDTO> detail(@PathVariable("id") @Min(1L) Long noticeId) {// 参数noticeId校验通过,执行后续业务逻辑return Reponse.ok();}// 请求参数@GetMapping("getByTitle")public Result getByTitle(@RequestParam("title") @Length(min = 1, max = 20) String title) {// 参数title校验通过,执行后续业务逻辑return Result.ok();}}原理Spring 框架中的 HandlerMethodArgumentResolver 策略接口,负责将方法参数解析为特定请求中的参数值。 codeduidaima.compublic interface HandlerMethodArgumentResolver {// 判断当前解析器是否支持给定的方法参数boolean supportsParameter(MethodParameter var1);// 堆代码 duidaima.com@Nullable// 实际解析参数的方法Object resolveArgument(MethodParameter var1, @Nullable ModelAndViewContainer var2, NativeWebRequest var3, @Nullable WebDataBinderFactory var4) throws Exception;}上述接口针对 @RequestBody 的实现类 RequestResponseBodyMethodProcessor 中,存在字段校验逻辑,调用 validateIfApplicable 方法校验参数。// RequestResponseBodyMethodProcessorpublic Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {// 前置处理// 校验逻辑if (binderFactory != null) {WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);if (arg != null) {//调用校验函数this.validateIfApplicable(binder, parameter);if (binder.getBindingResult().hasErrors() && this.isBindExceptionRequired(binder, parameter)) {throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());}}// 数据绑定逻辑}// 返回处理结果return this.adaptArgumentIfNecessary(arg, parameter);}validateIfApplicable 方法中,根据方法参数上的注解,决定是否进行字段校验:当存在 @Validated 或以 Valid 开头的注解时,进行校验。 codeduidaima.comprotected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {// 获取参数上的注解Annotation[] annotations = parameter.getParameterAnnotations();Annotation[] var4 = annotations;int var5 = annotations.length;// 遍历注解for(int var6 = 0; var6 < var5; ++var6) {Annotation ann = var4[var6];// 获取 @Validated 注解Validated validatedAnn = (Validated)AnnotationUtils.getAnnotation(ann, Validated.class);// 或者注解以 Valid 开头if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {// 开启校验Object hints = validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann);Object[] validationHints = hints instanceof Object[] ? (Object[])((Object[])hints) : new Object[]{hints};binder.validate(validationHints);break;}}}@PathVariable 和 @RequestParam 对应的实现类中,则没有相应字段校验逻辑,因此需要在 Controller 上使用 @Validated,开启字段校验。编程式校验1.配置 Validator codeduidaima.com@Configurationpublic class ValidatorConfiguration {@Beanpublic Validator validator() {ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class).configure()// 设置是否开启快速失败模式//.failFast(true).buildValidatorFactory();return validatorFactory.getValidator();}}2.获取 validator 并校验 codeduidaima.compublic class TestValidator {// 注入验证器@Resourceprivate javax.validation.Validator validator;public String testMethod(TestRequest request) {// 进行校验,获取校验结果Set<ConstraintViolation<TestRequest>> constraintViolations = validator.validate(request);// 组装校验信息并返回return res;}}Dubbo 接口校验可在 @DubboService 注解中,设置 validation 参数为 true,开启生产者的字段验证。 codeduidaima.com@DubboService(version = "1.0.0", validation="true")public class DubboApiImpl implements DubboApi {....}该方式返回的信息对使用者不友好,可通过 Dubbo 的 filter 自定义校验逻辑和返回信息。需要注意的是,在 Dubbo 中有自己的 IOC 实现来控制容器,因此需提供 setter 方法,供 Dubbo 调用。 codeduidaima.com@Activate(group = {"provider"},value = {"customValidationFilter"},order = 10000)@Slf4jpublic class CustomValidationFilter implements Filter {private javax.validation.Validator validator;// duubo会调用setter获取beanpublic void setValidator(javax.validation.Validator validator) {this.validator = validator;}public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {if (this.validator != null && !invocation.getMethodName().startsWith("$")) {// 补充字段校验,返回信息的组装以及异常处理}return invoker.invoke(invocation);}}进阶使用分组校验对于同一个 DTO, 不同场景下对其校验规则可能不同, @Validted 支持按照分组分别验证,示例代码如下:1.校验注解的 groups 属性中添加分组 codeduidaima.com@Datapublic class NoticeDTO {@Min(value = 0L, groups = Update.class)private Long id;@NotNull(groups = {Save.class, Update.class})@Length(min = 2, max = 10, groups = {Save.class, Update.class})private String title;// 保存的时候校验分组public interface Save {}// 更新的时候校验分组public interface Update {}}2.@Validted 上指定分组 codeduidaima.com@PostMapping("/save")public Response<Boolean> saveNotice(@RequestBody @Validated(NoticeDTO.Save.class) NoticeDTO noticeDTO) {// 分组为Save.class的校验通过,执行后续逻辑return Response.ok();}@PostMapping("/update")public Response<Boolean> updateNotice(@RequestBody @Validated(NoticeDTO.Update.class) NoticeDTO noticeDTO) {// 分组为Update.class的校验通过,执行后续逻辑return Response.ok();}自定义校验注解如果我们想自定义实现一些验证逻辑,可以使用自定义注解,主要包括两部分:实现自定义注解,实现对应的校验器 validator。下面尝试实现一个注解,用于校验集合中的指定属性是否存在重复,代码如下:实现校验注解,主要需要包含 message()、 filed()、 groups() 三个方法,功能如注释所示。 codeduidaima.com@Target({ElementType.FIELD, ElementType.PARAMETER})@Retention(RetentionPolicy.RUNTIME)@Documented// 指定校验器@Constraint(validatedBy = UniqueValidator.class)public @interface Unique {// 用于自定义验证信息String message() default "字段存在重复";// 指定集合中的待校验字段String[] field();// 指定分组Class<?>[] groups() default {};}实现对应的校验器,主要校验逻辑在 isValid 方法:获取集合中指定字段,并组装为 set,比较 set 和集合的长度,以判断集合中指定字段是否存在重复。 codeduidaima.com// 实现ConstraintValidator<T, R>接口,T为注解的类型,R为注解的字段类型public class UniqueValidator implements ConstraintValidator<Unique, Collection<?>> {private Unique unique;@Overridepublic void initialize(Unique constraintAnnotation) {this.unique = constraintAnnotation;}@Overridepublic boolean isValid(Collection collection, ConstraintValidatorContext constraintValidatorContext) {// 集合为空直接校验通过if (collection == null || collection.size() == 0) {return Boolean.TRUE;}// 从集合中获取filed中指定的待校验字段,看是否存在重复return Arrays.stream(unique.field()).filter(fieldName -> fieldName != null && !"".equals(fieldName.trim())).allMatch(fieldName -> {// 收集集合collection中字段为fieldName的值,存入set并计算set的元素个数countint count = (int) collection.stream().filter(Objects::nonNull).map(item -> {Class<?> clazz = item.getClass();Field field;try {field = clazz.getField(fieldName);field.setAccessible(true);return field.get(item);} catch (Exception e) {return null;}}).collect(Collectors.collectingAndThen(Collectors.toSet(), Set::size));// set中元素个数count与集合长度比较,若不相等则说明collection中字段存在重复,校验不通过if (count != collection.size()) {return false;}return true;});}}总结通过本文我们得以了解 Spring Validation 的机理及其在实际项目中的应用。无论是标准的校验注解,还是自定义的校验逻辑, Spring Validation 都为开发者提供了高效且强大的校验工具。总的来说, Spring Validation 是任何 Spring 应用不可或缺的一部分,对于追求高质量代码的 JAVA 开发者而言,掌握其用法和最佳实践至关重要。转载自cid:link_0Group/Topic/JAVA/18022
  • [技术干货] 前端如何发送date类型的参数给后端
    @DateTimeFormat第一次:Get方式传参-成功 这个时候是用的get请求方式,get是把参数数据队列加到提交表单的ACTION属性所指的URL中,值和表单内各个字段一一对应。/** * http://localhost:8080/intoParam?date=2023-01-18 11:11:11*/ @RequestMapping(value = "/intoParam",method = RequestMethod.GET) @ResponseBody public void intoParam(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date date){ System.out.println(date);//Fri Jan 18 08:00:00 CST 2023 }第二次:Post方式传参-失败/** * http://localhost:8080/intoParam * 请求体 * { * "date":"2023-01-18 11:11:11" * } */ @RequestMapping(value = "/intoParam",method = RequestMethod.POST) @ResponseBody public void intoParam2(@RequestBody DateVo dateVo){ System.out.println(dateVo.getDate());//Fri Jan 18 08:00:00 CST 2023 }@Data@AllArgsConstructor@NoArgsConstructorpublic class DateVo { @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss") private Date date;}错误信息{ "timestamp": "2023-10-19T07:05:22.407+0000", "status": 400, "error": "Bad Request", "message": "JSON parse error: Cannot deserialize value of type `java.util.Date` from String \"2023-01-18 11:11:11\": not a valid representation (error: Failed to parse Date value '2023-01-18 11:11:11': Cannot parse date \"2023-01-18 11:11:11\": while it seems to fit format 'yyyy-MM-dd'T'HH:mm:ss.SSSZ', parsing fails (leniency? null)); nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `java.util.Date` from String \"2023-01-18 11:11:11\": not a valid representation (error: Failed to parse Date value '2023-01-18 11:11:11': Cannot parse date \"2023-01-18 11:11:11\": while it seems to fit format 'yyyy-MM-dd'T'HH:mm:ss.SSSZ', parsing fails (leniency? null))\n at [Source: (PushbackInputStream); line: 2, column: 12] (through reference chain: com.mye.hl20springbootdataparam.vo.DateVo[\"date\"])", "path": "/intoParam"}第三次:post传参-成功/** * http://localhost:8080/intoParam * 請求體是: * { * "date":"2023-01-18" * } */ @RequestMapping(value = "/intoParam",method = RequestMethod.POST) @ResponseBody public void intoParam2(@RequestBody DateVo dateVo){ System.out.println(dateVo.getDate());//Fri Jan 18 08:00:00 CST 2023 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); String format = sdf.format(dateVo.getDate()); System.out.println(format);//2023-01-18 }@Data@AllArgsConstructor@NoArgsConstructorpublic class DateVo { @DateTimeFormat(pattern="yyyy-MM-dd") private Date date;}原因springboot默认采用jackson,而jackson只能识别以下几种日期格式"yyyy-MM-dd'T'HH:mm:ss.SSSZ";"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";"yyyy-MM-dd"; "EEE, dd MMM yyyy HH:mm:ss zzz"; long类型的时间戳(毫秒时间戳)解决方法采用long时间戳,如:1537191968000或者在传参的对象上加上@JsonFormat注解并且指定时区。@JsonFormat(locale="zh", timezone="GMT+8", pattern="yyyy-MM-dd HH:mm:ss")第四次:post传参-成功/** * http://localhost:8080/intoParam * 請求體是: * { * "date":"2019-01-18 11:11:11" * } */ @RequestMapping(value = "/intoParam",method = RequestMethod.POST) @ResponseBody public void intoParam2(@RequestBody DateVo dateVo){ System.out.println(dateVo.getDate());//Fri Jan 18 11:11:11 CST 2019 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); String format = sdf.format(dateVo.getDate()); System.out.println(format);//2019-01-18 11:11:11 }@Data@AllArgsConstructor@NoArgsConstructorpublic class DateVo { @DateTimeFormat(pattern="yyyy-MM-dd") @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") private Date date;}@jsonFormat@JsonFormat(pattern=“yyyy-MM-dd”,timezone = “GMT+8”)pattern:是你需要转换的时间日期的格式timezone:是时间设置为东八区,避免时间在转换中有误差@JsonFormat注解可以在属性的上方,同样可以在属性对应的get方法上,两种方式没有区别展示结果{ "date": "2023-10-19 15:44:51"}总结 前端Content-Type 为application/json的请求时,我们使用@JsonFormat来进行转化,如果为表单,则应该使用@DateTimeFormat。转载自https://www.duidaima.com/Group/Topic/JAVA/18030
  • [技术干货] cpu和内存的查看命令
    查看CPU相关命令#限制某个线程的cpu使用率sudo cpulimit -p pid -l 50ps -eo %cpu,args | grep -m1 PROCESS | awk '{print $1}'#将当前进程按照memory和cpu排序ps aux --sort=%mem,%cpu#按照cpu使用率排序ps -e -o pcpu,cpu,nice,state,cputime,args --sort pcpu | sed "/^ 0.0 /d"#查看当前系统的物理cpu个数grep "processor" /proc/cpuinfo | wc -l grep -c -e '^cpu[0-9]\+' /proc/stat#查看当前cpu型号grep "model name" /proc/cpuinfo#查看当前cpu信息cat /proc/cpuinfo#查看当前系统的位数grep -q '\<lm\>' /proc/cpuinfo && echo 64 bits || echo 32 bitsgetconf LONG_BIT | grep '64'java -version#查看当前系统的cpu频率awk -F": " '/cpu MHz\ */ { print "Processor (or core) running speed is: " $2 }' /proc/cpuinfo ; dmidecode | awk -F": " '/Current Speed/ { print "Processor real speed is: " $2 }'#查看每个cpu每个进程的cpu使用率ps ax -L -o pid,tid,psr,pcpu,args | sort -nr -k4| head -15 | cut -c 1-90#查看当前中断cat /proc/interrupts#查看多个处理器的使用率相关信息mpstat –P ALL 1#每个物理CPU中Core的个数:cat /proc/cpuinfo | grep "cpu cores" | uniq | awk -F: '{print $2}'#是否为超线程?#如果有两个逻辑CPU具有相同的”core id”,那么超线程是打开的。#每个物理CPU中逻辑CPU(可能是core, threads或both)的个数:cat /proc/cpuinfo | grep "siblings"#/proc/stat 文件中有一行记录的机器从启动依赖,各个中断序号发生中断的次数。#这一行以intr开头,接下来的第一个数字是总的中断数目,之后就是分别的中断数目,从0开始。cat /proc/stat | grep intr查找文件相关命令#按照目录大小排序战士最前面15个目录或者文件du -xB M --max-depth=2 /var | sort -rn | head -n 15#列出当前所有子目录的文件大小du -h --max-depth=1#列出当前文件或者目录最大的10个du -s * | sort -n | tail#按照目录大小从大到小排序du -b --max-depth 1 | sort -nr | perl -pe 's{([0-9]+)}{sprintf "%.1f%s", $1>=2**30? ($1/2**30, "G"): $1>=2**20? ($1/2**20, "M"): $1>=2**10? ($1/2**10, "K"): ($1, "")}e'#列出path这个目录的文件树du -h /path | sort -h#每隔60s监控对应目录的文件大小变化watch -n60 du /var/log/messages#递归删除当前目录下所有子目录中的.svn目录find . -type d -name '.svn' -print0 | xargs -0 rm -rdf#列出当前磁盘的使用情况df -P | column -t#监控磁盘的使用情况watch -d -n 5 df#列出当前inode的使用情况df -i <partition>#按照每个磁盘使用量从高到低排序df -h | grep -v ^none | ( read header ; echo "$header" ; sort -rn -k 5)#查看物理磁盘的使用情况df -x tmpfs | grep -vE "(gvfs|procbususb|rootfs)"#查看当前所有磁盘的大小和使用量df -H#查看所有分区使用情况fdisk -l /dev/sda# 显示系统所有的分区或给定的分区fdisk -l # 显示时,显示的是扇区数不是柱面数 fdisk -u # 显示指定partition的block数 fdisk -s partition #查看磁盘的读写容量iostat -m -d /dev/sda1#测试磁盘的读写速度hdparm -t /dev/sda#查看某个文件的所有链接find -L / -samefile /path/to/file -exec ls -ld {} +#查看最大的5个文件find . -type f -exec ls -s {} \; | sort -n -r | head -5#查看365天前的文件并删除find ./ -type f -mtime +365 -exec rm -f {} \;#查看大于100M的文件find . -type f -size +100M
  • [技术干货] 几种常见工具的功能对比和应用场景
    Flume是一种可靠、可扩展、分布式的日志收集、聚合和传输系统。它主要用于以下几个应用场景:1、日志收集:Flume能够从多个源头(例如服务器、应用程序、设备等)收集大量的日志数据,并将其中转到集中式存储系统(如Hadoop HDFS)或消息队列系统(如Kafka)中。这种集中式收集机制可以帮助企业对日志数据进行集中管理和分析。2、数据聚合:Flume可以将多个源头的数据聚合到一起,并将其传输到统一的目标系统。例如,在分布式计算中,可以使用Flume将不同节点上的数据聚合并传输到计算节点或结果节点。3、流式数据传输:Flume支持实时的、可靠的数据传输,因此被广泛用于构建数据流水线,以实现流数据的实时处理和分析。4、网络日志分析:Flume可以用于抓取并分析网络设备、服务器和应用程序产生的日志数据。通过配置适当的源和目标,Flume能够将日志数据从各个设备中收集、存储并进行分析。DataX CDC(Change Data Capture)的主要应用场景有以下几个:1、数据同步:将源数据库中的数据变更(增、删、改)实时同步到目标数据库,确保两个数据库中的数据保持一致。2、数据仓库加载:将源数据库中的增量数据加载到数据仓库或数据湖中,用于数据分析、报表生成等业务需求。3、数据备份与恢复:通过记录数据变更,实时备份源数据库中的增量数据,以防止数据库故障或数据丢失,同时可以快速恢复到指定的时间点。4、实时数据分析:将源数据库中的增量数据实时传输给大数据平台,用于实时数据分析、实时监控等业务场景。5、数据集成和ETL:将多个数据源中的数据变更集成到一个目标数据库中,实现数据的统一管理和分析。需要注意的是,DataX CDC是一款开源数据同步工具,能够实现增量数据的抽取和传输,但并不负责处理数据转换和清洗的任务。Flink CDC(Change Data Capture)的主要应用场景有以下几个:1、实时数据分析:Flink CDC可以将源数据库中的增量数据实时传输到Flink流处理引擎中,使得实时数据分析和实时计算成为可能。通过实时处理增量数据,可以进行实时指标计算、实时报表生成、实时业务监控等。2、数据协同与集成:Flink CDC可以将多个不同数据源的增量数据集成到一个目标系统中,实现不同数据源之间的数据协同和集成。比如将多个数据库中的数据变更实时同步到数据仓库或数据湖,以实现统一的数据分析和报表生成。3、数据流转与传输:Flink CDC可以将源数据库中的增量数据转换成数据流,并实时传输到指定的目标位置。这在数据实时传输和数据交换场景中非常有用,比如将数据传输到消息队列、实时推送数据给其他系统等。4、实时数据湖建设:通过Flink CDC将源数据库中的增量数据实时写入数据湖中,可以建立起一个实时的数据湖,进而支持实时分析、实时机器学习等高价值数据应用。5、实时数据缓存和缓冲:Flink CDC可以实时捕获和缓冲源数据库中的增量数据,并加快其它系统对数据的访问速度,减少对数据库的直接查询和压力。Kettle(也称为Pentaho Data Integration)是一个开源的ETL工具,主要用于数据抽取(Extract)、转换(Transform)和加载(Load)操作。下面是Kettle主要的应用场景:1、数据同步:Kettle可以将不同数据源(如关系型数据库、文件、Web服务等)中的数据进行抽取和同步,实现数据的一致性和更新。2、数据仓库加载:Kettle可以将数据从各种数据源加载到数据仓库(如数据仓库、数据湖等),并进行数据清洗、转换和映射操作,以支持后续的分析和报告需求。3、实时数据分析:Kettle可以实时抽取和转换数据,将其加载到实时数据分析平台中,实现实时监控、实时分析和实时决策。4、数据质量管理:Kettle提供了各种数据清洗和校验的功能,可以帮助用户识别和修复数据质量问题,提高数据的准确性和完整性。  以下是相关同步工具的功能对比:转载自https://www.studyjava.cn/post/2029
  • [技术干货] Kettle 的工作原理和优势
    Kettle,也被称为Pentaho Data Integration(PDI),是一款开源的ETL工具,用于提取、转换和加载数据。它的工作原理可以简单概括为以下几个步骤:1、设计和配置:在Kettle的图形化界面中,通过拖拽和连接各个组件,设计数据转换和加载的流程。每个组件代表不同的任务,如数据抽取、数据清洗、数据转换、数据加载等。为每个组件配置相应的参数,定义数据输入输出的连接和转换规则。2、读取数据:Kettle支持多种数据来源,如关系型数据库、文件、Web服务等。根据配置的数据源和查询条件,Kettle会读取数据源中的数据。3、数据转换和清洗:通过在转换和清洗组件中定义规则和函数,对读取的数据进行处理和转换。可以进行各种操作,如过滤、排序、合并、聚合、计算等,以满足数据处理的需求。4、加载数据:将经过转换和清洗的数据加载到目标数据源中。可以是关系型数据库、文件、数据仓库等。Kettle提供了各种输出组件,用于将数据写入到目标数据源中。5、执行和调度:完成数据处理流程的配置后,可以手动执行该流程。也可以使用Kettle内置的调度器,按照预定的时间和频率自动运行任务。总的来说,Kettle通过可视化的方式,串联和配置多个数据处理和转换组件,实现数据的提取、转换和加载。它具有强大的扩展性和灵活性,能够满足各种数据集成和处理的需求。优势:1、强大的可视化设计和配置:Kettle提供了一个易于使用的图形化界面,使用户可以轻松设计和配置ETL工作流程,而无需编写复杂的代码。2、多种数据源和目标的支持:Kettle可以与各种数据源(例如数据库、文件、Web服务等)以及数据目标(例如数据库、文件、Data Warehouse等)进行无缝集成,方便数据的抽取和加载。3、强大的数据转换和清洗功能:Kettle提供了丰富的数据转换和清洗步骤,如字段映射、数据过滤、数据合并、数据拆分、数据排序等,可以灵活地处理和转换数据。4、插件扩展机制:Kettle支持插件机制,用户可以通过自定义插件扩展Kettle的功能,满足特定需求。5、支持任务调度和并行处理:Kettle可以根据需求进行任务调度和并行处理,提高ETL工作的效率和可靠性。劣势:1、学习曲线较陡峭:尽管Kettle提供了图形化界面,但对于没有经验的用户来说,学习和掌握Kettle的各种功能和操作仍然需要一定的时间和精力。2、对大规模数据处理的限制:由于Kettle是基于Java开发的,对于大规模数据的处理和性能可能存在一些限制,特别是在并行处理和集群环境下。3、对于复杂数据处理场景的限制:尽管Kettle提供了丰富的数据转换和清洗步骤,但在处理复杂数据处理场景时,可能需要编写自定义的脚本或插件来实现。Kettle 支持的实时采集数据源类型:转载自https://www.studyjava.cn/post/2029
  • [技术干货] DataX CDC的工作原理和优势
    DataX CDC基于DataX框架,为用户提供了一种灵活、高效的数据同步解决方案。它通过监视源数据库的事务日志或数据库增量日志来捕获源数据库中的变更操作,并将这些操作应用于目标数据库,以保持两者之间的数据同步。这种增量方式可以大大减少数据传输的时间和成本,并提供更及时的数据更新。DataX CDC 是基于 CDC(Change Data Capture)技术实现的数据同步工具,其工作原理如下:1、数据源监控:DataX CDC 首先会监控数据源(如 Oracle 数据库)的事务日志。事务日志是数据库记录每个操作(如插入、更新、删除)的日志文件。2、数据解析:一旦有新的事务日志生成,DataX CDC 会解析事务日志,提取出新增、更新和删除的数据。3、数据同步:经过解析后,DataX CDC 将提取到的数据进行转换和转发,将其同步到目标数据源(如 HDFS、MySQL 等)。4、数据应用:目标数据源接收到同步的数据后,可以被应用程序直接使用或者进行进一步的处理和分析。具体举例来说,DataX CDC 监控 Oracle 事务日志的步骤如下:1、配置数据库连接:首先,需要在 DataX CDC 中配置 Oracle 数据库的连接信息,包括数据库地址、用户名、密码等。2、开启 CDC 日志模式:在 Oracle 数据库中,需要将数据库的日志模式设置为 CDC 日志模式。这可以通过在 Oracle 数据库中执行相应的 SQL 命令来完成,例如执行 ALTER DATABASE ADD SUPPLEMENTAL LOG DATA 命令。3、配置 Change Data Capture:在 DataX CDC 中,需要配置相应的 Change Data Capture 模块,以便能够捕获 Oracle 数据库中的变更事件。这包括指定表的 CDC 规则、列的映射关系等。4、监控事务日志:一旦配置完成,DataX CDC 将自动监控 Oracle 数据库的事务日志,捕获其中的数据变更事件。5、数据同步:捕获到的数据变更事件将会被传递给目标系统或者工具,以完成数据同步操作。注意,在使用 DataX CDC 监控 Oracle 事务日志时,需要确保数据库的参数正确配置,并且有足够的权限执行相关的 SQL 命令。此外,由于监控事务日志可能会带来额外的性能开销,因此需要根据实际情况进行调整和优化。优势:1、实时同步:DataX CDC 可以实时监控和同步数据源的变更,能够及时将数据更新到目标数据源,提供高实时性的数据同步。2、高性能:DataX CDC 使用 CDC 技术,只同步变更数据,避免了全量数据的传输和处理,能够提供高效的数据同步性能。3、精确同步:DataX CDC 可以精确捕获和同步数据源的每一次变更操作,保证了数据的一致性和准确性。4、灵活配置:DataX CDC 提供了灵活的配置选项,可以根据具体需求选择要同步的数据源、目标数据源等,并支持多种数据源类型。劣势:1、复杂性:DataX CDC 的配置和使用相对复杂,需要熟悉 CDC 技术和相关的配置知识。2、依赖数据库事务日志:DataX CDC 的工作原理是基于数据库事务日志的,因此需要确保数据源开启了事务日志功能,并且保证事务日志的稳定性和完整性。3、对数据库性能有影响:在进行实时同步的过程中,DataX CDC 需要读取和解析数据库的事务日志,可能会对源数据库的性能产生一定影响。DataX CDC 支持的实时采集数据源类型:转载自https://www.studyjava.cn/post/2029
  • [技术干货] Flink CDC的工作原理和优势
    Flink CDC通过与数据库进行交互,实时捕获数据库中的变更操作。它的工作原理可以分为以下几个步骤:1. 数据库连接和监控:首先,Flink CDC需要与目标数据库建立连接,并监控数据库的变更操作。它可以通过监听数据库的事务日志或者使用数据库引擎的内部机制来实现。2. 变更事件解析:一旦数据库发生变更操作,Flink CDC会解析这些变更事件。它会将变更事件转化为对应的数据结构,例如INSERT、UPDATE或DELETE操作。3. 数据转换和格式化:解析后的变更事件需要经过数据转换和格式化,以便能够被Flink进行处理。Flink CDC会将变更事件转化为Flink支持的数据格式,例如JSON、Avro等。4. 事件流生成:经过转换和格式化后,Flink CDC会将变更事件转化为数据流。这个数据流可以被Flink的流处理任务进行消费和处理。5. 数据同步和传输:生成的数据流可以被传输到不同的目的地,例如Flink的流处理任务、消息队列或者其他外部系统。这样,我们就可以对变更事件进行实时分析和处理。需要注意的是,Flink CDC并非直接支持所有数据库。它的可用性取决于数据库本身是否提供了事务日志的访问接口。目前,Flink CDC支持的数据库包括MySQL、PostgreSQL、Oracle等。优势:1. 实时性:Flink CDC能够实时捕获数据库中的变更操作,并将其转化为实时的数据流。这使得我们能够及时地对数据库中的数据变动进行响应和处理。2. 精确性:Flink CDC通过解析数据库的事务日志或者内部机制,能够准确地捕获数据库中的变更操作。这保证了数据的准确性和一致性。3. 可靠性:Flink CDC能够处理数据库中的变更操作,并将其转化为可靠的数据流。它具有容错机制,能够处理故障和数据丢失的情况。4. 扩展性:Flink CDC支持水平扩展,能够处理大规模的数据变更。它可以与Flink的流处理任务无缝集成,实现高效的数据处理和分析。劣势:1.对于非关系型数据库(如MongoDB、HBase等)的支持相对较弱。Flink CDC主要面向关系型数据库,对于非关系型数据库的支持相对有限。2.对于大规模数据变更的处理可能存在延迟。由于Flink CDC通过读取事务日志来捕获数据变化,如果有大量的数据变更发生,可能会造成读取和处理的延迟。3.需要访问数据库的主从部署。为了保持数据一致性,Flink CDC需要访问数据库的主库以读取事务日志,这可能会对数据库的性能产生一定影响,尤其是在高并发的情况下。4.需要数据库的binlog开启。Flink CDC依赖数据库的binlog来捕获数据变化,如果数据库的binlog没有开启,就无法正常使用Flink CDC。需要注意的是,这些劣势并不是Flink CDC本身的问题,而是基于流式数据捕获的一般限制。如果你的应用场景不适合流式数据捕获,可能需要考虑其他的数据同步方案。Flink CDC 支持的实时采集数据源类型:转载自https://www.studyjava.cn/post/2029
总条数:764 到第
上滑加载中