Using annotation + reflection to eliminate duplicate code, wonderful!

Author: Leilei Chen
Source: https://llchen60.com/

1.1 case scenario

Assuming that the bank provides some API interfaces, the serialization of parameters is a little special. Instead of using JSON, we need to put the parameters together in turn to form a large string:

1) According to the order of API documents provided by the bank, all parameters form fixed length data and are spliced together as an entire string

2) Because each parameter has a fixed length, it needs to be filled if the length is not reached

  • If the length of the string type parameter is less than the length, it shall be filled with the following line right, that is, the string content shall be left
  • The length part of the parameter of the number type is filled with 0 to the left, that is, the actual number is right
  • For the representation of currency type, the amount needs to be rounded down to 2 digits to minutes, and the unit is divided into minutes. It is also filled in as a number type
  • Parameter MD5 operation as signature

1.2 preliminary code implementation

public class BankService {

    //Create user method
    public static String createUser(String name, String identity, String mobile, int age) throws IOException {
        StringBuilder stringBuilder = new StringBuilder();
        //The string is left, and the extra space is filled_
        stringBuilder.append(String.format("%-10s", name).replace(' ', '_'));
        //The string is left, and the extra space is filled_
        stringBuilder.append(String.format("%-18s", identity).replace(' ', '_'));
        //The number is to the right, and the excess is filled with 0
        stringBuilder.append(String.format("%05d", age));
        //String to the left, extra places to use_ fill
        stringBuilder.append(String.format("%-11s", mobile).replace(' ', '_'));
        //Finally, add MD5 as the signature
        stringBuilder.append(DigestUtils.md2Hex(stringBuilder.toString()));
        return Request.Post("http://localhost:45678/reflection/bank/createUser")
                .bodyString(stringBuilder.toString(), ContentType.APPLICATION_JSON)
                .execute().returnContent().asString();
    }

    //Payment method
    public static String pay(long userId, BigDecimal amount) throws IOException {
        StringBuilder stringBuilder = new StringBuilder();
        //The number is to the right, and the excess is filled with 0
        stringBuilder.append(String.format("%020d", userId));
        //The amount shall be rounded down to 2 digits to minutes, which shall be taken as the number to the right, and the redundant places shall be filled with 0
        stringBuilder.append(String.format("%010d", amount.setScale(2, RoundingMode.DOWN).multiply(new BigDecimal("100")).longValue()));
        //Finally, add MD5 as the signature
        stringBuilder.append(DigestUtils.md2Hex(stringBuilder.toString()));
        return Request.Post("http://localhost:45678/reflection/bank/pay")
                .bodyString(stringBuilder.toString(), ContentType.APPLICATION_JSON)
                .execute().returnContent().asString();
    }
}

This can basically meet the needs, but there are some problems:

  • The processing logic repeats with each other, and a Bug will appear if you are careless
  • The logic of string splicing, tagging and request sending in the processing flow is repeated in all methods
  • The parameter type and order of the input parameters of the actual method are not necessarily consistent with the interface requirements and are prone to error
  • Code level parameters are hard coded and cannot be clearly checked

1.3 optimizing code using interfaces and reflection

1.3.1 implement the POJO class that defines all interface parameters

@Data
public class CreateUserAPI {
    private String name;
    private String identity;
    private String mobile;
    private int age;
}

1.3.2 definition annotation itself

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Inherited
public @interface BankAPI {
    String desc() default "";
    String url() default "";
}


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
@Inherited
public @interface BankAPIField {
    int order() default -1;
    int length() default -1;
    String type() default "";
}

1.3.3 reflection matching annotation to realize dynamic interface parameter assembly

private static String remoteCall(AbstractAPI api) throws IOException {
    //Get the request address from the BankAPI annotation
    BankAPI bankAPI = api.getClass().getAnnotation(BankAPI.class);
    bankAPI.url();
    StringBuilder stringBuilder = new StringBuilder();
    Arrays.stream(api.getClass().getDeclaredFields()) //Get all fields
            .filter(field -> field.isAnnotationPresent(BankAPIField.class)) //Find fields marked with annotations
            .sorted(Comparator.comparingInt(a -> a.getAnnotation(BankAPIField.class).order())) //Sort the fields according to the order in the annotation
            .peek(field -> field.setAccessible(true)) //Set private fields that can be accessed
            .forEach(field -> {
                //Get comments
                BankAPIField bankAPIField = field.getAnnotation(BankAPIField.class);
                Object value = "";
                try {
                    //Get field value by reflection
                    value = field.get(api);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
                //Format the string with the correct padding based on the field type
                switch (bankAPIField.type()) {
                    case "S": {
                        stringBuilder.append(String.format("%-" + bankAPIField.length() + "s", value.toString()).replace(' ', '_'));
                        break;
                    }
                    case "N": {
                        stringBuilder.append(String.format("%" + bankAPIField.length() + "s", value.toString()).replace(' ', '0'));
                        break;
                    }
                    case "M": {
                        if (!(value instanceof BigDecimal))
                            throw new RuntimeException(String.format("{} of {} Must be BigDecimal", api, field));
                        stringBuilder.append(String.format("%0" + bankAPIField.length() + "d", ((BigDecimal) value).setScale(2, RoundingMode.DOWN).multiply(new BigDecimal("100")).longValue()));
                        break;
                    }
                    default:
                        break;
                }
            });
    //Signature logic
   stringBuilder.append(DigestUtils.md2Hex(stringBuilder.toString()));
    String param = stringBuilder.toString();
    long begin = System.currentTimeMillis();
    //Send request
    String result = Request.Post("http://localhost:45678/reflection" + bankAPI.url())
            .bodyString(param, ContentType.APPLICATION_JSON)
            .execute().returnContent().asString();
    log.info("Call bank API {} url:{} parameter:{} time consuming:{}ms", bankAPI.desc(), bankAPI.url(), param, System.currentTimeMillis() - begin);
    return result;
}

Dynamically obtain class information through reflection, and complete the assembly process at runtime. The advantage of this is that the development will be much more convenient and intuitive, and then the logic and details will be hidden and concentrated in one method to reduce repetition and the occurrence of bug s in maintenance.

1.3.4 application in code

@BankAPI(url = "/bank/createUser", desc = "Create user interface")
@Data
public class CreateUserAPI extends AbstractAPI {
    @BankAPIField(order = 1, type = "S", length = 10)
    private String name;
    @BankAPIField(order = 2, type = "S", length = 18)
    private String identity;
    @BankAPIField(order = 4, type = "S", length = 11) //Note that the order here needs to follow the order in the API table
    private String mobile;
    @BankAPIField(order = 3, type = "N", length = 5)
    private int age;
}



@BankAPI(url = "/bank/pay", desc = "Payment interface")
@Data
public class PayAPI extends AbstractAPI {
    @BankAPIField(order = 1, type = "N", length = 20)
    private long userId;
    @BankAPIField(order = 2, type = "M", length = 10)
    private BigDecimal amount;
}

Recent hot article recommendations:

1.1000 + Java interview questions and answers (2021 latest version)

2.Stop playing if/ else on the full screen. Try the strategy mode. It's really fragrant!!

3.what the fuck! What is the new syntax of xx ≠ null in Java?

4.Spring Boot 2.5 heavy release, dark mode is too explosive!

5.Java development manual (Songshan version) is the latest release. Download it quickly!

Feel good, don't forget to like + forward!

Tags: Java

Posted on Tue, 28 Sep 2021 19:43:27 -0400 by RadGH