教程

Java 中的向上转型和向下转型

1、简介 了解如何处理 Java 类型层次结构中的对象对于编写灵活和可维护的代码至关重要。在这个领域中,两个基本概念是向上转型(Upcasting)和向下转型(Downcasting)。 本文将带你深入了解这些概念,探索它们之间的区别,以及它们在 Java 中的原理。 2、Java 中的类型转换 Java 是一种面向对象的编程语言(Object Oriented Programming,OOP),允许在其类型系统中将一种类型转换为另一种类型。转换是将一种类类型的引用转换为另一种类类型的过程。具体来说,Java 中主要有两种类型的转换:向上转型(Upcasting)和向下转型(Downcasting)。 假设我们有一个类的层次结构,其中 Dog 是 Animal 的子类。下图显示了向上转型(Upcasting)和向下转型(Downcasting)在这个层次结构中的工作原理: 向上转型(Upcasting)箭头从 Dog 类移动到 Animal 类,显示了子类引用(Dog)如何泛化为父类引用(Animal)。而,向下转型(Downcasting)箭头从 Animal 类返回 Dog 类,显示父类引用(Animal)如何再次被指定为子类引用(Dog)。 但是,如果尝试使用 Animal 引用实例化 Dog 对象,则会因类型不兼容而导致编译错误。 Dog dog = new Animal(); //异常 ClassCastException 3、Java 向上转型 向上转型(Upcasting)是指将子类引用转换为父类引用。这种类型的转换是隐式的,在处理多态性时经常使用它。 向上转型(Upcasting)允许我们将一个子类的对象当作一个父类的对象来处理: class Animal { public void makeSound() { System.out.println("Animal sound"); } } class Dog extends Animal { public void makeSound() { System.out.println("Bark"); } public void fetch() { System.

Jackson 序列化和反序列化 java.sql.Blob

