A quickstart and template code for development.
Code Styles
判断资源是否存在,不存在抛出异常
/**
* 资源不存在异常
*/
public class ResourceNotExistException extends Exception {
private final long resourceId;
public ResourceNotExistException(String message) {
this(message, 0);
}
public ResourceNotExistException(String message, long resourceId) {
super(String.format(message, resourceId));
this.resourceId = resourceId;
}
public long resourceId() {
return resourceId;
}
}
// . . .
private static ProtoDetail requireNonNull(ProtoDetail protoDetail, long protoId) throws ResourceNotExistException {
if (protoDetail == null) {
throw new ResourceNotExistException("ProtoDetail [%s] not exist", protoId);
}
return protoDetail;
}
// . . .
ProtoDetail protoDetail = store.get(protoDetailId);
requireNonNull(protoDetail, protoDetailId);
一种 Java Stream 的 shuffle 方式
list.stream() .sorted( (e1, e2) -> ThreadLocalRandom.current().nextInt(-5, 5) )
About Spring Boot
Web Server
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
server.address=0.0.0.0
server.port=5000
server.http2.enabled=true
server.undertow.threads.io=3
server.undertow.threads.worker=24
server.error.include-message=ALWAYS
server.error.include-exception=true
@Bean
public ObjectMapper objectMapper() {
JavaTimeModule module = new JavaTimeModule();
DateTimeFormatter f = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
module.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(f));
module.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(f));
DateTimeFormatter f2 = DateTimeFormatter.ofPattern("yyyy-MM-dd");
module.addSerializer(LocalDate.class, new LocalDateSerializer(f2));
module.addDeserializer(LocalDate.class, new LocalDateDeserializer(f2));
DateTimeFormatter f3 = DateTimeFormatter.ofPattern("HH:mm:ss");
module.addSerializer(LocalTime.class, new LocalTimeSerializer(f3));
module.addDeserializer(LocalTime.class, new LocalTimeDeserializer(f3));
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(module);
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd"));
objectMapper.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return objectMapper;
}
public class WebResponseException extends ResponseStatusException {
public WebResponseException(HttpStatus status, String reason) {
super(status, reason);
}
}
@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(WebResponseException.class)
public ResponseEntity<Object> handleWebResponseException(WebResponseException exception){
ErrorModel model = ErrorModel.of(exception.getRawStatusCode(), exception.getReason() );
return ResponseEntity.status(exception.getStatus()).body(model);
}
}
SprintBoot default error model handler @See
org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController
Method isProd
private Environment environment;
private Boolean prod(){
return environment.acceptsProfiles(Profiles.of("prod"));
}
Bean RedisTemplate
@Bean
public RedisTemplate<String, Map<Integer,List<AdRequestResult>>> maplistAdRequestResultRedisTemplate(
RedisConnectionFactory redisConnectionFactory, ObjectMapper objectMapper){
RedisTemplate<String, Map<Integer,List<AdRequestResult>>> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(StringRedisSerializer.UTF_8);
JavaType javaType = objectMapper.getTypeFactory().constructCollectionType(List.class, AdRequestResult.class);
JavaType javaType2 = objectMapper.getTypeFactory().constructMapType(Map.class, objectMapper.constructType(String.class), javaType);
Jackson2JsonRedisSerializer<Map<Integer,List<AdRequestResult>>> jsonRedisSerializer = new Jackson2JsonRedisSerializer<>(javaType2);
jsonRedisSerializer.setObjectMapper(objectMapper);
redisTemplate.setValueSerializer(jsonRedisSerializer );
return redisTemplate;
}
Test
@SpringBootTest(properties = {"logging.level.root=info" ,"spring.profiles.active=dev" })
@AutoConfigureJdbc
@AutoConfigureDataRedis
public class ApplicationTest {
}
Logging
logback
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.10</version>
</dependency>
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<contextName>logback</contextName>
<property name="appname" value="this-is-a-app-name" />
<property name="LOG_PATH" value="log" />
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>
%d{yyyy-dd-MM HH:mm:ss} %-5level [%thread] %logger{10} - %msg%n
</pattern>
</encoder>
</appender>
<appender name="FILE_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${appname}.log</file>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>
%d{yyyy-dd-MM HH:mm:ss} %-5level [%thread] %logger{10} - %msg%n
</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${appname}-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>15</maxHistory>
<totalSizeCap>5GB</totalSizeCap>
</rollingPolicy>
<append>true</append>
</appender>
<root level="info">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
log4j2
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.17.2</version>
</dependency>
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyy-MM-dd HH:mm:ss} [%t] %-5level %logger{1.} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
Authentication
Relational Database Data Access
Mybatis Multiple DataSources
@Configuration
@MapperScan(
basePackages = "",
sqlSessionFactoryRef = "springSqlSessionFactory",
annotationClass=SpringDataSource.class)
@MapperScan(
basePackages = "",
sqlSessionFactoryRef = "summerSqlSessionFactory",
annotationClass = SummerDataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@EnableTransactionManagement
public class DataConfig {
private MybatisProperties mybatisProperties;
public DataConfig(MybatisProperties mybatisProperties){
this.mybatisProperties = mybatisProperties;
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.spring")
public DataSource springDataSourcePool() {
return new HikariDataSource();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.summer")
public DataSource summerDataSourcePool() {
return new HikariDataSource();
}
@Bean
public SqlSessionFactory springSqlSessionFactory(ApplicationContext applicationContext) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(springDataSourcePool());
factoryBean.setConfigLocation(applicationContext.getResource(mybatisProperties.getConfigLocation()));
factoryBean.setMapperLocations(mybatisProperties.resolveMapperLocations());
factoryBean.setTypeAliasesPackage("");
return factoryBean.getObject();
}
@Bean
public SqlSessionFactory summerSqlSessionFactory(ApplicationContext applicationContext) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(summerDataSourcePool());
factoryBean.setConfigLocation(applicationContext.getResource(mybatisProperties.getConfigLocation()));
factoryBean.setMapperLocations(mybatisProperties.resolveMapperLocations());
factoryBean.setTypeAliasesPackage("");
return factoryBean.getObject();
}
@Bean
public DataSourceTransactionManager springTransactionManager(){
return new DataSourceTransactionManager(springSqlSessionFactory());
}
@Bean
public DataSourceTransactionManager summerTransactionManager(){
return new DataSourceTransactionManager(summerSqlSessionFactory());
}
}
mybatis.config-location=classpath:/mybatis-config.xml
mybatis.mapper-locations=classpath*:/mapper/*.xml
MVN Executable Jar
<properties>
<maven-shade-plugin.version>3.2.4</maven-shade-plugin.version>
<main.class></main.class>
</properties>
<build>
<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>${maven-shade-plugin.version}</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Main-Class>${main.class}</Main-Class>
</manifestEntries>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
</transformers>
<outputFile>
${project.build.directory}/${project.artifactId}-${project.version}-fat.jar
</outputFile>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
Standard POM
ES Data Access
Application Cache
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.9.2</version>
</dependency>
Run Scrpits
start.sh
#!/bin/bash
bashPath=/usr/local/app
appPath=/usr/local/application/bin
logFile=$bashPath/startScript.log
server=app.jar
source /etc/profile
if [ ! -d $bashPath ];then
mkdir -p $bashPath
fi
chmod a+x $appPath/$server
nohup java \
-XX:MaxRAMPercentage=75.0 \
-XX:+UseG1GC -XX:+UseContainerSupport \
-Xloggc:$bashPath/app_gc.log \
-Dfile.encoding=utf-8 \
-XX:ErrorFile=$bashPath/jvm_error.log \
-jar $appPath/$server \
2>&1 | tee $bashPath/server.log >> $bashPath/server.log 2>&1 &
sleep 3
num=`ps -ef |grep $appPath/$server |grep -v grep|wc -l`
echo "the num of process after start is $num">>$logFile
if [ $num -lt 1 ];then
echo "the process is not exit after start ">>$logFile
exit 1
fi
exit 0
stop.sh
#!/bin/bash
bashPath=/usr/local/app
appPath=/usr/local/application/bin
logFile=$bashPath/startScript.log
server=app.jar
pidInfo=$(ps -ef | grep "$appPath/$server" | grep -v grep | awk '{print $2}')
for pid in $pidInfo;do
kill -9 $pid
done
#check process num
num=$(ps -ef | grep "$appPath/$server" | grep -v grep |wc -l)
if [ $num -gt 0 ];then
echo "`date` after stop process in force way the processNum is $num still bigger than 0">>$logFile
exit 1
fi
exit 0
monitor.sh
#!/bin/bash
bashPath=/usr/local/app
appPath=/usr/local/application/bin
logFile=$bashPath/startScript.log
server=app.jar
num=`ps -ef |grep $appPath/$server |grep -v grep|wc -l`
echo "`date` the num of process is $num">>$logFile
if [ $num -lt 1 ];then
exit 1
fi
exit 0
Utils
Nullsafe
public class Nullsafe {
private Nullsafe() {}
public static <T> boolean nonNull(Supplier<T> supplier){
try {
return supplier.get() != null;
} catch (NullPointerException e) {
return false;
}
}
public static <T> T of(Supplier<T> supplier){
return of(supplier, null);
}
public static <T> T of(Supplier<T> supplier, T defauleValue){
try {
return supplier.get();
} catch (NullPointerException e) {
return defauleValue;
}
}
}
Lazyable
public class Lazyable<T> {
private T value;
private Supplier<T> supplier;
public static <T> Lazyable<T> of(Supplier<T> supplier) {
Objects.requireNonNull(supplier);
Lazyable<T> lazyable = new Lazyable<>();
lazyable.supplier = supplier;
return lazyable;
}
public synchronized T value() {
if (value == null) {
value = supplier.get();
}
return value;
}
}
FlowTracker
/**
* 用于记录跟踪一个操作流程(同一线程)内的数据
*/
public class FlowTracker {
private static final Map<Class<?>, ThreadLocal<Object>> trackers = new ConcurrentHashMap<>();
private static final Map<String, ThreadLocal<Map<String, Object>>> trackers2 = new ConcurrentHashMap<>();
private FlowTracker() {}
public static <T> T start(Class<T> namespace, T initValue) {
trackers.computeIfAbsent(namespace, k -> new ThreadLocal<>()).set(initValue);
return initValue;
}
public static Map<String, Object> start(String namespace) {
return trackers2.computeIfAbsent(namespace, k -> ThreadLocal.withInitial(LinkedHashMap::new)).get();
}
/**
* 获取指定 namespace 在当前线程的数据
* @param <T>
* @param namespace
* @return null 如果指定 namespace 在当前线程未 {@link #start(Class, Object)} 或已经 {@link #end(Class)}
*/
@Nullable
@SuppressWarnings("unchecked")
public static <T> T get(Class<T> namespace) {
ThreadLocal<Object> threadLocal = trackers.get(namespace);
if (threadLocal == null) return null;
return (T) threadLocal.get();
}
/**
* 获取指定 namespace 在当前线程的数据
* @param namespace
* @return null 如果指定 namespace 在当前线程未 {@link #start(String)} 或已经 {@link #end(String)}
*/
@Nullable
public static Map<String, Object> get(String namespace) {
ThreadLocal<Map<String, Object>> threadLocal = trackers2.get(namespace);
if (threadLocal == null) return null;
return threadLocal.get();
}
public static <T> void end(Class<T> clazz) {
ThreadLocal<Object> threadLocal = trackers.get(clazz);
if (threadLocal != null) {
threadLocal.remove();
}
}
public static void end(String namespace) {
ThreadLocal<Map<String, Object>> threadLocal = trackers2.get(namespace);
if (threadLocal != null) {
threadLocal.remove();
}
}
}
DevOPS
JVM Arguments in Container
java -XX:+UseContainerSupport -XX:MaxRAMPercentage=70.0
-
🎨 UseContainerSupport - 此参数用于使 JVM 在分配堆大小时考虑容器内存限制,而不是主机配置。🔗What does UseContainerSupport VM parameter do?\
-
🎨 InitialRAMPercentage、MinRAMPercentage 、MaxRAMPercentage - 设置JVM 初始化、最小、最大堆内存大小占系统内存大小的百分比。🔗JVM Parameters InitialRAMPercentage, MinRAMPercentage, and MaxRAMPercentage\
-
🎨 PrintFlagsFinal - 打印最终JVM的各个参数值,可以用来调试查看自己配置JVM参数后的最终效果。\
-
🎨 -XshowSettings:vm - 打印 jvm 配置信息。