使用SpringBoot快速构建Web API

news2024/9/22 4:16:59

Dubbo 框架现在在国内的中小企业当中已经成为 Java 生态下服务化的事实标准,出现这种状态的原因很多,比如 Dubbo 框架设计优秀、文档和资料丰富、配置灵活、特性丰富等,但最主要的,我认为是 Java 开发人员对速度这一因素的痴迷。

不可否认,Dubbo 框架设计和实现之初就将其自身定位为一款基于 TCP 长连接通信的高性能服务治理框架,但是,对于很多中小企业来说,不管从速度还是并发度,根本就没有到非要使用像 Dubbo 这样基于 TCP 长连接服务框架的程度。

笔者认为,不分场景和现状盲目选型 Dubbo 框架,或许就是 Dubbo 框架成为 java 生态下服务治理框架事实标准的原因。

Dubbo 框架虽然有很多优点,也确实面向高强度的互联网应用场景,且在多家知名的互联网企业的生产环境得到验证,但也并非没有缺点:

  • 只限于 Java 应用之间的服务调用。
  • 服务访问方需要依赖 API 以及关联依赖,在很多场景下导致依赖管理混乱的问题。
  • 核心项目人员转岗或者离职之后,项目不再有人专职投入维护和升级,虽然功能够用,但任何一个开源项目无推动者,无活跃社区的情况下,其生命走向只有一个,而这几乎是国内开源项目的共同宿命。

作为一名理性的研发人员,在项目技术选型的时候,需要综合考虑多种方案的优缺点,并根据现状进行权衡,实际上,对于大部分项目来说,性能可能并非技术选型的核心因素,开放和互通或许才是。

是要以互通性作为核心因素进行技术选型并构建一套开放繁荣的生态体系,还是以性能为核心因素进行技术选型构建一套封闭高效的生态体系,需要大家灵活把握,而本节我们将更多以 Web API 的形式,向大家展示如何基于 SpringBoot 构建一套开放、互通、稳定的 Web API 微服务体系。

使用 SpringBoot 构建 Web API 有几种选择,要么使用 spring-boot-starter-jersey 构建 RESTful 风格的 Web API,要么选择 spring-boot-starter-hateoas 构建更加有关联性和相对“智能”的 Web API,但笔者认为这些都有点儿“阳春白雪”。

对于大部分开发人员来说,HTTP 协议的 GET 和 POST 是直觉上最自然的选择,所以,我们选择使用最“下里巴人”的方式来构建 Web API。

Web API 强调统一和互通,所以,首先我们需要定义一套内外认知一致的 Web API 开发和访问规范,在 JSON 盛行、社群庞大的背景下,我们的 Web API 方案采用 JSON 作为数据交互格式并定义统一的协议格式,然后通过 HTTP 以及周边支持完成微服务的对外服务和开放访问。

1. 定义 Web API 规范

首先从服务访问的交互上来说,我们可以选择较为纯粹的 JSON RPC Over HTTP 的方式,如图 1 所示。


图 1 JSON RPC Over HTTP 示意图

也可以选择约束相对松一些的 RPC Over HTTP 方式,如图 2 所示。


图 2 一般意义上基于HTTP的RPC交互示意图

相对于纯粹的 JSON RPC Over HTTP 方案,后者对请求格式不做任何限制(所以也同样支持纯粹 JSON 形式的请求格式),只对响应(Response)做 JSON 格式上的统一规定。

好处是,客户端各种工具都能够很好的支持,服务器端 SpringMVC 也可以少做 HttpMessage 转换,给服务的开发者和访问者都提供了比较灵活的操作余地,至于请求的类型差异,我们可以通过配套生成的 API 文档进行补足。

不管怎么样,我们选择基于后一种方案进行说明,现在剩下的主要工作就是定义服务响应格式,只有规范和统一了服务的响应格式,才能让内部和外部的服务访问者形成统一的认知。

以上面同样的方式“复制”对我们提供的任何 Web API 的访问行为,减少用户的接入成本,所以,姑且我们简单规定一个服务的响应格式如下:

  1. { "code" : 1, "error" : "XXXXX", "data" : { ... }}

其中,code 表示调用结果的状态,0 表示成功,非 0 表示失败,并且失败情况下 error 字段将提供对应的错误信息描述,data 字段用于规范定义特定于 Web API 的响应内容。

