干货分享:小技巧大用处之Bean管理类工厂多种实现方式

前言:最近几个月很忙,都没有时间写文章了,今天周末刚好忙完下班相对早点(20:00下班)就在家把之前想总结的知识点写出来,于是就有了这篇文章。虽无很高深的技术,但小技巧有大用处。

有时我们经常需要将实现了某个基类或某个接口的所有Bean进行分类管理,在需要用到的时候按需获取实现了某个基类或某个接口的Bean实例对象,那么我们就需要Bean管理类工厂(即:工厂模式),实现Bean管理类工厂我总结了目前已知且常用的实现方式,敬请各位看官欣赏,如是不足或更好建议欢迎评论区留言指正,谢谢!

为了便于演示,我先自定义如下接口:

/**
 * @author zuowenjun
 *  <pre>www.zuowenjun.cn</pre>
 */
public interface IDemo {
    String getValue();
    int doFor();
}

然后定义3个实现了上述接口的Service Bean类:(注意到Bean类上方还有@DemoFactoryNeedBean这个先不用管,后面的方式中会有用到)

@Service
public class DemoService1 implements IDemo {

    @Override
    public String getValue() {
        return "DemoService1.getValue by 梦在旅途 zuowj.cnblogs.com";
    }

    @Override
    public int doFor() {
        return 1;
    }
}

@DemoFactoryNeedBean
@Service
public class DemoService2 implements IDemo {

    @Override
    public String getValue() {
        return "DemoService2.getValue by 梦在旅途 zuowj.cnblogs.com";
    }

    @Override
    public int doFor() {
        return 2;
    }
}

@DemoFactoryNeedBean
@Service
public class DemoService3 implements IDemo {

    @Override
    public String getValue() {
        return "DemoService3.getValue by 梦在旅途 zuowj.cnblogs.com";
    }

    @Override
    public int doFor() {
        return 3;
    }
}

