Actual Spring Boot 2.0 Series (2) — глобальная обработка исключений и тестирование

Spring Boot Java задняя часть Микросервисы Архитектура сервер Spring gradle

предисловие

в повседневной жизниwebКогда во время разработки возникает исключение, часто необходимо передать унифицированныйОбработка исключений, чтобы клиент мог получать дружественные подсказки. Эта статья познакомитSpring BootсерединаГлобальная унифицированная обработка исключений.

Статьи из этой серии

  1. Actual Spring Boot 2.0 Series (1) — Создание образов Docker с помощью Gradle
  2. Actual Spring Boot 2.0 Series (2) — глобальная обработка исключений и тестирование
  3. Actual Spring Boot 2.0 Series (3) — Подробное объяснение асинхронных вызовов с использованием @Async
  4. Actual Spring Boot 2.0 Series (4) — Использование WebAsyncTask для обработки асинхронных задач
  5. Actual Spring Boot 2.0 Series (5) — прослушиватель, сервлет, фильтр и перехватчик
  6. Actual Spring Boot 2.0 Series (6) — несколько реализаций одномашинных задач синхронизации

текст

1. Создайте проект

использоватьSpring InitializerСоздаватьgradleпроектspring-boot-global-exception-handle, добавляйте связанные зависимости при создании. получить начальныйbuild.gradleследующее:

buildscript {
    ext {
        springBootVersion = '2.0.3.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group = 'io.ostenant.springboot.sample'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
    mavenCentral()
}


dependencies {
    compile('org.springframework.boot:spring-boot-starter-web')
    compile('org.projectlombok:lombok')
    compile('org.apache.commons:commons-lang3:3.1')
    compile('com.google.guava:guava:19.0')
    testCompile('org.springframework.boot:spring-boot-starter-test')
}

2. Настройте класс входа

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

3. Настройте класс сущности

Установить первымIntellij IdeaизlombokПлагины, которые подробно здесь не описаны. Помните, вам нужно установитьEnable annotation processingПроверьте это, иначетестовый кодсуществуетвремя компиляциине быть способным сделатьlombokПлагин настроенаннотациядля обработки.

использоватьlombokпредоставляется инструментоманнотацияНастройка класса сущностей

import lombok.Data;

@Data
public class User implements Serializable {
    private Long id;
    private String username;
    private String accountName;
}

4. Настройте объект ответа на исключение

ErrorMessageСущности используются для записи конкретныхинформация об исключении, и ответьтеклиент.

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@NoArgsConstructor
@Setter
@Getter
@ToString
public class ErrorMessage<T> {
    public static final Integer OK = 0;
    public static final Integer ERROR = 100;

    private Integer code;
    private String message;
    private String url;
    private T data;
}

5. Настройте связанные классы исключений

SessionNotFoundException.java

public class SessionNotFoundException extends Exception {
    @Getter
    @Setter
    protected String message;

    public SessionNotFoundException() {
        setMessage("Session is not found!");
    }

    public SessionNotFoundException(String message) {
        this.message = message;
    }
}

NullOrEmptyException.java

public class NullOrEmptyException extends Exception {
    @Getter
    @Setter
    protected String message;

    public NullOrEmptyException() {
        setMessage("Parameter is null or empty!");
    }

    public NullOrEmptyException(String message) {
        this.message = message;
    }
}

IllegalPropertiesException.java

public class IllegalPropertiesException extends Exception {
    @Getter
    @Setter
    protected String message;

    public IllegalPropertiesException() {
        setMessage("Prop is illegal!");
    }

    public IllegalPropertiesException(String message) {
        this.message = message;
        setMessage(String.format("Prop: %s is illegal!", message));
    }
}

6. Настройте глобальное уведомление об исключении

отspring 3.2начало, добавлено@ControllerAdviceАннотация, которую можно использовать для определения@ExceptionHandler, и применяется к конфигу@RequestMappingв контроллере.

@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(SessionNotFoundException.class)
    @ResponseBody
    public ErrorMessage<String> sessionNotFoundExceptionHandler(HttpServletRequest request, SessionNotFoundException exception) throws Exception {
        return handleErrorInfo(request, exception.getMessage(), exception);
    }

    @ExceptionHandler(NullOrEmptyException.class)
    @ResponseBody
    public ErrorMessage<String> nullOrEmptyExceptionHandler(HttpServletRequest request, NullOrEmptyException exception) throws Exception {
        return handleErrorInfo(request, exception.getMessage(), exception);
    }

    @ExceptionHandler(IllegalPropertiesException.class)
    @ResponseBody
    public ErrorMessage<String> illegalPropExceptionHandler(HttpServletRequest request, IllegalPropertiesException exception) throws Exception {
        return handleErrorInfo(request, exception.getMessage(), exception);
    }

    @ExceptionHandler(Exception.class)
    @ResponseBody
    public ErrorMessage<String> exceptionHandler(HttpServletRequest request, Exception exception) throws Exception {
        return handleErrorInfo(request, exception.getMessage(), exception);
    }

    private ErrorMessage<String> handleErrorInfo(HttpServletRequest request, String message, Exception exception) {
        ErrorMessage<String> errorMessage = new ErrorMessage<>();
        errorMessage.setMessage(message);
        errorMessage.setCode(ErrorMessage.ERROR);
        errorMessage.setData(message);
        errorMessage.setUrl(request.getRequestURL().toString());
        return errorMessage;
    }
}

