文章目录
需求背景
举一个场景,比如说此时我们写了一个饭馆类,要根据用户的需求来给他做不同的面。
那此时要如何实现呢?
常规的写法可能就是把用户输入作为一个变量,然后对此变量做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);
}
}