Springboot使用自定义注解进行方法路由
使用背景
在一次对接第三方公共接口时遇到一个问题,第三方会将所有的请求打到同一个URL
地址,而我们则需要根据第三方的method
字段自行对请求进行路由。
考虑到会有多个路由方法,为了便于开发及后续的维护,所以采用了自定义注解的方式来实现。
新建自定义注解
@ApiService
创建@ApiService
注解,使用在类上,用于标注该类为自定义路由类,该注解直接使用Spring
的@Service
注解即可。
1 2 3 4 5 6 7 8
| @Inherited @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Service public @interface ApiService {
}
|
@ApiRouter
创建@ApiRouter
注解,使用在方法上,用于标注该方法为路由方法,该注解需要一个必填值为路由方法地址。
1 2 3 4 5 6 7 8 9 10 11
| @Inherited @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ApiRouter {
String value(); }
|
新建 API 路由工具类
新建 API 路由工具类,用于初始化、保存路由方法映射,以及执行路由方法等操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| public final class ApiRouterUtil {
private static final Map<String, Target> ROUTE_MAPPING = new HashMap<>();
private ApiRouterUtil() {}
@Data public static class Target {
private Class<?> targetClass;
private Method targetMethod;
private Target(Class<?> targetClass, Method targetMethod) { this.targetClass = targetClass; this.targetMethod = targetMethod; }
public static Target of(Class<?> targetClass, Method targetMethod) { return new Target(targetClass, targetMethod); } }
public static void put(String key, Target value) throws IllegalStateException { Target target = ROUTE_MAPPING.get(key); if (target != null) { throw new IllegalStateException(target.getTargetClass().getName() + "#" + target.getTargetMethod().getName() + ": There is already '" + key + "' bean method"); } ROUTE_MAPPING.put(key, value); }
public static String invoke(String method, String params) { Target target = ROUTE_MAPPING.get(method); if (target == null) { throw new ServiceException("未找到路由地址'" + method + "'对应的实现方法"); }
try { Method targetMethod = target.getTargetMethod(); Object[] args = new Object[targetMethod.getParameterCount()]; args[0] = params; return (String) targetMethod.invoke(SpringUtil.getBean(target.getTargetClass()), args); } catch (InvocationTargetException | IllegalAccessException e) { throw new ServiceException(e.getCause().getMessage()); } }
}
|
使用Springboot钩子函数初始化方法映射
众所周知,实现Spring
的ApplicationRunner
或CommandLineRunner
接口可以在项目启动成功后进行初始化操作,这里我使用了前者进行初始化操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| @Slf4j @Component public class ApiApplicationRunner implements ApplicationRunner { @Override public void run(ApplicationArguments args) { log.info("API Application init start..."); initApiRouter(); log.info("API Application init finish"); }
private void initApiRouter() { log.info("API Router init => 开始初始化..."); Class<ApiApplication> applicationClass = ApiApplication.class; ComponentScan componentScan = applicationClass.getAnnotation(ComponentScan.class); SpringBootApplication springBootApplication = applicationClass.getAnnotation(SpringBootApplication.class); Set<String> packages = new HashSet<>(ListUtil.of(componentScan.basePackages())); packages.addAll(ListUtil.of(springBootApplication.scanBasePackages())); if (packages.isEmpty()) { packages.add(ClassUtil.getPackage(applicationClass)); } Set<Class<?>> apiServiceClassSet = new HashSet<>(); packages.forEach(packageName -> apiServiceClassSet.addAll(ClassUtil.scanPackageByAnnotation(packageName, ApiService.class))); apiServiceClassSet.forEach(clazz -> { Method[] methods = ReflectUtil.getMethods(clazz, method -> method.getAnnotation(ApiRouter.class) != null); if (methods != null && methods.length > 0) { for (Method method : methods) { String router = method.getAnnotation(ApiRouter.class).value(); ApiRouterUtil.Target target = ApiRouterUtil.Target.of(clazz, method); ApiRouterUtil.put(router, target); log.info("API Router init => route: '{}', class: '{}', method: '{}'", router, clazz.getSimpleName(), method.getName()); } } }); log.info("API Router init => 初始化完成"); } }
|
至此就算完成了所有的准备步骤,接下来就可以尝试使用。
测试使用
定义路由方法
1 2 3 4 5 6 7 8 9 10 11 12
| @Slf4j @ApiService public class TestApiService() { @ApiRouter("user.info.change.notify") public String userInfoChangeNotify(String params) { String res = ""; UserInfoChangeRequest request = JSON.parseObject(params, UserInfoChangeRequest.class); return res; } }
|
定义接口接收第三方请求
1 2 3 4 5 6 7 8 9 10
| @RequestMapping("/test") @RestController public class TestController extends BaseController {
@PostMapping(value = "/api") public R<String> api(ApiRequest request) { String res = ApiRouterUtil.invoke(request.getMethod(), request.getParams()); return R.ok(res); } }
|
至此就算全部完成了,
当请求的method
字段为user.info.change.notify
时,
/api
接口会自动路由到我们定义的路由方法,
如果还需要添加更多的路由方法,只需在方法上标注注解@ApiRouter("xxx")
即可。