SpringBoot webmvc项目导出war包并在外部tomcat运行产生的诸多问题以及解决方案

背景:

  有需求要将原来的Spring(3.2.6) + Springmvc + Hibernate项目重构为Springboot(1.5.2)项目

描述:

  记录重构过程,以及期间遇到的种种问题和对应的解决方案  

环境:

  原项目: win10 + eclipse + jdk1.8 + mysql5.7

  新项目: win10 + IDEA + jdk1.8 + mysql5.7 + Maven

过程:

  第一步:  新建Maven项目

    IDEA: project > New > Module > Maven (选择 maven-archetype-quickstart 快速创建一个maven项目, 如下图)

    

    点击Next, 自己想一个项目的 groupid(一般为项目域名的倒写) 和 artifactid(项目名) 并填好(如下图)

    

    点击Next, 确认创建信息

    点击Next, 选择项目创建文件夹地址

    点击确认自动创建项目

    项目创建就完成了

    如果发现创建maven项目十分缓慢, 很可能是由于访问maven官方中央仓库网速太差导致的,建议可以修改Maven的settings.xml文件

    将默认的仓库地址改为国内阿里云的地址(http://maven.aliyun.com/nexus/content/groups/public/),(如下图)

    

 

   第二步: 配置pom.xml

     不多说,上代码,如果对其中某些节点含义不清楚, 可以参考此博文: https://www.cnblogs.com/hafiz/p/5360195.html

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.yjy.test</groupId>
    <version>1.0-SNAPSHOT</version>
    <artifactId>yjyboot-${project.version}</artifactId>
    <name>yjyboot</name>
    <packaging>war</packaging>
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <log4j2.level>debug</log4j2.level>
        <log4j2.root.path>/logs/${project.name}</log4j2.root.path>
        <log4j2.error.path>/logs/${project.name}-error</log4j2.error.path>
        <log4j2.package.path>/logs/${project.name}-kk</log4j2.package.path>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.2.RELEASE</version>
    </parent>

    <dependencies>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <!--https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-log4j2-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-web -->
        <!-- 如果没有此 log4j-web 导出的war将不能打印日志到文件!!! -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-web</artifactId>
            <version>2.7</version>
        </dependency>

        <!-- freemarker -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>

        <!-- 下面两个引入为了操作数据库 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <!-- Json包 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.16</version>
        </dependency>

        <!-- 为了监控数据库 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.25</version>
        </dependency>

        <!-- commons -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.7</version>
        </dependency>

        <!-- httpclient -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpmime</artifactId>
            <version>4.5.3</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/commons-httpclient/commons-httpclient -->
        <dependency>
            <groupId>commons-httpclient</groupId>
            <artifactId>commons-httpclient</artifactId>
            <version>3.1</version>
        </dependency>

        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.5</version>
        </dependency>

        <!-- 兼容log4j -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-1.2-api</artifactId>
            <version>2.8.2</version>
        </dependency>

        <!-- Redis -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/nl.bitwalker/UserAgentUtils -->
        <dependency>
            <groupId>nl.bitwalker</groupId>
            <artifactId>UserAgentUtils</artifactId>
            <version>1.2.4</version>
        </dependency>

        <!--
            打war包时加入此项 告诉spring-boot tomcat相关jar包用外部的 不要打进去
            IDEA运行时需要将此依赖注释掉, 否则会无法运行
         -->
        <!--<dependency>-->
            <!--<groupId>org.springframework.boot</groupId>-->
            <!--<artifactId>spring-boot-starter-tomcat</artifactId>-->
            <!--<scope>provided</scope>-->
        <!--</dependency>-->

    </dependencies>

    <build>
        <finalName>${project.name}</finalName>
        <directory>target</directory>
        <sourceDirectory>src/main/java</sourceDirectory>
        <testSourceDirectory>src/test/java</testSourceDirectory>
        <outputDirectory>target</outputDirectory>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
                <includes>
                    <include>**/*</include>
                </includes>
            </resource>
            <!-- 将自定义的Servlet(extends DispatcherServlet)默认xml配置文件打包至WEB-INF下
            否则外部tomcat无法处理此Servlet -->
            <resource>
                <directory>src/main/extraConfig</directory>
                <targetPath>${build.finalName}/WEB-INF/</targetPath>
            </resource>
        </resources>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>build-info</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <!--spring-boot为了保护application.yml和application.properties,修改了默认的占位符${...}为@...@-->
            <!--为了spring boot的yml和properties文件能够使用maven变量替换,使用${}占位符-->
            <plugin>
                <artifactId>maven-resources-plugin</artifactId>
                <configuration>
                    <encoding>utf-8</encoding>
                    <useDefaultDelimiters>true</useDefaultDelimiters>
                </configuration>
            </plugin>

        </plugins>
    </build>

</project>
pom.xml

  

   第三步: 创建配置文件, 特别注意:  log4j2.xml 只能放在resources目录下, 否则导出的war包,配置的log4j2将不起作用!!!!

    

spring:
  profiles:
    active: dev # 激活的配置文件
    include: freemarker,mysql,redis,interceptor # 加载其他配置文件
  mvc:
    favicon:
      enabled: true

debug: true # 是否启用debug

server:
  servlet-path: /common # 所有接口请求都交由自定义的Servlet处理了, 所以默认的servlet只用于处理静态资源
application.yml
spring:
  freemarker:
    enabled: true # 是否启用freemarker
    cache: false # 是否启用缓存
    prefix: # 模板文件前缀
    suffix: .ftl # 模板文件后缀
    charset: UTF-8 # 模板文件编码
    template-loader-path: classpath:templates/ # 模板文件目录
    check-template-location: true # 是否检查模板目录是否存在
    content-type: text/html # 模板类型
    request-context-attribute: req # RequestContext 引用
    settings: # 更多配置
      number_format: '0.##'  #数字格式化, 保留两位小数
    allow-request-override: false # 是否允许 request 属性覆盖 controller 属性
    allow-session-override: false # 是否允许 session 属性覆盖 controller 属性
    expose-request-attributes: false # 设置在与模板合并之前,是否应该将所有HttpRequest属性添加到模型中。
    expose-session-attributes: false # 设置在与模板合并之前,是否应该将所有HttpSession属性添加到模型中。
    expose-spring-macro-helpers: true # 设置是否公开一个请求上下文,以供Spring的宏库使用,名称为“springMacroRequestContext”
    prefer-file-system-access: true # 更喜欢文件系统访问模板加载。文件系统访问支持对模板更改进行热检测。
#    view-names: # whitelist of view names that can be resolved
application-freemarker.yml
front:
  login:
    excludeUrls: # 这里配置的前台登入验证的白名单
      - /hello.sv
      - /hello2.sv
      - /index.jtk
      - /autho.jtk
      - /code.jtk
      - /checkLogin.jtk
      - /checkUser.jtk
      - /test.jtk
      - /wxPay/notify.jtk
      - /api/list.jtk
      - /api/deposit.jtk
      - /config/wechat.jtk

back:
  login:
    excludeUrls: # 这里配置的后台登入验证的白名单
      - /login.do
      - /logout.do
      - /game/scores.do
      - /game/dayScore.do
application-interceptor.yml
spring:
  datasource:
    # 数据库访问配置
    # 主数据源,默认的
    type: com.alibaba.druid.pool.DruidDataSource
    dbUrl: jdbc:mysql://localhost:3306/hotpot?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: yjy
    password: yyyyyy
    driverClassName: com.mysql.jdbc.Driver

    # 下面为连接池的补充设置,应用到上面所有数据源中
    # 初始化大小,最小,最大
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000 # 配置获取连接等待超时的时间
    timeBetweenEvictionRunsMillis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
    minEvictableIdleTimeMillis: 300000 # 配置一个连接在池中最小生存的时间,单位是毫秒
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true # 打开PSCache,并且指定每个连接上PSCache的大小
    maxPoolPreparedStatementPerConnectionSize: 20
    filters: stat,wall,log4j # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
    useGlobalDataSourceStat: true # 合并多个DruidDataSource的监控数据
  #JPA Configuration:
  jpa:
    database: MYSQL
    show-sql: true # Show or not log for each sql query
    generate-ddl: true # Hibernate ddl auto (create, create-drop, update)
    hibernate:
      ddl-auto: update
      naming:
        strategy: org.hibernate.cfg.ImprovedNamingStrategy

    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL5Dialect
application-mysql.yml
<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<configuration status="INFO" monitorInterval="30">
    <!--先定义所有的appender-->
    <appenders>
        <!--这个输出控制台的配置-->
        <console name="Console" target="SYSTEM_OUT">
            <!--输出日志的格式-->
            <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
            <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
            <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
        </console>
        <!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,这个也挺有用的,适合临时测试用-->
        <File name="CurrentLog" fileName="logs/current.log" append="false">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
        </File>
        <!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,
        则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
        <RollingFile name="RollingFileInfo" fileName="F:/logs/info.log"
                     filePattern="F:/logs/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log">
            <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
            <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
            <Policies>
                <TimeBasedTriggeringPolicy/>
                <SizeBasedTriggeringPolicy size="50MB"/>
            </Policies>
        </RollingFile>
        <RollingFile name="RollingFileWarn" fileName="F:/logs/warn.log"
                     filePattern="F:/logs/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log">
            <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
            <Policies>
                <TimeBasedTriggeringPolicy/>
                <SizeBasedTriggeringPolicy size="30MB"/>
            </Policies>
            <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件,这里设置了20 -->
            <DefaultRolloverStrategy max="20"/>
        </RollingFile>
        <RollingFile name="RollingFileError" fileName="F:/logs/error.log"
                     filePattern="F:/logs/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log">
            <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
            <Policies>
                <TimeBasedTriggeringPolicy/>
                <SizeBasedTriggeringPolicy size="20MB"/>
            </Policies>
        </RollingFile>
    </appenders>
    <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
    <loggers>
        <!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
        <logger name="org.springframework" level="INFO"/>
        <logger name="org.springframework.boot.autoconfigure.logging" level="INFO"/>
        <logger name="org.springframework.boot.logging" level="INFO"/>
        <logger name="org.mybatis" level="INFO"/>
        <logger name="org.hibernate" level="INFO"/>
        <logger name="druid.sql" level="INFO"/>
        <root level="info">
            <appender-ref ref="Console"/>
            <appender-ref ref="CurrentLog"/>
            <appender-ref ref="RollingFileInfo"/>
            <appender-ref ref="RollingFileWarn"/>
            <appender-ref ref="RollingFileError"/>
        </root>
    </loggers>
</configuration>
log4j2.xml
server:
  port: 8082 # 嵌入server的运行端口
  context-path: /yjyboot # 配置项目运行地址
spring:
  devtools:
    restart:
      exclude: classpath:common/**,classpath:templates/**
application-dev.yml
server:
  port: 8080
  context-path: /yjyboot # 导出war包存放在tomcat后会有一个项目运行地址, 这里配置可以模拟项目地址, 达到IDEA运行与tomcat运行地址相同
application-pro.yml

 

  第四步: 主类(一般放在根包中)

    

package com.yjy.test;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.boot.web.support.SpringBootServletInitializer;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

/**
 * SpringBoot 启动入口
 *
 * @Author yjy
 * @Date 2018-04-17 12:43
 */
//@SpringBootApplication = (@Configuration, @EnableAutoConfiguration, @ComponentScan)
@Configuration
@EnableAutoConfiguration
@ComponentScan
@EntityScan("com.yjy.test.game.entity") // 扫描实体类
@ServletComponentScan(basePackages = "com.yjy.test") // 扫描自定义Servlet
@PropertySource(value = { // 导入配置
        "classpath:/config/application.yml",
})
public class Application extends SpringBootServletInitializer {

    // IDEA运行时 运行此函数
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    // 导出war在外部tomcat使用时, 不能使用main函数运行, 需要配置此项
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(Application.class);
    }

}

    第五步: 添加配置类(按自己的需要添加, 无特别说明的情况下, 配置类可以存在任意包内, 只需满足包级别不高于Application.java所在的包就可以

      当然也可以通过配置扫描包注解来自定义, 默认扫描主类所在包以下的所有包)

      1: 全局跨域配置类(放在与Application.java同目录下)

