• [ SSH框架 ] Struts2框架学习之一


    一、Struts2框架的概述

      Struts2是一种基于MVC模式的轻量级Web框架,它自问世以来,就受到了广大Web开发者的关注,并广泛应用于各种企业系统的开发中。目前掌握 Struts2框架几乎成为Wcb开发者的必备技能之一。

      接下来将针对 Struts2的特点、安装以及执行流程等内容进行详细的讲解。

    1.1 什么是Struts2

      在介绍 Struts2之前,先来认识一下 Struts1。Struts1是最早的基于MVC模式的轻量级Web框架,它能够合理的划分代码结构,并包含验证框架、国际化框架等多种实用工具框架。但是随着技术的进步,Struts1的局限性也越来越多的暴露出来。为了符合更加灵活、高效的开发需求,Struts2框架应运而生。
      Struts2是 Struts1的下一代产品,是在 Struts1和 WebWork技术的基础上进行合并后的全新框架( WebWork是由 OpenSymphony组织开发的,致力力于组件化和代码重用的J2 EE Web框架,它也是一个MVC框架)。虽然 Struts2的名字与 Struts1相似,但其设计思想却有很大不同。实质上,Struts2是以 WebWork为核心的,它采用拦截器的机制来处理用户的请求。这样的设计也使得业务逻辑控制器能够与 ServletAPI完全脱离开,所以 Struts2可以理解为 WebWork的更新产品。
    Struts2拥有优良的设计和功能,其优势具体如下:
      ●  项目开源,使用及拓展方便,天生优势。
      ●  提供 Exception处理机制。
      ●  Result方式的页面导航,通过 Result标签很方便的实现重定向和页面跳转。
      ●  通过简单、集中的配置来调度业务类,使得配置和修改都非常容易。
      ●  提供简单、统一的表达式语言来访问所有可供访问的数据。
      ●  提供标准、强大的验证框架和国际化框架。
      ●  提提供强大的、可以有效减少页面代码的标签。
      ●  提供良好的Ajax支持。
      ●  拥有简单的插件,只需放入相应的JAR包,任何人都可以扩展 Struts2框架,比如自定义拦截器。
      ●  自定义结果类型、自定义标签等,为 Struts2定制制需要的功能,不需要什么特殊配置,并且可以发布给其他人使用。
      ●  拥有智能的默认设置,不需要另外进行繁琐的设置。使用默认设置就可以完成大多数项目程序开发所需要的功能。
      上面列举的就是 Struts2的一系列技术优势,只需对它们简单了解即可,在学习了后面的知识后,会慢慢对这些技术优势有更好的理解和体会。
      那么除了 Struts2之外,还有那些优秀的WEB层框架呢?

    1.2 常见的WEB层框架
      ●  Struts2
      ●  Struts1
      ●  WebWork
      ●  SpringMVC
      WEB层框架都有一个特点:基于前端控制器模式实现。
     
    1.3 WEB层框架都会基于前端控制器的模式
      什么是前端控制器模式呢?我们来看下图,在图中传统方式的开发,有一次请求就会对应一个Servlet。这样会导致出现很多 Servlet。而 Struts2将所有的请求都先经过一个前端控制器,在前端控制器中实现框架的部分功能,剩下具体操作要提交到具体的 Action中。那么所有的请求都会经过前端控制器,那用什么来实现前端控制器呢?过滤器就是最好的一个实现方式,因为需要所有的请求都可以被过滤器拦截,然后在过滤器中实现部分的功能。所以 Struts2的前端控制器也是有过滤器来实现的。
       

      了解了这些之后,让我们通过一个Struts2的快速入门,来感受一下Struts2。

    二、Struts2快速入门

    2.1 下载Struts2的开发包:

    百度网盘链接:https://pan.baidu.com/s/1HeQLGcDaXdskUayFI-vGWw   密码:qyxp

    2.2 解压Struts2的开发包

    解压后的目录结构如下图:

      

    从图中可以看出,展示的是解压后的 Struts2.3.24 的目录结构,为了让大家对每个目录的内容和作用有一定的了解,接下来针对这些目录进行简单介绍,具体如下:
      ●  apps:该文件夹存用于存放官方提供的 Struts2示例程序,这些程序可以作为学习者的学习资料,可为学习者提供很好的参照。各示例均为war文件,可以通过zip方式进行解压。
      ●  docs:该文件夹用于存放官方提供的 Struts2文档,包括 Struts2的快速入门、 Struts2的文档,以及API文档等内容
      ●  lib:该文件夹用于存放 Struts2的核心类库,以及 Struts2的第三方插件类库。
      ●  src:该文件夹用于存放该版本 Struts2框架对应的源代码
    有了 Struts2的开发环境,接下来我们亻可以进行 Struts2的开发了。
     
    2.3 创建一个web工程引入jar包
      首先,需要我们创建一个WEB工程,引入相关的jar包文件。引入哪些jar包呢?将 struts-2.3.24框架目录中的lib文件夹打开,得到 Struts2开发中可能用到的所有JAR包(此版本有107个JAR包)。实际的开发中,我们们根本不用引入这么多的jar包。因此要进行 struts2的基本的开发,可以参考 struts-2.3.24中的aps下的一些示例代码,其中 struts2-blank.war 是一个 struts2的空的工程。我们只需要将 struts2-blank.war 解压后进入到WEB-INF下的的lib中査看。 如下图:
      

      以上这些就是struts2基本的开发包了,那么这些包都有什么含义呢?说明如下图:

       

      从表可以看出,此版本的 Struts2项目所依赖的基础JAR包共13个。 Struts2根据据版本的不同所依赖的基础JAR包可能不完全相同,不过基本上变化不大,读者可以视情况而定。
      需要注意的是,通通常使用 Struts2的Web项目并不需要利用到 Struts2的全部JAR包,因此没有必要一次将 Struts2的lib目录下的全部JAR包复制到Web项目的WEB-INF/ib路径下,而是根据需要,再添加加相应的JAR包。
      那么 Struts2的基本jar包已经引入完成了,我们使用用 Struts2都是是从页面发起请求到服务器,再由服务器处理请求,响应到页面的这个过程。接下来我们就从页面开发进行 Struts2的开发吧。

    2.4 创建一个页面,放置一个连接

     首先在WebContent下创建一个jsp文件,在jsp文件中编写一个Action的访问路径。如下图

        <<h1>Struts2的入门案例</h1>
        <a herf="${pageContext.request.contextPath }/HelloAction.action">访问Struts2的Action</a> 

    2.5 编写一个Action

      在src下创建一个包 com.Kevin.action,在该包下新建一个 HelloAction 的类。在这个类中编写一个公有的,返回值为 String类型的方法,这个方法的名称叫做 execute,且该方法没有任何参数。(因为这个方法最终要被反射执行) 如下图:
    package com.Kevin.action;
    
    public class HelloAction {
        
        /**
         * 提供一个默认的执行的方法:execute
         * @return
         */
        public String execute(){
            System.out.println("HelloAction中的execute方法执行了------");
            return null;
        }
    }

      Action类编写好了以后,Struts2框架是如何识别它就是一个Action呢,我们需要对Action类进行配置。

    2.6 完成Action配置

       这个时候,我们还需要观察apps中的示例代码,在WEB-INF的 classes中,有一个名称为 struts.xml 的文件,这个文件就是 struts2的配置文件。

      我们在开发中需要将 struts xml文件引入到工程的src下,因为src下内内容发布到web服务器中就是WEB-INF下的 classes中。将 struts.xml中的原有的内容删除掉,然后配置上自己编写的 Action类就可以了。

      配置内容如下:里面的具体的标签,会在后面的地方详细介绍。

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE struts PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
        "http://struts.apache.org/dtds/struts-2.3.dtd">
        
    <struts>
        <package name="hellodemo" extends="struts-default" namespace="/">
            <!-- name:访问名称 class:action位置 -->
            <action name="HelloAction" class="com.Kevin.action.HelloAction">
    
            </action>
        </package>
    
    </struts>    

       Action类已经配置好了,配置好了以后大家考虑一下,现在是否可以执行呢?其实现在还不行,因为之前我们介绍过,WEB层的框架都有一个特点就是基于前端控制器的模式,这个前端控制器是由过滤器实现的,所所以我们需要配置 Struts2的核心过滤器。这个过滤器的名称称是 StrutsPrepareAndExecuteFilter 。


    2.7 配置核心过滤器
      Struts2框架要想执行,所有的请求都需要经过这个前端控制器(核心过滤器),所以需要配置这个核心过滤器。因为这个过滤器完成了框架的部分的功能。那么我们接下来对过滤器进行配置。我们打开web.xml,在web.xml中进行如下配置。
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app id="WebApp_9" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
    
        <filter>
            <filter-name>struts2</filter-name>
            <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
        </filter>
    
        <filter-mapping>
            <filter-name>struts2</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
    
    </web-app>

      那么到这,我们程序就可以执行了,但是到了 Action以后,页面并没有跳转,只是会在控制台输出 Action中的内容,往往我们在实际的开发中,处理了请求以后,还需要进行页面的跳转,如何完成 Struts2的页面的跳转呢?
      这个时候我们需要修改改 Action类中的 execute方法法的返回值了,这个方法返回了一个 String类型,这个 String类型的值就是一个逻辑视图(逻辑视图:相相当于对一个真实的页面,取了一个别名)。那么我们来修改一个 Action类。

    2.8 修改Action,将方法设置一个返回值

      修改Action中的execute方法的返回值,我们先任意给其返回一个字符串,比如返回一个 ok 的字符串。这个字符串就作为一个逻辑视图的名称。

    package com.Kevin.action;
    
    public class HelloAction {
        
        /**
         * 提供一个默认的执行的方法:execute
         * @return
         */
        public String execute(){
            System.out.println("HelloAction中的execute方法执行了------");
            return "ok";
        }
    }

      返回一个 ok 的字符串了,这个字符串如何代表一个页面呢?我们就需要对struts2进行配置了。这个时候需要修改struts.xml,对Action的配置进行完善。

    2.9 修改struts.xml

      打开struts.xml 文件,对action标签进行完善,在action标签中配置一个result标签,这个标签中的name属性就是之前方法返回的那个字符串的逻辑视图名称ok 。标签内部就是跳转的页面。 

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE struts PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
        "http://struts.apache.org/dtds/struts-2.3.dtd">
        
    <struts>
        <package name="hellodemo" extends="struts-default" namespace="/">
            <!-- name:访问名称 class:action位置 -->
            <action name="HelloAction" class="com.Kevin.action.HelloAction">
                <!-- 配置方法的返回值到页面 -->
                <result name="ok">/hello.jsp</result>
            </action>
        </package>
    
    </struts>    

      到这里,我们的整个程序就执行完毕了,可以启动服务器并且测试项目。

    打开页面:

    页面跳转:

     

    三、Struts2开发流程分析

    3.1 Struts2的执行流程

      从客户端发送请求过来先经过前端控制器(核心过滤器 StrutsPreparedAndExecuteFilter )过滤器中执行一组拦截器(一组拦截器就会完成部分功能代码码),到底哪些拦截器执行了呢,在 Struts2中定义很多拦截器,在其默认栈中的拦截器会得到执行,这个我们可以通过断点调试的方式测试,拦截器执行完成以后,就会执行目标 Action,在 Action中返回一个结果视图,根据 Result的配置进行页面的跳转。

     四、Struts2的常见配置

    4.1 Action的配置

       Struts2框架的核心配置文件是 struts.xml文件,该文件主要用来配置 Action和请求的对应关系。
    【< package>的配置】
      Struts2框架架的核心组件是 Action和拦截器,它使用包来管理 Action和拦截器。每个包就是多个Action、多个拦截器、多个拦截器引用的集合。在 struts.xml文件中,package元素用于定义包配置每个 package元素定义了一个包配置。 package元素的常用属性,如表所示。
      
      表中就是 package元素的常用属性,其中,在配置包时,必须指定name属性,就是包的标识。除此之外,还可以指定一个可选的 extends属性,extends属性值必须是另一个包的name属性值,但该属性值通常都设置为 struts-default,这样该包中的 Action就具有了 Struts2框架默认的拦截器等功能了。除此之外外,Struts2还提供了一种所谓的抽象包,抽象包不能包含 Action定义。为为了显式指定个包是抽象包,可以为该 package元素增增加 abstract="true"属性。
      在 package中还有 namespace的配置,namespace属性与 action标签的name属性共同决定了访问路径。namespace有如下三种配置:
      ●  默认名称空间:默认的名称空间就是 namespace=""
      ●  跟名称空间:跟名称空间就是 namespace="/"
      ●  带名称的名称空间:带名称的名称空间就是 namespace="/demo1"
    【 Action的配置】
      Action映射是框架中的基本“工作单元”。 Action映射就是将一个请求的URL映射到一个 Action类,当一个请求匹配某个 Action名称时,框架就使用这个映射来确定如何处理请求。在 struts.xml文件中,通过< action>元素对请求的 Action和 Action类进行配置< action>元素中共有4个属性,这4个属性的说明如表所示。
      
      其中name属性和 namespace属性共同决定了访问路径,class对应的是 Action类的全路径。Method指定了执行 Action的那个方法,默认是 execute方法。
      基本的 Struts2的配置我们己经了解了,在实际的开发中我们需要大量的用到到 Struts2的常量,那么我们接下来学习一下 Struts2的常量。

    4.2 模块开发的配置
      在实际开发中,我们通常很多人都需要修改同一个配置文件就是 struts.xml。因为这个文件是Struts2的核心配置文件。而且这个文件一且改错了一点,那么会导致整个项目都会出现问题,所以Struts2提供了< include>标签解决这个问题。
      < include>元素用来在一个 struts.xml 配置文件中包含其他的配置文件,包含配置体现的是软件工程中的“分而治之”原则。 Struts2允许将一个配置文件分解成多个配置文件,从而提高配置文件的可读性。 Struts2默认只加载WEB-INF/ classes下的 struts.xml文件,但一且通过多个xml文件来配置Action,就必须通过 struts.xml文件来包含其他配置文件
      为了让大家更直观地理解如何在 struts.xml文件中进行包含配置,接下来通过一段示例代码来说明,具体如下
    <struts>    
            <!-- 包含了4个配置文件 -->
        <!-- 不指定路径,默认路径在src下的方式 -->
        <include file="struts-user.xml"></include>
        <include file="struts-customer.xml"></include>
        <include file="struts-book.xml"></include>
        <!-- 配置文件在具体包中时的方式 -->
        <include file="com.Kevin.action.hello.xml"></include>
    </struts>    
      在上述代码片段中,struts.xml文件包含了4个配置文件,这4个配置文件都包含在 <include>元素中。配置< include>元素时,指定了一个必需的file属性,该属性指定了被包含配置文件的文件名。上述 include元素的file属性中,前3个没有指定文件所在路径时,表示该文件在项目的src路径下,如果配置文件在具体的包中,那么引入配置文件时,需要包含文件所在包的路径。
      需要注意的是,每一个被包含的配置文件都是标准的 Struts2配置文件,一样包含DTD信息Struts2配置文件的根元素等信息。通过将 Struts2的所有配置文件都放在Web项目的WEB- INF/classes路径下, struts.xml文件包含了其他的配置文件,在 Struts2框架自动加载 struts.xml文件时,完成加载所有的配置信息。
      Action的基本配置以及熟悉了,那么通过配置 Struts2框架就可以找到具体的 Action类了,那么Action类又要如何编写呢?接下来学习 Action类的编写的方式。

    五、Struts2的Action访问
      在 Struts2的应用开发中,Action作为框架的核心类,实现对用户请求的处理,Action类被称为业务逻辑控制器。一个 Action类代表一次请求或调用,每个请求的动作都对应于一个相应的 Action类,一个 Action类是一个独立的工作单元。也就是说,用户的每次请求,都会转到一个相应的 Action类里面,由这个 Action类来进行处理。简而而言之,,Action就是用来处理一次用户请求的对象。
      实现 Action控制类共有3种方式,接下来,分别别对它们进行讲解,具体如下:

    5.1 Action的编写方式
    【 Action的是一个POJO的类】
      在 Struts2中,Action可以不继承特殊的类或不实现任何特殊的接口,仅仅是一个POJO。POJO全称 Plain Ordinary Java Object(简单的Java对象),只要具有一部分 getter/ setter方法的那种类,就可以称作POJO。一般在这个POJO类中,要有一个公共的无参的构造方法(采用默认的构造方法就可以)和一个 execute方法。定义格式如下:
    package com.Kevin.action;
    
    public class HelloAction {
        
        /**
         * 提供一个默认的执行的方法:execute
         * @return
         */
        public String execute(){
            System.out.println("HelloAction中的execute方法执行了------");
            return "ok";
        }
    }
    execute( )方法的要求如下:
      ●  方法的权限修饰符为 public。
      ●  返回一个字符串,就是指示的下一个页面的 Result。
      ●  方法没有参数。
    也就是说,满足上述要求的POJO都可算作是 Struts2的 Action实现。
    通常会让开发者自己编写 Action类或者者实现 Action接口或者继承 Actionsupport类。
    【 Action类实现一个 Action的接口】
      为了让用户开发的 Action类更规范,Struts2提供一个 Action接口,用户在实现 Action控制制类时,可以实现 Struts2提提供的这个 Action接口。
    Action接口定义了 Struts的的 Action处理理类应该实现的规范,Action接口中的具体代码如下所示。
    package com.Kevin.action;
    import com.opensymphony.xwork2.Action;
    public class UserAction implements Action{ @Override public String execute() throws Exception { System.out.println("实现了Action接口----------"); return NONE; } }
      从上述代码中可以看出,Action接接口位于 com.opensymphony.xwork2包中。这个接口里只定义了一个execute( )方法,该接口的规范规定了 Action处理类应该包含一个 execute方法,该方法返返回一个字符串。除此之外,该接口还定义了5个字符串常量,它们的作用是统一 execute( )方法的返回值。
    Action接口中提供了5个已经定义的常量如下:
      ●  SUCCESS:success,代表成功。
      ●  NONE:none,代表页面不跳转。
      ●  ERROR:error,代表跳转到到错误页面。
      ●  INPUT:input,数据校验的时候跳转的路径。
      ●  LOGIN:login,用来跳转到登录页面。
    由于 Xwork的 Action接口简单,为开发者提供的帮助较小,所以在实际开发过程中,Action类很少直接实现Action接口,通常都是从ActionSupport类继承。
    【Action类继承ActionSupport类】(推荐)
    package com.Kevin.action;
    
    import com.opensymphony.xwork2.ActionSupport;
    
    public class PersonAction extends ActionSupport{
        
        public String execute(){
            System.out.println("继承ActionSupport类----------");
            return NONE;
        }
    
    }
      ActionSupport 类本身实现了 Action接口,是 Struts2中默认的 Action接口的实现类,所以继承ActionSupport 就相当于实现了 Action接口。 ActionSupport 类还实现了 Validateable、ValidationawareTextprovider、Localeprovider、和 Serializable等接口,来为用户提供更多的功能。
      ActionSupport 类中提供了许多默认方法,这些默认方法包括获取国际化信息的方法、数据校验的方法、默认的处理用户请求的方法等。实际上,ActionSupport 类是 Struts2默认的 Action处理类。如果让开发者的 Action类继承该 ActionSupport 类,则会大大简化 Action的开发。
      Action的类已经会编写了,如果要执行这个 Action,可以通过前面的配置来完成,但是之前的方式有一个缺点,就是多次请求不能对应同一个 Action,因为实际的开发中,一个模块的请求通常由一个 Action类处理就好了,否则则会造成 Action类过多。那么接下来我们讲一下 Action访问的一些细节。
     
    5.2 Action的访问
      Action的访问不是难题,因为之前已经访问过了,但是出现一个问题。一次请求现在对应一个Action,那么如果请求很多对应很多个 Action。现在要处理的问题就是要让一个模块的操作提交到一个 Action中。
      其实我们学过在< action>的标签中有一个属性 method,通通过 method的配置来指定 Action中的某个方法执行。
    【解决 Action的访问的问题的方式一:通通过配置 method属性完成】

    编写测试代码如下:

    BookAction类:

    package com.Kevin.method;
    
    import com.opensymphony.xwork2.ActionSupport;
    
    public class BookAction extends ActionSupport{
        
        public String add(){
            System.out.println("add----");
            return NONE;
            
        }
        
        public String update(){
            System.out.println("update-----");
            return NONE;
        }
    
    }
    struts.xml中配置:
        <!-- 配置method方法访问 -->
        <package name="methoddemo" extends="struts-default" namespace="/">
            <action name="addAction" class="com.Kevin.method.BookAction" method="add"></action>
            <action name="updateAction" class="com.Kevin.method.BookAction" method="update"></action>
        </package>
    但是这种方式我们会发现,同一个 Action类就被配置了很多次,只是修改了后面的 method的值。那么能不能配置简单化呢?也就是一个 Action类,只配置一次就好了?这个时候我们就需要使用通配符的配置方式了。
    【解决 Action的访问的问题的方式二:通过通配符的配置完成】
    编写测试代码如下:
    BookAction类:
    package com.Kevin.method;
    
    import com.opensymphony.xwork2.ActionSupport;
    
    public class BookAction extends ActionSupport{
        
        public String add(){
            System.out.println("add----");
            return NONE;
            
        }
        
        public String update(){
            System.out.println("update-----");
            return NONE;
        }
    
    }
    struts.xml中配置:
        <!-- 通配符方式实现 -->
        <package name="methoddemo2" extends="struts-default" namespace="/">
            <!-- 
                name属性值里加符号 *
                1.执行action中的add方法,访问book_add
                2.执行actoin中的update方法,访问book_update
                上述两个路径使用book_* 就可匹配到
             -->
            <action name="book_*" class="com.Kevin.method.BookAction" method="{1}"></action>
        </package>
    【解决 Action的访问的问题的方式三:动态方法访问】(一般不用)
      动态方法访问在 Struts2中默认是不开启的,如果想要使用需要先去开启一个常量。
    <constant name="struts.enable.DynamicMethodInvocation" value=true"></constant>
      动态方法访问主要的控制是在页面端,所以编写 Action和和配置 Action都很简单,关键是访问路径的编写。 由于不经常使用,笔者在这里就不做过多介绍,有兴趣的朋友可自行查阅。
     

    六、简单区别Struts1和Struts2

    1.从 action类上分析
      ●  Struts1要求 Action类继承一个抽象基类。Struts1的一个普遍问题是使用抽象类编程而不是接口。
      ●  Struts2 Action类可以实现一个 Action接口,也可实现其他接口,使可选和定制的服务成为可能。 Struts2提供一个 Actionsupport基类去实现常用的接接口。 Action接口不是必须的,任何有 execute标识的POJO对象都可以用作 Struts2的 Action对象。
     
    2.从 Servlet依赖分析
      ●  Struts1 Action依赖于 Servlet Apl,因为当个 Action被调用时 Http Servletrequest和Http Servletresponse被传递给 execute方法
      ●  Struts2 Action不依赖于容容器,允许Action脱离容器单独被测试。如如果需要Struts2 Action仍仍然可以访问初始的 request和 response。但是,其他的元素减少或者消除了直接访问 Http Servetrequest和HttpServletresponsel的必要性。
    3.从 action线程模式分析
      ●  Struts1 Action是单例模式并且必须是线程安全的,因为仅有 Action的一个实例来处理所有的请求。单例策略限制了 Struts1 Actior能作的事,并且要在开发时特别小心。Action资源必须是线程安全的或同步的。
      ●  Struts2 Action对象为每一个请求产生一个实例,因此没有线程安全问题。(实际上servlet容器给每个请求产生许多可丢弃的对象,并且不会导致性能和垃圾回收问题)
  • 相关阅读:
    202011.19
    202011.18
    202011.17
    202011.16
    202011.14
    jdk的下载和配置
    layui中form表单
    JS中utocomplete
    转:JqueryUI学习笔记-自动完成autocomplete
    JSON.parse()与JSON.stringify()的区别
  • 原文地址:https://www.cnblogs.com/Kevin-ZhangCG/p/9011032.html
Copyright © 2020-2023  润新知