RESTful Demo

Demo 功能

兩個模塊, App 與 Admin, App 模塊提供增長用戶(/add?name=${name})與查詢用戶(/query/${id}), Admin 模塊提供列出全部用戶(/listAllUser), 用戶共有兩個屬性, id(由系統自動分配) 與 name(由增長用戶時的 name 參數提供)java

編碼

項目基礎配置

開發環境爲 IDEA ULTIMATE 2018.3.5, JDK 1.8, Tomcat 8, Maven Java Web 項目, RESTful 傳輸數據時只支持 JSON 格式, Group Id 爲 com.seliote, Artifact Id 爲 RESTfulDemoweb

Maven 配置

爲 pom.xml 添加如下配置spring

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.targer>1.8</maven.compiler.targer>
</properties>
<packaging>war</packaging>
<build>
    <sourceDirectory>src/main/java</sourceDirectory>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
        </resource>
    </resources>
    <testSourceDirectory>src/test/java</testSourceDirectory>
    <testResources>
        <testResource>
            <directory>src/test/resources</directory>
        </testResource>
    </testResources>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-war-plugin</artifactId>
            <version>2.3</version>
            <configuration>
                <warSourceDirectory>web</warSourceDirectory>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
    </plugins>
</build>

建立根應用上下文

添加 Spring 依賴, 先在 pom.xml 的 properties 標籤中添加屬性 <spring.version>5.1.5.RELEASE</spring.version> 用於 Spring 版本, 再新增 Spring MVC 依賴apache

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>${spring.version}</version>
    <scope>compile</scope>
</dependency>

建立類 com.seliote.restfuldemo.config.RootContextConfigjson

package com.seliote.restfuldemo.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;

/**
 * @author seliote
 * @date 2019-03-24
 * @description Spring 根應用上下文配置類
 */
@Configuration
@ComponentScan(
        basePackages = "com.seliote.restfuldemo",
        excludeFilters = @ComponentScan.Filter(
                type = FilterType.ANNOTATION,
                classes = {Configuration.class, Controller.class}
        )
)
public class RootContextConfig {
}

此配置類如今只配置了組建掃描, 暫沒有添加其餘配置, 以後會一點點添加進來api

建立 App 與 Admin 上下文以及啓動配置

建立 App 模塊 Controller 所歸屬的包 com.seliote.restfuldemo.app 以及 Admin 模塊 Controller 所歸屬的包 com.seliote.restfuldemo.admin
建立 App 上下文配置類 com.seliote.restfuldemo.config.AppContextConfig瀏覽器

package com.seliote.restfuldemo.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

/**
 * @author seliote
 * @date 2019-03-24
 * @description App 模塊的 Spring 上下文配置
 */
@Configuration
@EnableWebMvc
@ComponentScan(
        basePackages = "com.seliote.restfuldemo.app",
        useDefaultFilters = false,
        includeFilters = @ComponentScan.Filter(
                type = FilterType.ANNOTATION,
                classes = Controller.class
        )
)
public class AppContextConfig {
}

建立 Admin 上下文配置類 com.seliote.restfuldemo.config.AdminContextConfigrestful

package com.seliote.restfuldemo.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

/**
 * @author seliote
 * @date 2019-03-24
 * @description Admin 上下文配置類
 */
@Configuration
@EnableWebMvc
@ComponentScan(
        basePackages = "com.seliote.restfuldemo.admin",
        useDefaultFilters = false,
        includeFilters = @ComponentScan.Filter(
                type = FilterType.ANNOTATION,
                classes = Controller.class
        )
)
public class AdminContextConfig {
}

下面的配置會用到 Servlet, 因此添加 Servlet 依賴mvc

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
</dependency>

建立 Spring 啓動類 com.seliote.restfuldemo.config.Bootstrapapp

package com.seliote.restfuldemo.config;

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.ServletRegistration;

/**
 * @author seliote
 * @date 2019-03-24
 * @description Spring 啓動類
 */
