目录

一、模型数据放入 request 域

        1.如何将数据放入 request 域?

                1.1 默认方式(default)

                1.2 默认方式放入的局限性

                1.3 手动放入的方式

        2.实例:

                2.0 需求

                2.1 默认方式 演示

                2.2 HttpServletRequest 方式 演示

                2.3 Map 方式 演示

                2.4 ModelAndView 方式 演示

二、模型数据放入 session 域

        1.回顾 Session: 

        2.实例:

        3.@ModelAttribute 注解的使用:

                3.1 @ModelAttribute 基本介绍

                3.2 @ModelAttribute 和 Spring AOP 前置通知的区别?

                3.3 @ModelAttribute 应用实例

Δ总结


一、模型数据放入 request 域

        1.如何将数据放入 request 域?

                1.1 默认方式(default)

        我们在上一篇博文 “SpringMVC 请求数据绑定与参数映射” 中提到 —— “在 SpringMVC 中,有一个非常强大的特性,那就是 POJO 自动绑定,也就是说,我们只需在 Handler 方法的形参上直接定义一个 JavaBean(例如 Buyer 对象),当表单提交时,SpringMVC 底层就会自动实例化这个类,并将请求参数填充进去,自动完成数据的封装,并且还支持级联属性注入”。

        而且我们也了解了,SpringMVC 实现这种自动封装,底层靠的是 反射调用、getter和setter调用、以及类型转换。

        但是其实up 还是少说了关键的一点:SpringMVC 在自动完成数据封装的同时,还会自动把封装好的数据放入 request 域中

                1.2 默认方式放入的局限性

        虽然 SpringMVC 的默认方式(自动封装并放入 request 域)极大地方便了表单数据的处理,但在实际开发中,它的局限性也非常明显:

        首先是 数据来源单一,默认方式仅能处理从前端传来的数据。如果我们需要将从数据库查询到的结果、系统的计算数据或某些业务状态码传递给页面,默认方式就无能为力了。

        第二个局限性是 控制粒度不够:默认方式会自动以类名首字母小写作为 key 存入域对象,有时我们希望自定义更加直观的 key。

        除了默认方式外,还要三种手动放入的方式,分别是HttpServletRequest、请求方法的形参Map、ModelAndVIew对象。

                1.3 手动放入的方式

(1) 使用 HttpServletRequest 对象

        其实就是原生的 Servlet 那套,最 “原始” 但也最直观。由于 SpringMVC 底层依然是基于 Servlet 运行的,所以我们可以在 Handler 方法的形参中直接声明 HttpServletRequest

  • 操作:直接通过调用 request.setAttribute("key", value) 存入数据。
  • 特点:这种方式虽然简单,但代码会与 Servlet API 耦合,在 SpringMVC 的开发规范中通常不作为首选推荐,但在处理某些老旧项目或特定底层逻辑时效果反而挺不错。

(2) 使用 Map / Model / ModelMap 形式

        这是 SpringMVC 官方推荐的一种方式,我们只需在 Handler 方法的形参上定义一个 java.util.Maporg.springframework.ui.Modelorg.springframework.ui.ModelMap。这种方式的代码完全脱离了 Servlet API,更加简洁,且非常利于做单元测试

  • 原理:SpringMVC 在调用方法时,会自动创建一个隐含的模型对象(通常是 BindingAwareModelMap)。我们向这个 Map 中存入的任何数据,SpringMVC 都会在方法执行完毕后进行扫描,并自动帮我们把 Map 中的数据以k-v的形式放入到 request 域中。