package com.yjy.test;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

/**
 * 跨域配置
 *
 * @Author yjy
 * @Date 2018-04-26 15:55
 */
@Configuration
public class CorsConfig {

    private CorsConfiguration buildConfig() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.setAllowCredentials(true);
        return corsConfiguration;
    }

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", buildConfig());
        return new CorsFilter(source);
    }


}
跨域配置

 

      2: Date参数的格式化( 请求中符合格式的字符串参数可以使用Date类型接收参数, 比如请求参数 ?addTime=20180101, Controller层可以使用 func(Date addTime); 接收, 否则会报400错误)

package com.yjy.test;

import org.apache.commons.lang3.StringUtils;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;

import java.text.ParseException;
import java.text.SimpleDateFormat;

/**
 * Date参数格式化
 *
 * 尝试格式化java.util.Date类型的参数
 *
 * @Author yjy
 * @Date 2018-04-27 9:58
 */
@Component
public class DateConverterConfig implements Converter<String, java.util.Date> {

    private static final String[] formats = new String[] {
            "yyyy-MM-dd", // 0
            "yyyy-MM", // 1
            "yyyy-MM-dd HH:mm:ss", // 2
            "yyyy-MM-dd HH:mm", // 3
    };


    /**
     * 这里将参数格式化成 java.sql.Date 为了方便后面用来拼接sql
     * @param param 日期格式的字符串
     * @return java.sql.Date
     */
    @Override
    public java.sql.Date convert(String param) {
        if (StringUtils.isBlank(param)) {
            return null;
        }
        param = param.trim();
        if (param.matches("^\\d{4}-\\d{1,2}-\\d{1,2}$")) {
            return parseDate(param, formats[0]);
        }
        if (param.matches("^\\d{4}-\\d{1,2}$")) {
            return parseDate(param, formats[1]);
        }
        if (param.matches("^\\d{4}-\\d{1,2}-\\d{1,2} \\d{1,2}:\\d{1,2}:\\d{1,2}$")) {
            return parseDate(param, formats[2]);
        }
        if (param.matches("^\\d{4}-\\d{1,2}-\\d{1,2} \\d{1,2}:\\d{1,2}$")) {
            return parseDate(param, formats[3]);
        }
        throw new IllegalArgumentException("Invalid date param '" + param + "'");
    }

