内容简介:开发中经常有这样的场景:根据某个类型标识走不同的业务逻辑,通常我们会使用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对集合类型的注入方式。总结一下:
-
Spring在注入集合类的同时,会将集合泛型类的实例填入集合中,作为集合的初始值。
-
对于list、set填入的是注入类型Spring管理的实例,对于map,Spring会将service的名字作为key,对象作为value封装进入Map。
-
对于List类型,可以通过@Order指定加入List的顺序。只需要在实现类中加入@Order(value) 注解即可 ,值越小越先被初始化越先被放入List
版权声明:
原创不易,洗文可耻。除非注明,本博文章均为原创,转载请以链接形式标明本文地址。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 跟我学 Spring Cloud (Finchley 版)(六):服务注册与服务发现之 Eureka 深入
- 如何提高golang的可读性
- 可读性代码:为什么、怎样以及什么时候
- 代码注释的艺术,再也不怕被说代码可读性差啦!
- [译] 作为一个 Python 爱好者,如何写出高可读性的代码?
- Java开发中存在这样的代码,反而影响整体整洁和可读性
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
网红经济3.0 自媒体时代的掘金机会
王先明、陈建英 / 当代世界出版社 / 2016-9-1 / 42
深入剖析网红经济的商业模式和整体产业链! 正在崛起的网红经济,打造出多元化的盈利模式,催生了众多新兴的产业投资机会,成为移动互联网时候的资本新风口一起来看看 《网红经济3.0 自媒体时代的掘金机会》 这本书的介绍吧!