一、前言

1.1 背景

在工作过程中,有时候需要根据不同的枚举(常量)执行不同的逻辑。

比如不同的用户类型,使用不同的优惠政策;不同的配置变化,走不同的处理逻辑等。

下面模拟一个根据不同用户类型,走不同业务逻辑的案例。

不同的用户类型有不同的处理方式,接口为 Handler ,示例代码如下:

public interface Handler {void someThing();
}

1.2 不同同学的做法

1.2.1 switch case 模式

小A同学,通过编写 switch 来判断当前类型,去调用对应的 Handler:

@Service
public class DemoService {@Autowiredprivate CommonHandler commonHandler;@Autowiredprivate 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<String,Handler>type2BeanMap,然后使用 xml 的方式,将常量和对应 bean 注入进来。

【注意】: 这里的 key 并不是 beanName ,而是某个业务枚举值,如用户类型

<bean id="demoService" class="com.demo.DemoService"><property name="type2BeanMap"><map><entry key="Vip" value-ref="vipHandler"></entry><entry key="Common" value-ref="commonHandler"></entry></map></property>
</bean>

这样拿到用户类型(vip 或 common)之后,就可以通过该 map 拿到对应的处理 bean 去执行,代码清爽了好多。

@Service
public class DemoService {@Setterprivate Map<String,Handler> type2BeanMap;public void test(){String type ="Vip";type2BeanMap.get(type).someThing();}
}

这样做会导致,新增一个策略虽然不用修改代码,但是仍然需要修改SomeService 的 xml 配置,本质上和 switch 差不多。

如新增一个 superVip 类型

<bean id="demoService" class="com.demo.DemoService"><property name="type2BeanMap"><map><entry key="Vip" value-ref="vipHandler"></entry><entry key="Common" value-ref="commonHandler"></entry><entry key="SuperVip" value-ref="superVipHandler"></entry></map></property>
</bean>

那么有没有更有好的解决办法呢?如果脱离 Spring 又该如何实现?

二、解法

2.1 自动注入

import org.springframework.stereotype.Component;@Component("Vip")
public class VipHandler implements Handler{@Overridepublic void someThing() {System.out.println("Vip用户,走这里的逻辑");}}

使用 @Autowired 注解 自动注入 beanName -> Bean 即可使用:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.HashMap;
import java.util.Map;@Service
public class DemoService {@Autowiredprivate   Map<String,Handler> beanMap = new HashMap<>();public void test()  {// 执行逻辑String type ="Vip";beanMap.get(type).someThing();}}

这样写法比较简洁,很好的解决了问题。

如果当前场景比较简单,建议可以采用这种办法。

值得探讨的是,使用业务类型当做 Bean 的 name 是否合适?(中性的描述)

(1)同一个业务枚举可能会有多种策略,如果每种策略都以业务类型作为 name 会出现很多重名不同类型的 Bean,是否会造成困惑?

(2)如果业务枚举名称有修改,Bean 是否能更快感知到影响(如 这里的 VIp 被改为了 Star 那么每个 策略的 name 都要进行修改)
(3)有时候不是简单的类型 -> bean 的映射,可能是每个 Class -> Bean 或者 Bean -> Bean 的映
此时这种方法就不太能完美的解决。

如果不希望业务类型影响到 Bean 的 name ,当业务枚举修改时,可强感知到影响的地方,可以使用下面几种解法。

2.2 PostConstruct

Handler 接口新增一个方法,用于区分不同的用户类型。

public interface Handler {String getType();void someThing();
}

每个子类都给出自己可以处理的类型,如:

import org.springframework.stereotype.Component;@Component
public class VipHandler implements Handler{@Overridepublic String getType() {return "Vip";}@Overridepublic void someThing() {System.out.println("Vip用户,走这里的逻辑");}
}

普通用户:

@Component
public class CommonHandler implements Handler{@Overridepublic String getType() {return "Common";}@Overridepublic 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 {@Autowiredprivate List<Handler> handlers;private Map<String, Handler> type2HandlerMap;@PostConstructpublic 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<VipHandler> handlers 中。

注意:如果同一个类型可以有多处理器,需定义为 private Map<String, List<Handler> type2HandlersMap 然后在 init 方法进行构造即可,示例代码:

@Service
public class DemoService {@Autowiredprivate List<Handler> handlers;private Map<String, List<Handler>> type2HandlersMap;@PostConstructpublic void init(){type2HandlersMap= handlers.stream().collect(Collectors.groupingBy(Handler::getType));}public void test(){String type ="Vip";for(Handler handler : type2HandlersMap.get(type)){handler.someThing();;}}
}

2.3 实现 InitializingBean 接口

然后 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 的 Mapkey 为对应枚举即可。

大家可以看到这里注入进来的 List<Handler> 其实就在构造type 到 bean 的映射 Map 时用到,其他时候用不到,是否可以消灭掉它呢?


2.4 实现 ApplicationContextAware 接口

我们可以实现 ApplicationContextAware 接口,在 setApplicationContext 时,通过 applicationContext.getBeansOfType(Handler.class) 拿到 Hander 类型的 bean map 后映射即可:

@Service
public class DemoService implements ApplicationContextAware {private Map<String, List<Handler>> type2HandlersMap;public void test(){String type ="Vip";for(Handler handler : type2HandlersMap.get(type)){handler.someThing();;}}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {Map<String, Handler> 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;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {ApplicationContextHolder.context = applicationContext;}public static <T> T getBean(String id, Class<T> tClass) {return context.getBean(id,tClass);}public static <T> Map<String,T> getBeansOfType(Class<T> tClass){return context.getBeansOfType(tClass);}}

编写 DemoService:


public class DemoService {private static final Map<String,Handler> TYPE_TO_BEAN_MAP = null;public void test(){// 构造 mapinitType2BeanMap();// 执行逻辑String type ="Vip";type2BeanMap.get(type).someThing();}private  synchronized void initType2BeanMap() {if (TYPE_TO_BEAN_MAP == null) {TYPE_TO_BEAN_MAP = new HashMap<>();Map<String, Handler> 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 基于反射

