Several Points for Attention in the Development of Backend Framework

Keywords: Programming Spring Java

The author's writing skill is still shallow, if there is any mistake, please generously point out that I will certainly be grateful.

It's been a year since we stumbled on the path of programmers. It seems to me that most of this year's work is spent in simple CRUD. How many repetitive codes do we sometimes have in CRUD? Some codes need to be repeated every time we write them, which is not only a waste of time, but also not much improvement for our own promotion. I saw it by accident Why are programmers so tired? After the article, we wake up. Why don't we work so long to extract some public parts and reduce the amount of code so that we can concentrate more on the improvement of technology or business, right?

Combined with the problems described in the above article and some of my experiences in the past year, there are the following public parts that can be extracted in the development of the back-end framework

  • Custom Enumeration Class
  • Custom exception information
  • Unified Return Information
  • Global exception handling
  • Unified Log Printing

Custom Enumeration Class

For some of the errors we often return, we can extract them and encapsulate them as public parts, and then pass in the changes as parameters. For example, we often need to check whether a field is empty in our business. If it is empty, we need to return the error message that the xxx field cannot be empty. So why don't we pass xxx as a variable parameter? So I think of using enumeration classes to define exception information, and then using String.format() method to escape it.

public enum ResponseInfoEnum {

    SUCCESS(ResponseResult.OK,"Handling Success"),
    PARAM_LENGTH_ERROR(ResponseResult.ERROR, "parameter:%s,Error in length, max length: %s"),
    REQ_PARAM_ERROR(ResponseResult.ERROR, "Request message required parameters%s Defect"),;

    private Integer code;
    private String message;

    ResponseInfoEnum(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    public Integer getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }

}

The method of use is as follows.

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

You can see that the generated error message is missing the required parameter testValue for the request message

Custom exception information

First, we need to know why we use custom exception information. What are the benefits of using it?

  1. First of all, we must develop in modules, so first of all, we unified the custom exception class and unified the way to show the external exception.
  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 exceptions by throwing information. According to the exception name, we can know where there are exceptions, and modify the program according to the information of exception prompts.
  3. Sometimes when we encounter some validation or problem, we need to terminate the current request directly, then we can end by throwing a custom exception. If you use a newer version of Spring MVC in your project, you can enhance the controller by annotating a controller enhancement class to intercept from @Controller Advice. Define exceptions and respond to the corresponding information on the front end.

To customize exceptions, we need to inherit RuntimeException

public class CheckException extends RuntimeException{

    public CheckException() {
    }

    public CheckException(String message) {
        super(message);
    }

    public CheckException(ResponseInfoEnum responseInfoEnum,String ...strings) {
        super(String.format(responseInfoEnum.getMessage(),strings));
    }
}

Unified Return Information

In my first year of work, the most frequently encountered projects were front-end and back-end interaction projects. So having a unified return information is not only more convenient for the front end, but also has great benefits for the AOP agent behind us.

@Data
@NoArgsConstructor
public class ResponseResult<T> {
    public static final Integer OK = 0;
    public static final Integer ERROR = 100;

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

In this way, it will be more convenient for front-end and back-end to interact. If you want to get business data, you can get it from data. If you have been to pick up the sign of success, you can get it from code code code. If you want to get the information returned from back-end, you can get it from message.

Global exception handling

In my previous project, every Controller method was full of try... Catch... Code, and the code after catch was similar, encapsulating the error information returned and so on. So why don't we extract these code and simplify our code with Spring's global exception handling?

@Slf4j
@ControllerAdvice
public class ControllerExceptionHandler {


    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public ResponseResult<String> defaultErrorHandler(HttpServletRequest request, Exception exception){
        log.error(ControllerLog.getLogPrefix()+"Exception: {}"+exception);
        return handleErrorInfo(exception.getMessage());
    }

    @ExceptionHandler(CheckException.class)
    @ResponseBody
    public ResponseResult<String> checkExceptionHandler(HttpServletRequest request, CheckException exception){
        return handleErrorInfo(exception.getMessage());
    }

    private ResponseResult<String> handleErrorInfo(String message) {
        ResponseResult<String> responseEntity = new ResponseResult<>();
        responseEntity.setMessage(message);
        responseEntity.setCode(ResponseResult.ERROR);
        responseEntity.setData(message);
        ControllerLog.destoryThreadLocal();
        return responseEntity;
    }
}

In global exception handling, there is no print log for our custom exception, because we know the exception for our custom exception, and the error message has been returned clearly. For unknown anomalies such as Exception, we need to print the log. If there are special requirements, such as sending text messages and e-mails to inform the relevant personnel, we can also configure it globally.

Unified Log Printing

Unified log printing only extracts the common print logs from the project and uses AOP to print them. For example, in our project, almost every Controller method will print the input and output parameters, so this part will be extracted for unified management.

@Slf4j
@Aspect
@Component
public class ControllerLog {

