手写SpringMVC之ApplicationContextListener

什么是Spring MVC?

Spring Web MVC是基于Servlet API构建的原始Web框架,从一开始就包含在Spring Framework中。正式名称“Spring Web MVC”来自其源模块的名称( spring-webmvc ),但它通常被称为“Spring MVC”。

手写Spring MVC(一)

创建项目

创建父工程 [ 选择9号模板 :site-simple],命名为shop(以商城为例子)

创建子工程 [ 选择10号模板:webapp],命名为shop-web

创建子工程 [ 选择7号模板:quickstart],命名为shop-mvc

依赖准备

servlet-api
<dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>4.0.1</version><scope>provided</scope>
</dependency>
lombok
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.24</version>
</dependency>
编译插件
<build><finalName>shop_web</finalName><pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (maybe moved to parent pom) --><plugins><!--这个插件就是java类生成class的编译插件--><plugin><artifactId>maven-compiler-plugin</artifactId><configuration><!--编译参数--><compilerArgument>-parameters</compilerArgument><source>1.8</source><target>1.8</target></configuration></plugin></plugins></pluginManagement>
</build>

创建文件

  1. 在shop-web工程下创建以下目录

    image-20240626170051128

  2. 在mvc工程下创建以下目录

    image-20240626170120536

文件详解

