При разработке бэкэнд-фреймворка необходимо обратить внимание на

Java

При разработке бэкэнд-фреймворка необходимо обратить внимание на

Писательские навыки у автора пока неглубокие, если что не так, укажите великодушно, буду признателен

Прошел год с тех пор, как я наткнулся на дорогу программистов.Я потихоньку чувствую, что большая часть работы в этом году проходит в простом CRUD, а сколько у нас иногда повторяющегося кода в CRUD. Шерстяная ткань? Некоторые коды нам нужно писать повторно каждый раз, когда мы пишем, что не только тратит время, но и не сильно улучшает наше собственное улучшение. случайно увиделПрограммист ты чего такой усталыйПосле статьи я вдруг понял, почему мы так долго работаем без извлечения каких-то общих частей, а уменьшение количества кода может позволить нам больше сосредоточиться на технологиях или улучшении бизнеса, верно?

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

  • пользовательский класс перечисления
  • Пользовательская информация об исключении
  • унифицированная информация о возврате
  • глобальная обработка исключений
  • Единая печать журнала

пользовательский класс перечисления

Для некоторых сообщений об ошибках, которые мы часто возвращаем, мы можем извлечь их и инкапсулировать в общедоступные части, а затем передать измененные в качестве параметров. Например, нам часто нужно проверить, пусто ли поле в бизнесе.Если оно пусто, мы вернем сообщение об ошибке.Поле xxx не может быть пустым, так почему бы нам не передать xxx как переменный параметр. Поэтому я подумал об использовании класса перечисления для определения информации об исключении, а затем использоватьString.format()метод побега

 1public enum ResponseInfoEnum {
2
3    SUCCESS(ResponseResult.OK,"处理成功"),
4    PARAM_LENGTH_ERROR(ResponseResult.ERROR, "参数:%s,长度错误,max length: %s"),
5    REQ_PARAM_ERROR(ResponseResult.ERROR, "请求报文必填参数%s缺失"),;
6
7    private Integer code;
8    private String message;
9
10    ResponseInfoEnum(Integer code, String message) {
11        this.code = code;
12        this.message = message;
13    }
14
15    public Integer getCode() {
16        return code;
17    }
18
19    public String getMessage() {
20        return message;
21    }
22
23}

Способ применения следующий

1String.format(ResponseInfoEnum.REQ_PARAM_ERROR.getMessage(),"testValue")

Вы можете видеть, что сгенерированное сообщение об ошибке请求报文必填参数testValue缺失

Пользовательская информация об исключении

Во-первых, нам нужно знать, почему мы используем пользовательскую информацию об исключении? Каковы преимущества его использования?

  1. Прежде всего, наша разработка должна быть разделена на модули, поэтому в первую очередь мы унифицируем пользовательский класс исключений, чтобы унифицировать метод отображения внешних исключений.
  2. Использование настраиваемых исключений, связанных с наследованием исключений, для выдачи обработанных сведений об исключениях может скрыть базовое исключение, что является более безопасным, а сведения об исключении более интуитивно понятными. Пользовательские исключения могут выдавать информацию, которую мы хотим выдать.Мы можем определить местоположение исключения по выброшенной информации.В соответствии с именем исключения мы можем узнать, где находится исключение, и изменить программу в соответствии с информацией о подсказке об исключении.
  3. Иногда, когда мы сталкиваемся с какой-либо проверкой или проблемами, нам нужно напрямую завершить текущий запрос. В это время мы можем закончить, создав пользовательское исключение. Если вы используете более новую версию SpringMVC в своем проекте, есть усовершенствования контроллера. может написать класс расширения контроллера с помощью аннотации @ControllerAdvice, чтобы перехватывать пользовательские исключения и отвечать интерфейсу соответствующей информацией.

Пользовательские исключения, которые нам нужно наследоватьRuntimeException

 1public class CheckException extends RuntimeException{
2
3    public CheckException() {
4    }
5
6    public CheckException(String message) {
7        super(message);
8    }
9
10    public CheckException(ResponseInfoEnum responseInfoEnum,String ...strings) {
11        super(String.format(responseInfoEnum.getMessage(),strings));
12    }
13}

