[Java/Spring] 深入理解 : SpringBoot PropertyMapper

news/2024/10/12 10:15:39

1 概述: SpringBoot PropertyMapper

简介

  • PropertyMapper是Spring提供的一个工具类,主要用于重新赋值,转换等操作
  • 位于: org.springframework.boot.context.properties.PropertyMapper

2 应用场景

场景 :2个异构数据对象的转换

  • 在实际工作中,经常会遇到将数据库的实体类 Entity 转成 DTO 类的操作。通常的方法:
  • 手工方法:我们有可以将属性一个个get出来,再set进去。

但经常涉及到判空、数据类型的转换等简单的逻辑处理,容易留下一大堆 IF ELSE 的臃肿代码。

  • 第三方工具:用BeanUtils工具类 将对应类型的属性一个个copy进去。
  • 现在还可以尝试使用 SpringBoot 的 PropertyMapper 来做数据对象的转换

案例1:SpringBoot 的 RabbitTemplateConfiguration

  • SpringBoot 官方模块 spring-boot-starter-amqpRabbitTemplate 的配置实现
  • org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration.RabbitTemplateConfiguration
@Bean
@ConditionalOnSingleCandidate(ConnectionFactory.class)
@ConditionalOnMissingBean(RabbitOperations.class)
public RabbitTemplate rabbitTemplate(RabbitProperties properties,ObjectProvider<MessageConverter> messageConverter,ObjectProvider<RabbitRetryTemplateCustomizer> retryTemplateCustomizers,ConnectionFactory connectionFactory) {PropertyMapper map = PropertyMapper.get();RabbitTemplate template = new RabbitTemplate(connectionFactory);messageConverter.ifUnique(template::setMessageConverter);template.setMandatory(determineMandatoryFlag(properties));RabbitProperties.Template templateProperties = properties.getTemplate();if (templateProperties.getRetry().isEnabled()) {template.setRetryTemplate(new RetryTemplateFactory(retryTemplateCustomizers.orderedStream().collect(Collectors.toList())).createRetryTemplate(templateProperties.getRetry(),RabbitRetryTemplateCustomizer.Target.SENDER));}map.from(templateProperties::getReceiveTimeout).whenNonNull().as(Duration::toMillis).to(template::setReceiveTimeout);map.from(templateProperties::getReplyTimeout).whenNonNull().as(Duration::toMillis).to(template::setReplyTimeout);map.from(templateProperties::getExchange).to(template::setExchange);map.from(templateProperties::getRoutingKey).to(template::setRoutingKey);map.from(templateProperties::getDefaultReceiveQueue).whenNonNull().to(template::setDefaultReceiveQueue);return template;
}

案例2:基于 Http11NioProtocol、WebServerFactoryCustomizer 、自定义配置实体,实现 自定义 SpringBoot 的 Tomcat Server 配置

TomcatEmbedServerProperties : 应用程序的自定义配置实体

//import org.springframework.boot.context.properties.ConfigurationProperties;
//import org.springframework.context.annotation.ComponentScan;
//import org.springframework.context.annotation.Configuration;/*** @create-time 2023/4/11* @description ...*/
//@ComponentScan
//@Configuration
//@ConfigurationProperties(
//    prefix="service-config.tomcat-server"
//    , ignoreUnknownFields = true
//)
public class TomcatEmbedServerProperties { //应用程序的自定义配置实体private Integer port;private Integer minSpareThreads;private Integer maxThreads;private Integer acceptCount;private Integer maxConnections;private Integer maxKeepAliveRequests;private Integer keepAliveTimeout;private Integer connectionTimeout;public TomcatEmbedServerProperties(Integer port, Integer minSpareThreads, Integer maxThreads, Integer acceptCount, Integer maxConnections, Integer maxKeepAliveRequests, Integer keepAliveTimeout, Integer connectionTimeout) {this.port = port;this.minSpareThreads = minSpareThreads;this.maxThreads = maxThreads;this.acceptCount = acceptCount;this.maxConnections = maxConnections;this.maxKeepAliveRequests = maxKeepAliveRequests;this.keepAliveTimeout = keepAliveTimeout;this.connectionTimeout = connectionTimeout;}public TomcatEmbedServerProperties() {}public Integer getPort() {return port;}public void setPort(Integer port) {this.port = port;}public Integer getMinSpareThreads() {return minSpareThreads;}public void setMinSpareThreads(Integer minSpareThreads) {this.minSpareThreads = minSpareThreads;}public Integer getMaxThreads() {return maxThreads;}public void setMaxThreads(Integer maxThreads) {this.maxThreads = maxThreads;}public Integer getAcceptCount() {return acceptCount;}public void setAcceptCount(Integer acceptCount) {this.acceptCount = acceptCount;}public Integer getMaxConnections() {return maxConnections;}public void setMaxConnections(Integer maxConnections) {this.maxConnections = maxConnections;}public Integer getMaxKeepAliveRequests() {return maxKeepAliveRequests;}public void setMaxKeepAliveRequests(Integer maxKeepAliveRequests) {this.maxKeepAliveRequests = maxKeepAliveRequests;}public Integer getKeepAliveTimeout() {return keepAliveTimeout;}public void setKeepAliveTimeout(Integer keepAliveTimeout) {this.keepAliveTimeout = keepAliveTimeout;}public Integer getConnectionTimeout() {return connectionTimeout;}public void setConnectionTimeout(Integer connectionTimeout) {this.connectionTimeout = connectionTimeout;}private static boolean isPositive(int value) {return value > 0;}@Overridepublic String toString() {return "TomcatEmbedServerProperties{" +"port=" + port +", minSpareThreads=" + minSpareThreads +", maxThreads=" + maxThreads +", acceptCount=" + acceptCount +", maxConnections=" + maxConnections +", maxKeepAliveRequests=" + maxKeepAliveRequests +", keepAliveTimeout=" + keepAliveTimeout +", connectionTimeout=" + connectionTimeout +'}';}
}

