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

时间:2023-12-22 09:19:20

背景:

  有需求要将原来的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项目, 如下图)

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

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

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

    点击Next, 确认创建信息

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

    点击确认自动创建项目

    项目创建就完成了

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

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

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

   第二步: 配置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将不起作用!!!!

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

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", //
"yyyy-MM", //
"yyyy-MM-dd HH:mm:ss", //
"yyyy-MM-dd HH:mm", //
}; /**
* 这里将参数格式化成 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运行时, 会报找不到对应的配置文件的错误

      SpringBoot webmvc项目导出war包并在外部tomcat运行产生的诸多问题以及解决方案SpringBoot webmvc项目导出war包并在外部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函数时, 打包会报错, 配置如下:

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

    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

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