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

 2023-02-15
原文作者:南山的架构笔记 原文地址:https://juejin.cn/post/7013653299152814110

业务场景

假如现在有一个爬虫系统,需要查询航班信息,每次都需要根据参数拼接出请求的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));
    }

输出如下:

202301012122335931.png

总结:

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

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

END