унифицированная информация о возврате

В год, когда я впервые начал работать, проект, с которым я больше всего контактировал, — это проект взаимодействия между интерфейсом и сервером. Таким образом, наличие унифицированной возвращаемой информации не только более удобно для внешнего интерфейса, но и имеет большие преимущества для прокси-сервера AOP, стоящего за нами.

 1@Data
2@NoArgsConstructor
3public class ResponseResult<T> {
4    public static final Integer OK = 0;
5    public static final Integer ERROR = 100;
6
7    private Integer code;
8    private String message;
9    private T data;
10}

Таким образом, взаимодействие между фронтом и бэкендом будет более удобным.Если вы хотите получить бизнес-данные, вы можете получить их из данных.Если вы хотите получить признак успеха, то вы можете получить его из code code.Если вы хотите получить информацию, возвращаемую бэкендом, возьмите ее из сообщения.

глобальная обработка исключений

В моем предыдущем проекте каждыйControllerметоды наполненыtry....catch...Код после catch почти одинаков, и все они инкапсулируют возвращаемое сообщение об ошибке и тому подобное. Так почему бы нам не извлечь этот код и не использовать глобальную обработку исключений Spring для упрощения нашего кода?

 1@Slf4j
2@ControllerAdvice
3public class ControllerExceptionHandler {
4
5
6    @ExceptionHandler(value = Exception.class)
7    @ResponseBody
8    public ResponseResult<String> defaultErrorHandler(HttpServletRequest request, Exception exception){
9        log.error(ControllerLog.getLogPrefix()+"Exception: {}"+exception);
10        return handleErrorInfo(exception.getMessage());
11    }
12
13    @ExceptionHandler(CheckException.class)
14    @ResponseBody
15    public ResponseResult<String> checkExceptionHandler(HttpServletRequest request, CheckException exception){
16        return handleErrorInfo(exception.getMessage());
17    }
18
19    private ResponseResult<String> handleErrorInfo(String message) {
20        ResponseResult<String> responseEntity = new ResponseResult<>();
21        responseEntity.setMessage(message);
22        responseEntity.setCode(ResponseResult.ERROR);
23        responseEntity.setData(message);
24        ControllerLog.destoryThreadLocal();
25        return responseEntity;
26    }
27}

В глобальной обработке исключений наши пользовательские исключения не печатаются в журнале, потому что мы являемся известными исключениями для пользовательских исключений, и информация об ошибке была явно возвращена. И для неизвестных исключений, таких какExceptionДля неизвестных исключений нам необходимо распечатать журналы.Если есть особые требования, такие как отправка текстовых сообщений или отправка электронных писем для уведомления соответствующего персонала, здесь также можно выполнить глобальную настройку.

Единая печать журнала

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

 1@Slf4j