有了这样的规范定义,不同的开发者就可以根据情况选择打造对应的工具或者 SDK 了。而 Web API 的服务提供者也同样可以根据该规范考虑如何简化 Web API 的开发,或者通过约束减少规范认知不足可能导致的问题。

既然是使用 SpringBoot 构建 Web API,那么显然我们现在更加关注后者。

2. 根据规范构建 Web API

针对同样的汇率查询服务,这回我们采用 Web API 的形式对外提供服务。

使用 http://start.spring.io 构建新的 SpringBoot 项目,使其依赖 spring-boot-starter-web 模块:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <modelVersion>4.0.0</modelVersion>
  6. <groupId>com.keevol.springboot.chapter4</groupId>
  7. <artifactId>currency-webapi</artifactId>
  8. <version>0.0.1-SNAPSHOT</version>
  9. <packaging>jar</packaging>
  10. <name>currency-webapi</name>
  11. <description>Demo project for Spring Boot</description>
  12. <parent>
  13. <groupId>org.springframework.boot</groupId>
  14. <artifactId>spring-boot-starter-parent</artifactId>
  15. <version>1.3.1.RELEASE</version>
  16. <relativePath /> <!-- lookup parent from repository -->
  17. </parent>
  18. <properties>
  19. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  20. <java.version>1.8</java.version>
  21. </properties>
  22. <dependencies>
  23. <dependency>
  24. <groupId>org.springframework.boot</groupId>
  25. <artifactId>spring-boot-starter-web</artifactId>
  26. </dependency>
  27. <dependency>
  28. <groupId>com.keevol.springboot</groupId>
  29. <artifactId>currency-rates-service</artifactId>
  30. <version>1.0-SNAPSHOT</version>
  31. </dependency>
  32. </dependencies>
  33. <build>
  34. <plugins>
  35. <plugin>
  36. <groupId>org.springframework.boot</groupId>
  37. <artifactId>spring-boot-maven-plugin</artifactId>
  38. </plugin>
  39. </plugins>
  40. </build>
  41. </project>

因为我们已经实现了 CurrencyRateService,所以,可以直接将其作为项目依赖的一部分(当然,这样也让我们的 Web API 看起来更像一个适配网关了)。

我们直接使用 SpringMVC 构建对应的 Controller 对外提供 Web API 的访问如下:

  1. @Controller
  2. public class CurrencyRateQueryController {
  3. @Autowired
  4. private CurrencyRateService currencyRateService;
  5. @RequestMapping(value = "/", method = RequestMethod.GET)
  6. @ResponseBody
  7. public ExchangeRate quote(String symbol) throws IOException {
  8. return currencyRateService.quote(CurrencyPair.from(symbol));
  9. }
  10. }

spring-boot-starter-web 默认会提供一系列的 HttpMessageConverter 用于对请求参数和响应结果做类型转换。所以,ExchangeRate 类型将通过默认 HttpMessageConverter 序列中的 MappingJackson2HttpMessageConverter 转换成对应的 JSON 响应结果,类似于:

  1. { currencyPair: { symbol: “USD/CNY” }, bidPrice: 6.67, askPrice: 6.56}

整个 Web API 的功能流程算是跑通了,但跟我们之前定义的 Web API 规范却没有关系,所以,下一步我们要做的事情就是在此基础上规范 HTTP 响应格式,使其遵循我们之前定义的 Web API 规范,从而任何访问我们提供的 Web API 访问者都可以相同的认知使用这些 Web API,进而也可以打造和沉淀相应的工具或者类库。

我们定义的 Web API 规范并非最优,也并非必要,如果团队成员的认知差不多,那么直接使用 HTTP Status Code 结合直接的值类型响应就可以了,我们给出的 Web API 规范考虑了更多因素后做出的一个折中方案,但任何方案设计是否完美并非最主要的,执行才是。

要开发符合我们的 Web API 规范的 Web API,最少有两种方案可以选择:

  • 显式的强类型封装方式(explicit type wrapper)
  • 隐式的自动转换方式(implicit conversion)

