zoukankan      html  css  js  c++  java
  • JavaEE高级-Spring学习笔记

    *Spring是什么?

      - Spring是一个开源框架

      - Spring为简化企业级应用开发而生.使用Spring可以使简单的JavaBean实现以前只有EJB才能实现的功能

      - Spring是一个IOC(DI)和AOP容器框架

      - 具体描述:

        > 轻量级:Spring是非侵入性的-基于Spring开发的应用中对象可以不依赖与Spring的API

        > 依赖注入:(DI---dependency injection、IOC)

        > 面向切面编程:(AOP---aspect oriented programming)

        > 容器:Spring是一个容器,因为它包含并且管理应用对象的生命周期

        > 框架:Spring实现了使用简单的组件配置组合成一个复杂的应用.在Spring中可以使用XML和Java注解组合这些对象

        > 一站式:在IOC和AOP的基础上可以整合各种企业级应用的开源框架和优秀的第三方类库(实际上Spring自身也提供了展示层的SpringMVC和持久层的SpringJDBC)

      

    *安装SPRING TOOL SUITE

      - SPRING TOOL SUITE是一个Eclipse插件,利用该插件可以更方便的在Eclipse平台上开发基于Spring的应用

      

    *搭建Spring开发环境

      - 把以下jar包加入到工程的classpath下:

          

      - Spring的配置文件:一个典型的Spring项目需要创建一个或多个Bean配置文件,这些配置文件用于在SpringIOC容器里配置Bean.Bean的配置文件可以放在classpath下,也可以放在其它目录下

    *Spring中的Bean配置

      - IOC和DI:

        > IOC(Inversion of Control):其思想是反转资源获取的方向.传统的资源查找方式要求组件向容器发起请求查找资源.作为回应,容器适时的返回资源,而应用了IOC之后,则容器主动地将资源推送给它所管理的组件,

                        组件所要做的仅是选择一种合适的方式来接受资源.这种行为也被称为查找的被动形式

        > DI(Dependency Injection)- IOC的另一种表述方式:即组件以一些预先定义好的方式(例如:serter方法)接受来自容器资源的注入,相对于IOC而言,这种表述更直接

      - 配置bean:

        > 配置形式:基于XML文件的方式;基于注解的方式(基于注解配置Bean;基于注解来装配Bean的属性)

          *在xml文件中通过bean节点来配置bean

            

          *id:Bean的名称

            >>在IOC容器中必须唯一

            >>若id没有指定,Spring自动将权限定性类名作为Bean的名字

            >>id可以指定多个名字,名字之间可用逗号、分号、或空格分隔

            >.Bean中必须有无参构造器

          *在classpath中扫描组件

            >>组件扫描(component scanning):Spring能够从classpath下自动扫描,侦测和实例化具有特定注解的组件

            >>特定组件包括:

              ---- @Component:基本注解,标识了一个受Spring管理的组件

              ---- @Respository:标识持久层组件

              ---- @Service:标识服务层(业务层)组件

              ---- @Controller:标识表示层组件

            >>对于扫描到的组件,Spring有默认的命名策略:使用非限定类名,第一个字母小写,也可以在注解中通过value属性值标识组件的名称

            >>当在组件类上使用了特定的注解之后,还需要在spring的配置文件中声明<context:component-scan>:

              ---- base-package属性指定一个需要扫描的基类包,Spring容器将会扫描这个基类包里及其子包中的所有类

              ---- 当需要扫描多个包时,可以使用逗号分隔

              ---- 如果仅希望扫描特定的类而非基包下的所有类,可以使用resource-pattern属性过滤特定的类,例如:

                  

              ---- <context:include-filter>子节点表示要包含的目标类

              ---- <context:exclude-filter>子节点表示要排除在外的目标类

              ---- <context:component-scan>下可以拥有若干个<context:include-filter>和<context:exclude-filter>子节点

          *组件装配

            >><context:component-scan>元素还会自动注册AutowiredAnnotationBeanPostProcessor实例,该实例可以自动装配具有@Autowired和@Resource、@Inject注解的属性

            >>使用@Autowired自动装配Bean:@Autowired注解自动装配具有兼容类型的单个Bean属性

              ---- 构造器,普通字段(即使是非public),一切具有参数的方法都可以应用@Authwired注解

              ---- 默认情况下,所有使用@Authwired注解的属性都需要被设置,当Spring找不到匹配的Bean装配属性时,会抛异常,若某一属性允许不被设置,可以设置@Authwired注解的required属性为false

              ---- 默认情况下,当IOC容器里存在多个类型兼容的Bean时,通过类型的自动装配将无法工作,此时可以在@Qualifier注解里提供Bean的名称,Spring允许对方法的入参标注@Qualifiter已指定注入Bean的名称

              ---- @Authwired注解也可以应用在数组类型的属性上,此时Spring将会把所有匹配的Bean进行自动装配

              ---- @Authwired注解也可以应用在集合属性上,此时Spring读取该集合的类型信息,然后自动装配所有与之兼容的Bean

              ---- @Authwired注解用在java.util.Map上时,若该Map的键值为Spring,那么Spring将自动装配与之Map值类型兼容的Bean,此时Bean的名称作为键值

        > Bean的配置方式:通过全类名(反射)、通过工厂方法(静态工厂方法&实例化工厂方法)、FactoryBean

          *通过调用静态工厂方法创建Bean

            >>调用静态工厂方法创建Bean是将对象创建的过程封装到静态方法中,当客户端需要对象时,只需要简单地调用静态方法,而不用关心创建对象的细节

            >>要声明通过静态方法创建的Bean,需要在Bean的class属性里指定拥有该工厂的方法的类,同时在factory-method属性里指定工厂方法的名称,最后使用<constrctor-arg>元素为该方法传递方法参数

          *通过调用实例工厂方法创建Bean

            >>实例化工厂方法:将对象的创建过程封装到另一个对象实例的方法里,当客户端需要请求对象时,只需要简单的调用该实例方法而不需要关心对象的创建细节

            >>要声明通过实例化工厂方法创建Bean:

              ---在bean的factory-bean属性里指定拥有该工厂方法的Bean

              ---在factory-method属性里指定该工厂方法的名称

              ---使用construtor-arg元素为工厂方法传递方法参数

        > IOC容器BeanFactory&ApplicationContext概述

        > 依赖注入的方式:属性注入;构造器注入

        > 注入属性值细节

          *字面值:

            >>字面值:可用字符串表示的值,可以通过<value>元素标签或value属性进行注入

            >>基本数据类型及其封装类、String等类型都可以采取字面值注入的方式

            >>若字面值中包含特殊字符,可以使用<![CDATA[]]把字面值包裹起来

          *引用其它Bean

            >>组成应用程序的Bean经常需要相互协作以完成应用程序的功能,要使Bean能够相互访问,就必须在Bean配置文件中指定对Bean的引用

            >>在Bean的配置文件中,可以通过<ref>元素或ref属性为Bean的属性或构造器参数指定对Bean的引用

            >>也可以在属性或构造器里包含Bean的声明,这样的Bean称为内部Bean

          *注入参数详解:null值和级联属性

            >>可以使用专用的<null/>元素标签为Bean的字符串或其它对象类型的属性注入null值

            >>和Struts、Hiberante等框架一样,Spring支持级联属性的配置

          *集合属性

            >>在Spring中可以通过一组内置的xml标签(例如:<list>,<set>或<map>)来配置集合属性

            >>配置java.util.List类型的属性,需要指定<list>标签,在标签里包含一些元素,这些元素可以通过<value>指定简单的常量值,通过<ref>指定对其它Bean的引用,

              通过<bean>指定内置Bean定义,通过<null/>指定空元素,甚至可以内嵌其它集合

            >>数组的定义和List一样,都使用<list>

            >>配置java.util.Set需要使用<set>标签,定义元素的方法与List一样

            >>Java.util.Map通过<map>标签定义,<map>标签里可以使用多个<entry>作为子标签,每个条目包含一个键和一个值

            >>必须在<key>标签里定义键

            >>因为键和值的类型没有限制,所以可以自由地为它们指定<value>,<ref>,<bean>或<null>元素

            >>可以将Map的键和值作为<entry>的属性定义:简单常量使用key和value来定义;Bean引用通过key-ref和value-ref来定义;

            >>使用<props>定义java.util.Properties,该标签使用多个<prop>作为子标签,每个<prop>标签必须定义key属性

          *使用utility scheme定义集合

            >>使用基本的集合标签定义集合时,不能将集合作为独立的Bean定义,导致其他Bean无法引用该集合,所有无法在不同Bean之间共享集合

            >>可以使用util schema里的集合标签定义独立的集合Bean,需要注意的是,必须在<beans>根元素里添加util schema定义

          *使用p命名空间

            >>为了简化XML文件的配置,越来越多的XML文件采用属性而非子元素配置信息

            >>Spring从2.5版本开始引入了一个新的p命名空间,可以通过<bean>元素属性的方式配置Bean的属性

            >>使用p命名空间后,基于XML的配置方式将进一步简化

        > 自动装配

          *XML配置里的Bean自动装配

            >>SpringIOC容器可以自动装配Bean.需要做的仅仅是在<bean>的autowire属性里指定自动装配的模式

            >>byType(根据类型自动装配):若IOC容器中有多个与目标Bean类型一致的Bean.在这种情况下,Spring将无法判定哪个Bean最合适该属性,所以不能执行自动装配

            >>byName(根据名称自动装配):必须将目标Bean的名称和属性名设置的完全相同

            >>constructor(通过构造器自动装配):当Bean中存在多个构造器时,此种自动装配方式将会很复杂,不推荐使用

          *XML配置里的Bean自动装配的缺点

            >>在Bean配置文件里设置autowire属性进行自动装配将会装配Bean的所有属性,然而若只希望装配个别属性时,autowire属性就不够灵活了

            >>autowire属性要么根据类型自动装配,要么根据名称自动装配,不能两者兼而有之

            >>一般情况下,在实际的项目中很少使用自动装配功能,因为和自动装配功能所带来的好处比起来,明确清晰的配置文档更有说服力

        > bean之间的关系:继承、依赖

          *继承Bean配置

            >>Spring允许继承bean的配置,被继承的bean称为父bean,继承这个父Bean的Bean称为子Bean

            >>子Bean从父Bean中继承配置,包括Bean的属性配置

            >>子Bean也可以覆盖从父Bean继承过来的配置

            >>父Bean可以作为配置模板,也可以作为Bean实例,若只想把父Bean作为模板,可以设置<bean>的abstract属性为true,这样Spring将不会实例化这个Bean

            >>并不是<bean>元素里的所以属性都会被继承,比如:autowire,abstract等

            >>也可以忽略父Bean的class属性,让子Bean指定自己的类,而共享相同的属性配置,但此时abstract必须设为true

          *依赖Bean配置

            >>Spring允许用户通过depends-on属性设定Bean前置依赖的Bean,前置依赖的Bean会在本Bean实例化之前创建好

            >>如果前置依赖于多个Bean,则可以通过逗号,空格的方式配置Bean的名称

        > bean的作用域:singleton;prototype;WEB环境作用域

          *使用bean的scope属性来配置bean的作用域

            >>singleton:默认值,容器初始化时创建bean实例,在整个容器的生命周期内只创建一个bean,即单例的

            >>prototype:原型的,容器初始化时不创建bean实例,而在每次请求时都创建一个新的Bean实例,并返回

        > 使用外部属性文件

          *在配置文件里配置Bean时,有时需要在Bean的配置里混入系统部署的细节信息(例如:文件路径,数据源配置信息等),而这些部署细节实际上需要和Bean配置相分离

          *Spring提供了一个PropertyPlaceholderConfigurer的BeanFactory后置处理器,这个处理器允许用户将Bean配置的部分内容外移到属性文件中,

            可以在Bean配置文件里使用形式为${var}的变量,PropertyPlaceholderConfigurer从属性文件里加载属性,并使用这些属性来替换变量

          *Spring还允许在属性文件中使用${propName},以实现属性之间的相互引用

          *注册PropertyPlaceholderConfigurer

            >>spring2.5之后:可通过<context:property-placeholder>元素简化:

              ---- <beans>中添加context Schema定义

              ---- 在配置文件中加入如下配置:

                <context:property-placeholder location="classpath:db.properties"/>

        > spEL

          *Spring表达式语言(简称SpEL):是一个支持运行时查询和操作对象图的强大的表达式语言

          *语法类似于EL:SpEL使用#{...}作为定界符,所以在大框号中的字符都将被认为是SpEL

          *SpEL为bean的属性进行动态赋值提供了便利

          *通过SpEL可以实现:

            >>通过bean的id对bean进行引用

            >>调用方法以及引用对象中的属性

            >>计算表达式的值

            >>正则表达式的匹配

          *字面量的表示:

            >>整数:<property name="count" value="#{5}"/>

            >>小数:<property name="frequency" value="#{897}"/>

            >>科学计数法:<property name="capacity" value="#{1e4}"/>

            >>String可以使用单引号或者双引号作为字符串的定界符号:

              <property name="name" value="#{'Chuck'}"/>或<property name='name' value='#{"Chuck"}'/>

            >>Boolean:<property name="enabled" value="#{false}"/>

          *引用Bean、属性和方法

            >>引用其他对象:

              

            >>引用其他对象的属性:

              

            >>调用其他方法,还可以链式操作

              

            >>调用静态方法或静态属性:通过T()调用一个类的静态方法,它将返回一个Class Object,然后再调用相应的方法或属性

              

          *SpEL支持的运算符号

            >>算数运算符:+,-,*,/,%,^

              

            >>加号还可以用作字符串连接:

              

            >>比较运算符:<,>,==,<=,>=,lt,gt,eq,le,ge

              

            >>逻辑运算符:and,or,not,|

              

            >>if-else运算符:?:(temary),?:(Elvis)

              

            >>if-else的变体

              

            >>正则表达式:matches

              

        > IOC容器中Bean的生命周期

          *SpringIOC容器可以管理Bean的生命周期,Spring允许在Bean生命周期的特定点执行定制的任务

          *SpringIOC容器对Bean的生命周期进行管理的过程:

            >>通过构造器或工厂方法创建Bean实例

            >>为Bean的属性设置值和对其他Bean的引用

            >>调用Bean的初始化方法

            >>Bean可以使用了

            >>当容器关闭时,调用Bean的销毁方法

          *在Bean的声明里设置init-method和destroy-method属性,为Bean指定初始化和销毁方法

          *创建Bean后置处理器

            >>Bean后置处理器允许在调用初始化方法前后对Bean进行额外的处理

            >>Bean后置处理器对IOC容器里的所有Bean实例逐一处理,而非单一实例.其典型应用是:检查Bean属性额正确性或根据特定的标准更改Bean的属性

            >>对Bean后置处理器而言,需要实现接口,在初始化方法被调用前后,Spring将把每个Bean实例分别传递给上述接口的以下两个方法

                

        > Spring 4.x新特性:泛型依赖注入

          *Spring 4.x中可以为子类注入子类对应的泛型类型的成员变量的引用

            

       - Spring容器:

        > 在SpringIOC容器读取Bean配置创建Bean实例之前,必须对它进行实例化,只有在容器实例化后,才可以从IOC容器里获取Bean实例并使用

        > Spring提供了两种类型的IOC容器实现

          *BeanFactory:IOC容器的基本实现

          *ApplicationContext:提供了更多的高级特性,是BeanFactory的子接口

          *BeanFactory是Spring框架的基础设施,面向Spring本身;ApplicationContext面向使用Spring框架的开发者,几乎所有的应用场合都直接使用ApplicationContext而非底层的BeanFactory

          *无论使用何种方式,配置文件是相同的

    *SpringAOP

    *问题

      - 代码混乱:越来越多的非业务需求(日志和验证等)加入后,原有的业务方法急剧膨胀,每个方法在处理核心逻辑的同时还必须兼顾其他多个关注点

      - 代码分散:以日志需求为例,只是为了满足这个单一需求,就不得不在多个模块(方法)里重复相同的日志代码,如果日志需求方法发生变化,必须修改所有模块

    *使用动态代理解决上述问题

      - 代理设计模式的原理:使用一个代理将对象包装起来,然后用该代理对象取代原始对象,任何对原始对象的调用都要通过代理,代理对象决定是否以及何时将方法调用转到原始对象上

        

     *Spring AOP

      - AspectJ:Java社区里最完整最流行的AOP框架

      - 在Spring2.0以上版本中,可以使用基于AspectJ注解或基于XML配置的AOP

      - 在Spring中启用AspectJ注解支持

        > 要在Spring应用中使用AspectJ注解,必须在classpath下包含AspectJ类库:aopalliance.jar、aspectj.weaver.jar和spring-aspects.jar

        > 将aopSchema添加到<beans>根元素中

        > 要在SpringIOC容器中启用AspectJ注解支持,只要在Bean配置文件中定义一个空的XML元素<aop:aspectj-autoproxy>

        > 当SpringIOC容器侦测到Bean配置文件中的<aop:aspectj-autoproxy>元素时,会自动为与AspectJ切面匹配的Bean创建代理

      - 用AspectJ注解声明切面

        > 要在Spring中声明AspectJ切面,只需要在IOC容器中将切面声明为Bean实例.当在SpringIOC容器中初始化AspectJ切面之后,SpringIOC容器就会为那些与AspectJ切面相匹配的Bean创建代理

        > 在AspectJ注解中,切面只是一个带有@Aspect注解的Java类

        > 通知是标注有某种注解的简单的Java方法

        > AspectJ支持5种类型的通知注解:

          ---- @Before:前置通知,在方法执行之前执行

          ---- @After:后置通知,在方法执行之后执行

          ---- @AfterRunning:返回通知,在方法返回结果之后执行

          ---- @AfterThrowing:异常通知,在方法抛出异常之后

          ---- @Around:环绕通知,围绕这方法执行

              环绕通知需要携带ProceedingJoinPoint类型的参数

              环绕通知类似于动态代理的全过程:ProceedingJoinPoint类型的参数可以决定是否执行目标方法

              环绕通知必须有返回值,返回值即为目标方法的返回值

      - 前置通知

        > 前置通知:在方法执行之前执行的通知

        > 前置通知使用@Before注解,并将切入点表达式的值作为注解值

          

      - 利用方法签名编写AspectJ切入点表达式

        >最典型的切入点表达式是根据方法的签名来匹配各种方法:

          ---- execution * com.atguigu.spring.ArithmeticCalculator.*(..):匹配ArithmeticCalculator中声明的所有方法,第一个*代表任意修饰符及任意返回值,第二个*代表任意方法...匹配任意数量的参数.若目标类与接口与该切面在同一个包中,可以省略包名

          ---- execution public * ArithmeticCalculator.*(..):匹配ArithmeticCalculator接口的所有公有方法

          ---- execution public double ArithmeticCalculator.*(..):匹配ArithmeticCalculator中返回double类型数值的方法

          ---- execution public double ArithmeticCalculator.*(double,..):匹配第一个参数为double类型的方法,..匹配任意数量任意类型的参数

          ---- execution public double ArithmeticCalculator.*(double,double):匹配参数类型为double,double类型的方法

      - 后置通知

        > 后置通知是在连接点完成之后执行的,即连接点返回结果或者抛出异常的时候,下面的后置通知记录了方法的终止

        > 一个切面可以包含一个或者多个通知

          

        > 在后置通知中还不能访问目标方法执行的结果

      - 指定切面的优先级

        > 在同一个连接点上应用不止一个切面时,除非明确指定,否则它们的优先级是不确定

        > 切面的优先级可以通过实现Ordered接口或利用@Order注解指定

        > 实现Ordered接口,getOrder()方法的返回值越小,优先级越高

        > 若使用@Order注解,序号出现在注解中

        

    *Spring对JDBC的支持

      - JdbcTemplate简介

        > 为了使JDBC更加易于使用,Spring在JDBC API上定义了一个抽象,以此来建立一个JDBC存取框架

        > 作为Spring JDBC框架的核心,JDBC模板的设计目的是为不同类型的JDBC操作提供模板方法,每个模板方法都能控制整个过程,并允许覆盖过程中的特定任务.通过这种方式,可以在尽可能保留灵活性的情况下,将数据库存取额工作量降到最低

      - 使用JdbcTemplate更新数据库

        

      - 使用JdbcTemplate查询数据库

        

         

       - 简化JDBC模板查询

        > 每次使用都创建一个JdbcTemplate的新实例,这种做法效率很低下

        > JdbcTemplate类被设计成为线程安全的,所有可以在IOC容器中声明它的单个实例,并将这个实例注入到所有的DAO实例中

        > JdbcTemplate也利用了Java1.5的特定(自动装箱,泛型,可变长度等)来简化开发

        > Spring JDBC 框架还提供了一个JdbcDaoSupport类来简化DAO实现.该类声明了jdbcTemplate属性,它可以从IOC容器中注入,或者自动从数据源中创建

      - 在JDBC模板中使用具名参数

        > 在经典的JDBC用法中,SQL参数是用占位符?表示,并且受到位置的限制. 定位参数的问题在于,一旦参数的顺序发送变化,就必须改变参数绑定

        > 在SpringJDBC框架中,绑定SQL参数的另一种选择是使用具名参数(named parameter)

        > 具名参数:SQL按名称(以冒号开头)而不是按位置进行指定. 具名参数更易于维护,也提升了可读性,具名参数有框架类在运行时用占位符取代

        > 具名参数只在NamedParameterJdbcTemplate中得到支持

    *Spring中的事务管理

      - 事务简介:

        > 事务管理是企业级应用程序开发中必不可少的技术,用来确保数据的完整性和一致性

        > 事务就是一系列的动作,它们被当做一个独立的工作单元. 这些动作要么全部完成,要么全部不起作用

        > 事务的四个关键属性(ACID)

          1. 原子性(atomicity):事务是一个原子操作,由一系列动作组成,事务的原子性确保动作要么全部完成要么全部不起作用

          2. 一致性(consistency):一旦所有事务动作完成,事务就被提交,数据和资源就处于一种满足业务规则的一致性状态中

          3. 隔离性(isolation):可能有许多事务会同时处理相同的数据,因此每个事物都应该与其他事物隔离开来,防止数据损坏

          4. 持久性(durability):一旦事物完成,无论发生什么系统错误,它的结果都不应该受影响,通常情况下,事务的结果都被写到持久化存储器中

      - Spring中的事务管理

        > 作为企业级应用程序框架,Spring在不同的事务管理API之上定义了一个抽象层,而应用程序开发人员不必了解底层的事务管理API,就可以使用Spring的事务管理机制

        > Spring既支持编程式事务管理,也支持声明式的事务管理

        > 编程式事务管理:将事务管理代码嵌入到业务方法中来控制事务的提交和回滚,在编程式管理事务时,必须在每个事务操作中包含额外额事务管理代码

        > 声明式事务管理:大多数情况下比编程式事务管理更好用,它将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理,事务管理作为一种横切关注点,可以通过AOP方法模块化.Spring通过SpringAOP框架支持声明式事务管理

      - 事务传播属性

        > 当事务方法被另一个事务方法调用时,必须指定事务应该如何传播,例如:方法可以继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行

        > 事务的传播行为可以由传播属性指定,Spring定义了7种类传播行为

          

      - 并发事务所导致的问题

        > 当同一个应用程序或者不同应用程序中的多个事务在同一个数据集上并发执行时,可能会出现许多意外的问题

        > 并发事务所导致的问题可以分为下面三种类型:

          1).脏读:对于两个事务T1,T2,T1读取了已经被T2更新但还没有被提交的字段,之后,若T2回滚,T1读取的内容就是临时且无效的

          2).不可重复读:对于两个事务T1,T2,T1读取了一个字段,然后T2更新了该字段,之后,T1再次读取同一个字段,值就不同了

          3).幻度:对于两个事务T1,T2,T1从一个表中读取了一个字段,然后T2在该表中插入了一些新的行,之后,若T1再次读取同一个表,就会多出几行

    *Spring整合Hibernate

      - 1.整合什么?

        1). 让IOC容器来管理Hibernate的SessionFactory

        2). 让Hibernate使用上Spring的声明式事务

      - 2.整合步骤:

        1). 加入hibernate的jar包和添加hibernate的配置文件(hibernate.cfg.xml)以及编写持久化类对应的 .hbm.xml 文件

        2).加入Spring的jar包和配置文件

  • 相关阅读:
    Linux查看内容命令[持续添加]
    android4.0 x86下载编译简介
    android4.0 x86 裁剪与定制
    Android4.0 x86源码结构,生成目录结构
    android4.0 x86编译生成文件系统镜像system.img结构简介
    [翻译]Mootools 1.2新特性(一):元素存储(Element Storage)
    ASP.NET MVC Preview 3 STEP BY STEP 文章管理实例(一)
    Microsoft Speech API SDK
    PB串口编程资料MSCOMM32参数基本介绍
    Microsoft Speech API Overview(SAPI 5.4)
  • 原文地址:https://www.cnblogs.com/LzMingYueShanPao/p/11206633.html
Copyright © 2011-2022 走看看