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>
第三步: 创建配置文件, 特别注意: 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只用于处理静态资源
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
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
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
<?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>
server:
port: 8082 # 嵌入server的运行端口
context-path: /yjyboot # 配置项目运行地址
spring:
devtools:
restart:
exclude: classpath:common/**,classpath:templates/**
server:
port: 8080
context-path: /yjyboot # 导出war包存放在tomcat后会有一个项目运行地址, 这里配置可以模拟项目地址, 达到IDEA运行与tomcat运行地址相同
第四步: 主类(一般放在根包中)
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); } }
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); } }
对应的默认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>
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(); } }
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 { }
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); } }
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(); }
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); }
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; } }
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(); }
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; } }
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; } } }
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 + '}'); } }
大概就是这么个样子!!! 嗯