显式的强类型封装方式的出发点是说,既然 spring-boot-starter-web 已经提供了 MappingJackson2HttpMessageConverter 用于对象类型到 JSON 的类型转换,那么,我们只要提供针对 Web API 规范的 Java 对象类型作为所有 Web API 处理方法的返回值就可以了,比如:

  1. public class WebApiResponse<T> {
  2. public static final int SUCCESS_CODE = 0;
  3. public static final int ERROR_CODE = 1;
  4. private int code;
  5. private String error;
  6. private T data;
  7. // getters, setters, toString(), etc.
  8. }

然后,所有的 Web API 的处理方法统一定义为返回 WebApiResponse 作为结果类型:

  1. @RequestMapping(value = "/", method = RequestMethod.GET)
  2. @ResponseBody
  3. public WebApiResponse<ExchangeRate> quote(String symbol) throws IOException {
  4. WebApiResponse<ExchangeRate> response = new WebApiResponse<>();
  5. response.setCode(WebApiResponse.SUCCESS_CODE);
  6. response.setData(currencyRateService.quote(CurrencyPair.from(symbol)));
  7. return response;
  8. }

不过,这种模式过于强调规范的管控,对开发者来说不是太友好,即使我们通过 Builder 模式来简化 WebApiResponse 的构造过程,比如:

  1. public class WebApiResponse<T> {
  2. public static final int SUCCESS_CODE = 0;
  3. public static final int ERROR_CODE = 1;
  4. private int code;
  5. private String error;
  6. private T data;
  7. public static <T> WebApiResponse<T> success(T data) {
  8. WebApiResponse<T> response = new WebApiResponse<>();
  9. response.setCode(SUCCESS_CODE);
  10. response.setData(data);
  11. return response;
  12. }
  13. public static <T> WebApiResponse<T> error(String errorMessage) {
  14. return WebApiResponse.<T>error(errorMessage, ERROR_CODE);
  15. }
  16. // ...
  17. @RequestMapping(value = "/", method = RequestMethod.GET)
  18. @ResponseBody
  19. public WebApiResponse<ExchangeRate> quote(String symbol) throws IOException {
  20. return WebApiResponse.success(currencyRateService.quote(CurrencyPair.from(symbol)));
  21. }
  22. }

但从 API 的使用者角度来看,这种设计并非最优,最好的方式其实应该是隐式的自动转换方式。在隐式的自动转换方式下,用户的 Web API 处理方法定义保持不变,直接返回最原始的值类型(比如 ExchangeRate):

  1. @RequestMapping(value = "/", method = RequestMethod.GET)
  2. @ResponseBody
  3. public ExchangeRate quote(String symbol) throws IOException {
  4. return currencyRateService.quote(CurrencyPair.from(symbol));
  5. }

通过在框架层面对原始的值类型进行符合规范行为的封装,最终返回给用户的响应结果“自动”的或者说以“不打扰 API 开发者”的形式变成了符合我们 Web API 规范的响应结果形式。

要达到隐式的自动转换方式的效果,最简单粗暴的做法就是完全覆盖 Web 应用的配置,只配置一个自定义处理 JSON 转换的 HttpMessageConverter 实现,比如:

  1. public class JsonHttpMessageConverter extends AbstractHttpMessage-Converter<Object> {
  2. @Override
  3. protected boolean supports(Class<?> clazz) {
  4. return !clazz.isPrimitive();
  5. }
  6. @Override
  7. protected Object readInternal(Class<?> aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException {
  8. return null;
  9. }
  10. @Override
  11. protected void writeInternal(Object o, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException {
  12. httpOutputMessage.getHeaders().add("Content-Type", "application/json");
  13. // 其他header设置
  14. // toJson()方法中可以使用jackson或者fastjson等类库完成对象到json的转换
  15. httpOutputMessage.getBody().write(toJson(o));
  16. httpOutputMessage.getBody().flush();
  17. }
  18. }

但是,这会导致一些问题或者不便:
1)打破了 SpringBoot 对 SpringMVC 的完备支持,对于大部分已经很熟悉 SpringMVC 框架中各种功能和类库使用的读者来说,这些可能不再有效;

2)SpringBoot 提供的 spring-boot-starter-web 模块的默认配置项都不再有效,比如 SpringBoot 参考文档中的 spring.jackson.serialization.indent_output=true 之类的配置项,这显然是在舍弃已有的良好文档和功能支持;