Приведенный выше код указывает3Кусокконкретныйобработчик исключений и1Кусокдефолтобработчик исключений. Когда при обработке запроса возникает исключение,обработчик исключенийизПорядок конфигурациипопробуй по порядкуненормальное совпадениеииметь дело с.

Если исключение не находится в SessionNotFoundException, NullOrEmptyException, IllegalPropertiesException,SpringдоверитдефолтизexceptionHandlerдля обработки.

7. Настройте контроллер

В зависимости от разницы запрошенных данных, контроллер может покрыть вышеперечисленное3Путь обработки исключений.

@RestController
public class UserController {
    @PostMapping("user")
    public ResponseEntity<?> save(HttpServletRequest request, HttpSession session) throws Exception {
        String sessionId = (String) session.getAttribute("sessionId");
        if (StringUtils.isBlank(sessionId)) {
            throw new SessionNotFoundException();
        }

        String userPlainText = request.getParameter("user");
        if (StringUtils.isBlank(userPlainText) || StringUtils.equalsIgnoreCase("{}", userPlainText)) {
            throw new NullOrEmptyException();
        }

        ObjectMapper objectMapper = new ObjectMapper();
        User user = objectMapper.readValue(userPlainText, User.class);

        if (StringUtils.isBlank(user.getUsername())) {
            throw new IllegalPropertiesException("username");
        }

        if (StringUtils.isBlank(user.getAccountName())) {
            throw new IllegalPropertiesException("accountName");
        }
        return ResponseEntity.ok("Successful");
    }
}

8. Настройте класс фиктивного теста

Spring MockСоответствующая конфигурация здесь подробно не описывается, следующие тестовые классы охватываютUserControllerвсех путей выполнения.

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootApplication
@WebAppConfiguration
@Slf4j(topic = "UserControllerTester")
public class ApplicationTests {
    @Autowired
    private WebApplicationContext context;
    private MockMvc mockMvc;
    private MockHttpSession session;

    @Autowired
    private UserController userController;

    private ImmutableMap<Long, Pair<String, String>> map = new ImmutableMap.Builder<Long, Pair<String, String>>()
            .put(0x00001L, Pair.of("user", ""))
            .put(0x00002L, Pair.of("user", "{}"))
            .put(0x00003L, Pair.of("user", "{\"username\": \"\", \"accountName\": \"\"}"))
            .put(0x00004L, Pair.of("user", "{\"username\": \"Harrison\", \"accountName\": \"\"}"))
            .put(0x00005L, Pair.of("user", "{\"username\": \"Harrison\", \"accountName\": \"ostenant\"}"))
            .build();


    @Before
    public void setUp() throws Exception {
        boolean singleRunner = false;
        if (singleRunner) {
            this.mockMvc = MockMvcBuilders.standaloneSetup(userController).build();
        } else {
            this.mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
        }
        session = new MockHttpSession();
        session.setAttribute("sessionId", StringUtils.replace(UUID.randomUUID().toString(), "-", ""));
        log.debug("sessionId: {}", session.getAttribute("sessionId"));
    }