public class Bootstrap implements WebApplicationInitializer {
    @Override
    public void onStartup(javax.servlet.ServletContext aServletContext) {
        // 根應用上下文
        AnnotationConfigWebApplicationContext rootContext
                = new AnnotationConfigWebApplicationContext();
        rootContext.register(RootContextConfig.class);
        aServletContext.addListener(new ContextLoaderListener(rootContext));
        
        // 靜態資源直接處理
        aServletContext.getServletRegistration("default").addMapping("/resources/*");
        
        // App 模塊應用上下文
        AnnotationConfigWebApplicationContext appContext
                = new AnnotationConfigWebApplicationContext();
        appContext.register(AppContextConfig.class);
        ServletRegistration.Dynamic appServletRegistration 
                = aServletContext.addServlet("appServlet", new DispatcherServlet(appContext));
        appServletRegistration.setLoadOnStartup(1);
        // 該 Servlet 映射至 / 則能夠響應全部請求, 可是更具體的 URL 映射會優先進行處理
        appServletRegistration.addMapping("/");
        
        AnnotationConfigWebApplicationContext adminContext
                = new AnnotationConfigWebApplicationContext();
        adminContext.register(AdminContextConfig.class);
        DispatcherServlet adminDispatcherServlet = new DispatcherServlet(adminContext);
        // 禁用 OPTIONS 請求
        adminDispatcherServlet.setDispatchOptionsRequest(false);
        ServletRegistration.Dynamic adminServletRegistration
                = aServletContext.addServlet("adminServlet", adminDispatcherServlet);
        adminServletRegistration.setLoadOnStartup(2);
        // URL 映射模式必定不要少末尾的 *
        adminServletRegistration.addMapping("/admin/*");
    }
}

爲 IDEA 設置上下文配置以便檢測依賴注入, Project Structure -> Modules -> Spring -> 右側加號 -> 依次添加上下文並設置父上下文

建立 App 模塊

自頂向下進行建立, 首先建立一個控制器 com.seliote.restfuldemo.app.AppController

package com.seliote.restfuldemo.app;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

/**
 * @author seliote
 * @date 2019-03-24
 * @description App 模塊的 Controller
 */
@Controller
public class AppController {
    
    private UserService mUserService;

    @Autowired
    public void setUserService(UserService aUserService) {
        mUserService = aUserService;
    }
}

建立其所用的 Service 接口 com.seliote.restfuldemo.service.UserService

package com.seliote.restfuldemo.service;

import java.util.Set;

/**
 * @author seliote
 * @date 2019-03-24
 * @description User 相關的服務
 */
public interface UserService {
    void addUser(String aName);
    User queryUser(long aId);
    Set<User> listAllUser();
}

建立用戶 POJO 類, com.seliote.restfuldemo.pojo.User

package com.seliote.restfuldemo.pojo;

/**
 * @author seliote
 * @date 2019-03-24
 * @description 用戶的 POJO 類
 */
public class User {

    private static volatile long sNextId = 0;

    private long mId;
    private String mName;

    private static synchronized long getNextId() {
        return ++sNextId;
    }

    public User(String aName) {
        mId = getNextId();
        mName = aName;
    }

    public long getId() {
        return mId;
    }

    public void setId(long aId) {
        mId = aId;
    }

    public String getName() {
        return mName;
    }

    public void setName(String aName) {
        mName = aName;
    }
}

此處未作約束限制, 好比 id 應大於 0, name 不爲 null 或空字符串且長度小於 16 個字符, 下面添加約束
先增長約束所用的依賴, properties 標籤中先增長 hibernate 依賴版本 <hibernate.validator.version>6.0.16.Final</hibernate.validator.version>, 添加依賴

<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>2.0.1.Final</version>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>${hibernate.validator.version}</version>
    <scope>runtime</scope>
    <exclusions>
        <exclusion>
            <groupId>org.jboss.logging</groupId>
            <artifactId>jboss-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator-annotation-processor</artifactId>
    <version>${hibernate.validator.version}</version>
    <scope>compile</scope>
    <optional>true</optional>
</dependency>

建立自定義約束, 判斷字符串不爲 null 且不爲空且長度小於 16, com.seliote.restfuldemo.annotation.UserNameValidator

package com.seliote.restfuldemo.annotation;

import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
import javax.validation.ReportAsSingleViolation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author seliote
 * @date 2019-03-24
 * @description 用於驗證用戶名不爲 null 且不爲空且長度小於 16
 */
@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.CONSTRUCTOR
        , ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {UserNameValidatorConstraint.class})
@ReportAsSingleViolation
public @interface UserNameValidator {

    String message() default "{validator.usernamevalidator}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    @Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.CONSTRUCTOR
            , ElementType.METHOD, ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @interface List {
        UserNameValidator[] value();
    }
}