(3) 使用 ModelAndView 对象

        ModelAndView,正如其名,既包含了模型数据(Model),也包含了视图信息(View)

  • 操作:在方法内部手动创建一个 ModelAndView 实例,通过 addObject("key", value) 放入数据,并通过 setViewName("视图名") 指定跳转路径,最后将该对象返回。
  • 底层: 实际上,我们在之前的方式中直接返回一个 String 类型的视图名的操作,SpringMVC 底层最终也会将其封装成一个 ModelAndView 对象。因为当我们return String 时, Spring 会认为这个字符串是 View 信息,并自动结合隐含的 Model 数据,组装成一个临时的 ModelAndView 进行后续渲染。因此,直接使用 ModelAndView 只是将这个隐式的组装过程显式化了,让我们拥有了对数据和跳转最完全的控制权。
  • 联系:这里放一张 SpringMVC 的执行流程图,大家可以从 中央控制器处理器适配器 部分看到我们的“ModelAndView对象”。 

        2.实例:

                2.0 需求

                现在有一个简单的 JSP 页面 model_data.jsp,用于提交表单数据;有一个 PhoneHandler 会接收提交的数据,然后进行处理,接着把处理后的数据放入 request 域中,然后在另一个页面 model_OK.jsp 中展示数据。
                需要用到的 JavaBean类 有Phone类 和 Buyer类,这里就不再写一遍了,直接用我们上一篇中写过的就行。链接如下:

SpringMVC 请求数据绑定与参数映射 详解-CSDN博客https://blog.csdn.net/TYRA9/article/details/160678427?spm=1001.2014.3001.5501

                2.1 默认方式 演示

                在 PhoneHandler 类中新增一个 defaultRequest 方法,用于测试默认方式放入 request 域。
                PhoneHandler类代码如下:

package com.cyan.web.requestparam;

import com.cyan.web.pojo.Buyer;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

/**
 * @author : Cyan_RA9
 * @version : 23.0
 */
@RequestMapping(value = "/phone")
@Controller
public class PhoneHandler {

    @GetMapping(value = "/search")
    public String searchPhone(@RequestParam(value = "id", required = false) Integer phoneId) {
        /*
            1) @RequestParam 注解 表示会接收提交的参数;
            2) value = "id", 表示提交的参数名是"id"
            3) required = false, 表示这个参数可以不出现在请求的URL中. 默认是true
         */

        System.out.println("查询 phoneId = " + phoneId + " 的手机~");

        return "success";
    }

    @GetMapping(value = "/searchEX")
    public String searchHeaders(@RequestHeader("Host") String host,
                           @RequestHeader("Connection") String connection,
                           @RequestHeader(value = "Content-Type", required = false) String contentType) {

        System.out.println("Host = " + host);
        System.out.println("Connection = " + connection);
        System.out.println("Content-Type = " + contentType);

        return "success";
    }

    /**
     * 表单提交数据 -> 自动封装为POJO类
     * 1) 只要在方法的形参直接声明对应的类型, SpringMVC 底层会对传入的数据进行自动封装
     *    但是要求传入参数的参数名 必须和 对应对象的字段名保持一致.
     * 2) 匹配失败的字段自动为 null
     */
    @PostMapping(value = "/findBuyer")
    public String findPhoneBuyer(Buyer buyer) {
        System.out.println("买手机的人是:" + buyer);

        return "success";
    }

    //=======================================================
    /**
        验证默认方式 放入 request 域
     */
    @PostMapping(value = "/default")
    public String defaultRequest(Buyer buyer) {
        return "model_OK";
    }
}

                model_data.jsp 代码如下:

<%--
    User : Cyan_RA9
    Version : 24.0
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>模型数据处理</title>
</head>
<body>
    <form action="phone/default" method="post">
        <table style="border: 2px cornflowerblue solid">
            <tr>
                <td>买主id:</td>
                <td><input type="text" name="id"></td>
            </tr>
            <tr>
                <td>买主name:</td>
                <td><input type="text" name="name"></td>
            </tr>
            <tr>
                <td>手机id:</td>
                <td><input type="text" name="phone.id"></td>
            </tr>
            <tr>
                <td>手机name:</td>
                <td><input type="text" name="phone.name"></td>
            </tr>
            <tr>
                <td><input type="submit" value="SUBMIT"/></td>
                <td><input type="reset" value="RESET"/></td>
            </tr>
        </table>
    </form>
</body>
</html>

                model_OK.jsp 代码如下:

