-
前言 本文将讲解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上可以进行插件式开发, 扩展性极强, 可以针对不同项目开发不同插件, 进行不同插件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
-
前言 前后端分离项目中,在调用接口调试时候,我们可以通过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
-
温馨提示: 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
-
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
-
介绍 在项目开发中,借助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发送电子邮件。步骤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提供了一个简单而强大的方式来发送电子邮件,而无需编写复杂的代码。
-
在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 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层存在的意义了。
-
将字典数据,配置在 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的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
-
一、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 允许使用的外部配置方式包括: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
-
统一用户登录权限效验 用户登录权限的发展完善过程: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
-
引言 在上文中 我已经详细介绍了Spring 如果还有不懂的小伙伴 可以去参考一下 Spring和SpringMvc详细讲解 首先 在认识一个东西之前我们先来试着回答三个经典的问题问题,是什么?为什么?怎么样? 一.SpringBoot是什么? 在介绍 SpringBoot 之前我们首先来简单介绍一下 Spring。Spring 是诞生于2002年的 Java 开发框架,可以说已经成为 Java 开发的事实标准。所谓事实标准就是虽然 Java 官方没有说它就是开发标准,但是在当前 Java 开发的众多项目中,当我们谈到产品级的 Java 项目的时候,大多都是基于 Spring 或者应用了 Spring 特性的。 Spring 基于 IOC 和 AOP 两个特性对 Java 开发本身进行了大大的简化。但是一个大型的项目需要集成很多其他组件,比如一个 WEB 项目,至少要集成 MVC 框架、Tomcat 这种 WEB 容器、日志框架、ORM框架,连接数据库要选择连接池吧……使用 Spring 的话每集成一个组件都要去先写它的配置文件,比较繁琐且容易出错。 然后就有了SpringBoot。 Spring Boot 是由 Pivotal 团队提供的全新框架,2014 年 4 月发布 Spring Boot 1.0 2018 年 3 月 Spring Boot 2.0发布。它是对spring的进一步封装,其设计目的是用来简化 Spring 应用的初始搭建以及开发过程。怎么简化的呢?就是通过封装、抽象、提供默认配置等方式让我们更容易使用。 SpringBoot 基于 Spring 开发。SpringBoot 本身并不提供 Spring 框架的核心特性以及扩展功能,也就是说,它并不是用来替代 Spring 的解决方案,而是和 Spring 框架紧密结合用于提升 Spring 开发者体验的工具。 关于 SpringBoot 有一句很出名的话就是约定大于配置。采用 Spring Boot 可以大大的简化开发模式,它集成了大量常用的第三方库配置,所有你想集成的常用框架,它都有对应的组件支持,例如 Redis、MongoDB、Jpa、kafka,Hakira 等等。SpringBoot 应用中这些第三方库几乎可以零配置地开箱即用,大部分的 SpringBoot 应用都只需要非常少量的配置代码,开发者能够更加专注于业务逻辑。 二.SpringBoot为什么出现? 为什么会产生 SpringBoot 呢? 刚才说 SpringBoot 简化了基于 Spring 开发,这只是最直观的一方面,事实上 SpringBoot 的诞生有它所处的大时代背景这个原因在里面的,那就是微服务,这也是谈 SpringBoot 必谈微服务的原因。 2014年一个叫 Martin Fowler (同时也是经典著作《重构:改善既有代码的设计》一书的作者)发表了一篇关于微服务的博客,比较形象生动地介绍了什么是微服务,然后微服务才慢慢被人所熟知。他说微服务其实是一种架构风格,我们在开发一个应用的时候这个应用应该是由一组小型服务组成,每个小型服务都运行在自己的进程内;小服务之间通过HTTP的方式进行互联互通。和微服务相对应的就是我们之前的,单体应用,就是大名鼎鼎的 all in one 的风格。这种风格把所有的东西都写在一个应用里面,比如我们熟悉的OA,CRM,ERP系统,所有的页面,所有的代码都放在一起,打成打成一个war包,然后把war包放在Tomcat容器中运行。 Spring Boot本身并不提供Spring框架的核心特性以及扩展功能,只是用于快速、敏捷地开发新一代基于Spring 框架的应用程序。同时它集成了大量常用的第三方库配置(如 Redis、MongoDB、JP A、RabbitMQ、Quartz等),Spring Boot 应用中这些第三方库几乎可以零配置进行开箱即用,大部分的Spring Boot应用都只需要非常少量的配置代码,开发者能够更加专注于业务逻辑。 这种传统web开发的架构模式当然也有它的优势,比如它测试部署比较简单,因为不涉及到多个服务的互联互调,只需要把一个包上传到服务器就行了,可以说是一人吃饱全家不饿。同样也不会给运维带来麻烦,方便水平扩展,只需要又把相同的应用复制多份放在不同的服务器中就达到了扩展的目的。 单体应用的的缺点也显而易见,容易牵一发而动全身,比如要更改一个小小的功能,就可能需要重新部署整个应用。当然,更大的挑战就是日益增长的用户需求。 三:为什么学习 Spring Boot 使用 Spring Boot 开发项目,有以下几方面优势∶ Spring Boot 使开发变得简单,提供了丰富的解决方案,快速集成各种解决方案提升开发效率。 Spring Boot 使配置变得简单,提供了丰富的 Starters,集成主流开源产品往往只需要简单的配置即可。 Spring Boot 使部署变得简单,其本身内嵌启动容器,仅仅需要一个命令即可启动项目,结合Jenkins、Docker 自动化运维非常容易实现。 Spring Boot 使监控变得简单,自带监控组件,使用 Actuator轻松监控服务各项状态。 从软件发展的角度来讲,越简单的开发模式越流行,简单的开发模式解放出更多生产力,让开发人员可以避免将精力耗费在各种配置、语法所设置的门槛上,从而更专注于业务。这点上,Spring Boot已尽可能地简化了应用开发的门槛。Spring Boot 所集成的技术栈,涵盖了各大互联网公司的主流技术,跟着 Spring Boot 的路线去学习,基本可以了解国内外互联网公司的技术特点。 四:Spring Boot 特点: 1. 独立运行的 Spring 项目 Spring Boot 可以以 jar 包的形式独立运行,Spring Boot 项目只需通过命令“ java–jar xx.jar” 即可运行。 2. 内嵌 Servlet 容器 Spring Boot 使用嵌入式的 Servlet 容器(例如 Tomcat、Jetty 或者 Undertow 等),应用无需打成 WAR 包 。 3. 提供 starter 简化 Maven 配置 Spring Boot 提供了一系列的“starter”项目对象模型(POMS)来简化 Maven 配置。 4. 提供了大量的自动配置 Spring Boot 提供了大量的默认自动配置,来简化项目的开发,开发人员也通过配置文件修改默认配置。 5. 自带应用监控 Spring Boot 可以对正在运行的项目提供监控。 6. 无代码生成和 xml 配置 Spring Boot 不需要任何 xml 配置即可实现 Spring 的所有配置。 五.SpringBoot怎么样 介绍了一大堆,那 SpringBoot 的开箱即用是怎么体现的呢。 SpringBoot 官方推荐的构建应用的方式是使用 Spring Initializr,直接在网页上选择好构建工具、语言、SpringBoot 版本,填好自己的项目名和初始依赖,然后点Generate 按钮,就能下载一个构建好的工程的zip包,只需要把这个包解压之后导入IDE就可以了。 这已经是一个包含依赖的、完整的、可独立运行的springboot应用了!你所需要做的就是往里面填充自己的业务代码! 当然,如果能直接使用IDE来进行上述操作可以让这个过程变得更顺滑。如果你使用的是 IDEA 商业版的话,新建工程的时候直接有 Spring 的选项;如果是IDEA社区版的话,可以安装 Spring Assistant 这个插件可以实现同样的功能。它们的原理是帮你把连接 Spring Initializr 并下载解压这个过程自动化了,所以只需要保持网络畅通就行了。 那如果要用原生的springMVC来实现这个事情就复杂了,可以看看右边我大概罗列的这些步骤,当时学的时候让我我非常头疼。要单独安装Tomcat,安装的过程中要注意版本和当前的spring版本是否兼容,手动引入spring各个模块的依赖。pom.xml就不说了,maven工程都要用到,然后还有web.xml-用来配置servlet、拦截规则、字符编码器等等,applicationContext.xml,springmvc.xml 等一大堆xml文件…… 这个过程对初学者非常不友好,记忆这些步骤和配置文件能让人崩溃,xml这种表达方式又不是很直观。这些东西称为脚手架,在小公司里面会搭建这些东西就可以算半个师傅了,小弟们就可以在搭好的架子里面写业务代码了。 再聊回微服务,试想一下,如果我们要跟上时代的步伐,使用微服务去开发软件,每个功能模块都部署成一个单独的服务,这个时候我们再使用纯粹的 Spring 去开发,每开发一个服务都需要重复的搭建项目骨架,然后编写各种配置文件,几十几百个服务加起来,这部分工作量是很大的,这还不算业务代码的开发时间。这种时候就是 SpringBoot 发挥它开箱即用的特质的时候了。然后多个微服务之间再通过 Spring 全家桶里面的 SpringCloud 进行管理,比如服务注册、服务发现等等。所以我们现在说 SpringBoot 是 Java 企业级开发的一站式解决方案。 六:总结 Spring Boot 的设计初衷是解决 Spring 各版本配置工作过于繁重的问题,简化初始搭建流程、降低开发难度,使开发人员只需要专注应用程序的功能和业务逻辑实现,而不用在配置上花费太多时间。 Spring Boot 使用“默认大于配置”的理念,提供了很多已经集成好的方案,以便程序员在开发应用程序时能做到零配置或极简配置。同时,为了不失灵活性,它也支持自定义操作。 Spring Boot 内置了50多种 starter,以便快速配置和使用。比如,要使用 Email 服务,只需要添加"spring-boot-starter-mail"依赖,然后直接调用 JavaMailSender 接口发送邮件。 Spring Boot 可以看作是 Spring 框架的扩展和自动化,它消除了在 Spring 中需要进行的 XML(Extensible Markup Language) 文件配置,使得开发变得更快、更高效、更自动化。 Spring Boot 帮我们省去了烦琐的配置工作,开发人员只需要专注业务逻辑开发即可。 用一句话来说明:Spring Boot 是 Spring 框架的扩展和自动化。 七:Spring Boot和Spring的关系 不是:从马车到汽车那种交通出行的颠覆,从燃油车到纯电动车那种能源利用的变革,从人工驾驶到AI智能那种驾驶方式的升级。总之,不是产品的升级换代,不是谁要替换谁。 而是:汽车从手动挡变成自动挡,然后增加无钥匙进入、一键启动、发动机自动起停、自动泊车、定速巡航...等功能。越来越多新上市的车型开始标配上面这些新技术,让你开车更省心更轻松,把更多的注意力放在驾驶上。 八:Spring和Spring Boot区别 ———————————————— 版权声明:本文为CSDN博主「林在闪闪发光」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/weixin_68829137/article/details/128789375
上滑加载中
推荐直播
-
HDC深度解读系列 - Serverless与MCP融合创新,构建AI应用全新智能中枢2025/08/20 周三 16:30-18:00
张昆鹏 HCDG北京核心组代表
HDC2025期间,华为云展示了Serverless与MCP融合创新的解决方案,本期访谈直播,由华为云开发者专家(HCDE)兼华为云开发者社区组织HCDG北京核心组代表张鹏先生主持,华为云PaaS服务产品部 Serverless总监Ewen为大家深度解读华为云Serverless与MCP如何融合构建AI应用全新智能中枢
回顾中 -
关于RISC-V生态发展的思考2025/09/02 周二 17:00-18:00
中国科学院计算技术研究所副所长包云岗教授
中科院包云岗老师将在本次直播中,探讨处理器生态的关键要素及其联系,分享过去几年推动RISC-V生态建设实践过程中的经验与教训。
回顾中 -
一键搞定华为云万级资源,3步轻松管理企业成本2025/09/09 周二 15:00-16:00
阿言 华为云交易产品经理
本直播重点介绍如何一键续费万级资源,3步轻松管理成本,帮助提升日常管理效率!
回顾中
热门标签