【SpringMVC笔记】 - 3 - 获取请求数据

【SpringMVC笔记】 - 3 - 获取请求数据

假设请求:

url 复制代码
http://localhost:8080/springmvc/register?name=zhangsan&password=123&email=zhangsan@qq.com

核心问题:

  • SpringMVC中如何获取请求提交的数据?
  • SpringMVC中如何获取请求头信息?
  • SpringMVC中如何获取客户端提交的Cookie数据?

一、准备工作

1. 创建模块,添加依赖(pom.xml)

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.powernode.springmvc</groupId>
    <artifactId>springmvc-003</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <dependencies>
        <!--springmvc依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>6.1.4</version>
        </dependency>
        <!--logback依赖(日志)-->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.5.3</version>
        </dependency>
        <!--servlet依赖(Jakarta EE)-->
        <dependency>
            <groupId>jakarta.servlet</groupId>
            <artifactId>jakarta.servlet-api</artifactId>
            <version>6.0.0</version>
            <scope>provided</scope> <!--provided:服务器已提供,打包时不包含-->
        </dependency>
        <!--thymeleaf和spring6整合依赖(视图渲染)-->
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring6</artifactId>
            <version>3.1.2.RELEASE</version>
        </dependency>
    </dependencies>

    <properties>
        <maven.compiler.source>21</maven.compiler.source> <!--JDK版本-->
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <!--编码格式-->
    </properties>

</project>

2. 添加web支持(编写web.xml)

核心配置前端控制器(DispatcherServlet),指定SpringMVC配置文件路径,设置启动时初始化。

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
         version="6.0">

    <!--前端控制器:SpringMVC的核心,统一处理请求-->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--初始化参数:指定SpringMVC配置文件的路径和名称-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc.xml</param-value> <!--类路径下的springmvc.xml-->
        </init-param>
        <!--load-on-startup=1:服务器启动时初始化DispatcherServlet,提升第一次访问效率-->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern> <!--拦截所有请求(除了.jsp)-->
    </servlet-mapping>

</web-app>

3. 创建UserController(基础控制器)

用于跳转注册页面,后续扩展请求处理方法。

java 复制代码
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller // 标识为SpringMVC控制器,交给Spring容器管理
public class UserController {
    // 跳转注册页面:请求路径为"/",返回视图名为"register"(对应WEB-INF/templates/register.html)
    @RequestMapping("/")
    public String toRegisterPage(){
        return "register";
    }
}

4. 编写SpringMVC核心配置文件(springmvc.xml)

核心配置:组件扫描(扫描控制器)、视图解析器(Thymeleaf)。

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
      http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context 
      https://www.springframework.org/schema/context/spring-context.xsd">

    <!--组件扫描:扫描指定包下的注解(@Controller、@Service等)-->
    <context:component-scan base-package="com.zzz.springmvc.controller"/>

    <!--Thymeleaf视图解析器:负责将视图名解析为具体的HTML页面-->
    <bean id="thymeleafViewResolver" class="org.thymeleaf.spring6.view.ThymeleafViewResolver">
        <!--视图渲染时的编码字符集-->
        <property name="characterEncoding" value="UTF-8"/>
        <!--视图解析器优先级:值越小,优先级越高(若有多个视图解析器)-->
        <property name="order" value="1"/>
        <!--关联Thymeleaf模板引擎-->
        <property name="templateEngine">
            <bean class="org.thymeleaf.spring6.SpringTemplateEngine">
                <!--关联模板解析器:负责加载和解析HTML模板-->
                <property name="templateResolver">
                    <bean class="org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver">
                        <!--模板文件前缀:HTML文件所在路径-->
                        <property name="prefix" value="/WEB-INF/templates/"/>
                        <!--模板文件后缀:HTML文件扩展名-->
                        <property name="suffix" value=".html"/>
                        <!--模板类型:HTML-->
                        <property name="templateMode" value="HTML"/>
                        <!--模板读取时的编码字符集-->
                        <property name="characterEncoding" value="UTF-8"/>
                    </bean>
                </property>
            </bean>
        </property>
    </bean>

</beans>

5. 编写register.html(注册页面,视图层)

