SpringBoot 请求同一个方法兼容form格式与requestbody json格式

探讨SpringBoot中解决同一接口兼容form数据提交与json格式请求的问题,介绍自定义请求参数解析器的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

最近在重构一个旧服务,遇见这么一个问题,旧服务是PHP服务,PHP的controller方法可以同时兼容form的请求格式与requestbody json的请求格式,但是在SpringBoot中,是不可以的,只可以支持单一模式,使用form提交就不可以使用@RequestBody注解去接收,但是这个问题必须需要解决。

问题现象

在这里演示一下所说的问题:

首先是如果使用form格式的请求:

在这里插入图片描述

我们使用@RequestBody去接收,

@PostMapping("/test")
public String test(@RequestBody Map<String, String> params) {
    System.out.println(JSON.toJSONString(params));
    return "success";
}

就会抛出:

org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported
	at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(AbstractMessageConverterMethodArgumentResolver.java:235) ~[spring-webmvc-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:149) ~[spring-webmvc-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:127) ~[spring-webmvc-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121) ~[spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:161) ~[spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:128) ~[spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:114) ~[spring-webmvc-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827) ~[spring-webmvc-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738) ~[spring-webmvc-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) ~[spring-webmvc-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963) [spring-webmvc-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897) [spring-webmvc-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970) [spring-webmvc-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872) [spring-webmvc-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:648) [tomcat-embed-core-8.5.5.jar:8.5.5]
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846) [spring-webmvc-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:729) [tomcat-embed-core-8.5.5.jar:8.5.5]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:230) [tomcat-embed-core-8.5.5.jar:8.5.5]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) [tomcat-embed-core-8.5.5.jar:8.5.5]
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) [tomcat-embed-websocket-8.5.5.jar:8.5.5]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) [tomcat-embed-core-8.5.5.jar:8.5.5]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) [tomcat-embed-core-8.5.5.jar:8.5.5]
	at org.springframework.web.servlet.resource.ResourceUrlEncodingFilter.doFilterInternal(ResourceUrlEncodingFilter.java:53) [spring-webmvc-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) [tomcat-embed-core-8.5.5.jar:8.5.5]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) [tomcat-embed-core-8.5.5.jar:8.5.5]
	at org.springframework.boot.web.filter.ApplicationContextHeaderFilter.doFilterInternal(ApplicationContextHeaderFilter.java:55) [spring-boot-1.4.1.RELEASE.jar:1.4.1.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) [tomcat-embed-core-8.5.5.jar:8.5.5]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) [tomcat-embed-core-8.5.5.jar:8.5.5]
	at org.springframework.boot.actuate.trace.WebRequestTraceFilter.doFilterInternal(WebRequestTraceFilter.java:105) [spring-boot-actuator-1.4.1.RELEASE.jar:1.4.1.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) [tomcat-embed-core-8.5.5.jar:8.5.5]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) [tomcat-embed-core-8.5.5.jar:8.5.5]
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) [spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) [tomcat-embed-core-8.5.5.jar:8.5.5]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) [tomcat-embed-core-8.5.5.jar:8.5.5]
	at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:89) [spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) [tomcat-embed-core-8.5.5.jar:8.5.5]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) [tomcat-embed-core-8.5.5.jar:8.5.5]
	at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77) [spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) [tomcat-embed-core-8.5.5.jar:8.5.5]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) [tomcat-embed-core-8.5.5.jar:8.5.5]
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197) [spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) [tomcat-embed-core-8.5.5.jar:8.5.5]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) [tomcat-embed-core-8.5.5.jar:8.5.5]

可以从异常信息中看出,原因是不支持的格式,将请求方式换成application/json,即可顺利接受,但是希望兼容form的请求格式,使用@RequestParam注解,有无法兼容application/json格式:
在这里插入图片描述

@PostMapping("/test")
public String test(@RequestParam(value = "param1", defaultValue = "", required = false) String param1) {
    System.out.println(JSON.toJSONString(param1));
    return "success";
}

param1参数无法获取到值,这个让我很头疼,SpringBoot无法兼容两种方式的请求格式。

原因

Spring 3.X系列增加了新注解 @ResponseBody,@RequestBody。
@RequestBody 将HTTP请求正文转换为适合的HttpMessageConverter对象。
Spring MVC 使用HttpMessageConverter将请求对象转化为我们希望的格式,当我们在方法中加入@RequestBody注解,Spring MVC固定使用RequestMappingHandlerAdapter来解析,其中RequestMappingHandlerAdapter支持的HttpMessageConverter有四种:

ByteArrayHttpMessageConverter:转化 byte arrays.
StringHttpMessageConverter:转化 Strings.
FormHttpMessageConverter:转化 form data to/from a MultiValueMap.
SourceHttpMessageConverter:转化 to/from a javax.xml.transform.Source.

因此,对于两种请求格式的解析,是由分别不同的两种解析器去执行的,对于上面的请求,他们是不可以在同一个方法上并行执行解析的,即既有@RequestParam,也有@RequestBody

// 这样是不支持的
@PostMapping("/test")
public String test(@RequestParam(value = "param1", defaultValue = "", required = false) String param1,
							@RequestBody Map<String, Object> params) {
    System.out.println(param1);
    System.out.println(JSON.toJSONString(params));
    return "success";
}

那么怎么办呢?

解决方案

查了很多资料,也没有发现一个Spring提供的注解可以解决这个问题,有的人提出可以自己实现一个Converter去处理这种请求,但是自己实现一个Converter未免有点复杂,因此,我想起了一个类,javax.servlet.http.HttpServletRequest,通过原生的HttpServletRequest去捕捉参数,自行进行解析,废话不多说,上代码:

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.util.HashMap;
import java.util.Map;

/**
 * HttpRequest格式转换工具类
 * Created by xuanguangyao on 2018/11/6.
 */
public class HttpRequestUtil {

    /**
     * 通用请求格式转换
     * @param httpServletRequest
     * @return
     */
    public static Map<String, String> commonHttpRequestParamConvert(HttpServletRequest httpServletRequest) {
        Map<String, String> params = new HashMap<>();
        try {
            Map<String, String[]> requestParams = httpServletRequest.getParameterMap();
            if (requestParams != null && !requestParams.isEmpty()) {
                requestParams.forEach((key, value) -> params.put(key, value[0]));
            } else {
                StringBuilder paramSb = new StringBuilder();
                try {
                    String str = "";
                    BufferedReader br = httpServletRequest.getReader();
                    while((str = br.readLine()) != null){
                        paramSb.append(str);
                    }
                } catch (Exception e) {
                    System.out.println("httpServletRequest get requestbody error, cause : " + e);
                }
                if (paramSb.length() > 0) {
                    JSONObject paramJsonObject = JSON.parseObject(paramSb.toString());
                    if (paramJsonObject != null && !paramJsonObject.isEmpty()) {
                        paramJsonObject.forEach((key, value) -> params.put(key, String.valueOf(value)));
                    }
                }
            }
        } catch (Exception e) {
            System.out.println("commonHttpRequestParamConvert error, cause : " + e);
        }
        return params;
    }
}

这里我使用了FastJSON作为JSON格式的转换工具,你也可以自行使用其他的进行转换,代码很简单,分析HttpServletRequest 这个类,可以发现,如果是form格式的请求,其请求参数是可以通过getParameterMap方法进行获取,但是application/json的请求方式,可能就需要自行转换拼接一下,最终转换成一个Map形式,当然你也可以转换成JSONObject格式,对象DTO格式等等,你开心就好。