3)因为现在只有一个 HttpMessageConverter 处理单一类型的 Web 请求和响应,如果同一项目中有类似视图渲染的需求,则无法满足需求。

所以,为了能够不打破开发者对 SpringMVC 框架以及 SpringBoot 提供的 Web 应用各项功能支持的认知,最稳妥的做法是,在 SpringBoot 原有 Web 应用默认配置的基础上增加新的 HttpMessageConverter,专门处理 Web API 响应结果使其符合我们的 Web API 规范形式。

要达到这个目的,我们可以提供自定义的配置:

  1. @Configuration
  2. public class WebApiConfiguration extends WebMvcConfigurerAdapter {
  3. @Override
  4. public void extendMessageConverters(List<HttpMessageConvert-er<?>> converters) {
  5. // 添加或者插入我们自定义的HttpMessageConverter实现类
  6. // converters.add(converter)或者converters.add(0, converter)
  7. }
  8. }

extendMessageConverters 属于已经添加过默认 HttpMessageConverter 序列的参数(比如针对 String 的 HttpMessageConverter,或者针对 byte[] 的 Http-MessageConverter 等),所以,我们只要在其基础上添加或者插入我们的 HttpMessageConverter 实现类就可以了。

不过,这里有一个比较尴尬的地方,这可能也是 Spring 框架多处设计中都存在的尴尬,即循环条件判断应用哪个类的时候,条件判断 API 开放不足:

  1. for(HttpMessageConverter converter: converters){
  2. if(converter.canWrite(clazz, media)) {
  3. converter.write(..);
  4. }
  5. }

在 HttpMessageConverter 的场景中就是,我们只能根据目标对象的类型以及 mediaType 来判断是否应该使用当前这个 HttpMessageConverter,如果需要在这两种判断条件都相同的情况下,还要根据其他条件来判定是否应该使用当前 HttpMessageConverter,此时这种设计显然就无法满足需求了。尴尬之处就在于此。

对于我们的 Web API 规范这个实现场景来说,如果想继续享受原有的 MappingJackson2HttpMessageConverter 提供的功能和配置,就不得不继承并覆写(Override)相应方法,而不是略过 MappingJackson2HttpMessageConverter,然后在另一个 HttpMessageConverter 中只是必要的时候引用它(组合优于继承)。

不管怎么样,我们推荐使用隐式的自动转换方式为用户提供透明的 Web API 规范行为。

3. Web API 的短板和补足

相对于 Dubbo 这种强类型的服务框架,Web API 有强类型支持(Not Typesafe),在开发过程中,自然也无法享受到像 IDE 自动提示之类的功能,所以,对于 Web API 的使用者来说,需要与 Web API 的提供者沟通之后才能知道如何访问 Web API 的详细信息,比如应该传哪些参数,返回的响应结果又应该是什么格式的。

为了缓解这个问题,我们可以使用自动根据代码元信息生成 API 文档的方式来补足这块短板,像 Swagger 这样的项目,已经是比较成熟的 API 文档方案了。

不过,让每一个 Web API 项目都自己去初始化 API 文档相关的设置显然并不是很好的用户体验,为了服务到位,我们可以遵循 SpringBoot 的行事风格,新建一个 spring-boot-starter-webapi 这样的自动配置模块,其提供的主要特性包括但不限于:

  • 提供针对我们 Web API 规范的功能支持,即提供显式的强类型封装方式或者隐式的自动转换方式的功能实现。
  • 提供 API 文档相关功能的配置和设置。
  • 提供统一的 Web API 访问错误处理逻辑。

这样,任何 Web API 的开发者和提供者只要新建 SpringBoot 应用,然后依赖 spring-boot-starter-webapi,就可以自动享有以上所有特性支持了。

