Spring使用“注解”(自定义实现依赖注入)实现动态切换Bea

2023-05-11  本文已影响0人  小胖学编程

场景:在灰度上线某些功能时,我们一方面不希望侵入太多的业务逻辑,通过注解的方式可以动态的切换到不同的子类上。

原理:类似于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类中,还可以很多操作,例如打点、功能上报、根据配置动态切换不同子类等等。

上一篇 下一篇

猜你喜欢

热点阅读