<%--
    User : Cyan_RA9
    Version : 24.0
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>原神牛逼</title>
</head>
<body>
    <h2>Get data from request field</h2>
    <hr>

    <table style="border: 2px hotpink solid">
        <tr>
            <th colspan="2">Acquired Data from requestScope</th>
        </tr>
        <tr>
            <td>买主id:</td>
            <td>${requestScope.buyer.id}</td>
        </tr>
        <tr>
            <td>买主name:</td>
            <td>${requestScope.buyer.name}</td>
        </tr>
        <tr>
            <td>手机id:</td>
            <td>${requestScope.buyer.phone.id}</td>
        </tr>
        <tr>
            <td>手机name:</td>
            <td>${requestScope.buyer.phone.name}</td>
        </tr>
    </table>
</body>
</html>

                运行效果如下 GIF 演示:

                2.2 HttpServletRequest 方式 演示

                其实就是 原生Servlet 放入request 域的方式。

                在 PhoneHandler 类中新增一个 originalRequest 方法,用于测试 HttpServletRequest 方式放入 request 域。
                PhoneHandler类代码如下:

package com.cyan.web.requestparam;

import com.cyan.web.pojo.Buyer;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;

/**
 * @author : Cyan_RA9
 * @version : 23.0
 */
@RequestMapping(value = "/phone")
@Controller
public class PhoneHandler {

    @GetMapping(value = "/search")
    public String searchPhone(@RequestParam(value = "id", required = false) Integer phoneId) {
        /*
            1) @RequestParam 注解 表示会接收提交的参数;
            2) value = "id", 表示提交的参数名是"id"
            3) required = false, 表示这个参数可以不出现在请求的URL中. 默认是true
         */

        System.out.println("查询 phoneId = " + phoneId + " 的手机~");

        return "success";
    }

    @GetMapping(value = "/searchEX")
    public String searchHeaders(@RequestHeader("Host") String host,
                           @RequestHeader("Connection") String connection,
                           @RequestHeader(value = "Content-Type", required = false) String contentType) {

        System.out.println("Host = " + host);
        System.out.println("Connection = " + connection);
        System.out.println("Content-Type = " + contentType);

        return "success";
    }

    /**
     * 表单提交数据 -> 自动封装为POJO类
     * 1) 只要在方法的形参直接声明对应的类型, SpringMVC 底层会对传入的数据进行自动封装
     *    但是要求传入参数的参数名 必须和 对应对象的字段名保持一致.
     * 2) 匹配失败的字段自动为 null
     */
    @PostMapping(value = "/findBuyer")
    public String findPhoneBuyer(Buyer buyer) {
        System.out.println("买手机的人是:" + buyer);

        return "success";
    }

    //=======================================================
    /**
        验证默认方式 放入 request 域
     */
    @PostMapping(value = "/default")
    public String defaultRequest(Buyer buyer) {
        return "model_OK";
    }

    /**
        验证 HttpServletRequest 方式 放入 request 域
     */
    @PostMapping(value = "/origin")
    public String originalRequest(Buyer buyer, HttpServletRequest request) {
        //通过 request 对象在域中放入数据
        request.setAttribute("author", "Cyan_RA9");

        //修改表单提交的数据
        buyer.setName("Linnea");

        return "model_OK";
    }
}

                然后,更改 model_data.jsp 页面中,form表单的 action 属性,如下图所示:

                接着,在 model_OK.jsp 页面下,新增一行获取我们在 PhoneHandler 中放入的参数的代码,如下图所示:

                运行结果如下 GIF 图所示:

                2.3 Map 方式 演示

                Map 方式将数据放入 request 域,借助了SpringMVC的底层机制,SpringMVC在进行自动数据封装时,它会以形参对象类名小写作为key,然后反射创建的对象作为value,放入 request 域中,如果这时形参还定义了一个 Map 对象,它就会遍历Map里面的k-v,然后将遍历得到的结果也放入到 request 域中。

                在 PhoneHandler 类中新增一个 mapRequest 方法,用于测试 Map 方式放入 request 域。
                PhoneHandler类代码如下:

package com.cyan.web.requestparam;

import com.cyan.web.pojo.Buyer;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

