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