一个可以自动根据接口匹配实现类的执行器(面条)

文章目录

需求背景

举一个场景,比如说此时我们写了一个饭馆类,要根据用户的需求来给他做不同的面。

那此时要如何实现呢?

常规的写法可能就是把用户输入作为一个变量,然后对此变量做if if if

有n种面,就有n个if

但这样的写法显然也太low了,而且不容易维护和阅读。

给出新的方案

这里给出一种新的方案,其实也是借鉴与设计模式中的策略模式,

底层得益于java多态的特性写得的。

我们先上源码,然后再解释:

源码

注解类:

所有的“面条”类,都要加上该注解来识别!

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Component
public @interface ZoeAnnotation {
    String value();
}

手动注入bean的工具

说明:

作为“面条”实现类的类,内部的bean使用@autowired会注入不成功,这个bug目前还没查明白原因。

但是使用手动注入的方式却是可以成功的,所以暂且使用手动注入的方式。

@Component
public class SpringUtil implements ApplicationContextAware {
    private static ApplicationContext applicationContext;

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (SpringUtil.applicationContext == null) {
            SpringUtil.applicationContext = applicationContext;
        }
    }

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    public static Object getBean(String name) {
        return getApplicationContext().getBean(name);
    }

    public static <T> T getBean(Class<T> clazz) {
        return getApplicationContext().getBean(clazz);
    }

    public static <T> T getBean(String name, Class<T> clazz) {
        return getApplicationContext().getBean(name, clazz);
    }
}

上下文:

什么类型的面条,要用context这个字段表示(枚举类)

@Data
public class ZoeContext {
    public String context;
}

控制器:

所有请求的入口,不多bb了

@Slf4j
@RestController
@RequestMapping("/zoe")
public class Controller {

    @Autowired
    TestExecutor testExecutor;

    @RequestMapping("/exe/{enum}")
    public String execute(@PathVariable("enum") String enu) {
        System.out.println("enu=" + enu);
        ZoeContext zoeContext = new ZoeContext();
        zoeContext.setContext(enu);
        return testExecutor.executeVoid(ExtInterface.class, zoeContext, ext -> ext.say());
    }

    @RequestMapping("/exe/")
    public String execute() {
        ZoeContext zoeContext = new ZoeContext();
        zoeContext.setContext(Enum.DEFAULT_SHUI_ZHU_NOODLES);
        return testExecutor.executeVoid(ExtInterface.class, zoeContext, ext -> ext.say());
    }
}

代表“面的种类”的枚举:

这个是作为上面context的内容,不多bb了。

如果不用枚举不方便管理,也不容易识别。

public class Enum {
    /**
     * 肥牛面
     */
    public static final String FEI_NIU_NOODLES = "肥牛面";

    /**
     * 肥肠面
     */
    public static final String FEI_CHANG_NOODLES = "肥肠面";

    /**
     * 鸡蛋西红柿面
     */
    public static final String JI_DAN_XI_HONG_SHI_NOODLES = "鸡蛋西红柿面";

    /**
     * 默认水煮面
     */
    public static final String DEFAULT_SHUI_ZHU_NOODLES = "默认水煮面";
}

实现“匹配不同面”的执行器接口:

实现“根据上下文(入参)匹配不同面”的核心接口

public interface AbstractExecutor {
    <T,R>String executeVoid(Class<T> clz, ZoeContext context, Function<T,R> function);
}

执行器接口的实现类:

实现“根据上下文(入参)匹配不同面”的核心实现类

核心思想:

根据context的内容,返回不同的实现类,然后再执行方法。

@Component
public class TestExecutor implements AbstractExecutor {
    private Logger logger = LoggerFactory.getLogger(this.getClass().getName());

    @Override
    public <T, R> String executeVoid(Class<T> clz, ZoeContext context, Function<T, R> function) {
        if (context == null) {
            return null;
        }
        if (clz == null) {
            return null;
        }
        if (function == null) {
            return null;
        }
        T t = matchExt(context, clz);
        return function.apply(t).toString();
    }

    /**
     * 匹配器
     */
    private <T> T matchExt(ZoeContext context, Class<T> clz) {
        String ct = context.getContext();
        Map<String, ExtInterface> extMap = TestFactory.extMap;
        if (ct == null) {
            logger.info("匹配失败,走默认");
            return (T) extMap.get(Enum.DEFAULT_SHUI_ZHU_NOODLES);
        }
        T t = (T) extMap.get(ct);
        if (t == null) {
            logger.info("匹配失败,走默认");
            return (T) extMap.get(Enum.DEFAULT_SHUI_ZHU_NOODLES);
        }
        return t;
    }

}

