场景:在灰度上线某些功能时,我们一方面不希望侵入太多的业务逻辑,通过注解的方式可以动态的切换到不同的子类上。
原理:类似于Spring依赖注入,在加载Bean过程中,找到具有特定注解Field,然后通过反射new出一个新的对象。
源码实现
自定义注解:
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface RouteAnno {
String master() default "";
String gray() default "";
}
Bean的后置处理器
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Component
public class RouteBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
ReflectionUtils.doWithFields(bean.getClass(), field -> {
//类似于Sprig的依赖注入,在加载bean的过程中,依赖注入一个代理类。
RouteAnno annotation = field.getAnnotation(RouteAnno.class);
if (annotation != null) {
Route.buildRoute(annotation.master(), annotation.gray());
try {
field.setAccessible(true);
field.set(bean, Route.buildRoute(annotation.master(), annotation.gray()));
} catch (Exception e) {
log.error("", e);
}
}
});
return bean;
}
}
动态路由工程
@Component
public class Route<T> {
private Map<String, String> cache = new ConcurrentHashMap<>();
public static final ThreadLocal<String> bsTL = new ThreadLocal<>();
public static <T> Route<T> buildRoute(String master, String gray) {
Route<T> route = new Route<>();
route.put("master", master);
route.put("gray", gray);
return route;
}
private void put(String name, String bean) {
cache.put(name, bean);
}
public T getBean() {
//通过参数来控制例如此处
if (bsTL.get().equals("gray")) {
return (T) SpringUtil.getBean(cache.get("gray"));
}
return (T) SpringUtil.getBean(cache.get("master"));
}
}
使用方式
@Service
@Slf4j
public class ARouteService {
@RouteAnno(master = "baseServiceImpl", gray = "baseServiceMockImpl")
private Route<BaseService> baseServiceRoute;
public String test() {
return baseServiceRoute.getBean().doCheck();
}
}
调用者
@Slf4j
@RestController
public class RouteController {
@Autowired
private ARouteService aRouteService;
@GetMapping(value = "/route/t1")
public ResponseObject<String> t1(@RequestParam("bs") String bs) {
bsTL.set(bs);
return ResponseObject.success(aRouteService.test());
}
}
请求:http://localhost:8081/route/t1?bs=master
注:上述是MVP版本实现。在
Route
类中,还可以很多操作,例如打点、功能上报、根据配置动态切换不同子类等等。