My Development Quickstart

292 阅读3分钟

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