侧边栏壁纸
“学习日记” 共(42)篇
  • RedisTemplate与Jackson序列化配置详解 在使用Redis进行数据存储和访问的场景中,常常需要通过配置RedisTemplate来定义键值的序列化方式以及连接的基本设置,这样做可以确保数据在Redis中存储时的可读性和一致性,并且方便对复杂对象进行序列化和反序列化。使用合适的序列化方式可以提高性能、降低存储空间的占用,并且避免在读取数据时遇到不必要的兼容性问题。。本文将详细解析RedisConfig类的配置,帮助你更好地理解每一部分的实现。1. 代码整体描述我们采用Spring Boot配置Redis连接和序列化方式。以下是RedisConfig类的完整代码:import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); RedisSerializer<String> redisSerializer = new StringRedisSerializer(); redisTemplate.setConnectionFactory(redisConnectionFactory); redisTemplate.setKeySerializer(redisSerializer); redisTemplate.setHashKeySerializer(redisSerializer); redisTemplate.setValueSerializer(jackson2JsonRedisSerializer()); redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer()); return redisTemplate; } private Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer() { Jackson2JsonRedisSerializer<Object> jsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class); ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); jsonRedisSerializer.setObjectMapper(objectMapper); return jsonRedisSerializer; } } 2. 代码详细解析2.0 方法的作用说明在RedisConfig类中,有两个主要的方法:redisTemplate(RedisConnectionFactory redisConnectionFactory)作用:用于配置并创建一个RedisTemplate Bean,这个 Bean 用于和 Redis 进行交互。具体功能:通过传入的RedisConnectionFactory,来确保 RedisTemplate 可以连接到 Redis 服务器。设置键(Key)和哈希键(Hash Key)使用字符串序列化(StringRedisSerializer),确保所有的键以可读的字符串形式存储。设置值(Value)和哈希值(Hash Value)使用 JSON 序列化(Jackson2JsonRedisSerializer),以便存储 Java 对象时能以 JSON 格式存入 Redis 中。这样做可以更好地对复杂的数据结构进行序列化和反序列化。jackson2JsonRedisSerializer()作用:用于创建并配置一个Jackson2JsonRedisSerializer,并为其设置合适的对象映射(Object Mapper)。具体功能:Jackson2JsonRedisSerializer是一个专门用于将 Java 对象序列化为 JSON 格式的工具,特别适用于 Redis 中的复杂对象存储。创建ObjectMapper并对其进行配置,比如设置对象的可见性、忽略未知属性、启用类型信息等,以确保 Java 对象能够正确地被序列化和反序列化。设置类型信息时,启用默认类型信息以保留对象的类型结构,这样在反序列化时能够还原对象原本的类型。2.1 类注解与基本结构@Configuration:标注该类为配置类,Spring会自动扫描并加载该配置。@Bean:方法上的注解,表示该方法返回的对象会被注册为Spring容器的Bean,可以被其他组件注入使用。2.2 RedisTemplate Bean的配置public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory):创建并返回RedisTemplate实例,用于对Redis数据库进行操作。参数RedisConnectionFactory提供连接工厂,确保与Redis服务的连接。redisTemplate.setConnectionFactory(redisConnectionFactory):设置连接工厂以连接Redis服务器。RedisSerializer<String> redisSerializer = new StringRedisSerializer():创建StringRedisSerializer实例,设置键的序列化方式为字符串格式。redisTemplate.setKeySerializer(redisSerializer)和redisTemplate.setHashKeySerializer(redisSerializer):设置键和Hash键的序列化方式为字符串,以确保键以可读形式存储在Redis中。redisTemplate.setValueSerializer(jackson2JsonRedisSerializer())和redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer()):设置值和Hash值的序列化方式为JSON格式,使用Jackson库实现,便于存储复杂的对象结构。2.3 Jackson2JsonRedisSerializer配置private Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer():创建Jackson2JsonRedisSerializer实例,用于将对象序列化为JSON格式,以便于存储在Redis中。ObjectMapper objectMapper = new ObjectMapper():创建ObjectMapper实例,用于序列化和反序列化Java对象。objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY):设置对象的可见性,确保所有属性都可以被序列化和反序列化,无论其访问修饰符如何。objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false):配置ObjectMapper在遇到未知属性时不抛出异常,增强兼容性。objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY):启用默认类型信息,以在反序列化时保留对象的类型信息,确保对象能正确还原。需要注意的是,这样做存在潜在的安全风险,因为启用默认类型信息可能导致反序列化时执行恶意代码,从而引发安全漏洞。因此,在使用时需要确保输入数据的安全性,避免从不可信的来源获取数据。。jsonRedisSerializer.setObjectMapper(objectMapper):将ObjectMapper配置应用到Jackson2JsonRedisSerializer,确保其使用正确的序列化和反序列化规则。
    • 5个月前
    • 314
    • 0
    • 0
  • 🌩️ Spring Cloud Gateway中的全局异常处理 1️⃣ 异常处理器的配置在这次的异常处理中,我实现了一个类为 GatewayExceptionHandler 的全局异常处理器,它使用了 ErrorWebExceptionHandler 接口来对经过网关的请求进行应对的异常处理。为了便于网关的应用,我为此类添加了 @Component 模式,使得它能被实例化并当作应用程序的 Bean 被引用。🔍 代码解析类代码如下:package com.jingdianjichi.club.gateway.exception; import cn.dev33.satoken.exception.SaTokenException; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.jingdianjichi.club.gateway.entity.Result; import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler; import org.springframework.core.io.buffer.DataBufferFactory; import org.springframework.http.MediaType; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; @Component public class GatewayExceptionHandler implements ErrorWebExceptionHandler { private ObjectMapper objectMapper = new ObjectMapper(); @Override public Mono<Void> handle(ServerWebExchange serverWebExchange, Throwable throwable) { // 获取请求和响应对象 ServerHttpRequest request = serverWebExchange.getRequest(); ServerHttpResponse response = serverWebExchange.getResponse(); Integer code = 200; String message = ""; // 根据异常类型设置返回的状态码和信息 if (throwable instanceof SaTokenException) { code = 401; // 无权限 message = "用户无权限"; } else { code = 500; // 系统错误 message = "系统繁忙"; } // 创建返回结果对象 Result result = Result.fail(code, message); response.getHeaders().setContentType(MediaType.APPLICATION_JSON); // 将返回的结果转换为JSON格式并写入响应 return response.writeWith(Mono.fromSupplier(() -> { DataBufferFactory dataBufferFactory = response.bufferFactory(); byte[] bytes = null; try { bytes = objectMapper.writeValueAsBytes(result); } catch (JsonProcessingException e) { e.printStackTrace(); } return dataBufferFactory.wrap(bytes); })); } }🚀 代码讲解ServerHttpRequest 和 ServerHttpResponse:获取请求和响应对象,方便后续处理。异常类型判断:通过 instanceof 来判断是否是 SaTokenException,如果是,就返回401,表示“用户无权限”,否则返回500,表示“系统繁忙”。返回结果封装:使用 Result 类来封装返回结果,使返回格式统一。设置响应格式为 JSON:通过 response.getHeaders().setContentType(MediaType.APPLICATION_JSON) 设置返回类型为 JSON。写入响应:将结果转换为字节并写入响应中。2️⃣ Result类的实现为了方便统一返回的结果信息,我设计了一个全局的 Result 类用于表示结果。🔍 代码解析代码如下:package com.jingdianjichi.club.gateway.entity; import com.jingdianjichi.club.gateway.ResultCodeEnum; import lombok.Data; @Data public class Result<T> { private Boolean success; // 是否成功 private Integer code; // 返回码 private String message; // 返回信息 private T data; // 返回的数据内容 // 创建一个失败的结果对象,附带数据 public static <T> Result fail(T data) { Result result = new Result(); result.setSuccess(false); result.setCode(ResultCodeEnum.FAIL.getCode()); result.setMessage(ResultCodeEnum.FAIL.desc); result.setData(data); return result; } // 创建一个通用的失败结果对象 public static Result fail() { Result result = new Result(); result.setSuccess(false); result.setCode(ResultCodeEnum.FAIL.getCode()); result.setMessage(ResultCodeEnum.FAIL.desc); return result; } // 创建一个成功的结果对象,附带数据 public static <T> Result ok(T data) { Result result = new Result(); result.setSuccess(true); result.setCode(ResultCodeEnum.SUCCESS.getCode()); result.setMessage(ResultCodeEnum.SUCCESS.getDesc()); result.setData(data); return result; } // 创建一个通用的成功结果对象 public static Result ok() { Result result = new Result(); result.setSuccess(true); result.setCode(ResultCodeEnum.SUCCESS.getCode()); result.setMessage(ResultCodeEnum.SUCCESS.desc); return result; } // 创建一个自定义错误信息的失败结果对象 public static Result fail(Integer code, String message) { Result result = new Result(); result.setSuccess(false); result.setCode(code); result.setMessage(message); return result; } }🚀 代码讲解success:表示操作是否成功的布尔值。code 和 message:用于描述操作结果的状态码和信息。静态方法 ok 和 fail:提供便捷的方法来生成不同的返回结果,便于在代码中使用。3️⃣ 结果码枚举的设计为了统一结果码的管理,我使用了一个结果码的枚举类 ResultCodeEnum,以统一返回码和应对的描述。🔍 代码解析代码如下:package com.jingdianjichi.club.gateway; import lombok.Getter; @Getter public enum ResultCodeEnum { SUCCESS(200, "成功"), FAIL(500, "失败"); // 状态码 public int code; // 描述 public String desc; ResultCodeEnum(int code, String desc) { this.code = code; this.desc = desc; } public static ResultCodeEnum getByCode(int codeVal) { for (ResultCodeEnum resultCodeEnum : ResultCodeEnum.values()) { if (resultCodeEnum.code == codeVal) { return resultCodeEnum; } } return null; } }🚀 代码讲解SUCCESS 和 FAIL:定义了两种常见的结果码,分别表示成功和失败。getByCode 方法:根据状态码获取对应的枚举值,便于使用。
    • 5个月前
    • 220
    • 0
    • 0
  • nacos实现配置的动态加载出现localhost报错 使用nacos实现配置的动态加载,启动项目时一直出现报错,一直是localhost再看一眼配置,没错,确实是服务器地址,但为什么一直报的是localhost:8848呢?其实还是配置文件写错了,正确的如下{collapse}{collapse-item label="补充知识点" open}Spring Cloud Nacos 配置中的 server-addr 与 config.server-addr 区别spring.cloud.nacos.config.server-addr作用用于配置 Nacos 配置中心的服务器地址。主要用于从 Nacos 配置中心获取配置信息,并且对应用程序中的配置信息进行集中管理和动态更新。场景说明在微服务架构中,我们通常需要一个集中的配置管理系统,以便管理不同微服务的配置参数。Nacos 的配置中心功能非常适合这一需求。spring.cloud.nacos.config.server-addr 指定的是 Nacos 配置中心的地址,它告诉 Spring 应用程序去哪个 Nacos 服务器拉取配置文件。spring: cloud: nacos: config: server-addr: 60.204.218.162:8848 prefix: ${spring.application.name} group: DEFAULT_GROUP file-extension: yaml在上面的例子中,server-addr 指定了 Nacos 配置中心的地址 60.204.218.162:8848,这意味着应用程序会去这个地址获取相应的配置数据,以实现动态配置的能力。spring.cloud.nacos.server-addr作用用于配置 Nacos 注册中心的服务器地址。主要用于服务的注册和发现,让微服务可以互相识别和通信。场景说明微服务架构中,不同的服务需要彼此通信,服务注册与发现是这个架构中的核心功能之一。Nacos 作为一个注册中心,可以让所有微服务在启动时将自身信息注册到 Nacos,其他服务则可以通过 Nacos 找到它们。spring.cloud.nacos.server-addr 指定的是 Nacos 注册中心的地址,它告诉应用程序去哪个 Nacos 实例注册自己,或者从哪个 Nacos 实例中发现其他服务。spring: cloud: nacos: server-addr: 60.204.218.162:8848在这个例子中,server-addr 的地址是 60.204.218.162:8848,这意味着应用程序会将自己注册到这个 Nacos 注册中心,以便其他微服务可以找到它。两者的区别功能不同:spring.cloud.nacos.config.server-addr 用于配置中心,帮助应用程序获取集中配置的数据。spring.cloud.nacos.server-addr 用于服务注册与发现,帮助微服务进行相互通信。使用场景不同:config.server-addr 主要用于配置文件的管理,包括动态加载和热更新配置。server-addr 则用于将服务信息注册到 Nacos,并发现其他服务。可以相同,也可以不同:如果你的 Nacos 同时用作配置中心和注册中心,那么这两个地址可以相同。但是如果你有多套 Nacos 环境来分别处理配置和服务注册,那么这两个地址就会不同。典型配置示例在一个典型的 Spring Cloud 项目中,如果你使用 Nacos 作为服务注册中心和配置中心,那么配置文件可能会如下所示:spring: application: name: my-microservice cloud: nacos: server-addr: 60.204.218.162:8848 config: server-addr: 60.204.218.162:8848 prefix: ${spring.application.name} group: DEFAULT_GROUP file-extension: yaml在这个配置中,server-addr 和 config.server-addr 的值相同,表示 Nacos 同时作为注册中心和配置中心使用。总结spring.cloud.nacos.config.server-addr 用于配置中心,帮助应用程序获取和管理配置。spring.cloud.nacos.server-addr 用于服务注册与发现,帮助微服务进行相互识别和通信。两者可以指向同一个 Nacos 地址,但它们负责的功能是不同的。{/collapse-item}{/collapse}
    nacos实现配置的动态加载出现localhost报错 nacos实现配置的动态加载出现localhost报错 nacos实现配置的动态加载出现localhost报错
    • 5个月前
    • 286
    • 0
    • 1
  • Docker安装Nacos
  • Docker 安装 Jenkins 实现自动打包部署 Jenkins 是一个用来实现自动化部署的工具,在应用开发中非常有用。本文将详细记录如何通过 Docker 安装 Jenkins 的过程。前置条件在开始前,需要确保你的机器上已经安装了 JDK。可以通过以下命令来安装 OpenJDK 1.8 版本:yum install -y java-1.8.0-openjdk.x86_64确认 JDK 已成功安装后,我们就可以开始安装 Jenkins。在 Docker 上安装 Jenkins1. 查找 Jenkins 镜像首先,查询 Docker 中有哪些可用的 Jenkins 镜像:docker search jenkins2. 拉取 Jenkins 镜像从 Docker Hub 中拉取 Jenkins 的指定版本镜像,这里我们选择 2.414.2 版本:docker pull jenkins/jenkins:2.414.23. 运行 Jenkins 容器运行 Jenkins 并设置一些基本参数:docker run -d -u root -p 8080:8080 -p 50000:50000 -v /var/jenkins_home:/var/jenkins_home -v /etc/localtime:/etc/localtime --name jenkins jenkins/jenkins:2.414.2-d:以后台模式运行容器。-u root:以 root 用户运行 Jenkins,方便访问系统文件。-p 8080:8080:将容器的 8080 端口映射到主机的 8080 端口,用于连接 Jenkins。-p 50000:50000:用于 Jenkins agent 的连接。-v /var/jenkins_home:/var/jenkins_home:挂载数据卷以持久化 Jenkins 的数据。4. 启动 Jenkins启动 Jenkins 容器:docker start jenkins查看docker正在运行的容器:docker ps -a此时 Jenkins 已经启动完成,可以通过浏览器访问 http://<服务器IP>:8080 来连接 Jenkins。首次访问可能需要一些时间来等待启动过程完成。建议使用内存较大的服务器,因为内存不足的云服务器可能会导致 Jenkins 启动失败。获取 Jenkins 初始密码启动完成后,可以通过查看 Jenkins 容器的日志来获取初始密码:docker logs jenkins然后,在访问 Jenkins("<服务器IP>:8080")时,在页面上输入获取到的初始密码即可。在此步骤中,可以选择安装方式,推荐安装默认插件以获得最佳体验在这个界面等待安装完成安装完成后会需要创建一个管理员账户,创建完后点击保存并完成完成管理员账户的创建后,点击保存并继续以完成配置过程大功告成!然后点击新建item输入任务名称,选择Freestyle project配置源码管理,选择git,填写项目地址并添加凭证由于github已经取消使用账号和密码的方式来连接仓库,否则会拒绝访问,所以这里配置使用ssh密钥的方式。{collapse}{collapse-item label="配置凭证步骤" open}下面是通过 SSH 私钥和公钥配置 Jenkins 凭证的完整过程:在 Jenkins 中配置 SSH 凭证要配置 Jenkins 与 Git 之间的凭证连接,我们可以通过生成并使用 SSH 私钥和公钥来实现。这是一个安全且常见的做法,确保 Jenkins 能够访问你的代码库。以下是配置的完整步骤。1. 生成 SSH 密钥对首先,我们需要在服务器上生成 SSH 密钥对,用于 Jenkins 连接 Git 代码库。请使用以下命令生成 SSH 密钥对:ssh-keygen -t rsa -b 4096 -C "jenkins@example.com"-t rsa:指定密钥类型为 RSA。-b 4096:密钥长度为 4096 位。-C "jenkins@example.com":为密钥加上注释,以便识别。在命令执行后,系统会询问你密钥的存储位置,默认位置一般为 ~/.ssh/id_rsa。你也可以为其指定一个新的存储路径。2. 查看生成的公钥生成密钥对后,接下来要获取公钥内容,以便添加到代码库的版本控制系统中,确保 Jenkins 能够克隆代码。执行以下命令查看公钥:cat ~/.ssh/id_rsa.pub将输出的公钥内容复制下来,然后登录到你代码仓库的控制台(例如 GitHub、GitLab 或 Bitbucket),将公钥添加到仓库的 Deploy keys 或 SSH keys 选项中,授予 Jenkins 对仓库的读权限。3. 将私钥添加到 Jenkins 凭证中现在,我们需要将私钥配置到 Jenkins 中,确保 Jenkins 在运行构建任务时能够使用 SSH 凭证访问代码仓库。在 Kind(类型)下拉菜单中选择 SSH Username with private key(带私钥的 SSH 用户名)。Username:输入一个名称,例如 jenkins。Private Key(私钥):选择 Enter directly(直接输入),然后将之前生成的 SSH 私钥内容粘贴到这里。可以使用以下命令查看私钥内容:cat ~/.ssh/id_rsa将输出的内容复制粘贴到 Jenkins 的 Private Key 文本框中。Passphrase(密码):如果在生成密钥时设置了密码,这里需要填写;如果没有设置,可以留空。最后,点击 OK 保存凭证。{/collapse-item}{/collapse}配置jenkins的maven版本为了在Jenkins上运行Maven构建任务,我们需要在系统上安装Maven。1 下载并解压Maven前往 Maven官网 下载所需版本的Maven压缩包,或者直接使用命令下载并解压:# 下载 Maven (以版本3.8.8为例) wget https://downloads.apache.org/maven/maven-3/3.8.8/binaries/apache-maven-3.8.8-bin.tar.gz # 解压并移动到 /opt 目录 sudo tar -zxvf apache-maven-3.8.8-bin.tar.gz -C /opt2 配置环境变量接下来配置Maven的环境变量,以便系统中的所有用户都可以访问Maven:# 编辑 /etc/profile 文件 sudo vi /etc/profile在文件末尾添加以下内容:export M2_HOME=/opt/apache-maven-3.8.8 export PATH=$M2_HOME/bin:$PATH然后加载环境变量:source /etc/profile验证Maven是否安装成功:mvn -v3. 在Jenkins中配置Maven3.1 登录Jenkins在浏览器中访问Jenkins的地址,通常是 http://<服务器IP>:8080,登录到Jenkins管理界面。3.2 安装Maven Integration插件为了在Jenkins中使用Maven,我们需要安装Maven Integration插件:点击 "Manage Jenkins" (管理Jenkins)。点击 "Manage Plugins" (管理插件)。选择 "Available" (可用) 选项卡。搜索 "Maven Integration",然后安装该插件。3.3 配置Maven在Jenkins主界面,点击 "Manage Jenkins"。点击 "Global Tool Configuration" (全局工具配置)。滑动到 "Maven" 部分,点击 "Add Maven" (添加Maven)。给Maven设置一个名称,例如 "Maven 3.8.8"。取消选择 "Install automatically" (自动安装),并在 "MAVEN_HOME" 中填写之前Maven的安装路径,例如:/opt/apache-maven-3.8.8保存设置。4. 配置Jenkins任务使用Maven在为某个项目配置构建任务时,可以选择使用Maven:新建一个 "Freestyle project"。在构建部分,选择 "Invoke top-level Maven targets"。在 "Maven Version" 下拉框中选择之前配置的Maven版本 (例如 "Maven 3.8.8")。在 "Goals" 中填写需要执行的Maven命令,例如 clean install。5. 验证配置保存任务配置后,点击 "Build Now" (立即构建),观察构建日志,确认Maven已经正确执行。6. 可能遇到的问题6.1 环境变量未生效如果Maven未能成功运行,可能是环境变量没有正确加载,建议重新加载环境变量或重启Jenkins:# 重启Jenkins sudo systemctl restart jenkins6.2 权限问题确保Jenkins用户对Maven的安装目录有读执行权限,可以通过以下命令修改权限:sudo chmod -R 755 /opt/apache-maven-3.8.8在Jenkins中配置部署时,我使用了“Send files or execute commands over SSH”插件来自动化应用程序的部署。以下是我配置的脚本详细步骤和说明,脚本主要用于停止正在运行的Java应用并重新启动新版本。配置过程首先,我在Jenkins中使用了 Send files or execute commands over SSH 来执行脚本,通过SSH命令将构建生成的文件拷贝到服务器指定路径,并进行应用的停止和启动操作。文件复制部分cp /var/jenkins_home/workspace/jc-club-subject/jc-club-subject/jc-club-starter/target/jc-club-starter.jar /var/jenkins_home/jar/这条命令的作用是将构建好的 jc-club-starter.jar 文件从Jenkins的工作空间复制到目标目录 /var/jenkins_home/jar/ 中,以便后续进行启动操作。脚本内容下面的脚本用于停止正在运行的应用并重新启动新的版本:#!/bin/bash APP_NAME=jc-club-starter.jar LOG_NAME=jc-club-starter.log pid=`ps -ef | grep $APP_NAME | grep -v grep|awk '{print $2}'` function is_exist(){ pid=`ps -ef | grep $APP_NAME | grep -v grep|awk '{print $2}'` if [ -z ${pid} ]; then String="notExist" echo $String else String="exist" echo $String fi } str=$(is_exist) if [ ${str} = "exist" ]; then echo " 检测到已经启动的程序,pid 是 ${pid} " kill -9 $pid else echo " 程序没有启动了 " echo "${APP_NAME} is not running" fi str=$(is_exist) if [ ${str} = "exist" ]; then echo "${APP_NAME} 已经启动了. pid=${pid} ." else source /etc/profile BUILD_ID=dontKillMe nohup java -Xms300m -Xmx300m -jar /var/jenkins_home/jar/$APP_NAME >$LOG_NAME 2>&1 & echo "程序已重新启动..." fi脚本解析定义变量APP_NAME=jc-club-starter.jar:指定要启动的Java应用程序的名称。LOG_NAME=jc-club-starter.log:指定日志文件名称。检查应用是否存在通过 is_exist 函数来检测应用是否正在运行。使用 ps -ef | grep $APP_NAME | grep -v grep | awk '{print $2}' 来获取应用的进程ID(PID)。如果没有找到PID,则表示应用没有运行。停止正在运行的应用调用 is_exist 函数,如果返回 exist,说明程序正在运行,则使用 kill -9 $pid 强行停止该进程。如果程序未启动,则打印信息提示。重新启动应用再次调用 is_exist 函数来确认应用是否已被停止。如果程序未运行,使用 nohup 命令重新启动应用。source /etc/profile:加载环境变量。BUILD_ID=dontKillMe:防止Jenkins自动杀死后台运行的进程。nohup java -Xms300m -Xmx300m -jar /var/jenkins_home/jar/$APP_NAME >$LOG_NAME 2>&1 &:以 nohup 方式启动应用,并将日志输出到指定的日志文件中。
    Docker 安装 Jenkins 实现自动打包部署 Docker 安装 Jenkins 实现自动打包部署 Docker 安装 Jenkins 实现自动打包部署
    • 5个月前
    • 208
    • 0
    • 1
  • Docker 常用命令整理 一、Docker 基础命令1. 安装与配置安装 Docker:sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io查看 Docker 版本:docker --version启动 Docker 服务:sudo systemctl start docker停止 Docker 服务:sudo systemctl stop docker开机启动 Docker:sudo systemctl enable docker2. 镜像操作搜索镜像:docker search [image_name]拉取镜像:docker pull [image_name]:[tag]列出本地镜像:docker images删除本地镜像:docker rmi [image_id]二、容器管理命令1. 启动与停止容器运行容器:docker run -d --name [container_name] [image_name]停止容器:docker stop [container_id]启动已停止的容器:docker start [container_id]重启容器:docker restart [container_id]2. 容器交互与信息查看进入运行中的容器:docker exec -it [container_id] /bin/bash查看容器日志:docker logs [container_id]查看容器进程:docker ps查看所有容器(包括已停止的):docker ps -a3. 删除容器删除停止的容器:docker rm [container_id]删除所有停止的容器:docker container prune三、网络管理列出 Docker 网络:docker network ls创建自定义网络:docker network create [network_name]连接容器到自定义网络:docker network connect [network_name] [container_name]四、数据卷管理创建数据卷:docker volume create [volume_name]查看数据卷:docker volume ls删除数据卷:docker volume rm [volume_name]在运行容器时挂载数据卷:docker run -d -v [volume_name]:[container_path] [image_name]五、Docker Compose安装 Docker Compose:sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose sudo chmod +x /usr/local/bin/docker-compose启动服务:docker-compose up -d停止服务:docker-compose down六、Dockerfile构建镜像:docker build -t [image_name]:[tag] .基于 Dockerfile 运行容器:docker run -d -p [host_port]:[container_port] [image_name]七、常用选项说明-d:后台运行容器。-p:指定主机端口与容器端口的映射,例如 -p 8080:80。-v:挂载数据卷到容器,例如 -v my_volume:/app/data。--name:指定容器名称。--rm:容器停止后自动删除。
    • 5个月前
    • 200
    • 0
    • 0
  • 解决 Docker 安装镜像时的网络超时问题 在使用 Docker 搜索并安装镜像时,可能会遇到网络超时的问题,例如在执行以下命令时:docker search jenkins报错信息如下:Error response from daemon: Get "https://index.docker.io/v1/search?q=jenkins&n=25": dial tcp 31.13.87.34:443: i/o timeout这种情况通常是由于 Docker 默认源网络不稳定导致的。解决方法就是更换 Docker 镜像源。本文将为您介绍如何通过更换镜像源解决此类问题。最基础的解决方法:修改 /etc/docker/daemon.json 文件该文件用于配置 Docker 参数,默认是不存在的,需要自己创建。首先使用 cat 命令查看一下 daemon.json 中有没有内容:cat /etc/docker/daemon.json如果没有内容或者此文件不存在,我们可以使用 touch 命令创建并命名为 daemon.json 文件:touch /etc/docker/daemon.json通过 vim 命令打开 daemon.json 文件,注意如果您已经切换到 /etc/docker 目录中,可以直接使用 vim 进入即可:vi /etc/docker/daemon.json点击键盘上的 i 键切换到插入模式,在插入模式中将以下内容复制粘贴:{ "registry-mirrors": [ "https://registry.docker-cn.com", "http://hub-mirror.c.163.com", "https://docker.mirrors.ustc.edu.cn", "https://pee6w651.mirror.aliyuncs.com" ] }点击键盘 Esc 键退出插入模式,点击键盘 Shift + :,在冒号后面输入 wq 进行保存并退出。重启 Docker 使更改生效:systemctl daemon-reload systemctl restart docker目前可用的镜像代理拉取 pull 镜像时,遇到不可用、关停、访问比较慢的状态,建议同时配置多个镜像源。提供商地址DaoCloudhttps://docker.m.daocloud.io阿里云https://<your_code>.mirror.aliyuncs.comDocker镜像代理https://dockerproxy.com百度云https://mirror.baidubce.com南京大学https://docker.nju.edu.cn中科院https://mirror.iscas.ac.cn解决方案1:配置加速地址配置加速地址适用于 Ubuntu 16.04+、Debian 8+、CentOS 7+。具体步骤如下:方式一:配置 daemon.json 文件创建或编辑 /etc/docker/daemon.json 文件:sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-'EOF' { "registry-mirrors": [ "https://do.nark.eu.org", "https://dc.j8.work", "https://docker.m.daocloud.io", "https://dockerproxy.com", "https://docker.mirrors.ustc.edu.cn", "https://docker.nju.edu.cn" ] } EOF重启 Docker 使更改生效:sudo systemctl daemon-reload sudo systemctl restart docker检查加速是否生效:docker info如果从输出结果中看到了配置的 registry-mirrors 地址,说明配置成功。方式二:直接使用镜像加速地址如果有正在运行的容器不方便重启 Docker 服务,可以直接在拉取镜像时使用加速地址,例如:docker pull do.nark.eu.org/library/mysql:5.7第三方镜像AtomHub 可信镜像中心:大部分需要的镜像都可以找到。官网:https://atomhub.openatom.cn/用法示例:docker pull atomhub.openatom.cn/amd64/redis:7.0.13在使用 docker compose 时,可以先拉取对应版本的镜像,然后再执行 docker compose。加速代理站点Github 下载加速代理站点:站点地址:https://docker.888666222.xyz/设置方法:sudo tee /etc/docker/daemon.json <<EOF { "registry-mirrors": ["https://docker.888666222.xyz"] } EOF拉取镜像并重新打标签:docker pull docker.888666222.xyz/library/alpine:latest docker pull docker.888666222.xyz/coredns/coredns:latest解决方案2:使用代理拉取镜像创建配置文件:sudo mkdir -p /etc/systemd/system/docker.service.d sudo vim /etc/systemd/system/docker.service.d/http-proxy.conf在文件中添加代理设置:[Service] Environment="HTTP_PROXY=socks5://user:pass@127.0.0.1:1080" Environment="HTTPS_PROXY=socks5://user:pass@127.0.0.1:1080"重启 Docker:sudo systemctl daemon-reload sudo systemctl restart docker查看环境变量:sudo systemctl show --property=Environment docker解决方案3:直接传送镜像A 服务器保存 Docker 镜像:docker save myimage > myimage.tar将镜像传送到 B 服务器:scp myimage.tar root@192.0.2.0:/homeB 服务器加载 Docker 镜像:cd /home docker load < myimage.tar查看镜像:docker images
    • 5个月前
    • 436
    • 0
    • 1
  • Spring Boot 中定制 ObjectMapper 的全局 JSON 序列化配置 在开发 Web API 时,处理 JSON 数据的序列化和反序列化是非常常见的需求。Spring Boot 默认使用 Jackson 来进行 JSON 的处理,但有时候我们需要对 Jackson 的 ObjectMapper 进行自定义配置,以满足特定的业务需求。1. 全局配置 ObjectMapper在 Spring Boot 中,我们可以通过自定义 HttpMessageConverter 来实现全局的 JSON 序列化配置。具体实现如下:package com.jingdianjichi.subject.application.config; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; import java.util.List; @Configuration public class GlobalConfig extends WebMvcConfigurationSupport { @Override protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) { super.configureMessageConverters(converters); converters.add(mappingJackson2HttpMessageConverter()); } private MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() { ObjectMapper objectMapper = new ObjectMapper(); // 1. 允许空对象序列化 objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); // 2. 忽略空值字段 objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); return new MappingJackson2HttpMessageConverter(objectMapper); } }在这个类中,我们继承了 WebMvcConfigurationSupport 并覆盖了 configureMessageConverters 方法,通过添加自定义的 MappingJackson2HttpMessageConverter 实现了对全局 JSON 处理的自定义。2. 配置详解接下来,我们详细解读两行关键代码,它们分别实现了忽略空字段和允许空对象序列化的功能。2.1 objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);这行代码的作用是控制 Jackson 是否允许序列化空对象。SerializationFeature.FAIL_ON_EMPTY_BEANS: 默认情况下,Jackson 在遇到没有任何字段的空对象时会抛出异常。例如,当你有一个类的所有字段值都为 null 或没有任何字段时,Jackson 会认为这个对象无法序列化,并抛出错误。false: 通过将这个配置项设置为 false,即使对象为空,Jackson 仍然会将其序列化为 {},而不会抛出异常。这对于一些复杂对象结构或需要返回空对象的场景非常有用。示例:如果我们有一个类 User,但所有字段都是空的,默认情况下序列化会失败,而通过这行配置后,可以安全地将其序列化为 {}。2.2 objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);这行代码用于设置对象序列化时的字段包含规则,具体是忽略 null 值的字段。JsonInclude.Include.NON_NULL: 该配置告诉 Jackson 在序列化时,忽略所有值为 null 的字段。也就是说,如果对象的某个字段值为 null,那么这个字段不会出现在最终的 JSON 结果中。示例:假设我们有如下 Java 对象:User user = new User(); user.setName(null); user.setAge(25);通过配置 NON_NULL,序列化后的 JSON 将会是:{ "age": 25 }而不会包含 null 值的 name 字段。这样可以减少 API 响应的大小,同时提高数据的简洁性。
    Spring Boot 中定制 ObjectMapper 的全局 JSON 序列化配置 Spring Boot 中定制 ObjectMapper 的全局 JSON 序列化配置 Spring Boot 中定制 ObjectMapper 的全局 JSON 序列化配置
    • 5个月前
    • 177
    • 0
    • 1
  • SpringBoot基于工厂+策略模式的题目处理思路详解
    SpringBoot基于工厂+策略模式的题目处理思路详解 在软件开发中,常常会遇到需要根据不同类型执行不同处理逻辑的场景。如果直接使用大量的 if-else 或 switch-case,代码会变得冗长且难以维护。为了解决这个问题,我们可以采用策略模式和工厂模式,使代码结构更加清晰,且方便扩展。下面,我将结合代码,详细讲解这种设计思路,并说明实现后数据的流向。特别是,对于工厂类的设计,我们将进行详细的拆分和讲解。一、设计思路1. 问题背景有一个试题需求,需要处理多种类型的题目:单选题(RADIO)多选题(MULTIPLE)判断题(JUDGE)简答题(BRIEF)每种题型的处理逻辑不同,如果直接在代码中大量使用条件判断,不利于代码的维护和扩展。2. 设计目标解耦不同题型的处理逻辑:每种题型的处理逻辑独立,互不影响。提高代码的可扩展性:新增题型时,尽量不修改原有代码。简化业务层的代码:业务层不需要关注具体的处理逻辑。3. 采用的设计模式策略模式(Strategy Pattern):定义一系列算法,将每个算法封装起来,使它们可以互相替换,且算法的变化不会影响到使用算法的客户。工厂模式(Factory Pattern):定义一个创建对象的接口,让子类决定实例化哪个类。4. 设计步骤定义策略接口:抽象出题目处理的通用接口。实现具体策略类:为每种题型创建一个处理器,实现策略接口。创建工厂类:根据题目类型,返回对应的处理器。业务层使用工厂获取处理器:业务层调用处理器的方法,完成题目的处理。二、代码实现1. 定义策略接口 SubjectTypeHandlerpublic interface SubjectTypeHandler { SubjectInfoTypeEnum getHandlerType(); void add(SubjectInfoBO subjectInfoBO); }getHandlerType():返回处理器对应的题目类型枚举。add(SubjectInfoBO subjectInfoBO):处理添加题目的方法。2. 实现具体策略类(1)单选题处理器 RadioTypeHandlerpublic class RadioTypeHandler implements SubjectTypeHandler { @Override public SubjectInfoTypeEnum getHandlerType() { return SubjectInfoTypeEnum.RADIO; } @Override public void add(SubjectInfoBO subjectInfoBO) { // 实现单选题的添加逻辑 System.out.println("添加单选题:" + subjectInfoBO); } }(2)多选题处理器 MultipleTypeHandlerpublic class MultipleTypeHandler implements SubjectTypeHandler { @Override public SubjectInfoTypeEnum getHandlerType() { return SubjectInfoTypeEnum.MULTIPLE; } @Override public void add(SubjectInfoBO subjectInfoBO) { // 实现多选题的添加逻辑 System.out.println("添加多选题:" + subjectInfoBO); } }(3)判断题处理器 JudgeTypeHandlerpublic class JudgeTypeHandler implements SubjectTypeHandler { @Override public SubjectInfoTypeEnum getHandlerType() { return SubjectInfoTypeEnum.JUDGE; } @Override public void add(SubjectInfoBO subjectInfoBO) { // 实现判断题的添加逻辑 System.out.println("添加判断题:" + subjectInfoBO); } }(4)简答题处理器 BriefTypeHandlerpublic class BriefTypeHandler implements SubjectTypeHandler { @Override public SubjectInfoTypeEnum getHandlerType() { return SubjectInfoTypeEnum.BRIEF; } @Override public void add(SubjectInfoBO subjectInfoBO) { // 实现简答题的添加逻辑 System.out.println("添加简答题:" + subjectInfoBO); } }3. 创建工厂类 SubjectTypeHandlerFactory下面,我们对工厂类的设计进行详细的拆分和讲解。(1)工厂类的定义@Component public class SubjectTypeHandlerFactory implements InitializingBean { // 注入所有实现了 SubjectTypeHandler 接口的处理器 @Resource private List<SubjectTypeHandler> subjectTypeHandlerList; private Map<SubjectInfoTypeEnum, SubjectTypeHandler> handlerMap = new HashMap<>(); public SubjectTypeHandler getHandler(int subjectType) { SubjectInfoTypeEnum typeEnum = SubjectInfoTypeEnum.getByCode(subjectType); return handlerMap.get(typeEnum); } @Override public void afterPropertiesSet() { // 将处理器按题目类型存入 Map for (SubjectTypeHandler handler : subjectTypeHandlerList) { handlerMap.put(handler.getHandlerType(), handler); } } }一定要注意添加@Component注解,表示将代码交给springboot托管,否则代码不会生效,这样在springboot在启动完成之后,我们的Handler就被装配进了spring容器了。(2)成员变量和注入// 注入所有实现了 SubjectTypeHandler 接口的处理器 @Resource private List<SubjectTypeHandler> subjectTypeHandlerList; private Map<SubjectInfoTypeEnum, SubjectTypeHandler> handlerMap = new HashMap<>();subjectTypeHandlerList:通过 @Resource 注解,Spring 会自动注入容器中所有实现了 SubjectTypeHandler 接口的 Bean。handlerMap:用于存储题目类型与对应处理器的映射关系。(3)初始化方法 afterPropertiesSet()@Override public void afterPropertiesSet() { // 将处理器按题目类型存入 Map for (SubjectTypeHandler handler : subjectTypeHandlerList) { handlerMap.put(handler.getHandlerType(), handler); } }作用:在 Bean 初始化完成后,Spring 会调用 afterPropertiesSet() 方法。逻辑:遍历 subjectTypeHandlerList,获取每个处理器。调用处理器的 getHandlerType() 方法,获取对应的题目类型。将题目类型和处理器存入 handlerMap,建立映射关系。目的:将所有的处理器按照题目类型注册到工厂中,方便后续根据题目类型获取对应的处理器。(4)获取处理器方法 getHandler(int subjectType)public SubjectTypeHandler getHandler(int subjectType) { SubjectInfoTypeEnum typeEnum = SubjectInfoTypeEnum.getByCode(subjectType); return handlerMap.get(typeEnum); }参数:subjectType,题目类型的整数表示。逻辑:调用 SubjectInfoTypeEnum.getByCode(subjectType),将整数类型转换为对应的枚举类型 SubjectInfoTypeEnum。从 handlerMap 中获取对应的处理器。返回值:SubjectTypeHandler,对应题目类型的处理器。(5)类注解 @Component@Component public class SubjectTypeHandlerFactory implements InitializingBean { ... }@Component:将工厂类声明为一个 Spring Bean,交由 Spring 容器管理。implements InitializingBean:实现 InitializingBean 接口,目的是在 Bean 初始化后执行 afterPropertiesSet() 方法。4. 业务层调用@Service public class SubjectService { @Autowired private SubjectTypeHandlerFactory handlerFactory; public void addSubject(SubjectInfoBO subjectInfoBO) { // 获取对应的处理器 SubjectTypeHandler handler = handlerFactory.getHandler(subjectInfoBO.getType()); if (handler != null) { // 调用处理器的添加方法 handler.add(subjectInfoBO); } else { throw new IllegalArgumentException("未知的题目类型"); } } }注入工厂类:@Autowired 将 SubjectTypeHandlerFactory 注入到业务层。添加题目方法:根据 subjectInfoBO.getType() 获取题目类型。调用工厂的 getHandler() 方法获取对应的处理器。调用处理器的 add() 方法,处理题目添加逻辑。三、实现后的数据流向1. 数据流整体概述当需要添加一个新题目时,数据流向如下:接收请求:SubjectService.addSubject() 接收 SubjectInfoBO 对象,包含题目的详细信息。获取处理器:SubjectTypeHandlerFactory.getHandler() 根据题目类型返回对应的处理器。处理题目:调用处理器的 add() 方法,执行具体的处理逻辑。完成添加:处理器完成题目的添加操作。2. 数据流详细步骤(1)接收题目信息SubjectInfoBO subjectInfoBO = new SubjectInfoBO(); subjectInfoBO.setType(1); // 假设 1 代表单选题 subjectInfoBO.setContent("题目内容"); // ... 设置其他属性 subjectService.addSubject(subjectInfoBO);创建题目对象:实例化 SubjectInfoBO,设置题目类型和内容等属性。调用业务方法:调用 SubjectService.addSubject() 方法,传入题目对象。(2)业务层获取处理器public void addSubject(SubjectInfoBO subjectInfoBO) { // 获取处理器 SubjectTypeHandler handler = handlerFactory.getHandler(subjectInfoBO.getType()); // ... }获取题目类型:从 subjectInfoBO 中获取题目类型。调用工厂方法:使用工厂的 getHandler() 方法获取对应的处理器。(3)工厂返回处理器public SubjectTypeHandler getHandler(int subjectType) { SubjectInfoTypeEnum typeEnum = SubjectInfoTypeEnum.getByCode(subjectType); return handlerMap.get(typeEnum); }类型转换:将整数类型的题目类型转换为枚举类型。获取处理器:从 handlerMap 中根据枚举类型获取处理器。(4)处理器处理题目if (handler != null) { handler.add(subjectInfoBO); } else { throw new IllegalArgumentException("未知的题目类型"); }调用处理方法:如果获取到了处理器,调用其 add() 方法。异常处理:如果没有对应的处理器,抛出异常。处理器的 add() 方法,例如单选题处理器:public class RadioTypeHandler implements SubjectTypeHandler { @Override public void add(SubjectInfoBO subjectInfoBO) { // 实现单选题的添加逻辑 System.out.println("添加单选题:" + subjectInfoBO); // 例如,将题目信息保存到数据库 } }具体处理逻辑:处理器执行特定题型的添加逻辑,例如数据校验、数据库操作等。3. 数据流示意图用户请求 --> SubjectService.addSubject() --> SubjectTypeHandlerFactory.getHandler() --> 返回对应处理器 | v 调用处理器的 add() 方法 --> 处理器执行添加逻辑 --> 完成题目添加四、工厂类设计的详细讲解1. 工厂类的职责SubjectTypeHandlerFactory 的主要职责是根据题目类型返回对应的处理器。这一过程包含:初始化处理器映射关系:在工厂类初始化时,将所有的处理器注册到一个映射中。提供获取处理器的方法:根据题目类型,返回相应的处理器实例。2. 工厂类的实现细节(1)注入所有处理器@Resource private List<SubjectTypeHandler> subjectTypeHandlerList;@Resource 注解:由 Spring 自动注入容器中所有实现了 SubjectTypeHandler 接口的 Bean。目的:获取所有的处理器实例,方便后续的映射注册。(2)定义处理器映射private Map<SubjectInfoTypeEnum, SubjectTypeHandler> handlerMap = new HashMap<>();handlerMap:用于存储题目类型和处理器实例的对应关系。类型安全:使用 SubjectInfoTypeEnum 作为键,确保类型的一致性。(3)初始化处理器映射@Override public void afterPropertiesSet() { for (SubjectTypeHandler handler : subjectTypeHandlerList) { handlerMap.put(handler.getHandlerType(), handler); } }afterPropertiesSet() 方法:由 InitializingBean 接口提供,在 Bean 的属性设置完成后由 Spring 自动调用。处理逻辑:遍历 subjectTypeHandlerList 中的所有处理器。调用每个处理器的 getHandlerType() 方法,获取其支持的题目类型。将题目类型和处理器实例存入 handlerMap。(4)获取处理器的方法public SubjectTypeHandler getHandler(int subjectType) { SubjectInfoTypeEnum typeEnum = SubjectInfoTypeEnum.getByCode(subjectType); return handlerMap.get(typeEnum); }参数说明:subjectType,题目类型的整数表示。步骤解析:类型转换:调用 SubjectInfoTypeEnum.getByCode(subjectType) 方法,将整数类型转换为枚举类型。SubjectInfoTypeEnum typeEnum = SubjectInfoTypeEnum.getByCode(subjectType);获取处理器:从 handlerMap 中根据枚举类型获取对应的处理器实例。return handlerMap.get(typeEnum);返回值:对应题目类型的处理器,如果不存在则返回 null。(5)异常处理(可选)在实际应用中,可能需要对获取不到处理器的情况进行处理,可以在 getHandler() 方法中添加异常处理:public SubjectTypeHandler getHandler(int subjectType) { SubjectInfoTypeEnum typeEnum = SubjectInfoTypeEnum.getByCode(subjectType); SubjectTypeHandler handler = handlerMap.get(typeEnum); if (handler == null) { throw new IllegalArgumentException("未知的题目类型:" + subjectType); } return handler; }目的:避免业务层需要处理 null 值,将异常抛出,提高代码的健壮性。(6)工厂类的优势集中管理:所有处理器的实例化和获取都由工厂类负责,方便管理。解耦业务层和处理器:业务层无需关心处理器的实现细节,只需通过工厂获取即可。易于扩展:新增题型处理器时,只需实现 SubjectTypeHandler 接口,并将其注册到 Spring 容器中,无需修改工厂类的代码。3. 工厂类的完整代码@Component public class SubjectTypeHandlerFactory implements InitializingBean { @Resource private List<SubjectTypeHandler> subjectTypeHandlerList; private Map<SubjectInfoTypeEnum, SubjectTypeHandler> handlerMap = new HashMap<>(); public SubjectTypeHandler getHandler(int subjectType) { SubjectInfoTypeEnum typeEnum = SubjectInfoTypeEnum.getByCode(subjectType); SubjectTypeHandler handler = handlerMap.get(typeEnum); if (handler == null) { throw new IllegalArgumentException("未知的题目类型:" + subjectType); } return handler; } @Override public void afterPropertiesSet() { for (SubjectTypeHandler handler : subjectTypeHandlerList) { handlerMap.put(handler.getHandlerType(), handler); } } }
    • 5个月前
    • 179
    • 0
    • 0
  • Java中for循环、增强型for循环与forEach()方法的比较 Java 中 for 循环、增强型 foreach 循环与 forEach() 方法的比较传统 for 循环基本语法传统的 for 循环由三个部分组成:初始化部分、循环条件和迭代部分。其基本语法如下:for (初始化; 条件; 迭代) { // 循环体 }示例:for (int i = 0; i < array.length; i++) { System.out.println(array[i]); }优缺点优点:高度控制:可以精确控制循环变量、步进方式,以及在循环中灵活操作索引。适用广泛:适用于需要索引访问或复杂循环控制的场景。缺点:代码冗长:需要手动管理循环变量和边界条件,增加了代码的复杂性。易出错:手动管理索引可能导致越界错误或无限循环。增强型 foreach 循环基本语法增强型 foreach 循环简化了遍历数组或集合的过程,其基本语法如下:for (元素类型 元素变量 : 数组或集合) { // 循环体 }示例:for (int element : array) { System.out.println(element); }优缺点优点:简洁明了:语法更简洁,减少了循环控制变量和边界检查的代码。可读性高:清晰地表达了“对于每个元素”这一意图,使代码更易理解。缺点:灵活性较低:无法在循环中轻松获取元素的索引,或控制循环的步进。不适合修改集合:在遍历过程中直接修改集合(如删除元素)可能导致 ConcurrentModificationException。forEach() 方法基本语法自 Java 8 引入的 forEach() 方法提供了一种函数式编程的方式来遍历集合。它通常与 Lambda 表达式或方法引用一起使用。示例:List<String> list = Arrays.asList("A", "B", "C"); // 使用 Lambda 表达式 list.forEach(element -> System.out.println(element)); // 使用方法引用 list.forEach(System.out::println);{callout color="#4d7eef"}stream().map() 方法基本语法stream().map() 是 Java 8 引入的 Stream API 中的一个中间操作,用于将流中的每个元素映射成另一种形式。它常与终端操作如 forEach() 结合使用,实现数据的转换和处理。示例:List<String> names = Arrays.asList("alice", "bob", "charlie"); // 使用 map() 将名字转换为大写 names.stream() .map(String::toUpperCase) .forEach(System.out::println);优缺点优点:数据转换:map() 方法允许对流中的每个元素进行转换,如类型转换、数据格式转换等。链式操作:支持链式调用,可以轻松组合多个操作,如过滤、排序、映射等。高效处理:与 Stream API 结合,可以利用并行流进行高效的数据处理。函数式编程风格:促进更清晰、声明式的代码编写方式。缺点:学习曲线:需要理解函数式编程的概念和 Stream API 的使用。调试复杂:链式操作中的每一步不易单独调试,特别是在复杂的转换过程中。过度使用可能降低可读性:过长的链式调用可能导致代码难以阅读和维护。应用场景数据转换和处理:如将对象列表转换为特定字段的列表。复杂的数据操作:如筛选、分组、排序和聚合等。并行处理:利用多核处理器加快大数据量的处理速度。示例:List<User> users = getUsers(); // 提取所有用户的邮箱并转换为大写 List<String> emails = users.stream() .map(User::getEmail) .map(String::toUpperCase) .collect(Collectors.toList()); // 打印所有邮箱 emails.forEach(System.out::println);{/callout}三者的比较1. 语法与可读性传统 for 循环:需要显式管理循环变量和条件,代码较为冗长,但提供了高度的控制力。for (int i = 0; i < list.size(); i++) { System.out.println("Element at " + i + ": " + list.get(i)); }增强型 foreach 循环:语法简洁,适用于只需访问元素的场景,增强了代码的可读性。for (String element : list) { System.out.println(element); }forEach() 方法:结合 Lambda 表达式或方法引用,实现了更简洁和表达力强的遍历方式,适合函数式编程风格。list.forEach(System.out::println);stream().map() 方法:通过流式操作和中间操作(如 map()),实现数据的转换和处理,代码更加声明式和模块化。List<String> upperCaseNames = names.stream() .map(String::toUpperCase) .collect(Collectors.toList());2. 使用场景传统 for 循环:需要访问元素的索引。需要对循环变量进行复杂操作(如步进、跳跃)。需要在循环中进行控制(如 break、continue)。增强型 foreach 循环:仅需访问集合或数组中的元素。不需要元素的索引信息。遍历过程中不需要修改集合。forEach() 方法:适用于函数式编程风格的遍历。需要对每个元素执行相同的操作。结合 Stream API 进行复杂的数据处理和并行操作。stream().map() 方法:需要对数据进行转换或映射。需要进行复杂的数据操作,如过滤、排序、分组等。需要利用并行流提高数据处理性能。3. 性能表现在大多数情况下,这三种循环方式在性能上差异不大,尤其是在遍历数组时。然而,具体表现取决于集合类型和具体的使用场景:遍历数组:传统 for 和增强型 foreach 循环的性能相近。forEach() 方法的性能也相似,但可能略受 Lambda 表达式的影响。遍历 ArrayList:传统 for 循环可能略快,因为它通过索引直接访问元素。增强型 foreach 和 forEach() 方法依赖于迭代器,性能差异较小。遍历 LinkedList:传统 for 循环效率较低,因为 get(i) 操作需要线性时间。增强型 foreach 和 forEach() 方法使用迭代器,遍历效率更高。并行处理:forEach() 方法可与 Stream API 结合,利用并行流提高大数据量的处理性能。传统 for 循环和增强型 foreach 循环不直接支持并行处理。结论: 性能差异通常较小,除非在特殊情况下需要优化。选择哪种循环结构应更多基于代码的可读性和需求。4. 修改集合的能力传统 for 循环:允许在遍历过程中修改集合(如删除元素),但需要谨慎操作以避免 ConcurrentModificationException。for (int i = 0; i < list.size(); i++) { if (list.get(i).isInvalid()) { list.remove(i); i--; // 调整索引以避免跳过元素 } }增强型 foreach 循环:不推荐在遍历过程中直接修改集合,尤其是删除元素,因为这可能导致 ConcurrentModificationException。错误示例:for (String name : names) { if (name.isEmpty()) { names.remove(name); // 会抛出 ConcurrentModificationException } }forEach() 方法:同样不推荐在遍历过程中修改集合。使用 forEach() 方法时,如果需要修改集合,建议使用 Iterator 或其他安全的方式。错误示例:list.forEach(element -> { if (element.isInvalid()) { list.remove(element); // 会抛出 ConcurrentModificationException } });正确示例(使用 Iterator):Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String element = iterator.next(); if (element.isInvalid()) { iterator.remove(); } }stream().map() 方法:通常用于数据转换,不建议在流操作中修改源集合。这不仅会导致并发修改异常,还可能引入不可预测的行为。错误示例:List<String> names = new ArrayList<>(Arrays.asList("Alice", "Bob", "Charlie")); names.stream() .map(name -> { if (name.equals("Bob")) { names.remove(name); // 会抛出 ConcurrentModificationException } return name.toUpperCase(); }) .forEach(System.out::println);正确示例:List<String> filteredNames = names.stream() .filter(name -> !name.equals("Bob")) .collect(Collectors.toList()); filteredNames.forEach(System.out::println);结论: 如果需要在遍历过程中修改集合,传统 for 循环或使用 Iterator 更为合适。forEach() 和流操作应避免直接修改源集合。举例说明示例 1:遍历数组传统 for 循环:int[] numbers = {1, 2, 3, 4, 5}; for (int i = 0; i < numbers.length; i++) { System.out.println("Index " + i + ": " + numbers[i]); }增强型 foreach 循环:int[] numbers = {1, 2, 3, 4, 5}; for (int number : numbers) { System.out.println(number); }forEach() 方法:int[] numbers = {1, 2, 3, 4, 5}; Arrays.stream(numbers).forEach(System.out::println);stream().map() 方法:int[] numbers = {1, 2, 3, 4, 5}; int[] squaredNumbers = Arrays.stream(numbers) .map(n -> n * n) .toArray(); Arrays.stream(squaredNumbers).forEach(System.out::println);分析:传统 for 循环可以同时获取元素值和索引。增强型 foreach 循环代码更简洁,但无法直接获取索引。forEach() 方法通过流式操作实现,语法简洁,适合函数式编程。stream().map() 方法不仅遍历元素,还对每个元素进行转换(如平方操作),并生成新的数组。示例 2:遍历 ArrayList传统 for 循环:List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C")); for (int i = 0; i < list.size(); i++) { System.out.println("Element at " + i + ": " + list.get(i)); }增强型 foreach 循环:List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C")); for (String element : list) { System.out.println(element); }forEach() 方法:List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C")); list.forEach(System.out::println);stream().map() 方法:List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C")); List<String> lowerCaseList = list.stream() .map(String::toLowerCase) .collect(Collectors.toList()); lowerCaseList.forEach(System.out::println);分析:传统 for 循环适用于需要元素索引的场景。增强型 foreach 和 forEach() 方法适用于只需访问元素值的场景,代码更为简洁。stream().map() 方法用于将所有元素转换为小写字母,并收集到新的列表中,然后遍历打印。示例 3:遍历 LinkedList传统 for 循环(低效):List<String> linkedList = new LinkedList<>(Arrays.asList("X", "Y", "Z")); for (int i = 0; i < linkedList.size(); i++) { System.out.println(linkedList.get(i)); }增强型 foreach 循环(高效):List<String> linkedList = new LinkedList<>(Arrays.asList("X", "Y", "Z")); for (String element : linkedList) { System.out.println(element); }forEach() 方法(高效):List<String> linkedList = new LinkedList<>(Arrays.asList("X", "Y", "Z")); linkedList.forEach(System.out::println);stream().map() 方法:List<String> linkedList = new LinkedList<>(Arrays.asList("X", "Y", "Z")); List<String> reversedList = linkedList.stream() .map(element -> new StringBuilder(element).reverse().toString()) .collect(Collectors.toList()); reversedList.forEach(System.out::println);分析:传统 for 循环在 LinkedList 中效率较低,因为 get(i) 操作需要线性时间。增强型 foreach 和 forEach() 方法使用迭代器,遍历效率更高。stream().map() 方法用于将每个元素反转,并收集到新的列表中,然后遍历打印。示例 4:使用 forEach() 方法进行复杂操作示例:List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); // 使用 Lambda 表达式进行过滤和打印 names.stream() .filter(name -> name.startsWith("A")) .forEach(name -> System.out.println("Filtered Name: " + name)); // 使用方法引用进行打印 names.stream() .filter(name -> name.startsWith("A")) .forEach(System.out::println); // 使用 map() 进行转换后打印 names.stream() .map(String::toUpperCase) .forEach(System.out::println);分析:结合 Stream API,forEach() 方法能够实现复杂的数据处理和操作。使用 map() 方法将所有名字转换为大写,然后打印,展示了数据转换与遍历的结合。提高了代码的表达力和可读性,适用于需要对数据进行多步处理的场景。最佳实践建议优先使用增强型 foreach 循环和 forEach() 方法:在不需要索引的情况下,选择增强型 foreach 循环或 forEach() 方法,以提高代码的简洁性和可读性。在需要索引访问时使用传统 for 循环:当需要访问元素的索引,或根据索引进行计算时,传统 for 循环更为合适。结合 Stream API 使用 forEach() 和 map() 方法:在需要进行复杂的数据处理、过滤、转换或并行操作时,结合 Stream API 使用 forEach() 和 map() 方法,以发挥其优势。谨慎修改集合:在遍历过程中需要修改集合时,避免使用增强型 foreach 循环或 forEach() 方法,改用传统 for 循环或迭代器,以防止 ConcurrentModificationException。考虑集合类型:根据所使用的集合类型(如 ArrayList vs LinkedList),选择最合适的循环结构以优化性能。利用 stream().map() 进行数据转换:当需要对集合中的元素进行转换、映射或其他中间操作时,优先考虑使用 stream().map(),以实现更高效和更具可读性的代码。示例:List<User> users = getUsers(); // 使用 stream().map() 提取用户邮箱并转换为大写 List<String> upperCaseEmails = users.stream() .map(User::getEmail) .map(String::toUpperCase) .collect(Collectors.toList()); // 打印所有转换后的邮箱 upperCaseEmails.forEach(System.out::println);总结在 Java 编程中,选择合适的循环结构对于编写高效、可读性强的代码至关重要。传统的 for 循环、增强型 foreach 循环、forEach() 方法以及 stream().map() 方法各有优劣,适用于不同的场景:传统 for 循环:适用于需要高度控制、访问索引或在循环中修改集合的场景。增强型 foreach 循环:适用于只需访问元素值的简单遍历,代码更简洁。forEach() 方法:适用于函数式编程风格的遍历,结合 Stream API 实现复杂的数据处理和并行操作。stream().map() 方法:适用于需要对数据进行转换、映射和中间处理的场景,增强代码的表达力和模块化。
    • 5个月前
    • 188
    • 0
    • 0
  • 人生倒计时
    舔狗日记