    /**
     * 格式化日期
     * @param dateStr 日期字符串
     * @param format 格式
     * @return 日期
     */
    private java.sql.Date parseDate(String dateStr, String format) {
        java.sql.Date date = null;
        try {
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format);
            java.util.Date dates = simpleDateFormat.parse(dateStr);
            date = new java.sql.Date(dates.getTime());
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return date;
    }

}
日期参数格式化配置

 

      3: 自定义指定请求前缀的Servlet(一个前台, 一个后台)

package com.yjy.test.game.web.servlet;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.DispatcherServlet;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 后台servlet
 * 需要添加对应的 *-servlet.xml
 *
 * @Author yjy
 * @Date 2018-04-23 16:26
 */
@WebServlet(name = "backServlet", urlPatterns = {"/manager/admin/*"})
public class CustomBackServlet extends DispatcherServlet {

    private static final Logger log = LoggerFactory.getLogger(CustomBackServlet.class);

    @Override
    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        log.info("backServlet doService...");
        super.doService(request, response);
    }

}
后台自定义Servlet
package com.yjy.test.game.web.servlet;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.DispatcherServlet;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


/**
 * 前台servlet
 * 需要添加对应的 *-servlet.xml
 *
 * @Author yjy
 * @Date 2018-04-23 16:26
 */
@WebServlet(name = "frontServlet", urlPatterns = {"/*"})
public class CustomFrontServlet extends DispatcherServlet {

    private static final Logger log = LoggerFactory.getLogger(CustomFrontServlet.class);

    @Override
    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        log.info("frontServlet doService...");
        super.doService(request, response);
    }

}
前台自定义Servlet

      对应的默认xml文件, 打包war的时候需要, 看pom.xml中相应配置, 否则到外部tomcat运行时, 会报找不到对应的配置文件的错误

      

      xml内容都是一样的

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!-- 此文件用于项目导出war包在外部tomcat运行时检测, 如果没有此文件, 则自定义Servlet无法访问 -->

</beans>
自定义Servlet默认配置

 

       4: 因为上面两个自定义的Servlet继承自DispatcherServlet, 不允许重写init()方法, 所以如果需要自定义初始化ServletContext, 则必须自己写一个Servlet继承HttpServlet,( 此Servlet不需要配置相应的xml文件)

package com.yjy.test.game.web.servlet;

import com.yjy.test.game.service.OptionItemService;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;

/**
 * 自定义初始化 ServletContext
 *
 * WebServlet中 urlPatterns必须填写, 否则不会加载此Servlet, 同时需要配置 loadOnStartup = 1
 *
 * @Author yjy
 * @Date 2018-05-02 11:47
 */
@WebServlet(urlPatterns = "", loadOnStartup = 1)
public class DictServlet extends HttpServlet {

    private OptionItemService optionItemService;

    public void setOptionItemService(OptionItemService optionItemService) {
        this.optionItemService = optionItemService;
    }

    public void init() throws ServletException {
        System.out.println("DictServlet init..............................");

        WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(this.getServletContext());
        setOptionItemService(wac.getBean (OptionItemService.class));
        optionItemService.getAllFieldName();
        // init something...
        // 例子: 设置Servlet全局属性
        this.getServletContext().setAttribute("appName", "项目名");

        super.init();
    }

}
初始化ServletContext

 

      5: 静态资源请求配置

 

package com.yjy.test.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

