技术阅读周刊第十一期

8e7f8fd63ca2050d0c9bb84b8c6a7ec8.png

技术阅读周刊,每周更新。

历史更新

A Comprehensive guide to Spring Boot 3.2 with Java 21, Virtual Threads, Spring Security, PostgreSQL, Flyway, Caching, Micrometer, Opentelemetry, JUnit 5, RabbitMQ, Keycloak Integration, and More! (10/17) | by Jonathan Chevalier | Nov, 2023 | Medium

URL: https://medium.com/@jojoooo/exploring-a-base-spring-boot-application-with-java-21-virtual-thread-spring-security-flyway-c0fde13c1eca#551c

本文讲解了基于最新的 Spring Boot3.2 和 Java 21 所使用到的技术栈

数据库

数据库使用 Postgres15 和 flyway 来管理数据库 schema 的迁移。8fbbd8a4bc2deed561ad191d89cb0a27.png

异常处理

Spring6 实现了新的 RFC9457规范,实现以下接口:

@Slf4j
@ControllerAdvice
@RequiredArgsConstructor
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

  // Process @Valid
  @Override
  protected ResponseEntity<Object> handleMethodArgumentNotValid(
      @NonNull final MethodArgumentNotValidException ex,
      @NonNull final HttpHeaders headers,
      @NonNull final HttpStatusCode status,
      @NonNull final WebRequest request) {
    log.info(ex.getMessage(), ex);

    final List<ApiErrorDetails> errors = new ArrayList<>();

    for (final ObjectError err : ex.getBindingResult().getAllErrors()) {
      errors.add(
          ApiErrorDetails.builder()
              .pointer(((FieldError) err).getField())
              .reason(err.getDefaultMessage())
              .build());
    }

    return ResponseEntity.status(BAD_REQUEST)
        .body(this.buildProblemDetail(BAD_REQUEST, "Validation failed.", errors));
  }


  private ProblemDetail buildProblemDetail(
      final HttpStatus status, final String detail, final List<ApiErrorDetails> errors) {
  
    final ProblemDetail problemDetail =
        ProblemDetail.forStatusAndDetail(status, StringUtils.normalizeSpace(detail));
    
    // Adds errors fields on validation errors, following RFC 9457 best practices.
    if (CollectionUtils.isNotEmpty(errors)) {
      problemDetail.setProperty("errors", errors);
    }
  
   
    return problemDetail;
  }
{
    "type": "about:blank",
    "title": "Bad Request",
    "status": 400,
    "detail": "Validation failed.",
    "instance": "/management/companies",
    "errors": [
        {
            "pointer": "name",
            "reason": "must not be blank"
        },
        {
            "pointer": "slug",
            "reason": "must not be blank"
        }
    ]
}

应用异常

@Getter
public class RootException extends RuntimeException {

  @Serial private static final long serialVersionUID = 6378336966214073013L;

  private final HttpStatus httpStatus;
  private final List<ApiErrorDetails> errors = new ArrayList<>();

  public RootException(@NonNull final HttpStatus httpStatus) {
    super();
    this.httpStatus = httpStatus;
  }

  public RootException(@NonNull final HttpStatus httpStatus, final String message) {
    super(message);
    this.httpStatus = httpStatus;
  }
}

@ExceptionHandler(RootException.class)
public ResponseEntity<ProblemDetail> rootException(final RootException ex) {
  log.info(ex.getMessage(), ex);
  
  // Uses default message, can be adapted to use ex.getMessage().
  final ProblemDetail problemDetail =
      this.buildProblemDetail(
          ex.getHttpStatus(), API_DEFAULT_REQUEST_FAILED_MESSAGE, ex.getErrors());
  
  return ResponseEntity.status(ex.getHttpStatus()).body(problemDetail);
}

{
    "type": "about:blank",
    "title": "Internal Server Error",
    "status": 500,
    "detail": "Request failed.",
    "instance": "/back-office/hello-world"
}

异常降级

@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(Throwable.class)
public ProblemDetail handleAllExceptions(final Throwable ex, final WebRequest request) {
  log.warn(ex.getMessage(), ex);

  this.slack.notify(format("[API] InternalServerError: %s", ex.getMessage()));

  return this.buildProblemDetail(HttpStatus.INTERNAL_SERVER_ERROR, API_DEFAULT_ERROR_MESSAGE);
}

{
    "type": "about:blank",
    "title": "Internal Server Error",
    "status": 500,
    "detail": "Something went wrong. Please try again later or enter in contact with our service.",
    "instance": "/back-office/hello-world"
}

