Some considerations for backend framework development

The author’s writing skill is still shallow, if there is something wrong, please point out generously, will be grateful

Stumbled in the programmer’s road for a year, slowly feel that most of the time in this year’s work is spent in simple CRUD, and sometimes we have how much repetitive code in CRUD? Some of the code we need to write repeatedly every time we write it is a waste of time and does not improve ourselves very much. When I accidentally saw why you were so tired of writing for programmers, I suddenly realized why we did not extract some common parts and reduce the amount of code so that we could focus more on the improvement of technology or business, right?

In combination with the issues described in the above article, and some of my experiences over the last year, the common parts that can be extracted from back-end framework development are the following

  • Custom enumerated classes
  • User-defined exception information
  • Unified Return information
  • Global exception handling
  • Unified Log Printing

Custom enumerated classes

For some of the error messages we often return, we can extract them and encapsulate them into a common part, then pass in the variable as a parameter. For example, in business, we often need to check whether a field is empty. If it is empty, an error message will be returned. The XXX field cannot be empty, so why not pass XXX as a variable parameter? The idea was to define the exception information with an enumerated class and then escape it with the string.format () method

 1public enum ResponseInfoEnum {

2

3    SUCCESS(ResponseResult.OK,"Processed successfully"),

4    PARAM_LENGTH_ERROR(ResponseResult.ERROR, "Parameter :%s, length error, Max length: %s"),

5    REQ_PARAM_ERROR(ResponseResult.ERROR, "Request message required parameter %s is missing"),;

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}

Copy the code

The usage is as follows

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

Copy the code

The generated error message is that the required parameter testValue of the request packet is missing

User-defined exception information

The first thing we need to know is why do we use custom exception messages? What are the benefits of using it?

  1. First of all, we must develop modules in the development, so first of all, we unified the custom exception class and unified the display of external exceptions.
  2. Using custom exception inheritance to throw the processed exception information can hide the underlying exception, which is safer and more intuitive. Custom exceptions can throw the information we want to throw, we can distinguish the location of the exception through the information thrown, according to the name of the exception we can know where there is an exception, according to the exception prompt for program modification.
  3. Sometimes we run into some kind of validation or problem and we need to terminate the current request directly. This can be done by throwing custom exceptions. If you are using a newer version of SpringMVC in your project with controller enhancements, You can write a controller enhancement class using the @ControllerAdvice annotation to intercept custom exceptions and respond to the front-end with appropriate information.

For custom exceptions, we need to inherit RuntimeException

 1public class CheckException extends RuntimeException{

2

3    public CheckException(a) {

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}

Copy the code

Unified Return information

In my first year on the job, the most common projects I worked on were projects with front – and back-end interactions. So having a unified return message is not only more convenient for the front end, but also a big benefit for our AOP proxies behind us.

 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}

Copy the code

This makes it easier for the front and back ends to interact. If you want to fetch business data you fetch it from data, if you want to fetch success signs you fetch it from code, and if you want to fetch information returned by the back end you fetch it from Message.

Global exception handling

In my previous projects, every Controller method was filled with try…. catch… “, and the code after catch is much the same, are the return of the error message encapsulation and so on. So why don’t we extract this code and take advantage of Spring’s global exception handling to simplify our code?

 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}

Copy the code

In global exception handling, no log is printed for our custom exception, because the custom exception is known to us, and the error message has been clearly returned. For unknown exceptions, such as Exception, we need to print logs. If there are special requirements, such as sending SMS messages or emails to inform related personnel, global configuration can also be performed here.

Unified Log Printing

Unified log printing only extracts the common printed log in the project and uses AOP to print it. For example, basically every input and output parameter of Controller method in our project will be printed, so this part will be extracted for unified management.

 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: Slice Before method execution</li>

14     * <li>Execution: Defines an aspect expression</li>

15     * <p>public * com.example.javadevelopmentframework.javadevelopmentframework.controller..*. *(..) )

16     *      <li>Public: matches all public methods of the target class, or all access permissions if not written</li>

17     *
  • the first *
  • : method return value type, * represents all types</li>

    18     *      <li>第二个*: Wildcard of the package path</li>

    19     *
  • third.. *
  • : represents all classes in the impl directory, including classes in subdirectories</li>

    20     *
  • the fourth *
  • (..) : * denotes all arbitrary method names,.. Represents any parameter</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        // Get the class name

    33        String className = joinPoint.getSignature().getDeclaringType().getSimpleName();

    34        // Get the method name

    35        String methodName = joinPoint.getSignature().getName();

    36        // Gets the parameters passed to the target method

    37        Object[] args = joinPoint.getArgs();

    38        for (int i = 0; i < args.length; i++) {

    39            argsDes.append("The first" + (i + 1) + "Parameter is :" + args[i]+"\n");

    40        }

    41        String logPrefix = className+"."+methodName;

    42        LOG_PREFIX_THREAD_LOCAL.set(logPrefix);

    43        log.info(logPrefix+"Begin takes the following parameter :{}",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(a);

    50        log.info(LOG_PREFIX_THREAD_LOCAL.get() +"End output parameter :{}, time :{}",rtn,endTime-begin);

    51        destoryThreadLocal();

    52        return rtn;

    53    }

    54

    55    public static String getLogPrefix(){

    56        return LOG_PREFIX_THREAD_LOCAL.get(a);

    57    }

    58

    59    public static void destoryThreadLocal(){

    60        START_TIME_THREAD_LOCAL.remove();

    61        LOG_PREFIX_THREAD_LOCAL.remove();

    62    }

    63

    64}

    Copy the code

    test

    We wrote the following test in 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}

    Copy the code

    The code in the unit test is as follows

     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}

    Copy the code

    You can see the print below

    1The 2019-09-03 20:38:22. INFO 248 73902. -- - [the main] C.E.J.J.A op ControllerLog: TestFrameworkController. SuccessBegin into arguments for: the parameters for 1:11

    2The 2019-09-03 20:38:22. INFO 257 73902--- [           main] c.e.j.j.aop.ControllerLog                : TestFrameworkController.successEnd 出参为:Return 11,耗时:10

    3The 2019-09-03 20:38:22. INFO 286 73902. -- - [the main] C.E.J.J.A op ControllerLog: TestFrameworkController. ErrorBegin into arguments for: the parameters for 1:11

    4The 2019-09-03 20:38:22. 288 ERROR 73902--- [ main] c.e.j.j.aop.ControllerExceptionHandler : TestFrameworkController.errorException: {}java.lang.ArithmeticException: / by zero

    Copy the code

    You can see that the input, output, and execution times of each method that accesses the Controller are printed out. In addition, in the second test method, the exception message is caught and the log is printed.

    The complete code

    conclusion

    In the process of writing code, we also need to constantly summarize. Whether it is the change of requirements or the construction of the system, we need to consider which part is changed and which part is unchanged, and extract the unchanged part and encapsulate the changed part. This allows us to accomplish tasks with minimal cost in future system expansion and requirement changes.