博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
1、工程构建、打包的一些经验
阅读量:6425 次
发布时间:2019-06-23

本文共 24519 字,大约阅读时间需要 81 分钟。

hot3.png

1、工程结构描述

源工程目录结构:

打包之后的标准部署工程结构:

bin: 包含start.sh、stop.sh脚本sbin:一些供用户打包的bat脚本和shell脚本config:包含工程配置文件xxx.properties(包括数据库连接信息等)、logback.xml等java-project.jar:工程打包之后的jar,也可以放在lib文件夹下,只要在start.sh中能将其拼接CLASS_PATH中即可lib:工程依赖的第三方jar,比如commons-lang3-3.2.1.jar、mysql-connector-java-5.1.29.jar等version:里面一本是一个txt文件,用于描述每个版本修改了什么webapp:web的HTML、css、js、web.xml等文件需要在JettyServiceStarter指定路径

小结:任何能够从main方法启动的工程,比如java、spring-java、springboot、spring-webmvc-jetty,都能够打包成这样的部署结构。因为是通过java --classpath $CLASS_PATH MAIN_CLASS 来启动的,只要在start.sh中实现CLASS_PATH拼接上lib文件夹下的jar即可解决jar依赖问题

2、如何打包成上面的结构?

如何将自己的工程打包成上面的结构?

使用如下三个插件将工程打包成上面的结构

