Showing Posts From

Spring

Spring RestTemplate 发 HTTP 请求:常用姿势速查

Spring 里发 HTTP 请求最常见还是 RestTemplate。虽然官方已经推 WebClient,但存量项目里 RestTemplate 到处都是——写起来简单、同步阻塞、跟传统 Servlet 模型贴合。 注册 Bean @Configuration public class RestConfig { @Bean public RestTemplate restTemplate() { return new RestTemplate(); } }注入用: @Autowired private RestTemplate restTemplate;或者构造器注入(推荐): private final RestTemplate rt; public UserService(RestTemplate rt) { this.rt = rt; }GET 最简单: String json = restTemplate.getForObject( "https://api.example.com/users/1", String.class);带 URL 参数(占位符按顺序填): String url = "https://api.example.com/users/{id}?type={type}"; String json = restTemplate.getForObject(url, String.class, 1, "vip");返回对象: @Data public class User { private Integer id; private String name; }User u = restTemplate.getForObject( "https://api.example.com/users/1", User.class);Jackson 会自动反序列化——User 里字段名和 JSON 对得上就行。 POST 发 JSON: Map<String, Object> body = new HashMap<>(); body.put("username", "admin"); body.put("password", "12345");String result = restTemplate.postForObject( "https://api.example.com/login", body, String.class);RestTemplate 会把 Map 序列化成 JSON 并设 Content-Type: application/json(前提是有 Jackson)。 表单: MultiValueMap<String, String> form = new LinkedMultiValueMap<>(); form.add("username", "admin"); form.add("password", "12345");HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);HttpEntity<MultiValueMap<String, String>> req = new HttpEntity<>(form, headers); String result = restTemplate.postForObject(url, req, String.class);自定义 Header 带 Token 的接口: HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.set("Authorization", "Bearer " + token);Map<String, Object> body = new HashMap<>(); body.put("name", "test");HttpEntity<Map<String, Object>> entity = new HttpEntity<>(body, headers);ResponseEntity<String> resp = restTemplate.exchange( "https://api.example.com/create", HttpMethod.POST, entity, String.class);if (resp.getStatusCode().is2xxSuccessful()) { System.out.println(resp.getBody()); }exchange 是最灵活的方法——可以自由选择 HTTP method、传 headers、拿完整的 ResponseEntity。 超时配置(生产必配) 默认没有超时。远端 hang 住就一直等: @Bean public RestTemplate restTemplate() { SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); factory.setConnectTimeout(5_000); // 连接超时 factory.setReadTimeout(10_000); // 读超时 return new RestTemplate(factory); }更强大的选择:Apache HttpClient 5: @Bean public RestTemplate restTemplate() { RequestConfig config = RequestConfig.custom() .setConnectTimeout(Timeout.ofSeconds(5)) .setResponseTimeout(Timeout.ofSeconds(10)) .build(); CloseableHttpClient http = HttpClients.custom() .setDefaultRequestConfig(config) .setMaxConnTotal(100) .setMaxConnPerRoute(20) .build(); HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(http); return new RestTemplate(factory); }带连接池、更好的性能、支持重试。 上传文件 FileSystemResource file = new FileSystemResource("/path/to/a.zip");MultiValueMap<String, Object> body = new LinkedMultiValueMap<>(); body.add("file", file); body.add("desc", "备份");HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.MULTIPART_FORM_DATA);HttpEntity<MultiValueMap<String, Object>> req = new HttpEntity<>(body, headers);String result = restTemplate.postForObject( "https://api.example.com/upload", req, String.class);忽略 SSL 自签证书内网服务: SSLContext ssl = SSLContexts.custom() .loadTrustMaterial((chain, authType) -> true) .build();CloseableHttpClient http = HttpClients.custom() .setConnectionManager( PoolingHttpClientConnectionManagerBuilder.create() .setSSLSocketFactory( SSLConnectionSocketFactoryBuilder.create() .setSslContext(ssl) .setHostnameVerifier(NoopHostnameVerifier.INSTANCE) .build()) .build()) .build();生产上少这么干,会漏中间人。 Spring Boot 3 之后怎么办 Spring Framework 6 起 RestTemplate 进入维护模式——不再加新功能,但不会立刻删。新项目推荐:RestClient(Spring 6.1+)— 同步、fluent API,最平滑的替代 WebClient — 响应式(reactive),跟 Reactor 生态整合RestClient 示例: RestClient client = RestClient.create();User u = client.get() .uri("https://api.example.com/users/{id}", 1) .retrieve() .body(User.class);比 RestTemplate 好看得多。老项目不用急着换,新代码优先用 RestClient。 一句话总结 RestTemplate:GET 用 getForObject、POST 用 postForObject 或 exchange、复杂场景走 exchange 拿 ResponseEntity。生产必配超时和连接池。新项目改用 RestClient。

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 常用命令 + 依赖解析失败排查

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 插件解决。

Spring多模块开发坑

问题描述 运行模块:sou-main 创建模块:sou-admin sou-main必须依赖sou-admin,否则项目运行的模块找不到新创建模块的类 解决方案 在sou-main中添加依赖: <!-- sou-main--> <dependency> <groupId>com.sou</groupId> <artifactId>sou-admin</artifactId> </dependency>在sou-parent中配置版本管理: <!-- sou-parent--> <dependencyManagement> <dependency> <groupId>com.sou</groupId> <artifactId>sou-admin</artifactId> <version>1.0</version> </dependency> </dependencyManagement>

Spring MCV配置文件方式事务回滚失败(Mysql8.0+)

问题描述 在Mysql8.0+环境下,使用Spring MVC配置文件方式配置事务时,事务回滚失败。 解决方案 在Mysql8.0需要注入SqlSessionTemplate: <!-- Mysql8.0事务配置回滚必须注入这个--> <bean class="org.mybatis.spring.SqlSessionTemplate" id="sessionTemplate"> <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/> <constructor-arg name="executorType" value="BATCH"/> </bean>关键点 必须注入SqlSessionTemplate,并设置executorType为BATCH模式。