数据库
- 数据库创建脚本, create.sql
-- Create database
CREATE DATABASE `drift_bottle`;
-- Create new user add grant privileges
GRANT INSERT, SELECT, UPDATE ON `drift_bottle`.* TO `dft_bt`@`127.0.0.1` IDENTIFIED BY 'QGUHuLhFy';
FLUSH PRIVILEGES;
-- Change database
USE `drift_bottle`;
-- Create tables
CREATE TABLE `user_info` (
`row_id` CHAR(36) NOT NULL COMMENT "UUID to distinguish a user",
`user_name` VARCHAR(36) NOT NULL COMMENT "User name display to user",
`create_date` DATETIME NOT NULL COMMENT "Row create date",
PRIMARY KEY (`row_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- 数据库删除脚本, destory.sql
-- Drop database
DROP DATABASE IF EXISTS `drift_bottle`;
-- Drop user
DROP USER IF EXISTS `dft_bt`@`127.0.0.1`;
数据库连接池
- 下载 Mariadb 的 JDBC 驱动, 将 JAR 包复制进 /usr/share/tomcat8/lib/
- 定义 Tomcat 内建连接池, 此处采用的是应用内部定义, 当然也可以直接更改 /etc/tomcat8/context.xml 添加连接池, web/META-INF/context.xml
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<Resource
name="jdbc/DriftBottle"
type="javax.sql.DataSource"
maxTotal="20"
maxIdle="5"
maxWaitMillis="10000"
username="dft_bt"
password="QGUHuLhFy"
driverClassName="org.mariadb.jdbc.Driver"
defaultTransactionIsolation="READ_COMMITTED"
url="jdbc:mariadb://127.0.0.1:3306/drift_bottle" />
</Context>
依赖
- pom.xml
<?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.seliote</groupId>
<artifactId>drift-bottle-endpoint</artifactId>
<version>1.0</version>
<packaging>war</packaging>
<properties>
<!-- source file encoding -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- if not add two line will warning that version not support -->
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.targer>1.8</maven.compiler.targer>
<!-- Spring version -->
<spring.version>5.1.9.RELEASE</spring.version>
<!-- Log4j version -->
<log4j.version>2.12.1</log4j.version>
<!-- Jackson version -->
<jackson.version>2.9.9</jackson.version>
</properties>
<build>
<!-- Compile source file root -->
<sourceDirectory>src/main/java</sourceDirectory>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<!-- Test source file root -->
<testSourceDirectory>src/test/java</testSourceDirectory>
<testResources>
<testResource>
<directory>src/test/resources</directory>
</testResource>
</testResources>
<!-- Plugins for build -->
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.3</version>
<configuration>
<warSourceDirectory>web</warSourceDirectory>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<!-- Servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- Spring web MVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
<scope>compile</scope>
</dependency>
<!-- Spring OXM use for Object/XML mapper -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-oxm</artifactId>
<version>${spring.version}</version>
<scope>compile</scope>
</dependency>
<!-- Websocket for spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>${spring.version}</version>
<scope>compile</scope>
</dependency>
<!-- Spring Data JPA dependency -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>2.1.10.RELEASE</version>
<scope>compile</scope>
</dependency>
<!-- Hibernate dependency scope is runtime to avoid use hibernate api -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.4.4.Final</version>
<scope>runtime</scope>
</dependency>
<!-- Spring Test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<!-- JUnit Test, version 4.12 had some error -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13-beta-3</version>
<scope>test</scope>
</dependency>
<!-- Maven repository does not has JPA dependency, use eclipse JPA instead of it -->
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>javax.persistence</artifactId>
<version>2.2.1</version>
<scope>compile</scope>
</dependency>
<!-- Validation api -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
<scope>compile</scope>
</dependency>
<!-- Validation api implement -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.17.Final</version>
<scope>runtime</scope>
</dependency>
<!-- Log4j2 api -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>${log4j.version}</version>
<scope>compile</scope>
</dependency>
<!-- Log4j2 implement -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${log4j.version}</version>
<scope>runtime</scope>
</dependency>
<!-- JCL bridge interface for log4j2 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-jcl</artifactId>
<version>${log4j.version}</version>
<scope>compile</scope>
</dependency>
<!-- Slf4j bridge interface for log4j2 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>${log4j.version}</version>
<scope>runtime</scope>
</dependency>
<!-- Jackson implement -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
<scope>compile</scope>
</dependency>
<!-- Jackson annotation -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
<scope>compile</scope>
</dependency>
<!-- Jackson databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
<scope>compile</scope>
</dependency>
<!-- Jackson extension support for JSR-310,like Date and Time API in Java 8 -->
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>${jackson.version}</version>
<scope>compile</scope>
</dependency>
<!-- Json for handle some simple data -->
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20190722</version>
<scope>compile</scope>
</dependency>
<!-- Use for generate String from entity -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
应用全局配置
- config.properties
#################################################
## Application properties configuration
#################################################
#######################################
## Database configuration
#######################################
# Datasource name
database.datasourceName=jdbc/DriftBottle
# Database dialect for hibernate
database.hibernateDialect=org.hibernate.dialect.MariaDB103Dialect
#######################################
## System payload config
#######################################
# Thread pool size
system.payload=20
Log4J2 配置
- main/resources/log4j2.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!-- DEBUG for system running information -->
<!-- INFO for user data running information -->
<!-- WARNING for user data error -->
<!-- ERROR for system running error -->
<!-- Root logger level is debug -->
<configuration status="DEBUG">
<appenders>
<!-- Define a console appender -->
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %l: %msg%n"/>
</Console>
<!-- Define a rolling file appender -->
<RollingFile name="FileAppender"
fileName="/tmp/logs/application.log"
filePattern="/tmp/logs/application-%d{yyyy-MM-dd}-%i.log">
<!-- fileName="../logs/application.log"-->
<!-- filePattern="../logs/application-%d{yyyy-MM-dd}-%i.log">-->
<PatternLayout>
<pattern>%d{HH:mm:ss.SSS} [%t] %-5level %l: %msg%n</pattern>
</PatternLayout>
<Policies>
<!-- Max log file size -->
<SizeBasedTriggeringPolicy size="10 MB"/>
</Policies>
<!-- Count of log file -->
<DefaultRolloverStrategy min="1" max="10"/>
</RollingFile>
</appenders>
<loggers>
<!-- Root log output to console -->
<root level="INFO">
<!-- TODO If in produce environment, remove this! -->
<appender-ref ref="Console"/>
</root>
<!-- Package of com.seliote will put log to console and file -->
<logger name="com.seliote" level="DEBUG" additivity="false">
<appender-ref ref="FileAppender"/>
<!-- TODO If in produce environment, remove this! -->
<appender-ref ref="Console"/>
</logger>
<!-- Ensure framework log also could output -->
<logger name="org.apache" level="DEBUG"/>
<logger name="org.springframework" level="DEBUG"/>
</loggers>
</configuration>
- test/resources/log4j2-test.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!-- Root logger level is debug -->
<configuration status="DEBUG">
<appenders>
<!-- Define a console appender -->
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %l: %msg%n"/>
</Console>
</appenders>
<loggers>
<!-- Root log output to console -->
<root level="DEBUG">
<!-- TODO If in produce environment, remove this! -->
<appender-ref ref="Console"/>
</root>
<!-- Package of com.seliote will put log to console and file -->
<logger name="com.seliote" level="DEBUG" additivity="false">
<appender-ref ref="Console" />
</logger>
<!-- Ensure framework log also could output -->
<logger name="org.apache" level="DEBUG"/>
<logger name="org.springframework" level="DEBUG"/>
</loggers>
</configuration>
Spring 配置与启动
- 几个标记接口
package com.seliote.driftbottleendpoint;
/**
* Root context component scan mark interface
*
* @author seliote
* @version 1.0 2019-09-08
*/
public interface RootComponentScanMark {
}
package com.seliote.driftbottleendpoint.md;
/**
* Md context component scan mark interface
*
* @author seliote
* @version 1.0 2019-09-09
*/
public interface MdComponentScanMark {
}
package com.seliote.driftbottleendpoint.md.repository;
/**
* Mobile device model repository component scan mark
*
* @author seliote
* @version 1.0 2019-09-09
*/
public interface MdRepoComponentScanMark {
}
- Root Context, RootContextConfig.java
package com.seliote.driftbottleendpoint.config;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.seliote.driftbottleendpoint.RootComponentScanMark;
import com.seliote.driftbottleendpoint.md.repository.MdRepoComponentScanMark;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.*;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.Ordered;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
import org.springframework.web.bind.annotation.ControllerAdvice;
import javax.persistence.SharedCacheMode;
import javax.persistence.ValidationMode;
import javax.sql.DataSource;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executor;
/**
* Root context configuration file class
*
* @author seliote
* @version 1.0 2019-09-08
*/
@SuppressWarnings("DefaultAnnotationParam")
@Configuration
// Scan anything besides @Controller or @ControllerAdvice
@ComponentScan(
basePackageClasses = {RootComponentScanMark.class},
excludeFilters = @ComponentScan.Filter({Controller.class, ControllerAdvice.class})
)
// Enable Spring transaction and config it
@EnableTransactionManagement(
mode = AdviceMode.PROXY,
proxyTargetClass = false,
order = Ordered.LOWEST_PRECEDENCE
)
// Enable Spring Data Jpa and config it
@EnableJpaRepositories(
basePackageClasses = {MdRepoComponentScanMark.class},
entityManagerFactoryRef = "entityManagerFactory",
transactionManagerRef = "jpaTransactionManager"
)
// Enable async
@EnableAsync(
mode = AdviceMode.PROXY,
proxyTargetClass = false,
order = Ordered.HIGHEST_PRECEDENCE
)
@EnableScheduling
@PropertySource({"classpath:config.properties"})
public class RootContextConfig implements AsyncConfigurer, SchedulingConfigurer {
@Value("${database.datasourceName}")
private String mDatasourceName;
@Value("${database.hibernateDialect}")
private String mHibernateDialect;
@Value("${system.payload}")
private int mThreadPoolSize;
// Thread pool logger
private final static Logger THREAD_POOL_LOGGER = LogManager.getLogger("[Thread-Pool]");
private Logger mLogger = LogManager.getLogger();
@Override
public Executor getAsyncExecutor() {
mLogger.debug("Set async Executor");
return threadPoolTaskScheduler();
}
@Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
mLogger.debug("Set task Scheduler");
scheduledTaskRegistrar.setTaskScheduler(threadPoolTaskScheduler());
}
// Use for Spring to parse @Value("${}")
@Bean
// Be careful that PropertySourcesPlaceholderConfigurer bean define must be static
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
/**
* The bean of type Executor and Scheduler
*/
@Bean
public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
mLogger.debug("Set thread pool with size " + mThreadPoolSize);
ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
threadPoolTaskScheduler.setPoolSize(mThreadPoolSize);
threadPoolTaskScheduler.setThreadNamePrefix("Thread-Pool: ");
// The max seconds to prevent close
threadPoolTaskScheduler.setAwaitTerminationSeconds(10);
// Wait if thread is not shutdown
threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(true);
// Error handler for thread pool
threadPoolTaskScheduler.setErrorHandler(throwable ->
THREAD_POOL_LOGGER.warn("Thread pool occurred an exception: " + throwable.getMessage()));
// Reject handler
threadPoolTaskScheduler.setRejectedExecutionHandler((runnable, threadPoolExecutor) ->
THREAD_POOL_LOGGER.warn("Thread " + runnable.toString() + " had bean reject by "
+ threadPoolExecutor.toString())
);
return threadPoolTaskScheduler;
}
@Bean
public DataSource dataSource() {
mLogger.debug("Set JPA datasource with name " + mDatasourceName);
JndiDataSourceLookup jndiDataSourceLookup = new JndiDataSourceLookup();
return jndiDataSourceLookup.getDataSource(mDatasourceName);
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
// The properties of EntityManager
Map<String, Object> properties = new HashMap<>();
// Disable schema auto create
properties.put("javax.persistence.schema-generation.database.action", "none");
HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
// Set dialect
mLogger.debug("Set JPA database platform: " + mHibernateDialect);
hibernateJpaVendorAdapter.setDatabasePlatform(mHibernateDialect);
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setJpaVendorAdapter(hibernateJpaVendorAdapter);
entityManagerFactoryBean.setDataSource(dataSource());
// Parameter is String..., multiple package could add below
String[] packageToScan = new String[]{"com.seliote.driftbottleendpoint.md.entity"};
mLogger.debug("Set JPA entity manager factory packages: " + Arrays.toString(packageToScan));
entityManagerFactoryBean.setPackagesToScan(packageToScan);
entityManagerFactoryBean.setSharedCacheMode(SharedCacheMode.ENABLE_SELECTIVE);
entityManagerFactoryBean.setValidationMode(ValidationMode.NONE);
entityManagerFactoryBean.setJpaPropertyMap(properties);
return entityManagerFactoryBean;
}
/**
* Transaction manager for JPA
*/
@Bean
public PlatformTransactionManager jpaTransactionManager() {
mLogger.debug("Set JPA transaction manager");
return new JpaTransactionManager(entityManagerFactory().getObject());
}
/**
* Bean validator factory, figure out the implement as hibernate validator
*/
@Bean
public LocalValidatorFactoryBean localValidatorFactoryBean() throws ClassNotFoundException {
mLogger.debug("Set LocalValidatorFactoryBean and its message source");
LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();
// Figure out implement
localValidatorFactoryBean.setProviderClass(Class.forName("org.hibernate.validator.HibernateValidator"));
return localValidatorFactoryBean;
}
/**
* Method post processor, configuration for validation method parameter and return value,
* figure out validator to prevent default use the validator not has message source.
* This processor will find for @Validated or @ValidateOnExecution and create proxy.
*/
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() throws ClassNotFoundException {
mLogger.debug("Set MethodValidationPostProcessor");
MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor();
methodValidationPostProcessor.setValidator(localValidatorFactoryBean());
return methodValidationPostProcessor;
}
@Bean
public ObjectMapper objectMapper() {
mLogger.debug("Set ObjectMapper with WRITE_DATES_AS_TIMESTAMPS-false, " +
"ADJUST_DATES_TO_CONTEXT_TIME_ZONE-false");
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.findAndRegisterModules();
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
objectMapper.configure(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE, false);
return objectMapper;
}
}
- Servlet Context, MdContextConfig.java
package com.seliote.driftbottleendpoint.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.seliote.driftbottleendpoint.md.MdComponentScanMark;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.stereotype.Controller;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.SpringValidatorAdapter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
/**
* Md context configuration file class
*
* @author seliote
* @version 1.0 2019-09-09
*/
@Configuration
@EnableWebMvc
@ComponentScan(
basePackageClasses = {MdComponentScanMark.class},
// Be careful that useDefaultFilters must be false!!!
useDefaultFilters = false,
includeFilters = @ComponentScan.Filter({Controller.class, ControllerAdvice.class})
)
public class MdContextConfig implements WebMvcConfigurer {
private Logger mLogger = LogManager.getLogger();
private SpringValidatorAdapter mSpringValidatorAdapter;
private ObjectMapper mObjectMapper;
@Autowired
public void setSpringValidatorAdapter(SpringValidatorAdapter springValidatorAdapter) {
mSpringValidatorAdapter = springValidatorAdapter;
}
@Autowired
public void setObjectMapper(ObjectMapper objectMapper) {
mLogger.debug("Inject ObjectMapper.");
mObjectMapper = objectMapper;
}
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
mLogger.debug("Set content negotiation only support JSON_UTF-8, ignore path extension, " +
"favor parameter, accept header");
// Only support JSON
configurer.favorPathExtension(false)
.favorParameter(false)
.ignoreAcceptHeader(true)
.defaultContentType(MediaType.APPLICATION_JSON_UTF8)
.mediaType("json", MediaType.APPLICATION_JSON_UTF8);
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
mLogger.debug("Set message converters only support json(application or text), " +
"be careful that request header must set Content-Type like application/json");
// Only support JSON
// Be careful: This required mobile device set Content-Type: application/json
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter
= new MappingJackson2HttpMessageConverter();
mappingJackson2HttpMessageConverter.setSupportedMediaTypes(Arrays.asList(
new MediaType("application", "json"),
new MediaType("text", "json")
));
mLogger.debug("Set message converts default charset to UTF-8");
mappingJackson2HttpMessageConverter.setDefaultCharset(StandardCharsets.UTF_8);
mappingJackson2HttpMessageConverter.setObjectMapper(mObjectMapper);
converters.add(mappingJackson2HttpMessageConverter);
}
/**
* Spring default use their own Spring Validator for SpringMVC parameter.
* This will override this behavior.
*/
@Override
public Validator getValidator() {
mLogger.debug("Set Validator for SpringMVC module MD");
return mSpringValidatorAdapter;
}
}
- Boot config, Bootstrap.java
package com.seliote.driftbottleendpoint.config;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletRegistration;
/**
* Bootstrap for Spring
*
* @author seliote
* @version 1.0 2019-09-09
*/
public class Bootstrap implements WebApplicationInitializer {
private Logger mLogger = LogManager.getLogger();
@Override
public void onStartup(ServletContext servletContext) {
mLogger.debug("Starting application DriftBottle...");
// Use default servlet to handle static resources
servletContext.getServletRegistration("default").addMapping("/resources/*");
mLogger.debug("Set default servlet for static resources in /resources/*");
// Root context
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(RootContextConfig.class);
servletContext.addListener(new ContextLoaderListener(rootContext));
mLogger.debug("Set root context finished");
// Md Context
AnnotationConfigWebApplicationContext mdContext = new AnnotationConfigWebApplicationContext();
mdContext.register(MdContextConfig.class);
ServletRegistration.Dynamic mdDynamic =
servletContext.addServlet("mdDispatcherServlet", new DispatcherServlet(mdContext));
mdDynamic.setLoadOnStartup(1);
// Or "/" not "/*"
mdDynamic.addMapping("/md/*");
mLogger.debug("Set md context finished mapping to /md/*");
mLogger.debug("Bootstrap finished!");
}
}
- Exception handler, ExceptionHandler.java
package com.seliote.driftbottleendpoint.md.config;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import javax.validation.ConstraintViolationException;
/**
* Exception handler for md module
*
* @author seliote
* @version 1.0 2019-09-14
*/
@SuppressWarnings("unused")
@ControllerAdvice
public class ExceptionHandler {
private Logger mLogger = LogManager.getLogger();
@org.springframework.web.bind.annotation.ExceptionHandler({HttpMessageNotReadableException.class})
@ResponseStatus(HttpStatus.OK)
@ResponseBody
private ErrorMsgResp handler(HttpMessageNotReadableException ex) {
mLogger.warn("HttpMessageNotReadableException: " + ex.getMessage());
return new ErrorMsgResp(400, "Request parameter convert had error!");
}
@org.springframework.web.bind.annotation.ExceptionHandler(
{
MethodArgumentNotValidException.class,
ConstraintViolationException.class
}
)
@ResponseStatus(HttpStatus.OK)
@ResponseBody
private ErrorMsgResp handler(Exception ex) {
mLogger.warn("MethodArgumentNotValidException: " + ex.getMessage());
return new ErrorMsgResp(401, "Request parameter validation error!");
}
}
/**
* Response entity when catch exception
*/
@SuppressWarnings({"unused", "WeakerAccess"})
class ErrorMsgResp {
private int mCode;
private String mMsg;
/**
* Must include a non parameter constructor
*/
public ErrorMsgResp() {
}
public ErrorMsgResp(int code, String msg) {
mCode = code;
mMsg = msg;
}
@JsonProperty("code")
public int getCode() {
return mCode;
}
@JsonProperty("code")
public void setCode(int code) {
mCode = code;
}
@JsonProperty("msg")
public String getMsg() {
return mMsg;
}
@JsonProperty("msg")
public void setMsg(String msg) {
mMsg = msg;
}
}
Bean 验证
- Not blank, NotBlank.java
package com.seliote.driftbottleendpoint.md.validation;
import javax.validation.*;
import javax.validation.constraints.NotNull;
import java.lang.annotation.*;
/**
* Validation for String not null and trim().length() > 0
*
* @author seliote
* @version 1.0 2019-09-15
*/
@SuppressWarnings("unused")
@Target({
ElementType.ANNOTATION_TYPE,
ElementType.CONSTRUCTOR,
ElementType.FIELD,
ElementType.METHOD,
ElementType.PARAMETER
})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@NotNull
// If not has other validator could use {}, tis annotation could not omit
@Constraint(validatedBy = {NotBlankValidator.class})
@ReportAsSingleViolation
public @interface NotBlank {
String message() default "Not null or empty";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
@Target({
ElementType.ANNOTATION_TYPE,
ElementType.CONSTRUCTOR,
ElementType.FIELD,
ElementType.METHOD,
ElementType.PARAMETER
})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@interface List {
NotBlank[] value();
}
}
/**
* NotBlank validator implement
*/
class NotBlankValidator implements ConstraintValidator<NotBlank, CharSequence> {
@Override
public void initialize(NotBlank constraintAnnotation) {
// Empty
}
@Override
public boolean isValid(CharSequence charSequence, ConstraintValidatorContext constraintValidatorContext) {
return charSequence != null && ((String) charSequence).trim().length() > 0;
}
}
Spring CSR
- Controller
LoginController.java
package com.seliote.driftbottleendpoint.md.controller;
import com.seliote.driftbottleendpoint.md.controller.reqentity.LoginInfo;
import com.seliote.driftbottleendpoint.md.controller.respentity.AccessToken;
import com.seliote.driftbottleendpoint.md.service.LoginService;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
/**
* Login controller
*
* @author seliote
* @version 1.0 2019-09-09
*/
@Controller
public class LoginController {
private Logger mLogger = LogManager.getLogger();
private LoginService mLoginService;
@Autowired
public void setLoginService(LoginService loginService) {
mLoginService = loginService;
}
@RequestMapping(value = "login", method = {RequestMethod.POST})
@ResponseStatus(HttpStatus.OK)
@ResponseBody
// Don't forget @RequestBody for parameter
// @Valid to recursive validate
public AccessToken requireAccessToken(@Validated @RequestBody LoginInfo loginInfo) {
mLogger.info("Login info: " + loginInfo);
String accessTokenString = mLoginService.generateAccessToken(loginInfo.getUserName());
AccessToken accessToken = new AccessToken();
accessToken.setAccessToken(accessTokenString);
return accessToken;
}
}
请求与响应实体
package com.seliote.driftbottleendpoint.md.controller.reqentity;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.seliote.driftbottleendpoint.md.validation.NotBlank;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import javax.validation.constraints.Max;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
/**
* Login info entity for LoginController
*
* @author seliote
* @version 1.0 2019-09-10
*/
@SuppressWarnings("unused")
public class LoginInfo {
@NotBlank
// If this filed is a POJO and has its own validator, u can @Validated this filed to recursive validate
private String mUserName;
@NotBlank
@Pattern(
regexp = "^[a-zA-Z0-9!@#$%^&*]{6,16}$",
flags = {Pattern.Flag.DOTALL}
)
private String mPassword;
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
}
@JsonProperty("user_name")
public String getUserName() {
return mUserName;
}
@JsonProperty("user_name")
public void setUserName(String userName) {
mUserName = userName;
}
@JsonProperty("password")
public String getPassword() {
return mPassword;
}
@JsonProperty("password")
public void setPassword(String password) {
mPassword = password;
}
}
package com.seliote.driftbottleendpoint.md.controller.respentity;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
/**
* Access token entity
*
* @author seliote
* @version 1.0 2019-09-10
*/
public class AccessToken {
private String mAccessToken;
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
}
@JsonProperty("access_token")
public String getAccessToken() {
return mAccessToken;
}
@JsonProperty("access_token")
public void setAccessToken(String accessToken) {
mAccessToken = accessToken;
}
}
- Service
LoginService.java
package com.seliote.driftbottleendpoint.md.service;
import com.seliote.driftbottleendpoint.md.validation.NotBlank;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.Pattern;
/**
* Service for login
*
* @author seliote
* @version 1.0 2019-09-13
*/
// Open MethodValidationPostProcessor validator for method execution
@Validated
public interface LoginService {
/**
* Generate access token for user
*
* @param userName User id for generate
* @return The access token generated
*/
// PbC programming
@NotBlank
String generateAccessToken(
@NotBlank
@Pattern(
regexp = "^[a-zA-Z0-9!@#$%^&*]{3,16}$",
flags = {Pattern.Flag.DOTALL}
)
String userName);
}
实现
package com.seliote.driftbottleendpoint.md.service.Impl;
import com.seliote.driftbottleendpoint.md.entity.UserInfoEntity;
import com.seliote.driftbottleendpoint.md.repository.UserInfoRepository;
import com.seliote.driftbottleendpoint.md.service.LoginService;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Base64;
import java.util.UUID;
/**
* Service for login implement
*
* @author seliote
* @version 1.0 2019-09-13
*/
@Service
public class LoginServiceImpl implements LoginService {
private Logger mLogger = LogManager.getLogger();
private UserInfoRepository mUserInfoRepository;
@Autowired
public void setUserInfoRepository(UserInfoRepository userInfoRepository) {
mUserInfoRepository = userInfoRepository;
}
@Override
public String generateAccessToken(String userName) {
byte[] bytes = Base64.getEncoder().encode(userName.getBytes(StandardCharsets.UTF_8));
String accessToken = new String(bytes, StandardCharsets.UTF_8);
mLogger.info("Generate access token for: " + userName + ", " + accessToken);
UserInfoEntity userInfoEntity = new UserInfoEntity();
userInfoEntity.setRowId(UUID.randomUUID().toString());
userInfoEntity.setUserName(userName);
userInfoEntity.setCreateDate(Instant.now());
mUserInfoRepository.save(userInfoEntity);
return accessToken;
}
}
- Repository
实体
package com.seliote.driftbottleendpoint.md.entity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.time.Instant;
/**
* user_info entity
*
* @author seliote
* @version 1.0 2019-09-14
*/
@Entity
@Table(name = "user_info")
public class UserInfoEntity {
private String mRowId;
private String mUserName;
private Instant mCreateDate;
@Id
@Column(name = "row_id")
public String getRowId() {
return mRowId;
}
public void setRowId(String rowId) {
mRowId = rowId;
}
@Column(name = "user_name")
public String getUserName() {
return mUserName;
}
public void setUserName(String userName) {
mUserName = userName;
}
@Column(name = "create_date")
public Instant getCreateDate() {
return mCreateDate;
}
public void setCreateDate(Instant createDate) {
mCreateDate = createDate;
}
}
UserInfoRepository.java
package com.seliote.driftbottleendpoint.md.repository;
import com.seliote.driftbottleendpoint.md.entity.UserInfoEntity;
import org.springframework.data.repository.CrudRepository;
/**
* UserInfo Entity Spring Data JPA repository
*
* @author seliote
* @version 1.0 2019-09-14
*/
public interface UserInfoRepository extends CrudRepository<UserInfoEntity, String> {
}