数据库

  • 数据库创建脚本, 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> {
}

RESTful Demo的更多相关文章

  1. restful demo 演示; jquery min1.1;

    [说明]上午建立了一个restful风格的一个测试,运行通过:下午试了试postman,想看看http请求的具体过程,但是chrome浏览器的network面板也可以查看,并且很方便,就索性用它了 一 ...

  2. 第四篇:用IntelliJ IDEA 搭建基于jersey的RESTful api

    编译器:Intellij IDEA 系统环境: MAC OS 相关技术:Maven.tomcat 7.jdk8 1.创建项目 首先创建一个web Application项目(这里我们打算用maven引 ...

  3. 使用CXF暴露您的REST服务

    使用CXF暴露您的REST服务 REST应用服务器SpringBeanServlet  1.  前言 现在互联网Open API流行,将您的Web应用也可以开放Open API给其他第三方使用.达到一 ...

  4. 17秋 软件工程 团队第五次作业 Alpha Scrum6

    17秋 软件工程 团队第五次作业 Alpha Scrum6 今日完成的任务 世强:APP内通知消息发送; 港晨:APP前端登陆界面编写: 树民:Web后端数据库访问模块代码实现: 伟航:Web后端Re ...

  5. 17秋 软件工程 团队第五次作业 Alpha Scrum7

    17秋 软件工程 团队第五次作业 Alpha Scrum7 今日完成的任务 世强:部员详情列表的编写与数据交互,完善APP通知模块: 港晨:完成前端登陆界面编写: 树民:完善Web后端数据库访问模块: ...

  6. C#中缓存的使用 ajax请求基于restFul的WebApi(post、get、delete、put) 让 .NET 更方便的导入导出 Excel .net core api +swagger(一个简单的入门demo 使用codefirst+mysql) C# 位运算详解 c# 交错数组 c# 数组协变 C# 添加Excel表单控件(Form Controls) C#串口通信程序

    C#中缓存的使用   缓存的概念及优缺点在这里就不多做介绍,主要介绍一下使用的方法. 1.在ASP.NET中页面缓存的使用方法简单,只需要在aspx页的顶部加上一句声明即可:  <%@ Outp ...

  7. 由浅入深一个Demo带你认识Restful风格的架构

    java作为一门后端语言,其厉害之处在于web,大家比较熟知的各种网络应用,java都能做,那么在这个移动优先的时代,如何继续发挥java的强大之处呢? 通常是让java作为一个app的服务端,为ap ...

  8. 前后端分离开发,基于SpringMVC符合Restful API风格Maven项目实战(附完整Demo)!

    摘要: 本人在前辈<从MVC到前后端分离(REST-个人也认为是目前比较流行和比较好的方式)>一文的基础上,实现了一个基于Spring的符合REST风格的完整Demo,具有MVC分层结构并 ...

  9. Flask Restful Small Demo

    参考: http://www.pythondoc.com/flask-restful/first.html 什么是Rest Client-Server:服务器端与客户端分离. Stateless(无状 ...

随机推荐

  1. D3——绘制SVG图形-直方图

    1.创建SVG元素 var svg = d3.select("body").append("svg"); 2.为SVG元素设置属性 svg.attr() .at ...

  2. Html 列表实现展开和收起

    HTML中,点击列表元素,在其下展开更多的小选项.不点的时候是收起来的.就是实现路由器左边的菜单那样的功能.怎么实现,知道的指点一下,谢谢了!! 最常见的方法是通过Javascript控制某标签的CS ...

  3. 简要总结 数据仓库VS数据库

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/wl101yjx/article/details/31015367 本文简要总结以下两个问题,旨在高速 ...

  4. 如何访问tomcat所在服务器的其他盘符的资源。

    <Host appBase="webapps" autoDeploy="true" name="localhost" unpackWA ...

  5. ADF中VO的查询方法比较

    getRowCount(),getQueryHitCount(oracle.jbo.server.ViewRowSetImpl),getEstimatedRangePageCount,getCappe ...

  6. Yii 多表关联relations

    1,首先多表关联是在models/xx.php的relations里配置的.而且是互配,但有区别.格式:'VarName'=>array('RelationType', 'ClassName', ...

  7. 利用MSF批量打RMI漏洞

    声明:不会Java. 参考:https://www.secpulse.com/archives/6203.html 下载mjet,https://github.com/mogwaisec/mjet 按 ...

  8. POJ 3264 Balanced Lineup 【ST表 静态RMQ】

    传送门:http://poj.org/problem?id=3264 Balanced Lineup Time Limit: 5000MS   Memory Limit: 65536K Total S ...

  9. GitBash初始目录的修改

    GitBash初始目录是定为到用户目录的,例如: 所以,每次打开都要手动调试到仓库所在的目录,可以通过修改目标和起始位置来定位到仓储文件夹下. 再次打开git,完美~~

  10. 学会WCF之试错法——超时

    服务契约 [ServiceContract] public interface IService { [OperationContract] string GetData(int value); [O ...