在工作过程中,有时候需要根据不同的枚举(常量)执行不同的逻辑。
比如不同的用户类型,使用不同的优惠政策;不同的配置变化,走不同的处理逻辑等。
下面模拟一个根本不同用户类型,走不同业务逻辑的案例。
不同的用户类型有不同的处理方式,接口为 Handler ,示例代码如下:
public interface Handler { void someThing(); }1.2 不同同学的做法 1.2.1 switch case 模式
小A同学,通过编写 switch 来判断当前类型,去调用对应的 Handler:
@Service public class DemoService { @Autowired private CommonHandler commonHandler; @Autowired private VipHandler vipHandler; public void test(){ String type ="Vip"; switch (type){ case "Vip": vipHandler.someThing(); break; case "Common": commonHandler.someThing(); break; default: System.out.println("警告"); } } }
这样新增一个类型,需要写新的 case 语句,不太优雅。
1.2.2 xml 注入 type 到 bean 的映射
小B 同学选择在 Bean 中定义一个 Map
这样拿到用户类型(vip 或 common)之后,就可以通过该 map 拿到对应的处理 bean 去执行,代码清爽了好多。
@Service public class DemoService { @Setter private Maptype2BeanMap; public void test(){ String type ="Vip"; type2BeanMap.get(type).someThing(); } }
这样做会导致,新增一个策略虽然不用修改代码,但是仍然需要修改SomeService 的 xml 配置,本质上和 switch 差不多。
如新增一个 superVip 类型
那么有没有更有好的解决办法呢?如果脱离 Spring 又该如何实现?
二、解法 2.1 PostConstruct对 Handler 接口新增一个方法,用于区分不同的用户类型。
public interface Handler { String getType(); void someThing(); }
每个子类都给出自己可以处理的类型,如:
import org.springframework.stereotype.Component; @Component public class VipHandler implements Handler{ @Override public String getType() { return "Vip"; } @Override public void someThing() { System.out.println("Vip用户,走这里的逻辑"); } }
普通用户:
@Component public class CommonHandler implements Handler{ @Override public String getType() { return "Common"; } @Override public void someThing() { System.out.println("普通用户,走这里的逻辑"); } }
然后在使用的地方自动注入目标类型的 bean List 在初始化完成后构造类型到bean 的映射:
import javax.annotation.PostConstruct; import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; @Service public class DemoService { @Autowired private Listhandlers; private Map type2HandlerMap; @PostConstruct public void init(){ type2HandlerMap= handlers.stream().collect(Collectors.toMap(Handler::getType, Function.identity())); } public void test(){ String type ="Vip"; type2HandlerMap.get(type).someThing(); } }
此时,Spring 会自动将 Handler 类型的所有 bean 注入 List
注意:如果同一个类型可以有多处理器,需定义为 private Map
@Service public class DemoService { @Autowired private List2.2 实现 InitializingBean 接口handlers; private Map > type2HandlersMap; @PostConstruct public void init(){ type2HandlersMap= handlers.stream().collect(Collectors.groupingBy(Handler::getType)); } public void test(){ String type ="Vip"; for(Handler handler : type2HandlersMap.get(type)){ handler.someThing();; } } }
然后 init 方法将在依赖注入完成后构造类型到 bean 的映射。(也可以通过实现 InitializingBean 接口,在 afterPropertiesSet 方法中编写上述 init 部分逻辑。
)
在执行业务逻辑时,直接可以根据类型获取对应的 bean 执行即可。
测试类:
public class AnnotationConfigApplication { public static void main(String[] args) throws Exception { ApplicationContext ctx = new AnnotationConfigApplicationContext(QuickstartConfiguration.class); DemoService demoService = ctx.getBean(DemoService.class); demoService.test(); } }
运行结果:
Vip用户,走这里的逻辑
当然这里的 getType 的返回值也可以直接定义为枚举类型,构造类型到bean 的 Map 时 key 为对应枚举即可。
大家可以看到这里注入进来的 List
2.3 实现 ApplicationContextAware 接口
我们可以实现 ApplicationContextAware 接口,在 setApplicationContext 时,通过 applicationContext.getBeansOfType(Handler.class) 拿到 Hander 类型的 bean map 后映射即可:
@Service public class DemoService implements ApplicationContextAware { private Map> type2HandlersMap; public void test(){ String type ="Vip"; for(Handler handler : type2HandlersMap.get(type)){ handler.someThing();; } } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { Map beansOfType = applicationContext.getBeansOfType(Handler.class); beansOfType.forEach((k,v)->{ type2HandlersMap = new HashMap<>(); String type =v.getType(); type2HandlersMap.putIfAbsent(type,new ArrayList<>()); type2HandlersMap.get(type).add(v); }); } }
在实际开发中,可以结合根据实际情况灵活运用。
可能很多人思考到这里就很满足了,但是作为有追求的程序员,我们不可能止步于此。
三、More 3.1 如果 SomeService 不是 Spring Bean 又该如何解决?如果 Handler 是 Spring Bean 而 SomeService 不是 Spring 的 Bean,可以同样 @PostConstruct 使用 ApplicationHolder 的方式构造映射。
构造 ApplicationHolder :
import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; import java.util.Map; @Component public class ApplicationContextHolder implements ApplicationContextAware { private static ApplicationContext context; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { ApplicationContextHolder.context = applicationContext; } public staticT getBean(String id, Class tClass) { return context.getBean(id,tClass); } public static Map getBeansOfType(Class tClass){ return context.getBeansOfType(tClass); } }
编写 DemoService:
public class DemoService { private static final MapTYPE_TO_BEAN_MAP = null; public void test(){ // 构造 map initType2BeanMap(); // 执行逻辑 String type ="Vip"; type2BeanMap.get(type).someThing(); } private synchronized void initType2BeanMap() { if (TYPE_TO_BEAN_MAP == null) { TYPE_TO_BEAN_MAP = new HashMap<>(); Map beansOfType = ApplicationContextHolder.getBeansOfType(Handler.class); beansOfType.forEach((k,v)->{ TYPE_TO_BEAN_MAP.put(v.getType(),v); }); } } }
加上锁,避免首次构造多个 DemoService 时,多次执行 initType2BeanMap。
3.2 如果 Handler 也不是 Spring 的Bean 怎么办? 3.2.1 基于反射org.reflections reflections0.10.2
示例代码:
import org.reflections.Reflections; import java.util.HashMap; import java.util.Map; import java.util.Set; import static org.reflections.scanners.Scanners.SubTypes; public class DemoService { private static final MapTYPE_TO_BEAN_MAP = new HashMap<>(); private synchronized void initType2BeanMap() { try{ // 构造方法中传入扫描的目标包名 Reflections reflections = new Reflections("com.demo.xxx"); Set > subTypes = reflections.get(SubTypes.of(Handler.class).asClass()); for(Class> clazz : subTypes){ Handler handler = (Handler)clazz.newInstance(); TYPE_TO_BEAN_MAP.put(handler.getType(),handler); } }catch(Exception ignore){ // 实际编码时可忽略,也可以抛出 } } public void test() { // 构造 map initType2BeanMap(); // 执行逻辑 String type ="Vip"; TYPE_TO_BEAN_MAP.get(type).someThing(); } }
运行测试代码正常:
public class Demo { public static void main(String[] args) { DemoService demoService = new DemoService(); demoService.test(); } }
运行结果
Vip用户,走这里的逻辑
本质上是通过 Java 反射机制来扫描某个接口子类型来代替 Spring 通过 BeanFactory 扫描里面某种类型的 Bean 机制,大同小异。
虽然这里用到了反射,但是只执行一次,不会存在性能问题。
3.2.2 其他 (待补充)可以在构造子类型时自动将自身添加都某个容器中,这样使用时直接从容器拿到当前对象即可。
可能还有其他不错的方式,欢迎补充。
四、总结本文简单介绍了通过 Spring 自动注入实现策略模式的方法,还提供了在非 Spring 环境下的实现方式。
避免新增一个新的 bean 时,多一处修改(硬编码 or 硬配置)。
对编写新的处理类的同学来说非常友好,符合开闭原则,符合封装复杂度的要求。
创作不易,你的支持和鼓励是我创造的最大动力,如果本文对你有帮助,欢迎点赞、收藏,也欢迎评论和我交流。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)