当有无法处理的异常时,就需要配置一个兜底的异常。

缓存

<dependency>  
 <groupId>org.springframework.boot</groupId>  
 <artifactId>spring-boot-starter-cache</artifactId>  
</dependency>
public interface CompanyRepository extends JpaRepository<Company, Long> {

  String CACHE_NAME = "company";

  @NonNull
  @Cacheable(value = CACHE_NAME, key = "{'byId', #id}")
  @Override
  Optional<Company> findById(@NonNull Long id);

  @Cacheable(value = CACHE_NAME, key = "{'bySlug', #slug}")
  Optional<Company> findBySlug(String slug);

  @Caching(
      evict = {
        @CacheEvict(value = CACHE_NAME, key = "{'byId', #entity.id}"),
        @CacheEvict(value = CACHE_NAME, key = "{'bySlug', #entity.slug}"),
      })
  @Override
  <S extends Company> @NonNull S save(@NonNull S entity);

  /*
   * This cache implementation is only valid if the table is not
   * frequently updated since it will clear the cache at every update operation
   * If you want to be more performant you can use something like https://github.com/ms100/cache-as-multi
   * */
  @NonNull
  @CacheEvict(cacheNames = CACHE_NAME, allEntries = true)
  @Override
  <S extends Company> List<S> saveAll(@NonNull Iterable<S> entities);

  @Caching(
      evict = {
        @CacheEvict(value = CACHE_NAME, key = "{'byId', #entity.id}"),
        @CacheEvict(value = CACHE_NAME, key = "{'bySlug', #entity.slug}"),
      })
  @Override
  void delete(@NonNull Company entity);

  /*
   * This cache implementation is only valid if the table is not
   * frequently updated since it will clear the cache at every delete operation
   * If you want to be more performant you can use something like https://github.com/ms100/cache-as-multi
   * */
  @CacheEvict(cacheNames = CACHE_NAME, allEntries = true)
  @Override
  void deleteAll(@NonNull Iterable<? extends Company> entities);
}

Spring 提供了标准的缓存接口,即便是后续需要切换到 Redis,使用的 API 和注解都不会发生改变。

线程

Java21 后支持了虚拟线程,几乎可以无限的实现线程,在 Spring Boot 3.2 需要单独开启。

spring.threads.virtual.enabled

可观测性

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
  <groupId>io.micrometer</groupId>
  <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
spring:
  endpoints:
    web:
      exposure:
        include: info, health, prometheus, metrics
47a2b41e5f02f32403aef1c66fed76df.png
image.png

注意在生成环境不要暴露管理 API

Trace

<dependency>
  <groupId>io.micrometer</groupId>
  <artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>
<dependency>
  <groupId>net.ttddyy.observation</groupId>
  <artifactId>datasource-micrometer-spring-boot</artifactId>
  <version>${datasource-micrometer.version}</version>
</dependency>
<dependency>
  <groupId>io.opentelemetry</groupId>
  <artifactId>opentelemetry-exporter-otlp</artifactId>
  <version>${opentelemetry-exporter-otlp.version}</version>
</dependency>

同步请求的时候每个请求都会带上 traceIdspanId ,如果是异步请求时候需要配置:spring.reactor.context-propagation=true

如果使用 @Async时:

@Configuration
public class TaskExecutorConfig {

  /*
   * Override default SimpleAsyncTaskExecutor to provide context propagation in @Async function
   * */
  @Bean
  public TaskExecutor simpleAsyncTaskExecutor() {
    final SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
    taskExecutor.setTaskDecorator(new ContextPropagatingTaskDecorator());
    return taskExecutor;
  }
}

本地测试时候可以使用 Otel Desktop Viewer

management:  
  tracing:
    sampling:
      probability: 1
  otlp:
    tracing:
      endpoint: http://localhost:4317
ef844879e1f1c20a055ea79efda76353.png
image.png

Rust Vs Go: A Hands-On Comparison

URL: https://www.shuttle.rs/blog/2023/09/27/rust-vs-go-comparison

动手比较 Rust 和 Go

664e13eacef4fc883db9001d55a29730.png
image.png

本文是通过编写一个 web 服务来进行比较的。

  • Go 更加简单易学,同时标准库非常强大,只需要配合 gin+sqlx 这两个第三方库就能实现一个 web 服务

  • Rust也可以快速的构建一个安全的 web 服务,但需要依赖许多第三方库,比如http/JSON/模板引擎/时间处理等

  • 但 Rust 在异常处理方面心智负担更低,代码更容易阅读。

  • 如果是一个初创小团队,使用 Go 的上手难度确实更低;

  • 但如果团队愿意花时间投入到 Rust 中,结合他出色的错误处理,和强大的编译检查,长时间来看会得到更好的效果。