面类统一父接口:

public interface ExtInterface {
    String say();
}

“水煮面”类:

内部的bean使用@autowired会注入不成功,这个bug目前还没查明白原因。

但是使用手动注入的方式却是可以成功的,所以暂且使用手动注入的方式。

@ZoeAnnotation(Enum.DEFAULT_SHUI_ZHU_NOODLES)
@Component
public class ExtDefault implements ExtInterface {

    @Override
    public String say() {

        ZoeService zoeService = SpringUtil.getBean("zoeService", ZoeService.class);

        if (zoeService == null) {
            System.out.println("注入失败-----------");
        }
        return zoeService.sayDefault();
    }
}

“鸡蛋西红柿面”:

内部的bean使用@autowired会注入不成功,这个bug目前还没查明白原因。

但是使用手动注入的方式却是可以成功的,所以暂且使用手动注入的方式。

@ZoeAnnotation(Enum.JI_DAN_XI_HONG_SHI_NOODLES)
public class ExtOne implements ExtInterface {
    public String say() {
        ZoeService zoeService = SpringUtil.getBean("zoeService", ZoeService.class);

        if (zoeService == null) {
            System.out.println("注入失败-----------");
        }
        return zoeService.sayJiDanMian();
    }
}

“肥牛面”:

内部的bean使用@autowired会注入不成功,这个bug目前还没查明白原因。

但是使用手动注入的方式却是可以成功的,所以暂且使用手动注入的方式。

@ZoeAnnotation(Enum.FEI_NIU_NOODLES)
public class ExtTwo implements ExtInterface {
    @Override
    public String say() {
        ZoeService zoeService = SpringUtil.getBean("zoeService", ZoeService.class);

        if (zoeService == null) {
            System.out.println("注入失败-----------");
        }
        return zoeService.sayFeiNiuMian();
    }
}

“肥肠面”:

内部的bean使用@autowired会注入不成功,这个bug目前还没查明白原因。

但是使用手动注入的方式却是可以成功的,所以暂且使用手动注入的方式。

@ZoeAnnotation(Enum.FEI_CHANG_NOODLES)
public class ExtThree implements ExtInterface {
    @Override
    public String say() {
        ZoeService zoeService = SpringUtil.getBean("zoeService", ZoeService.class);

        if (zoeService == null) {
            System.out.println("注入失败-----------");
        }
        return zoeService.sayFeiChangMian();
    }
}

“面条类”工厂:

所有“面条类”,实际都在这里实例化后存入缓存。也就是说并不是调用才实例化,而是在整个项目启动的时候,所有“面条”实现类就已经实例化成对象,存入缓存等待被调用了。

这也是实现“匹配”的核心。

@Component
public class TestFactory {

    public static Map<String, ExtInterface> extMap;

    public TestFactory() {
        Object zoeService = SpringUtil.getBean("zoeService");

        System.out.println("TestFactory:" + zoeService.toString());
        if (zoeService != null) {
            System.out.println("工厂注入zoeservice成功");
        }
        //初始化
        extMap = new ConcurrentHashMap<>(4);
        ExtOne extOne = new ExtOne();
        ExtTwo extTwo = new ExtTwo();
        ExtThree extThree = new ExtThree();
        ExtDefault extDefault = new ExtDefault();
        //注册
        extMap.put(ExtOne.class.getAnnotation(ZoeAnnotation.class).value(), extOne);
        extMap.put(ExtTwo.class.getAnnotation(ZoeAnnotation.class).value(), extTwo);
        extMap.put(ExtThree.class.getAnnotation(ZoeAnnotation.class).value(), extThree);
        extMap.put(ExtDefault.class.getAnnotation(ZoeAnnotation.class).value(), extDefault);
    }
}

“面条”服务类:

目前实现类里并没有复杂内容,只是单纯的返回字符串。

@Service
public class ZoeService {
    public String sayDefault() {
        return "默认水煮面";
    }

    public String sayJiDanMian() {
        return "鸡蛋西红柿面";
    }

    public String sayFeiNiuMian() {
        return "肥牛面";
    }

    public String sayFeiChangMian() {
        return "肥肠面";
    }
}

主启动类:

@SpringBootApplication
@ComponentScan(basePackages = {"com.zoe2.*"})
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}
上一篇:xctf | pwn进阶


下一篇:SpringBoot2.6.x默认禁用循环依赖后的应对策略