WebServerConfiguration : 应用程序的自定义 Tomcat Server 配置 Bean

package xx.xx.biz.common.configuration;import xx.xx.common.dto.serviceconfig.TomcatEmbedServerProperties;
import org.apache.catalina.Context;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.startup.Tomcat;
import org.apache.coyote.UpgradeProtocol;
import org.apache.coyote.http11.Http11NioProtocol;
import org.apache.tomcat.util.descriptor.web.SecurityCollection;
import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.embedded.TomcatWebServerFactoryCustomizer;
import org.springframework.boot.autoconfigure.web.servlet.TomcatServletWebServerFactoryCustomizer;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.ConfigurableWebServerFactory;
import org.springframework.boot.web.server.WebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;import javax.servlet.ServletException;/*** @create-time 2023/4/3 * @description 内嵌tomcat*  [1] 在 spring-configuration-metadata.json 文件中相关tomcat配置*      server.tomcat.accept-count:                               等待队列长度,默认100*      server.tomcat.max-connections:                            最大可连接数,默认10000*      server.tomcat.max-threads:                                最大工作线程数,默认200*      server.tomcat.min-spare-threads:                          最小工作线程数,默认10*      server.tomcat.accesslog.enabled=true                     开启access日志*      server.tomcat.accesslog.directory=/var/www//tomcat       日志存放的路径*      server.tomcat.accesslog.pattern=%h %l %u %t "%r" %s %b %D  日志格式为:请求的host主机地址,时间,方式,路径,协议,状态,返回字节数,处理时间**      注意:默认配置下,连接数超过 10000 后出现拒接连接情况;触发的请求超过 200+100 后拒绝*   [2] 定制内嵌 tomcat 开发*      相关参数:*          keepAliveTimeOut :多少毫秒后不响应的断开keepalive*          maxKeepAliveRequests :多少次请求后keepalive断开失效*      keepAlive 简介:参考这里*          优点:*              Keep-Alive 功能使客户端到服务器端的连接持续有效,当出现对服务器的后继请求时,Keep-Alive功能避免了建立或者重新建立连接。*              长连接能够保证服务器和客户端的socket能够高效利用,减少握手等额外的开销。*          缺点:*              但是对于负担较重的网站来说,这里存在另外一个问题:*                  虽然为客户保留打开的连接有一定的好处,但它同样影响了性能,因为在处理暂停期间,本来可以释放的资源仍旧被占用。*                  当Web服务器和应用服务器在同一台机器上运行时,Keep-Alive功能对资源利用的影响尤其突出。* @reference-doc*  [1] springboot内嵌tomcat优化 - 博客园 - https://www.cnblogs.com/hjwucc/p/11425306.html*  [2] 我可以为 Spring Boot 的嵌入式 tomcat 启用 tomcat 管理器应用程序吗? - IT1352 - https://www.it1352.com/2405633.html*  [3] Tomcat卷三:Jasper引擎 - CSDN - https://blog.csdn.net/m0_53157173/article/details/123131713*  [4] Spring Boot 最佳实践(二)集成Jsp与生产环境部署 - 51CTO - https://blog.51cto.com/vipstone/5408719*  [5] 嵌入式 Tomcat (Embedded Tomcat) - 博客园 - https://www.cnblogs.com/develon/p/11602969.html**  [6] 【Java基础】-- isAssignableFrom的用法详细解析 - 腾讯云 - https://cloud.tencent.com/developer/article/1754376*  [7] The HTTP Connector - Apache Tomcat(9) - https://tomcat.apache.org/tomcat-9.0-doc/config/http.html*  [8] HTTP/1.1与HTTP/1.0的区别 - CSDN - https://blog.csdn.net/qq_25827845/article/details/80127198*/@Configuration
public class WebServerConfiguration {private static final Logger logger = LoggerFactory.getLogger(WebServerConfiguration.class);// 在某配置类中添加如下内容// 监听的http请求的端口,需要在application配置中添加http.port=端口号  如 80
//    @Value("${http.port}")
//    Integer httpPort;//正常启用的https端口 如 443
//    @Value("${server.port}")
//    Integer httpsPort;@AutowiredEnvironment environment;@AutowiredServerProperties serverProperties;@AutowiredTomcatEmbedServerProperties tomcatServerProperties;//应用程序的 自定义 Tomcat Server 配置实体//    @Bean
//    public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
//        return new TomcatWebServerFactoryCustomizer(environment, serverProperties);
//    }@Beanpublic WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer() {return new WebServerFactoryCustomizer() {@Overridepublic void customize(WebServerFactory factory) {//使用工厂类定制 tomcat connectorTomcatServletWebServerFactory webServerFactory =  ((TomcatServletWebServerFactory)factory);//webServerFactory.addContextCustomizers((context -> {//    context.addWelcomeFile("/index.html");//    // 使用 Tomcat 的 LegacyCookieProcessor 处理器//    context.setCookieProcessor(new LegacyCookieProcessor())//}));//TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer = new TomcatServletWebServerFactoryCustomizer(serverProperties);//tomcatServletWebServerFactoryCustomizer.customize(webServerFactory);//int order = tomcatServletWebServerFactoryCustomizer.getOrder();TomcatConnectorCustomizer tomcatConnectorCustomizer = new TomcatConnectorCustomizer() {@Overridepublic void customize(Connector connector) {Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();PropertyMapper propertyMapper = PropertyMapper.get();//protocol.setPort(tomcatServerProperties.getPort());propertyMapper.from(tomcatServerProperties::getPort).whenNonNull().to(protocol::setPort);/** Tomcat 9.0.46 默认配置 **//*** @param 最小备用线程数* @defaultValue 10* tomcat启动时的初始化的线程数*///protocol.setMinSpareThreads(10);propertyMapper.from(tomcatServerProperties::getMinSpareThreads).whenNonNull().to(protocol::setMinSpareThreads);/*** @param 最大线程数* @defaultValue 200* Tomcat可创建的最大的线程数,每一个线程处理一个请求* 超过这个请求数后,客户端请求只能排队,等有线程释放才能处理* 可以在服务器 CPU 核心数的 200~250 倍之间*///protocol.setMaxThreads(200);propertyMapper.from(tomcatServerProperties::getMaxThreads).whenNonNull().to(protocol::setMaxThreads);/*** @param 请求等待队列的容量大小* @defaultValue 100* 当tomcat请求处理线程池中的所有线程都处于忙碌状态时,此时新建的链接将会被放入到pending队列* acceptCount即是此队列的容量,如果队列已满,此后所有的建立链接的请求(accept),都将被拒绝* 在高并发/短链接较多的环境中,可以适当增大此值;当长链接较多的场景中,可以将此值设置为0*///protocol.setAcceptCount(100);propertyMapper.from(tomcatServerProperties::getAcceptCount).whenNonNull().to(protocol::setAcceptCount);/*** @param 在同一时间,tomcat能够接受的最大连接数* @defaultValue 8192* 当达到`max-connections `临界值时,系统可能会基于accept-count继续接受连接* tomcat允许接收和处理的最大链接数,对于BIO而言此值默认与maxThreads参数一样,对于NIO而言此值默认为10000* 此值还受限于系统的 ulimit、CPU、内存等配置。*/// protocol.setMaxConnections(8192);propertyMapper.from(tomcatServerProperties::getMaxConnections).whenNonNull().to(protocol::setMaxConnections);/*** @param 处于keepAlive状态的请求的个数* @defaultValue 100* -1 表示不限制,1表示关闭 keepAlive 机制* 建议: 此值为 maxThreads * 0.5; 不得大于 maxThreads*///protocol.setMaxKeepAliveRequests(100);propertyMapper.from(tomcatServerProperties::getMaxKeepAliveRequests).whenNonNull().to(protocol::setMaxKeepAliveRequests);/*** @param Tomcat 在关闭连接(Connection)之前,等待另一个请求的时间 (HTTP 1.1 KeepAlive 持久连接)* @defaultValue 60000* 此值控制/影响: HTTP响应报文中的 2个Header*     Connection: Keep-Alive*     keep-alive: timeout={KeepAliveTimeout/1000}s*///protocol.setKeepAliveTimeout(60000);propertyMapper.from(tomcatServerProperties::getKeepAliveTimeout).whenNonNull().to(protocol::setKeepAliveTimeout);/*** @param 与客户端建立连接后, Tomcat 等待客户端请求的时间* @defaultValue 60000* 如果客户端没有请求进来,等待一段时间后断开连接,释放线程*///protocol.setConnectionTimeout(60000);propertyMapper.from(tomcatServerProperties::getConnectionTimeout).whenNonNull().to(protocol::setConnectionTimeout);}};webServerFactory.addConnectorCustomizers(tomcatConnectorCustomizer);webServerFactory.addContextCustomizers();}};}// springboot2 写法
//    @Bean
//    public TomcatServletWebServerFactory tomcatServletContainer() {
//        TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() {
//            @Override
//            protected void postProcessContext(Context context) {
//                SecurityConstraint constraint = new SecurityConstraint();
//                constraint.setUserConstraint("CONFIDENTIAL");
//                SecurityCollection collection = new SecurityCollection();
//                collection.addPattern("/*");
//                constraint.addCollection(collection);
//                context.addConstraint(constraint);
//            }
//        };
//        tomcat.addAdditionalTomcatConnectors(httpConnector());
//        tomcat.addConnectorCustomizers();
//        return tomcat;
//    }// springboot 1.x/*** @reference-doc*  [1] Spring Boot如何控制Tomcat缓存? - codenong - https://www.codenong.com/39146476/*      EmbeddedServletContainerCustomizer / TomcatEmbeddedServletContainerFactory / ConfigurableEmbeddedServletContainer* @return*/
//当 Spring 容器中没有 TomcatEmbeddedServletContainerFactory 这个 bean 时,会把此 bean 加载进容器
/*    @Beanpublic EmbeddedServletContainerFactory servletContainer() {return new TomcatEmbeddedServletContainerFactory() {@Overrideprotected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(Tomcat tomcat) {tomcat.addUser("admin", "secret");tomcat.addRole("admin", "manager-gui");try {tomcat.addWebapp("/manager", "/path/to/manager/app");}catch (ServletException ex) {throw new IllegalStateException("Failed to add manager app", ex);}return super.getTomcatEmbeddedServletContainer(tomcat);}};}*///    public Connector httpConnector() {
//        Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
//        connector.setScheme("http");
//        //Connector监听的http的端口号
//        connector.setPort(httpPort);
//        connector.setSecure(false);
//        //监听到http的端口号后转向到的https的端口号
//        connector.setRedirectPort(httpsPort);
//        return connector;
//    }//    @Configuration(
//            proxyBeanMethods = false
//    )
//    @ConditionalOnClass({Tomcat.class, UpgradeProtocol.class})
//    public static class TomcatWebServerFactoryCustomizerConfiguration {
//        public TomcatWebServerFactoryCustomizerConfiguration() {
//        }
//
//        @Bean
//        public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
//            return new TomcatWebServerFactoryCustomizer(environment, serverProperties);
//        }
//    }
}

