• RESTful Demo


    数据库

    • 数据库创建脚本, 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> {
    }
    
  • 相关阅读:
    bzoj1818 [Cqoi2010]内部白点
    bzoj4001 [TJOI2015]概率论
    bzoj3997 [TJOI2015]组合数学
    bzoj3193 [JLOI2013]地形生成
    bzoj4869 [Shoi2017]相逢是问候
    bzoj4868 [Shoi2017]期末考试
    CF421D Bug in Code
    CCPC-WFinal-女生专场
    CF915F Imbalance Value of a Tree
    soj考试2
  • 原文地址:https://www.cnblogs.com/seliote/p/10587468.html
Copyright © 2020-2023  润新知