放在 /WEB-INF/templates/ 目录下(与springmvc.xml中模板前缀对应),后续会逐步完善表单。

html 复制代码
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"> <!--引入Thymeleaf命名空间-->
<head>
    <meta charset="UTF-8">
    <title>用户注册</title>
</head>
<body>
<h3>用户注册</h3>
<hr>
</body>
</html>

6. 部署测试

将模块部署到Tomcat服务器(建议Tomcat10+),启动服务器后访问http://localhost:8080/springmvc/,能正常显示注册页面即部署成功。

二、获取请求提交的数据(4种核心方式,含完整测试)

方式1:使用原生Servlet API获取(不推荐)

核心原理

SpringMVC会自动将当前请求对象(HttpServletRequest)传递给控制器方法的形参,通过该对象调用 getParameter()getParameterValues()等方法获取请求参数。

步骤1:完善register.html表单(添加提交控件)
html 复制代码
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>用户注册</title>
</head>
<body>
<h3>用户注册</h3>
<hr>
<!--Thymeleaf的action写法:@{/register} 对应控制器的请求路径-->
<form th:action="@{/register}" method="post">
    用户名:<input type="text" name="username"><br>
    密码:<input type="password" name="password"><br>
    性别:
        男 <input type="radio" name="sex" value="1">
        女 <input type="radio" name="sex" value="0">
        <br>
    爱好:
        抽烟 <input type="checkbox" name="hobby" value="smoke">
        喝酒 <input type="checkbox" name="hobby" value="drink">
        烫头 <input type="checkbox" name="hobby" value="perm">
        <br>
    简介:<textarea rows="10" cols="60" name="intro"></textarea><br>
    <input type="submit" value="注册">
</form>
</body>
</html>
步骤2:在UserController中添加请求处理方法
java 复制代码
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.stereotype.Controller;
import java.util.Arrays;

@Controller
public class UserController {
    @RequestMapping("/")
    public String toRegisterPage(){
        return "register";
    }

    // 处理注册请求:请求路径/register,请求方式post
    @PostMapping(value="/register")
    public String register(HttpServletRequest request){
        // 1. 获取单个请求参数(用户名、密码、性别、简介)
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        String sex = request.getParameter("sex");
        String intro = request.getParameter("intro");
        // 2. 获取多个同名参数(爱好,checkbox)
        String[] hobbies = request.getParameterValues("hobby");
        // 3. 控制台打印测试
        System.out.println(username + "," + password + "," + sex + "," + Arrays.toString(hobbies) + "," + intro);
        // 返回成功页面(需创建success.html,路径/WEB-INF/templates/success.html)
        return "success";
    }
}
步骤3:创建success.html(成功页面)
html 复制代码
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>注册成功</title>
</head>
<body>
<h1>注册成功</h1>
</body>
</html>
步骤4:测试结果
  1. 访问注册页面,填写表单并提交;
  2. 打开浏览器F12,查看Network,确认请求已提交所有参数;
  3. 查看IDEA控制台,能正常打印所有提交的参数(如:zhangsan,123,1,[smoke,drink],我是张三)。
注意事项(不推荐使用的原因)
  1. 控制器方法依赖Servlet原生API(HttpServletRequest),导致控制器无法单独测试,必须依赖WEB服务器;
  2. 违背SpringMVC"解耦"的核心思想,若使用原生API,无需依赖SpringMVC框架。

方式2:使用@RequestParam注解标注(推荐,灵活)

核心作用

将请求参数(表单name、URL参数)与控制器方法的形参进行强制映射,无需依赖Servlet API。

1. 基本使用(形参名与请求参数名不一致)
java 复制代码
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.stereotype.Controller;
import java.util.Arrays;

@Controller
public class UserController {
    // 省略toRegisterPage()方法...

    @PostMapping(value = "/register")
    public String register(
        // @RequestParam(value="请求参数名"):将请求参数映射到形参
        @RequestParam(value="username") String a, // 请求参数name=username → 形参a
        @RequestParam(value="password") String b, // 请求参数name=password → 形参b
        @RequestParam(value="sex") String c,     // 请求参数name=sex → 形参c
        @RequestParam(value="hobby") String[] d, // 请求参数name=hobby(多个)→ 形参数组d
        @RequestParam(name="intro") String e     // name属性与value属性作用完全一致
    ) {
        // 控制台打印测试
        System.out.println(a);
        System.out.println(b);
        System.out.println(c);
        System.out.println(Arrays.toString(d));
        System.out.println(e);
        return "success";
    }
}
测试注意