案例3: Order 转 OrderDTO

Order

@Data
public class Order {private Long id;private BigDecimal totalAmout;private Integer status;private Long userId;private LocalDateTime createTime;
}

OrderDTO

@Data
public class OrderDTO {private Long id;private BigDecimal totalAmout;private Integer status;private Long userId;private String createTime;
}

使用 PropertyMapper 转换

Order order = new Order();
order.setId(1L);
order.setStatus(1);
order.setTotalAmout(BigDecimal.ONE);
order.setUserId(100L);
order.setCreateTime(LocalDateTime.now());PropertyMapper propertyMapper = PropertyMapper.get();
OrderDTO orderDTO = new OrderDTO();propertyMapper.from(order::getId).to(orderDTO::setId);
// 如果from获取到的元素不是null,则执行to里面的动作
propertyMapper.from(order::getStatus).whenNonNull().to(orderDTO::setStatus);
propertyMapper.from(order::getUserId).to(orderDTO::setUserId);
propertyMapper.from(order::getTotalAmout).to(orderDTO::setTotalAmout);// 因为Order里面的createTime是LocalDateTime类型,OrderDTO里面则是String类型,需要转换一下
propertyMapper.from(order::getCreateTime).as(createTime -> {DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");return createTime.format(formatter);
}).to(orderDTO::setCreateTime);