为什么要使用 Go 语言?Go 语言的优势在哪里?- 知乎

URL: https://www.zhihu.com/question/21409296/answer/1040884859

图文并茂,讲解了 G-M-P 各自之间的关系,以及调度模型。

76e96da497713fbb4ba7b9eb41b941e4.png
image.png
  • G: Goroutine,用户创建的协程,图中搬运的砖头。

  • M: Machine,OS 内核的线程的抽象,代表真正执行的资源;对应到就是图中的地鼠,地鼠不能用户直接创建;得是砖头 G 太多,地鼠 M 本身太少,同时还有空闲的小车 P,此时就会从其他地方借一些地鼠 M 过来直到把小车 P 用完为止。

  • P: Processor 处理器,G 只有绑定到 P 才能被调度;P 是图中的小车,由用户设置的 GoMAXPROCS 决定小车的数量。

文章链接:

  • 19
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
编程狂人是推酷网旗下的周刊,每周一发布,并且同步发布pdf离线版本,内容以开发技术,编程语言,框架等为主,并且配有一周IT业界新闻! 目录 业界新闻 2014年最值得学习的编程语言 Elasticsearch 1.0.0发布 15 个有用的项目管理工具 Java 8新闻:发布候选版面世、新的原子数、放弃简易实现 2013年StackOverflow开发者调查:JS最火 前端开发 再谈榔头和钉子 [译]短小强悍的JavaScript异步调用库 BigPipe学习研究 Using Bootstrap 3 With Sass How to make a Flappy Bird in HTML5 with Phaser 编程语言 使用python/casperjs编写终极爬虫-客户端App的抓取 2013年度Python运维工具 又被 Python 的 Unicode 坑了 FutureTask 源码解析 C++:在堆上创建对象,还是在栈上? 技术纵横 UPYUN:用Erlang开发的对象存储系统 iOS 7最佳实践:一个天气App案例 看看百度招iOS开发面试的问题,你能答对多少? CoconutKit:iOS开发必备的开源组件库 Associated Objects [技术翻译]构建现代化的 Objective-C (上) iOS的后台运行和多任务处理 AFNetworking 2.0 Tutorial 一个用 Arduino 实现完整项目 后端架构 腾讯大规模Hadoop集群实践 日800万访客、20万RPS网站的5个9可用性架构 Redis 到底有多快[译文] TCP网络协议以及其思想的应用 OSChina 第 45 高手问答 —— 游戏引擎架构 程序人生 一个对 Dijkstra 的采访视频 【开源访谈】ECharts 作者 林峰 访谈实录
编程狂人是推酷网旗下的周刊,每周一发布,并且同步发布pdf离线版本,内容以开发技术,编程语言,框架等为主,并且配有一周IT业界新闻! 目录 业界新闻 斯坦福大学发布免费在线文本分析工具 Parse发布Bolts,一个面向iOS和Android的底层库集合 9 款最好的基于 Rails 的 CMS 内容管理系统 开源世界应该致谢的五家公司 Square、LinkedIn、Google、Facebook... 游戏公司的那些奇葩事 前端开发 跨域方法汇总 JavaScript多文件下载 写一个更好的Javascript DOM库 Hybrid APP开发者一定不要错过的框架和工具 编程语言 Python超级程序员使用的开发工具 基于Capistrano工具的Rails程序部署方案 Java IO vs NIO vs AIO vs 协议Servlet 3.0 以及NIO的框架 Integer.valueOf(String) 方法之惑 在运行通过反射了解JVM内部机制 程序设计 Objective-C相关Category的收集 文章: 豆瓣 CODE 两年历程回顾:git 不是万能的,没有 review 是万万不能的 Android WebKit消息处理 Tim Bray:2014年软件之路 后端架构 MongoDB与内存 《淘宝技术这十年》读书笔记 - 大CC 探索 Hibernate 新 TableGenerator 机制 服务好“最后一公里”,高效CDN架构经验 探索推荐引擎内部的秘密 一起 select 引起的崩溃 课堂上传纸条如何防范中间人攻击? 程序人生 《9Tech访谈录》揭秘游戏王子白泽的技术之路 我的北漂在路上--------时不时的停下脚步思考 纸书的秘密 我的第一次和最后一次 Hackathon 经历 一个技术人的知识管理方法论 ReactiveCocoa - iOS开发的新框架

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值