class UserNameValidatorConstraint implements ConstraintValidator<UserNameValidator, CharSequence> {

    @Override
    public void initialize(UserNameValidator constraintAnnotation) {

    }

    @Override
    public boolean isValid(CharSequence aCharSequence, ConstraintValidatorContext aConstraintValidatorContext) {
        if (aCharSequence == null) {
            return false;
        }
        return aCharSequence.toString().trim().length() > 0 && aCharSequence.toString().trim().length() <= 16;
    }
}

將約束用在 User 類上, 在 mName 字段上添加 @UserNameValidator 約束, 並指定國際化信息, User 類應該以下所示,

package com.seliote.restfuldemo.pojo;

import com.seliote.restfuldemo.annotation.UserNameValidator;

/**
 * @author seliote
 * @date 2019-03-24
 * @description 用戶的 POJO 類
 */
public class User {

    private static volatile long sNextId = 0;

    private long mId;
    @UserNameValidator(message = "{validation.user.name}")
    private String mName;

    private static synchronized long getNextId() {
        return ++sNextId;
    }

    public User(String aName) {
        mId = getNextId();
        mName = aName;
    }

    public long getId() {
        return mId;
    }

    public void setId(long aId) {
        mId = aId;
    }

    public String getName() {
        return mName;
    }

    public void setName(String aName) {
        mName = aName;
    }

    @Override
    public boolean equals(Object aO) {
        if (!(aO instanceof User)) {
            return false;
        }
        return ((User) aO).getId() == mId;
    }
}

建立約束國際化數據源文件 /WEB-INF/i18n/validation_zh_CN.properties

validation.user.name=用戶名長度需大於 1 且小於 16

接下來配置約束所用的 Bean 依賴, Root 上下文中添加 MessageSource LocalResolver LocalValidationFactoryBean 以及 MethodValidationPostProcessor 依賴, 以下

@Bean
public MessageSource messageSource() {
    ReloadableResourceBundleMessageSource reloadableResourceBundleMessageSource
            = new ReloadableResourceBundleMessageSource();
    reloadableResourceBundleMessageSource.setCacheSeconds(-1);
    reloadableResourceBundleMessageSource.setDefaultEncoding(StandardCharsets.UTF_8.name());
    reloadableResourceBundleMessageSource.setBasenames("/WEB-INF/i18n/validation");
    return reloadableResourceBundleMessageSource;
}
@Bean
public LocalValidatorFactoryBean localValidatorFactoryBean() throws ClassNotFoundException {
    LocalValidatorFactoryBean localValidatorFactoryBean
            = new LocalValidatorFactoryBean();
    // 若是類路徑上提供了多個實現這步則是必須, 可是對於 Tomcat 來講這步是多餘的
    localValidatorFactoryBean.setProviderClass(Class.forName("org.hibernate.validator.HibernateValidator"));
    localValidatorFactoryBean.setValidationMessageSource(messageSource());
    return localValidatorFactoryBean;
}
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() throws ClassNotFoundException {
    MethodValidationPostProcessor methodValidationPostProcessor
            = new MethodValidationPostProcessor();
    methodValidationPostProcessor.setValidator(localValidatorFactoryBean());
    return methodValidationPostProcessor;
}
@Bean
public LocaleResolver localResolver() {
    return new AcceptHeaderLocaleResolver();
}

接下來添加 App 上下文使用 Bean 驗證所需的依賴, 須要讓其實現 WebMvcConfigurer 接口並實現 getValidator 方法以更換 Spring MVC 爲 Controller 自動配置的驗證器

package com.seliote.restfuldemo.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.SpringValidatorAdapter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author seliote
 * @date 2019-03-24
 * @description App 模塊的 Spring 上下文配置
 */
@Configuration
@EnableWebMvc
@ComponentScan(
        basePackages = "com.seliote.restfuldemo.app",
        useDefaultFilters = false,
        includeFilters = @ComponentScan.Filter(
                type = FilterType.ANNOTATION,
                classes = Controller.class
        )
)
public class AppContextConfig implements WebMvcConfigurer {
        
        private SpringValidatorAdapter mSpringValidatorAdapter;

        @Autowired
        public void setSpringValidatorAdapter(SpringValidatorAdapter aSpringValidatorAdapter) {
                mSpringValidatorAdapter = aSpringValidatorAdapter;
        }

