升级到 Spring Boot 3 后 javax.inject.Named 不可用

为了紧跟 Spring 6 的步伐,Spring Boot 在 2022 年 11 月 24 日释放了 3.0.0. 当前版本是 3.0.1(2022-12-23)。Spring 6 要求用 JDK 17+, Spring Boot 3 自然也要上 JDK 17+ 才能使用,对于一直死死抱住 JDK 8 不放的要升级到 Spring Boot 3 就是个比较大的挑战。

Spring Boot 到底带来了什么显著的特性呢?

  1. 依赖于 Spring 6, 最低 Java 17, 兼容 Java 19
  2. 支持生成 GraalVM 本地映像,取代实验性的 Spring Native 项目
  3. 最低 Java EE 9 和支持 Jakarta EE 10
  4. 依赖从 Java EE 迁移到 Jakarta EE API
  5. 升级到 Tomcat 10

从 Spring Boot 2.x 升级到 Spring Boot 3 的指南请阅官方的文档 Spring Boot 3.0 Migration Guide。Spring Boot 1 的项目还得老老实实的先升级到 Spring Boot 2,如果是早期的 Spring Boot 2,第一步是升级到 Spring Boot 2.7.x, 一步步来,免得步子大了扯到X。再到是把 JDK 换成  17 或更新的版本,编译,运行,有问题就改代码。 阅读全文 >>

Spring Boot Web 输出 Tomcat 的访问日志到控制台

当我们直接使用 Tomcat  时,访问日志的配置在 $TOMCAT_HOME/conf/server.xml 中

<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
              prefix="localhost_access_log" suffix=".txt"
              pattern="%h %l %u %t &quot;%r&quot; %s %b" />

产生的日志文件在 $TOMCAT_HOME/logs 目录中,生成以日期戳进行区分的滚动日志文件,如 localhost_access_log.2022-10-25.txt 等

而在我们使用  Spring Boot Web 时, 默认的嵌入式 Web 应用服务器是 Tomcat,我们可以在 Spring 属性文件中配置是否启用 Tomcat 访问日志(默认不启用)。属性文件中的配置针对的是如何输出访问日志到文件,而我们有时候需要输出访问日志到控制台而不非文件,比如 AWS 上的 ECS 应用,控制台的输出可直接发送到 CloudWatch,这样便于分析日志。

在 Spring Web 中可以用 Interceptor 或 Filter 来记录访问日志,但在请求出错时无法准确显示出响应时间和状态码,毕竟 Tomcat 的访问日志在代码的外层,进出 Controller 方法的输入输出信息也就掌握的更清楚。

本文的任务是探索输出 Spring Boot Web 嵌入式 Tomcat 的访问日志到控制台。有两种方式 阅读全文 >>

SpringBoot 启用 GZIP 对响应进行压缩

SpringBoot Web 应用默认是不启用响应数据的压缩,对大的文本类型的响应数据进行压缩是十分必要的,如 JSON, XML 等应用数据,甚至是 JS, CSS 等。

早先的 Web 应用基本是要配置一个叫做 GzipFilter 之类的东西,然后判断请求的 Accept-Encoding 是否含有 gzip, 再对需要的 Content-Type 响应类型的数据进行压缩。

在使用了 SpringBoot 之后,在碰到有压缩响应的需求的时候,第一件事情应该要想到是否能通过在 application.properties(或 application.yml) 配置就行。于是查阅 SpringBoot 2.7.x 的帮助文档 Spring Boot Reference Document, 搜索关键字 compression,翻几页就能找到 17.3.6. Enable HTTP Response Compression, 介绍了三个配置项 阅读全文 >>

Spring Boot 如何选择 Cache 实现的

写作此篇是作为对 Spring 使用 Cache 解析及使用不同类型的 Cache 一文的补充,该文中提到了自定 CacheManager 及配置 spring.cache.type 来选择自己的 Cache 实例,但对 Spring 是如何确定具体 Cache 实现未作展开。本文将介绍选择 Cache 实现的几种方式

  1. 默认选择 Cache
  2. 声明 Spring Bean Cache
  3. 声明 Spring Bean CacheManager
  4. 通过 spring.cache.type 属性选择
  5. 引入相应的 Cache 实现依赖

阅读全文 >>

Spring 使用 Cache 解析及使用不同类型的 Cache

要在一个 Spring 应用中开启缓存方法返回结果的功能很简单,不需要额外的依赖,相关的的注解  @Cacheable, @CacheConfig, @CachePut, @CacheEvict, @EnableCache 等来自 spring-context 包。默认的的 Cache 实现是把数据存入到 ConcurrentMap 中,所以数据一直在内存中,除非显式的调用被 @CacheEvict 的方法来清理。实际进行数据缓存时会有更复杂的策略,如元素个数,占用内存,过期时间,何时使用磁盘等,而且不同的数据类型应有不同的缓存策略。

因此,除了使用默认的 ConcurrentMap 作为缓存外,还可通过配置属性 spring.cache.type 来使用其他类型的缓存,如 Caffeine, Couchbase, EhCache, INfinispan, JCache, Redis 等,或自定义 CacheManager 来使用 Guava Cache。 阅读全文 >>

自定义 Spring Web Controller 方法的参数