/**
 * @author : Cyan_RA9
 * @version : 23.0
 */
@RequestMapping(value = "/phone")
@Controller
public class PhoneHandler {

    @GetMapping(value = "/search")
    public String searchPhone(@RequestParam(value = "id", required = false) Integer phoneId) {
        /*
            1) @RequestParam 注解 表示会接收提交的参数;
            2) value = "id", 表示提交的参数名是"id"
            3) required = false, 表示这个参数可以不出现在请求的URL中. 默认是true
         */

        System.out.println("查询 phoneId = " + phoneId + " 的手机~");

        return "success";
    }

    @GetMapping(value = "/searchEX")
    public String searchHeaders(@RequestHeader("Host") String host,
                           @RequestHeader("Connection") String connection,
                           @RequestHeader(value = "Content-Type", required = false) String contentType) {

        System.out.println("Host = " + host);
        System.out.println("Connection = " + connection);
        System.out.println("Content-Type = " + contentType);

        return "success";
    }

    /**
     * 表单提交数据 -> 自动封装为POJO类
     * 1) 只要在方法的形参直接声明对应的类型, SpringMVC 底层会对传入的数据进行自动封装
     *    但是要求传入参数的参数名 必须和 对应对象的字段名保持一致.
     * 2) 匹配失败的字段自动为 null
     */
    @PostMapping(value = "/findBuyer")
    public String findPhoneBuyer(Buyer buyer) {
        System.out.println("买手机的人是:" + buyer);

        return "success";
    }

    //=======================================================
    /**
        验证默认方式 放入 request 域
     */
    @PostMapping(value = "/default")
    public String defaultRequest(Buyer buyer) {
        return "model_OK";
    }

    /**
        验证 HttpServletRequest 方式 放入 request 域
     */
    @PostMapping(value = "/origin")
    public String originalRequest(Buyer buyer, HttpServletRequest request) {
        //通过 request 对象在域中放入数据
        request.setAttribute("author", "Cyan_RA9");

        //修改表单提交的数据
        buyer.setName("Linnea");

        return "model_OK";
    }

    /**
     验证 Map 方式 放入 request 域
     */
    @PostMapping(value = "/map")
    public String mapRequest(Buyer buyer, Map<String, Object> map) {
        //通过 map 对象在 request 域中放入数据
        map.put("author", "Cyan_RA9");
        map.put("flower", "Sakura");
        
        return "model_OK";
    }
}

                然后,更改 model_data.jsp 页面中,form表单的 action 属性,如下图所示:

                接着,在 model_OK.jsp 页面下,新增一行获取我们在 PhoneHandler 中放入的 flower 参数的代码,如下图所示:

                运行效果如下 GIF 图所示:

                2.4 ModelAndView 方式 演示

                演示 ModelAndView 方式将数据放入 request 域,我们在 PhoneHandler 类中新增一个 modelAndViewRequest 方法,用于测试 ModelAndView 方式放入 request 域。
                PhoneHandler类代码如下:

package com.cyan.web.requestparam;

import com.cyan.web.pojo.Buyer;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

/**
 * @author : Cyan_RA9
 * @version : 23.0
 */
@RequestMapping(value = "/phone")
@Controller
public class PhoneHandler {

    @GetMapping(value = "/search")
    public String searchPhone(@RequestParam(value = "id", required = false) Integer phoneId) {
        /*
            1) @RequestParam 注解 表示会接收提交的参数;
            2) value = "id", 表示提交的参数名是"id"
            3) required = false, 表示这个参数可以不出现在请求的URL中. 默认是true
         */

        System.out.println("查询 phoneId = " + phoneId + " 的手机~");

        return "success";
    }

    @GetMapping(value = "/searchEX")
    public String searchHeaders(@RequestHeader("Host") String host,
                           @RequestHeader("Connection") String connection,
                           @RequestHeader(value = "Content-Type", required = false) String contentType) {

        System.out.println("Host = " + host);
        System.out.println("Connection = " + connection);
        System.out.println("Content-Type = " + contentType);

        return "success";
    }