<think>好的,我现在需要处理用户关于Spring Boot实现用户注册登录功能的请求。首先,用户希望得到示例代码,所以我应该提供清晰的步骤代码片段。我需要先回顾相关的知识,确保代码的准确性最佳实践。 首先,用户提到了引用[1],里面提到了用户登录注册功能的实现步骤,但具体代码可能没有详细展示。所以,我需要构建一个完整的示例,涵盖实体类、Repository、Service、Controller以及安全配置部分。 接下来,我需要确定使用哪些Spring Boot的模块。用户认证授权通常使用Spring Security,所以必须包含Security的依赖。同时,数据存储需要Spring Data JPA数据库,这里可以选择H2内存数据库方便示例,或者MySQL,但为了简化,先用H2。 第一步是创建用户实体类。这里要注意使用JPA注解,如@Entity、@Id、@GeneratedValue,同时用户名密码字段需要合理设置,比如唯一约束。密码存储必须加密,不能明文保存,所以后面在Service层需要处理密码加密。 然后是创建UserRepository接口,继承JpaRepository,这样可以利用Spring Data的CRUD方法。需要添加根据用户名查找用户的方法,这会在登录验证时用到。 接下来是UserService,负责注册登录逻辑。注册时需要检查用户名是否已存在,如果存在则抛出异常。密码需要使用BCryptPasswordEncoder进行加密存储。登录功能可能由Spring Security处理,但也可以自定义,不过更常见的是通过Security配置来实现认证流程。 然后是Controller层,处理HTTP请求。注册登录的端点,比如/auth/register/auth/login,需要处理POST请求。注册时接收用户信息,调用Service进行保存;登录的话,如果是使用Security的表单登录,可能不需要额外处理,但如果是API登录,可能需要生成JWT令牌,这里需要明确。根据用户需求,可能更倾向于传统的表单登录,或者RESTful API使用JWT。用户的问题中没有特别说明,所以可能需要两种方式,但考虑到简洁性,可能先用Security的表单登录,或者提供一个简单的示例。 另外,需要配置Spring Security,定义哪些端点需要认证,哪些开放。密码编码器必须配置为BCrypt。可能需要自定义UserDetailsService来从数据库中加载用户。 在代码示例中,需要注意避免敏感信息泄露,比如密码加密,不能明文存储。同时,异常处理要到位,比如用户名已存在时返回错误信息。 现在,检查是否有遗漏的部分。例如,前端页面虽然用户可能不需要,但可以简要提及使用Thymeleaf或简单表单。不过用户主要关注后端代码,所以重点在后端。 最后,生成相关问题,需要考虑用户可能的后续疑问,比如密码加密方法、Session管理、第三方登录集成等。 可能遇到的疑问点:是否需要用数据库迁移工具,比如Flyway?示例中使用H2是否合适?是否需要邮件验证?但用户的问题主要是注册登录的基础实现,所以保持简单。 现在,编写代码时,确保语法正确,比如Spring Security的配置类是否正确扩展WebSecurityConfigurerAdapter(虽然在新版本中可能弃用,改用组件配置,但为了兼容性,可能需要调整)。例如,最新的Spring Security 6.x中使用Lambda DSL配置,但用户可能更熟悉传统方式,所以需要选择广泛接受的方式。 总结步骤:实体类 → Repository → Service → Security配置 → Controller → 测试。确保每个部分代码正确,并添加必要的注释。</think>### Spring Boot用户注册登录实现步骤 #### 1. 添加依赖(pom.xml) ```xml <dependencies> <!-- Spring Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring Security --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- Spring Data JPA --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!-- H2 Database --> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> </dependencies> ``` #### 2. 用户实体类 ```java @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(unique = true) private String username; private String password; // 省略getter/setter } ``` #### 3. 用户Repository ```java public interface UserRepository extends JpaRepository<User, Long> { User findByUsername(String username); } ``` #### 4. 用户服务层 ```java @Service public class UserService { @Autowired private UserRepository userRepository; @Autowired private BCryptPasswordEncoder passwordEncoder; public void register(User user) { if (userRepository.findByUsername(user.getUsername()) != null) { throw new RuntimeException("用户名已存在"); } user.setPassword(passwordEncoder.encode(user.getPassword())); userRepository.save(user); } } ``` #### 5. 安全配置 ```java @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeRequests(auth -> auth .antMatchers("/auth/register").permitAll() .anyRequest().authenticated() ) .formLogin(form -> form .loginPage("/login") .permitAll() ) .logout(logout -> logout .permitAll() ); return http.build(); } @Bean public BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } } ``` #### 6. 控制器 ```java @RestController @RequestMapping("/auth") public class AuthController { @Autowired private UserService userService; @PostMapping("/register") public ResponseEntity<?> register(@RequestBody User user) { try { userService.register(user); return ResponseEntity.ok("注册成功"); } catch (RuntimeException e) { return ResponseEntity.badRequest().body(e.getMessage()); } } } ``` #### 7. 运行配置(application.properties) ```properties spring.h2.console.enabled=true spring.datasource.url=jdbc:h2:mem:testdb spring.jpa.hibernate.ddl-auto=update ``` ### 功能验证 1. 访问 `http://localhost:8080/h2-console` 查看数据库 2. 使用POST请求测试注册: ```http POST /auth/register Content-Type: application/json { "username": "test", "password": "123456" } ``` ### 关键点说明 1. 密码使用$bcrypt$算法加密存储,保障安全性[^1] 2. 用户名校验通过`@Column(unique = true)`确保唯一性 3. 安全配置中开放了注册端点,其他请求需要认证
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值