mvc工程
  1. cn.cnmd.shop.mvc.annotation:主要是注解,包括控制器、路由映射、配置

    1. cn.cnmd.shop.mvc.annotation.Controller
    package cn.cnmd.shop.mvc.annotation;import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;@Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Controller {
    }
    
    1. cn.cnmd.shop.mvc.annotation.RequestMapping
    package cn.cnmd.shop.mvc.annotation;import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;@Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface RequestMapping {String value();
    }
    
    1. cn.cnmd.shop.mvc.annotation.Configuration
    package cn.cnmd.shop.mvc.annotation;import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;@Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Configuration {String value();
    }
    
  2. cn.cnmd.shop.mvc.canstant:包含了常见的错误码和错误信息

    1. cn.cnmd.shop.mvc.canstant.ResponseCodeInterface

      package cn.cnmd.shop.mvc.constant;public interface ResponseCodeInterface {int getCode();String getMessage();void setCode(int code);void setMessage(String message);
      }
      
    2. cn.cnmd.shop.mvc.canstant.ResponseCode

      package cn.cnmd.shop.mvc.constant;public enum ResponseCode implements ResponseCodeInterface {CONFIG_EXCEPTION(100, "config的配置信息出错"),CONFIGURATION_EXCEPTION(101, "需要配置Configuration这个注解"),CLASS_FILE_EXCEPTION(102, "class文件转换异常"),REQUEST_MAPPING_PATH_EXCEPTION(103, "RequestMapping地址设置有误"),REQUEST_PATH_EXCEPTION(104, "uri映射错误"),EXCEPTION_CONFIG_EXCEPTION(105, "未配置全局异常的路径"),ADVISER_CONFIG_EXCEPTION(106, "未配置处理器的路径");private int code;private String message;ResponseCode(int code, String message) {this.code = code;this.message = message;}@Overridepublic int getCode() {return code;}@Overridepublic String getMessage() {return message;}@Overridepublic void setCode(int code) {this.code = code;}@Overridepublic void setMessage(String message) {this.message = message;}
      }
  3. cn.cnmd.shop.mvc.container:容器,用于存储项目启动之后创建的BeanDefinition对象

    1. cn.cnmd.shop.mvc.container.BeanContainer

      package cn.cnmd.shop.mvc.container;import cn.cnmd.shop.mvc.model.BeanDefinition;
      import lombok.Getter;import java.util.Map;
      import java.util.concurrent.ConcurrentHashMap;public class BeanContainer {@Getterprivate static Map<String, BeanDefinition<?>> maps = null;static {maps = new ConcurrentHashMap<>();}
      }
  4. cn.cnmd.shop.mvc.exception:异常类,用于处理异常

    1. cn.cnmd.shop.mvc.exception.FrameWorkException

      package cn.cnmd.shop.mvc.exception;import lombok.AllArgsConstructor;
      import lombok.Data;@AllArgsConstructor
      @Datapublic class FrameWorkException extends RuntimeException {private int code;private String message;
      }
      
  5. cn.cnmd.shop.mvc.listener:监听器,主要作用是在项目启动时扫描controller下的文件生成BeanDefinition对象

    1. cn.cnmd.shop.mvc.listener.ApplicationListener

      package cn.cnmd.shop.mvc.listener;import cn.cnmd.shop.mvc.annotation.Configuration;
      import cn.cnmd.shop.mvc.annotation.RequestMapping;
      import cn.cnmd.shop.mvc.constant.ResponseCode;
      import cn.cnmd.shop.mvc.container.BeanContainer;
      import cn.cnmd.shop.mvc.exception.FrameWorkException;
      import cn.cnmd.shop.mvc.model.BeanDefinition;
      import cn.cnmd.shop.mvc.model.MethodDefinition;
      import cn.cnmd.shop.mvc.model.ParameterDefinition;import javax.servlet.ServletContext;
      import javax.servlet.ServletContextEvent;
      import javax.servlet.ServletContextListener;
      import java.io.File;
      import java.lang.reflect.Method;
      import java.lang.reflect.Parameter;
      import java.util.ArrayList;
      import java.util.List;
      import java.util.Map;public class ApplicationListener implements ServletContextListener {@Overridepublic void contextInitialized(ServletContextEvent sce) {/** 扫描web项目的Controller层* 封装成类描述类的对象* 添加到容器中 -- Key(父级uri+子级uri) Value(类描述类的对象)*/ServletContext servletContext = sce.getServletContext();String config = servletContext.getInitParameter("config");//cn.cnmd.config.AppConfigif (config == null) {throw new FrameWorkException(ResponseCode.CONFIG_EXCEPTION.getCode(), ResponseCode.CONFIG_EXCEPTION.getMessage());}//获取配置类的class对象Class<?> configurationClass = getConfiguration(config);//通过配置类的class对象拿到 AppConfig注解 中的cn.cnmd.controllerString controllerPosition = getControllerPosition(configurationClass);//D:\Desktop\code\apache-tomcat-8.0.49\webapps\ROOT\WEB-INF\classes\cn\cnmd\controllerString controllerAbsolutePath = getControllerAbsolutePath(servletContext, controllerPosition);//获取controller文件夹下所有的文件List<File> fileList = new ArrayList<>();findFileByPath(fileList, controllerAbsolutePath);//文件对象集合 --> class对象集合List<Class<?>> classes = transformTo(servletContext, fileList);//封装成类描述类的对象: BeanDefinition -> MethodDefinition -> ParameterDefinitionhandleController(classes);}/*** 通过web.xml的配置* <context-param>* <param-name>config</param-name>* <param-value>cn.cnmd.config.AppConfig</param-value>* </context-param>* 参数 [config] 获取的 [cn.cnmd.config.AppConfig] 获取配置文件类的class对象** @param config [cn.cnmd.config.AppConfig]* @return class对象*/public Class<?> getConfiguration(String config) {try {return Class.forName(config);} catch (ClassNotFoundException e) {throw new RuntimeException(e);}}/*** 通过配置文件类的class对象获取注解中的controller的类的全限定名 [cn.cnmd.controller]** @param configurationClass 配置文件类的class对象* @return controller的类的全限定名*/public String getControllerPosition(Class<?> configurationClass) {Configuration annotation = configurationClass.getAnnotation(Configuration.class);if (annotation == null) {throw new FrameWorkException(ResponseCode.CONFIGURATION_EXCEPTION.getCode(), ResponseCode.CONFIGURATION_EXCEPTION.getMessage());}return annotation.value();}/*** 通过配置文件类 [AppConfig] 的注解信息 [cn.cnmd.controller] 获取controller包的发布路径** @param servletContext     servlet上下文对象* @param controllerPosition 配置文件类 [AppConfig] 的注解信息 [cn.cnmd.controller]* @return controller包的发布路径*/public String getControllerAbsolutePath(ServletContext servletContext, String controllerPosition) {//D:\Desktop\code\apache-tomcat-8.0.49\webapps\ROOT\WEB-INF\classesString absolutePath = servletContext.getRealPath("WEB-INF" + File.separator + "classes");controllerPosition = controllerPosition.replace(".", File.separator);return absolutePath + File.separator + controllerPosition;}/*** 通过controller包路径找到路径下所有文件对象** @param fileList               文件对象集合* @param controllerAbsolutePath controller包的发布路径*/public void findFileByPath(List<File> fileList, String controllerAbsolutePath) {File file = new File(controllerAbsolutePath);File[] files = file.listFiles();if (files != null) {for (File f : files) {if (f.isDirectory()) {findFileByPath(fileList, f.getAbsolutePath());} else if (f.isFile()) {fileList.add(f);}}}}public List<Class<?>> transformTo(ServletContext servletContext, List<File> fileList) {List<Class<?>> classes = new ArrayList<>();for (File file : fileList) {//D:\Desktop\code\apache-tomcat-8.0.49\webapps\ROOT\WEB-INF\classes\cn\cnmd\controller\UserController.classString classPath = file.getAbsolutePath();String absolutePath = servletContext.getRealPath("WEB-INF" + File.separator + "classes");//cn\cnmd\controller\UserController.class --> cn.cnmd.controller.UserControllerclassPath = classPath.substring(absolutePath.length() + 1).split("\\.")[0].replace("\\", ".");System.out.println(classPath);Class<?> clazz = null;try {clazz = Class.forName(classPath);} catch (ClassNotFoundException e) {throw new RuntimeException(e);}classes.add(clazz);}return classes;}public void handleController(List<Class<?>> classes) {Map<String, BeanDefinition<?>> maps = BeanContainer.getMaps();for (Class<?> clazz : classes) {try {//父级url requestMappingPath --> requestMapping.value()RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class);String requestMappingPath1 = requestMapping.value();//类名 beanName --> clazz.getName()String beanName = clazz.getName();//controller的类对象 t --> clazz.newInstance()Object t = clazz.newInstance();//方法描述对象Method[] methods = clazz.getMethods();for (Method method : methods) {method.setAccessible(true);if (!method.isAnnotationPresent(RequestMapping.class)) {continue;}RequestMapping requestMapping1 = method.getAnnotation(RequestMapping.class);//子级url requestMappingPath --> requestMapping1.value()String requestMappingPath2 = requestMapping1.value();//方法名 methodName --> method.getName()String methodName = method.getName();//方法对象 method --> method//返回值类型 returnType -->  method.getReturnType()Class<?> returnType = method.getReturnType();//参数描述对象Parameter[] parameters = method.getParameters();List<ParameterDefinition> parameterDefinitions = new ArrayList<>();for (int i = 0; i < parameters.length; i++) {Class<? extends Parameter> paramClass = parameters[i].getClass();//参数名 parameterName --> paramClass.getName()String parameterName = paramClass.getName();//参数类型 type --> paramClass//参数下标 index --> iParameterDefinition parameterDefinition = new ParameterDefinition(parameterName, paramClass, i);parameterDefinitions.add(parameterDefinition);}MethodDefinition methodDefinition = new MethodDefinition(requestMappingPath2, methodName, method, returnType, parameterDefinitions);BeanDefinition<?> beanDefinition = new BeanDefinition<>(requestMappingPath1, beanName, clazz, t, methodDefinition);String route = requestMappingPath1 + File.separator + requestMappingPath2;maps.put(route, beanDefinition);}} catch (InstantiationException | IllegalAccessException e) {throw new RuntimeException(e);}}}
      }
  6. cn.cnmd.shop.mvc.model:类描述类,包括参数描述类、方法描述类、类描述类

    1. cn.cnmd.shop.mvc.model.ParameterDefinition

      package cn.cnmd.shop.mvc.model;import com.sun.istack.internal.NotNull;
      import lombok.AllArgsConstructor;
      import lombok.Data;
      import lombok.NoArgsConstructor;/*** 参数描述类*/@NoArgsConstructor
      @AllArgsConstructor
      @Data
      public class ParameterDefinition {private String name;//参数名private Class<?> type;//参数类型private int index;//参数下标
      }
    2. cn.cnmd.shop.mvc.model.MethodDefinition

      package cn.cnmd.shop.mvc.model;import lombok.AllArgsConstructor;
      import lombok.Data;
      import lombok.NoArgsConstructor;import java.lang.reflect.Method;
      import java.util.List;@NoArgsConstructor
      @AllArgsConstructor
      @Data
      public class MethodDefinition {private String requestMappingPath;//子级urlprivate String methodName;//方法名private Method method;//方法对象private Class<?> returnType;//返回值类型private List<ParameterDefinition> parameters;//参数描述列表
      }
    3. cn.cnmd.shop.mvc.model.BeanDefinition

      package cn.cnmd.shop.mvc.model;import lombok.AllArgsConstructor;
      import lombok.Data;
      import lombok.NoArgsConstructor;@NoArgsConstructor
      @AllArgsConstructor
      @Data
      public class BeanDefinition<T> {private String requestMappingPath;//父级urlprivate String name;//类名private Class<?> beanClass;//controller类的class对象private T t;//controller类对象private MethodDefinition methodDefinition;//方法描述对象
      }
      
web工程
  1. cn.cnmd.config

    1. cn.cnmd.config.AppConfig

      package cn.cnmd.config;import cn.cnmd.shop.mvc.annotation.Configuration;@Configuration("cn.cnmd.controller")
      public class AppConfig {}
      
  2. cn.cnmd.controller:存放各种controller

    1. cn.cnmd.controller.UserController

      package cn.cnmd.controller;import cn.cnmd.pojo.User;
      import cn.cnmd.shop.mvc.annotation.Controller;
      import cn.cnmd.shop.mvc.annotation.RequestMapping;@Controller
      @RequestMapping("user")
      public class UserController {@RequestMapping("login1")public void login(String username, String password) {}@RequestMapping("login2")public void login(User user) {}public void method(String name){}
      }
      
    2. cn.cnmd.controller.back.AdminController

      package cn.cnmd.controller.back;import cn.cnmd.shop.mvc.annotation.Controller;
      import cn.cnmd.shop.mvc.annotation.RequestMapping;@Controller
      @RequestMapping("admin")
      public class AdminController {@RequestMapping("login")public void login(String name, String password){}
      }
  3. cn.cnmd.pojo:用于存放web项目中的JavaBean对象

    1. cn.cnmd.pojo.User

      package cn.cnmd.pojo;import lombok.AllArgsConstructor;
      import lombok.Data;
      import lombok.NoArgsConstructor;@NoArgsConstructor
      @AllArgsConstructor
      @Data
      public class User {String name;int age;String sex;
      }
      

ApplicationContextListener扫描过程(个人理解)

applicationContextListener扫描

roller
@RequestMapping(“admin”)
public class AdminController {

	    @RequestMapping("login")public void login(String name, String password){}}```
  1. cn.cnmd.pojo:用于存放web项目中的JavaBean对象

    1. cn.cnmd.pojo.User

      package cn.cnmd.pojo;import lombok.AllArgsConstructor;
      import lombok.Data;
      import lombok.NoArgsConstructor;@NoArgsConstructor
      @AllArgsConstructor
      @Data
      public class User {String name;int age;String sex;
      }
      

ApplicationContextListener扫描过程(个人理解)

[外链图片转存中…(img-wN91i1Ja-1719492618962)]

image-20240626191314663

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【python】OpenCV—Color Map

文章目录 cv2.applyColorMapcv2.putText小试牛刀自定义颜色 参考学习来自 OpenCV基础&#xff08;21&#xff09;使用 OpenCV 中的applyColorMap实现伪着色 cv2.applyColorMap cv2.applyColorMap() 是 OpenCV 中的一个函数&#xff0c;用于将灰度图像或单通道图像应用一个颜色…

The First Descendant卡顿怎么办?快速处理第一后裔卡顿

第一后裔/The First Descendant是一款刷宝类RPG射击游戏&#xff0c;以虚幻引擎5为基础&#xff0c;使其对决场景十分精彩、刺激&#xff0c;从而吸引了大批冒险者前往&#xff0c;这里揭开属于英格里斯和继承者的秘密。不过有很多玩家&#xff0c;反馈在对局时遇到了卡顿、画面…

Ubuntu 20.04.3 LTS 安装打印服务器 局域网发现系统服务 共享给 windows 10/11 使用

ubuntu安装部署打印服务器可参考: Ubuntu 20.04.3 LTS 安装hp 1020 plus 打印机 通过cups共享给 windows 10/11 使用-CSDN博客 1 windows 10 ,局域网搜索不到共享的hp1020打印机 2 Ubuntu使用Avahi进行局域网服务发现和设备发现&#xff0c;安装avahi-daemon sudo apt-updat…

【Linux】Linux下使用套接字进行网络编程

&#x1f525;博客主页&#xff1a; 我要成为C领域大神&#x1f3a5;系列专栏&#xff1a;【C核心编程】 【计算机网络】 【Linux编程】 【操作系统】 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 本博客致力于知识分享&#xff0c;与更多的人进行学习交流 ​ 用于网络应用开…

Java基础(四)——字符串、StringBuffer、StringBuilder、StringJoiner

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 ⚡开源项目&#xff1a; rich-vue3 &#xff08;基于 Vue3 TS Pinia Element Plus Spring全家桶 MySQL&#xff09; &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;正逐渐往全干发展 &#x1…

【技巧】如何检查多个GPU之间是否支持P2P通信

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 需要用到cuda_samples&#xff1a;GitHub - NVIDIA/cuda-samples 该工具的详细解释可以看这个&#xff1a; 【知识】详细介绍 CUDA Samples 示例工程…

不止是只有维度建模,数据仓库还有Data Vault建模

引言 在数据仓库设计中&#xff0c;传统的星型和雪花型模型有着各自的优势和劣势。随着数据量的增大和数据源的多样化&#xff0c;Data Vault&#xff08;数据仓库&#xff09;建模方法逐渐受到关注和应用。Data Vault建模是一种灵活、可扩展、适应性强的建模方法&#xff0c;…

Java知识点整理 12 — 前端 Ant Design Pro 初始化模板使用

一. 项目初始化 Ant Design Pro 是基于 Ant Design 和 umi 封装的一整套企业级中后台前端设计框架&#xff0c;致力于在设计规范和基本组件的基础上&#xff0c;继续向上构建&#xff0c;提炼出典型模板或配套设计资源&#xff0c;进一步提升企业级中后台产品设计研发过程中的…

用MySQL和navicatpremium做一个项目—(财务管理系统)。

1 ER图缩小的话怕你们看不清&#xff0c;所以截了两张图 2 vsdx绘图结果 3DDL和DML,都有点长分了好多次上传&#xff0c;慢慢看 DDL -- 用户表 CREATE TABLE users (user_id INT AUTO_INCREMENT PRIMARY KEY COMMENT 用户ID,username VARCHAR(50) NOT NULL UNIQUE COMMENT 用…

SpringCloud分布式微服务链路追踪方案:Skywalking

一、引言 随着微服务架构的广泛应用&#xff0c;系统的复杂性也随之增加。在这种复杂的系统中&#xff0c;应用通常由多个相互独立的服务组成&#xff0c;每个服务可能分布在不同的主机上。微服务架构虽然提高了系统的灵活性和可扩展性&#xff0c;但也带来了新的挑战&#xf…

vue的学习--day2

如有错误&#xff0c;烦请指正~ 目录 一、什么是单页面应用程序 二、使用工具&#xff1a;node.js 三、工具链 易错点 一、什么是单页面应用程序 多个组件&#xff08;例如登录、注册等以vue结尾的都叫做组件&#xff09;在一个页面显示&#xff0c;叫单页面应用…

【软件测试】白盒测试与接口测试详解

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 一、什么是白盒测试 白盒测试是一种测试策略&#xff0c;这种策略允许我们检查程序的内部结构&a…

Vite: Esbuild的使用与其插件开发

概述 作为 Vite 的双引擎之一&#xff0c;Esbuild 在很多关键的构建阶段(如 依赖预编译 、 TS 语法转译 、 代码压缩 ) 让 Vite 获得了相当优异的性能&#xff0c;是 Vite 高性能的得力助手无论是在 Vite 的配置项还是源码实现中&#xff0c;都包含了不少 Esbuild 本身的基本概…

你知道大数据信用分低需要如何改善吗?

在当今社会&#xff0c;大数据信用分已经成为个人信用评估的重要指标之一。然而&#xff0c;有时候我们会发现自己的大数据信用分较低&#xff0c;这可能会对我们的信用状况产生负面影响。那么&#xff0c;如何改善自己的大数据信用分呢?本文将从信用分低的原因进行分析&#…

【Micro-ROS学习】

Micro-ROS 是专为 ROS 2 设计的&#xff0c;它允许在微控制器&#xff08;microcontrollers&#xff09;上实现ROS 2的功能。Micro-ROS 从 ROS 2 架构优化而来&#xff0c;目的是让那些资源有限的嵌入式设备也能够接入ROS 2生态系统&#xff0c;享受ROS 2带来的标准化通信、模块…

java简易计算器(多种方法)

parseDouble() 方法属于 java.lang.Double 类。它接收一个字符串参数&#xff0c;其中包含要转换的数字表示。如果字符串表示一个有效的 double&#xff0c;它将返回一个 double 值。 应用场景 parseDouble() 方法在以下场景中非常有用&#xff1a; 从用户输入中获取数字&a…

国内AI行业对GPU算力的需求有多大?

随着人工智能&#xff08;AI&#xff09;技术的飞速发展&#xff0c;算力作为支撑其持续进步的核心动力&#xff0c;在国内的重要性日益凸显&#xff0c;无论是海外还是国内&#xff0c;AI算力行业都呈现出蓬勃发展的态势&#xff0c;而国内对于AI算力的需求更是呈现出爆发式的…

离散傅里叶变化

傅里叶变换 对傅里叶变换了解不是很清楚的朋友推荐一下这个帖子&#xff0c;讲得很详细 傅里叶变换 源码 先看源码链接 #include "opencv2/core.hpp" #include "opencv2/imgproc.hpp" #include "opencv2/imgcodecs.hpp" #include "open…

从零开始了解GPT-4o模型:它是如何工作的?

人工智能&#xff08;AI&#xff09;技术正以惊人的速度发展&#xff0c;其中最引人注目的是OpenAI发布的GPT-4o模型。作为GPT系列的新成员&#xff0c;GPT-4o在多模态输入处理和响应速度上取得了重大进展。本文将深入探讨GPT-4o的工作原理&#xff0c;帮助您全面了解这一尖端A…

MySQL实训

项目名称与项目简介 股票交易系统是一个综合性的金融服务平台&#xff0c;它提供了股票买卖、交易查询、用户管理、股票信息管理以及资金账户管理等功能。系统旨在为用户提供一个安全、高效、便捷的股票交易环境&#xff0c;让用户能够实时掌握市场动态&#xff0c;做出合理的…