    /**
     * 表单提交数据 -> 自动封装为POJO类
     * 1) 只要在方法的形参直接声明对应的类型, SpringMVC 底层会对传入的数据进行自动封装
     *    但是要求传入参数的参数名 必须和 对应对象的字段名保持一致.
     * 2) 匹配失败的字段自动为 null
     */
    @PostMapping(value = "/findBuyer")
    public String findPhoneBuyer(Buyer buyer) {
        System.out.println("买手机的人是:" + buyer);

        return "success";
    }

    //=======================================================
    /**
        验证默认方式 放入 request 域
     */
    @PostMapping(value = "/default")
    public String defaultRequest(Buyer buyer) {
        return "model_OK";
    }

    /**
        验证 HttpServletRequest 方式 放入 request 域
     */
    @PostMapping(value = "/origin")
    public String originalRequest(Buyer buyer, HttpServletRequest request) {
        //通过 request 对象在域中放入数据
        request.setAttribute("author", "Cyan_RA9");

        //修改表单提交的数据
        buyer.setName("Linnea");

        return "model_OK";
    }

    /**
     验证 Map 方式 放入 request 域
     */
    @PostMapping(value = "/map")
    public String mapRequest(Buyer buyer, Map<String, Object> map) {
        //通过 map 对象在 request 域中放入数据
        map.put("author", "Cyan_RA9");
        map.put("flower", "Sakura");

        return "model_OK";
    }

    /**
     验证 ModelAndView 方式 放入 request 域
     */
    @PostMapping(value = "/MV")
    public ModelAndView modelAndViewRequest(Buyer buyer) {
        ModelAndView modelAndView = new ModelAndView();

        modelAndView.addObject("author", "Cyan_RA9~~");
        modelAndView.addObject("flower", "cornflower");

        modelAndView.setViewName("model_OK");

        return modelAndView;
    }
}

                然后,更改 model_data.jsp 页面中,form表单的 action 属性,如下图所示:

                model_OK.jsp 页面不需要变动。

                运行效果如下 GIF 图所示:


二、模型数据放入 session 域

        1.回顾 Session: 

        我们在之前的 “JavaWeb 速通Session” 一文中较为系统地讲解的 Session 的来龙去脉。这里简单回顾一下 Session的基本介绍:

        (1) Session是服务器端技术,即Session数据保存在服务器端;服务器在运行时为每一个用户的浏览器创建一个其独享的session对象。

        (2) 由于session对象为各个用户浏览器所独享,所以各个用户在访问服务器的不同页面时,可以从各自的session中读取/添加数据,实现自己操纵自己相关的数据。

        (3) Session可以用于保存网络商场中的购物车信息,网站登录用户的信息等,也可以防止用户非法登录到某个页面。

        2.实例:

                这里新建一个 session 包,并在包下新建一个 PhoneHandlerEX类,代码如下:

package com.cyan.web.session;

import com.cyan.web.pojo.Buyer;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpSession;

/**
 * @author : Cyan_RA9
 * @version : 23.0
 */
@RequestMapping(value = "/phoneEX")
@Controller
public class PhoneHandlerEX {
    /**
     验证 ModelAndView 方式 放入 request 域
     */
    @PostMapping(value = "/putSession")
    public String putSession(Buyer buyer, HttpSession httpSession) {
        httpSession.setAttribute("author", "王敖");
        httpSession.setAttribute("flower", "Carnation");
        httpSession.setAttribute("life", "Genshin Impact");

        return "model_OK";
    }
}

                然后,更改 model_data.jsp 页面中,form表单的 action 属性,如下图所示:

                model_OK.jsp 页面需要进行更改,因为我们之前取出 author 和 flower属性都是通过 requestScope 域对象取出的,这里因为 PhoneHandlerEX类中是在session域里面放的数据,所以测试 Session 就必须用到 sessionScope 域对象了。当然,由于SpringMVC 自动封装的数据默认是放入 request 域的,所以 Buyer对象的取出不受影响。model_OK.jsp 代码如下:

