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

巧用 Spring 自动注入实现策略模式升级版,第1张

巧用 Spring 自动注入实现策略模式升级版 一、前言 1.1 背景

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

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

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

不同的用户类型有不同的处理方式,接口为 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 的 type2BeanMap,然后使用 xml 的方式,将常量和对应 bean 注入进来。


	
		
			
			
		
	

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

@Service
public class DemoService {

    @Setter
    private Map type2BeanMap;

    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 List handlers;

    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 handlers 中。

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

@Service
public class DemoService {

    @Autowired
    private List 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();;
      }
    }
}

2.2 实现 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 的 Map 时 key 为对应枚举即可。

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


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 static  T 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 Map TYPE_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
            reflections
            0.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 Map TYPE_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 硬配置)。

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

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

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5481028.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-12
下一篇 2022-12-12

发表评论

登录后才能评论

评论列表(0条)

保存