        @Override
        public Validator getValidator() {
                return mSpringValidatorAdapter;
        }
}

此時, 驗證器以能夠正常工做, 若是遇到約束不符將拋出異常
返回 UserService 引入 User 類, 並添加約束檢測, 最終的 UserService 類應該以下

package com.seliote.restfuldemo.service;

import com.seliote.restfuldemo.annotation.UserNameValidator;
import com.seliote.restfuldemo.pojo.User;
import org.springframework.validation.annotation.Validated;

import javax.validation.Valid;
import java.util.Set;

/**
 * @author seliote
 * @date 2019-03-24
 * @description User 相關的服務
 */
@Validated
public interface UserService {
    void addUser(@UserNameValidator(message = "{validation.user.name}") String aName);
    @Valid
    User queryUser(long aId);
    @Valid
    Set<User> listAllUser();
}

返回 AppController, 此時發現 UserService 尚未實現, 添加一個類 com.seliote.restfuldemo.impl.service.UserServiceImpl

package com.seliote.restfuldemo.impl.service;

import com.seliote.restfuldemo.pojo.User;
import com.seliote.restfuldemo.repository.UserRepository;
import com.seliote.restfuldemo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.validation.Valid;
import java.util.Set;

/**
 * @author seliote
 * @date 2019-03-24
 * @description UserService 的實現
 */
@Service
public class UserServiceImpl implements UserService {

    private UserRepository mUserRepository;

    @Autowired
    public void setUserRepo(UserRepository aUserRepository) {
        mUserRepository = aUserRepository;
    }

    @Override
    public void addUser(String aName) {
        mUserRepository.addUser(new User(aName));
    }

    @Override
    public User queryUser(long aId) {
        return mUserRepository.queryUser(aId);
    }

    @Override
    public Set<User> listAllUser() {
        return mUserRepository.listAllUser();
    }
}

接着定義用戶倉庫 com.seliote.restfuldemo.repository.UserRepository

package com.seliote.restfuldemo.repository;

import com.seliote.restfuldemo.pojo.User;

import java.util.Set;

/**
 * @author seliote
 * @date 2019-03-24
 * @description 用戶的倉庫
 */
// 參數校驗是服務的事情
public interface UserRepository {
    void addUser(User aUser);
    User queryUser(long aId);
    Set<User> listAllUser();
}

倉庫實現 com.seliote.restfuldemo.impl.repository.UserRepositoryImpl

package com.seliote.restfuldemo.impl.repository;

import com.seliote.restfuldemo.pojo.User;
import com.seliote.restfuldemo.repository.UserRepository;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Repository;

import javax.validation.constraints.NotNull;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

/**
 * @author seliote
 * @date 2019-03-24
 * @description 用戶倉庫實現
 */
@Repository
public class UserRepositoryImpl implements UserRepository {

    private Set<User> mUserSet = new HashSet<>();

    @Override
    public synchronized void addUser(User aUser) {
        mUserSet.add(aUser);
    }

    @Override
    @NotNull
    public synchronized User queryUser(long aId) {
        User user = null;
        for (User eachUser : mUserSet) {
            if (aId == eachUser.getId()) {
                user = eachUser;
                break;
            }
        }
        if (user == null) {
            throw new NoSuchUserExceprion("用戶 " + aId + " 不存在");
        }
        return user;
    }

    @Override
    public synchronized Set<User> listAllUser() {
        return Collections.unmodifiableSet(mUserSet);
    }
}

建立自定義異常,
接下來建立一個能夠響應請求的 Controller 方法

@RequestMapping(value = "add", method = RequestMethod.GET)
@ResponseBody
@ResponseStatus(HttpStatus.OK)
public ApiResult addUser(@RequestParam("name") String aName) {
    mUserService.addUser(aName);
    return new ApiResult(200, "建立用戶成功");
}

建立 ApiResult 實體 com.seliote.restfuldemo.pojo.ApiResult

package com.seliote.restfuldemo.pojo;

/**
 * @author seliote
 * @date 2019-03-25
 * @description Api 返回實體
 */
public class ApiResult {
    private int mCode;
    private String mMsg;
    
    public ApiResult(int aCode, String aMsg) {
        mCode = aCode;
        mMsg = aMsg;
    }

    public int getCode() {
        return mCode;
    }

    public void setCode(int aCode) {
        mCode = aCode;
    }