在 Spring Web Controller 方法中的参数可用 org.springframework.web.bind.annotation 下的各种注解来说明参数值从哪儿获得,比如我们熟知的 @PathVariable, @RequestParam, @RequestHeader, @RequestBody, 还有较少使用的 @ReqeustAttribute, @SessionAttribute, @RequestPart, @MatrixVariable, @ModelAttribute, @AuthenticationPrincipal, @CurrentSecurityContext 等。其实在它们背后工作的是相应的 HandlerMethodArgumentResolver 的子孙们,当然还有 HttpMessageConverter 的各个实现类还默默的对输入数据进入类型转换。

为进一步深入了解 Spring Web 如何获得用户输入,我们先尝试一下不常用的注解,然后实现一个自己的注解参数 @ProductId, 它来从 queryString 或 requestHeader 中获得 productId。写作本文的起因是在上一篇 理解 Spring Boot Security + JWT Token 的简单应用 里, JwtTokenFilter 住 SecurityContextFilter 放一个 Authentication 实例, 在 Controller 方法中便能用 @AuthenticationPrincipal 自动注入 authentication.getPrincipal() 的值。 阅读全文 >>

理解 Spring Boot Security + JWT Token 的简单应用

项目中有用到 Spring Security 来控制 API 的访问权限,但对于配置应用它基本上是照葫芦画瓢。至于为什么要调用方法

SecurityContextHolder.getContext().setAuthentication()

并且能从 HttpServletRequest 中得到 Authentication。还有,只要在 Controller 的方法中添加一个带 @AuthenticationPrincipal 注解的参数

public String sampleApi(@AuthenticationPrincipal DecodedJWT decodedJWT) {...}

之后,decodedJWT 便自动有了值,诸如此类的,此前一概模糊不清。

早先配置 spring-security-config 是通过继承 WebSecurityConfigurerAdapter, 覆盖它的 configure(HttpSecurity http) 来配置访问规则等。在 spring-security-config 5.7.x 开始不建议用 WebSecurityConfigurerAdapter, 而是借由 SecurityFilterChain 来配置 HttpSecurity 中的规则 ,或者通过 WebSecurityCustomizer 完成定制。 阅读全文 >>

Java 调用本地动态库的组件(javah, JNA, JNR-FFI)

还是很 久很久以前,当初有 Java 调用本地动态库需求的时候,尝试过用 javah/native 原生的方式在 Java 中使用动态库,再就是小试了 JNative,它调用动态库只需 Java 端的动作, 它最后的更新日期是 9 年前 2013-04-26,基本是应该选择放弃了。

关于 JNative 的使用写过两篇

  1. Java调用动态库最简便方法和最好用的组件
  2. 使用JNative,在Java中传递一个C/C++结构参数到动态库中

如今想继续发掘下是否有别的更好的调用本地库的 JNI 组件,找到有

  1. JNIWrapper:居然是一个收费的,而且价格不菲,不作绍
  2. BridJ:也是 7 年前才有过代码的更新
  3. JNA(Java Native Access): 也就它稍为活跃一点点
  4. JNR-FFI:最近几个月也有更新,不知道使用体验如何

阅读全文 >>

macOS 如何定位 JAVA_HOME

多数的 Java 入门教程都是要求同时设置 JAVA_HOME 和 PATH(包含 $JAVA_HOME/bin) 两个环境变量,反正两个都有了就保险。其实一般情况下系统能在 PATH 中找到 java 程序时就知道 JAVA_HOME, 基本上只要配置 PATH 就行,而 JAVA_HOME 环境变量是可选的。但也有例外,比如 TOMCAT 就可能要求有 JAVA_HOME 环境变量。

在 macOS 下,JAVA_HOME 与 PATH 的关系又显得有点微妙了。一个新的 macOS 系统,它自带有 java 命令

$ which java
/usr/bin/java

你要直接执行它的话

$ java
The operation couldn’t be completed. Unable to locate a Java Runtime.
Please visit http://www.java.com for information on installing Java.

所以它实际上只是执行 java 的辅助入口,没有实际的 JDK 或 JRE 是没用的。 阅读全文 >>

Java 直接插入 CLOB/BLOB 数据到 Oracle 数据库

向数据库中插入 CLOB 或 BLOB 类型的数据,Oracle 总是比其他类型的数据库操作上要麻烦多了。当然,对于不大于 4K 长度的 CLOB 字符串在 JDBC 中可简单的用 PreparedStatement.setString(idx, "short string") 。如果要插入大于 4K 长度的内容,网上找来的例子许多都是分两步走

  1. 先插入 EMPTY_CLOB() 或 EMPTY_BLOB()
  2. 然后 SELECT 原来的记录 FOR UPDATE, 再更新先前插入的记录

这就存在两个问题,含 CLOB/BLOB 的表必需要有主键,还有因为 FOR UPDATE 的使用我们需要开启事物,不能采用自动提交。

其实还有更简单的方法可直接插入大的 CLOB/BLOB 数据,要用到 Oracle JDBC 驱动的 setStringForClob(),  CLOB.createTemporary(), 或 BLOB.createTemporary() 方法。来看下面的例子,例子中只演示 CLOB, 类似的方法可应用于 BLOB, NCLOB。

本文中所使用的 Oracle JDBC 驱动比较老,是 ojdbc:ojdbc:5。Docker 启动一个本地的 Oracle 11G 作为测试数据库

$ docker run -d -p 1521:1521 -p 8080:8080 wnameless/oracle-xe-11g-r2

默认的 SID 是 xe, 数据库用户名和密码分别是 system/oracle 阅读全文 >>