以下是一个 spring-boot-starter-webapi 原型项目的 pom.xml 定义:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <modelVersion>4.0.0</modelVersion>
  6. <parent>
  7. <groupId>org.springframework.boot</groupId>
  8. <artifactId>spring-boot-starters</artifactId>
  9. <version>1.2.5.RELEASE</version>
  10. </parent>
  11. <groupId>com.keevol.springboot</groupId>
  12. <artifactId>spring-boot-starter-webapi</artifactId>
  13. <version>1.0.0-SNAPSHOT</version>
  14. <packaging>jar</packaging>
  15. <name>spring-boot-starter-webapi</name>
  16. <url></url>
  17. <properties>
  18. <java.version>1.8</java.version>
  19. <file.encoding>UTF-8</file.encoding>
  20. </properties>
  21. <build>
  22. <plugins>
  23. <plugin>
  24. <groupId>org.apache.maven.plugins</groupId>
  25. <artifactId>maven-compiler-plugin</artifactId>
  26. <version>3.2</version>
  27. <configuration>
  28. <source>${java.version}</source>
  29. <target>${java.version}</target>
  30. <encoding>${file.encoding}</encoding>
  31. </configuration>
  32. </plugin>
  33. </plugins>
  34. </build>
  35. <dependencies>
  36. <dependency>
  37. <groupId>org.springframework.boot</groupId>
  38. <artifactId>spring-boot-starter-web</artifactId>
  39. </dependency>
  40. <dependency>
  41. <groupId>io.springfox</groupId>
  42. <artifactId>springfox-swagger2</artifactId>
  43. <version>2.1.2</version>
  44. </dependency>
  45. <dependency>
  46. <groupId>io.springfox</groupId>
  47. <artifactId>springfox-swagger-ui</artifactId>
  48. <version>2.1.2</version>
  49. </dependency>
  50. <dependency>
  51. <groupId>javax.servlet</groupId>
  52. <artifactId>javax.servlet-api</artifactId>
  53. <version>${servlet-api.version}</version>
  54. </dependency>
  55. </dependencies>
  56. </project>

以及对应的 JavaConfig 配置类示例:

  1. @Configuration
  2. @EnableSwagger2
  3. @ComponentScan("com.wacai.springboot.webapi.errors")
  4. @AutoConfigureAfter(WebMvcAutoConfiguration.class)
  5. public class WebApiAutoConfiguration extends WebMvcConfigurerAdapter {
  6. protected Logger logger = LoggerFactory.getLogger(WebApiAuto - Configuration.class);
  7. @Value("${springfox.api.group:[your api group name]}")
  8. private String apiGroupName;
  9. @Value("${springfox.api.title:[set a api title via 'springfox.api.title']}")
  10. private String title;
  11. @Value("${springfox.api.description:[add your api description via 'springfox.api.description'}]")
  12. private String desc;
  13. @Value("${springfox.api.version:[set specific api version via 'springfox.api.version'}]")
  14. private String version;
  15. @Value("${springfox.api.termsOfServiceUrl:[set termsOf-ServiceUrl via 'springfox.api.termsOfServiceUrl']}")
  16. private String termsOfServiceUrl;
  17. @Value("${springfox.api.contact:[set contact via 'springfox.api.contact'}]")
  18. private String contact;
  19. @Value("${springfox.api.license:Your WebAPI License}")
  20. private String license;
  21. @Value("${springfox.api.licenseUrl:http://keevol.com}")
  22. private String licenseUrl;
  23. @Autowired
  24. private TypeResolver typeResolver;
  25. @Bean
  26. public Docket api() {
  27. return new Docket(DocumentationType.SWAGGER_2).groupName(apiGroupName)
  28. .apiInfo(new ApiInfo(title, desc, version, termsOf - ServiceUrl, contact, license, licenseUrl)).select()
  29. .apis(RequestHandlerSelectors.any()).paths(excludedPathSelector()).build().pathMapping("/")
  30. .directModelSubstitute(Date.class, String.class).genericModelSubstitutes(ResponseEntity.class)
  31. .alternateTypeRules(newRule(
  32. typeResolver.resolve(DeferredResult.class,
  33. typeResolver.resolve(ResponseEntity.class, WildcardType.class)),
  34. typeResolver.resolve(WildcardType.class)))
  35. .useDefaultResponseMessages(false)
  36. .globalResponseMessage(RequestMethod.GET, newArrayList(new ResponseMessageBuilder().code(500)
  37. .message("服务出错啦~").responseModel(new ModelRef("Error")).build()))
  38. .forCodeGeneration(true);
  39. }
  40. // ...
  41. }