下面直接无废话列举各种实现方式

  1. 实现方式一:直接使用集合的依赖注入方式(利用spring注入时会判断是否为集合,若为集合则获取所有实现了该类的BEAN集合并进行注入)

    /**
     * @author zuowenjun
     *  <pre>www.zuowenjun.cn</pre>
     */
    @Service
    public class DemoFactory1 {
    
        @Autowired
        private List<IDemo> demos;
    
        public IDemo getOne(int index){
            return demos.stream().filter(d->d.doFor()==index).findFirst().orElseThrow(()->new IllegalArgumentException("not found demo bean"));
        }
    }
    

    单元测试【DemoFactory1】BEAN管理工厂用法及结果:

        @Autowired
        private DemoFactory1 demoFactory1;
        
            @Test
        public void testDemoFactory1(){
            for (int i=1;i<=3;i++){
                    IDemo demo = demoFactory1.getOne(i);
                    System.out.printf("testDemoFactory1--bean class: %s , getValue:%s,  doFor:%d %n", demo.getClass().getSimpleName(), demo.getValue(), demo.doFor());
            }
        }
    

    运行结果:

    testDemoFactory1--bean class: DemoService1 , getValue:DemoService1.getValue by 梦在旅途 zuowj.cnblogs.com, doFor:1
    testDemoFactory1--bean class: DemoService2 , getValue:DemoService2.getValue by 梦在旅途 zuowj.cnblogs.com, doFor:2
    testDemoFactory1--bean class: DemoService3 , getValue:DemoService3.getValue by 梦在旅途 zuowj.cnblogs.com, doFor:3

  2. 实现方式二:通过实现BeanPostProcessor接口,利用每个BEAN实例化后均会调用postProcessAfterInitialization方法的特点,直接在postProcessAfterInitialization方法中收集所需的BEAN实例并添加到集合中

    /**
     * @author zuowenjun
     *  <pre>www.zuowenjun.cn</pre>
     */
    @Service
    public class DemoFactory2 implements BeanPostProcessor {
    
        private List<IDemo> demos=new ArrayList<>();
    
        public IDemo getOne(int index){
            return demos.stream().filter(d->d.doFor()==index).findFirst().orElseThrow(()->new IllegalArgumentException("not found demo bean"));
        }
    
        @Override
        @Nullable
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            if (bean instanceof IDemo) {
                System.out.printf("postProcessAfterInitialization->bean class:%s",bean.getClass().getSimpleName());
                demos.add((IDemo) bean);
            }
            return bean;
        }
    }
    

    单元测试【DemoFactory2】BEAN管理工厂用法及结果:

        @Autowired
        private DemoFactory2 demoFactory2;
        
            @Test
        public void testDemoFactory2(){
            for (int i=1;i<=3;i++){
                IDemo demo= demoFactory2.getOne(i);
                System.out.printf("testDemoFactory2--bean class: %s , getValue:%s,  doFor:%d %n",demo.getClass().getSimpleName(),demo.getValue(),demo.doFor());
            }
        }
    

    运行结果:

    testDemoFactory2--bean class: DemoService1 , getValue:DemoService1.getValue by 梦在旅途 zuowj.cnblogs.com, doFor:1
    testDemoFactory2--bean class: DemoService2 , getValue:DemoService2.getValue by 梦在旅途 zuowj.cnblogs.com, doFor:2
    testDemoFactory2--bean class: DemoService3 , getValue:DemoService3.getValue by 梦在旅途 zuowj.cnblogs.com, doFor:3

  3. 实现方式三:通过实现ApplicationRunner、ApplicationContextAware接口,以便在setApplicationContext能获取到上下文实例对象并保存,然后在spring初始化完成执行run方法中使用上下文实例对象获取指定类型的BEAN实例集合。当然也可以不用实现ApplicationRunner接口,而是在工厂方法获取BEAN对象第一次时才用上下文实例对象获取指定类型的BEAN实例集合(即:初始化一次)如代码中的getOneForLazy方法所示。

    /**
     * @author zuowenjun
     *  <pre>www.zuowenjun.cn</pre>
     */
    @Service
    public class DemoFactory3 implements ApplicationRunner, ApplicationContextAware {
    
        private ApplicationContext context;
    
        @Autowired
        private List<IDemo> demos;
    
        public IDemo getOne(int index) {
            return demos.stream().filter(d -> d.doFor() == index).findFirst().orElseThrow(() -> new IllegalArgumentException("not found demo bean"));
        }
    
        public IDemo getOneForLazy(int index) {
            if (CollectionUtils.isEmpty(demos)){
                demos = new ArrayList<>(context.getBeansOfType(IDemo.class).values());
            }
            return demos.stream().filter(d -> d.doFor() == index).findFirst().orElseThrow(() -> new IllegalArgumentException("not found demo bean"));
        }
    
    
        @Override
        public void run(ApplicationArguments args) throws Exception {
            demos = new ArrayList<>(context.getBeansOfType(IDemo.class).values());
        }
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.context = applicationContext;
        }
    }
    

    单元测试【DemoFactory3】BEAN管理工厂用法及结果:

        @Autowired
        private DemoFactory3 demoFactory3;
    
        @Test
        public void testDemoFactory3(){
            for (int i=1;i<=3;i++){
                IDemo demo= demoFactory3.getOne(i);
                System.out.printf("testDemoFactory3--bean class: %s , getValue:%s,  doFor:%d %n",demo.getClass().getSimpleName(),demo.getValue(),demo.doFor());
            }
        }
    

    运行结果:

    testDemoFactory3--bean class: DemoService1 , getValue:DemoService1.getValue by 梦在旅途 zuowj.cnblogs.com, doFor:1
    testDemoFactory3--bean class: DemoService2 , getValue:DemoService2.getValue by 梦在旅途 zuowj.cnblogs.com, doFor:2
    testDemoFactory3--bean class: DemoService3 , getValue:DemoService3.getValue by 梦在旅途 zuowj.cnblogs.com, doFor:3

  4. 实现方式四:此为组合模式,先定义注入ApplicationContext上下文对象,然后定义一个枚举类,在枚举类中为每个枚举项都指明BEAN的实现类型,最后需要获取BEAN实例时,直接根据上下文对象获取指定类型的BEAN实例即可。

    
    /**
     * @author zuowenjun
     * <pre>www.zuowenjun.cn</pre>
     */
    @Service
    public class DemoFactory4 {
    
        private final ApplicationContext context;
    
        public DemoFactory4(ApplicationContext context) {
            this.context = context;
        }
    
        public IDemo getOne(DemoFactory4Enum factory4Enum) {
            return context.getBean(factory4Enum.getBeanClass());
        }
    
    
        public enum DemoFactory4Enum {
            Demo1(1, DemoService1.class),
            Demo2(2, DemoService2.class),
            Demo3(3, DemoService3.class),
            ;
    
            private final Class<? extends IDemo> beanClass;
            private final int index;
    
            DemoFactory4Enum(int i, Class<? extends IDemo> beanClass) {
                this.index = i;
                this.beanClass = beanClass;
            }
    
            public Class<? extends IDemo> getBeanClass() {
                return beanClass;
            }
    
            public int getIndex() {
                return index;
            }
    
            public static DemoFactory4Enum parse(int i){
                return Arrays.stream(values()).filter(d->d.getIndex()==i).findFirst().orElseThrow(()->new IllegalArgumentException("not found enum item!"));
            }
    
        }
    }
    

    单元测试【DemoFactory4】BEAN管理工厂用法及结果:(演示了2种方式,当然本质都是先确定枚举项,再获取BEAN对象)

        @Autowired
        private DemoFactory4 demoFactory4;
        
            @Test
        public void testDemoFactory4(){
    //        for (DemoFactory4.DemoFactory4Enum enumItem:DemoFactory4.DemoFactory4Enum.values()){
    //            IDemo demo= demoFactory4.getOne(enumItem);
    //            System.out.printf("testDemoFactory4--bean class: %s , getValue:%s,  doFor:%d %n",demo.getClass().getSimpleName(),demo.getValue(),demo.doFor());
    //        }
    
            for (int i=1;i<=3;i++){
                IDemo demo= demoFactory4.getOne(DemoFactory4.DemoFactory4Enum.parse(i));
                System.out.printf("testDemoFactory4--bean class: %s , getValue:%s,  doFor:%d %n",demo.getClass().getSimpleName(),demo.getValue(),demo.doFor());
            }
        }
    

    运行结果:

    testDemoFactory4--bean class: DemoService1 , getValue:DemoService1.getValue by 梦在旅途 zuowj.cnblogs.com, doFor:1
    testDemoFactory4--bean class: DemoService2 , getValue:DemoService2.getValue by 梦在旅途 zuowj.cnblogs.com, doFor:2
    testDemoFactory4--bean class: DemoService3 , getValue:DemoService3.getValue by 梦在旅途 zuowj.cnblogs.com, doFor:3

  5. 实现方式五:此为组合模式,与实现方式四有点类似,但又有不同,仍然是先定义注入ApplicationContext上下文对象,然后定义一个抽象枚举类(有一个抽象方法,如:getBean),在枚举类中为每个枚举项都实现这个抽象方法,在抽象方法中通过静态上下文对象字段来获取指定类型的BEAN实例,最后需要获取BEAN实例就比较简单了,只要得到枚举项,就可以直接获取到对应的BEAN实例。

    
    /**
     * @author zuowenjun
     *  <pre>www.zuowenjun.cn</pre>
     */
    @Service
    public class DemoFactory5 {
    
        private static ApplicationContext context;
    
        public DemoFactory5(ApplicationContext context) {
            DemoFactory5.context = context;
        }
    
        public enum DemosEnum {
            Demo1(1) {
                @Override
                public IDemo getBean() {
                    return context.getBean(DemoService1.class);
                }
            },
            Demo2(2) {
                @Override
                public IDemo getBean() {
                    return context.getBean(DemoService2.class);
                }
            },
            Demo3(3) {
                @Override
                public IDemo getBean() {
                    return context.getBean(DemoService3.class);
                }
            },
            ;
    
            private final int index;
    
            DemosEnum(int index) {
                this.index = index;
            }
    
            public int getIndex() {
                return index;
            }
    
            public abstract IDemo getBean();
    
    
            public static DemosEnum parse(int i){
                return Arrays.stream(values()).filter(d->d.getIndex()==i).findFirst().orElseThrow(()->new IllegalArgumentException("not found enum item!"));
            }
    
        }
    }
    

    单元测试【DemoFactory5】BEAN管理工厂用法及结果:(演示了2种方式,当然本质都是先确定枚举项,再获取BEAN对象)

        @Test
        public void testDemoFactory5(){
    //        for (DemoFactory5.DemosEnum demosEnum:DemoFactory5.DemosEnum.values()){
    //            IDemo demo= demosEnum.getBean();
    //            System.out.printf("testDemoFactory5--bean class: %s , getValue:%s,  doFor:%d %n",demo.getClass().getSimpleName(),demo.getValue(),demo.doFor());
    //        }
    
            for (int i=1;i<=3;i++){
                IDemo demo= DemoFactory5.DemosEnum.parse(i).getBean();
                System.out.printf("testDemoFactory5--bean class: %s , getValue:%s,  doFor:%d %n",demo.getClass().getSimpleName(),demo.getValue(),demo.doFor());
            }
        }
    

    运行结果:

    testDemoFactory5--bean class: DemoService1 , getValue:DemoService1.getValue by 梦在旅途 zuowj.cnblogs.com, doFor:1
    testDemoFactory5--bean class: DemoService2 , getValue:DemoService2.getValue by 梦在旅途 zuowj.cnblogs.com, doFor:2
    testDemoFactory5--bean class: DemoService3 , getValue:DemoService3.getValue by 梦在旅途 zuowj.cnblogs.com, doFor:3

  6. 实现方式六:其实本质还是实现方式一的灵活应用,通过自定义标注了@Qualifier注解的过滤注解类(如:@DemoFactoryNeedBean),然后在对应的BEAN类上加上该自定义的过滤注解,最后在工厂类的内部集合依赖注入字段上同样增加自定义的过滤注解,这样就可以在原有的基础上(BEAN的基类或接口)增加过滤必需包含指明了自定义过滤注解的BEAN实例集合。

    @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Qualifier
    public @interface DemoFactoryNeedBean {
    }
    
    /**
     * @author zuowenjun
     *  <pre>www.zuowenjun.cn</pre>
     */
    @Service
    public class DemoFactory1 {
    
        @DemoFactoryNeedBean
        @Autowired
        private List<IDemo> demos;
    
        public IDemo getOne(int index){
            return demos.stream().filter(d->d.doFor()==index).findFirst().orElseThrow(()->new IllegalArgumentException("not found demo bean"));
        }
    
        public boolean hasBean(int index){
            return demos.stream().anyMatch(d->d.doFor()==index);
        }
    }
    

    然后再看文章开头定义的3个BEAN类,其中:DemoService2、DemoService3是有加@DemoFactoryNeedBean注解的,最后再次单元测试【DemoFactory1】BEAN管理工厂用法及结果:

        @Test
        public void testDemoFactory1(){
            for (int i=1;i<=3;i++){
                if (demoFactory1.hasBean(i)) {
                    IDemo demo = demoFactory1.getOne(i);
                    System.out.printf("testDemoFactory1--bean class: %s , getValue:%s,  doFor:%d %n", demo.getClass().getSimpleName(), demo.getValue(), demo.doFor());
                }
            }
        }
    

    运行结果:(少了DemoService1 的BEAN)

    testDemoFactory1--bean class: DemoService2 , getValue:DemoService2.getValue by 梦在旅途 zuowj.cnblogs.com, doFor:2
    testDemoFactory1--bean class: DemoService3 , getValue:DemoService3.getValue by 梦在旅途 zuowj.cnblogs.com, doFor:3

    好了, 以上就是全部的实现方式了,至于哪种更好,我认为在不同的场景下选择合适的实现方式即可,没有所谓的最好,存在即有意义,最后期待我下次再写新的博文吧!~

posted @ 2022-07-30 11:46  梦在旅途  阅读(548)  评论(0编辑  收藏  举报