跟我学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

版权声明:

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


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

查看所有标签

猜你喜欢:

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

The Cult of the Amateur

The Cult of the Amateur

Andrew Keen / Crown Business / 2007-6-5 / USD 22.95

Amateur hour has arrived, and the audience is running the show In a hard-hitting and provocative polemic, Silicon Valley insider and pundit Andrew Keen exposes the grave consequences of today’s......一起来看看 《The Cult of the Amateur》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具