这样一来就可以通过 PropertyMapper 将 Order 对象的值 set 到 OrderDTO 对象中。

3 PropertyMapper API

  • <T> Source<T> from(Supplier<T> supplier) :提供值的来源,入参为Supplier
  • <T> Source<T> from(T value) :一种重载形式,入参可以为一个对象
  • void to(Consumer<T> consumer) :通过将任何未过滤的值传递给指定的使用者来完成映射
  • <R> R toInstance(Function<T, R> factory) :通过从未过滤的值创建新实例来完成映射
  • void toCall(Runnable runnable) :当值还没有时,通过调用指定的方法来完成映射
  • <R> Source<R> as(Function<T, R> adapter) :将T类型的入参转成R类型的出参,类似于Stream中的map
  • Source<T> when... :这一系列方法,都是过滤用的。在from后面调用,如果满足条件,就直接to方法
  • static PropertyMapper get() :提供PropertyMapper实例
  • PropertyMapper alwaysApplyingWhenNonNull() :提供实例时,当前实例就过滤掉from之后是null的元素。PropertyMapper.get().alwaysApplyingWhenNonNull();
  • PropertyMapper alwaysApplying(SourceOperator operator) :自定义过滤规则,参考代码
//使当前 PropertyMapper 只会映射 LocalDateTime 类型的字段
PropertyMapper propertyMapper = PropertyMapper.get().alwaysApplying( new PropertyMapper.SourceOperator() {@Overridepublic <T> PropertyMapper.Source<T> apply(PropertyMapper.Source<T> source) {return source.when(t -> t instanceof LocalDateTime);}} );//注意:如果from方法后面有when条件,则 alwaysApplying 中设置的初始化提交将会失效

