• [技术干货] Spring Boot 中的缓存注解-转载
     Spring Boot 中的缓存注解 在 Spring Boot 中,缓存是一个非常重要的话题。当我们需要频繁读取一些数据时,为了提高性能,可以将这些数据缓存起来,避免每次都从数据库中读取。为了实现缓存,Spring Boot 提供了一些缓存注解,可以方便地实现缓存功能。   缓存注解是什么? Spring Boot 提供了四个缓存注解,分别是:  @Cacheable @CachePut @CacheEvict @Caching 这些注解可以用来标记一个方法需要被缓存,或者缓存需要被更新或删除。  缓存注解的原理 在 Spring Boot 中,缓存的实现是通过缓存管理器来实现的。缓存管理器负责缓存的创建、读取、更新和删除等操作。Spring Boot 提供了多种缓存管理器的实现,例如 Ehcache、Redis、Caffeine 等。  当一个方法被标记为缓存方法时,Spring Boot 会先查找是否存在缓存,如果存在,则直接从缓存中读取数据。如果缓存中不存在,则执行方法并将结果缓存到缓存中。  当一个方法被标记为更新或删除缓存时,Spring Boot 会根据注解中的参数来更新或删除缓存。例如,@CachePut 注解会将方法的结果缓存起来,而 @CacheEvict 注解会删除缓存。  如何使用缓存注解? 在 Spring Boot 中,可以通过在方法上添加缓存注解来开启缓存功能。下面介绍四个常用的缓存注解。  @Cacheable @Cacheable 注解可以标记一个方法需要被缓存。在注解中,可以指定缓存的名称和缓存的键。例如:  @Cacheable(value = "users", key = "#id") public User getUserById(Long id) {     // 从数据库中读取用户信息 } 在上面的例子中,缓存的名称是 users,缓存的键是方法的参数 id。当方法被执行时,Spring Boot 会先查找缓存,如果缓存中存在相应的数据,则直接从缓存中读取,否则执行方法并将结果缓存到缓存中。  @CachePut @CachePut 注解可以标记一个方法需要更新缓存。在注解中,可以指定缓存的名称和缓存的键。例如:  @CachePut(value = "users", key = "#user.id") public User updateUser(User user) {     // 更新数据库中的用户信息 } 在上面的例子中,缓存的名称是 users,缓存的键是方法返回值 user.id。当方法被执行时,Spring Boot 会更新缓存中的数据。  @CacheEvict @CacheEvict 注解可以标记一个方法需要删除缓存。在注解中,可以指定缓存的名称和缓存的键。例如:  @CacheEvict(value = "users", key = "#id") public void deleteUserById(Long id) {     // 删除数据库中的用户信息 } 在上面的例子中,缓存的名称是 users,缓存的键是方法的参数 id。当方法被执行时,Spring Boot 会删除缓存中对应的数据。  @Caching @Caching 注解可以将多个缓存注解组合在一起使用。例如:  @Caching(     cacheable = @Cacheable(value = "users", key = "#id"),     put = @CachePut(value = "users", key = "#result.id"),     evict = @CacheEvict(value = "allUsers", allEntries = true) ) public User getUserById(Long id) {     // 从数据库中读取用户信息 } 在上面的例子中,@Caching 注解包含了三个缓存注解:@Cacheable、@CachePut 和 @CacheEvict。当方法被执行时,Spring Boot 会先查找缓存,如果缓存中存在相应的数据,则直接从缓存中读取;如果缓存中不存在,则执行方法并将结果缓存到缓存中;同时更新 users 缓存中的数据,并删除 allUsers 缓存中的所有数据。  缓存注解的配置 在 Spring Boot 中,可以通过配置文件来配置缓存的属性。下面是一个使用 Redis 作为缓存管理器的配置文件示例:  spring:   cache:     type: redis     redis:       host: localhost       port: 6379       password: password       time-to-live: 30000 在上面的例子中,使用 Redis 作为缓存管理器,设置 Redis 的主机地址、端口号、密码和超时时间。可以根据实际情况进行配置。  代码示例 下面是一个使用缓存注解的代码示例。在这个例子中,我们定义了一个 UserService 类,其中包含一个 getUserById() 方法和一个 updateUser() 方法。在方法上添加了缓存注解,可以方便地实现缓存功能。  @Service public class UserService {      @Autowired     private UserRepository userRepository;      @Cacheable(value = "users", key = "#id")     public User getUserById(Long id) {         return userRepository.findById(id).orElse(null);     }      @CachePut(value = "users", key = "#user.id")     public User updateUser(User user) {         userRepository.save(user);         return user;     }  } 在上面的例子中,getUserById() 方法被标记为 @Cacheable 注解,缓存的名称是 users,缓存的键是方法的参数 id;updateUser() 方法被标记为 @CachePut 注解,缓存的名称是 users,缓存的键是方法返回值 user.id。当方法被执行时,Spring Boot 会先查找缓存,如果缓存中存在相应的数据,则直接从缓存中读取,否则执行方法并将结果缓存到缓存中。  总结 在 Spring Boot 中,缓存是非常重要的。通过使用缓存注解,可以方便地实现缓存功能,提高程序的性能。在代码中,我们可以通过使用 @Cacheable、@CachePut、@CacheEvict 和 @Caching 注解来开启缓存功能,也可以通过配置文件来配置缓存属性。 ———————————————— 版权声明:本文为CSDN博主「程序媛徐师姐」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/2301_77835649/article/details/131434427 
  • [技术干货] Spring Boot是什么?详解它的优缺点以及四大核心-转载
     前言  本文将讲解Spring Boot是什么?详解它的优缺点、四大核心和应用场景。   一、Spring Boot 是什么? Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。通过这种方式,Boot致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者。  二、Spring Boot 的优缺点  1、优点 ①可快速构建独立的 Spring 应用 Spring Boot是一个依靠大量注解实现自动化配置的全新框架。在构建Spring应用时,我们只需要添加相应的场景依赖,Spring Boot就会根据添加的场景依赖自动进行配置,在无须额外手动添加配置的情况下快速构建出一个独立的Spring应用。  ②直接嵌入Tomcat、Jetty 和Undertow 服务器 传统的Spring应用部署时,通常会将应用打成 WAR包形式并部署到Tomcat、Jetty或Undertow 服务器中。Spring Boot框架内嵌了Tomcat、Jetty和Undertow 服务器,而且可以自动将项目打包,并在项目运行时部署到服务器中。  ③通过依赖启动器简化构建配置 在Spring Boot项目构建过程中,无须准备各种独立的JAR文件,只需在构建项目时根据开发场景需求选择对应的依赖启动器“starter”,在引入的依赖启动器“starter”内部已经包含了对应开发场景所需的依赖,并会自动下载和拉取相关JAR包。  ④自动化配置Spring和第三方库 Spring Boot 充分考虑到与传统Spring 框架以及其他第三方库融合的场景,在提供了各种场景依赖启动器的基础上,内部默认提供了各种自动化配置类(例如 RedisAuto Configuration)。使用Spring Boot开发项目时,一旦引入了某个场景的依赖启动器,Spring Boot内部提供的默认自动化配置类就会生效,开发者无须手动在配置文件中进行相关配置(除非开发者需要更改默认配置),从而极大减少了开发人员的工作量,提高了程序的开发效率。  ⑤提供生产就绪功能 Spring Boot提供了一些用于生产环境运行时的特性,例如指标、监控检查和外部化配置。其中,指标和监控检查可以帮助运维人员在运维期间监控项目运行情况;外部化配置可以使运维人员快速、方便地进行外部化配置和部署工作。  ⑥极少的代码生成和XML配置 Spring Boot 框架内部已经实现了与Spring以及其他常用第三方库的整合连接,并提供了默认最优化的整合配置,使用时基本上不需要额外生成配置代码和XML配置文件。在需要自定义配置的情况下,Spring Boot更加提倡使用Java config(Java 配置类)替换传统的XML配置方式,这样更加方便查看和管理。  2、缺点 Spring Boot也有一些明显的缺点:  例如,Spring Boot入门较为简单,但是深入理解和学习却有一定的难度,这是因为SpringBoot是在Spring框架的基础上推出的,所以读者想要弄明白Spring Boot的底层运行机制,有必要对Spring框架有一定的了解。  三、Spring Boot 的四大核心 1、自动配置 针对很多Spring应用程序和常见的应用功能,Spring Boot相关配置可自动提供,通过简单的配置,甚至零配置,可以构建一套完整的框架。  2、起步依赖 告诉Spring Boot它可以引入所需的依赖库;通过启动依赖机制(Starter),简化jar包的引用,解决jar版本的冲突。  3、Actuator 是SpringBoot的程序监控器,可以监控Spring应用程序上下文中的Beann、查看自动配置决策、Controller映射、线程活动、应用程序健康状况等,能深入运行的Spring Boot应用程序,探索Spring boot 程序内部信息。  4、命令界面 这是Spring Booot的可选特性主要用于Grovy语言。  四、Spring Boot 的应用场景 1、快速构建RESTful API服务 Spring Boot提供了一系列的自动配置和基础组件,可以帮助你快速构建基于 Spring MVC 的 RESTful API 服务。  2、快速构建微服务架构 Spring Boot 提供了一系列的微服务工具和组件,包括服务注册与发现、负载均衡、断路器等,可以帮助你快速构建微服务架构。  3、快速构建企业级应用 Spring Boot 提供了丰富的企业级应用组件,包括数据持久化、消息中间件、安全认证、任务调度等,可以帮助你快速构建企业级应用。  4、快速构建云原生应用 Spring Boot 提供了对云原生应用的支持,包括对云服务的集成、对容器化应用的支持等,可以帮助你快速构建云原生应用。  文章知识点与官方知识档案匹配,可进一步学习相关知识 ———————————————— 版权声明:本文为CSDN博主「Insist--」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/m0_73995538/article/details/131293800 
  • [技术干货] springboot插件式开发,个性部署-转载
     功能介绍 介绍 该框架主要是集成于springboot项目,用于开发插件式应用的集成框架。 核心功能  插件配置式插拔于springboot项目。 在springboot上可以进行插件式开发, 扩展性极强, 可以针对不同项目开发不同插件, 进行不同插件jar包的部署。 可通过配置文件指定要启用或者禁用插件。 支持上传插件和插件配置文件到服务器, 并且无需重启主程序, 动态部署插件、更新插件。 支持查看插件运行状态, 查看插件安装位置。 无需重启主程序, 动态的安装插件、卸载插件、启用插件、停止插件、备份插件、删除插件。 在插件应用模块上可以使用Spring注解定义组件, 进行依赖注入。 支持在插件中开发Rest接口。 支持在插件中单独定义持久层访问等需求。 可以遵循主程序提供的插件接口开发任意扩展功能。 插件可以自定义配置文件。目前只支持yml文件。 支持自定义扩展开发接口, 使用者可以在预留接口上扩展额外功能。 利用扩展机制, 定制了SpringBoot-Mybatis扩展包。使用该扩展包, 使用者可以在插件中自定义Mapper接口、Mapper xml 以及对应的实体bean。 并且支持集成Mybatis-Plus。 支持插件之间的通信。 支持插件中使用事务注解。 支持Swagger。(仅支持首次启动初始化的插件) 运行环境 jdk1.8+ apache maven 3.6 maven 仓库地址 https://mvnrepository.com/artifact/com.gitee.starblues/springboot-plugin-framework 快速入门 新建项目。 Maven目录结构下所示 结构说明:  pom.xml 代表maven的pom.xml plugin.properties 为开发环境下, 插件的元信息配置文件, 配置内容详见 插件包集成步骤。 example 为项目的总Maven目录。 example-runner 在运行环境下启动的模块。主要依赖example-main模块和插件中使用到的依赖包, 并且解决开发环境下无法找到插件依赖包的问题。可自行选择是否需要。(可选) example-main 该模块为项目的主程序模块。 example-plugin-parent 该模块为插件的父级maven pom 模块, 主要定义插件中公共用到的依赖, 以及插件的打包配置。 plugins 该文件夹下主要存储插件模块。上述模块中主要包括example-plugin1、example-plugin2 两个插件。 example-plugin1、example-plugin2 分别为两个插件Maven包。 主程序集成步骤 主程序为上述目录结构中的 example-main 模块。 在主程序中新增maven依赖包 2.    实现并定义配置 实现 com.plugin.development.integration.IntegrationConfiguration 接口。    *    生产/部署 环境: deployment、prod      */     @Value("${runMode:dev}")     private String runMode;     /** *    插件的路径      */     @Value("${pluginPath:plugins}")     private String pluginPath;     /** *    插件文件的路径      */     @Value("${pluginConfigFilePath:pluginConfigs}")     private String pluginConfigFilePath;     @Override     public RuntimeMode environment() {         return RuntimeMode.byName(runMode);     }     @Override     public String pluginPath() {         return pluginPath;     }     @Override     public String pluginConfigFilePath() {         return pluginConfigFilePath;     }     /** *    重写上传插件包的临时存储路径。只适用于生产环境 *    @return String      */     @Override     public String uploadTempPath() {         return "temp"; ———————————————— 版权声明:本文为CSDN博主「码上代码」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/weixin_44302240/article/details/110387273 
  • [技术干货] 【服务器】springboot实现HTTP服务监听-转载
    前言 前后端分离项目中,在调用接口调试时候,我们可以通过cpolar内网穿透将本地服务端接口模拟公共网络环境远程调用调试,本次教程我们以Java服务端接口为例。  1. 本地环境搭建 1.1 环境参数 JDK1.8 IDEA SpringBoot Maven Tomcat9.0 Postman 1.2 搭建springboot服务项目 搭建一个springboot服务的项目,编写一个接口,为了更好直观看到,这里创建一个pos请求的接口  @RestController @RequestMapping("/test") public class InterfaceTest {          /**      * 测试接口      * @param data      * @return Map<String,String>      */     @PostMapping("/interTest")     public Map<String,String>interTest(@RequestBody Map<String,String> data){         System.out.println(data);          if (data.size()>0){              return  data;         }          data.put("code","404");         return data;     } }  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 2. 内网穿透 这里我们使用cpolar来进行内网穿透,支持http/https/tcp协议,不限制流量,无需公网IP,也不用设置路由器,使用简单。  2.1 安装配置cpolar内网穿透 cpolar官网:https://www.cpolar.com/  2.1.1 windows系统 进入cpolar官网后,下载windows版本版本,双击安装包一路默认安装即可。  2.1.2 linux系统 cpolar 安装(国内使用) curl -L https://www.cpolar.com/static/downloads/install-release-cpolar.sh | sudo bash 1 或 cpolar短链接安装方式:(国外使用) curl -sL https://git.io/cpolar | sudo bash 1 查看版本号,有正常显示版本号即为安装成功 cpolar version 1 token认证 登录cpolar官网后台,点击左侧的验证,查看自己的认证token,之后将token贴在命令行里  cpolar authtoken xxxxxxx 1  简单穿透测试 cpolar http 8080 1 按ctrl+c退出  向系统添加服务 sudo systemctl enable cpolar 1 启动cpolar服务 sudo systemctl start cpolar 1 查看服务状态  2.2 创建隧道映射本地端口 cpolar安装成功后,在浏览器上x问本地9200端口【http://localhost:9200】,使用cpolar账号登录。  点击左侧仪表盘的隧道管理——创建隧道,创建一个tomcat的8080端口 http隧道  隧道名称:可自定义命名,注意不要与已有的隧道名称重复 协议:选择http 本地地址:8080 域名类型:免费选择随机域名 地区:选择China vip 点击创建  隧道创建成功后,点击左侧的状态——在线隧道列表,查看所生成的公网地址,然后复制地址  2.3 测试公网地址 这里以Postman接口调试工具向接口发送请求,在postman创建一个post请求方式.输入复制的公网地址加上接口路径,参数使用JSON格式,设置好参数点击  在服务接口端debug调试接口,查看请求是否进入接口,进入接口表示调用成功  3. 固定公网地址 由于以上使用cpolar所创建的隧道使用的是随机公网地址,24小时内会随机变化,不利于长期远程访问。因此我们可以为其配置二级子域名,该地址为固定地址,不会随机变化  注意需要将cpolar套餐升级至基础套餐或以上,且每个套餐对应的带宽不一样。【cpolar.cn已备案】  3.1 保留一个二级子域名 登录cpolar官网,点击左侧的预留,选择保留二级子域名,设置一个二级子域名名称,点击保留,保留成功后复制保留的二级子域名名称  3.2 配置二级子域名 访问http://127.0.0.1:9200/,登录cpolar web UI管理界面,点击左侧仪表盘的隧道管理——隧道列表,找到所要配置的8080隧道,点击右侧的编辑  修改隧道信息,将保留成功的二级子域名配置到隧道中  域名类型:选择二级子域名 Sub Domain:填写保留成功的二级子域名,本例为test01 点击更新  更新完成后,打开在线隧道列表,此时可以看到公网地址已经发生变化,地址名称也变成了保留过的二级子域名名称,将其复制下来  3.2 测试使用固定公网地址 打开postman,使用固定http地址进行调用  同样在服务端debug调试查看请求是否进入接口,进入接口表示成功  4. Cpolar监听器 我们还可以使用cpolar监听器(http://localhost:4040)查看接口请求日志,处理一个讨厌的bug。甚至可以重播请求消息包,加速测试的请求,单击重放(Replay)按钮,重新发送该HTTP信令请求,而不是手动重新触发操作。下面介绍使用cpolar监听器监听请求。  4.1 开启侦听功能 选择我们刚刚创建配置的http隧道,并点击右侧的编辑  打开高级设置,开启侦听功能  4.2 请求侦听 在浏览器访问本地4040端口,http://localhost:4040  向服务端发送请求后,此处就会显示相关的请求日志,可以看到请求的方式,请求的数据,接口路径,和返回状态及结果,极大提高了调试效率。  ———————————————— 版权声明:本文为CSDN博主「Yan-英杰」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/m0_73367097/article/details/130940834 
  • [技术干货] springBoo3.0集成knife4j4.1.0(swagger3)-转载
     温馨提示:  springBoot 版本 3.0+  knife4j 版本 4.1.0   添加依赖:knife4j包含了swagger,openapi3中的依赖,所以加这一个就行。  <dependency>     <groupId>com.github.xiaoymin</groupId>     <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>     <version>4.1.0</version> </dependency> <!--springBoot相关配置忽略--> yml文件中配置:  #springdoc相关配置 springdoc:   swagger-ui:     path: /swagger-ui.html     tags-sorter: alpha     operations-sorter: alpha   api-docs:     path: /v3/api-docs   group-configs:     - group: 'hyc'       paths-to-match: '/**'       packages-to-scan: com.hyc  #改成你自己的包名,一般到启动类的包名  #knife4j相关配置 可以不用改 knife4j:   enable: true   setting:     language: zh_cn     swagger-model-name: 实体类   配置类,主要是配置接口文档的主页信息,也可以写在yml文件中,这里是采用的配置类  package com.hyc.config;   import cn.hutool.core.util.RandomUtil; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Contact; import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.info.License; import org.springdoc.core.customizers.GlobalOpenApiCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;   import java.util.HashMap; import java.util.Map;   /**  * Swagger配置  */ @Configuration public class SwaggerConfig {     /**      * 根据@Tag 上的排序,写入x-order      *      * @return the global open api customizer      */     @Bean     public GlobalOpenApiCustomizer orderGlobalOpenApiCustomizer() {         return openApi -> {             if (openApi.getTags()!=null){                 openApi.getTags().forEach(tag -> {                     Map<String,Object> map=new HashMap<>();                     map.put("x-order", RandomUtil.randomInt(0,100));                     tag.setExtensions(map);                 });             }             if(openApi.getPaths()!=null){                 openApi.addExtension("x-test123","333");                 openApi.getPaths().addExtension("x-abb",RandomUtil.randomInt(1,100));             }           };     }       @Bean     public OpenAPI customOpenAPI() {         return new OpenAPI()                 .info(new Info()                         .title("SpringCloud微服务系统API")                         .version("1.0")                         .contact(new Contact().name("hyc").url("www.baidu.com").email("XXXXX@163.com"))                         .description( "SpringCloud微服务系统API")                         .termsOfService("www.baidu.com")                         .license(new License().name("Apache 2.0")                                 .url("www.baidu.com")));     } }  然后,就可以启动测试输入地址http://ip:port/doc.html    注解的基本使用可以看下这里:swagger3注解和swagger2的区别   这里主要提下请求参数为文件的时候怎么写   @Parameter(name = "image", description = "图片文件", required = true,schema =@Schema(type = "file")) 在@Parameter参数注解里面加 schema = @Schema(type = "string", format = "binary") ———————————————— 版权声明:本文为CSDN博主「如果又如果啊」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_45001053/article/details/129875578 
  • [技术干货] SpringBoot很实用的请求过滤器 - FilterRegistrationBean
    1、为什么需要Filter 在日常的开发中,我们的项目可能会被各种各样的客户端进行访问,那么,一些带有意图的朋友,就会利用自己所学的技术进行有目的的访问,那么我们的服务端就不再安全和可靠,我相信每位开发者都知道爬虫这种东西,那么当我们的请求不再安全,那么我们后台的数据就会变得透明。 数据透明,是一件多么可怕的事情,在这个数字潮流时代,数据就是金钱,在生活中任何一个系统都会录入我们的个人信息。 那么对请求进行过滤、请求的校验就变得尤为重要。  2、常用的Filter方式 在很久以前的Servlet项目中,可以使用@WebFilter注解来进行Filter的配置。 在目前SpringBoot作为后端主流框架而言,使用更多的是配置FilterRegistrationBean类,本文也主要以此类来配置Filter。 两种方式都是针对于Filter接口的实现类而言的。  3、Filter接口 一般我们实现Filter接口,只需要实现doFilter方法即可,但是也可以实现另外两个方法。  public interface Filter {     default void init(FilterConfig filterConfig) throws ServletException {     }      void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;      default void destroy() {     } } 4、FilterRegistrationBean类 可以看到此类的内部需要一个T类型的filter属性,而这个属性也是FilterRegistrationBean的核心,后面我们只需要将自定义的Filter放入到不同的FilterRegistrationBean中就可以了。  public class FilterRegistrationBean<T extends Filter> extends AbstractFilterRegistrationBean<T> {     private T filter;      public FilterRegistrationBean() {         super(new ServletRegistrationBean[0]);     }      public FilterRegistrationBean(T filter, ServletRegistrationBean<?>... servletRegistrationBeans) {         super(servletRegistrationBeans);         Assert.notNull(filter, "Filter must not be null");         this.filter = filter;     }      public T getFilter() {         return this.filter;     }      public void setFilter(T filter) {         Assert.notNull(filter, "Filter must not be null");         this.filter = filter;     } } 5、自定义Filter代码实现 5.1、自定义Filter 自定义的Filter不用使用@Bean进行注入  5.1.1、UserFilter拦截对用户信息的请求 public class UserFilterConfig implements Filter {      @Override     public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {         System.out.println("用户过滤器触发成功");         // 核心代码省略         filterChain.doFilter(servletRequest,servletResponse);     }  } 5.1.2、AuthFilter拦截基本的认证信息 public class AuthFilterConfig implements Filter {      @Override     public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {         System.out.println("认证过滤器触发成功");         // 核心代码省略         filterChain.doFilter(servletRequest,servletResponse);     } } 5.2、配置FilterRegistrationBean类 对于不同的Filter对象需要配置不同的FilterRegistrationBean类,因为存在重复代码,所以我进行了代码提取,并且向容器中注入相应的对象。 在此配置类中我使用到了Builder这种方式来进行数据的配置,这种方式在当前的SpringBoot框架中是非常常见的,这种方式也非常的好用,值得学习。  @Configuration public class FilterConfig {      @Bean     public FilterRegistrationBean<UserFilterConfig> userFilterConfigFilterRegistrationBean(){         FilterRegistrationBean<UserFilterConfig> userFilter = new FilterRegistrationBean<>();         Builder<UserFilterConfig> userBuilder = new Builder<>(userFilter);         userBuilder.filterConfiguration(UserFilterConfig.class,1,false,"/*");         return userFilter;     }      @Bean     public FilterRegistrationBean<AuthFilterConfig> authFilterConfigFilterRegistrationBean(){         FilterRegistrationBean<AuthFilterConfig> authFilter = new FilterRegistrationBean<>();         Builder<AuthFilterConfig> authBuilder = new Builder<>(authFilter);         authBuilder.filterConfiguration(AuthFilterConfig.class,6,false,"/test/*");         return authFilter;     }      private class Builder<T extends Filter>{          private FilterRegistrationBean<T> filterRegistrationBean = null;          public Builder(FilterRegistrationBean<T> filterRegistrationBean){             this.filterRegistrationBean = filterRegistrationBean;         }          public Builder filterConfiguration(Class<? extends Filter> clazz,int order,boolean async,String ...patterns){             T filter = null;             try {                 filter = (T)clazz.getDeclaredConstructor().newInstance();             } catch (Exception e) {                 System.out.println("[ " + clazz.toString() + " ] 过滤器对象不存在");             }             this.filterRegistrationBean.setFilter(filter); // 设置过滤器             this.filterRegistrationBean.setOrder(order); // 设置启动顺序             String clazzPath = clazz.toString().toLowerCase(Locale.ROOT);             // 配置过滤器的名称,首字母一定要小写,不然拦截了请求后会报错             this.filterRegistrationBean.setName(clazzPath.substring(clazzPath.lastIndexOf(".")));             this.filterRegistrationBean.addUrlPatterns(patterns); // 配置拦截的请求地址             return this;         }      } } 6、运行结果 6.1、Controller类如下: @RestController @RequiredArgsConstructor(onConstructor = @__(@Autowired)) public class FilterDemoController {      private final ApplicationContext applicationContext;      @GetMapping(value = "/abc")     public void show(){         for (String beanDefinitionName : applicationContext.getBeanDefinitionNames()) {             if(beanDefinitionName.contains("Filter")){                 System.out.println(beanDefinitionName);             }         }         System.out.println("=====> end <=====");     }      @GetMapping(value = "/test/abc")     public void test(){      } }   /abc:会打印当前容器中所有的Filter对象。 /test/abc:什么也不做。  6.2、控制台显示 当我访问http://localhost:8080/abc时,就会触发UserFilter这个过滤器,结果如下:  可以看到,过滤器会先触发,然后打印出所有的Filter,容器中会存在两个不同的FilterRegistrationBean。  当我访问http://localhost:8080/test/abc时,就会触发AuthFilter这个过滤器,结果如下:  耶??为啥结果不是想象的那样?? 这是因为我的UserFilter的拦截路径为/*,而AuthFilter的拦截路径为/test/*。 那为什么UserFilter会在AuthFilter之前执行呢? 因为/*的拦截范围比/test/*的范围大,可以说/test/*是经过了/*拦截过再进行了匹配拦截。于此同时,我在相应的FilterRegistrationBean中也设置了Filter的执行顺序。  7、总结 使用Builder这种方式对配置类中的数据进行配置,是当前许多框架都在使用的方式,能够在一定程度上隐藏内部的实现。 FilterRegistrationBean类提供了自定义FIlter的执行顺序,上文的Demo中因为拦截的范围问题,所以不容易看出存在执行顺序的问题,但是想要看到顺序问题也非常的简单,重新给setOrder方法赋值就行了,优先级低的先执行。 ———————————————— 版权声明:本文为CSDN博主「中国胖子风清扬」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_45515182/article/details/127217852 
  • [技术干货] 数据翻译——Easy_Trans的简单使用
    介绍 在项目开发中,借助JPA和Mybatis Plus我们已经可以做到单表查询不写SQL,但是很多时候我们需要关联字典表,关联其他表来实现字典码和外键的翻译,又要去写sql,使用 EasyTrans 你只需要在被翻译的pojo属性上加一个注解即可完成字典码/外键 翻译。 先看效果:  easy trans适用于三种场景 1 我有一个id,但是我需要给客户展示他的title/name 但是我又不想做表关联查询 2 我有一个字典码 sex 和 一个字典值0 我希望能翻译成 男 给客户展示。 3 我有一组user id 比如 1,2,3 我希望能展示成 张三,李四,王五 给客户 食使用步骤 技术经理/架构 需要做的事情 1 、先把maven 引用加上        <dependency>             <groupId>com.fhs-opensource</groupId>             <artifactId>easy-trans-spring-boot-starter</artifactId>             <version>1.1.3</version>         </dependency> Mybatis plus用户另外还需要加以下扩展:         <dependency>             <groupId>com.fhs-opensource</groupId>             <artifactId>easy_trans_mybatis_plus_extend</artifactId>             <version>1.1.3</version>         </dependency> JPA 用户另外还需要加以下扩展:         <dependency>             <groupId>com.fhs-opensource</groupId>             <artifactId>easy_trans_jpa_extend</artifactId>             <version>1.1.3</version>         </dependency> 如果使用Redis请添加redis的引用(如果之前加过了请不要重复添加)         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-data-redis</artifactId>         </dependency> 2、在yaml中添加如下配置 easy-trans:    autotrans:        #您的service/dao所在的包 支持通配符比如com.*.**.service.**,他的默认值是com.*.*.service.impl        package: com.fhs.test.service.**;com.fhs.test.dao.**     #启用redis缓存 如果不用redis请设置为false    is-enable-redis: true     #启用全局翻译(拦截所有responseBody进行自动翻译),如果对于性能要求很高可关闭此配置    is-enable-global: true     #启用平铺模式    is-enable-tile: true spring:#如果用到redis配置redis连接   redis:     host: 192.168.0.213     port: 6379     password: 123456     database: 0     timeout: 6000 3、如果不使用redis,请在启动类加禁用掉redis的自动配置类  @SpringBootApplication(exclude = { RedisAutoConfiguration.class }) 4、初始化字典数据(如果你们项目没字典表请忽略)        @Autowired  //注入字典翻译服务        private  DictionaryTransService dictionaryTransService;        //在某处将字典缓存刷新到翻译服务中,以下是demo        Map<String,String> transMap = new HashMap<>();        transMap.put("0","男");        transMap.put("1","女");        dictionaryTransService.refreshCache("sex",transMap); 5、微服务配置(比如订单服务用到了用户服务的user数据来进行翻译,不牵扯微服务的可以不管) A、白名单添加 /easyTrans/proxy/** 保证其不被拦截,RPC trans的时候easytrans会自动调用目标微服务的接口来获取数据。 B、应用之间的认证可以通过filter/interceptor实现,然后自定义RestTemplate 保证easytrans在请求用户服务的时候带上需要认证的参数 普通程序员需要做的事情 pojo 中添加 @Data @Builder @AllArgsConstructor @NoArgsConstructor //实现TransPojo  接口,代表这个类需要被翻译或者被当作翻译的数据源 public class Student implements TransPojo {      // 字典翻译 ref为非必填     @Trans(type = TransType.DICTIONARY,key = "sex",ref = "sexName")     private Integer sex;     //这个字段可以不写,实现了TransPojo接口后有一个getTransMap方法,sexName可以让前端去transMap取     private String sexName;     //SIMPLE 翻译,用于关联其他的表进行翻译    schoolName 为 School 的一个字段     @Trans(type = TransType.SIMPLE,target = School.class,fields = "schoolName")     private String schoolId;     //远程翻译,调用其他微服务的数据源进行翻译     @Trans(type = TransType.RPC,targetClassName = "com.fhs.test.pojo.School",fields = "schoolName",serviceName = "easyTrans",alias = "middle")     private String middleSchoolId; } 然后访问你的controller,看返回结果。 
  • 使用Spring Boot发送邮件
    使用Spring Boot发送邮件Spring Boot提供了一种简单而强大的方式来发送电子邮件。在本文中,我们将学习如何使用Spring Boot发送电子邮件。步骤1:添加依赖项首先,我们需要将Spring Boot的邮件启动器添加到我们的项目中。我们可以在pom.xml文件中添加以下依赖项:<dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-mail</artifactId></dependency>步骤2:配置SMTP服务器信息接下来,我们需要为我们的应用程序配置SMTP服务器信息。我们可以在application.properties文件中添加以下属性:spring.mail.host=smtp.gmail.comspring.mail.port=587spring.mail.username=youremail@gmail.comspring.mail.password=your-email-passwordspring.mail.properties.mail.smtp.auth=truespring.mail.properties.mail.smtp.starttls.enable=true这里,我们使用Gmail作为我们的SMTP服务器。请注意,如果您使用不同的SMTP服务器,则需要更改相应的属性。步骤3:创建Java类现在,我们将编写一个Java类来发送电子邮件。我们可以使用JavaMailSender接口来发送电子邮件。以下是一个示例代码:import org.springframework.beans.factory.annotation.Autowired;import org.springframework.mail.SimpleMailMessage;import org.springframework.mail.javamail.JavaMailSender;import org.springframework.stereotype.Component;@Componentpublic class EmailSender {    @Autowired    private JavaMailSender mailSender;    public void sendEmail() {        SimpleMailMessage message = new SimpleMailMessage();        message.setTo("recipient-email@gmail.com");        message.setSubject("Test email");        message.setText("This is a test email");        mailSender.send(message);    }}在这里,我们首先使用@Autowired注释注入JavaMailSender bean。然后,我们创建一个SimpleMailMessage对象并设置收件人,主题和正文。最后,我们使用mailSender.send()方法将邮件发送给收件人。步骤4:测试邮件发送现在我们已经准备好发送电子邮件了。我们可以在Spring Boot应用程序的任何位置调用sendEmail()方法,以便发送电子邮件。以下是一个示例代码:import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.ApplicationContext;@SpringBootApplicationpublic class Application {    public static void main(String[] args) {        ApplicationContext context = SpringApplication.run(Application.class, args);        EmailSender emailSender = context.getBean(EmailSender.class);        emailSender.sendEmail();    }}在这里,我们首先获取ApplicationContext对象,并使用@Bean注释标记的类获取EmailSender bean。然后,我们调用sendEmail()方法以发送测试邮件。结论到此为止,我们已经学会了如何使用Spring Boot发送电子邮件。Spring Boot提供了一个简单而强大的方式来发送电子邮件,而无需编写复杂的代码。
  • [技术干货] Springboot在有锁的情况下如何正确使用事务
     在Spring Boot应用程序中,如果需要操作有锁的数据,请确保采用正确的事务管理技术。  以下是在有锁的情况下使用Spring Boot事务的最佳实践:  了解不同类型的锁 数据库中有两种类型的锁:共享锁和排他锁。 共享锁允许其他用户读取数据但不允许修改它,而排他锁则会阻止其他用户读取或修改数据。 在使用Spring Boot事务时,您需要确定要使用哪种类型的锁。  配置事务管理器 在Spring Boot应用程序中,您需要配置一个事务管理器来处理事务。 您可以使用Spring框架提供的JPA或Hibernate事务管理器,或者使用Spring Boot提供的默认JDBC事务管理器。 然后,您需要将事务管理器与数据源关联起来。  使用@Transactional注释 在代码中使用@Transactional注释来指示Spring在执行方法时启动事务。 当您需要更新有锁的数据时,必须将事务设置为悲观锁定,以确保其他线程无法同时修改数据。 您可以添加@Lock注释来指示Spring使用悲观锁定。  例如,以下代码片段演示了如何使用Spring Boot事务和悲观锁定:  java import org.springframework.transaction.annotation.Transactional; import javax.persistence.LockModeType;  @Service public class UserService {      @Autowired     private UserRepository userRepository;      @Transactional     public void updateUserWithLock(User user) {         User lockedUser = userRepository.findById(user.getId(), LockModeType.PESSIMISTIC_WRITE).orElse(null);         if (lockedUser != null) {             lockedUser.setName(user.getName());             lockedUser.setEmail(user.getEmail());             userRepository.saveAndFlush(lockedUser);         }     } } 在上面的代码中,@Transactional注释指示Spring在此方法中启动事务。 UserRepository是一个接口,它定义了一些常见的数据访问方法,例如findById()。 通过使用PESSIMISTIC_WRITE锁定模式,我们确保在更新用户记录时不会发生竞争条件。  总之,在使用Spring Boot事务时,请确保了解有关锁的不同类型,并正确配置事务管理器和悲观锁定以避免数据竞争问题。 
  • [技术干货] Spring 中对 controller和service层的一些见解
    接触Spring Boot开发一年不到,回想起前几年使用spring MVC的时候,因为当时公司业务比较简单,所以service层和dao层实际上是一样的,业务逻辑全部放在了controller层来做;当时觉得很纳闷,service层感觉是多余的,根本用不到;最近接触的项目,架构师设计的框架,直接根据模型设计dao层接口和service接口,代码写了不少,突然发现这么定义接口很多功能是没法实现的。于是回头重新思考了spring MVC模型,刚才看了篇 非常不错的博客 ,感觉作者能把这个问题解释清楚了。还是从MVC三层模型开始,这三层模型的设计之初,就是为了将业务层(controller)、视图层(view)以及模型层(modal)区分开来。需要注意的是,这里并没有数据库这个概念,所以模型层会有一些冗杂,两个表的联合查询出来的数据,会被封装成一个模型交给控制层;同样的,控制层因为没有服务的概念,如果项目比较大,也会变的有些冗余。基于controller和modal层并没有很好的实现模块化,因此,我们将modal层去掉,改为更加原子化的dao层;同时,将controller层的业务逻辑,划分成多个服务(service)。每个服务可以组合使用dao层数据,组装成一个服务,比如用户的注册服务;而controller层,调用多个service服务完成url请求。简单来说,增加service层,替换modal层,第一是细化了数据模型,使得我们在改动某张表时,只需要改动dao层实现即可,最大化的减少了代码的改动成本;当然,更多的情况是service服务和controller可能都需要更改; service层将controller的逻辑分类,保证了controller的逻辑更加清晰。举个生活中的例子,用户预约某个酒店的客房,这是酒店首先会调用验证服务对用户提供的信息进行验证,之后调用预约服务进行预约,如果预约失败,酒店可能会把客户的预约信息提交给另外一家酒店请求它们的预约服务,然后将结果返回给客户;对于服务层来说,需要判断酒店是否有空余客房,之后修改客房信息,同时将客房和用户信息存入临时表。这里至少需要两种不同的dao层服务实现service。所以整体上来看,controllrt->service->dao至少是一对一,更多的情况下是一对多。这也就是service层存在的意义了。
  • [技术干货] Java SpringBoot 加载 yml 配置文件中字典项
    将字典数据,配置在 yml 文件中,通过加载yml将数据加载到 Map中Spring Boot 中 yml 配置、引用其它 yml 中的配置。# 在配置文件目录(如:resources)下新建application-xxx,必须以application开头的yml文件, 多个文件用 "," 号分隔,不能换行项目结构文件application.yml codeduidaima.comserver:port: 8088application:name: VipSoft Env Demospring:profiles:include:dic # 在配置文件目录(如:resources)下新建application-xxx 开头的yml文件, 多个文件用 "," 号分隔,不能换行#性别字典user-gender:0: 未知1: 男2: 女application-dic.yml将字典独立到单独的yml文件中 codeduidaima.com#支付方式pay-type:1: 微信支付2: 货到付款在 resources 目录下,创建META-INF目录,创建 spring.factories文件,Spring Factories是一种类似于Java SPI的机制,它在META-INF/spring.factories文件中配置接口的实现类名称,然后在程序中读取这些配置文件并实例化。内容如下: codeduidaima.com# Environment Post Processororg.springframework.boot.env.EnvironmentPostProcessor=com.vipsoft.web.utils.ConfigUtilConfigUtil codeduidaima.compackage com.vipsoft.web.utils;import org.springframework.boot.SpringApplication;import org.springframework.boot.context.properties.bind.BindResult;import org.springframework.boot.context.properties.bind.Binder;import org.springframework.boot.env.EnvironmentPostProcessor;import org.springframework.core.env.ConfigurableEnvironment;import org.springframework.core.env.PropertySource;import org.springframework.util.Assert;public class ConfigUtil implements EnvironmentPostProcessor {private static Binder binder;private static ConfigurableEnvironment environment;public static String getString(String key) {Assert.notNull(environment, "environment 还未初始化!");return environment.getProperty(key, String.class, "");}public static <T> T bindProperties(String prefix, Class<T> clazz) {Assert.notNull(prefix, "prefix 不能为空");Assert.notNull(clazz, "class 不能为空");BindResult<T> result = ConfigUtil.binder.bind(prefix, clazz);return result.isBound() ? result.get() : null;}/*** 通过 META-INF/spring.factories,触发该方法的执行,进行环境变量的加载*/@Overridepublic void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {for (PropertySource<?> propertySource : environment.getPropertySources()) {if (propertySource.getName().equals("refreshArgs")) {return;}}ConfigUtil.environment = environment;ConfigUtil.binder = Binder.get(environment);}}DictVo codeduidaima.compackage com.vipsoft.web.vo;public class DictVO implements java.io.Serializable {private static final long serialVersionUID = 379963436836338904L;/*** 堆代码 duidaima.com* 字典类型*/private String type;/*** 字典编码*/private String code;/*** 字典值*/private String value;public DictVO(String code, String value) {this.code = code;this.value = value;}public String getType() {return type;}public void setType(String type) {this.type = type;}public String getCode() {return code;}public void setCode(String code) {this.code = code;}public String getValue() {return value;}public void setValue(String value) {this.value = value;}}DefaultController codeduidaima.compackage com.vipsoft.web.controller;import com.vipsoft.web.utils.ConfigUtil;import com.vipsoft.web.vo.DictVO;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RestController;import java.util.ArrayList;import java.util.LinkedHashMap;import java.util.List;@RestControllerpublic class DefaultController {@GetMapping(value = "/")public String login() {return "VipSoft Demo !!!";}@GetMapping("/list/{type}")public List<DictVO> listDic(@PathVariable("type") String type) {LinkedHashMap dict = ConfigUtil.bindProperties(type.replaceAll("_", "-"), LinkedHashMap.class);List<DictVO> list = new ArrayList<>();if (dict == null || dict.isEmpty()) {return list;}dict.forEach((key, value) -> list.add(new DictVO(key.toString(), value.toString())));return list;}}运行效果单元测试 codeduidaima.compackage com.vipsoft.web;import com.vipsoft.web.controller.DefaultController;import com.vipsoft.web.utils.ConfigUtil;import com.vipsoft.web.vo.DictVO;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import java.util.List;@SpringBootTestpublic class DicTest {@AutowiredDefaultController defaultController;@Testpublic void DicListTest() throws Exception {List<DictVO> pay_type = defaultController.listDic("pay-type");pay_type.forEach(p -> System.out.println(p.getCode() + " => " + p.getValue()));List<DictVO> user_gender = defaultController.listDic("user-gender");user_gender.forEach(p -> System.out.println(p.getCode() + " => " + p.getValue()));}@Testpublic void getString() throws Exception {String includeYml = ConfigUtil.getString("spring.profiles.include");System.out.println("application 引用了配置文件 =》 " + includeYml);}}转载自cid:link_0Group/Topic/JAVA/10559
  • [其他] SpringApplication执行流程
    SpringApplication的run方法的实现是我们本次旅程的主要线路,该方法的主要流程大体可以归纳如下:1) 如果我们使用的是SpringApplication的静态run方法,那么,这个方法里面首先要创建一个SpringApplication对象实例,然后调用这个创建好的SpringApplication的实例方法。在SpringApplication实例初始化的时候,它会提前做几件事情:根据classpath里面是否存在某个特征类org.springframework.web.context.ConfigurableWebApplicationContext来决定是否应该创建一个为Web应用使用的ApplicationContext类型。使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationContextInitializer。使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationListener。推断并设置main方法的定义类。2) SpringApplication实例初始化完成并且完成设置后,就开始执行run方法的逻辑了,方法执行伊始,首先遍历执行所有通过SpringFactoriesLoader可以查找到并加载的SpringApplicationRunListener。调用它们的started()方法,告诉这些SpringApplicationRunListener,“嘿,SpringBoot应用要开始执行咯!”。3) 创建并配置当前Spring Boot应用将要使用的Environment(包括配置要使用的PropertySource以及Profile)。4) 遍历调用所有SpringApplicationRunListener的environmentPrepared()的方法,告诉他们:“当前SpringBoot应用使用的Environment准备好了咯!”。5) 如果SpringApplication的showBanner属性被设置为true,则打印banner。6) 根据用户是否明确设置了applicationContextClass类型以及初始化阶段的推断结果,决定该为当前SpringBoot应用创建什么类型的ApplicationContext并创建完成,然后根据条件决定是否添加ShutdownHook,决定是否使用自定义的BeanNameGenerator,决定是否使用自定义的ResourceLoader,当然,最重要的,将之前准备好的Environment设置给创建好的ApplicationContext使用。7) ApplicationContext创建好之后,SpringApplication会再次借助Spring-FactoriesLoader,查找并加载classpath中所有可用的ApplicationContext-Initializer,然后遍历调用这些ApplicationContextInitializer的initialize(applicationContext)方法来对已经创建好的ApplicationContext进行进一步的处理。8) 遍历调用所有SpringApplicationRunListener的contextPrepared()方法。9) 最核心的一步,将之前通过@EnableAutoConfiguration获取的所有配置以及其他形式的IoC容器配置加载到已经准备完毕的ApplicationContext。10) 遍历调用所有SpringApplicationRunListener的contextLoaded()方法。11) 调用ApplicationContext的refresh()方法,完成IoC容器可用的最后一道工序。12) 查找当前ApplicationContext中是否注册有CommandLineRunner,如果有,则遍历执行它们。13) 正常情况下,遍历执行SpringApplicationRunListener的finished()方法、(如果整个过程出现异常,则依然调用所有SpringApplicationRunListener的finished()方法,只不过这种情况下会将异常信息一并传入处理)总览:上图为SpringBoot启动结构图,我们发现启动流程主要分为三个部分:第一部分进行SpringApplication的初始化模块,配置一些基本的环境变量、资源、构造器、监听器;第二部分实现了应用具体的启动方案,包括启动流程的监听模块、加载配置环境模块、及核心的创建上下文环境模块;第三部分是自动化配置模块,该模块作为springboot自动配置核心,在后面的分析中会详细讨论。在下面的启动程序中我们会串联起结构中的主要功能。启动:每个SpringBoot程序都有一个主入口,也就是main方法,main里面调用SpringApplication.run()启动整个spring-boot程序,该方法所在类需要使用@SpringBootApplication注解,以及@ImportResource注解(if need),@SpringBootApplication包括三个注解,功能如下:@EnableAutoConfiguration:SpringBoot根据应用所声明的依赖来对Spring框架进行自动配置。@SpringBootConfiguration(内部为@Configuration):被标注的类等于在spring的XML配置文件中(applicationContext.xml),装配所有bean事务,提供了一个spring的上下文环境。@ComponentScan:组件扫描,可自动发现和装配Bean,默认扫描SpringApplication的run方法里的Booter.class所在的包路径下文件,所以最好将该启动类放到根包路径下。引用自:https://mp.weixin.qq.com/s/jP9UBSSRBa7v3pJ_R5HBGA
  • [其他] Springboot 启动原理
    一、SpringBootApplication背后的秘密@SpringBootApplication注解是Spring Boot的核心注解,它其实是一个组合注解:@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = {         @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),         @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication { ... }虽然定义使用了多个Annotation进行了原信息标注,但实际上重要的只有三个Annotation:@Configuration(@SpringBootConfiguration点开查看发现里面还是应用了@Configuration)@EnableAutoConfiguration@ComponentScan即 @SpringBootApplication = (默认属性)@Configuration + @EnableAutoConfiguration + @ComponentScan所以,如果我们使用如下的SpringBoot启动类,整个SpringBoot应用依然可以与之前的启动类功能对等:@Configuration @EnableAutoConfiguration @ComponentScan public class Application {     public static void main(String[] args) {         SpringApplication.run(Application.class, args);     } }每次写这3个比较累,所以写一个@SpringBootApplication方便点。接下来分别介绍这3个Annotation。1、@Configuration这里的@Configuration对我们来说不陌生,它就是JavaConfig形式的Spring Ioc容器的配置类使用的那个@Configuration,SpringBoot社区推荐使用基于JavaConfig的配置形式,所以,这里的启动类标注了@Configuration之后,本身其实也是一个IoC容器的配置类。举几个简单例子回顾下,XML跟config配置方式的区别:(1)表达形式层面基于XML配置的方式是这样:<beans xmlns="http://www.springframework.org/schema/beans"        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"        default-lazy-init="true">      </beans>而基于JavaConfig的配置方式是这样:@Configuration public class MockConfiguration{     //bean定义 }任何一个标注了@Configuration的Java类定义都是一个JavaConfig配置类。(2)注册bean定义层面基于XML的配置形式是这样:<bean id="mockService" class="..MockServiceImpl">     ... 而基于JavaConfig的配置形式是这样的:@Configuration public class MockConfiguration{     @Bean     public MockService mockService(){         return new MockServiceImpl();     } }任何一个标注了@Bean的方法,其返回值将作为一个bean定义注册到Spring的IoC容器,方法名将默认成该bean定义的id。(3)表达依赖注入关系层面为了表达bean与bean之间的依赖关系,在XML形式中一般是这样:<bean id="mockService" class="..MockServiceImpl">    <propery name ="dependencyService" ref="dependencyService" /> </bean> <bean id="dependencyService" class="DependencyServiceImpl"></bean>而基于JavaConfig的配置形式是这样的:@Configuration public class MockConfiguration{     @Bean     public MockService mockService(){         return new MockServiceImpl(dependencyService());     }     @Bean     public DependencyService dependencyService(){         return new DependencyServiceImpl();     } }如果一个bean的定义依赖其他bean,则直接调用对应的JavaConfig类中依赖bean的创建方法就可以了。@Configuration:提到@Configuration就要提到他的搭档@Bean。使用这两个注解就可以创建一个简单的spring配置类,可以用来替代相应的xml配置文件。<beans>      <bean id = "car" class="com.test.Car">          <property name="wheel" ref = "wheel">property>      <bean>      <bean id = "wheel" class="com.test.Wheel">bean>  </beans>相当于:@Configuration  public class Conf {      @Bean      public Car car() {          Car car = new Car();          car.setWheel(wheel());          return car;      }     @Bean      public Wheel wheel() {          return new Wheel();      }  }@Configuration的注解类标识这个类可以使用Spring IoC容器作为bean定义的来源。@Bean注解告诉Spring,一个带有@Bean的注解方法将返回一个对象,该对象应该被注册为在Spring应用程序上下文中的bean。2、@ComponentScan@ComponentScan这个注解在Spring中很重要,它对应XML配置中的元素,@ComponentScan的功能其实就是自动扫描并加载符合条件的组件(比如@Component和@Repository等)或者bean定义,最终将这些bean定义加载到IoC容器中。我们可以通过basePackages等属性来细粒度的定制@ComponentScan自动扫描的范围,如果不指定,则默认Spring框架实现会从声明@ComponentScan所在类的package进行扫描。注:所以SpringBoot的启动类最好是放在root package下,因为默认不指定basePackages。3、@EnableAutoConfiguration个人感觉@EnableAutoConfiguration这个Annotation最为重要,所以放在最后来解读,大家是否还记得Spring框架提供的各种名字为@Enable开头的Annotation定义?比如@EnableScheduling、@EnableCaching、@EnableMBeanExport等,@EnableAutoConfiguration的理念和做事方式其实一脉相承,简单概括一下就是,借助@Import的支持,收集和注册特定场景相关的bean定义。@EnableScheduling是通过@Import将Spring调度框架相关的bean定义都加载到IoC容器。@EnableMBeanExport是通过@Import将JMX相关的bean定义加载到IoC容器。而@EnableAutoConfiguration也是借助@Import的帮助,将所有符合自动配置条件的bean定义加载到IoC容器,仅此而已!@EnableAutoConfiguration会根据类路径中的jar依赖为项目进行自动配置,如:添加了spring-boot-starter-web依赖,会自动添加Tomcat和Spring MVC的依赖,Spring Boot会对Tomcat和Spring MVC进行自动配置。@EnableAutoConfiguration作为一个复合Annotation,其自身定义关键信息如下:@SuppressWarnings("deprecation") @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(EnableAutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration {     ... }其中,最关键的要属@Import(EnableAutoConfigurationImportSelector.class),借助EnableAutoConfigurationImportSelector,@EnableAutoConfiguration可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器。就像一只“八爪鱼”一样,借助于Spring框架原有的一个工具类:SpringFactoriesLoader的支持,@EnableAutoConfiguration可以智能的自动配置功效才得以大功告成!自动配置幕后英雄:SpringFactoriesLoader详解SpringFactoriesLoader属于Spring框架私有的一种扩展方案,其主要功能就是从指定的配置文件META-INF/spring.factories加载配置。public abstract class SpringFactoriesLoader {     //...     public static  List loadFactories(Class<T> factoryClass, ClassLoader classLoader) {         ...     }     public static List loadFactoryNames(Class factoryClass, ClassLoader classLoader) {         ....     } }配合@EnableAutoConfiguration使用的话,它更多是提供一种配置查找的功能支持,即根据@EnableAutoConfiguration的完整类名org.springframework.boot.autoconfigure.EnableAutoConfiguration作为查找的Key,获取对应的一组@Configuration类。上图就是从SpringBoot的autoconfigure依赖包中的META-INF/spring.factories配置文件中摘录的一段内容,可以很好地说明问题。所以,@EnableAutoConfiguration自动配置的魔法骑士就变成了:从classpath中搜寻所有的META-INF/spring.factories配置文件,并将其中org.springframework.boot.autoconfigure.EnableutoConfiguration对应的配置项通过反射(Java Refletion)实例化为对应的标注了@Configuration的JavaConfig形式的IoC容器配置类,然后汇总为一个并加载到IoC容器。引用自:https://mp.weixin.qq.com/s/jP9UBSSRBa7v3pJ_R5HBGA
  • [其他] Spring Boot配置实现方案
    Spring Boot 允许使用的外部配置方式包括:properties 格式配置文件YAML 格式配置文件环境变量命令行参数启动参数Spring Boot 加载配置文件路径顺序:当前目录下/config子目录当前目录类路径下/config包类路径根目录properties格式配置 key=value形式app.name=myapp app.host=www.xxx.com app.urls[0]=localhost app.urls[1]=127.0.0.1yml格式 key: valueapp: name: myapp host: www.xxx.com urls: - localhost - 127.0.0.1注入方式1. @Value("${app.name}")形式@Value("${app.name}") private String appname;2. @ConfigurationProperties注解@Configuration @ConfigurationProperties("app") public class TestListConfig { private List<String> urls; private String name; }环境配置方式1.多环境配置application.yml #主配置文件application-dev.yml #开发环境的配置application-prod.yml #生产环境的配置application-test.yml #测试环境的配置2.application.yml中指定spring: profiles: active: dev #需要使用的配置文件的后缀3.jar启动时指定java -jar -Dspring.profiles.active=dev demo-0.0.1-SNAPSHOT.jar或者java -jar demo-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev
  • [其他] Spring Boot 统一功能处理(用户登录权限效验-拦截器、异常处理、数据格式返回)
    统一用户登录权限效验 用户登录权限的发展完善过程:1.最初用户登录效验:在每个方法中获取 Session 和 Session 中的用户信息,如果存在用户,那么就认为登录成功了,否则就登录失败了2.第二版用户登录效验:提供统一的方法,在每个需要验证的方法中调用统一的用户登录身份效验方法来判断3.第三版用户登录效验:使用 Spring AOP 来统一进行用户登录效验4.第四版用户登录效验:使用 Spring 拦截器来实现用户的统一登录验证1.1 最初用户登录权限效验@RestController @RequestMapping("/user") public class UserController { @RequestMapping("/a1") public Boolean login (HttpServletRequest request) { // 有 Session 就获取,没有就不创建 HttpSession session = request.getSession(false); if (session != null && session.getAttribute("userinfo") != null) { // 说明已经登录,进行业务处理 return true; } else { // 未登录 return false; } } @RequestMapping("/a2") public Boolean login2 (HttpServletRequest request) { // 有 Session 就获取,没有就不创建 HttpSession session = request.getSession(false); if (session != null && session.getAttribute("userinfo") != null) { // 说明已经登录,进行业务处理 return true; } else { // 未登录 return false; } } }这种方式写的代码,每个方法中都有相同的用户登录验证权限,缺点是:1.每个方法中都要单独写用户登录验证的方法,即使封装成公共方法,也一样要传参调用和在方法中进行判断2.添加控制器越多,调用用户登录验证的方法也越多,这样就增加了后期的修改成功和维护成功3.这些用户登录验证的方法和现在要实现的业务几乎没有任何关联,但还是要在每个方法中都要写一遍,所以提供一个公共的 AOP 方法来进行统一的用户登录权限验证是非常好的解决办法。1.2 Spring AOP 统一用户登录验证 统一用户登录验证,首先想到的实现方法是使用 Spring AOP 前置通知或环绕通知来实现。@Aspect // 当前类是一个切面 @Component public class UserAspect { // 定义切点方法 Controller 包下、子孙包下所有类的所有方法 @Pointcut("execution(* com.example.springaop.controller..*.*(..))") public void pointcut(){} // 前置通知 @Before("pointcut()") public void doBefore() {} // 环绕通知 @Around("pointcut()") public Object doAround(ProceedingJoinPoint joinPoint) { Object obj = null; System.out.println("Around 方法开始执行"); try { obj = joinPoint.proceed(); } catch (Throwable e) { e.printStackTrace(); } System.out.println("Around 方法结束执行"); return obj; } }但如果只在以上代码 Spring AOP 的切面中实现用户登录权限效验的功能,有这样两个问题:1.没有办法得到 HttpSession 和 Request 对象2.我们要对一部分方法进行拦截,而另一部分方法不拦截,比如注册方法和登录方法是不拦截的,也就是实际的拦截规则很复杂,使用简单的 aspectJ 表达式无法满足拦截的需求1.3 Spring 拦截器 针对上面代码 Spring AOP 的问题,Spring 中提供了具体的实现拦截器:HandlerInterceptor,拦截器的实现有两步:1.创建自定义拦截器,实现 Spring 中的 HandlerInterceptor 接口中的 preHandle方法2.将自定义拦截器加入到框架的配置中,并且设置拦截规则1) 给当前的类添加 @Configuration 注解 2)实现 WebMvcConfigurer 接口 3)重写 addInterceptors 方法注意:一个项目中可以同时配置多个拦截器(1)创建自定义拦截器/** * @Description: 自定义用户登录的拦截器 * @Date 2023/2/13 13:06 */ @Component public class LoginIntercept implements HandlerInterceptor { // 返回 true 表示拦截判断通过,可以访问后面的接口 // 返回 false 表示拦截未通过,直接返回结果给前端 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 1.得到 HttpSession 对象 HttpSession session = request.getSession(false); if (session != null && session.getAttribute("userinfo") != null) { // 表示已经登录 return true; } // 执行到此代码表示未登录,未登录就跳转到登录页面 response.sendRedirect("/login.html"); return false; } }(2)将自定义拦截器添加到系统配置中,并设置拦截的规则 addPathPatterns:表示需要拦截的 URL,**表示拦截所有⽅法 excludePathPatterns:表示需要排除的 URL 说明:拦截规则可以拦截此项⽬中的使⽤ URL,包括静态⽂件(图⽚⽂件、JS 和 CSS 等⽂件)。/** * @Description: 将自定义拦截器添加到系统配置中,并设置拦截的规则 * @Date 2023/2/13 13:13 */ @Configuration public class AppConfig implements WebMvcConfigurer { @Resource private LoginIntercept loginIntercept; @Override public void addInterceptors(InterceptorRegistry registry) { // registry.addInterceptor(new LoginIntercept());//可以直接new 也可以属性注入 registry.addInterceptor(loginIntercept). addPathPatterns("/**"). // 拦截所有 url excludePathPatterns("/user/login"). //不拦截登录注册接口 excludePathPatterns("/user/reg"). excludePathPatterns("/login.html"). excludePathPatterns("/reg.html"). excludePathPatterns("/**/*.js"). excludePathPatterns("/**/*.css"). excludePathPatterns("/**/*.png"). excludePathPatterns("/**/*.jpg"); } }1.4 练习:登录拦截器 要求: 1.登录、注册页面不拦截,其他页面都拦截2.当登录成功写入 session 之后,拦截的页面可正常访问在 1.3 中已经创建了自定义拦截器 和 将自定义拦截器添加到系统配置中,并设置拦截的规则(1)下面创建登录和首页的 html 图片 (2)创建 controller 包,在包中创建 UserController,写登录页面和首页的业务代码@RestController @RequestMapping("/user") public class UserController { @RequestMapping("/login") public boolean login(HttpServletRequest request,String username, String password) { boolean result = false; if (StringUtils.hasLength(username) && StringUtils.hasLength(password)) { if(username.equals("admin") && password.equals("admin")) { HttpSession session = request.getSession(); session.setAttribute("userinfo","userinfo"); return true; } } return result; } @RequestMapping("/index") public String index() { return "Hello Index"; } }(3)运行程序,访问页面,对比登录前和登录后的效果 图片 图片1.5 拦截器实现原理 有了拦截器之后,会在调⽤ Controller 之前进⾏相应的业务处理,执⾏的流程如下图所示:图片 实现原理源码分析: 所有的 Controller 执行都会通过一个调度器 DispatcherServlet 来实现图片 而所有方法都会执行 DispatcherServlet 中的 doDispatch 调度⽅法,doDispatch 源码分析如下:图片 通过源码分析,可以看出,Sping 中的拦截器也是通过动态代理和环绕通知的思想实现的。1.6 统一访问前缀添加 所有请求地址添加 api 前缀,c 表示所有。@Configuration public class AppConfig implements WebMvcConfigurer { // 所有的接口添加 api 前缀 @Override public void configurePathMatch(PathMatchConfigurer configurer) { configurer.addPathPrefix("api", c -> true); } }图片 2. 统一异常处理 1.给当前的类上加 @ControllerAdvice 表示控制器通知类2.给方法上添加 @ExceptionHandler(xxx.class),表示异常处理器,添加异常返回的业务代码@RestController @RequestMapping("/user") public class UserController { @RequestMapping("/index") public String index() { int num = 10/0; return "Hello Index"; } }在 config 包中,创建 MyExceptionAdvice 类:@RestControllerAdvice // 当前是针对 Controller 的通知类(增强类) public class MyExceptionAdvice { @ExceptionHandler(ArithmeticException.class) public HashMap<String,Object> arithmeticExceptionAdvice(ArithmeticException e) { HashMap<String, Object> result = new HashMap<>(); result.put("state",-1); result.put("data",null); result.put("msg" , "算出异常:"+ e.getMessage()); return result; } }也可以这样写,效果是一样的:@ControllerAdvice public class MyExceptionAdvice { @ExceptionHandler(ArithmeticException.class) @ResponseBody public HashMap<String,Object> arithmeticExceptionAdvice(ArithmeticException e) { HashMap<String, Object> result = new HashMap<>(); result.put("state",-1); result.put("data",null); result.put("msg" , "算数异常:"+ e.getMessage()); return result; } }图片 如果再有一个空指针异常,那么上面的代码是不行的,还要写一个针对空指针异常处理器:@ExceptionHandler(NullPointerException.class) public HashMap<String,Object> nullPointerExceptionAdvice(NullPointerException e) { HashMap<String, Object> result = new HashMap<>(); result.put("state",-1); result.put("data",null); result.put("msg" , "空指针异常异常:"+ e.getMessage()); return result; } @RequestMapping("/index") public String index(HttpServletRequest request,String username, String password) { Object obj = null; System.out.println(obj.hashCode()); return "Hello Index"; }图片 但是需要考虑的一点是,如果每个异常都这样写,那么工作量是非常大的,并且还有自定义异常,所以上面这样写肯定是不好的,既然是异常直接写 Exception 就好了,它是所有异常的父类,如果遇到不是前面写的两种异常,那么就会直接匹配到 Exception。当有多个异常通知时,匹配顺序为当前类及其⼦类向上依次匹配。@ExceptionHandler(Exception.class) public HashMap<String,Object> exceptionAdvice(Exception e) { HashMap<String, Object> result = new HashMap<>(); result.put("state",-1); result.put("data",null); result.put("msg" , "异常:"+ e.getMessage()); return result; }可以看到优先匹配的还是前面写的 空指针异常: 图片 3. 统一数据格式返回 3.1 统一数据格式返回的实现1.给当前类添加 @ControllerAdvice2.实现 ResponseBodyAdvice 重写其方法supports 方法,此方法表示内容是否需要重写(通过此⽅法可以选择性部分控制器和方法进行重写),如果要重写返回 true。beforeBodyWrite 方法,方法返回之前调用此方法。@ControllerAdvice public class MyResponseAdvice implements ResponseBodyAdvice { // 返回一个 boolean 值,true 表示返回数据之前对数据进行重写,也就是会进入 beforeBodyWrite 方法 // 返回 false 表示对结果不进行任何处理,直接返回 @Override public boolean supports(MethodParameter returnType, Class converterType) { return true; } // 方法返回之前调用此方法 @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { HashMap<String,Object> result = new HashMap<>(); result.put("state",1); result.put("data",body); result.put("msg",""); return result; } } @RestController @RequestMapping("/user") public class UserController { @RequestMapping("/login") public boolean login(HttpServletRequest request,String username, String password) { boolean result = false; if (StringUtils.hasLength(username) && StringUtils.hasLength(password)) { if(username.equals("admin") && password.equals("admin")) { HttpSession session = request.getSession(); session.setAttribute("userinfo","userinfo"); return true; } } return result; } @RequestMapping("/reg") public int reg() { return 1; } }3.2 @ControllerAdvice 源码分析 通过对 @ControllerAdvice 源码的分析我们可以知道上面统一异常和统一数据返回的执行流程。(1)先看 @ControllerAdvice 源码 图片 可以看到 @ControllerAdvice 派生于 @Component 组件而所有组件初始化都会调用 InitializingBean 接口。(2)下面查看 initializingBean 有哪些实现类 在查询过程中发现,其中 Spring MVC 中的实现子类是 RequestMappingHandlerAdapter,它里面有一个方法 afterPropertiesSet()方法,表示所有的参数设置完成之后执行的方法。图片 (3)而这个方法中有一个 initControllerAdviceCache 方法,查询此方法 图片 发现这个方法在执行时会查找使用所有的 @ControllerAdvice 类,发送某个事件时,调用相应的 Advice 方法,比如返回数据前调用统一数据封装,比如发生异常是调用异常的 Advice 方法实现的。转自:cid:link_0
总条数:234 到第
上滑加载中