2@Aspect
3@Component
4public class ControllerLog {
5
6    private static final ThreadLocal<Long> START_TIME_THREAD_LOCAL =
7            new NamedThreadLocal<>("ThreadLocal StartTime");
8
9    private static final ThreadLocal<String> LOG_PREFIX_THREAD_LOCAL =
10            new NamedThreadLocal<>("ThreadLocal LogPrefix");
11
12    /**
13     * <li>Before       : 在方法执行前进行切面</li>
14     * <li>execution    : 定义切面表达式</li>
15     * <p>public * com.example.javadevelopmentframework.javadevelopmentframework.controller..*.*(..))
16     *      <li>public :匹配所有目标类的public方法,不写则匹配所有访问权限</li>
17     *      <li>第一个* :方法返回值类型,*代表所有类型 </li>
18     *      <li>第二个* :包路径的通配符</li>
19     *      <li>第三个..* :表示impl这个目录下所有的类,包括子目录的类</li>
20     *      <li>第四个*(..) : *表示所有任意方法名,..表示任意参数</li>
21     * </p>
22     * @param
23     */
24    @Pointcut("execution(public * com.example.javadevelopmentframework.javadevelopmentframework.controller..*.*(..))")
25    public void exectionMethod(){}
26
27
28    @Before("exectionMethod()")
29    public void doBefore(JoinPoint joinPoint){
30        START_TIME_THREAD_LOCAL.set(System.currentTimeMillis());
31        StringBuilder argsDes = new StringBuilder();
32        //获取类名
33        String className = joinPoint.getSignature().getDeclaringType().getSimpleName();
34        //获取方法名
35        String methodName = joinPoint.getSignature().getName();
36        //获取传入目标方法的参数
37        Object[] args = joinPoint.getArgs();
38        for (int i = 0; i < args.length; i++) {
39            argsDes.append("第" + (i + 1) + "个参数为:" + args[i]+"\n");
40        }
41        String logPrefix = className+"."+methodName;
42        LOG_PREFIX_THREAD_LOCAL.set(logPrefix);
43        log.info(logPrefix+"Begin 入参为:{}",argsDes.toString());
44    }
45
46    @AfterReturning(pointcut="exectionMethod()",returning = "rtn")
47    public Object doAfter(Object rtn){
48        long endTime = System.currentTimeMillis();
49        long begin = START_TIME_THREAD_LOCAL.get();
50        log.info(LOG_PREFIX_THREAD_LOCAL.get()+"End 出参为:{},耗时:{}",rtn,endTime-begin);
51        destoryThreadLocal();
52        return rtn;
53    }
54
55    public static String getLogPrefix(){
56        return LOG_PREFIX_THREAD_LOCAL.get();
57    }
58
59    public static void destoryThreadLocal(){
60        START_TIME_THREAD_LOCAL.remove();
61        LOG_PREFIX_THREAD_LOCAL.remove();
62    }
63
64}

контрольная работа

мы вConrollerнаписать следующий тест в

 1@RestController
2public class TestFrameworkController {
3
4    @RequestMapping("/success/{value}")
5    public String success(@PathVariable String value){
6        return "Return "+value;
7    }
8
9    @RequestMapping("/error/{value}")
10    public String error(@PathVariable String value){
11        int i = 10/0;
12        return "Return "+value;
13    }
14}

Код в модульном тесте выглядит следующим образом

 1@RunWith(SpringJUnit4ClassRunner.class)
2@SpringBootTest(classes = JavadevelopmentframeworkApplication.class)
3@AutoConfigureMockMvc
4public class JavadevelopmentframeworkApplicationTests {
5
6    @Autowired
7    private MockMvc mockMvc;
8
9    @Test
10    public void success() throws Exception {
11        mockMvc.perform(get("/success/11"));
12        mockMvc.perform(get("/error/11"));
13    }
14
15}

Вы можете видеть, что печать выглядит следующим образом

12019-09-03 20:38:22.248  INFO 73902 --- [           main] c.e.j.j.aop.ControllerLog                : TestFrameworkController.successBegin 入参为:第1个参数为:11
22019-09-03 20:38:22.257  INFO 73902 --- [           main] c.e.j.j.aop.ControllerLog                : TestFrameworkController.successEnd 出参为:Return 11,耗时:10
32019-09-03 20:38:22.286  INFO 73902 --- [           main] c.e.j.j.aop.ControllerLog                : TestFrameworkController.errorBegin 入参为:第1个参数为:11
42019-09-03 20:38:22.288 ERROR 73902 --- [           main] c.e.j.j.aop.ControllerExceptionHandler   : TestFrameworkController.errorException: {}java.lang.ArithmeticException: / by zero

Вы можете видеть, что были распечатаны входные параметры, выходные параметры и время выполнения всего метода каждого метода, обращающегося к контроллеру. Кроме того, в методе второго теста фиксируется информация об исключении и распечатывается журнал.

полный код

Суммировать

В процессе написания кода нам также необходимо постоянно подводить итоги, будь то изменение требований или построение системы, нам необходимо учитывать, какая часть изменена, а какая неизменна, извлекать неизменённое и инкапсулировать изменившееся . Таким образом, мы можем выполнить задачу с минимальными затратами в будущем, будь то расширение системы или изменение требований.