使用注解 + 反射优化冗余代码【附源码】

166 阅读2分钟

业务场景

假如现在有一个爬虫系统,需要查询航班信息,每次都需要根据参数拼接出请求的url,为了避免反爬,我们需要模拟真实的用户请求,保持请求url后边跟的参数的顺序不变,最简单的做法就是一个参数一个参数的拼接出请求url,就像这样:

public String createUrl(FlightInfo flightInfo) {
    String url = "/flight/query?" + "org=" + flightInfo.getOrg()
            + "&dst=" + flightInfo.getDst()
            + "&flightDate=" + flightInfo.getFlightDate()
            + "&flightNo=" + flightInfo.getFlightNo()
            + "&passengerNum=" + flightInfo.getPassengerNum();
    return url;
}

@Data
public class FlightInfo {
    private String org;         //出发机场
    private String dst;         // 到达机场
    private String flightDate;  // 航班日期
    private String flightNo;    // 航班号
    private int passengerNum;   // 乘客数
}

这种方式,如果参数多的话,会非常麻烦,代码可读性也非常差。有没有什么优化方法呢?

使用注解 + 反射优化代码

先定义注解

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

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

作为请求参数的实体类,就变成这样了

@FlightQuery(url = "/flight/query", desc = "查询航班")
@Data
public class FlightInfo {
    @FlightQueryField(name = "org", order = 1)
    private String org;         //出发机场
    @FlightQueryField(name = "dst", order = 2)
    private String dst;         // 到达机场
    @FlightQueryField(name = "flightDate", order = 3)
    private String flightDate;  // 航班日期
    @FlightQueryField(name = "flightNo", order = 4)
    private String flightNo;    // 航班号
    @FlightQueryField(name = "passengerNum", order = 5)
    private int passengerNum;   // 乘客数
}

然后使用反射配合注解的方式,实现接口参数的动态组装,方法参数上你完全可以是其他实体类,比如登陆、下单,其思路都是一样的。

public static String createUrl(FlightInfo flightInfo) {
    FlightQuery flightQuery = flightInfo.getClass().getAnnotation(FlightQuery.class);
    StringBuilder stringBuilder = new StringBuilder(flightQuery.url());
    Arrays.stream(flightInfo.getClass().getDeclaredFields())
            .filter(x -> x.isAnnotationPresent(FlightQueryField.class))
            .sorted(Comparator.comparing(x -> x.getAnnotation(FlightQueryField.class).order()))
            .peek(x -> x.setAccessible(true))
            .forEach(field -> {
                FlightQueryField flightQueryField = field.getAnnotation(FlightQueryField.class);
                Object value = "";
                try {
                    value = field.get(flightInfo);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
                if (flightQueryField.order() == 1) {
                    stringBuilder.append("?").append(flightQueryField.name() + "=" + value).append("&");
                } else {
                    stringBuilder.append(flightQueryField.name() + "=" + value).append("&");
                }
            });
    return stringBuilder.toString().substring(0, stringBuilder.toString().length() - 1);
}

public static void main(String[] args) {
    FlightInfo flightInfo = new FlightInfo();
    flightInfo.setOrg("beijing");
    flightInfo.setDst("shanghai");
    flightInfo.setFlightDate("2021-10-01");
    flightInfo.setFlightNo("CA9988");
    flightInfo.setPassengerNum(1);
    System.out.println(createUrl(flightInfo));
}

输出如下:

图片

总结:

通过反射来动态获得实体对象上的注解信息,在运行时组装出请求url,这样代码看起来直观很多,而且灵活很多,如果需要url参数有变化,只需要在实体类上修改相关的注解即可,不用修改具体的拼装url的代码。

注解与反射的组合常常是优化代码的利器,可以使代码变得更简洁,可扩展性更好。本文的例子比较简单,只是起一个抛砖引玉的作用,重要的是介绍一种优化代码的思路方法。

END