跟我学Spring之自定义bean容器提升代码可读性

栏目: IT技术 · 发布时间: 4年前

内容简介:开发中经常有这样的场景:根据某个类型标识走不同的业务逻辑,通常我们会使用if(type.equals(xxxxx)) 或者 switch语句来进行逻辑处理。这样做当然是没什么问题的。

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

根据某个类型标识走不同的业务逻辑,通常我们会使用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

版权声明:

原创不易,洗文可耻。除非注明,本博文章均为原创,转载请以链接形式标明本文地址。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

网红经济3.0 自媒体时代的掘金机会

网红经济3.0 自媒体时代的掘金机会

王先明、陈建英 / 当代世界出版社 / 2016-9-1 / 42

深入剖析网红经济的商业模式和整体产业链! 正在崛起的网红经济,打造出多元化的盈利模式,催生了众多新兴的产业投资机会,成为移动互联网时候的资本新风口一起来看看 《网红经济3.0 自媒体时代的掘金机会》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

URL 编码/解码
URL 编码/解码

URL 编码/解码