maven-compiler-plugin    ==> 指定jdk版本maven-jar-plugin         ==> 自定义自己工程,剔除一些配置信息maven-assembly-plugin    ==> 将自己工程的jar和其他依赖的jar组装起来							 打包成bin、 config、 lib、 version的标准格式
  • 1、pom.xml 的build部分

    ${project.artifactId}-${project.version}
    org.apache.maven.plugins
    maven-compiler-plugin
    3.2
    1.8
    1.8
    UTF-8
    org.apache.maven.plugins
    maven-jar-plugin
    db/db.properties
    quartz/job.properties
    caiwutong.properties
    logback.xml
    notice.txt
    org.apache.maven.plugins
    maven-assembly-plugin
    2.6
    ${project.finalName}
    assembly/assembly.xml
    false
    package
    single
  • 2、assembly.xml

    assembly
    zip
    true
    bin
    bin
    0755
    sbin
    sbin
    0755
    src/main/resources
    config
    db/*.xml
    db/*.sql
    mybatis/
    quartz/*.xml
    spring/
    notice.txt
    src/main/resources
    version
    notice.txt
    src/main/webapp
    webapp
    .
    :*${project.artifactId}*:
    lib
    :*${project.artifactId}*:

3、启动和停止脚本

  • 1、启动脚本start.sh

    主要是:找deploydir、jps判断程序是否已经启动、拼接lib下jar和config到CLASS_PATH

    #!/bin/bash  # 获取关键路径:BIN_DIR 和 DEPLOY_DIR  cd `dirname $0`  BIN_DIR=`pwd`  cd ..  DEPLOY_DIR=`pwd`  # 创建logs文件夹  mkdir -p $DEPLOY_DIR/logs  STDOUT_FILE=$DEPLOY_DIR/logs/stdout.log  # main类,程序入口 并且 从conf.properties中获取程序名和端口  MAIN_CLASS=com.yuanmei.caiwutong.JettyServiceStarter  APPLICATION_NAME="JettyServiceStarter"  APPLICATION_PORT=`cat config/caiwutong.properties | grep 'application.port=' | cut -d '=' -f2- | tr -d '\r'`  # 先判断此应用程序是否已经启动了以及端口是否被占用  PIDS=`jps | grep $APPLICATION_NAME | awk '{print $1}'`  if [ -n "$PIDS" ]; then      echo "ERROR: The $APPLICATION_NAME already started!"      echo "PID: $PIDS"      exit 1  fi  if [ -n "$APPLICATION_PORT" ]; then      SERVER_PORT_COUNT=`netstat -tln | grep $APPLICATION_PORT | wc -l`      if [ $SERVER_PORT_COUNT -gt 0 ]; then          echo "ERROR: The $APPLICATION_NAME port $APPLICATION_PORT already used!"          exit 1      fi  fi  # 将config文件夹和lib下jar拼接到--classpath中。  # config 放在前面,是因为如下两种方式都是顺序查找--classpath,找到就返回  #   classpath:/db/db.properties  #   xxx.class.getResourceAsStream("/db/db.properties")  # 将config拼接到--classpath中,那么logger使用class.getResource("logback.xml")能够找到  # tr 表示将前面的那个值替换为后面的那个值  CLASS_PATH=$DEPLOY_DIR/config  CLASS_PATH=$CLASS_PATH:`ls $DEPLOY_DIR/lib/*.jar | tr "\n" ":"`  CLASS_PATH=$CLASS_PATH`ls $DEPLOY_DIR/*.jar`  # jvm参数  JAVA_OPTS=" -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true "  # 开启debug  JAVA_DEBUG_OPTS=""  if [ "$1" = "debug" ]; then      JAVA_DEBUG_OPTS=" -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n "  fi  # 开启远程jmx,也就是可以使用jconsole进行连接  JAVA_JMX_OPTS=""  if [ "$1" = "jmx" ]; then      JAVA_JMX_OPTS=" -Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false "  fi  #内存以及gc的配置  JAVA_MEM_OPTS=" -server -Xmx2g -Xms2g -Xmn256m -XX:PermSize=128m -Xss256k -XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:LargePageSizeInBytes=128m -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 "  # 启动应用程序  echo -e "Starting the $APPLICATION_NAME ...\c"  nohup java $JAVA_OPTS $JAVA_DEBUG_OPTS $JAVA_JMX_OPTS $JAVA_MEM_OPTS -classpath $CLASS_PATH $MAIN_CLASS $APPLICATION_PORT $1> $STDOUT_FILE 2>&1 &  # 循环判断是否有对应的pid(也就是是否启动成功)  while true; do      sleep 1      echo -e ".\c"      PIDS=`jps | grep $APPLICATION_NAME | awk '{print $1}'`      if [ -n PIDS ]; then          break      fi  done  echo "OK!"  echo "PID: $PIDS"  echo "STDOUT: $STDOUT_FILE"
  • 2、stop.sh脚本 ==> 和纯java工程一模一样,都是jps找出pid,然后kill -9

    主要是找路径、jps判断程序是否已经启动

    #!/bin/bash  # 先获取关键位置BIN_DIR和DEPLOY_DIR  cd `dirname $0`  BIN_DIR=`pwd`  cd ..  DEPLOY_DIR=`pwd`  # 先停止监控脚本  monitorPidArr=($(ps aux | grep $BIN_DIR/monitorRestart.sh | awk '{print $2}'))  if [ ${#monitorPidArr[*]} -gt 1 ]; then      ps aux | grep monitorRestart.sh | grep -v "S+" | awk '{print $2}' | xargs kill -9  fi  # 给定应用名字(和start.sh配置一样)  APPLICATION_NAME="JettyServiceStarter"  #  通过应用名字来获取pid,最后kill(下面是通用的,不用改了,只要配置上面的APPLICATION_NAME即可)  PIDS=`jps | grep $APPLICATION_NAME | awk '{print $1}'`  if [ -z "$PIDS" ]; then      echo "ERROR: The $APPLICATION_NAME does not started!"      exit 1  fi  echo -e "Stopping the $APPLICATION_NAME ...\c"  while true; do      echo -e ".\c"      PIDSTEMPS=`jps | grep $APPLICATION_NAME | awk '{print $1}'`      if [ -n "$PIDSTEMPS" ]; then          for PID in $PIDSTEMPS ; do              kill -9 $PID > /dev/null 2>&1          done      else          break;      fi      sleep 1  done  echo "OK!"  echo "PID: $PIDS"

4、如何找到配置文件?

  • 4.1、使用xxx.class.getResource("/db/db.properties")

    任何时候都可以在代码中使用这种方式来找到配置文件,并且获取流来使用。

    它使用了java启动命令中--classpath参数的路径,逐个查找,找到就返回,所以如果想优先查找config文件夹,则需要将config拼接在--classpath最前面。

    代码使用例子:

  • 4.2、spring工程使用classpath:/db/db.properties

    内部还是使用了上面的class.getResource()来加载配置文件。找到就返回,不会继续找,所以同样的如果想要优先使用config的配置文件,需要将config拼接在--classpath的最前面

    内部实现源码:

  • 4.3、使用user.dir系统参数来获取(不通用)

    上面两种情况都是通过--classpath的路径来获取配置文件,所以必须将config文件夹放到--classpath中,但是user.dir不需要。

    user.dir的路径指的是用户工作空间,也就是start.sh脚本在启动java命令之前,cd到哪里就是哪里,一般都会先cd到部署目录才启动java命令

    cd到部署目录,这样就可以使用如下命令获取路径:

    String dbPath = System.getProperty("user.dir") + "/config/db/db.properties";

    可以发现,有一个缺陷,需要拼接config,如果不打包,直接在工程里面调试,那么还需要

    1、设置user.dir System.setProperty("user.dir", "e:/javaproject/src/main/resources");

    2、去除config这段,因为resources下面是没有config这一层的。

|||||||||||||||||||||||||||||||||||||| ||||||||||||||||||||||||||||||||||||||

5、代码编程方面的一些经验

  • 1、@Scope("prototype") 实现多例

  • 2、@authwrite注解的全局变量不能设置为static,否则注解不进来,为null

  • 3、关于profile

    1、web工程,需要在web.xml中配置
    spring.profiles.default
    production
    2、如果是spring-java工程需要在main方法中设置 System.setProperty("spring.profiles.active", "production");
  • 4、关于classpath: 和 classpath*:

    classpath:notice*.txt                    加载不到资源  classpath*:notice*.txt                   加载到resource根目录下notice.txt  classpath:META-INF/notice*.txt           加载到META-INF下的一个资源  				                        (classpath是加载到匹配的第一个资源,就算删除classpath下的notice.txt,他仍然可以加载jar包中的notice.txt)  classpath:META-*/notice*.txt             加载不到任何资源  classpath*:META-INF/notice*.txt          加载到classpath以及所有jar包中META-INF目录下以notice开头的txt文件  classpath*:META-*/notice*.txt            只能加载到classpath下 META-INF目录的notice.txt  经验配置:将config文件夹路径拼接到CLASS_PATH中,就可统一使用classpath:/db/db.properties  (强烈推荐这样处理)
  • 5、关于mybatis

    • 5.1、Mapper.xml配置项设置,里面的一定是Mapper.xml这种mybatis认识的格式文件,比如如果配置为<property name="mapperLocations" value="classpath:/mybatis/"/>,刚好mybatis文件夹下有一个logback.xml的文件,它不符合mybatis认识的Mapper.xml格式,就会如下错误

      Caused by: org.xml.sax.SAXParseException; lineNumber: 2; columnNumber: 16; 文档根元素 "configuration" 必须匹配 DOCTYPE 根 "null"

      所以一定要加上*Mapper.xml确保全部都是mybatis需要的xml文件

    • 5.2、entity的配置也需要注意,com.yuanmei.caiwutong.entity中必须包含所有Mapper.xml配置文件中需要用到的类,否则即使你的工程中有对应的java类,还是会报:classnotfound错误。比如某个Mapper.xml中需要一个com.yuanmei.caiwutong.response.UserOV这个类,由于不在entity包中,所以会报错,此时必须将response包放在entity包中

    • 5.3、定义mybatis标签模板,方便后面的sql拼接,比如表的字段

      account, `name`, passwd, gender, phone, `identity`, `job`, addr, interest, privilege, headPath, motto, createTime
      id,
    • 5.4、save和msave时,设置useGeneratedKeys="true" keyProperty="自增字段名"取回自增id设置给当前实例对象。msave需要mybatis3.3.1之后的版本才支持。

      INSERT INTO user_info (
      ) VALUES (#{account}, #{name}, #{passwd}, #{gender}, #{phone}, #{identity}, #{job}, #{addr}, #{interest}, #{privilege}, #{headPath}, #{motto}, now())
      INSERT INTO user_info (
      ) VALUES
      (#{item.account}, #{item.name}, #{item.passwd}, #{item.gender}, #{item.phone}, #{item.identity}, #{item.job},#{item.addr}, #{item.interest}, #{item.privilege}, #{item.headPath}, #{item.motto}, now())
    • 5.5、Dao类中的接口(注意返回值类型一定是Long,而不是long,因为有可能没有找到任何的值,为null,无法转化为long)

    • 5.6、MySQL date 类型只精确到秒,不精确到毫秒,所以在做时间等于查询的时候,不能够使用new Date()对象去比较

    • 5.7、如果值传递一个参数,可以使用任何类型:int string long 等,而且不需要名字对应 List<TControlWarning> findValidConByType(String varl);

      其他单参数也是这样

    • 5.8、传递多个参数(也可以用map)List<TControlWarning> findBySameParam(@Param("name") String var1, @Param("age") int var2);

  • 6、代码中读取配置文件properties中的参数

    • 6.1、xml加载properties配置文件

    • 6.2、代码中使用@Value来获取

      @Value("${stat.amount.total.cron.one:0/3 * * * * ?}")  private String cronExps;
  • 7、quartz定时调度方法的写法

    • 7.1、xml配置文件中扫描调度方法

    • 7.2、代码调度

      @Scheduled(cron = "${stat.amount.total.cron.one}")  public void amountJobTest1() {      LOG.info("第一个schedule,appName : {}, basePath : {}", appName, appPort);  }
  • 8、关于读写分离实现(读写分离中间件,比如“mysql-proxy和Amoeba for MySQL”或者自己在业务层写代码,如下)

    • 8.1、首先数据库一定要先配置主从结构,具体的请自行百度

    • 8.2、application-db.xml配置多个数据源、并且使用spring提供的AbstractRoutingDataSource来组织,这个类需要实现

      1:)实现类DynamicDataSource.java,DataSourceAspect.getDataSource()是获取8.3中保存在ThreadLocal的值(master或者slave)

      public class DynamicDataSource extends AbstractRoutingDataSource {      @Override      protected Object determineCurrentLookupKey() {          return DataSourceAspect.getDataSource();      }  }

      2:)application-db.xml(),使用上面的实现类DynamicDataSource来组织所有数据源

    • 8.3、现在的问题就是如何才能够让DynamicDataSource知道到底用那个数据源:1、设置aop(内部原理是动态代理和反射)这样就能够知道当前执行的service的那个方法;2、需要在每一个service层方法的上面加上自定义的一个注解@DataSource(value = "master"),因为如果这个方法上面没有任何东西,即使动态代理得到这个方法信息,还是不知道这个方法到底是用master还是slave;3、最后定义一个类来处理动态代理得到的方法,获取它上面的DataSource注解,得到值,保存在ThreadLocal中给DynamicDataSource使用

      1:)先定义一个注解

      @Retention(RetentionPolicy.RUNTIME)  @Target(ElementType.METHOD)  public @interface DataSource {      String value() default "master";  }

      2:)在service层的方法上加上DataSource注解,一般save用master,find用slave

      @DataSource(value = "master")  public CommonResult save(UserInfo userInfo, UserInfo loginUser) {  }

      3:)设置aop,动态代理所有service层的方法,同样在application-db.xml配置文件中

      3:)从上面的配置可以发现aop得到的代理方法信息会给DataSourceAspect处理,这里是关键。1、反射方法上的DataSource值,保存在ThreadLocal中供DynamicDataSource使用,具体如下代码:

      package com.yuanmei.caiwutong.datasource;  import org.apache.commons.lang3.StringUtils;  import org.aspectj.lang.JoinPoint;  import org.aspectj.lang.reflect.MethodSignature;  import org.slf4j.Logger;  import org.slf4j.LoggerFactory;  import java.lang.annotation.Annotation;  import java.lang.reflect.Method;  import java.lang.reflect.Parameter;  import java.util.concurrent.atomic.AtomicInteger;  /**   * 面向切面的类   * 通过before方法,解析本次service方法上的DataSource注解,得出是master还是slave,同时保存到ThreadLocal中,供DynamicDataSource获取   * 通过ThreadLocal获取当前线程需要处理DataSource的类型(master或者是slave)   *   * @Author liufu   * @CreateTime 2018/3/13  10:31   */  public class DataSourceAspect {      private static final Logger LOGGER = LoggerFactory.getLogger(DataSourceAspect.class);      private AtomicInteger masterIndex = new AtomicInteger(0);      private AtomicInteger slaveIndex = new AtomicInteger(0);      private String[] masterArr;      private String[] slaveArr;      private int masterLength;      private int slaveLength;      private static final ThreadLocal
      THREAD_LOCAL = new ThreadLocal
      (); public DataSourceAspect() { } public DataSourceAspect(String masterPool, String slavePool) { if (StringUtils.isNotBlank(slavePool)) { String splitStr = ","; if (slavePool.contains("|")) { splitStr = "\\|"; } else if (slavePool.contains(";")) { splitStr = ";"; } else if (slavePool.contains("&&")) { splitStr = "&&"; } masterArr = masterPool.split(splitStr); slaveArr = slavePool.split(splitStr); for (int i = 0; i < masterArr.length; i++) { masterArr[i] = masterArr[i].trim(); } for (int i = 0; i < slaveArr.length; i++) { slaveArr[i] = slaveArr[i].trim(); } masterLength = masterArr.length; slaveLength = slaveArr.length; } } /** * 绑定当前线程数据源 * * @param datasource */ public static void putDataSource(String datasource) { THREAD_LOCAL.set(datasource); } /** * 获取当前线程的数据源 * * @return */ public static String getDataSource() { return THREAD_LOCAL.get(); } /** * service方法在调用dao层方法前,解析方法上的DataSource注解得到master还是master * 分两种情况,service层有接口,和没有接口 */ public void methodBefore(JoinPoint point) { // 获取方法信息 MethodSignature signature = (MethodSignature) point.getSignature(); Method method = signature.getMethod(); String methodName = method.getName(); Class
      [] parameterTypes = method.getParameterTypes(); Parameter[] parameters = method.getParameters(); // 获取方法上的注解信息 Annotation[] annotations = method.getAnnotations(); DataSource annotation = method.getAnnotation(DataSource.class); boolean flag = method.isAnnotationPresent(DataSource.class); //判断方法所在类到底是接口还是业务实现类 Class
      targetClass = point.getTarget().getClass(); if (targetClass.isInterface()) { //接口改怎么做 } else { //业务实现类该怎么做 } if (annotation != null) { String value = annotation.value(); String dataSource = null; if ("master".equalsIgnoreCase(value)) { int index = masterIndex.getAndIncrement(); if (index > 999) { index = 0; masterIndex.set(0); } dataSource = masterArr[index % masterLength]; } else { int index = slaveIndex.getAndIncrement(); if (index > 999) { index = 0; slaveIndex.set(0); } dataSource = slaveArr[index % slaveLength]; } LOGGER.info("用户选择数据库库类型:" + dataSource); DataSourceAspect.putDataSource(dataSource); // 数据源放到当前线程中 } } /** * 在方法调用完成后,执行此方法进行通知打印 */ public void methodAfter(JoinPoint point) { //获取方法,然后获取方法上的DataSource注解,得到master或者是slave Method method = ((MethodSignature) point.getSignature()).getMethod(); LOGGER.info("service 层方法:{},执行完毕, 调用了:{} 数据库", method.getName(), DataSourceAspect.getDataSource()); } }
  • 9、swagger前后端联调利器

    通过swagger可以在页面上直接浏览到后端的所有接口,能够直接在上面输入参数去测试后端的接口情况。

    • 9.1、maven依赖

      io.springfox
      springfox-swagger2
      ${springfox.version}
      io.springfox
      springfox-swagger-ui
      ${springfox.version}
    • 9.2、配置swaggerConfig,来启动swagger扫描web包,从而生成json描述信息供swagger-ui来访问获取

      package com.yuanmei.caiwutong.config;  import org.springframework.beans.factory.annotation.Value;  import org.springframework.context.annotation.Bean;  import org.springframework.context.annotation.Configuration;  import springfox.documentation.annotations.ApiIgnore;  import springfox.documentation.builders.ApiInfoBuilder;  import springfox.documentation.builders.PathSelectors;  import springfox.documentation.builders.RequestHandlerSelectors;  import springfox.documentation.service.ApiInfo;  import springfox.documentation.spi.DocumentationType;  import springfox.documentation.spring.web.plugins.Docket;  import springfox.documentation.swagger2.annotations.EnableSwagger2;  /**   * swagger扫描配置,只要自动扫描到就行   *   * @Author liufu   * @CreateTime 2018/1/29  21:04   */  @Configuration   // 配置注解,自动在本类上下文加载一些环境变量信息  @EnableSwagger2  // 开启swagger2  public class SwaggerConfig {      /**       * 正是因为加了@Configuration,才能够拿到application.name这个配置值       */      @Value("${application.name}")      private String applicationName;      @Bean      public Docket productApi() {          return new Docket(DocumentationType.SWAGGER_2)                  .apiInfo(apiInfo())                  .select()                  .apis(RequestHandlerSelectors.basePackage("com.yuanmei.caiwutong.web"))                  .paths(PathSelectors.any())                  .build()                  .ignoredParameterTypes(ApiIgnore.class)                  .enableUrlTemplating(false);      }      private ApiInfo apiInfo() {          System.out.println(applicationName);          return new ApiInfoBuilder()                  .title("源美财务通项目")                  .description("模块: spring-mvcweb-project, 平台页面 Restful 接口说明.")                  .version("4.0")                  .build();      }  }
    • 9.3、需要在application-webmvc.xml中配置信息扫描它,同时由于swagger-ui.html页面是springfox-swagger-ui这个jar提供的,所以还需要配置静态资源映射,否则访问不到

    • 9.4、最后需要在web.xml中配置servlet拦截器拦截所有的路径/,尝试过拦截swagger-ui.html 和 /webjars不成功,最后设置了/才能成功,如果有解决此问题的兄弟麻烦下面留言,谢谢!

      servelet
      /
  • 10、原本的war工程基础上套jetty容器

    • 10.1、引入jetty依赖

      org.eclipse.jetty.aggregate
      jetty-all-server
      8.2.0.v20160908
    • 10.2、pom.xml的build部分使用上面的打包方式,将工程打成bin、sbin、config、lib、version、webapp的格式

    • 10.3、JettyServiceStarter启动类配置webapp路径即可

      package com.yuanmei.caiwutong;  import org.eclipse.jetty.server.Server;  import org.eclipse.jetty.webapp.WebAppContext;  /**   * 通过启动嵌入式的jetty,然后由jetty解析web.xml配置文件   * 由于web.xml配置了spring和springMVC,所以最终能够把整个springmvc工程启动起来   *   * @Author liufu   * @CreateTime 2018/1/25  11:32   */  public class JettyServiceStarter {      public static void main(String[] args) throws Exception {          //在idea启动测试时,设置user.dir。在Linux上就不需要了,因为start.sh脚本启动时user.dir会设置正确  //        System.setProperty("user.dir", "E:\\workplace\\chinaOpenGit\\spring-mvcweb-jetty-separation\\src\\main");          int port = 8080;          if (args.length > 0){              port = Integer.parseInt(args[0]);          }          // 创建服务器,并设置监听端口          Server server = new Server(port);          // 关联一个已经存在的上下文          WebAppContext context = new WebAppContext();          // 设置上下文路径          context.setContextPath("/caiwutong");          // 设置Web内容上下文路径(所以assembly.xml需要将webapp文件夹打包出来)          context.setResourceBase(System.getProperty("user.dir") + "/webapp");          /**           * 设置描述符位置(通过web.xml,jetty就可以启动spring和springmvc)           *           * ===================注意:=======================================           * 默认这个参数是可以不配置的,只要给定上面的webapp,那么他就知道webapp下面有一个WEB-INF/web.xml           * 但是如果把web.xml 改为了如下的webtest.xml,程序虽然能启动,但是由于找不到web.xml,所以没法构建spring和springmvc           * 这个时候就需要指定这个描述文件是在哪里了           */  //        context.setDescriptor(System.getProperty("user.dir") + "/webapp/WEB-INF/webtest.xml");          context.setParentLoaderPriority(true);          server.setHandler(context);          try {              server.start();          } catch (Exception e) {              e.printStackTrace();          }          System.out.println("server is  start");      }  }

转载于:https://my.oschina.net/liufukin/blog/2245330

你可能感兴趣的文章
python 写json格式字符串到文件
查看>>
分布式文件系统MogileFS
查看>>
Java23种设计模式案例:策略模式(strategy)
查看>>
XML解析之DOM4J
查看>>
图解微服务架构演进
查看>>
SQL PATINDEX 详解
查看>>
一些常用的网络命令
查看>>
CSP -- 运营商内容劫持(广告)的终结者
查看>>
DIV+CSS命名规范有助于SEO
查看>>
web项目buildPath与lib的区别
查看>>
我的友情链接
查看>>
ifconfig:command not found的解决方法
查看>>
计算机是怎么存储数字的
查看>>
Codeforces Round #369 (Div. 2) A. Bus to Udayland 水题
查看>>
adb上使用cp/mv命令的替代方法(failed on '***' - Cross-device link解决方法)
查看>>
C++标准库简介、与STL的关系。
查看>>
Spring Boot 3 Hibernate
查看>>
查询EBS请求日志的位置和名称
查看>>
大型机、小型机、x86服务器的区别
查看>>
J2EE十三个规范小结
查看>>