<%--
    User : Cyan_RA9
    Version : 24.0
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>原神牛逼</title>
</head>
<body>
    <h2>Get data from request field</h2>
    <hr>

    <table style="border: 2px lightsalmon solid">
        <tr>
            <th colspan="2">Acquired Data from requestScope</th>
        </tr>
        <tr>
            <td>买主id:</td>
            <td>${requestScope.buyer.id}</td>
        </tr>
        <tr>
            <td>买主name:</td>
            <td>${requestScope.buyer.name}</td>
        </tr>
        <tr>
            <td>手机id:</td>
            <td>${requestScope.buyer.phone.id}</td>
        </tr>
        <tr>
            <td>手机name:</td>
            <td>${requestScope.buyer.phone.name}</td>
        </tr>
<%--        <tr>
            <td>author —— </td>
            <td>${requestScope.author}</td>
        </tr>
        <tr>
            <td>flower —— </td>
            <td>${requestScope.flower}</td>
        </tr>--%>
        <tr>
            <td>author —— </td>
            <td>${sessionScope.author}</td>
        </tr>
        <tr>
            <td>flower —— </td>
            <td>${sessionScope.flower}</td>
        </tr>
        <tr>
            <td>life —— </td>
            <td style="color: deepskyblue">${sessionScope.life}</td>
        </tr>
    </table>
</body>
</html>

                运行结果如下 GIF 图所示:

        3.@ModelAttribute 注解的使用:

                3.1 @ModelAttribute 基本介绍

                在 SpringMVC 中,@ModelAttribute 作用是:在执行具体的 Handler 方法(目标方法)之前先执行 @ModelAttribute 修饰的方法,为模型数据做准备。

                当 @ModelAttribute 标注在方法上时: 被标注的方法会在该控制器(Controller)的每一个目标方法执行前被调用,它的返回值也会自动存入 Model 中。例如,当我们需要对多个请求共用某些初始数据(如下拉列表选项、从数据库查询出的用户信息)时。
                @ModelAttribute 标注在参数上时: 相当于告诉 SpringMVC,“请从现有的模型(Model)中取出这个对象,而不是直接创建一个新的空对象。” 这样的话就解决了 “对象部分更新” 的问题。比如修改用户信息时,前端只传了姓名,没传密码,通过该注解可以确保从数据库查出的原始密码不会被空值覆盖。

                3.2 @ModelAttribute 和 Spring AOP 前置通知的区别?

                具体而言,如下表所示:

维度 @ModelAttribute (SpringMVC 特性) Spring AOP 前置通知 (@Before)
关注点 数据准备 (Data Preparation) 横切关注点 (Cross-cutting Concerns) (全局)
作用域 仅限于 Web 层(Controller) 可以在 任意层(Service, DAO 等)
数据交互 深度集成 Model 对象。可以直接向 Model 中存取数据,并参与数据绑定。 很难直接操作 SpringMVC 的 Model 对象,主要用于日志、权限、事务等。
实现原理 由 HandlerAdapter 在调用 Handler 的生命周期中通过反射直接调用。 基于 动态代理机制(JDK/CGLib) 在目标方法执行前拦截。
目的 主要是为了解决数据填充对象合并 主要是为了解决代码重复功能增强

                3.3 @ModelAttribute 应用实例

                我们先在 PhoneHandlerEX 类中新增一个方法,用 @ModelAttribute 修饰,代码如下:

    @ModelAttribute
    public void prepareModel() {
        System.out.println("~~@ModelAttribute 修饰的 prepareModel() 方法被调用了~~");
    }

                然后,在刚刚的 putSession 方法中新增一行日志代码,用于测试,如下图所示:

                接着,正常提交表单数据,可以看到运行结果如下图所示:


Δ总结

  • 🆗,以上就是本篇博文的全部内容了,感谢阅读!
  • 在 SpringMVC 的 MVC 架构中,“Model(模型)” 专门指代那些“要在视图页面上展示的数据”,这也映射了我们的博文标题。
  • 文章整体上都是“理论”+“实战” 的结构,应该还是比较清晰的,大家可以自己动手写写、敲一敲。我们下期再见!

        System.out.println("END------------------------------------------------------------");

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