• struts2学习笔记


    1、struts2环境的简单搭建

       

    •     必须的jar包有

               

    •   struts.xml文件格式
    <?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>
    
    </struts>
    • web.xml文件的格式
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
        id="WebApp_ID" version="2.5">
        <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>

     以上为简单配置,然后部署启动如果在控制台没有发现错误则环境OK,此处使用的版本是struts-2.3.24,上面的jar包经测试都是必须的jar包。

    知识点:

    struts.xml文件:

    •     使用package对Action进行管理
    •     package的name必须唯一,继承时有用
    •     namespace作为路径的一部分
    •     自定义的package必须extends="struts-default",否则struts2的强大的拦截器功能将不会使用到
    •     如果package 的 abstract="true"则该package将不能在其中定义action,该package只能被集成
    •     action 的class指定action是哪个类,method指定方法名称
    •     struts.xml文件struts在启动时只会加载一次,后面如果有改动,需重新部署启动服务器

    action类:

    • action中被调用的方法返回的是String类型,不许继承任何类
    • action中的属性在页面可直接通过EL表达式访问,不许指定访问范围

    浏览器访问Action的方法如下:

    • http://主机名(或IP):8080(端口号)/struts2(应用的名称)/test(package的namespace)/test(action 的name)
    • struts2的默认访问后缀是.action,如果不加.action也是可以的

    struts2路径寻找规则:

    假如路径是:http://localhost:8080/app/path1/path2/path3/action.action

    1. 首先,struts2会在namespace为/path1/path2/path3的package下寻找名称为action的Action,如果找到则返回,否则进入2
    2. 然后,struts2会在namespace为/path1/path2的package下寻找名称为action的Action,如果找到则返回,否则进入3
    3. 然后,struts2会在namespace为/path1的package下寻找名称为action的Action,如果找到则返回,否则进入4
    4. 然后,struts2会在namespace为/的package下寻找名称为action的Action,如果找到则返回,否则进入5
    5. 最后,struts2会在默认的命名空间下寻找名称为action的Action,如果找到则返回,否则返回404

     struts2中Action中的默认值

    1. 如果class没有指定,则默认值是ActionSupport类
    2. 如果method没有指定,则默认值是execute方法
    3. 如果result节点的name属性没有指定,则默认值是success

       struts2中result的类型:

    其中比较常用的有:dispatcher、redirect、redirectAction、plainText

    • dispatcher:默认的方式,代表请求转发
    • redirect:重定向,注意不能重定向到WEB-INFO下面的页面
    • redirectAction:重定向到Action,同一个包时不必指定namespace,但是必须指定actionName,不在同一个包时namespace和actionName都要指定

          例如:         

    <result type="redirectAction">
         <param name="actionName">dashboard</param>
         <param name="namespace">/secure</param>
    <result>
    
    <result name="error" type="redirectAction">error</result>
    • plainText: 文本显示,如果乱码需要指定编码,如上通过param标签传递编码参数和资源路径即可PlainTextResult

     全局视图:

    •     包全局视图:通过<global-results></global-results>直接在包中定义即可
    •     全局全局视图:包和包都可以访问的全局视图,可通过包的继承实现,定义一个包,包中通过<global-results></global-results>定义全局视图,然后其他的包继承该包即可实现

    在视图中的资源文件后可以通过${属性名称}带上Action的属性的值,注意如果是URL要对属性值进行URL编码。

    struts.xml中可以通过<param name=""></param>为Action注入数据:

    <action name="" type="">
                <param name="name">张三</param>
    </action>


    struts2可以配置常量的地方:

    1. struts-default.xml(struts2-core-2.3.24.jar)
    2. struts-plugin.xml(struts2-convention-plugin-2.3.24.jar)
    3. struts.xml (src/)
    4. struts.properties (src/)
    5. web.xml

    如果在上面的文件中都配置了同一个常量的值,那么后面的值会覆盖掉前面配置的值。

    sturts2的常量:

          默认常量的位置:struts2-core-2.3.24.jar(org.apache.struts2/default.properties)

    常见常量的含义: 

    struts.i18n.encoding:这个常量会作用于setCharacterEncoding方法和freemarker,velocity(这两种为模板技术)的输出,POST方式提交的数据,可以交由这个常量去设置它的编码格式。

    <constant name="struts.i18n.encoding" value="UTF-8"/>

    struts.configuration.xml.reload:当struts的配置文件修改后,系统是否自动重新加载该文件,默认值为false,开发阶段最好打开,开发完后再关闭。

    <constant name="struts.configuration.xml.reload" value="true" />

    struts.serve.static.browserCache:设置浏览器是否缓存,默认值为true,开发阶段最好关闭。

    <constant name="struts.serve.static.browserCache" value="false"/>

    struts.devMode:打印出更详细的错误信息,用于排错,主要用于开发模式,做好了再关闭,在copy sturts-blank中的stuts.xml中,里面有这常量。

    <constant name="struts.devMode" value="true" />

    struts.ui.theme:标签所使用的额外的自定义样式,不太实用,最好设置成simple,且一般都不用到struts 2的标签库。

    <constant name="struts.ui.theme" value="simple" />

    struts.objectFactory:与spring继承时,指定由spring负责action对象的创建,在继承spring时,会用到这个常量.

    <constant name="struts.objectFactory" value="spring"/>

    struts.enable.DynamicMethodInvocation:该属性设置struts2是否支持动态方法调用,该属性的默认值是true,如果需要关闭动态方法调用,则可设置该属性为false,

    <constant name="struts.enable.DynamicMethodInvocation" value="true" />

    注意:DMI的调用方式Struts2的文档不建议使用,且下载的struts2-2-X.jar中,它的sturts-blank中sturts.xml有这常量,并设其值为false。

    struts.multipart.maxSize:设置上传文件的总大小限制

    <constant name="struts.multipart.maxSize" value="20971520" />

    struts.action.extension:修改后缀名,默认是action,可以修改。若值有多个,可用逗号隔开,这对于常量的值都通用。

    <constant name="struts.action.extension" value="do,lz" />

    struts.custom.i18n.resources:用于配置国际化全局XML资源文件,须在指明该全局资源文件的基础名。

    <constant name="struts.custom.i18n.resources" value="baseName" />

    struts.ognl.allowStaticMethodAccess:如其名,用于设置OGNL是否允许静态方法访问,默认为false。

    <constant name="struts.ognl.allowStaticMethodAccess" value="true" />

     struts2流程图(官网的一张)

    struts2的简单流程:

    客户端请求来临时,StrutsPrepareAndExecuteFilter进行处理,如果请求是以.action或者为空则转给Intecepter(准确的说是一系列拦截器,这些拦截器都是struts2的核心连接器),拦截器处理完成后交由Action进行处理,Action处理结束后返回result视图的名称,最后把视图返回给客户端。值得注意的是struts2的Action是线程安全的,因为struts2会对每一个请求实例化一个Action进行对请求的处理。

    为struts2指定多个配置文件:

    <?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>
        <include file="default.xml"></include>
        <include file="other.xml"></include>
    </struts>


    struts2的动态方法调用:

    • 感叹号的形式

    假如Action中有两个方法,一个方法使add另一个方法使delete,Action的名称为xxxAction,应用名称为test,action的命名空间为test,则感叹的动态方法调用如下:

    htttp://localhost:8080/test/test/xxxAction!add.action

    struts2官方不建议使用这种形式的动态方法调用,可以通过下面的常量配置是否支持动态方法调用

    <constant name="struts.enable.DynamicMethodInvocation" value="true" />
    • 通配符的形式

    假如Action中有两个方法,一个方法使add另一个方法使delete,Action的名称为xxxAction,应用名称为test,struts.xml配置文件如下:

    <package name="test" namespace="/test" extends="struts-default">
            <action name="test_*" class="xxxAction" method="{1}">
                <result name="success" >/WEB-INF/jsp/hello.jsp</result>
            </action>
    </package>

    则客户端的调用形式如下
           http://localhost:8080/struts2/test/test/test_add

    注意:一个*好就代表一个通配符,{1}代表action name中的第一个*,如果有多个直接修改{}中的数值即可。通配符的位置可以出现在很多地方,不仅仅是上面的例子看到的那样。

     struts2 Action对请求参数的接收:

    • 基本类型的接收

                 Action属性的名称和页面表单的字段的名称一致,且Action中属性必须存在set方法

    • 复合类型的接收

                复合类型在Action中必须存在set方法,页面使用复合类型属性.复合类型内部的属性传递参数

                 如 Person有个name属性,那么 在页面访问时可以通过person.name

    struts2类型转换器:

    •    局部类型转换器
    1. 定义类型转换器:该转换器集成com.opensymphony.xwork2.conversion.implDefaultTypeConverter并重写convertValue(Map<String, Object> context, Object value, Class toType)
    2. 注册类型转换器:在Action所在的包下创建actionName-conversion.properties(actionName:Action的简单类名,也就是不带包的类型)文件,文件的内容为Action中需要转换的属性=类型转换器的全名称
    •    全局类型转换器
    1. 定义类型转换器:该转换器集成com.opensymphony.xwork2.conversion.implDefaultTypeConverter并重写convertValue(Map<String, Object> context, Object value, Class toType)
    2. 注册类型转换器:在类路径下创建xwork-conversion.properties文件,文件的内容为需要转换的数据类型(全名称)=类型转换器的全名称

    struts2向request、session、application范围存放数据:

    ActionContext act = ActionContext.getContext();
    act.getApplication().put(key, value)// application范围存放数据
    act.getSession().put(key, value)// session范围内存放数据
    act.put(key, value) // request范围内存放数据

    struts2获取request、session、application等对象

    ServletActionContext.getRequest()
    ServletActionContext.getServletContext()

    另外一种获取request、session、application等对象的方法使实现相应的接口,由容器自动注入这些对象,例如:ServletRequestAware,其他的接口与该接口类似

    struts2文件上传

    1.  页面表单的method="post" enctype="multipart/form-data"
    2. Action中通过如下的字段接收客户端传递的文件资源
    private File image; // 与file表单的name一致
    private String imageFileName; // 文件名称
    private String imageContentType; // 文件的类型

    服务端可通过FileUtils 对文件进行保存。

    多文件上传:

            只需将接收的参数设置问数组形式的就OK了。

    private File image[]; // 与file表单的name一致
    private String imageFileName[]; // 文件名称
    private String imageContentType[]; // 文件的类型

    struts2拦截器:

    拦截器的定义:拦截器必须实现com.opensymphony.xwork2.interceptor.Interceptor接口,并实现intercept(ActionInvocation invocation)方法,

                        如果在intercept方法中不调用invocation.invoke()方法则Action中的方法将得不到执行的机会。

    拦截器在Action中的使用:

    struts.xml中拦截器的配置:

    <package name="test" namespace="/test" extends="struts-default">
            <interceptors>
                <interceptor name="permisson" class="xxx.PermissionInterceptor">   </interceptor>
            </interceptors>      
    </package>

    Action中拦截器的使用:

    <package name="test" namespace="/test" extends="struts-default">
            <interceptors>
                <interceptor name="permisson" class="xxx.PermissionInterceptor"></interceptor>
            </interceptors>
            <action name=" " class=" " method=" ">
                <interceptor-ref name="permisson"></interceptor-ref>
                <result name="success" ></result>
            </action>
    </package>

    注意:如果Action中指定了拦截器那么系统默认的拦截器将不会被使用,系统默认的拦截器在文件struts-default.xml中,如下:

    <interceptor-stack name="defaultStack">
                    <interceptor-ref name="exception"/>
                    <interceptor-ref name="alias"/>
                    <interceptor-ref name="servletConfig"/>
                    <interceptor-ref name="i18n"/>
                    <interceptor-ref name="prepare"/>
                    <interceptor-ref name="chain"/>
                    <interceptor-ref name="scopedModelDriven"/>
                    <interceptor-ref name="modelDriven"/>
                    <interceptor-ref name="fileUpload"/>
                    <interceptor-ref name="checkbox"/>
                    <interceptor-ref name="datetime"/>
                    <interceptor-ref name="multiselect"/>
                    <interceptor-ref name="staticParams"/>
                    <interceptor-ref name="actionMappingParams"/>
                    <interceptor-ref name="params"/>
                    <interceptor-ref name="conversionError"/>
                    <interceptor-ref name="validation">
                        <param name="excludeMethods">input,back,cancel,browse</param>
                    </interceptor-ref>
                    <interceptor-ref name="workflow">
                        <param name="excludeMethods">input,back,cancel,browse</param>
                    </interceptor-ref>
                    <interceptor-ref name="debugging"/>
                    <interceptor-ref name="deprecation"/>
    </interceptor-stack>

    那么我们应该在package中定义一个拦截器栈,然后在给Action使用

    <interceptors>
                <interceptor name="permisson" class="xxx.PermissionInterceptor"></interceptor>
                <interceptor-stack name="permissonStack">
                     <interceptor-ref name="defaultStack"></interceptor-ref>
                     <interceptor-ref name="permisson"></interceptor-ref>
                </interceptor-stack>
    </interceptors>

    注意系统默认的拦截器应该在我们自定义的拦截器前面,因为拦截器的调用时按照在文件中的配置顺序执行的。

    然后在对我们的Action应用这个拦截器栈。

    对package中所有的Action都应用同一个拦截器,如下配置即可对package下的所有Action都使用了拦截器permissonStack:

    <default-interceptor-ref name="permissonStack"></default-interceptor-ref>

    如果Action一次要使用多个拦截器,那么可以在Action通过多个<interceptor-ref name=""></interceptor-ref>指定拦截器。

    值得注意的是,如果Action中指定了拦截器,那么默认的拦截器和package内默认的拦截器将都不会被使用到。

    struts2的输入校验

    1. 编码校验
    2. xml配置文件校验
    •  编码校验
      • 所有方法校验
      • 指定方法校验

    所有方法校验,类必须继承ActionSupport类,然后重写validate()方法,在该方法中对数据进行验证,如果存在问题则调用addFieldError(String fieldName, String errorMessage)方法,如果校验出现不通过的情况,那么就会返回input视图。

    针对Action中的某个方法进行校验,只需要在Action中实现validateXxx方法,Xxx为方法的签名,其他的和所有方法校验一致。

     当类型转换异常或者校验失败时,fieldErrors不为空,则会进入input视图。

    基于XML文件的输入校验:

    1. Action也必须继承ActionSupport
    2. 编写校验文件

    基于XML所有方法校验:

    编写XML文件,文件的命名格式为ActionClassName-validation-xml,ActionClassName为Action的简单类名。该文件应该与Action在同一个包中。

    校验文件格式如下:

    <!DOCTYPE validators PUBLIC
            "-//Apache Struts//XWork Validator 1.0.3//EN"
            "http://struts.apache.org/dtds/xwork-validator-1.0.3.dtd">
    
    <validators>
        <field name="username">
            <field-validator type="requiredstring">
                <message key="requiredstring"/>
            </field-validator>
        </field>
    </validators>

    filed节点的name为Action中属性的名称,field-validator的type属性指定了校验器,message为校验不通过的提示信息(注意Action应该配置input视图)。

    所有的校验器我们可以在work-core-2.3.24.jar jar包下 com/opensymphony/xwork2/validator/validators/default.xml找到,如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE validators PUBLIC
            "-//Apache Struts//XWork Validator Definition 1.0//EN"
            "http://struts.apache.org/dtds/xwork-validator-definition-1.0.dtd">
    
    <!-- START SNIPPET: validators-default -->
    <validators>
        <validator name="required" class="com.opensymphony.xwork2.validator.validators.RequiredFieldValidator"/>
        <validator name="requiredstring" class="com.opensymphony.xwork2.validator.validators.RequiredStringValidator"/>
        <validator name="int" class="com.opensymphony.xwork2.validator.validators.IntRangeFieldValidator"/>
        <validator name="long" class="com.opensymphony.xwork2.validator.validators.LongRangeFieldValidator"/>
        <validator name="short" class="com.opensymphony.xwork2.validator.validators.ShortRangeFieldValidator"/>
        <validator name="double" class="com.opensymphony.xwork2.validator.validators.DoubleRangeFieldValidator"/>
        <validator name="date" class="com.opensymphony.xwork2.validator.validators.DateRangeFieldValidator"/>
        <validator name="expression" class="com.opensymphony.xwork2.validator.validators.ExpressionValidator"/>
        <validator name="fieldexpression" class="com.opensymphony.xwork2.validator.validators.FieldExpressionValidator"/>
        <validator name="email" class="com.opensymphony.xwork2.validator.validators.EmailValidator"/>
        <validator name="url" class="com.opensymphony.xwork2.validator.validators.URLValidator"/>
        <validator name="visitor" class="com.opensymphony.xwork2.validator.validators.VisitorFieldValidator"/>
        <validator name="conversion" class="com.opensymphony.xwork2.validator.validators.ConversionErrorFieldValidator"/>
        <validator name="stringlength" class="com.opensymphony.xwork2.validator.validators.StringLengthFieldValidator"/>
        <validator name="regex" class="com.opensymphony.xwork2.validator.validators.RegexFieldValidator"/>
        <validator name="conditionalvisitor" class="com.opensymphony.xwork2.validator.validators.ConditionalVisitorFieldValidator"/>
    </validators>
    <!--  END SNIPPET: validators-default -->

    假如我们的Action为UserAction,那么校验文件的命名应该为:UserAction-validation.xml。

    基于XML对Action中的指定方法进行校验,大体上和对所有方法校验一样,只是校验文件的命名有所变化。文件命名的格式是:ActionClassName-actionName-validation.xml,其中actionName为struts.xml中Action的配置名称。例如:

    假如我们的Action为UserAction,在UserAction中有两个方法,分别为add、delete,UserAction的配置如下:

    <package name="user" namespace="/user" extends="struts-default">
            <action name="user_*" class="com.xxx.UserAction" method="{1}">
                <result name="input">/WEB-INFO/page/input.jsp</result>
            </action>
    </package>

    那么如果我们需要对add方法校验,那么校验文件的命名应该为UserAction-user_add-validation.xml,该文件与Action在同一个包下。

    如果一个Action既有所有方法校验配置文件也有方法校验配置文件,那么struts2会搜索所有的检验文件,对于方法校验以方法校验文件为准。

    struts2国际化:

    全局范围的国际化、包范围的国际化、action范围的国际化。

    国际化资源文件的命名规则:

    baseName_language_country.properties

    baseName_language.properties

    baseName.properties

    其中baseName可为任意值

    比如汉语国际化资源文件命名:resources_zh_CN.properties,美国英语资源文件为:resources_en_US.properties

    全局资源文件应该放在src目录下。

    struts.xml中可通过常量引入全局资源文件:

    <constant name="struts.custom.i18n.resources" value="baseName" />

    jsp页面访问国际化资源:

    <s:text name="key" />

    key为国际化资源文件中的key。

    Action中访问国际化资源文件需要继承ActionSupport,通过getText()方法获取。

    带占位符的国际化资源的访问:

    name={0}你好,{1}
    <s:text name="key" >
        <s:param> </s:param>
        <s:param> </s:param>
    </s:text>

    包范围的国际化资源的命名格式package_language_country.properties,package为固定写法。包范围的国际化资源可以被该包和子包进行访问,访问格式和访问全局国际化资源文件一样。如果在包范围内找不到,则会去全局范围内找。

    Action范围的国际化资源文件的命名格式ActionClassName_language_country.properties,ActionClassName为Action的简单类名称。

    OGNL表达式:Object Graphic Navigation Language(对象图导航语言)的缩写,struts2框架默认使用OGNL做为表达式语言。

    OGNL有一个上下文的概念,在struts2中上下文的实现时ActionContext。结构图如下:

    当struts2接收到一个请求时,会迅速的创建ActionContext、ValueStack、action,然后把action存放进ValueStack中,所以action中的实例变量可以被OGNL访问。

    访问上下文(Context)中的对象需要使用#标注命名空间,如#application、#session

    另外OGNL会设定一个根对象(root对象),在struts2中根对象就是ValueStack(值栈),如果需要访问根对象中的对象属性,则可以不加#命名空间,直接访问该对象的属性即可。

    在struts2中,根对象ValueStack的实现类为OgnlValueStack,该对象可以存放一组对象,在OgnlValueStack类里有一个List类型的root变量,就是使用它存放一组对象。

    在root变量中处于第一位的对象叫栈顶对象,通常我们在ONGL表达式里直接写上属性名即可访问root变量里对象的属性。搜索顺序是从栈顶开始寻找,如果栈顶对象不存在该属性,就会从第二个对象寻找,如果没有找到,则从第三个对象寻找,知道找到为止。

    值得注意的是,OGNL表达式需要配合struts2的标签才可以使用。

    由于ValueStack是struts2中OGNL的根对象,那么可以直接使用EL表达式直接访问值栈中的对象的属性。

    如果是其他的Context中的对象,由于他们不是根对象,所以访问时需要添加#前缀。

    application对象:用于访问ServletContext,例如#application.userName或者#application["userName"]相当于调用ServletContext的getAttribute("userName");

    session对象:用于访问HttpSession对象,例如:#session.userName或者#session["userName"]相当于调用seesion.getAttibute("userName")

    request对象:用来访问HttpServletRequest属性(attribute)的Map,例如#request.userName或者#request["userName"],相当于调用requset.getAttribute("userName")

    parameters对象:用于访问HTTP的请求参数,例如#parameters.userName或者#parameters["userName"]相当于调用request.getParameter("userName")

    attr对象:用于按page->request->session->application顺序访问其属性

    OGNL表达式创建List/Map集合对象

    使用如下代码创建一个List集合对象:

    <s:set name="list" value="{'a', 'b', 'c'}"/>

    遍历List集合:

    <s:iterator value="#list"> 
        <s:property/><br/>
    </s:iterator>

    Set标签用于将某个值存放在指定的范围内

    scope:指定变量被存放的范围,该属性可接受application、seesion、request、page、action,如果没有设置该属性,则默认放置在OGNL Context中。

    value:赋给变量的值,如果没有设置该属性,则将ValueStatck栈顶的值赋给变量

    生成Map集合:

    <s:set name="map" value="#{'key1':'value1', 'key2':'value2'}"/>

    遍历Map集合

    <s:iterator value="#map">
        <s:property value="key"/>
        <s:property value="value"/>
    </s:iterator >

    iterator标签的特点:该标签会把当前遍历的元素放置在值栈的栈顶。
    property标签的特定,如果value没有指定,则表示输出值栈栈顶的元素。


    OGNL表达式可以判断对象是否存在于某个集合中:
    in:表示对象在集合中
    not in: 表示对象不在集合中

    <s:if test="'a' in {'a','b'}"></s:if>
    <s:else>
        不在
    </s:else>
    
    
    <s:if test="'a' not in {'a','b'}"></s:if>
    <s:else>
        不在
    </s:else>

    OGNL表达式的投影功能

    常用的有以下几个:

    ?:获取所有符合逻辑的元素

    ^:获取符合逻辑的第一个元素

    $:获取符合逻辑的最后一个元素

    使用方式:

    <s:iterator value="books.{?#this.price>35}">
        <s:property value="title"/>
        <s:property value="price"/>
    </s:iterator>

    上面代码直接在集合后面紧跟  .{}  运算符表明用于取出该集合的子集,{}内的表达式用于获取符合条件的元素,this指的是为了从大集合中筛选元素到小集合中,需要对大集合进行迭代,this代表当前迭代的元素。

    OGNL栈顶的元素可以直接使用EL表达式访问。

  • 相关阅读:
    编译并使用Lua语言
    C#中使用DLL文件
    将Unity3D游戏移植到Android平台上
    Unity3D知识点
    清下书柜,工作书,旧书,正版书,个人学习过的书asp,net,delphi,java,flex,actionscript,vb...
    使用ABP打造SAAS系统(2)——前端框架选择
    使用ABP打造SAAS系统(1)——环境准备
    延迟实例单例模式注意点
    jvm指令解释i = i++ + i++ + i++ + ++i;等于多少
    MYSQL增加库表权限
  • 原文地址:https://www.cnblogs.com/heml/p/4713259.html
Copyright © 2020-2023  润新知