      <!-- https://mvnrepository.com/artifact/org.reflections/reflections --><dependency><groupId>org.reflections</groupId><artifactId>reflections</artifactId><version>0.10.2</version></dependency>

示例代码:

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 Map<String,Handler> TYPE_TO_BEAN_MAP = new HashMap<>();private  synchronized void initType2BeanMap()  {try{// 构造方法中传入扫描的目标包名Reflections reflections = new Reflections("com.demo.xxx");Set<Class<?>> 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()  {// 构造 mapinitType2BeanMap();// 执行逻辑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 基于 SPI

可以在外部 Jar 包内定义实现,使用 SPI 机制获取所有实现,执行操作。

3.2.3 其他 (待补充)

可以在构造子类型时自动将自身添加都某个容器中,这样使用时直接从容器拿到当前对象即可。

可能还有其他不错的方式,欢迎补充。

四、总结

本文简单介绍了通过 Spring 自动注入实现策略模式的方法,还提供了在非 Spring 环境下的实现方式。

避免新增一个新的 bean 时,多一处修改(硬编码 or 硬配置)。

对编写新的处理类的同学来说非常友好,符合开闭原则,符合封装复杂度的要求。

创作不易,你的支持和鼓励是我创造的最大动力,如果本文对你有帮助,欢迎点赞、收藏,也欢迎评论和我交流。

巧用 Spring 自动注入实现策略模式升级版相关推荐

  1. java spring 实现策略,Spring 环境下实现策略模式的示例

    背景 最近在忙一个需求,大致就是给满足特定条件的用户发营销邮件,但是用户的来源有很多方式:从 ES 查询的.从 csv 导入的.从 MongoDB 查询-.. 需求很简单,但是怎么写的优雅,方便后续扩 ...

  2. Spring自动注入

    谈及一个问题,无非牵扯到三点,是什么,怎么来的,怎么用的 Spring自动注入是什么 是指容器中的一个组件中需要用到另一个组件(例如聚合关系)时,依靠spring容器创建对象,而不是手动创建: Spr ...

  3. 基于spring自动注入及AOP的表单二次提交验证

    2019独角兽企业重金招聘Python工程师标准>>> 这几天在网上闲逛,看到了几个关于spring的token二次提交问题,受到不少启发,于是自己动手根据自己公司的项目框架结构,制 ...

  4. Spring自动注入原理

    我的博客 spring的属性注入属于spring bean的生命周期一部分,bean的生命周期首先记住两个概念: spring bean:最终存在spring容器当中的对象 对象:实例化出来的对象,但 ...

  5. 【Spring】Spring 自动注入(autowire)详解

    1.概述 转载:添加链接描述 2. 手动注入的不足 [Spring]Spring 依赖注入之手动注入 上篇文章中介绍了依赖注入中的手动注入,所谓手动注入是指在xml中采用硬编码的方式来配置注入的对象, ...

  6. Spring体系下单例策略模式,java策略模式最佳实践

    java springboot 策略模式最佳实践 本文将以最常用最好理解的一个业务场景--支付进行模拟 将不同支付场景业务拆分,后续增加.修改业务,逻辑解耦. 简单的逻辑流程如下: 业务类型为支付 i ...

  7. Spring自动注入(引用类型)

    spring可以根据某些规则给引用类型完成赋值,只对引用类型有效.有两种方式实现自动注入,下面简单的介绍以下 1-ByName 按名称自动注入:Java类中引用类型的属性名称和spring容器中bea ...

  8. java应用中spring自动注入_Spring自动注入的几种方式

    ---恢复内容开始--- @Service("accountEmailService") public class AccountEmailServiceImpl implemen ...

  9. Spring自动注入的几种方式

    ---恢复内容开始--- @Service("accountEmailService") public class AccountEmailServiceImpl implemen ...

最新文章

  1. 图灵奖得主Judea Pearl 智源大会演讲:从“大数据革命”到“因果革命”
  2. JavaScript原型-进阶者指南
  3. Metasploit设置VERBOSE参数技巧
  4. 网络推广外包——网络推广外包网站专员如何避免“网站过度优化”
  5. 论文阅读|How Does Batch Normalization Help Optimization
  6. boost::push_front相关的测试程序
  7. 长沙理工大学第十二届ACM大赛-重现赛C 安卓图案解锁 (模拟)
  8. Centos下安装配置WordPress与nginx教程
  9. matlab 多核并行编程
  10. GPhone、OPhone、UPhone、APhone、IPhone:满城尽带XPhone
  11. 转:使用 Tkprof 分析 ORACLE 跟踪文件
  12. android如何适配平板,适用于平板电脑、大屏设备和可折叠设备的自适应布局
  13. Android Studio、 补充知识以及主要组件
  14. 界面设计方法 (2) — 1. 界面与组件的概念
  15. 1.企业应用架构模式 --- 分层
  16. C#使用webclient下载图片返回403forbiden
  17. 电脑运行很慢怎么办_为什么电脑用久了,就算重新安装系统也会变得很慢?
  18. 软件单元测试及测试用例设计
  19. linux系统 安装主板驱动,I810 Graphics LINUX Driver的安装
  20. 快速使用ros小乌龟教程——ROS初体验

热门文章

  1. Lora和Zigbee无线通讯技术的对比(哪种技术更适合物联网连接)
  2. 吐血整理:小白学python编程基础(1)
  3. [SSD固态硬盘保养 4] 装完固态硬盘,笔记本(台式机)电脑要不要开省电模式(LPM)?
  4. 百度地图实现 区域高亮
  5. Linux11-权限的介绍 rwx详解 修改权限 修改文件目录所有者 修改文件目录所在组 一个实践和两个练习
  6. linux网卡rx errors,linux – 如何解决rx_missed_errors问题?
  7. Windows查询内存(内存条)信息
  8. Idea 2022.1.3版本显示内存使用情况的方法
  9. 卡西欧 casio prw-3100t-7 手表手动调时间
  10. 全国计算机等级考试三级数据库技术-知识点汇总