1、简介 本文将带你了解如何使用 Jackson 序列化和反序列化 java.sql.Blob 对象。 java.sql.Blob 表示 Java 中的二进制大对象(Binary Large Object,Blob),可以存储大量二进制数据。在使用 Jackson 处理 JSON 序列化和反序列化时,处理 Blob 对象可能比较棘手,因为 Jackson 并不直接支持它们。不过,我们可以创建自定义的序列化器(Serializer)和反序列化器(Deserializer)来处理 Blob 对象。 2、依赖和示例项目 首先,在 pom.xml 中添加 jackson-databind 依赖: <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.12.3</version> </dependency> 然后,创建一个简单的 User POJO,其中包含 id、name 和 Blob 类型的 profilePicture(图片数据): public class User { private int id; private String name; private Blob profilePicture; // 构造函数,Getter/Setter 省略 } 3、定义 Blob 序列化器 定义一个序列化器(Serializer),将 User 的 profilePicture 属性转换为 Base64 编码的二进制字符串: @JacksonStdImpl public class SqlBlobSerializer extends JsonSerializer<Blob> { @Override public void serialize(Blob value, JsonGenerator gen, SerializerProvider serializers) throws IOException { try { byte[] blobBytes = value.

使用 Spring WebClient 和 WireMock 进行集成测试

1、简介 Spring WebClient 是一款非阻塞、响应式的 HTTP 客户端,而 WireMock 是一个强大的用于模拟基于 HTTP 的 API 的工具。 2、依赖和示例 首先,需要在 Spring Boot 项目中添加必要的依赖。 在 pom.xml 中添加 spring-boot-starter-flux(WebClient) 和 spring-cloud-starter-wiremock(WireMock Server)依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-contract-wiremock</artifactId> <version>4.1.2</version> <scope>test</scope> </dependency> 假设,我们的应用需要调用外部天气 API,以获取给定城市的天气数据。 定义 WeatherData POJO: public class WeatherData { private String city; private int temperature; private String description; // 构造函数、Getter/Setter 方法省略 我们要使用 WebClient 和 WireMock 进行集成测试,以测试这个功能。 3、使用 WireMock API 进行集成测试 首先用 WireMock 和 WebClient 设置 Spring Boot 测试类:

Spring Security 检测密码是否泄露

1、概览 在构建处理敏感数据的 Web 应用时,确保用户密码的安全性非常重要。密码安全的一个重要方面是检查密码是否泄露,这通常是由于密码出现在 数据泄露事件 中。 Spring Security 6.3 引入了一项新功能,让我们可以轻松检查密码是否被已泄露。 本文将带你了解 Spring Security 中新的 CompromisedPasswordChecker API 以及如何将其集成到 Spring Boot 应用中。 2、密码泄露 密码泄露是指在数据泄露事件中暴露的密码,使其容易受到未经授权的访问。攻击者通常在凭证填充和密码填充攻击中使用这些泄露的密码,在多个网站上使用泄露的用户名-密码对,或在多个账户上使用通用密码。 要降低这种风险,关键是要在创建账户前检查用户密码是否泄露。 同样重要的是要注意,以前有效的密码可能会随着时间的推移而泄露,因此建议不仅在创建账户时,而且在登录过程中或任何允许用户更改密码的过程中都要检查密码是否泄露。如果登录尝试因检测到密码泄露而失败,可以提示用户重设密码。 3、CompromisedPasswordChecker Spring Security 提供了一个简单的 CompromisedPasswordChecker 接口,用于检查密码是否被泄露: public interface CompromisedPasswordChecker { CompromisedPasswordDecision check(String password); } 该接口只暴露了一个 check() 方法,该方法将密码作为输入,并返回一个 CompromisedPasswordDecision 的实例,表明密码是否已被破解/泄露。 check() 方法需要明文密码,因此必须在使用 PasswordEncoder 加密密码之前调用该方法。 3.1、配置 CompromisedPasswordChecker Bean 要在应用中启用密码泄露检查,需要声明 CompromisedPasswordChecker 类型的 Bean: @Bean public CompromisedPasswordChecker compromisedPasswordChecker() { return new HaveIBeenPwnedRestApiPasswordChecker(); } HaveIBeenPwnedRestApiPasswordChecker 是 Spring Security 提供的 CompromisedPasswordChecker 的默认实现。

Spring AI 和 Open AI 入门

Open AI 和 Spring AI 简介 当 OpenAI 发布 ChatGPT 时,它在全球掀起了一场风暴。这是第一次有语言模型能够根据提示生成类似人类的回答。此后,OpenAI 又发布了其他几个模型,包括可以根据文字提示生成图像的 DALL-E。 Spring AI 是一个 Java 库,为与 LLM 模型交互提供了一个简单易用的接口。Spring AI 提供了与各种 LLM(如 Open AI、Azure Open AI、Hugging Face、Google Vertex、Ollama、Amazon Bedrock 等)交互的高级抽象。 本文将带你了解如何使用 Spring AI 与 Open AI 能进行交互。 首先,需要在 OpenAI 上创建账户并获取 API Key。 访问 OpenAI Platform 并创建账户。 在控制面板中,点击左侧导航菜单中的 API Keys,创建一个新的 API Key。 如果你是新用户,你可能会获得一些免费点数来使用 OpenAI API。否则,你需要购买点数才能使用 OpenAI API。 获得 API KEY 后,把它添加到环境变量 OPENAI_API_KEY 中。 export OPENAI_API_KEY=<your-api-key> 创建 Spring AI 项目 使用 Spring Initializr 创建一个新的 Spring Boot 项目。

Tomcat 不停机升级 Web 应用

1、概览 Apache Tomcat,简称 Tomcat,是 Jakarta Servlet 规范的开源实现。它作为 Web 服务器接收 HTTP 或 WebSocket 请求,并调用负责的 Servlet 来处理请求。 本文将带你了解 Tomcat 中的并行部署(Parallel Deployment),以及如何实现不停机升级 Web 应用。 2、Tomcat 部署模式 我们可以通过两种方式使用 Apache Tomcat 为 Web 应用提供服务。第一种方法是将 Tomcat 程序嵌入 Java 应用本身。或者,可以将 Apache Tomcat 作为一个专用的 Web 服务器进程运行,为一个或多个 Web 应用提供服务。在这种模式下,开发人员会将 Web 应用打包成 WAR (Web Application Archive,Web 应用归档)包。然后,Web 服务器管理员会将 Web 应用部署到 Tomcat Web 服务器上。 独立的 Tomcat 部署模式现在不怎么流行了,但是也有好处。多个不同的 Web 应用使用同一个 Tomcat 服务器会节省一定的资源。 将 Tomcat 作为独立 Web 服务器运行时,需要了解如何对运行中的 Web 应用进行不停机重新部署。与通常将此任务委托给 Kubernetes 等外部协调器的容器化 Web 应用相反,Tomcat 中的部署依赖于 Tomcat 服务器,以最大限度地减少 Web 应用升级的停机时间。

如何控制 Spring Bean 的加载顺序?

先说结论,使用 @Order 注解或者是实现 Ordered 接口并不能控制 Bean 的加载顺序。 一、@Order 注解和 Ordered 接口 在 Spring 框架中,@Order 是一个非常实用的元注解,它位于 spring-core 包下,主要用于控制某些特定上下文(Context)中组件的执行顺序或排序,但它并不直接控制 Bean 的初始化顺序。 1.1、用途 @Order 注解或者是 Ordered 接口,常见的用途主要是两种: 定义执行顺序:当多个组件(如 Interceptor、Filter、Listrner 等)需要按照特定的顺序执行时,@Order 注解可以用来指定这些组件的执行优先级。数值越小,优先级越高,相应的组件会更早被执行或被放置在集合的前面(@Order 注解接受一个整数值,这个值可以是负数、零或正数。Spring 框架提供了 Ordered.HIGHEST_PRECEDENCE(默认最低优先级)和 Ordered.LOWEST_PRECEDENCE(默认最高优先级)常量,分别对应于 Integer.MIN_VALUE 和 Integer.MAX_VALUE,可以方便地设定优先级。 集合排序:当相同类型的组件被自动装配到一个集合中时,@Order 注解会影响它们在这个集合中的排列顺序。 1.2、使用场景 经典的使用场景如下。 拦截器的排序 在 Spring MVC 中,可以使用 @Order 来控制拦截器(Interceptor)的执行顺序。 Spring Security Filter 在 Spring Security 中,过滤器链(Filter Chain)的顺序通过 @Order 来定义,确保正确的安全处理流程。 // HttpSecurity 的 performBuild 方法 @Override protected DefaultSecurityFilterChain performBuild() { // 对 Filter 进行排序 this.

Spring 异常 “No Multipart Boundary Was Found”

1、简介 本文将带你了解在 Spring 中处理文件上传(Multipart)请求时出现异常:“No Multipart Boundary Was Found” 的原因,以及解决办法。 2、理解 Multipart 请求 简而言之,Multipart 请求是一种 HTTP 请求,它在一条消息的请求体中传输一种或多种不同的数据。请求体被分成多个部分,请求中的每个部分都可能代表不同的文件或数据。 通常使用它来传输或上传文件、交换电子邮件、流媒体或提交 HTML 表单,并使用 Content-Type 标头来指明在请求中发送的数据类型。 来看看 Multipart 请求需要设置哪些值。 2.1、主类型 主类型(顶级类型)指定了我们发送的内容的主要类别。如果我们在单个 HTTP 请求中提交多种数据类型,则需要将 Content-Type Header 值设置为 multipart。 2.2、子类型 除了顶级类型外,Content-Type Header 值还包含一个强制的子类型。子类型值提供了有关数据格式的附加信息。 在不同的 RFC 中定义了多种 multipart 子类型。例如 multipart/mixed、multipart/alternative、multipart/related 和 multipart/form-data(常用)。 由于我们在一个请求中封装了多种不同的数据类型,因此需要一个额外的参数来分隔 multipart 消息的不同部分:即,boundary 参数。 2.3、Boundary 参数 Boundary 指令(参数)是 multipart Content-Type 的强制值,它指定了封装边界。 根据 RFC 1341 的定义,封装(Boundary)边界是由两个连字符(“–”)后跟 Content-Type Header 中的 boundary 值组成的分隔线。它用于分隔 HTTP 请求体中的各个部分(即,子请求体)。 来看一个实际的案例,Web 浏览器请求包含两个 Part。通常情况下,Content-Type Header 信息如下:

在 Spring 中实现 Bulk 和 Batch API

1、概览 尽管标准的 REST API 可以满足大多数常见需求,但在处理批量(Bulk)或批处理(Batch)操作时,基于 REST 的架构风格存在一些限制。 本文将带你了解如何在微服务中应用 Bulk 和 Batch 操作,以及如何实现一些自定义的面向 “写” 的 Bulk 和 Batch API。 2、 Bulk 和 Batch API 简介 Bulk 和 Batch 操作这两个术语经常被互换使用。不过,两者之间还是有硬性区别的。 通常,Bulk(批量)操作是指对同一类型的多个条目执行相同的操作。一种简单的方法是为每个请求调用相同的 API 来执行批量操作。这种方法可能太慢,而且会浪费资源。相反,我们可以在一次往返中处理多个条目。 我们可以通过在一次调用中对同一类型的多个条目执行相同的操作来实现 Bulk(批量)操作。这种对条目集合进行操作的方式可减少整体延迟并提高应用性能。要实现 Bulk(批量)操作,我们可以重用用于于单个条目的现有端点,或者为 Bulk(批量)方法创建一个单独的路由。 Batch(批处理)操作通常是指对多个资源类型执行不同的操作。Batch API 是在一次调用中对多个资源执行多个操作的集合。这些资源操作可能没有任何连贯性。每个请求路由可以是独立的,互相之间没有依赖关系。 简而言之,“Batch” 一词是指批量处理不同的请求。 目前,对于实现 Bulk 或 Batch 操作来说,缺乏明确定义的标准或规范。此外,许多流行的框架,如 Spring,没有内置的 Bulk(批量)操作支持。 尽管如此,在本文中,我们将使用常见的 REST 架构来实现自定义 Bulk 和 Batch 操作的方法。 3、Spring 示例应用 假设我们需要构建一个简单的微服务,同时支持 Bulk 和 Batch 操作。 3.1、Maven 依赖 首先,添加 spring-boot-starter-web 和 spring-boot-starter-validation 依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>3.

使用 Embedding 模型和向量数据库的 Spring AI RAG

本文主要介绍以下内容: 嵌入式模型简介。 使用 DocumentReader 加载数据。 在 VectorStore 中存储 Embedding。 实现 RAG(Retrieval-Augmented Generation,检索增强生成),又名 Prompt Stuffing。 你可以在 GitHub 中找到本文的示例代码 大型语言模型(LLM),如 OpenAI、Azure Open AI、Google Vertex 等,都是在大型数据集上训练出来的。但这些模型并不是在你的私人数据上训练出来的,因此它们可能无法回答你所在领域的特定问题。但是,在你的私人数据上训练模型可能既昂贵又耗时。那么,我们该如何使用这些 LLM 来回答我们领域的特定问题呢? 其中一种方法是使用 RAG(Retrieval-Augmented Generation,检索增强生成),又称 Prompt Stuffing(提示填充)。使用 RAG,从数据存储中检索相关文档,并将其传递给 LLM 以生成答案。在这一过程中,使用嵌入模型将文档转换为 Embedding,并将其存储到向量数据库中。 了解检索增强生成(RAG) 你可能在关系数据库中存储结构化数据,在 NoSQL 数据库中存储非结构化数据,甚至在文件中存储结构化数据。你能够使用 SQL 有效地查询关系数据库,使用 NoSQL 数据库的查询语言有效地查询 NoSQL 数据库。你还可以使用 Elasticsearch、Solr 等全文搜索引擎来查询非结构化数据。 不过,你可能希望使用具有语义的自然语言检索数据。 例如,“我喜欢 Java 编程语言” 和 “Java 始终是我的首选语言” 具有相同的语义,但使用了不同的词语。尝试使用准确的词语检索数据可能不会有效。 这就是 Embedding 的作用所在。Embedding 是单词、句子或文档的向量表示。你可以通过这些 Embedding,使用自然语言检索数据。 你可以将结构化和非结构化数据转换为 Embedding,并将其存储在向量数据库中。然后,你可以使用自然语言查询向量数据库并检索相关数据。然后,你可以通过相关数据查询 AI 模型,以获得响应。 检索增强生成(RAG)是在生成响应之前,通过使用训练数据之外的额外知识库来优化 LLM 输出的过程。 Embedding API Embedding API 可以将单词、句子、文档或图像转换为 Embedding 。Embedding 是单词、句子或文档的向量表示。