若@RequestParam的value(或name)与请求参数名不一致,会抛出400错误(参数缺失),例如:将value="username"改为value="uname",但表单中name仍为username,会报错:Required parameter 'uname' is not present

2. required属性(控制参数是否必需)

默认值:true(表示该请求参数必须提交,否则报错400);可设置为false(参数非必需,未提交时形参为null)。

java 复制代码
@PostMapping(value = "/register")
public String register(
    @RequestParam(value="username") String username,
    @RequestParam(value="password") String password,
    @RequestParam(value="age", required = false) String age // age非必需
) {
    System.out.println("用户名:" + username);
    System.out.println("密码:" + password);
    System.out.println("年龄:" + age); // 未提交age时,输出null
    return "success";
}
测试结果
  1. 不提交age参数:控制台输出"年龄:null",无报错;
  2. required默认true(不写required),不提交age:报错400,提示"Required parameter 'age' is not present"。
3. defaultValue属性(设置形参默认值)

作用:当请求参数未提交,或提交的参数值为空字符串("")时,形参使用默认值(优先级高于required=false)。

java 复制代码
@PostMapping(value = "/register")
public String register(
    @RequestParam(value="username") String username,
    @RequestParam(value="email", required = false, defaultValue = "default@163.com") String email
) {
    System.out.println("用户名:" + username);
    System.out.println("邮箱:" + email);
    return "success";
}
测试场景及结果
  • 场景1:未提交email参数 → 邮箱:default@163.com;
  • 场景2:提交email参数,但值为空(表单中输入框留空) → 邮箱:default@163.com;
  • 场景3:提交email参数(如zhangsan@powernode.com) → 邮箱:zhangsan@powernode.com。

方式3:省略@RequestParam注解(简化,推荐)

核心前提

控制器方法的形参名 ,必须与请求参数的name完全一致(大小写敏感)。

补充:Spring6+版本,需在pom.xml中配置maven-compiler-plugin的-parameters参数(保留形参名,否则编译后形参名会变为arg0、arg1...);Spring5及以下版本无需配置。

步骤1:配置pom.xml(Spring6+必需)
xml 复制代码
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.12.1</version>
            <configuration>
                <source>17</source>
                <target>17</target>
                <compilerArgs>
                    <arg>-parameters</arg> <!--保留形参名,Spring6+必需-->
                </compilerArgs>
            </configuration>
        </plugin>
    </plugins>
</build>
步骤2:控制器方法编写(省略@RequestParam)
java 复制代码
@PostMapping(value="/register")
public String register(String username, String password, String sex, String[] hobby, String intro){
    // 形参名(username、password等)与表单name完全一致
    System.out.println(username + "," + password + "," + sex + "," + Arrays.toString(hobby) + "," + intro);
    return "success";
}
测试结果

填写表单提交后,控制台正常打印所有参数,与方式2效果一致。

补充细节
  1. 形参名与请求参数名不一致 → 形参值为null(无报错,除非参数必需);
  2. 多个同名参数(如hobby),可使用String数组接收(String[] hobby),也可使用String接收(此时多个值用逗号拼接,如"smoke,drink"):
java 复制代码
// String接收多个同名参数
@PostMapping(value="/register")
public String register(String username, String password, String sex, String hobby, String intro){
    System.out.println(hobby); // 输出:smoke,drink(选中两个爱好时)
    return "success";
}

方式4:使用POJO/JavaBean接收(推荐,参数较多时)

核心前提

JavaBean的属性名 ,必须与请求参数的name完全一致(底层通过反射机制赋值)。

步骤1:创建User类(JavaBean)
java 复制代码
import java.util.Arrays;

// 必须提供无参构造器(Spring反射赋值必需),有参构造器可选
public class User {
    // 属性名与请求参数name完全一致(username、password、sex、hobby、intro)
    private Long id;
    private String username;
    private String password;
    private String sex;
    private String[] hobby;
    private String intro;

