• Spring的学习


    IoC是什么?

    ioc通过文件配置来实现对象的创建,以及对象的赋值和注入。

       

    IoC的依赖

    ioc所依赖的包

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    32

    33

    34

    35

    36

    37

    38

    39

    40

    41

    42

    <?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.wiggin</groupId>

    <artifactId>aispring</artifactId>

    <version>1.0-SNAPSHOT</version>

    <packaging>jar</packaging>

    <repositories>

    <repository>

    <id>huaweicloud</id>

    <name>huaweicloud</name>

    <url>>https://mirrors.huaweicloud.com/repository/maven/</url>

    </repository>

    </repositories>

    <dependencies>

    <dependency>

    <groupId>org.springframework</groupId>

    <artifactId>spring-context</artifactId>

    <version>5.0.7.RELEASE</version>

    </dependency>

    <!-- 简化实体类代码开发-->

    <dependency>

    <groupId>org.projectlombok</groupId>

    <artifactId>lombok</artifactId>

    <version>1.18.0</version>

    <scope>provided</scope>

    </dependency>

       

    <dependency>

    <groupId>dom4j</groupId>

    <artifactId>dom4j</artifactId>

    <version>1.1</version>

    </dependency>

    </dependencies>

       

       

       

       

    </project>

    @Data:控制数据输出成String类型,以及gettersetter函数

    @AllArgsConstructor:快速条件构造函数

    @NoArgsConstructor:添加无参构造

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    package Com.wiggin.entity;

    @Data

    @AllArgsConstructor

    @NoArgsConstructor

    public class Student {

    private Long id;

    private String name;

    private int age;

    private Address address;

       

    }

       

    配置文件

    1.通过配置Bean标签来完成对象的管理

    2.id:对象名

    3.class:对象的模板类。(交给ioc管理的类必须要有无参构造方法,spring底层通过反射机制来创建对象,调用的是无参构造)

    4.对象的成员变量通过property标签完成赋值

    5.name:成员变量名,value成员变量值(基本数据类型,String可以直接赋值,引用类型通过ref注入)

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    <?xml version="1.0" encoding="UTF-8"?>

    <beans xmlns="http://www.springframework.org/schema/beans"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="student" class="com.wiggin.entity.Student">

    <property name="id" value="1"></property>

    <property name="name" value="wiggin"></property>

    <property name="age" value="22"></property>

    <property name="address" ref="address"></property>

    </bean>

    <bean id="address" class="com.wiggin.entity.Address">

    <property name="id" value="2"></property>

    <property name="name" value="vivian"></property>

    </bean>

    </beans>

       

       

    IOC的底层原理

    1.读取配置文件,解析XML

    2.通过反射机制,实例化配置文件中所配置文件中所有的Bean

       

    通过运行时类获取bean

    1

    2

    3

    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");

    Student student = (Student) applicationContext.getBean("student");

    System.out.println(student);

    这种方式存在一个问题,配置文件中一个数据类型的对象只能有一个实例。否则会抛出异常,因为没有唯一的bean

       

       

    通过有参构造来创建bean

    1

    2

    3

    4

    5

    6

    <bean id="student3" class="com.wiggin.entity.Student">

    <constructor-arg name="id" value="3"></constructor-arg>

    <constructor-arg name="name" value="bob"></constructor-arg>

    <constructor-arg name="age" value="18"></constructor-arg>

    <constructor-arg name="address" ref="address"></constructor-arg>

    </bean>

    其中name可以实省略,但是可以通过下标来确定位置,但顺序必须与成员变量顺序一致

    1

    2

    3

    4

    5

    6

    <bean id="student3" class="com.wiggin.entity.Student">

    <constructor-arg index="0" value="3"></constructor-arg>

    <constructor-arg index="1" value="bob"></constructor-arg>

    <constructor-arg index="2" value="18"></constructor-arg>

    <constructor-arg index="3" ref="address"></constructor-arg>

    </bean>

       

    给bean注入集合

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    <bean id="student" class="com.wiggin.entity.Student">

    <property name="id" value="1"></property>

    <property name="name" value="wiggin"></property>

    <property name="age" value="22"></property>

    <property name="addresses">

    <list>

    <ref bean="address"></ref>

    <ref bean="address2"></ref>

    </list>

    </property>

    </bean>

    <bean id="address" class="com.wiggin.entity.Address">

    <property name="id" value="1"></property>

    <property name="name" value="科技路"></property>

    </bean>

    <bean id="address2" class="com.wiggin.entity.Address">

    <property name="id" value="2"></property>

    <property name="name" value="高新区"></property>

    </bean>

       

    Scope作用域

    Spring管理bean是根据scope来生成的,表示bean的作用域,共有4种,默认是单例模式

    1)singleton:单例,表示通过Spring容器获取的bean是唯一的,

    2)prototype:原型,表示通过Spring容器获取的bean是不同的

    3)request:请求,表示一次Http请求内有效

    4)session:回话,表示在一个用户会话内有效

    requestsession是适用于web项目,大多数使用单例和原型。

    prototype模式当业务代码获取Ioc容器中的bean时(getbean),Spring才去调用无参构造来创建对应的bean。创建一个bean开辟一个空间,避免bean的利用率低的问题,但是也浪费了地址空间。

    1

    2

    3

    <bean id="student2" class="com.wiggin.entity.Student" scope="prototype">

    <property name="name" value="vivian"></property>

    </bean>

    singleton模式当ioc容器创建时(new ClassPathXmlApplicationContext("applicationContext.xml"))时,bean已经被创造。多个bean指向同一个地址更加节省空间,但是bean在被创建后的利用率低。

       

    Spring的继承

    与java的继承不同,java是类层面的继承,子类可以继承父类的父类的内部结构信息,Spring是对象层面的继承,子对象可以继承父对象的属性值。

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    <bean id="student2" class="com.wiggin.entity.Student">

    <property name="name" value="vivian"></property>

    <property name="id" value="2"></property>

    <property name="age" value="22"></property>

    <property name="addresses">

    <list>

    <ref bean="address"></ref>

    <ref bean="address2"></ref>

    </list>

    </property>

    </bean>

    <bean id="stu" class="com.wiggin.entity.Student" parent="student2">

    <property name="name" value="wzxsb"></property>

    </bean>

       

    Spring的继承的关注点在于具体的对象,而不在于类,即不同的两个类的实例化对象可以完成继承,前提是子对象的属于一定是包括父对象的属性。

    1

    <bean id="user" class="com.wiggin.entity.User" parent="student2"></bean>

       

       

    Spring的依赖

    与继承类似,依赖也是描述bean和bean之间的一种关系,配置依赖之后,被依赖的bean一定要先被创建。

    1

    2

    3

    <bean id="student" class="com.wiggin.entity.Student" ></bean>

       

    <bean id="user" class="com.wiggin.entity.User" depends-on="student"></bean>

       

    Spring的p命名空间

    p命名空间实对ioc/DI的简化操作,使用p命名空间可以更加方便的完成bean的配置以及bean之间的依赖注入

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    <?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"

    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

       

       

    <bean id="student" class="com.wiggin.entity.Student" p:id="1" p:name="张三" p:age="22" p:address-ref="address"></bean>

    <bean id="address" class="com.wiggin.entity.Address" p:id="2" p:name="科技路"></bean>

    </beans>

       

    Spring的工厂方法

    ioC通过工厂模式创建bean的方式有两种:

    1)静态工厂方法

    2)实例工厂方法

    静态工厂方法:工厂不需要实例化,直接使用工厂方法

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    package com.wiggin.entity;

       

    import lombok.AllArgsConstructor;

    import lombok.Data;

    import lombok.NoArgsConstructor;

       

    @Data

    @AllArgsConstructor

    @NoArgsConstructor

    public class Car {

    private long id;

    private String name;

    }

    ------

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    package com.wiggin.factory;

       

    import com.wiggin.entity.Car;

       

    import java.util.HashMap;

    import java.util.Map;

       

    public class StaticFactory {

    private static Map<Long, Car> carMap; // Long id,Car是返回容器

    static{

    carMap = new HashMap<Long, Car>();

    carMap.put(1L,new Car(1L,"sf"));

    carMap.put(2L,new Car(2L,"sb"));

    }

       

    public static Car getCar(long id){

    // 根据id提取car信息

    return carMap.get(id);

    }

    }

    ------

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    package com.wiggin.test;

       

    import com.wiggin.entity.Car;

    import com.wiggin.factory.StaticFactory;

    import org.springframework.context.ApplicationContext;

    import org.springframework.context.support.ClassPathXmlApplicationContext;

       

    public class Test3 {

    public static void main(String[] args) {

    // 传统方法

    /*Car car = StaticFactory.getCar(1L);

    System.out.println(car);*/

    // ioc工厂模式

    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-factory.xml");

    Car car = (Car)applicationContext.getBean("car");

    System.out.println(car);

    }

    }

    ------

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    <?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"

    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

       

    <!--用到几个对象配置几个bean-->

    <bean id="car" class="com.wiggin.factory.StaticFactory" factory-method="getCar">

    <constructor-arg value="2"></constructor-arg>

    </bean>

    </beans>

    实例工厂方法:工厂需要实例化,且工厂方法要依赖工厂

    1

    2

    3

    4

    5

    6

    <!-- 配置实例工厂 bean -->

    <bean id="carFactory" class="com.wiggin.factory.InstanceCarFactory"></bean>

    <!-- 配置实例工厂创建 car -->

    <bean id="car2" factory-bean="carFactory" factory-method="getCar">

    <constructor-arg value="2"></constructor-arg>

    </bean>

       

    IoC自动装载(Autowire)

    ioC负责创建对象,DI负责完成对象的依赖注入,通过配置Property标签的ref属性来完成,同时Spring提供了另一种更加间接的依赖注入方法,即自动装载,不用手动配置property,IoC容器会自动选择bean完成注入。

       

    自动装载有两种方式:

    1)byname:通过属性名自动装载

    2)bytype:通过属性类型装载

    byName:通过属性名自动装载

    1

    2

    3

    4

    5

    6

    7

    8

    9

    <bean id="car" class="com.wiggin.entity.Car">

    <property name="id" value="1"></property>

    <property name="name" value="宝马"></property>

    </bean>

    <bean id="person" class="com.wiggin.entity.Person" autowire="byName">

    <property name="id" value="1"></property>

    <property name="name" value="wiggin"></property>

       

    </bean>

    byType:通过属性类型装载

    1

    2

    3

    4

    5

    <bean id="person2" class="com.wiggin.entity.Person" autowire="byType">

    <property name="id" value="1"></property>

    <property name="name" value="wiggin"></property>

       

    </bean>

       

    Spring的AOP

    AOP:Aspect Oriented Programming 面向切面编程

    AOP 优点:

    1. 降低模块之间的耦合度
    2. 使得系统更容易扩展
    3. 更好的代码服用
    4. 非业务代码更加集中,不分散,使于统一管理
    5. 业务代码更加简介纯粹,不掺杂其他代码的影响

    总结:AOP是对面向对象编程的补充,在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面编程,将不同方法的同一个位置抽象成为一个企鹅面对象,对该企鹅面对象进行编程,这就是AOP。

       

    如何使用?

    1)创建一个maven工程,pom.xml添加

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    <dependencies>

       

    <dependency>

    <groupId>org.springframework</groupId>

    <artifactId>spring-aop</artifactId>

    <version>5.0.7.RELEASE</version>

    </dependency>

    <dependency>

    <groupId>org.springframework</groupId>

    <artifactId>spring-aspects</artifactId>

    <version>5.0.7.RELEASE</version>

    </dependency>

    </dependencies>

    2)创建一个计算器接口Cal,定义四则运算。

    1

    2

    3

    4

    5

    6

    package com.wiggin.utils;

    public interface Cal {

    public int add(int num1,int num2);

    public int sub(int num1,int num2);

    public int mul(int num1,int num2);

    public int div(int num1,int num2);

    }

    3)创建接口的实现类impl

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    package com.wiggin.utils.impl;

    public class CalImpl implements Cal {

       

    public int add(int num1, int num2) {

    System.out.println("add方法参数是["+num1+","+num2+"]");

    int result = num1 + num2;

    System.out.println("add方法的结果是:"+result);

    return num1 + num2;

    }

       

    public int sub(int num1, int num2) {

    int result = num1 - num2;

    System.out.println("sub方法参数是["+num1+","+num2+"]");

    System.out.println("sub方法结果是:"+result);

    return result;

    }

       

    public int mul(int num1, int num2) {

    int result = num1 * num2;

    System.out.println("mul方法参数是["+num1+","+num2+"]");

    System.out.println("mul方法结果是:"+result);

    return result;

    }

       

    public int div(int num1, int num2) {

    int result = num1 / num2;

    System.out.println("div方法参数是["+num1+","+num2+"]");

    System.out.println("div方法结果是:"+result);

    return result;

    }

    }

    上述代码种,日志信息和业务逻辑的耦合性很高,不利于系统的维护,使用AOP可以进行优化,如何来实现AOP?实现动态代理方式来实现。

       

    给予业务代码找一个代理,打印日志信息的工作交给代理来做,这样的话业务代码就只需要关注自身的业务即可。

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    32

    33

    34

    35

    package com.wiggin.utils;

    public class MyInvocationHandler implements InvocationHandler {

       

    // 接收委托对象

    private Object object = null;

       

    // 返回代理对象(委托对象传入代理对象),用这个类创建动态代理类

    public Object bind(Object object){

    this.object = object;

    /*Proxy.newProxyInstance:返回代理实类,

    object.getClass()获得委托对象的运行实类,

    .getClassLoader()获取类加载器,生成动态实类

    object.getClass().getInterfaces()委托对象的功能代理类必须全部拥有,根据它创建功能一样的代理类

    this:通过当前类创建对象*/

    //

    return Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),this);

    }

       

    @Override

    // 打印日志结果

    /*

    method为代理类实现的方法

    method.invoke(this.object,objects)通过反射机制调用,this.object是委托对象,objects为传入参数

       

    */

    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {

    System.out.println(method.getName()+"方法参数是"+ Arrays.toString(objects));

    // 委托对象调用自己的方法

    Object result = method.invoke(this.object,objects);

    System.out.println(method.getName()+"方法的结果是:"+result);

    return result;

       

    }

       

       

    }

    以上是通过动态代理来实现AOP的过程,过程复杂,不好理解,Spring框架对AOP进行了封装,使用Spring框架可以用面向对象的思想来实现AOP。

    Spring框架种不需要创建InvocationHandler,只需要创建一个切面对象,将所有的非业务代码在切面对象种完成即可,Spring框架底层会自动根据切面类以及目标类生成代理对象。

       

    LoggerAspect类

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    package com.wiggin.aop;

    @Aspect // 成为切面对象,给予功能

    @Component // LoggerAspect对象在ioc里面配置

    public class LoggerAspect {

    // 传入实体类,切割实体类种的所有方法,在执行业务之前,*为方法的通配符,(..)为参数的通配符

    // @Before表示执行的具体时机

    @Before("execution(public int com.wiggin.utils.impl.CalImpl.*(..))")

    // JoinPoint joinPoint获得目标方法的相关信息

    public void before(JoinPoint joinPoint){

    // 获取方法joinPoint.getSignature(),方法名getName()

    String name = joinPoint.getSignature().getName();

       

    //获取参数joinPoint.getArgs()

    String args = Arrays.toString(joinPoint.getArgs());

    System.out.println(name + "方法的参数是:" + args);

       

       

       

    }

    }

    注意CaLimpl也需要@Component注解,交给IoC容器进行管理

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    package com.wiggin.utils.impl;

    @Component

    public class CalImpl implements Cal {

       

    public int add(int num1, int num2) {

       

    int result = num1 + num2;

       

    return result;

    }

       

    public int sub(int num1, int num2) {

    int result = num1 - num2;

       

    return result;

    }

       

    public int mul(int num1, int num2) {

    int result = num1 * num2;

       

    return result;

    }

       

    public int div(int num1, int num2) {

    int result = num1 / num2;

       

    return result;

    }

    }

    在Spring-aop.xml种配置AOP

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    <?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:aop="http://www.springframework.org/schema/aop"

    xmlns:context="http://www.springframework.org/schema/context"

    xmlns:tx="http://www.springframework.org/schema/tx"

    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd

    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd

    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd

    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

    <!-- 自动扫描 寻找component注解-->

    <context:component-scan base-package="com.wiggin"></context:component-scan>

       

    <!-- aspect注解生效,为目标类自动生成代理对象-->

    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

    </beans>

    loggerAspect实现更多业务功能

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    32

    33

    34

    35

    36

    37

    38

    39

    40

    41

    42

    package com.wiggin.aop;

    @Aspect // 成为切面对象,给予功能

    @Component // LoggerAspect对象在ioc里面配置

    public class LoggerAspect {

    // 传入实体类,切割实体类种的所有方法,在执行业务之前,*为方法的通配符,(..)为参数的通配符

    // @Before表示执行的具体时机

    @Before("execution(public int com.wiggin.utils.impl.CalImpl.*(..))")

    // JoinPoint joinPoint获得目标方法的相关信息

    public void before(JoinPoint joinPoint){

    // 获取方法joinPoint.getSignature(),方法名getName()

    String name = joinPoint.getSignature().getName();

       

    //获取参数joinPoint.getArgs()

    String args = Arrays.toString(joinPoint.getArgs());

    System.out.println(name + "方法的参数是:" + args);

       

       

       

       

    }

    @After("execution(public int com.wiggin.utils.impl.CalImpl.*(..))")

    public void after(JoinPoint joinPoint){

    // 获取方法joinPoint.getSignature(),方法名getName()

    String name = joinPoint.getSignature().getName();

    System.out.println(name + "方法执行了");

       

    }

    @AfterReturning(value = "execution(public int com.wiggin.utils.impl.CalImpl.*(..))",returning = "result")

    // result交给了 @AfterReturning,然后给了public void afterReturning

    public void afterReturning(JoinPoint joinPoint,Object result){

    // 获取方法名

    String name = joinPoint.getSignature().getName();

    System.out.println(name + "方法执行结果是:"+result);

    }

       

    @AfterThrowing(value = "execution(public int com.wiggin.utils.impl.CalImpl.*(..))",throwing = "expection")

    // result交给了 @AfterReturning,然后给了public void afterReturning

    public void afterThrowing(JoinPoint joinPoint,Exception expection){

    // 获取方法名

    String name = joinPoint.getSignature().getName();

    System.out.println(name + "方法执行异常:"+expection);

    }

    }

    IoC调用

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    package com.wiggin.test;

       

    import com.wiggin.utils.Cal;

    import com.wiggin.utils.MyInvocationHandler;

    import com.wiggin.utils.impl.CalImpl;

    import org.springframework.context.ApplicationContext;

    import org.springframework.context.support.ClassPathXmlApplicationContext;

       

    public class Test2 {

    public static void main(String[] args) {

    // 加载配置文件

    ApplicationContext applicationContext =new ClassPathXmlApplicationContext("Spring.xml");

       

    // 获取代理对象

    Cal proxy = (Cal) applicationContext.getBean("test");

    proxy.add(1,1);

    proxy.div(6,0);

       

    }

    切面:横切关注点被模块化的抽象对象(对象中相同的非业务代码)

    通知:切面对象完成的工作(非业务代码)

    目标:被通知的对象,即被切面的对象(非业务代码存在的类)

    代理:切面、通知、目标混合之后的对象(一个代理实现非业务代码功能的类)

    连接点:通知要插入业务代码的具体位置(代理和目标之间的联系)

    切点:AOP通过切点定位到连接点

  • 相关阅读:
    浅谈Javascript数据属性与访问器属性
    深入浅析JavaScript中的constructor
    javascript 继承
    很认真的聊一聊程序员的自我修养
    JavaScript数据属性与访问器属性
    Js中的数据属性和访问器属性
    [javascript基础]constructor与prototype
    C# System.Net.Mail
    Execl (转)导入导出execl 全
    delegate 委托方法
  • 原文地址:https://www.cnblogs.com/wigginess/p/13416795.html
Copyright © 2020-2023  润新知