/**
 * 自定义WebMvcConfigurerAdapter配置
 *
 * @Author yjy
 * @Date 2018-04-23 11:40
 */
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {

    private static final Logger log = LoggerFactory.getLogger(WebMvcConfig.class);

    /**
     * 静态资源请求配置
     * @param registry 资源处理注册器
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        log.info("addResourceHandlers...........................");
        registry.addResourceHandler("/**").addResourceLocations("classpath:/common/");
        super.addResourceHandlers(registry);
    }

}
静态资源配置

 

      6: tomcat上传配置

package com.yjy.test.config;

import org.springframework.boot.web.servlet.MultipartConfigFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.MultipartConfigElement;

/**
 * 配置tomcat上传限制
 *
 * @Author yjy
 * @Date 2018-04-24 14:38
 */
@Configuration
public class MultipartConfig {

    /**
     * 配置tomcat上传限制
     * @return 配置
     */
    @Bean
    public MultipartConfigElement multipartConfigElement(){
        MultipartConfigFactory factory = new MultipartConfigFactory();
        factory.setMaxFileSize("50MB");
        factory.setMaxRequestSize("10MB");
        return factory.createMultipartConfig();
    }

}
上传配置

 

      7: 前后台登入拦截器, 以及相应配置类

package com.yjy.test.game.web.interceptor;

import com.yjy.test.game.entity.Config;
import com.yjy.test.game.entity.User;
import com.yjy.test.game.service.ConfigService;
import com.yjy.test.game.util.FrontUtils;
import com.yjy.test.game.web.ErrorCode;
import com.yjy.test.util.UnicodeUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import org.springframework.web.util.UrlPathHelper;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

/**
 * 前台登入拦截器
 *
 * @Author yjy
 * @Date 2018-04-24 15:03
 */
// 这里导入前缀为 front.login 的配置参数
@ConfigurationProperties(prefix = "front.login")
public class FrontLoginInterceptor extends HandlerInterceptorAdapter {

    private static final Logger log = LoggerFactory.getLogger(FrontLoginInterceptor.class);

    // 例外
    private List<String> excludeUrls = new ArrayList<>();
    private ConfigService configService;

    @Autowired
    public void setConfigService(ConfigService configService) {
        this.configService = configService;
    }

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response, Object handler) throws Exception {
        log.info("FrontLoginInterceptor > excludeUrls: {}", excludeUrls);
        String uri = getURI(request);
        if (exclude(uri)) {
            return true;
        }

        try {
            request.setCharacterEncoding("UTF-8");
            response.setCharacterEncoding("UTF-8");
            response.setContentType("text/html;charset=UTF-8");
            User user = FrontUtils.getCurrentUser(request);
            if (null == user) {
                Enumeration s = request.getHeaderNames();
                String requestType = request.getHeader("X-Requested-With");
                if (requestType != null && requestType.equals("XMLHttpRequest")) {
                    response.setCharacterEncoding("UTF-8");
                    response.setContentType("application/json; charset=utf-8");
                    response.getOutputStream().print("{\"status\":0,\"info\":\""
                            + UnicodeUtil.toEncodedUnicode( "登录超时,请重新登录", false)
                            + "\", \"data\":null, \"code\": \"" + ErrorCode.ER_NOT_LOGIN + "\"}" );
                    return false;
                }
                Config config = configService.findThisConfig();
                String path = null;
                if(null != config){
                    path = StringUtils.isNotBlank(request.getContextPath()) ? request.getContextPath():"";
                }
                String reLogin = "/autho.jtk";
                if(StringUtils.isNotBlank(path) && path.length() > 1) {
                    reLogin = path + reLogin;
                }
                response.sendRedirect(reLogin);
                return false;
            }
        } catch (Exception e) {
            log.error("检查前台登录参数出错", e);
        }

        return super.preHandle(request, response, handler);
    }


    private boolean exclude(String uri) {
        if (excludeUrls != null) {
            for (String exc : excludeUrls) {
                if (exc.equals(uri)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 获得第三个路径分隔符的位置
     *
     * @param request
     * @throws IllegalStateException
     *             访问路径错误,没有三(四)个'/'
     */
    private static String getURI(HttpServletRequest request)
            throws IllegalStateException {
        UrlPathHelper helper = new UrlPathHelper();
        String uri = helper.getOriginatingRequestUri(request);
        return uri;
    }

    public List<String> getExcludeUrls() {
        return excludeUrls;
    }

    public void setExcludeUrls(List<String> excludeUrls) {
        this.excludeUrls = excludeUrls;
    }

    public ConfigService getConfigService() {
        return configService;
    }
}
前台登入拦截
package com.yjy.test.game.web.interceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.yjy.test.game.entity.Admin;
import com.yjy.test.game.entity.Config;
import com.yjy.test.game.service.ConfigService;
import com.yjy.test.game.util.BackUtils;
import com.yjy.test.game.util.UnicodeUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import org.springframework.web.util.UrlPathHelper;

import java.util.ArrayList;
import java.util.List;

/**
 * 后台上下文登录检测
 *
 * @author wdy
 * @version :2016年2月29日 下午6:22:56
 */
// 这里导入前缀为 back.login 的配置参数
@ConfigurationProperties(prefix = "back.login")
public class AdminLoginInterceptor extends HandlerInterceptorAdapter {

    private static final Logger log = LoggerFactory.getLogger(AdminLoginInterceptor.class);

    // 例外
    private List<String> excludeUrls = new ArrayList<>();
    private ConfigService configService;

    @Autowired
    public void setConfigService(ConfigService configService) {
        this.configService = configService;
    }

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response, Object handler) throws Exception {
        String uri = getURI(request);
        if (exclude(uri)) {
            return true;
        }
        try {
            request.setCharacterEncoding("UTF-8");
            response.setCharacterEncoding("UTF-8");
            response.setContentType("text/html;charset=UTF-8");
            Admin user = BackUtils.getCurrentUser(request);
            if (null == user) {
                String requestType = request.getHeader("X-Requested-With");
                if (requestType != null && requestType.equals("XMLHttpRequest")) {
                    response.setCharacterEncoding("UTF-8");
                    response.setContentType("application/json; charset=utf-8");
                    response.getOutputStream().print("{\"status\":2, \"code\":\"login\", \"info\":\""
                            + UnicodeUtil.toEncodedUnicode("登录超时,请重新登录", false)
                            + "\", \"data\":null}");
                    return false;
                }
                Config config = configService.findThisConfig();
                String path = null;
                if (null != config) {
                    path = StringUtils.isNotBlank(request.getContextPath()) ? request.getContextPath() : "";
                }
                String reLogin = "/manager/admin/login.do";
                if (StringUtils.isNotBlank(path) && path.length() > 1) {
                    reLogin = path + reLogin;
                }
                response.sendRedirect(reLogin);
                return false;
            }
        } catch (Exception e) {
            log.error("检查后台登录参数出错", e);
        }

        return super.preHandle(request, response, handler);
    }


    private boolean exclude(String uri) {
        if (excludeUrls != null) {
            for (String exc : excludeUrls) {
                if (exc.equals(uri)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 获得第三个路径分隔符的位置
     *
     * @param request
     * @throws IllegalStateException 访问路径错误,没有三(四)个'/'
     */
    private static String getURI(HttpServletRequest request)
            throws IllegalStateException {
        UrlPathHelper helper = new UrlPathHelper();
        String uri = helper.getOriginatingRequestUri(request);
        String ctxPath = helper.getOriginatingContextPath(request);
        int start = 0, i = 0, count = 2;
        if (!StringUtils.isBlank(ctxPath)) {
            count++;
        }
        while (i < count && start != -1) {
            start = uri.indexOf('/', start + 1);
            i++;
        }

        if (start <= 0) {
            throw new IllegalStateException(
                    "admin access path not like '/manager/admin/...' pattern: "
                            + uri);
        }
        return uri.substring(start);
    }

    public List<String> getExcludeUrls() {
        return excludeUrls;
    }

    public void setExcludeUrls(List<String> excludeUrls) {
        this.excludeUrls = excludeUrls;
    }

    public ConfigService getConfigService() {
        return configService;
    }
}
后台登入拦截
package com.yjy.test.game.web.config;

import com.yjy.test.game.web.interceptor.AdminLoginInterceptor;
import com.yjy.test.game.web.interceptor.FrontLoginInterceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

/**
 * 自定义WebMvcConfigurerAdapter配置
 *
 * @Author yjy
 * @Date 2018-04-23 11:40
 */
@Configuration
public class GameWebMvcConfig extends WebMvcConfigurerAdapter {

    private static final Logger log = LoggerFactory.getLogger(GameWebMvcConfig.class);

    /**
     * 拦截器配置
     * @param registry 拦截器注册器
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        log.info("addInterceptors1....................");
        registry.addInterceptor(getFrontLoginInterceptor())
                .addPathPatterns("*.jtk", "/*.jtk", "/*/*.jtk", "/*/*/*.jtk");
        registry.addInterceptor(getAdminLoginInterceptor())
                .addPathPatterns("*.do", "/*.do", "/*/*.do", "/*/*/*.do");
        super.addInterceptors(registry);
    }


    @Bean
    AdminLoginInterceptor getAdminLoginInterceptor() {
        return new AdminLoginInterceptor();
    }

    @Bean
    FrontLoginInterceptor getFrontLoginInterceptor() {
        return new FrontLoginInterceptor();
    }

}
拦截器配置类

      

      8: 添加过滤器

package com.yjy.test.game.web.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 记录请求执行时间
 */
@WebFilter(urlPatterns = "/*")
public class ProcessTimeFilter implements Filter {

    protected final Logger log = LoggerFactory.getLogger(ProcessTimeFilter.class);

    /**
     * 请求执行开始时间
     */
    public static final String START_TIME = "_start_time";

    public void destroy() {
    }

    public void doFilter(ServletRequest req, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        long time = System.currentTimeMillis();
        log.info("process start at {} for uri: {}", time, request.getRequestURI());
        request.setAttribute(START_TIME, time);
        chain.doFilter(request, response);
        time = System.currentTimeMillis() - time;
        log.info("process in {} ms: {}", time, request.getRequestURI());
    }

    public void init(FilterConfig arg0) throws ServletException {
        log.info("CustomFilter: ProcessTimeFilter init....");
    }

}
请求执行时间过滤器

     

    第六步: 迁移原项目源码 几个遇到问题的点:

      1: hibernate -> hibernate + JPA

      原来的 *-hbm.xml 映射方式全部需要改成注解的方式, 实体类注解子如下:

package com.yjy.test.game.entity.club;

import com.yjy.test.base.BaseEntity;

import javax.persistence.*;
import java.math.BigInteger;
import java.util.Date;

/**
 * 俱乐部消息表
 *
 * @author yjy
 * Created on 2017年12月6日 上午9:34:07
 */
@Entity
@Table(name = "cg_club_message")
public class ClubMessage extends BaseEntity {

    private static final long serialVersionUID = -1353909238958898740L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id; // id
    private Long receiveId; // 消息接收人
    private Long sendId; // 消息发送人
    private Long clubId; // 俱乐部id
    private Long clubUserId; // 相关成员id
    private Integer type; // 类型
    private Integer status; // 已操作/已读状态
    private Integer result; // 申请结果
    private String remark; // 备注
    private Integer isDelete; // 是否删除
    private Date addTime; // 创建时间
    private Date updateTime; // 更新时间

    @ManyToOne
    @JoinColumn(name = "clubId", insertable = false, updatable = false,
            foreignKey = @ForeignKey(name = "none", value = ConstraintMode.NO_CONSTRAINT ))
    private ClubUser clubUser;

    // 非持久化字段
    @Transient
    private String nickName; // 昵称
    @Transient
    private String headImg; // 头像
    @Transient
    private String userCode; // 用户code
    @Transient
    private String clubName; // 俱乐部名称

    public ClubMessage() {
    }

    public ClubMessage(Long sendId, Long receiveId, Long clubId, Long clubUserId, Integer type) {
        this.sendId = sendId;
        this.receiveId = receiveId;
        this.clubId = clubId;
        this.clubUserId = clubUserId;
        this.type = type;
        this.init();
    }

    private void init() {
        this.isDelete = NO;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Long getReceiveId() {
        return receiveId;
    }

    public void setReceiveId(Long receiveId) {
        this.receiveId = receiveId;
    }

    public Long getSendId() {
        return sendId;
    }

    public String getNickName() {
        return nickName;
    }

    public Long getClubId() {
        return clubId;
    }

    public Integer getIsDelete() {
        return isDelete;
    }

    public void setIsDelete(Integer isDelete) {
        this.isDelete = isDelete;
    }

    public void setClubId(Long clubId) {
        this.clubId = clubId;
    }

    public void setNickName(String nickName) {
        this.nickName = nickName;
    }

    public String getHeadImg() {
        return headImg;
    }

    public void setHeadImg(String headImg) {
        this.headImg = headImg;
    }

    public String getUserCode() {
        return userCode;
    }

    public void setUserCode(String userCode) {
        this.userCode = userCode;
    }

    public String getClubName() {
        return clubName;
    }

    public void setClubName(String clubName) {
        this.clubName = clubName;
    }

    public void setSendId(Long sendId) {
        this.sendId = sendId;
    }

    public Long getClubUserId() {
        return clubUserId;
    }

    public void setClubUserId(Long clubUserId) {
        this.clubUserId = clubUserId;
    }

    public ClubUser getClubUser() {
        return clubUser;
    }

    public void setClubUser(ClubUser clubUser) {
        this.clubUser = clubUser;
    }

    public Integer getType() {
        return type;
    }

    public void setType(Integer type) {
        this.type = type;
    }

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

    public Integer getResult() {
        return result;
    }

    public void setResult(Integer result) {
        this.result = result;
    }

    public String getRemark() {
        return remark;
    }

    public void setRemark(String remark) {
        this.remark = remark;
    }

    public Date getAddTime() {
        return addTime;
    }

    public void setAddTime(Date addTime) {
        this.addTime = addTime;
    }

    public Date getUpdateTime() {
        return updateTime;
    }

    public void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
    }

    @Override
    public String toString() {
        return "ClubMessageDao [id=" + id + ", receiveId=" + receiveId
                + ", sendId=" + sendId + ", clubId=" + clubId + ", clubUserId="
                + clubUserId + ", type=" + type + ", status=" + status
                + ", result=" + result + ", remark=" + remark + ", isDelete="
                + isDelete + ", addTime=" + addTime + ", updateTime="
                + updateTime + ", nickName=" + nickName + ", headImg="
                + headImg + ", userCode=" + userCode + ", clubName=" + clubName
                + "]";
    }

}
实体类例子
package com.yjy.test.base;

import java.io.Serializable;

/**
 * 实体类父类
 */
public class BaseEntity extends BaseClass implements Serializable {

}
BaseEntity
package com.yjy.test.base;

import org.apache.commons.lang3.StringUtils;

public abstract class BaseClass {

    protected static final int YES = 1;
    protected static final int NO = 0;

    /**
     * 验证字符串
     * @param s 字符串
     * @return 是否为空
     */
    protected static boolean isBlank(String s) {
        return StringUtils.isBlank(s);
    }

    /**
     * 验证字符串
     * @param s 字符串
     * @return 是否不为空
     */
    protected static boolean notBlank(String s) {
        return StringUtils.isNotBlank(s);
    }

    /**
     * 验证字符串
     * @param s 字符串
     * @return 是否数字
     */
    protected static boolean isNumber(String s) {
        return StringUtils.isNumeric(s);
    }

}
BaseClass

 

    2: 重写Base层(代码如下),注意: 原来BaseDaoImpl中的 sessionFactory 没有了, 就是说不能通过getSession().createSQLQuery(sql) 的方式获取SQLQuery了, 需要通过em.createNativeQuery(sql).unwrap(SQLQuery.class); 来获得SQLQuery, em在BaseServiceImpl中已经注入, 通过这种方式可以兼容之前的代码

package com.yjy.test.base;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.NoRepositoryBean;

import java.io.Serializable;

/**
 * BaseJpaRepository
 *
 * @Author yjy
 * @Date 2018-04-25 15:55
 */
@NoRepositoryBean
public interface BaseJpaRepository<T extends BaseEntity, L extends Serializable> extends JpaRepository<T, L> {

    public T findTopByOrderByIdDesc();

}
BaseJpaRepository.java
package com.yjy.test.base;

import com.yjy.test.util.hibernate.Pagination;
import org.springframework.data.domain.Sort;

import java.io.Serializable;
import java.util.List;

public interface BaseService<T extends BaseEntity, L extends Serializable> {

    /**
     * 保存对象
     *
     * @param entity 实体对象
     * @return 操作信息
     */
    T save(T entity);

    T update(T entity);

    void delete(T entity);

    /**
     * 根据ID删除记录
     *
     * @param id 记录ID
     */
    void deleteById(L id);

    /**
     * 根据ID数组删除记录,当发生异常时,操作终止并回滚
     *
     * @param ids 记录ID数组
     * @return 删除的对象
     */
    void deleteById(L[] ids);

    /**
     * 保存并刷新对象,避免many-to-one属性不完整
     *
     * @param entity
     */
    T saveAndRefresh(T entity);

    /**
     * 通过ID查找对象
     *
     * @param id 记录的ID
     * @return 实体对象
     */
    T findById(L id);

    T load(L id);

    T findByProperty(String property, Object value);

    List<T> findListByProperty(String property, Object value);

    /**
     * 根据属性查找
     * @param propertyName 属性
     * @param value 值
     * @param anywhere 是否模糊匹配
     * @return
     */
    List<T> findListByProperty(String propertyName, Object value, boolean anywhere);

    /**
     * 查找所有对象
     *
     * @return 对象列表
     */
    List<T> findAll();

    /**
     * 分页查询
     * @param pageNo 页号
     * @param pageSize 条数
     * @param orders 排序规则
     * @return 分页列表
     */
    Pagination findAllPage(int pageNo, int pageSize, Sort.Order... orders);

    List<T> findList(T entity, Sort.Order... orders);

    List<T> findList(T entity, int pageNo, int pageSize, Sort.Order... orders);

    Pagination findListPage(T entity, int pageNo, int pageSize, Sort.Order... orders);

    T findLast();

    T findFirst(T entity, Sort.Order... orders);

    long findAllCount();

    long findCount(T entity);

}
BaseService
package com.yjy.test.base;

import com.yjy.test.util.hibernate.Finder;
import com.yjy.test.util.hibernate.Pagination;
import org.hibernate.Query;
import org.springframework.data.domain.*;
import org.springframework.data.jpa.repository.JpaRepository;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;

// 这里不能加@Service注解, 否则会无法获取泛型T的Class
public class BaseServiceImpl<T extends BaseEntity, L extends Serializable>
        extends BaseClass implements BaseService<T, L> {

    //@Autowired和@PersistenceContext注解任取一
    @PersistenceContext
    protected EntityManager em;

    @Override
    public T save(T entity) {
        return dao.save(entity);
    }

    @Override
    public T update(T entity) {
        return dao.saveAndFlush(entity);
    }

    @Override
    public void delete(T entity) {
        dao.delete(entity);
    }

    @Override
    public void deleteById(L id) {
        dao.delete(id);
    }

    @Override
    public void deleteById(L[] ids) {
        if (ids != null) {
            for (L id : ids) {
                dao.delete(id);
            }
        }
    }

    @Override
    public T saveAndRefresh(T entity) {
        return dao.saveAndFlush(entity);
    }

    @Override
    public T findById(L id) {
        return dao.findOne(id);
    }

    @Override
    public T load(L id) {
        return dao.getOne(id);
    }

    @Override
    public T findByProperty(String property, Object value) {
        List<T> list = findListByProperty(property, value);
        return list != null ? list.get(0) : null;
    }

    @Override
    public List<T> findListByProperty(String property, Object value) {
        return findListByProperty(property, value, false);
    }

    @Override
    public List<T> findListByProperty(String property, Object value, boolean anywhere) {
        CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
        CriteriaQuery<T> query = criteriaBuilder.createQuery(getPersistentClass());
        Root<T> root = query.from(getPersistentClass());
        Predicate predicate;
        if (anywhere)
            predicate = criteriaBuilder.like(root.get(property), "%" + value.toString() + "%");
        else
            predicate = criteriaBuilder.equal(root.get(property), value);
        query.where(predicate);
        return em.createQuery(query).getResultList();
    }

    @Override
    public List<T> findAll() {
        return dao.findAll();
    }

    @Override
    public Pagination findAllPage(int pageNo, int pageSize, Sort.Order... orders) {
        Sort sort = orders != null && orders.length > 0 ? new Sort(orders) : null;
        Pageable pageable = new PageRequest(pageNo - 1, pageSize, sort);
        Page<T> page = dao.findAll(pageable);
        Pagination pagination = new Pagination(pageNo, pageSize, (int) page.getTotalElements());
        pagination.setList(page.getContent());
        return pagination;
    }

    @Override
    public List<T> findList(T entity, Sort.Order... orders) {
        Example<T> example = Example.of(entity);
        if (orders != null && orders.length > 0)
            return dao.findAll(example, new Sort(orders));
        else
            return dao.findAll(example);
    }

    @Override
    @SuppressWarnings("unchecked")
    public List<T> findList(T entity, int pageNo, int pageSize, Sort.Order... orders) {
        Pagination pagination = findListPage(entity, pageNo, pageSize, orders);
        if (pagination != null) {
            return (List<T>)pagination.getList();
        }
        return new ArrayList<>();
    }

    @Override
    public Pagination findListPage(T entity, int pageNo, int pageSize, Sort.Order... orders) {
        Example<T> example = Example.of(entity);
        Sort sort = orders != null && orders.length > 0 ? new Sort(orders) : null;
        Pageable pageable = new PageRequest(pageNo - 1, pageSize, sort);
        Page<T> page = dao.findAll(example, pageable);
        Pagination pagination = new Pagination(pageNo, pageSize, (int) page.getTotalElements());
        pagination.setList(page.getContent());
        return pagination;
    }

    @Override
    public T findLast() {
        return dao.findTopByOrderByIdDesc();
    }

    @Override
    public T findFirst(T entity, Sort.Order... orders) {
        List<T> list = findList(entity, 1, 1, orders);
        if (!list.isEmpty()) {
            return list.get(0);
        }
        return null;
    }

    @Override
    public long findAllCount() {
        return dao.count();
    }

    @Override
    public long findCount(T entity) {
        Example<T> example = Example.of(entity);
        return dao.count(example);
    }


    @SuppressWarnings("rawtypes")
    protected Pagination find(Finder finder, int pageNo, int pageSize) {
        int totalCount = countQueryResult(finder);
        Pagination p = new Pagination(pageNo, pageSize, totalCount);
        if (totalCount < 1) {
            p.setList(new ArrayList());
            return p;
        }
        Query query = em.createQuery(finder.getOrigHql()).unwrap(Query.class);
        finder.setParamsToQuery(query);
        query.setFirstResult(p.getFirstResult());
        query.setMaxResults(p.getPageSize());
        List list = query.list();
        p.setList(list);
        return p;
    }

    /**
     * 通过count查询获得本次查询所能获得的对象总数.
     *
     * @param finder
     * @return
     */
    protected int countQueryResult(Finder finder) {
        Query query = em.createQuery(finder.getRowCountHql()).unwrap(Query.class);
        finder.setParamsToQuery(query);
        return ((Number) query.iterate().next()).intValue();
    }

    /*************************************************************************/
    private Class<T> persistentClass;

    @SuppressWarnings("unchecked")
    public BaseServiceImpl() {
        ParameterizedType parameterizedType = (ParameterizedType) getClass().getGenericSuperclass();
        this.persistentClass = (Class<T>) parameterizedType.getActualTypeArguments()[0];
    }

    private BaseJpaRepository<T, L> dao;

    public void setDao(BaseJpaRepository<T, L> dao) {
        this.dao = dao;
    }

    protected BaseJpaRepository<T, L> getDao() {
        return this.dao;
    }

    private Class<T> getPersistentClass() {
        return persistentClass;
    }

    public void setPersistentClass(Class<T> persistentClass) {
        this.persistentClass = persistentClass;
    }
}
BaseServiceImpl
package com.yjy.test.util.hibernate;

/**
 * 分页接口
 */
public interface Paginable {
    /**
     * 总记录数
     * 
     * @return
     */
    int getTotalCount();

    /**
     * 总页数
     * 
     * @return
     */
    int getTotalPage();

    /**
     * 每页记录数
     * 
     * @return
     */
    int getPageSize();

    /**
     * 当前页号
     * 
     * @return
     */
    int getPageNo();

    /**
     * 是否第一页
     * 
     * @return
     */
    boolean isFirstPage();

    /**
     * 是否最后一页
     * 
     * @return
     */
    boolean isLastPage();

    /**
     * 返回下页的页号
     */
    int getNextPage();

    /**
     * 返回上页的页号
     */
    int getPrePage();
}
Paginable
package com.yjy.test.util.hibernate;

import java.util.List;

/**
 * 列表分页。包含list属性。
 */
@SuppressWarnings("serial")
public class Pagination extends SimplePage implements java.io.Serializable, Paginable {

    public Pagination() { }

    /**
     * 构造器
     * 
     * @param pageNo
     *            页码
     * @param pageSize
     *            每页几条数据
     * @param totalCount
     *            总共几条数据
     */
    public Pagination(int pageNo, int pageSize, int totalCount) {
        super(pageNo, pageSize, totalCount);
    }

    /**
     * 构造器
     * 
     * @param pageNo
     *            页码
     * @param pageSize
     *            每页几条数据
     * @param totalCount
     *            总共几条数据
     * @param list
     *            分页内容
     */
    public Pagination(int pageNo, int pageSize, int totalCount, List<?> list) {
        super(pageNo, pageSize, totalCount);
        this.list = list;
    }

    /**
     * 第一条数据位置
     *
     * @return
     */
    public int getFirstResult() {
        return (pageNo - 1) * pageSize;
    }

    /**
     * 当前页的数据
     */
    private List<?> list;
    
    /**
     * 获得分页内容
     * 
     * @return
     */
    public List<?> getList() {
        return list;
    }

    /**
     * 设置分页内容
     * 
     * @param list
     */
    @SuppressWarnings("rawtypes")
    public void setList(List list) {
        this.list = list;
    }
}
Pagination
package com.yjy.test.util.hibernate;

/**
 * 简单分页类
 */
public class SimplePage implements Paginable {
    
    
    public static final int DEF_COUNT = 20;

    /**
     * 检查页码 checkPageNo
     * 
     * @param pageNo
     * @return if pageNo==null or pageNo<1 then return 1 else return pageNo
     */
    public static int cpn(Integer pageNo) {
        return (pageNo == null || pageNo < 1) ? 1 : pageNo;
    }
    
    /**
     * 检查每页条数
     * @author yjy
     * Created on 2017年12月5日 下午2:39:23
     * @param pageSize 条数
     * @return 矫正值
     */
    public static int cps(Integer pageSize) {
        return (pageSize == null || pageSize < 1) ? DEF_COUNT : pageSize;
    }

    /**
     * 根据页号和条数计算起始下标
     * @author yjy
     * Created on 2017年12月6日 上午9:36:17
     * @param pageNo 页号
     * @param pageSize 条数
     * @return 起始下标 return (pageNo - 1) * pageSize
     */
    public static int getStart(Integer pageNo, Integer pageSize) {
        return (cpn(pageNo) - 1) * cps(pageSize);
    }
    
    public SimplePage() {
    }

    /**
     * 构造器
     * 
     * @param pageNo
     *            页码
     * @param pageSize
     *            每页几条数据
     * @param totalCount
     *            总共几条数据
     */
    public SimplePage(int pageNo, int pageSize, int totalCount) {
        setTotalCount(totalCount);
        setPageSize(pageSize);
        setPageNo(pageNo);
        adjustPageNo();
    }

    /**
     * 调整页码,使不超过最大页数
     */
    public void adjustPageNo() {
        if (pageNo == 1) {
            return;
        }
        int tp = getTotalPage();
        if (pageNo > tp) {
            pageNo = tp;
        }
    }

    /**
     * 获得页码
     */
    public int getPageNo() {
        return pageNo;
    }

    /**
     * 每页几条数据
     */
    public int getPageSize() {
        return pageSize;
    }

    /**
     * 总共几条数据
     */
    public int getTotalCount() {
        return totalCount;
    }

    /**
     * 总共几页
     */
    public int getTotalPage() {
        int totalPage = totalCount / pageSize;
        if (totalPage == 0 || totalCount % pageSize != 0) {
            totalPage++;
        }
        return totalPage;
    }

    /**
     * 是否第一页
     */
    public boolean isFirstPage() {
        return pageNo <= 1;
    }

    /**
     * 是否最后一页
     */
    public boolean isLastPage() {
        return pageNo >= getTotalPage();
    }

    /**
     * 下一页页码
     */
    public int getNextPage() {
        if (isLastPage()) {
            return pageNo;
        } else {
            return pageNo + 1;
        }
    }

    /**
     * 上一页页码
     */
    public int getPrePage() {
        if (isFirstPage()) {
            return pageNo;
        } else {
            return pageNo - 1;
        }
    }

    protected int totalCount = 0;
    protected int pageSize = 20;
    protected int pageNo = 1;

    /**
     * if totalCount<0 then totalCount=0
     * 
     * @param totalCount
     */
    public void setTotalCount(int totalCount) {
        if (totalCount < 0) {
            this.totalCount = 0;
        } else {
            this.totalCount = totalCount;
        }
    }

    /**
     * if pageSize< 1 then pageSize=DEF_COUNT
     * 
     * @param pageSize
     */
    public void setPageSize(int pageSize) {
        if (pageSize < 1) {
            this.pageSize = DEF_COUNT;
        } else {
            this.pageSize = pageSize;
        }
    }

    /**
     * if pageNo < 1 then pageNo=1
     * 
     * @param pageNo
     */
    public void setPageNo(int pageNo) {
        if (pageNo < 1) {
            this.pageNo = 1;
        } else {
            this.pageNo = pageNo;
        }
    }
}
SimplePage

    3: 使用 mvn package 打包项目时, 需要配置主类, 否则如果项目中存在多个类有main函数时, 打包会报错, 配置如下:

     

    4: 有一个比较实用的点, 就是可以通过@Value(property)注解将配置绑定至静态变量中, 下面是Redis静态配置类的例子:

package com.yjy.test.game.redis;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;

import javax.validation.constraints.NotNull;

@Component
@Validated
public class RedisConfig {

    @NotNull
    public static String addr; //Redis服务器IP

    @NotNull
    public static int port; //Redis的端口号

    public static String auth; //访问密码

    @NotNull
    public static int maxActive = 10; // 可用连接实例的最大数目,默认值为8;如果赋值为-1,则表示不限制;
                                        // 如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)。
    @NotNull
    public static int maxIdle = 200; //控制一个pool最多有多少个状态为idle(空闲的)的jedis实例,默认值也是8。

    @NotNull
    public static int timeOut = 2000; //连接的超时时间

    @NotNull
    public static int maxWait = 10000; //等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException;

    @NotNull
    public static boolean testOnBorrow = true; //在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的;

    @NotNull
    public static boolean testOnReturn = true; //在return一个jedis实例时,是否提前进行validate操作.

    @Value("${redis.addr}")
    public void setAddr(String addr) {
        this.addr = addr;
    }

    @Value("${redis.port}")
    public void setPort(int port) {
        this.port = port;
    }

    @Value("${redis.auth}")
    public void setAuth(String auth) {
        this.auth = auth;
    }

    @Value("${redis.maxActive}")
    public void setMaxActive(int maxActive) {
        this.maxActive = maxActive;
    }

    @Value("${redis.maxIdle}")
    public void setMaxIdle(int maxIdle) {
        this.maxIdle = maxIdle;
    }

    @Value("${redis.timeOut}")
    public void setTimeOut(int timeOut) {
        this.timeOut = timeOut;
    }

    @Value("${redis.maxWait}")
    public void setMaxWait(int maxWait) {
        this.maxWait = maxWait;
    }

    @Value("${redis.testOnBorrow}")
    public void setTestOnBorrow(boolean testOnBorrow) {
        this.testOnBorrow = testOnBorrow;
    }

    @Value("${redis.testOnReturn}")
    public void setTestOnReturn(boolean testOnReturn) {
        this.testOnReturn = testOnReturn;
    }

    public static void printAllConfig() {
        System.out.println("RedisConfig{" +
                "addr='" + addr + '\'' +
                ", port=" + port +
                ", auth='" + auth + '\'' +
                ", maxActive=" + maxActive +
                ", maxIdle=" + maxIdle +
                ", timeOut=" + timeOut +
                ", maxWait=" + maxWait +
                ", testOnBorrow=" + testOnBorrow +
                ", testOnReturn=" + testOnReturn +
                '}');
    }

}
RedisConfig.java

 

大概就是这么个样子!!! 嗯

 

 

    

    

    

    

posted @ 2018-05-02 17:30  EEEEET  阅读(2900)  评论(0编辑  收藏  举报