X 参考文献

  • [小工具]PropertyMapper使用 - 稀土掘金

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/70564.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

--Nacos服务注册与发现的概述与原理--

什么是 Nacos 官网中的概述:Nacos官网链接 Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service的首字母简称,一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。 Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集…

E64 树形DP P3174 [HAOI2009] 毛毛虫

视频链接:E64 树形DP P3174 [HAOI2009] 毛毛虫_哔哩哔哩_bilibili P3174 [HAOI2009] 毛毛虫 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)// 树形DP O(n) #include <iostream> #include <cstring> #include <algorithm> using namespace std;const int …

NocoBase 与 Appsmith:哪个低代码平台更适合你?

深入了解 NocoBase 和 Appsmith 这两大低代码/无代码开发平台的关键差异。通过我们的深度对比,全面了解它们在数据管理、集成能力、UI/UX 设计等方面的优劣,帮助您选择更适合的开发工具。欢迎回到我们深度对比系列文章,这是本系列的第三篇。在之前我们已经与两个非常优秀的产…

使用 InstallShield 2020 打包项目 安装程序

打开InstallShield 2020程序,单击New填写信息下一步 打包成功效果 注:打包过程中报错,进行如下设置

H3C交换机SSH使用RSA公钥免密登录配置

1.使用puttygen.exe计算RSA 2.保存公钥和私钥 公钥:pub.key 注意:公钥上传到交换机(FTP等方式)。 私钥:private.ppk 3.配置交换机 <Switch> system-view [Switch] public-key local create rsa The range of public key size is (512 ~ 2048). If the key modulus …

AD9129板卡设计原理图:303-两路5.6Gsps 14bit DA FMC子卡

一、板卡概述 FMC303可实现宽波段、双通道、14位、5.6GSPS(2.8gsps直接射频综合)DAC功能,时钟可采用内部时钟源(可选择锁定到外部参考),或外部提供的采样时钟。此外还为用户提供定制采样控制的触发器输入。FMC303在机械上和电气上符合FMC标准(ANSI/VITA 57.1)。该卡具有…

Camstar : The remote server returned an error. (500) intemal Server Error.

这个报错让人摸不着头脑。 过程:安装CamstarEnterprise,新建了数据库,配置了managementStudio,Create database,然后放最新的mdb,update wcf server,放好最新的camstarPort代码。登录-->报错。 mdb是有电子套件内容的,但是呢,我还么有执行安装电子套件,想着先登录…

.NET程序获取当前IP经纬度,并通过经纬度实现天气查询功能

创建一个.net 8的webapi项目备用 编辑一个实体类,该实体类用于存储获取ip地址的经纬度数据使用 继续编辑三个类,用来存储对应经纬度的具体天气数据包: 改造默认的天气控制器,里面写成我们自己的。例如先写个获取IP的经纬度坐标的请求 运行一下,看下现在的效果,可以看…