一、什么是springmvc域对象共享数据?
在 Spring MVC 中,控制器在接收到 DispatcherServlet 分发过来的请求后,会继续调用 Model 层对请求进行处理。Model 层处理完请求后的结果被称为模型数据,会将模型数据返回给 Controller。Controller 在接收到 Model 层返回的模型数据后,下一步就是将模型数据通过域对象共享的方式传递给 View 视图进行渲染,最终返回给客户端展示。
域对象是服务器在内存上创建的一块存储空间,主要用不同动态资源之间的数据传递和数据共享。在 Spring MVC 中,常用的域对象有 request 域对象、session 域对象、application 域对象和page域对象等。
下面是常见的域对象分类:

PageContext对象
- 作用域范围:当前jsp页面内有效
- request对象
- 作用域范围:一次请求内。
- 作用: 解决了一次请求内的资源的数据共享问题
session对象
- 作用域范围:一次会话内有效。
- 说明:浏览器不关闭,并且后台的session不失效,在任意请求中都可以获取到同一个session对象。
- 作用:解决了一个用户不同请求的数据共享问题。
application(ServletContext)对象
- 作用域范围:整个项目内有效。
- 特点:一个项目只有一个,在服务器启动的时候即完成初始化创建无论如何获取都是同一个项目。
- 作用:解决了不同用户的数据共享问题。
二、Spring MVC 提供了多种域对象共享数据的方式,其中最常用的方式如下:
- 使用 Servlet API 向 request 域对象中共享数据
- 使用 ModelAndView 向 request 域对象中共享数据
- 使用 Model 向 request 域对象中共享数据
- 使用 Map 向 request 域对象中共享数据
- 使用 ModelMap 向 request 域对象中共享数据
- 使用 Servlet API 向 session 域中共享数据
- 使用 Servlet API 向 application 域中共享数据
1. 使用 Servlet API 向 request 域对象中共享数据
在控制器方法中设置一个 HttpServletRequest 类型的形参。就可以将模型数据共享到 request 域对象中:
@RequestMapping("/testServletApi") public String testServletApi(HttpServletRequest request){ request.setAttribute("message","reqMessage"); return "success"; }
此方式是通过原生 Servlet API 实现的,会导致控制器与 Servlet 容器耦合度过高,不推荐使用这种方式向 request 域对象中共享数据。
2. 使用 ModelAndView 向 request 域对象中共享数据
通过 Spring 提供的 ModelAndView 向 reuqest 域对象中共享数据。ModelAndView 主要由 model(模型)和 view(视图)两部分组成。其中,model 负责数据共享,而 view 则主要用于设置视图,实现页面的跳转。
ModelAndView 为我们提供了多种方法,其中常用的方法如下表。
方法 | 说明 |
---|---|
ModelAndView addObject(String attributeName, @Nullable Object attributeValue) | 添加模型数据 |
ModelAndView addObject(Object attributeValue) | |
ModelAndView addAllObjects(@Nullable Map<String, ?> modelMap) | |
void setViewName(@Nullable String viewName) | 设置视图 |
void setView(@Nullable View view) |
在 Controller 类中,ModelAndView 只有在作为控制器方法的返回值,返回给前端控制器(DispatcherServlet)时,前端控制器解析才会去解析它,示例代码如下。
@RequestMapping("/testModelAndView") public ModelAndView testModelAndView(){ //new 一个对象 ModelAndView modelAndView = new ModelAndView(); //向请求域中共享数据 modelAndView.addObject("testModelAndView", "hello testModelAndView"); //设置视图,实现页面跳转 modelAndView.setViewName("success"); return modelAndView; }
3. 使用 Model 向 request 域对象中共享数据
在 Controller 控制器方法中设置一个 Model 类型的形参。就可以向 request 域对象中共享数据。
@RequestMapping("/testModel") public String testModel(Model model){ model.addAttribute("testModel", "hello testModel"); return "success"; }
4. 使用 Map 向 request 域对象中共享数据
可以在 Controller 控制器方法中设置一个 Map 类型的形参。就可以向 request 域对象中共享数据
@RequestMapping("/testMap") public String testMap(Map<String, Object> map) { map.put("testScope", "hello,Map"); return "success"; }
5. 使用 ModelMap 向 request 对象中共享数据
在 Controller 控制器方法中设置一个 ModelMap 类型的形参。就可以向 request 域对象中共享数据
@RequestMapping("/modelMap") public String testModeMap(ModelMap modelMap){ modelMap.addAttribute("testModeMap", "hello testModeMap"); return "success"; }
6. 使用 Servlet API 向 session 域对象中共享数据
在控制器方法中设置一个 HttpSession 类型的形参。就可以将数据共享到 session 域对象中
@RequestMapping("/testSession") public String testSession(HttpSession session){ session.setAttribute("testSession", "hello testSession"); return "success"; }
7. 使用 Servlet API 向 application 域对象中共享数据
我们可以在控制器方法中设置一个 HttpSession 类型的形参,并通过它获取到 application 域对象,最终我们就可以将数据共享到 application 域对象中,示例代码如下。
@RequestMapping("/testApplication") public String testApplication(HttpSession session){ ServletContext servletContext = session.getServletContext(); servletContext.setAttribute("testApplication", "hello testApplication"); return "success"; }
三、实现案例
1.需求说明
测试通过域向前端传递信息。
2.在数据库中创建两张表
- user
CREATE TABLE `user` ( `uid` int(255) NOT NULL AUTO_INCREMENT, `username` varchar(255) DEFAULT NULL, `password` varchar(255) DEFAULT NULL, PRIMARY KEY (`uid`) )
3.创建模块,模块名为ssm01
4.补充项目结构,准备好MVC模式下的主要目录
注意:要通过 Mark Directory as 将补充的目录进行设置,选择的参数说明:
- Sources Root:告诉idea这个文件夹及其子文件夹中包含源代码,是需要编译构建的一部分
- Test Sources Root:测试源文件夹允许您将与测试相关的代码与生产代码分开。通常,源和测试源的编译结果被放置在不同的文件夹中。
- Resources Root:用于应用程序中的资源文件(图像、各种配置XML和属性文件等)。
- 在构建过程中,资源文件夹的所有内容都复制到输出文件夹中,如下所示。
- 类似于源,您可以指定生成资源。您还可以指定输出文件夹中的文件夹,您的资源应该复制到
- Test Resources Root:测试的资源文件
- Exclued:不包括、排除
5.在pom.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.augus</groupId> <artifactId>ssm</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <dependencies> <!-- spring核心容器包 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.18</version> </dependency> <!-- spring切面 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.3.18</version> </dependency> <!--aop联盟包--> <dependency> <groupId>aopalliance</groupId> <artifactId>aopalliance</artifactId> <version>1.0</version> </dependency> <!-- 德鲁伊连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.8</version> </dependency> <!--MySQL驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> </dependency> <!-- spring jdbc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.3.20</version> </dependency> <!-- MySQL事务包 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.3.18</version> </dependency> <!-- spring-orm映射依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>5.3.18</version> </dependency> <!-- Apache Commons日志包 --> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency> <!-- log4j2 日志 --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>2.17.1</version> <scope>test</scope> </dependency> <!-- lombok包 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> <scope>provided</scope> </dependency> <!-- spring test测试支持包 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.3.18</version> <scope>test</scope> </dependency> <!-- junit5单元测试 --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.8.2</version> <scope>test</scope> </dependency> <!-- springMVC支持包 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>5.3.20</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.20</version> </dependency> <!-- JSON支持 --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.13.3</version> </dependency> <!-- mybatis核心 jar包 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency> <!-- mybatis-spring整合包 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.6</version> </dependency> <!--jsp 和Servlet 可选--> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.3</version> <scope>provided</scope> </dependency> </dependencies> <build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> </resource> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.xml</include> <include>**/*.properties</include> </includes> </resource> </resources> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>6</source> <target>6</target> </configuration> </plugin> </plugins> </build> </project>
6.更新web.xml 文件,内容如下
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!--spring核心配置文件位置--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <!-- ContextLoaderListener监听器 (1)ContextLoaderListener监听器的作用就是启动Web容器时,自动装配ApplicationContext的配置信息。因为它实现了ServletContextListener这个接口,在web.xml配置这个监听器,启动容器时,就会默认执行它实现的方法。 (2)在ContextLoaderListener中关联了ContextLoader这个类,所以整个加载配置过程由ContextLoader来完成。 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!--编码过滤器--> <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> </filter> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!--配置DispatcherServlet--> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
7.在resource目录下准备配置文件
7.1.创建 log4j2.xml 内容如下
<?xml version="1.0" encoding="UTF-8"?> <Configuration status="DEBUG"> <Appenders> <Console name="Console" target="SYSTEM_ERR"> <PatternLayout pattern="%d{YYYY-MM-dd HH:mm:ss} [%t] %-5p %c{1}:%L - %augus%n" /> </Console> </Appenders> <Loggers> <Root level="DEBUG"> <AppenderRef ref="Console" /> </Root> </Loggers> </Configuration>
7.2.创建 jdbc.properties 内容如下
jdbc_driver=com.mysql.cj.jdbc.Driver jdbc_url=jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai jdbc_username=test jdbc_password=123456
7.3.创建 springmvc.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:mvc="http://www.springframework.org/schema/mvc" 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 http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd "> <!--扫描controller--> <context:component-scan base-package="com.augus.controller"></context:component-scan> <!--会自动注册RequestMappingHandlerMapping与RequestMappingHandlerAdapter两个Bean 除此之外,我们还可以在 Spring MVC 的配置文件中使用以下配置,将静态资源交给 Tomcat 提供的默认的 Servlet 进行处理,这样也可以实现 Spring MVC 项目中静态资源的访问。 --> <mvc:annotation-driven></mvc:annotation-driven> <!--配置视图解析器--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!--配置前置解析器--> <property name="prefix" value="/WEB-INF/templates/"></property> <!--配置后置解析器--> <property name="suffix" value=".jsp"></property> </bean> <!-- 将DispatcherServlet请求映射配置为"/"时,会出现CSS、JS文件加载失败的情况,页面无效果显示。 这是因为Spring MVC会捕获Web容器所有的请求,包括静态资源的请求,Spring MVC会将它们当成一个普通请求处理,因此找不到对应处理器将导致错误。 解决方法 : 在spring-mvc.xml中插入当前代码。它会对进入DispatcherServlet的URL进行筛查,如果发现是静态资源的请求,就将该请求转由Web应用服务器默认的Servlet处理,如果不是静态资源的请求,才由DispatcherServlet继续处理。 --> <mvc:default-servlet-handler></mvc:default-servlet-handler> </beans>
7.4.创建 applicationContext.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:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c" xmlns:util="http://www.springframework.org/schema/util" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd "> <!--加载外部属性文件 --> <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder> <!--扫描service层--> <context:component-scan base-package="com.augus.service"></context:component-scan> <!--配置德鲁伊连接池--> <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="username" value="${jdbc_username}"></property> <property name="password" value="${jdbc_password}"></property> <property name="url" value="${jdbc_url}"></property> <property name="driverClassName" value="${jdbc_driver}"></property> </bean> <!-- SqlSessionFactoryBean能在Spring IoC容器中以SqlSessionFactory的类型保存并被获取。就是继承了FactoryBean这个接口了,这是个支持泛型的接口: 当实现了这个接口的Bean在配置为被Spring接管时,存入IoC容器中的实例类型将会是实例化泛型的那个类型,从IoC容器中获取时也是实例化泛型的那个类型, 这种情况下,Spring 将会在应用启动时为你创建SqlSessionFactory对象,然后将它以 SqlSessionFactory为名来存储。当把这个bean注入到Spring中去了以后,IoC容器中的其他类型就可以拿到SqlSession实例了,就可以进行相关的SQL执行任务了。 --> <bean id="sessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean"> <!--获取上面的数据源--> <property name="dataSource" ref="druidDataSource"></property> <property name="typeAliasesPackage" value="com.augus.pojo"></property> </bean> <!-- 配置MapperScanner 扫描mapper.xml接口 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!--配置SQLSessionFactory--> <property name="sqlSessionFactoryBeanName" value="sessionFactoryBean"></property> <!--配置mapper扫描--> <property name="basePackage" value="com.augus.mapper"></property> </bean> <!--配置事务管理器--> <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="druidDataSource"></property> </bean> <!--开启事务注解--> <tx:annotation-driven transaction-manager="dataSourceTransactionManager"></tx:annotation-driven> </beans>
8.在WEB-INF/templates下分别创建userjsp、showDataPage.jsp
- user.html
<%-- Created by IntelliJ IDEA. User: Augus Date: 2023/8/1 Time: 11:52 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <!--域中的数据--> requestScope:message:${requestScope.message},user1:${requestScope.userList[0].username}<br/> sessionScope:message:${sessionScope.message},user1:${sessionScope.userList[0].password}<br/> applicationScope:message:${applicationScope.message},user1:${applicationScope.userList[1].uid}<br/> </body> </html>
- showDataPage.jsp
<%-- Created by IntelliJ IDEA. User: Augus Date: 2023/8/1 Time: 12:30 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> requestScope:message:${requestScope.message},user1:${requestScope.userList[0]}<br/> </body> </html>
9.在java下创建包com.augusu,在分别创建四个包,如下
9.1.新建pojo包
在下面创建 User、Product实体类,代码如下
- User
package com.augus.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; @AllArgsConstructor @NoArgsConstructor @Data public class User implements Serializable { private Integer uid; private String username; private String password; }
9.2.新建mapper包(作为持久层)
- 在创建 UserMapper、UserMapper.xml
package com.augus.mapper; import com.augus.pojo.User; import org.apache.ibatis.annotations.Param; import java.util.List; public interface UserMapper { User findByUserName(@Param("name") String username); List<User> findUserAll(); }
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.augus.mapper.UserMapper"> <select id="findByUserName" resultType="user"> SELECT * FROM user WHERE username=#{name} </select> <select id="findUserAll" resultType="user"> SELECT * FROM user </select> </mapper>
9.3.新建service包
在下面创建 接口UserService同时在创建impl包里面创建 UserServiceImpl实现类
- UserService
package com.augus.service; import com.augus.pojo.User; import java.util.List; public interface UserService { User findByUserName(String username); List<User> findUserAll(); }
- UserServiceImpl
package com.augus.service.impl; import com.augus.mapper.UserMapper; import com.augus.pojo.User; import com.augus.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override public User findByUserName(String username) { return userMapper.findByUserName(username); } @Override public List<User> findUserAll() { return userMapper.findUserAll(); } }
9.4.新建controller包
在下面创建ScopeController类,代码如下
package com.augus.controller; import com.augus.pojo.User; import com.augus.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import java.util.List; import java.util.Map; @Controller public class ScopeController { @Autowired private UserService userService; @RequestMapping("/setData") public String setData(HttpServletRequest request, HttpSession session){ List<User> userList = userService.findUserAll(); //向三个域中放入数据 request.setAttribute("message","竹杖芒鞋轻胜马"); request.setAttribute("userList",userList); session.setAttribute("message","一蓑烟雨任平生"); session.setAttribute("userList",userList); ServletContext servletContext = request.getServletContext(); servletContext.setAttribute("message","莫听穿林打叶声"); servletContext.setAttribute("userList",userList); return "user"; } /* 使用Model传递数据 * model对象addAttribute * 主要是对请求域传递数据进行了API上的封装 * 降低controller和Servlet之间的耦合度 * 重定向下,没法使用model传递域中的数据 * model中的字符串类型的键值对信息会转换为请求参数,转发给目标组件 * */ @RequestMapping("/setData2") public String setData2(Model model){ List<User> userList = userService.findUserAll(); //向域中添加数据 model.addAttribute("message", "念去去"); model.addAttribute("userList",userList); //return "redirect:/showDataPage";//重定向下无法使用 return "forward:/showDataPage"; } @RequestMapping("/showDataPage") public String showDataPage(){ return "showDataPage"; } /** * 使用ModelAndView传递数据 * @return */ @RequestMapping("/setData3") public ModelAndView setData3(){ ModelAndView modelAndView = new ModelAndView(); //向request域中放入数据 Map<String, Object> model = modelAndView.getModel(); List<User> userList = userService.findUserAll(); model.put("message","念去去,千里烟波,暮霭沉沉楚天阔"); model.put("userList",userList); //设置视图 modelAndView.setViewName("forward:/showDataPage"); //modelAndView.setViewName("redirect:/showDataPage"); return modelAndView; } }
10.重新部署项目,访问:http://localhost:8080/ssm01_war_exploded/user 如下:
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/253656.html