当前位置: 首页 > news >正文

1、工程构建、打包的一些经验

2019独角兽企业重金招聘Python工程师标准>>> 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部分

      <build>
          <!--
              本工程的jar包名字,默认就是下面这个
              但是assembly插件提取依赖时,即使下面设置为aaa,抽取dependencySets时一定是${project.artifactId}-${project.version}
          -->
          <finalName>${project.artifactId}-${project.version}</finalName>
    
          <plugins>
              <!-- java编译插件 -->
              <plugin>
                  <groupId>org.apache.maven.plugins</groupId>
                  <artifactId>maven-compiler-plugin</artifactId>
                  <version>3.2</version>
                  <configuration>
                      <source>1.8</source>
                      <target>1.8</target>
                      <encoding>UTF-8</encoding>
                  </configuration>
              </plugin>
    
              <!--设置jar插件,将需要修改的配置文件从jar中删除-->
              <plugin>
                  <groupId>org.apache.maven.plugins</groupId>
                  <artifactId>maven-jar-plugin</artifactId>
                  <configuration>
                      <!--
                          对于文件,直接用文件名即可
                          对于文件夹:
                          如果要删除文件夹下的某个文件/mybatis/XXXMapper.xml
                          如果要删除整个文件夹db/ 注意:/不能少,否则会被认为是文件,而不是文件夹
                                  注意:不要写成db/*  这样还是会有一个db空文件夹
                      -->
                      <excludes>
                          <exclude>db/db.properties</exclude>
                          <exclude>quartz/job.properties</exclude>
                          <exclude>caiwutong.properties</exclude>
                          <exclude>logback.xml</exclude>
                          <exclude>notice.txt</exclude>
                      </excludes>
    
                      <!--指定主类,一般都不需要指定,只需要在Java命令启动的时候指定即可-->
                      <!--除非是在window下双击执行-->
                      <!--<archive>-->
                          <!--<manifest>-->
                              <!--<addDefaultImplementationEntries>true</addDefaultImplementationEntries>-->
                              <!--<addClasspath>true</addClasspath>-->
                              <!--<mainClass>com.surfilter.pscms.timer.Timer</mainClass>-->
                          <!--</manifest>-->
                          <!--<manifestEntries>-->
                              <!--<Class-Path>.</Class-Path>-->
                          <!--</manifestEntries>-->
                      <!--</archive>-->
    
                  </configuration>
              </plugin>
    
              <!--
                  配置assembly插件,默认的版本是pom4.0里面的2.2-beta-5
                  但是这个版本对dependencyManagement的传递依赖在dependencySets抽取依赖时有bug
                  总是使用dependencyManagement中的依赖,而不是额外指定的版本
                  这个bug在2.3以上的版本修复了,一般使用2.6,如下
              -->
              <plugin>
                  <groupId>org.apache.maven.plugins</groupId>
                  <artifactId>maven-assembly-plugin</artifactId>
                  <version>2.6</version>
                  <configuration>
                      <!-- 这个是最终打包出来的压缩文件名字,${maven.build.timestamp}是加上一个时间戳 -->
                      <finalName>${project.finalName}</finalName>
                      <descriptor>assembly/assembly.xml</descriptor>
                      <!--最终zip包名是否拼接assemblyID名称,默认是加上的-->
                      <!--加上后就变成:  spring-mvcweb-jetty-1.0-assembly.zip-->
                      <appendAssemblyId>false</appendAssemblyId>
                  </configuration>
                  <executions>
                      <execution>
                          <phase>package</phase> <!-- 绑定到package生命周期阶段上 -->
                          <goals>
                              <goal>single</goal>  <!-- 只运行一次 -->
                          </goals>
                      </execution>
                  </executions>
              </plugin>
          </plugins>
      </build>
    
  • 2、assembly.xml

      <!--
          - 1、任何从main方法启动的工程,都可以使用assembly进行打包,形成: bin   config  lib  version 的标准格式
    
               在java -classpath 中拼接上config,这样就可以直接使用
               classpath:/db/db.properties
               xxx.class.getResourceAsStream("/db/db.properties")
    
          - 2、路径是相对于工程根目录的,直接写即可。比如:bin 和 src/main/resources
    
          - 3、依赖打包,默认使用pom4.0的2.2-beta-5版本assembly插件
               但是此版本对传递依赖有bug,比如如果dependencyManagement里面包含了某依赖,总是会使用dependencyManagement中的依赖,没法额外指定
               同时对于下面的文件权限无效,即使设置了0755,还是会设置为777
               这些问题在2.3及以后版本修复了,一般打包都是用2.6版本
      -->
      <assembly>
          <id>assembly</id>
          <formats>
              <format>zip</format>
          </formats>
          <!--如果为false,那么解压后就是bin 、 config、 lib、 version。外面没有工程名称-->
          <includeBaseDirectory>true</includeBaseDirectory>
    
          <fileSets>
              <!--bin配置-->
              <fileSet>
                  <directory>bin</directory>
                  <outputDirectory>bin</outputDirectory>
                  <fileMode>0755</fileMode>
              </fileSet>
    
              <!--sbin配置-->
              <fileSet>
                  <directory>sbin</directory>
                  <outputDirectory>sbin</outputDirectory>
                  <fileMode>0755</fileMode>
              </fileSet>
    
              <!--config设置-->
              <fileSet>
                  <directory>src/main/resources</directory>
                  <outputDirectory>config</outputDirectory>
                  <!--
                      对于文件,直接用文件名即可
                      对于文件夹:
                      如果要删除文件夹下的某个文件mybatis/XXXMapper.xml
                      如果要删除整个文件夹mybatis/ 注意:/不能少,否则会被认为是文件,而不是文件夹
                              注意:不要写成mybatis/*  这样还是会有一个mybatis空文件夹
                  -->
                  <excludes>
                      <exclude>db/*.xml</exclude>
                      <exclude>db/*.sql</exclude>
                      <exclude>mybatis/</exclude>
                      <exclude>quartz/*.xml</exclude>
                      <exclude>spring/</exclude>
                      <exclude>notice.txt</exclude>
                  </excludes>
              </fileSet>
    
              <!--version设置-->
              <fileSet>
                  <directory>src/main/resources</directory>
                  <outputDirectory>version</outputDirectory>
                  <includes>
                      <include>notice.txt</include>
                  </includes>
              </fileSet>
    
              <!-- 抽取webapp,包括html和WEN-INF以及下面的web.xml (这些是jetty容器需要设置的)-->
              <fileSet>
                  <directory>src/main/webapp</directory>
                  <outputDirectory>webapp</outputDirectory>
              </fileSet>
          </fileSets>
    
          <!--lib配置,把工程放在根目录下,其他依赖包放在lib下-->
          <dependencySets>
              <dependencySet>
                  <outputDirectory>.</outputDirectory>
                  <includes>
                      <include>:*${project.artifactId}*:</include>
                  </includes>
              </dependencySet>
              <dependencySet>
                  <outputDirectory>lib</outputDirectory>
                  <excludes>
                      <exclude>:*${project.artifactId}*:</exclude>
                  </excludes>
              </dependencySet>
          </dependencySets>
      </assembly>
    

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

      <beans profile="production">
          <import resource="classpath:/db/applicationContext-db.xml"/>
          <import resource="classpath:/quartz/applicationContext-job.xml"/>
      </beans>
    
      <beans profile="test">
          <import resource="classpath:/db/applicationContext-dbmemory.xml"/>
          <import resource="classpath:/quartz/applicationContext-job.xml"/>
      </beans>
    
      1、web工程,需要在web.xml中配置
      	<!-- 指定部署模式为production -->
          <context-param>
              <param-name>spring.profiles.default</param-name>
              <param-value>production</param-value>
          </context-param>
    
      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文件

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

        <property name="typeAliasesPackage" value="com.yuanmei.caiwutong.entity"/>
      
    • 5.3、定义mybatis标签模板,方便后面的sql拼接,比如表的字段

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

        <!--只有设置了useGeneratedKeys="true" keyProperty="id"才能够获得主键ID,注意:这个是在mybatis3.3.1版本之后才支持-->
        <insert id="save" parameterType="UserInfo" useGeneratedKeys="true" keyProperty="id">
            INSERT INTO user_info (<include refid="allColWithoutId"/>)
            VALUES (#{account}, #{name}, #{passwd}, #{gender}, #{phone}, #{identity}, #{job}, #{addr}, #{interest}, #{privilege}, #{headPath}, #{motto}, now())
        </insert>
      
        <!--只有设置了useGeneratedKeys="true" keyProperty="id"才能够获得主键ID,注意:这个是在mybatis3.3.1版本之后才支持-->
        <insert id="msave" parameterType="list"  useGeneratedKeys="true" keyProperty="id">
            INSERT INTO user_info (<include refid="allColWithoutId"/>) VALUES
            <foreach collection="list" item="item" separator=",">
                (#{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())
            </foreach>
        </insert>
      
    • 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);

        <select id="findBySameParam" parameterType="string" resultType="TControlWarning">
        SELECT <include refid="allColumns" /> FROM user
           WHERE `user.name` = #{name} AND `user.age` = #{age}
        </select>
      
  • 6、代码中读取配置文件properties中的参数

    • 6.1、xml加载properties配置文件

        <context:property-placeholder location="classpath:/caiwutong.properties" ignore-unresolvable="true"/>
      
    • 6.2、代码中使用@Value来获取

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

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

        <!--开启这个配置,spring才能识别@Scheduled注解-->
        <task:annotation-driven executor="timerExecutor" scheduler="timerScheduler"/>
        <task:executor id="timerExecutor" pool-size="10"/>
        <task:scheduler id="timerScheduler" pool-size="10"/>
      
        <!-- 自动扫描注解的Scheduled -->
        <context:component-scan base-package="com.yuanmei.caiwutong.quarzjob"/>
      
    • 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来组织所有数据源

        <!-- 定义数据源:dbcp,cp30,boneCpDataSource  ==>也就是如何连接数据库,连接哪个数据库等 -->
        <bean id="master" class="com.jolbox.bonecp.BoneCPDataSource" destroy-method="close">
        	<property name="driverClass" value="${master.jdbc.driverClassName}"/>
        	<property name="jdbcUrl" value="${master.jdbc.url}"/>
        	<property name="username" value="${master.jdbc.username}"/>
        	<property name="password" value="${master.jdbc.password}"/>
        	<!-- 每个分区最大的连接数 -->
        	<!--判断依据:请求并发数-->
        	<property name="maxConnectionsPerPartition" value="100"/>
        	<!-- 每个分区最小的连接数 -->
        	<property name="minConnectionsPerPartition" value="5"/>
        </bean>
        <bean id="slave0" class="com.jolbox.bonecp.BoneCPDataSource" destroy-method="close">
        	<property name="driverClass" value="${slave1.jdbc.driverClassName}"/>
        	<property name="jdbcUrl" value="${slave1.jdbc.url}"/>
        	<property name="username" value="${slave1.jdbc.username}"/>
        	<property name="password" value="${slave1.jdbc.password}"/>
        	<property name="maxConnectionsPerPartition" value="100"/>
        	<property name="minConnectionsPerPartition" value="5"/>
        </bean>
        <bean id="slave1" class="com.jolbox.bonecp.BoneCPDataSource" destroy-method="close">
        	<property name="driverClass" value="${slave2.jdbc.driverClassName}"/>
        	<property name="jdbcUrl" value="${slave2.jdbc.url}"/>
        	<property name="username" value="${slave2.jdbc.username}"/>
        	<property name="password" value="${slave2.jdbc.password}"/>
        	<property name="maxConnectionsPerPartition" value="100"/>
        	<property name="minConnectionsPerPartition" value="5"/>
        </bean>
      
        <!--定义一个动态DataSource,根据注解的master、slave1还是slave2来判断用什么数据源-->
        <bean id="dataSource" class="com.yuanmei.caiwutong.datasource.DynamicDataSource">
        	<property name="targetDataSources">
        		<map key-type="java.lang.String">
        			<!-- read or master   -->
        			<entry key="master" value-ref="master"/>
        			<!-- write or slave -->
        			<entry key="slave0" value-ref="slave0"/>
        			<entry key="slave1" value-ref="slave1"/>
        		</map>
        	</property>
        	<property name="defaultTargetDataSource" ref="master"/>
        </bean>
      
    • 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配置文件中

        <!--=================配置aop,实现DataSource的动态选择配置==============-->
        <!-- 解析service层方法上的@DataSource注解,从而得到master还是slave -->
        <bean id="dataSourceAspect" class="com.yuanmei.caiwutong.datasource.DataSourceAspect">
        	<constructor-arg name="masterPool" value="master"/>
        	<constructor-arg name="slavePool" value="slave0,slave1"/>
        </bean>
        <aop:config proxy-target-class="true">
        	<aop:aspect id="dataSourceAspect" ref="dataSourceAspect" order="2">
        		<!--注意,这里service之后一定是“两个点”-->
        		<aop:pointcut id="myPointcut" expression="execution(* com.yuanmei.caiwutong.service..*.*(..)) "/>
        		<aop:before pointcut-ref="myPointcut" method="methodBefore" />
        		<aop:after pointcut-ref="myPointcut" method="methodAfter" />
        	</aop:aspect>
        </aop:config>
      

      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<String> THREAD_LOCAL = new ThreadLocal<String>();
      
            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依赖

        <!-- swagger restful api begin -->
        <!-- 配置swaggerconfig,扫描controller层的api,生成一个json描述信息,http://localhost:8080/caiwutong/v2/api-docs -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>${springfox.version}</version>
        </dependency>
        <!-- 内置了swagger-ui.html,静态资源需要在applicationContext-webmvc.xml中配置静态资源映射 -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>${springfox.version}</version>
        </dependency>
        <!-- swagger restful api end -->
      
    • 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提供的,所以还需要配置静态资源映射,否则访问不到

        <!-- 扫描config,加载swagger等配置 -->
        <context:component-scan base-package="com.yuanmei.caiwutong.config"/>
      
        <!-- 由于swagger的页面swagger-ui.html在 springfox-swagger-ui-2.4.0.jar中提供,需要配置静态资源才能访问到 -->
        <mvc:resources mapping="swagger-ui.html" location="classpath:/META-INF/resources/"/>
        <mvc:resources mapping="/webjars/**" location="classpath:/META-INF/resources/webjars/"/>
      
        <!-- 如果web.xml中设置了servelet拦截为/,必须配置此项将静态资源进行映射 -->
        <mvc:default-servlet-handler/>
      
    • 9.4、最后需要在web.xml中配置servlet拦截器拦截所有的路径/,尝试过拦截swagger-ui.html 和 /webjars不成功,最后设置了/才能成功,如果有解决此问题的兄弟麻烦下面留言,谢谢!

        <servlet-mapping>
            <servlet-name>servelet</servlet-name>
      
            <!-- 指定那些请求进行拦截,这样swagger不能工作 -->
            <!--<url-pattern>*.do</url-pattern>-->
            <!--<url-pattern>*.params</url-pattern>-->
            <!--<url-pattern>swagger-ui.html</url-pattern>-->
            <!--<url-pattern>/webjars/**</url-pattern>-->
      
            <!--
                所有请求都被拦截,包括静态资源
                这样的话就不能够直接访问静态资源,而是需要请求controller,然后拼接上pre和fix才能够请求
                除非在application-webmvc.xml中配置<mvc:default-servlet-handler/>,表示把所有静态资源进行映射
            -->
            <url-pattern>/</url-pattern>
        </servlet-mapping>
      
  • 10、原本的war工程基础上套jetty容器

    • 10.1、引入jetty依赖

        <!--嵌入式jetty-->
        <dependency>
            <groupId>org.eclipse.jetty.aggregate</groupId>
            <artifactId>jetty-all-server</artifactId>
            <version>8.2.0.v20160908</version>
        </dependency>
      
    • 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

相关文章:

  • VI 你不知道的事
  • try{}----------catch{}的作用
  • Docker Compose 原理
  • SQLSERVER 里SELECT COUNT(1) 和SELECT COUNT(*)哪个性能好?
  • hfrk2410_a1.1开发板移植linux-2.6.32.27--网卡篇(cs8900)
  • VS2005相关----不能添加新项
  • nexus启动错报:1067 与jdk9相关
  • 谈谈VIP漂移那点破事
  • 程序员的几款利器
  • 数据库系统设计_银行业务管理系统
  • 云桌面、云课堂究竟是什么?企业追捧是坑还是福?
  • 对hibernate框架的认知及总结
  • 三个常用的PHP图表类库
  • 在android源码中单独编译modkoid工程遇见的问题
  • Hanlp中使用纯JAVA实现CRF分词
  • SegmentFault for Android 3.0 发布
  • [js高手之路]搞清楚面向对象,必须要理解对象在创建过程中的内存表示
  • [Vue CLI 3] 配置解析之 css.extract
  • 【css3】浏览器内核及其兼容性
  • ➹使用webpack配置多页面应用(MPA)
  • Effective Java 笔记(一)
  • 排序算法学习笔记
  • 如何抓住下一波零售风口?看RPA玩转零售自动化
  • 腾讯优测优分享 | Android碎片化问题小结——关于闪光灯的那些事儿
  • 微信小程序实战练习(仿五洲到家微信版)
  • 一加3T解锁OEM、刷入TWRP、第三方ROM以及ROOT
  • d²y/dx²; 偏导数问题 请问f1 f2是什么意思
  • 测评:对于写作的人来说,Markdown是你最好的朋友 ...
  • 国内开源镜像站点
  • ​LeetCode解法汇总1410. HTML 实体解析器
  • #Java第九次作业--输入输出流和文件操作
  • #使用清华镜像源 安装/更新 指定版本tensorflow
  • ${ }的特别功能
  • (10)STL算法之搜索(二) 二分查找
  • (4) openssl rsa/pkey(查看私钥、从私钥中提取公钥、查看公钥)
  • (保姆级教程)Mysql中索引、触发器、存储过程、存储函数的概念、作用,以及如何使用索引、存储过程,代码操作演示
  • (亲测有效)解决windows11无法使用1500000波特率的问题
  • (四)图像的%2线性拉伸
  • (转)Spring4.2.5+Hibernate4.3.11+Struts1.3.8集成方案一
  • *_zh_CN.properties 国际化资源文件 struts 防乱码等
  • .bat批处理出现中文乱码的情况
  • .FileZilla的使用和主动模式被动模式介绍
  • .NET MVC第五章、模型绑定获取表单数据
  • .net wcf memory gates checking failed
  • .net 获取url的方法
  • .NET 事件模型教程(二)
  • .NET 中 GetHashCode 的哈希值有多大概率会相同(哈希碰撞)
  • .Net 中Partitioner static与dynamic的性能对比
  • .NET序列化 serializable,反序列化
  • .NET中的Event与Delegates,从Publisher到Subscriber的衔接!
  • .skip() 和 .only() 的使用
  • @manytomany 保存后数据被删除_[Windows] 数据恢复软件RStudio v8.14.179675 便携特别版...
  • @RequestMapping处理请求异常
  • [【JSON2WEB】 13 基于REST2SQL 和 Amis 的 SQL 查询分析器
  • [2024最新教程]地表最强AGI:Claude 3注册账号/登录账号/访问方法,小白教程包教包会