美文网首页老男孩的成长之路
自定义Spring bean容器了解一下?实战及原理解读

自定义Spring bean容器了解一下?实战及原理解读

作者: 路人甲java | 来源:发表于2020-08-08 09:22 被阅读0次

    开发中经常有这样的场景:

    根据某个类型标识走不同的业务逻辑,通常我们会使用if(type.equals(xxxxx)) 或者 switch语句来进行逻辑处理。

    这样做当然是没什么问题的。

    当业务逻辑变得越来越复杂,类型标识增多之后,难免会出现if判断增加,或者switch case分支变多,这样的代码往往会过于冗长,代码重复性较大,或者说逼格不够高。

    本文介绍一种基于自定义Bean容器的开发方式,消除代码中的判断分支,提升代码可读性。

    我们通过一个demo来看如何实现这种编码方式。

    定义接口

    首先定义一个接口,主要有两个方法:

    public interface AbstractService<T> {
    
        /**
        * 返回serviceName
        * 作为bean选择标识
        * @return
        */
        String serviceName();
    
        /**
        * 具体的service方法
        * @param parm
        * @return
        */
        T execute(Object parm);
    }
    

    实现类需要实现serviceName,返回具体的类型,注意不同的bean实现类该返回值不能重复

    execute方法为业务方法,这里只是做个示范,实际开发中可以是任意的通用业务方法。

    实现接口

    接着编写实现类,实现接口

    ServiceAImpl标记类型为 ServiceA
    @Component
    public class ServiceAImpl implements AbstractService<DemoA> {
    
        @Override
        public String serviceName() {
            return "ServiceA";
        }
    
        @Override
        public DemoA execute(Object parm) {
            System.out.println("ServiceAImpl execute");
            return new DemoA().setName("DemoA");
        }
    }
    
    ServiceBImpl标记类型为 ServiceB
    @Component
    public class ServiceBImpl implements AbstractService<DemoB> {
    
        @Override
        public String serviceName() {
            return "ServiceB";
        }
    
        @Override
        public DemoB execute(Object parm) {
            System.out.println("ServiceBImpl execute");
            return new DemoB().setName("DemoB");
        }
    }
    

    编写自定义Bean上下文

    这里是重头戏,我们需要编写一个Bean上下文,并注入AbstractService集合。

    @Component
    public class ServiceContext {
    
        // IService容器,key=serviceName,velue=实例
        private static Map<String, AbstractService> SERVICE_CONTEXT;
    
        @Autowired
        List<AbstractService> services;
    
        @PostConstruct
        void init() {
            SERVICE_CONTEXT = new ConcurrentHashMap<> ();
            if (services == null) {
                return;
            }
            // 将IService所有的实现类注册到serviceContext
            for(AbstractService service : services) {
                SERVICE_CONTEXT.put(service.serviceName(), service);
            }
            System.out.println(JSON.toJSONString(SERVICE_CONTEXT));
        }
    
        /**
        * 根据serviceName获取实例
        * @param serviceName
        * @return
        */
        public AbstractService getServiceImpl(String serviceName) {
            return SERVICE_CONTEXT.get(serviceName);
        }
    }
    

    其实注释已经很清楚了,首先定义一个Map,key为String,代表我们上文中接口返回的serviceName。

    value为接口实现类bean实例。

    接着通过@Autowired注入AbstractService集合,这里是一个List。当Spring容器初始化完成,会将AbstractService的实现类都加载到List中。

    在@PostConstruct标记的初始化方法中,遍历 List<AbstractService>,并依次加载到我们初始化好的Map中。key=AbstractService.serviceName()的返回值,value为AbstractService实例。

    定义一个getServiceImpl(String serviceName)提供给业务使用,能够让我们通过具体的serviceName标识获取到Bean实例。这也是为何serviceName不能重复的原因。

    测试

    到此主要的逻辑编写就完成了,我们编写一个测试类测试一下具体如何使用。

    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = SpringApplication.run(DemoApplication.class, args);
        // 获取bean Context
        ServiceContext serviceContext = applicationContext.getBean("serviceContext", ServiceContext.class);
        // 根据serviceName获取具体的接口实现类
        AbstractService serviceA = serviceContext.getServiceImpl("ServiceA");
        AbstractService serviceB = serviceContext.getServiceImpl("ServiceB");
        // 调用service方法
        serviceA.execute(null);
        serviceB.execute(null);
    }
    

    这里从Spring上下文中获取到ServiceContext,并通过具体的serviceName获取到对应的Bean实例,并调用实例的execute方法。执行结果如下:

    ServiceAImpl execute
    ServiceBImpl execute
    

    可能这还不算很直观,我们模拟一个业务场景。

    业务需要先判断serviceName,再根据具体的值选择不同的执行逻辑。

    正常情况下,我们会这样编写业务代码:

    if ("ServiceA".equals(serviceName)) {
        serviceA.execute()
        return;
    }
    
    if ("ServiceB".equals(serviceName)) {
        serviceB.execute()
        return;
    }
    
    

    如果有一百个serviceName,那么这里就要有100个if分支,switch也同理。

    但是采取本文中的编码方式则只需要这么写:

    ...省略获取serviceContext过程,最简单的方法是通过@Autowired/@Resource注入...
    AbstractService service = serviceContext.getServiceImpl(serviceName);
    service.execute()
    这样我们就只需要在新增serviceName类型后,开发一个对应的实现类即可。

    如果是传统的编码方式,则除了新增service实现,还需要修改if/switch判断逻辑,不够灵活且容易出错。

    这里其实就是开放封闭原则的体现。传统的方式对修改和扩展都是开放的,而这种方式则是对扩展开放,对修改封闭的。尤其适用于复杂业务场景的开发。

    原理

    简单讲一下原理。

    Spring框架支持对集合类型进行依赖注入,对于集合类型依赖注入与查找起作用的ApplicationContext实现类为 ListableBeanFactory。

    我们看下源码是如何实现该特性的:

    具体的逻辑在 org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency 这个方法中

    打开该方法,重点关注下面这行

    Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeans, typeConverter);
    进入resolveDependency方法,看到下面这一行,跳入doResolveDependency方法
    
    result = doResolveDependency(descriptor, requestingBeanName, 
    autowiredBeanNames, typeConverter);
    重点关注下面的逻辑
    
    Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
    if (multipleBeans != null) {
     return multipleBeans;
    }
    

    此处的resolveMultipleBeans方法逻辑为,如果解析到了多个匹配条件的Bean,就直接返回解析结果。

    那具体的解析结果又是什么呢?我们进入resolveMultipleBeans方法

    private Object resolveMultipleBeans(DependencyDescriptor descriptor, @Nullable String beanName,
                @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) {
    
        Class<?> type = descriptor.getDependencyType();
        // 数组类型
        if (type.isArray()) {
            Class<?> componentType = type.getComponentType();
            ResolvableType resolvableType = descriptor.getResolvableType();
            Class<?> resolvedArrayType = resolvableType.resolve();
            if (resolvedArrayType != null && resolvedArrayType != type) {
                type = resolvedArrayType;
                componentType = resolvableType.getComponentType().resolve();
            }
            if (componentType == null) {
                return null;
            }
            Map<String, Object> matchingBeans = findAutowireCandidates(beanName, componentType,
                    new MultiElementDescriptor(descriptor));
            if (matchingBeans.isEmpty()) {
                return null;
            }
            if (autowiredBeanNames != null) {
                autowiredBeanNames.addAll(matchingBeans.keySet());
            }
            TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
            Object result = converter.convertIfNecessary(matchingBeans.values(), type);
            if (getDependencyComparator() != null && result instanceof Object[]) {
                Arrays.sort((Object[]) result, adaptDependencyComparator(matchingBeans));
            }
            return result;
        }
        // 集合类型,如List set
        else if (Collection.class.isAssignableFrom(type) && type.isInterface()) {
            Class<?> elementType = descriptor.getResolvableType().asCollection().resolveGeneric();
            if (elementType == null) {
                return null;
            }
            Map<String, Object> matchingBeans = findAutowireCandidates(beanName, elementType,
                    new MultiElementDescriptor(descriptor));
            if (matchingBeans.isEmpty()) {
                return null;
            }
            if (autowiredBeanNames != null) {
                autowiredBeanNames.addAll(matchingBeans.keySet());
            }
            TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
            Object result = converter.convertIfNecessary(matchingBeans.values(), type);
            if (getDependencyComparator() != null && result instanceof List) {
                ((List<?>) result).sort(adaptDependencyComparator(matchingBeans));
            }
            return result;
        }
        // Map类型
        else if (Map.class == type) {
            ResolvableType mapType = descriptor.getResolvableType().asMap();
            Class<?> keyType = mapType.resolveGeneric(0);
            if (String.class != keyType) {
                return null;
            }
            Class<?> valueType = mapType.resolveGeneric(1);
            if (valueType == null) {
                return null;
            }
            Map<String, Object> matchingBeans = findAutowireCandidates(beanName, valueType,
                    new MultiElementDescriptor(descriptor));
            if (matchingBeans.isEmpty()) {
                return null;
            }
            if (autowiredBeanNames != null) {
                autowiredBeanNames.addAll(matchingBeans.keySet());
            }
            return matchingBeans;
        }
        else {
            return null;
        }
    }
    

    这里便是@Autowired注入集合类型的核心。

    • 首先判断注入类型,如果是数组、Collection、Map等类型,则注入元素数据,即查找与元素类型相同的Bean,并注入到集合中。

    • 这里重点强调下Map类型,我们能够看出,Map的 key 为Bean的 name,value 为 与定义的元素类型相同的Bean。

    // Map的key
    Class<?> keyType = mapType.resolveGeneric(0);
    if (String.class != keyType) {
        return null;
    }
    // Map的value
    Class<?> valueType = mapType.resolveGeneric(1);
    if (valueType == null) {
        return null;
    }
    

    也就是说,如果业务上不依赖外部的type,那么我们可以直接注入一个Map集合,比如:

    @Autowired
    private Map<String, BeanInterface> map;
    

    这样就能够将接口BeanInterface的实现都注入到Map中,key的值为具体Bean的name,value为Bean实例。

    小结

    本文中,我们通过案例与源码,全方位呈现了Spring对集合类型的注入方式。总结一下:

    1. Spring在注入集合类的同时,会将集合泛型类的实例填入集合中,作为集合的初始值。

    2. 对于list、set填入的是注入类型Spring管理的实例,对于map,Spring会将service的名字作为key,对象作为value封装进入Map。

    3. 对于List类型,可以通过@Order指定加入List的顺序。只需要在实现类中加入@Order(value) 注解即可 ,值越小越先被初始化越先被放入List

    相关文章

      网友评论

        本文标题:自定义Spring bean容器了解一下?实战及原理解读

        本文链接:https://www.haomeiwen.com/subject/kfwcdktx.html