    public String getMsg() {
        return mMsg;
    }

    public void setMsg(String aMsg) {
        mMsg = aMsg;
    }
}

接下來配置 App 模塊的內容類型協商以及消息轉換器, 須要注入一個 ObjectMapper 的 Bean, 其餘均爲實現接口 WebMvcConfigurer 的方法

private ObjectMapper mObjectMapper;
@Autowired
public void setObjectMapper(ObjectMapper aObjectMapper) {
    mObjectMapper = aObjectMapper;
}

@Override
public void configureContentNegotiation(ContentNegotiationConfigurer aContentNegotiationConfigurer) {
    // 容許擴展名與 Accept 頭
    aContentNegotiationConfigurer.favorPathExtension(true)
            .favorParameter(false)
            .ignoreAcceptHeader(false)
            .defaultContentType(MediaType.APPLICATION_JSON);
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> aHttpMessageConverters) {
    // 只容許 JSON
    MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter
            = new MappingJackson2HttpMessageConverter();
    mappingJackson2HttpMessageConverter.setSupportedMediaTypes(Arrays.asList(
            new MediaType("application", "json"),
            new MediaType("text", "json")
    ));
    mappingJackson2HttpMessageConverter.setObjectMapper(mObjectMapper);
    aHttpMessageConverters.add(mappingJackson2HttpMessageConverter);
}

下來添加 Lg4j 依賴, 由於排除了 JBOSS, 若是不添加如下依賴則會一直 404

<!-- log4j 接口 -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>${log4j.version}</version>
    <scope>compile</scope>
</dependency>
<!-- log4j 具體實現 -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>${log4j.version}</version>
    <scope>runtime</scope>
</dependency>
<!-- 使 slf4j 使用 log4j 的橋接口 -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>${log4j.version}</version>
    <scope>runtime</scope>
</dependency>
<!-- commons logging 使用 log4j 的橋接口 -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-jcl</artifactId>
    <version>${log4j.version}</version>
    <scope>runtime</scope>
</dependency>
<!-- Hibernate Validator 默認使用不支持 Log4j2 的 JBoss 版本用於記錄日誌 -->
<!-- 咱們須要明確提供一個支持 Log4j 的版本並排除舊版依賴 -->
<dependency>
    <groupId>org.jboss.logging</groupId>
    <artifactId>jboss-logging</artifactId>
    <version>3.3.2.Final</version>
    <scope>runtime</scope>
</dependency>

建立 Log4j 2 的配置文件, /resources/log4j2.xml

<?xml version="1.0" encoding="UTF-8" ?>

<configuration status="info">
    <appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>
        <RollingFile name="FileAppender" fileName="/home/seliote/Temp/application.log"
                     filePattern="../logs/application-%d{MM-dd-yyyy}-%i.log">
            <PatternLayout>
                <pattern>%d{HH:mm:ss.SSS} [%t] %X{%id} %X{username} %-5level %c{36} %l: %msg%n</pattern>
            </PatternLayout>
            <Policies>
                <SizeBasedTriggeringPolicy size="10 MB"/>
            </Policies>
            <DefaultRolloverStrategy min="1" max="4"/>
        </RollingFile>
    </appenders>

    <loggers>
        <root level="info">
            <appender-ref ref="Console"/>
        </root>
        <logger name="com.seliote" level="info" additivity="false">
            <appender-ref ref="Console" />
            <appender-ref ref="FileAppender">
                <MarkerFilter marker="file" onMatch="NEUTRAL" onMismatch="DENY"/>
            </appender-ref>
        </logger>
        <logger name="org.apache" level="info"/>
        <logger name="org.springframework" level="info"/>
    </loggers>
</configuration>

再把依賴添加至生成的 WAR 文件的 lib 目錄裏, Project Structure -> Artifacts -> RESTfulDemo:Web Exploded -> 而後選中 Avaliable Elements 中的全部 JAR 後 put into /WEB-INF/lib.
打開瀏覽器訪問 http://localhost:8080/add?name=OK, 應該是正常的 json 返回數據, 接着訪問 http://localhost:8080/add?name=, 此時約束不經過, 代碼拋出異常 ConstraintViolationException 且未處理, 用戶將收到 500 返回碼, 下面建立一個處理程序中可能拋出異常的處理器 com.seliote.restfuldemo.config.ExceptionHandler

package com.seliote.restfuldemo.config;

