場景:在灰度上線某些功能時,我們一方面不希望侵入太多的業(yè)務邏輯,通過注解的方式可以動態(tài)的切換到不同的子類上。
原理:類似于Spring依賴注入,在加載Bean過程中,找到具有特定注解Field,然后通過反射new出一個新的對象。
源碼實現(xiàn)
自定義注解:
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;
}
}
動態(tài)路由工程
@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() {
//通過參數(shù)來控制例如此處
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();
}
}
調(diào)用者
@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版本實現(xiàn)。在
Route類中,還可以很多操作,例如打點、功能上報、根據(jù)配置動態(tài)切換不同子類等等。