Showing Posts From

Maven

OkHttp 发起 HTTP 请求:POST JSON、表单、文件上传与异步调用

Maven 依赖 <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>4.12.0</version> </dependency>GET 请求 OkHttpClient client = new OkHttpClient();Request request = new Request.Builder() .url("https://api.example.com/users/1") .build();try (Response response = client.newCall(request).execute()) { String body = response.body().string(); System.out.println(body); }POST JSON OkHttpClient client = new OkHttpClient();MediaType JSON = MediaType.get("application/json; charset=utf-8"); String json = "{\"username\": \"admin\", \"password\": \"secret\"}";RequestBody body = RequestBody.create(json, JSON);Request request = new Request.Builder() .url("https://api.example.com/login") .post(body) .build();try (Response response = client.newCall(request).execute()) { System.out.println(response.body().string()); }自定义 Header Request request = new Request.Builder() .url("https://api.example.com/data") .header("Authorization", "Bearer your-token") .header("Content-Type", "application/json") .post(body) .build();表单提交(application/x-www-form-urlencoded) RequestBody formBody = new FormBody.Builder() .add("username", "admin") .add("password", "secret") .build();Request request = new Request.Builder() .url("https://api.example.com/login") .post(formBody) .build();文件上传(multipart/form-data) File file = new File("/path/to/file.jpg");RequestBody fileBody = RequestBody.create(file, MediaType.get("image/jpeg"));RequestBody multipart = new MultipartBody.Builder() .setType(MultipartBody.FORM) .addFormDataPart("file", file.getName(), fileBody) .addFormDataPart("description", "头像上传") .build();Request request = new Request.Builder() .url("https://api.example.com/upload") .post(multipart) .build();超时配置 OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(5, TimeUnit.SECONDS) .readTimeout(10, TimeUnit.SECONDS) .writeTimeout(10, TimeUnit.SECONDS) .build();异步请求(enqueue) client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { e.printStackTrace(); } @Override public void onResponse(Call call, Response response) throws IOException { try (ResponseBody body = response.body()) { System.out.println(body.string()); } } });异步回调在 OkHttp 内部线程池中执行,Android 中更新 UI 需要切回主线程。 封装工具类 public class HttpUtil { private static final OkHttpClient CLIENT = new OkHttpClient.Builder() .connectTimeout(5, TimeUnit.SECONDS) .readTimeout(10, TimeUnit.SECONDS) .build(); private static final MediaType JSON = MediaType.get("application/json; charset=utf-8"); public static String get(String url) throws IOException { Request request = new Request.Builder().url(url).build(); try (Response response = CLIENT.newCall(request).execute()) { return response.body().string(); } } public static String postJson(String url, String json) throws IOException { RequestBody body = RequestBody.create(json, JSON); Request request = new Request.Builder().url(url).post(body).build(); try (Response response = CLIENT.newCall(request).execute()) { return response.body().string(); } } }OkHttpClient 实例应该全局复用,避免每次请求创建新实例浪费连接池资源。

Spring RestTemplate 发起 HTTP 请求:GET、POST、自定义 Header 与超时配置