    // 无参构造器(必需)
    public User() {
    }

    // 有参构造器(可选)
    public User(Long id, String username, String password, String sex, String[] hobby, String intro) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.sex = sex;
        this.hobby = hobby;
        this.intro = intro;
    }

    // 所有属性的getter和setter方法(必需,Spring通过setter赋值)
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String[] getHobby() {
        return hobby;
    }

    public void setHobby(String[] hobby) {
        this.hobby = hobby;
    }

    public String getIntro() {
        return intro;
    }

    public void setIntro(String intro) {
        this.intro = intro;
    }

    // toString方法(用于控制台打印测试)
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", sex='" + sex + '\'' +
                ", hobby=" + Arrays.toString(hobby) +
                ", intro='" + intro + '\'' +
                '}';
    }
}
步骤2:控制器方法编写(形参为User对象)
java 复制代码
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.stereotype.Controller;

@Controller
public class UserController {
    // 省略toRegisterPage()方法...

    // 形参为User对象,Spring自动将请求参数赋值到User的对应属性
    @PostMapping("/register")
    public String register(User user){
        System.out.println(user); // 调用User的toString()方法打印
        return "success";
    }
}
测试结果

填写表单提交后,控制台打印User对象信息(如:User{id=null, username='zhangsan', password='123', sex='1', hobby=[smoke,drink], intro='我是张三'}),说明赋值成功。

关键细节(底层原理)

Spring赋值的核心是setter方法,而非属性名:

  • 场景1:属性名与请求参数名不一致,但setter方法名对应(如属性名uname,setter方法setUsername()) → 赋值成功;
  • 场景2:属性名与请求参数名一致,但setter方法名错误(如属性名username,setter方法setUname()) → 赋值失败(属性值为null)。

测试验证(修改User类,验证setter的作用):

java 复制代码
public class User {
    // 属性名改为uname(与请求参数username不一致)
    private String uname;

    // setter方法名仍为setUsername(与请求参数username一致)
    public void setUsername(String username) {
        this.uname = username;
    }

    // getter方法名改为getUsername
    public String getUsername() {
        return uname;
    }

    // 其他属性和方法不变...
}

测试结果:username参数能正常赋值到uname属性(控制台打印uname的值为提交的用户名)。

三、获取请求头信息(@RequestHeader注解)

核心作用

将请求头信息(如Referer、User-Agent)与控制器方法的形参映射,用法与@RequestParam完全一致,包含3个属性:value(请求头名)、required(是否必需)、defaultValue(默认值)。

测试代码

java 复制代码
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.stereotype.Controller;

@Controller
public class UserController {
    // 省略toRegisterPage()方法...

    @PostMapping("/register")
    public String register(
        User user,
        // 获取请求头Referer(当前请求的来源页面)
        @RequestHeader(value="Referer", required = false, defaultValue = "") String referer
 ) {
        System.out.println("用户信息:" + user);
        System.out.println("请求来源:" + referer); // 输出:http://localhost:8080/springmvc/
        return "success";
    }
}

测试结果

提交表单后,控制台打印请求头Referer的值(即注册页面的URL),若直接访问/register(无来源),则打印默认值(空字符串)。

四、获取Cookie数据(@CookieValue注解)

1. 核心作用

将客户端提交的 Cookie 数据 (通过请求头 Cookie 字段携带)与控制器方法的形参进行映射。

用法与 @RequestParam@RequestHeader 保持一致,同样支持三个属性。

2. 可使用属性

  • value / name:指定 Cookie 的 key
  • required:是否必须携带该 Cookie,默认 true
  • defaultValue:Cookie 不存在时使用的默认值

在注册页面中通过 JS 设置 Cookie,并发送到服务器:

html 复制代码
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>用户注册</title>
</head>
<body>
<h3>用户注册</h3>
<hr>