关于如何将 WebApiAutoConfiguration 配置到 META-INF/spring.factories 并发布项目则不再赘述。有了 spring-boot-starter-webapi 之后,Web API 形式的微服务开发者所要做的仅仅是把它加为项目依赖:

  1. <dependency>
  2. <groupId>com.keevol.springboot</groupId>
  3. <artifactId>spring-boot-starter-webapi</artifactId>
  4. <version>1.0.0-SNAPSHOT</version>
  5. </dependency>

然后像往常那样写 SpringMVC 的 @Controller 或者 @RestController 就可以了,现在,我们可以直接享受 API 文档的自动生成。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/67971.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

详解设计模式:解释器模式

解释器模式&#xff08;interpreter pattern&#xff09;&#xff0c;是在 GoF 23 种设计模式中定义了的行为型模式。 解释器模式 这种模式被用在 SQL 解析、符号处理引擎等。 解释器模式 给定一个语言&#xff0c;定义它的文法的一种表示&#xff0c;并定义一个解释器&#xf…

HTTP状态码是什么?

文章目录HTTP状态码1. 消息&#xff1a;1xx2. 成功&#xff1a;2xx3. 重定向&#xff1a;3xx4. 请求错误&#xff1a;4xx5. 服务器错误&#xff1a;5xxHTTP状态码 HTTP 状态码&#xff08;HTTP Status Code&#xff09;是一个表示服务器响应状态的 3 位整数代码。比如当服务器…

[Database] 关系型数据库中的MVCC是什么?怎么理解?原理是什么?MySQL是如何实现的?

文章目录前言并发控制并发控制的实现与锁的本质MVCC是什么&#xff1f;MVCC的多版本&#xff08;Multi-Version&#xff09;指的是什么&#xff1f;MVCC的实现方式&#xff1f;MySQL的实现PostgreSQL的实现结语前言 在并发场景下&#xff0c;如果我们不对数据做保护&#xff0c…

分布式文件存储系统FastDFS[1]-介绍以及安装

一、分布式文件存储 1.分布式文件存储的由来 在我们的项目中有很多需要存储的内容出现&#xff0c;比如图片&#xff0c;视频&#xff0c;文件等等&#xff0c;在早期的时候用户量不大&#xff0c;产生的文件也不是很多&#xff0c;这时我们可以把文件和服务程序放在一个服务器…

STAAD.Pro CONNECT Edition

STAAD.Pro CONNECT Edition Bentley STAAD或STAAD Pro软件是分析和设计世界第三世界的第一款最佳工程软件&#xff0c;对于金属、混凝土、木材、铝和冷金属的专业选择&#xff0c;设计几乎所有类型的塑料、石化、隧道、桥梁、蜡烛等结构专业的功能和软件中任何数据大小的使用。…

Java课程案例学习(3)

2.1 学生管理系统实现步骤 案例需求 针对目前我们的所学内容&#xff0c;完成一个综合案例&#xff1a;学生管理系统&#xff01;该系统主要功能如下&#xff1a; 添加学生&#xff1a;通过键盘录入学生信息&#xff0c;添加到集合中 删除学生&#xff1a;通过键盘录入要删…

Jcmd 虚拟机诊断利器

Jcmd 虚拟机诊断利器 Java虚拟机&#xff08;JVM&#xff09;是运行Java程序的抽象化的计算器。今天&#xff0c;来学习下如何轻松诊断正在运行的JVM。 JDK本身中提供了许多可用的工具&#xff0c;可以用于各种开发、监视和故障排除活动。推荐使用jcmd&#xff0c;简单易懂&a…

[附源码]计算机毕业设计南通大学福利发放管理系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

ThinkPHP6综合业务管理系统

有需要请私信或看评论链接哦 可远程调试 ThinkPHP6综合业务管理系统一 介绍 综合业务管理系统基于ThinkPHP6开发&#xff0c;数据库mysql&#xff0c;前端Layui&#xff0c;后台模板ok-admin。管理员登录系统后可对场所&#xff0c;分类&#xff0c;用户&#xff0c;运营商&am…

图片扫描仪第一弹,我实现了一个办公位简笔画

来一波对比 其实我工位还有点手办&#xff0c;一个植物&#xff0c;实在不会画&#xff0c;于是画了个基础版的。 办公位简笔画 虽然&#xff0c;这个工位上没有一个动画&#xff0c;但是图形和实物神似&#xff0c;所以可以用另一个动总结这个简笔画&#xff0c;那就是生动…