    private static final ThreadLocal<Long> START_TIME_THREAD_LOCAL =
            new NamedThreadLocal<>("ThreadLocal StartTime");

    private static final ThreadLocal<String> LOG_PREFIX_THREAD_LOCAL =
            new NamedThreadLocal<>("ThreadLocal LogPrefix");

    /**
     * <li>Before       : Section before method execution</li>
     * <li>execution    : Define tangent expression</li>
     * <p>public * com.example.javadevelopmentframework.javadevelopmentframework.controller..*.*(..))
     *      <li>public :The public method matches all the target classes and all access privileges are matched if it is not written.</li>
     *      <li>The first *: Method return value type, * represents all types</li>
     *      <li>The second *: wildcards for package paths</li>
     *      <li>Third. *: Represents all classes in impl, including subdirectories</li>
     *      <li>The fourth *(.):* denotes all arbitrary method names,... Denotes any parameters.</li>
     * </p>
     * @param
     */
    @Pointcut("execution(public * com.example.javadevelopmentframework.javadevelopmentframework.controller..*.*(..))")
    public void exectionMethod(){}


    @Before("exectionMethod()")
    public void doBefore(JoinPoint joinPoint){
        START_TIME_THREAD_LOCAL.set(System.currentTimeMillis());
        StringBuilder argsDes = new StringBuilder();
        //Get the class name
        String className = joinPoint.getSignature().getDeclaringType().getSimpleName();
        //Get the method name
        String methodName = joinPoint.getSignature().getName();
        //Obtaining the parameters of the incoming target method
        Object[] args = joinPoint.getArgs();
        for (int i = 0; i < args.length; i++) {
            argsDes.append("The first" + (i + 1) + "The parameters are:" + args[i]+"\n");
        }
        String logPrefix = className+"."+methodName;
        LOG_PREFIX_THREAD_LOCAL.set(logPrefix);
        log.info(logPrefix+"Begin Participation:{}",argsDes.toString());
    }

    @AfterReturning(pointcut="exectionMethod()",returning = "rtn")
    public Object doAfter(Object rtn){
        long endTime = System.currentTimeMillis();
        long begin = START_TIME_THREAD_LOCAL.get();
        log.info(LOG_PREFIX_THREAD_LOCAL.get()+"End Participate in:{},time consuming:{}",rtn,endTime-begin);
        destoryThreadLocal();
        return rtn;
    }

    public static String getLogPrefix(){
        return LOG_PREFIX_THREAD_LOCAL.get();
    }

    public static void destoryThreadLocal(){
        START_TIME_THREAD_LOCAL.remove();
        LOG_PREFIX_THREAD_LOCAL.remove();
    }

}

test

We wrote the following tests in Conroller

@RestController
public class TestFrameworkController {

    @RequestMapping("/success/{value}")
    public String success(@PathVariable String value){
        return "Return "+value;
    }

    @RequestMapping("/error/{value}")
    public String error(@PathVariable String value){
        int i = 10/0;
        return "Return "+value;
    }
}

The code in the unit test is as follows

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = JavadevelopmentframeworkApplication.class)
@AutoConfigureMockMvc
public class JavadevelopmentframeworkApplicationTests {

	@Autowired
	private MockMvc mockMvc;

	@Test
	public void success() throws Exception {
		mockMvc.perform(get("/success/11"));
		mockMvc.perform(get("/error/11"));
	}

}

You can see that the print is as follows

2019-09-03 20:38:22.248  INFO 73902 --- [           main] c.e.j.j.aop.ControllerLog                : TestFrameworkController.successBegin Participation:The first parameter is:11
2019-09-03 20:38:22.257  INFO 73902 --- [           main] c.e.j.j.aop.ControllerLog                : TestFrameworkController.successEnd Participate in:Return 11,time consuming:10
2019-09-03 20:38:22.286  INFO 73902 --- [           main] c.e.j.j.aop.ControllerLog                : TestFrameworkController.errorBegin Participation:The first parameter is:11
2019-09-03 20:38:22.288 ERROR 73902 --- [           main] c.e.j.j.aop.ControllerExceptionHandler   : TestFrameworkController.errorException: {}java.lang.ArithmeticException: / by zero

You can see that each method accessing Controller's input, output, and execution time of the entire method have been printed out. In addition, exception information was captured and printed in the second test method.

Complete code

summary

In the process of coding, 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 of the change is invariable, extract the invariable and encapsulate the change. In the future, we can accomplish the task at the minimum cost in both system expansion and requirement change.

Posted by stephenjharris on Wed, 04 Sep 2019 23:22:00 -0700