<form th:action="@{/register}" method="post">
    用户名:<input type="text" name="username"><br>
    密码:<input type="password" name="password"><br>
    性别:
    男 <input type="radio" name="sex" value="1">
    女 <input type="radio" name="sex" value="0">
    <br>
    爱好:
    抽烟 <input type="checkbox" name="hobby" value="smoke">
    喝酒 <input type="checkbox" name="hobby" value="drink">
    烫头 <input type="checkbox" name="hobby" value="perm">
    <br>
    简介:<textarea rows="10" cols="60" name="intro"></textarea><br>
    <input type="submit" value="注册">
</form>

<!-- JS 设置并发送 Cookie -->
<script type="text/javascript">
    function sendCookie(){
        // 设置Cookie:key=id,value=123456789,设置有效期和路径
        document.cookie = "id=123456789; expires=Thu, 18 Dec 2025 12:00:00 UTC; path=/";
        // 跳转到注册接口,携带Cookie
        document.location = "/springmvc/register";
    }
</script>
<br>
<button onclick="sendCookie()">向服务器端发送 Cookie</button>

</body>
</html>

UserController 中编写方法,使用 @CookieValue 接收 Cookie:

java 复制代码
@GetMapping("/register")
public String register(User user,
        @RequestHeader(value="Referer", required = false, defaultValue = "") String referer,
        @CookieValue(value="id", required = false, defaultValue = "2222222222") String id) {

    System.out.println("User:" + user);
    System.out.println("Referer:" + referer);
    System.out.println("Cookie中的id:" + id);

    return "success";
}

5. 测试结果说明

  • 点击【向服务器端发送 Cookie】按钮后,JS 设置 Cookie 并跳转
  • 后端成功获取到 id=123456789
  • 若客户端未携带该 Cookie,则使用 defaultValue="2222222222"
  • 若未设置默认值且 required=true,缺少 Cookie 会报 400 错误

六、请求中文乱码问题解决

1. GET 请求乱码

  • 原因 :GET 请求参数拼接在 URI 中,低版本 Tomcat 默认使用 ISO-8859-1 编码

  • 高版本 Tomcat(9/10):

  • URI 编码默认已配置为 UTF-8无需处理

  • 低版本 Tomcat(8 及以下):

    修改 server.xml<Connector> 标签,添加:

    xml 复制代码
    URIEncoding="UTF-8"

2. POST 请求乱码

(1)为什么 Controller 中设置编码无效
java 复制代码
request.setCharacterEncoding("UTF-8");
  • 在 Controller 方法中执行时,SpringMVC 已经通过 request.getParameter() 获取过参数
  • 编码设置晚于参数获取,因此无效
(2)解决方案:使用 Spring 提供的字符编码过滤器

SpringMVC 内置 CharacterEncodingFilter,在 DispatcherServlet 之前执行,统一设置编码。

(3)在 web.xml 中配置过滤器
xml 复制代码
<!-- 字符编码过滤器 -->
<filter>
    <filter-name>characterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
        <param-name>forceRequestEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
    <init-param>
        <param-name>forceResponseEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>characterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
(4)关键说明
  • encoding=UTF-8:指定使用 UTF-8 编码
  • forceRequestEncoding=true强制覆盖请求编码,即使已有编码也会被替换
  • forceResponseEncoding=true:同时强制设置响应编码
  • 过滤器路径 /*:对所有请求生效
  • 配置后,POST 中文乱码彻底解决
相关推荐
Predestination王瀞潞2 小时前
彻底解决IDEA Console控制台乱码(Python可供参考第一部分)
java·ide·intellij-idea
Seven972 小时前
【从0到1构建一个ClaudeAgent】并发-后台任务
java
Java面试题总结2 小时前
Java常见面试题(160道)
java·开发语言
xmaaaa2 小时前
订单系统到底该怎么建模(四):微服务拆分与聚合边界的终极实践
java·ddd领域驱动
浪客川2 小时前
【百例RUST - 007】结构体
java·前端·rust
Rsun045512 小时前
7、Java 装饰器模式从入门到实战
java·开发语言·装饰器模式
黎雁·泠崖2 小时前
二叉树基础精讲(上):树形结构 · 二叉树概念 · 性质 · 遍历 · 基础操作全解析
java·数据结构·算法
biwenjun9992 小时前
chatBI构建思路拆解(重点是元数据增强)
java·数据库·人工智能