量子计算(十一):常见逻辑门以及含义

文章目录 常见逻辑门以及含义 一、Hadamard&#xff08;H&#xff09;门 二、Pauli-X 门 三、Pauli-Y 门 四、Pauli-Z 门 五、旋转门&#xff08;rotation operators&#xff09; 1、RX&#xff08;θ&#xff09;门 2、RY&#xff08;θ&#xff09;门 3、RZ&#xf…

深入理解计算机系统

hi&#xff0c;大家好&#xff0c;我是大师兄&#xff0c;让我们一起复习了一遍计算机系统核心知识&#xff0c;希望大家早日掌握这些核心知识&#xff0c;打造自己坚实的基础&#xff0c;为自己目标慢慢积累&#xff0c;等到自己春天的到来。详细点击查看-> 极客星球。计算…

PDF解除密码怎么操作?教你一键解除密码

我们在处理工作文件时&#xff0c;经常会有一些文件是加密状态的&#xff0c;这种文件必须要输入密码才可以打开&#xff0c;这时候就造成了很多不便&#xff0c;如果文件需要打印&#xff0c;我们就必须先输入密码&#xff0c;而且在每次查看文件前&#xff0c;都必须输入密码…

记录一次 在linux 搭建的mysql迁移到docker容器中

上篇文章写道&#xff0c;我腾讯云被攻击了&#xff0c;然后我各种删除木马文件&#xff0c;但在这个过程中&#xff0c;有些系统文件没认到&#xff0c;以为是木马文件&#xff0c;就删除了&#xff0c;各种处理无果&#xff0c;打开腾讯云的救援模式&#xff0c;备份了些重要…

【数据库】mysql索引

DB索引&#xff1a; 如果表创建了索引&#xff0c;那么DB会维护一张关于索引和主键关系的表 好处&#xff1a;快速查找数据 缺点&#xff1a;要额外维护一张表&#xff0c;会额外占用空间&#xff0c;对于表数据的更新操作都涉及更新索引 索引的操作 创建索引&#xff1a;…

LeetCode 74. 搜索二维矩阵

&#x1f308;&#x1f308;&#x1f604;&#x1f604; 欢迎来到茶色岛独家岛屿&#xff0c;本期将为大家揭晓LeetCode 74. 搜索二维矩阵&#xff0c;做好准备了么&#xff0c;那么开始吧。 &#x1f332;&#x1f332;&#x1f434;&#x1f434; 一、题目名称 LeetCode 74.…

[Android] [ROOT] Magisk(魔术师/面具) 设置以及必装模块的安装

&#x1f341;简介 当我们给手机刷入Magisk(面具)后&#xff0c;等于获得了安卓系统的最高管理员权限&#xff0c;也就是root权限。获取root权限后的设备相对于未root的设备&#xff0c;安全性会有一定的降低。也许是基于这个原因&#xff0c;现在大部分应用都会检测你的设备是…

xcode常用功能与操作

查看target是静态还是动态库 选中target&#xff0c;点击Build Settings tag&#xff0c;然后再右上角搜索框输入 mach&#xff0c;查看Mach-O type的值即可 mach-o文件类型分为&#xff1a; Executable&#xff1a;应用的主要二进制 Dylib Library&#xff1a;动态链接库&am…

栈与队列5:逆波兰表达式求值

主要是我自己刷题的一些记录过程。如果有错可以指出哦&#xff0c;大家一起进步。 转载代码随想录 原文链接&#xff1a; 代码随想录 leetcode链接&#xff1a;150. 逆波兰表达式求值 题目&#xff1a; 根据 逆波兰表示法&#xff0c;求表达式的值。 有效的算符包括 、-、*、…

安卓玩机搞机技巧综合资源------如何提取手机分区 小米机型代码分享等等 【一】

&#x1f495;&#x1f495;&#x1f495;&#x1f495;&#x1f495;&#x1f495;&#x1f495;&#x1f495;&#x1f495;&#x1f495;&#x1f495;提取手机分区方法列举&#x1f495;&#x1f495;&#x1f495;&#x1f495;&#x1f495;&#x1f495;&#x1f495;&a…