import com.seliote.restfuldemo.exception.NoSuchUserException;
import org.springframework.http.HttpStatus;
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;

/**
 * @author seliote
 * @date 2019-03-25
 * @description 異常處理
 */
@ControllerAdvice
public class ExceptionHandler {

    @org.springframework.web.bind.annotation.ExceptionHandler(ConstraintViolationException.class)
    @ResponseBody
    // 該狀態碼將覆蓋 Controller 方法返回的狀態碼
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Error ConstraintViolationExceptionHandler(ConstraintViolationException aExp) {
        return new Error(aExp.getMessage());
    }

    @org.springframework.web.bind.annotation.ExceptionHandler(NoSuchUserException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Error NoSuchUserException(NoSuchUserException aExp) {
        return new Error(aExp.getMessage());
    }
}

class Error {
    String errMsg;

    public Error(String aErrMsg) {
        errMsg = aErrMsg;
    }

    public String getErrMsg() {
        return errMsg;
    }

    public void setErrMsg(String aErrMsg) {
        errMsg = aErrMsg;
    }
}

再在 AppController 添加一個查詢方法

@RequestMapping(value = "query/{userId:\\d+}", method = RequestMethod.GET)
@ResponseBody
@ResponseStatus(HttpStatus.OK)
public User query(@PathVariable("userId") long aId) {
    return mUserService.queryUser(aId);
}

App 模塊至此完成, 嘗試訪問

Admin 模塊

添加控制器 com.seliote.restfuldemo.admin.AdminController

package com.seliote.restfuldemo.admin;

import com.seliote.restfuldemo.pojo.User;
import com.seliote.restfuldemo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

import java.util.Set;

/**
 * @author seliote
 * @date 2019-03-25
 * @description Admin 模塊的控制器
 */
@Controller
public class AdminController {
    
    private UserService mUserService;

    @Autowired
    public void setUserService(UserService aUserService) {
        mUserService = aUserService;
    }
    
    @RequestMapping(value = "listAllUser", method = RequestMethod.GET)
    @ResponseBody
    @ResponseStatus(HttpStatus.OK)
    public Set<User> listAllUser() {
        return mUserService.listAllUser();
    }
}

Admin 模塊與 App 模塊配置相似 com.seliote.restfuldemo.config.AdminContextConfig

package com.seliote.restfuldemo.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
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.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.Arrays;
import java.util.List;

/**
 * @author seliote
 * @date 2019-03-24
 * @description Admin 上下文配置類
 */
@Configuration
@EnableWebMvc
@ComponentScan(
        basePackages = "com.seliote.restfuldemo.admin",
        useDefaultFilters = false,
        includeFilters = @ComponentScan.Filter(
                type = FilterType.ANNOTATION,
                classes = Controller.class
        )
)
public class AdminContextConfig implements WebMvcConfigurer {

    private SpringValidatorAdapter mSpringValidatorAdapter;
    private ObjectMapper mObjectMapper;

    @Autowired
    public void setObjectMapper(ObjectMapper aObjectMapper) {
        mObjectMapper = aObjectMapper;
    }

    @Autowired
    public void setSpringValidatorAdapter(SpringValidatorAdapter aSpringValidatorAdapter) {
        mSpringValidatorAdapter = aSpringValidatorAdapter;
    }

    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer aContentNegotiationConfigurer) {
        // 容許擴展名與 Accept 頭
        aContentNegotiationConfigurer.favorPathExtension(true)
                .favorParameter(false)
                .ignoreAcceptHeader(false)
                .defaultContentType(MediaType.APPLICATION_JSON)
                .mediaType("json", MediaType.APPLICATION_JSON);
    }

    @SuppressWarnings("Duplicates")
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> aHttpMessageConverters) {
        // 只容許 JSON
        MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter
                = new MappingJackson2HttpMessageConverter();
        mappingJackson2HttpMessageConverter.setSupportedMediaTypes(Arrays.asList(
                new MediaType("application", "json"),
                new MediaType("text", "json")
        ));
        mappingJackson2HttpMessageConverter.setObjectMapper(mObjectMapper);
        aHttpMessageConverters.add(mappingJackson2HttpMessageConverter);
    }

    @Override
    public Validator getValidator() {
        return mSpringValidatorAdapter;
    }
}

訪問 http://localhost:8080/admin/listAllUser 測試

相關文章
相關標籤/搜索