Using annotation + reflection to eliminate duplicate code, wonderful!

Posted by LordTyphon on Tue, 07 Dec 2021 17:18:02 +0100

  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

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:

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. In addition, the java series interview questions and answers are all sorted out. Wechat searches the Java technology stack and sends them in the background: the interview can be read online.

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.

The latest Java core technology tutorial and actual source code: https://github.com/javastacks/javastack

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;
}

I found it for you recently    ssm framework takeout ordering system