    /**
     * 测试SessionNotFoundException
     * @throws Exception
     */
    @Test
    public void testSessionNotFoundException() throws Exception {
        session.clearAttributes();
        // 模拟发送请求
        mockMvc.perform(
                MockMvcRequestBuilders.post("/user")
                        .param(map.get(0x00005L).getKey(), map.get(0x00005L).getValue())
                        .session(session))
                .andExpect(MockMvcResultMatchers.handler().handlerType(UserController.class))
                .andExpect(MockMvcResultMatchers.handler().methodName(("save")))
                .andDo(MockMvcResultHandlers.print())
                .andReturn();
    }

    /**
     * 测试NullOrEmptyException
     * @throws Exception
     */
    @Test
    public void testNullOrEmptyException() throws Exception {
        mockMvc.perform(
                MockMvcRequestBuilders.post("/user")
                        .param(map.get(0x00001L).getKey(), map.get(0x00001L).getValue())
                        .session(session))
                .andExpect(MockMvcResultMatchers.handler().handlerType(UserController.class))
                .andExpect(MockMvcResultMatchers.handler().methodName(("save")))
                .andDo(MockMvcResultHandlers.print())
                .andReturn();

        mockMvc.perform(
                MockMvcRequestBuilders.post("/user")
                        .param(map.get(0x00002L).getKey(), map.get(0x00002L).getValue())
                        .session(session))
                .andExpect(MockMvcResultMatchers.handler().handlerType(UserController.class))
                .andExpect(MockMvcResultMatchers.handler().methodName(("save")))
                .andDo(MockMvcResultHandlers.print())
                .andReturn();

    }

    /**
     * 测试IllegalPropException
     * @throws Exception
     */
    @Test
    public void testIllegalPropException() throws Exception {
        mockMvc.perform(
                MockMvcRequestBuilders.post("/user")
                        .param(map.get(0x00003L).getKey(), map.get(0x00003L).getValue())
                        .session(session))
                .andExpect(MockMvcResultMatchers.handler().handlerType(UserController.class))
                .andExpect(MockMvcResultMatchers.handler().methodName(("save")))
                .andDo(MockMvcResultHandlers.print())
                .andReturn();

        mockMvc.perform(
                MockMvcRequestBuilders.post("/user")
                        .param(map.get(0x00004L).getKey(), map.get(0x00004L).getValue())
                        .session(session))
                .andExpect(MockMvcResultMatchers.handler().handlerType(UserController.class))
                .andExpect(MockMvcResultMatchers.handler().methodName(("save")))
                .andDo(MockMvcResultHandlers.print())
                .andReturn();
    }

    /**
     * 测试正常运行的情况
     * @throws Exception
     */
    @Test
    public void testNormal() throws Exception {
        mockMvc.perform(
                MockMvcRequestBuilders.post("/user")
                        .param(map.get(0x00005L).getKey(), map.get(0x00005L).getValue())
                        .session(session))
                .andExpect(MockMvcResultMatchers.handler().handlerType(UserController.class))
                .andExpect(MockMvcResultMatchers.handler().methodName(("save")))
                .andDo(MockMvcResultHandlers.print())
                .andReturn();
    }
}

9. Результаты испытаний

Запускайте тесты партиями, результаты тестов будут следующими, и все тестовые примеры пройдены.

резюме

использовать@ControllerAdviceОбработка исключений также имеет определенныеограничение. только введитеControllerошибки слоя будут вызваны@ControllerAdviceиметь дело с.перехватчиквыдана ошибка, иДоступ к неправильному адресуСлучай@ControllerAdviceне может быть обработанSpring Bootдефолтмеханизм обработки исключенийиметь дело с.


Добро пожаловать в технический публичный аккаунт: Zero One Technology Stack

零壹技术栈

Эта учетная запись будет продолжать делиться сухими товарами серверных технологий, включая основы виртуальных машин, многопоточное программирование, высокопроизводительные фреймворки, асинхронное ПО, промежуточное ПО для кэширования и обмена сообщениями, распределенные и микросервисы, материалы для обучения архитектуре и расширенные учебные материалы и статьи.