配置 Bean @Configuration public class RestConfig { @Bean public RestTemplate restTemplate() { SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); factory.setConnectTimeout(5000); factory.setReadTimeout(10000); return new RestTemplate(factory); } }然后在 Service/Controller 中注入: @Autowired private RestTemplate restTemplate;GET 请求 简单 GET: String result = restTemplate.getForObject( "https://api.example.com/users/1", String.class );带路径参数: String result = restTemplate.getForObject( "https://api.example.com/users/{id}", String.class, 1 );返回实体类(需要 Jackson): @Data public class User { private Integer id; private String name; }User user = restTemplate.getForObject( "https://api.example.com/users/1", User.class );POST 请求 发送 JSON: Map<String, Object> body = new HashMap<>(); body.put("username", "admin"); body.put("password", "secret");String result = restTemplate.postForObject( "https://api.example.com/login", body, String.class );自定义 Header(带 Token) HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.set("Authorization", "Bearer your-token");Map<String, Object> body = new HashMap<>(); body.put("name", "test");HttpEntity<Map<String, Object>> entity = new HttpEntity<>(body, headers);ResponseEntity<String> response = restTemplate.exchange( "https://api.example.com/data", HttpMethod.POST, entity, String.class );String responseBody = response.getBody(); int statusCode = response.getStatusCode().value();GET 请求带 Header HttpHeaders headers = new HttpHeaders(); headers.set("Authorization", "Bearer your-token");HttpEntity<Void> entity = new HttpEntity<>(headers);ResponseEntity<String> response = restTemplate.exchange( "https://api.example.com/profile", HttpMethod.GET, entity, String.class );处理异常 try { String result = restTemplate.getForObject(url, String.class); } catch (HttpClientErrorException e) { // 4xx 错误 System.out.println("HTTP " + e.getStatusCode() + ": " + e.getResponseBodyAsString()); } catch (HttpServerErrorException e) { // 5xx 错误 System.out.println("Server error: " + e.getMessage()); } catch (ResourceAccessException e) { // 连接超时、网络错误 System.out.println("Connection error: " + e.getMessage()); }Spring Boot 3 注意 RestTemplate 已进入维护模式,Spring Boot 3 推荐使用 WebClient(响应式)或 Spring 6.1 引入的 RestClient(同步): // RestClient(Spring 6.1+) RestClient client = RestClient.create(); String result = client.get() .uri("https://api.example.com/users/1") .retrieve() .body(String.class);老项目继续使用 RestTemplate 完全没问题,新项目可以考虑迁移到 RestClient。

Maven 运行 Spring Boot:mvn spring-boot:run 常见错误与多模块 ${revision} 问题

基本命令 在 Spring Boot 项目根目录执行: mvn spring-boot:run常用选项: # 跳过测试(开发时推荐) mvn spring-boot:run -DskipTests# 指定激活 profile mvn spring-boot:run -Dspring-boot.run.profiles=dev# 组合使用 mvn spring-boot:run -DskipTests -Dspring-boot.run.profiles=dev启动成功标志: Started XxxApplication in 3.421 seconds (process running for 4.012)打包后运行(生产推荐) mvn clean package -DskipTests java -jar target/xxx-0.0.1-SNAPSHOT.jar打包出的 fat jar 包含所有依赖,可在任意安装了 JRE 的机器上运行。 错误一:mvn 不是内部或外部命令 Maven 未加入 PATH,测试: mvn -v如果报错,检查 MAVEN_HOME 和 PATH 配置,重开终端或重启后重试。 错误二:No plugin found for prefix 'spring-boot' pom.xml 缺少 Spring Boot Maven 插件: <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>如果项目继承了 spring-boot-starter-parent,插件版本已由父 POM 管理,不需要指定版本号。 错误三:多模块项目 ${revision} 无法解析 多模块项目使用 ${revision} 占位符统一版本号时: [ERROR] Could not find artifact net.xxx:parent:pom:${revision}根因是运行命令的子模块还没安装父 POM 到本地仓库。解决方法: 从根目录先执行 install: # 进入多模块根目录(有最顶层 pom.xml 的目录) cd /path/to/root# 安装所有模块到本地仓库 mvn clean install -DskipTests安装后再进入子模块运行: cd maku-server mvn spring-boot:run -DskipTests或直接在根目录指定子模块: mvn spring-boot:run -pl maku-server -am -DskipTests-pl 指定目标模块,-am 同时构建依赖的模块。 端口占用 # Windows netstat -ano | findstr 8080 taskkill /PID <PID> /F# Linux lsof -i :8080 kill -9 <PID>或在 application.yml 修改端口: server: port: 8081

Maven 跑 Spring Boot 常用命令 + 依赖解析失败排查

Spring Boot 项目用 Maven 跑,其实就三个常用命令。但组合起来 + 遇到依赖冲突时坑不少。 三个常用命令 开发时启动: mvn spring-boot:run看到 Started XxxApplication in x.xxx seconds 就通了。 跳过测试加速启动: mvn spring-boot:run -DskipTests老项目测试跑几分钟是常态,开发时基本都会跳。 指定环境: mvn spring-boot:run -Dspring-boot.run.profiles=dev读 application-dev.yml 里的配置。多环境就用这个切。 打包 + 运行 生产部署更常用两步走: mvn clean package -DskipTests java -jar target/*.jar产物是 fat jar(Spring Boot 把依赖都塞里面),直接 java -jar 起就行。加参数: java -jar target/myapp.jar \ --spring.profiles.active=prod \ --server.port=8081后台跑用 nohup: nohup java -jar target/myapp.jar > app.log 2>&1 &生产建议走 systemd 或 Docker,不是裸 nohup。 常见问题 1. mvn 不是内部或外部命令 PATH 没配好。先确认: mvn -v不通就照 Windows 追加目录到 PATH 里的方法把 MAVEN_HOME\bin 加进去。改完 新开一个终端 或注销重登。 2. No plugin found for prefix 'spring-boot' pom.xml 里没引 Spring Boot 插件。加: <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>spring-boot-starter-parent 已经管好版本,不用写 version。没继承 parent 的话就得写。 3. 端口占用 Web server failed to start. Port 8080 was already in use.先查是谁: netstat -ano | findstr 8080 # Windows lsof -i:8080 # Linux/Mac杀掉: taskkill /PID 12345 /F或者改端口: server: port: 80814. artifact ${revision} not found 碰到这种: Could not find artifact net.maku:maku-boot:pom:${revision} in public问题不在 Maven 仓库、不在网络。是这个项目用了 Maven 的 CI Friendly Versions 特性——pom.xml 里的版本号写成 ${revision}: <version>${revision}</version>需要在根 pom.xml 里定义: <properties> <revision>4.11.0</revision> </properties>或者启动时传: mvn spring-boot:run -Drevision=4.11.0推荐:用 flatten-maven-plugin 生成扁平化的 pom: <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>flatten-maven-plugin</artifactId> <version>1.6.0</version> <configuration> <flattenMode>resolveCiFriendliesOnly</flattenMode> </configuration> <executions> <execution> <id>flatten</id> <phase>process-resources</phase> <goals><goal>flatten</goal></goals> </execution> <execution> <id>flatten.clean</id> <phase>clean</phase> <goals><goal>clean</goal></goals> </execution> </executions> </plugin>之后 mvn install 会先把 ${revision} 替换成真实版本再安装到本地库,其它模块拉的时候就不会崩了。 5. 依赖下载慢 Maven 默认仓库在国外,换阿里云或华为云: ~/.m2/settings.xml: <settings> <mirrors> <mirror> <id>aliyun</id> <mirrorOf>central</mirrorOf> <url>https://maven.aliyun.com/repository/central</url> </mirror> </mirrors> </settings>一次配好,永久生效。 6. 开发热重载 Spring Boot 自带 devtools: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency>IDEA 里要开 "Build project automatically" + "Registry: compiler.automake.allow.when.app.running",代码保存自动 recompile + restart。 一句话总结 开发用 mvn spring-boot:run -DskipTests、生产用 mvn clean package + java -jar。${revision} 报错是 CI Friendly Versions,加 flatten 插件解决。