基于ObjectFactory实现的Spring动态注入

背景

在最近以流程业务为主的开发过程中,由于功能上线后发生业务变更,遇到了多版本控制问题。业务方要求新功能上线后,尚未完结的流程需按照旧版的功能继续流转,新发起的流程按照新版的功能流转


Springboot版本约定

<dependencies>
   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      <version>2.2.13.RELEASE</version>
   </dependency>
</dependencies>

功能实现

最初的实现

当拿到这个需求后,我很快就动手了,这也就是加个if else的事儿,如果还要加新的版本,就在后面增加更多的 else if,那不要太简单:

/**
 * 流程第1环节提交:多版本
 */
public void submit1(SubmitData submitData) {
    String version = submitData.getVersion();
    if ("v1".equals(version)) {
       // v1版本的业务逻辑...
    } else {
       // v2版本的业务逻辑...
    }
}

/**
 * 流程第2环节提交:多版本
 */
public void submit2(SubmitData submitData) {
	String version = submitData.getVersion();
    if ("v1".equals(version)) {
       // v1版本的业务逻辑...
    } else {
       // v2版本的业务逻辑...
    }
}

写完提交收工。此时,开发同事小帅code review一看,你这代码写得太丑陋了,所有版本的逻辑写在一个方法里,后面再有需求变更只会越写越长,可读性差,不符合单一职责的原则。
我这么一听,他说的确实有点道理,不过这可难不倒我。此处调用相同的方法,但不同版本对应不同的业务逻辑,可以套用策略模式来进行改造。


升级版的实现

先定义一个顶级的接口类:FlowService

public interface FlowService {

	/**
 	 * 流程第1环节提交:多版本
 	 */
    void submit1(SubmitData submitData);

	/**
 	 * 流程第2环节提交:多版本
 	 */
	void submit2(SubmitData submitData);

}

再分别写出两个版本的实现类:FlowServiceImplV1FlowServiceImplV2

@Service
public class FlowServiceImplV1 implements FlowService {

	/**
 	 * 流程第1环节提交:多版本
 	 */
 	@Override
    public void submit1(SubmitData submitData) {
       // v1版本的业务逻辑...
    }

	/**
 	 * 流程第2环节提交:多版本
 	 */
 	@Override
	public void submit2(SubmitData submitData) {
	   // v1版本的业务逻辑...
	}

}

///

@Service
public class FlowServiceImplV2 implements FlowService {

	/**
 	 * 流程第1环节提交:多版本
 	 */
 	@Override
    public void submit1(SubmitData submitData) {
       // v2版本的业务逻辑...
    }

	/**
 	 * 流程第2环节提交:多版本
 	 */
 	@Override
	public void submit2(SubmitData submitData) {
	   // v2版本的业务逻辑...
	}

}

再写一个门面类做统一调用入口和不同版本的调用转发:FlowFacadeService

@Primary
@Service
public class FlowFacadeService implements FlowService {

	@Resource
    private ApplicationContext applicationContext;

	/**
 	 * 流程第1环节提交:多版本
 	 */
 	@Override
    public void submit1(SubmitData submitData) {
    	String version = submitData.getVersion();
        getServiceByVersion(version).submit1(submitData);
    }

	/**
 	 * 流程第2环节提交:多版本
 	 */
 	@Override
	public void submit2(SubmitData submitData) {
	    String version = submitData.getVersion();
        getServiceByVersion(version).submit2(submitData);
	}
    
    /**
 	 * 获取对应版本逻辑的service
 	 */
    private void FlowService getServiceByVersion(String version) {
    	if ("v1".equals(version)) {
       		return applicationContext.getBean(FlowServiceImplV1.class);
    	} else {
       		return applicationContext.getBean(FlowServiceImplV2.class);
    	}
    	// 如有新增的版本逻辑,可以在此处做扩展
    }

}

大功告成,写完之后,我看着这个漂亮的多版本逻辑分离代码,自信满满地提交了上去,关上电脑,心满意足地下班回家。第二天早上刚过来,看到开发同事小帅正好在code review这段代码,我便得意地凑了过去:

"怎么样,这段代码写得挺吊的吧?"

"嗯,比昨天的是好多了,但是..."

"但是什么?你别搞事,这还不行?"

"但是实现起来有点麻烦,假如以后我也要做类似需求的开发,还都要写这么多的话,那太烦了,而且每次迭代,还要记得去维护这个门面类"
 
"来来来,键盘给你,你来写!"

"你先别急,有没有一种可能,当我注入顶级的FlowService之后,在不写门面类的情况下,自动识别需要使用哪个版本的service?"
 
"你的这个想法确实很不错,但我觉得你在想屁吃"

"你可以参考Spring里HttpServletRequest的那种注入方式呀"

"你这么一说我好像有点思路了"

基于ObjectFactory实现的多版本逻辑控制的实现

要想实现同事小帅说的效果,我们有必要研究一下HttpServletRequest的实现方式。可能有不少同学在获取HttpServletRequest对象时,都使用过以下这种注入方式。同时你可能也已经注意到了,这里的request对象在每次发起http请求时获取到的都是不同的

@Resource
private HttpServletRequest request;

那么,上面的HttpServletRequest对象是通过什么方式来实现这种动态注入的呢?
我们借助IDEA打断点可以发现,这里注入的对象并不是HttpServletRequest
而是AutowireUtils$ObjectFactoryDelegatingInvocationHandler
它里面有一个成员变量WebApplicationContextUtils$RequestObjectFactory,如下所示:

HttpServletRequest实际注入的对象

我们在Spring源码中追踪一下RequestObjectFactory,可以发现它在WebApplicationContextUtils中进行了注册,此处用到的beanFactory.registerResolvableDependency()方法的作用是,使得需要注入ServletRequest类型bean的地方,都使用RequestObjectFactory的代理对象,如下:

beanFactory.registerResolvableDependency(ServletRequest.class, new RequestObjectFactory());
beanFactory.registerResolvableDependency(ServletResponse.class, new ResponseObjectFactory());
beanFactory.registerResolvableDependency(HttpSession.class, new SessionObjectFactory());
beanFactory.registerResolvableDependency(WebRequest.class, new WebRequestObjectFactory());

再来看RequestObjectFactory的源码,它实现了ObjectFactory类的getObject()方法,大致逻辑就是从当前的上下文中获取request对象并返回(此处currentRequestAttributes()方法的具体内容不作展开,感兴趣的同学可以研究一下源码),如下所示:

private static class RequestObjectFactory implements ObjectFactory<ServletRequest>, Serializable {

		@Override
		public ServletRequest getObject() {
			return currentRequestAttributes().getRequest();
		}

		@Override
		public String toString() {
			return "Current HttpServletRequest";
		}
}

这样一来,当我们注入HttpServletRequest对象的时候,实际注入的是另外一个代理类。只有在我们使用的时候,这个代理类才会调用RequestObjectFactorygetObject()方法来提供我们真正需要使用的HttpServletRequest对象。

AutowireUtils中的解析注入对象方法:

public static Object resolveAutowiringValue(Object autowiringValue, Class<?> requiredType) {
	if (autowiringValue instanceof ObjectFactory && !requiredType.isInstance(autowiringValue)) {
		ObjectFactory<?> factory = (ObjectFactory<?>) autowiringValue;
		if (autowiringValue instanceof Serializable && requiredType.isInterface()) {
            // 此处生成代理对象
			autowiringValue = Proxy.newProxyInstance(requiredType.getClassLoader(),
					new Class<?>[] {requiredType}, new ObjectFactoryDelegatingInvocationHandler(factory));
		}
		else {
				return factory.getObject();
		}
	}
	return autowiringValue;
}

调用实际对象方法的逻辑由ObjectFactoryDelegatingInvocationHandler实现

private static class ObjectFactoryDelegatingInvocationHandler implements InvocationHandler, Serializable {

	private final ObjectFactory<?> objectFactory;

	public ObjectFactoryDelegatingInvocationHandler(ObjectFactory<?> objectFactory) {
		this.objectFactory = objectFactory;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		String methodName = method.getName();
		if (methodName.equals("equals")) {
			// 对equals方法做特殊处理,只有同一个代理对象才会认为相等
			return (proxy == args[0]);
		}
		else if (methodName.equals("hashCode")) {
			// 对hashCode方法做特殊处理
			return System.identityHashCode(proxy);
		}
		else if (methodName.equals("toString")) {
            // 对toString方法做特殊处理,调用所代理的objectFactory的toString方法
			return this.objectFactory.toString();
		}
		try {
            // 其他方法调用objectFactory生产出来的对象的方法
			return method.invoke(this.objectFactory.getObject(), args);
		}
		catch (InvocationTargetException ex) {
			throw ex.getTargetException();
		}
	}
}

在了解了HttpServletRequest的动态注入实现方式之后,我们就可以照葫芦画瓢,来对原本的代码进行改造了。

首先,定义一个用于识别版本的基础接口类,也是我们需要代理的顶级接口:

public interface ProxyService {

	/**
 	 * 版本是否匹配
 	 */
    boolean isVersionMatch(String version);

}

同时对原本的几个FlowService进行扩展:

public interface FlowService extends ProxyService {

	// 原本的方法省略,该方法用作测试
    String test();

}
@Service
public class FlowServiceImplV1 implements FlowService {

    /**
 	 * 版本是否匹配
 	 */
    @Override
    public boolean isVersionMatch(String version) {
        return "v1".equals(version);
    }

    @Override
    public String test() {
        return "我是:flowService1";
    }

    // 原本的方法省略

}

///

@Service
public class FlowServiceImplV2 implements FlowService {

    /**
 	 * 版本是否匹配
 	 */
    @Override
    public boolean isVersionMatch(String version) {
        return "v2".equals(version);
    }

    @Override
    public String test() {
        return "我是:flowService2";
    }

    // 原本的方法省略

}

再创建一个管理版本号的上下文类VersionContextHolder,以方便版本信息的传递和获取:

public class VersionContextHolder {

    private final static ThreadLocal<String> THREAD_LOCAL = new NamedInheritableThreadLocal<>("versionContext");

    public static void setVersion(String version) {
        THREAD_LOCAL.set(version);
    }

    public static String currentVersion() {
        return THREAD_LOCAL.get();
    }

    public static void clear() {
        THREAD_LOCAL.remove();
    }

}

这里为了方便演示,我们约定通过参数version来获取流程的版本信息,并通过拦截器来设置和获取:

public class ProxyVersionInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String version = request.getParameter("version");
        VersionContextHolder.setVersion(version);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        VersionContextHolder.clear();
    }
}

然后是我们的核心类,代理类工厂ProxyServiceBeanFactory

注意,根据AutowireUtils.resolveAutowiringValue()方法里的解析要求,这里必须实现Serializable接口,否则无法生成代理对象:

public class ProxyServiceBeanFactory implements ObjectFactory<ProxyService>, Serializable {

    private final List<ProxyService> proxyServiceList;

    public ProxyServiceBeanFactory(Collection<ProxyService> proxyServiceList) {
        this.proxyServiceList = new ArrayList<>(proxyServiceList);
    }

    @Override
    public ProxyService getObject() throws BeansException {
        String version = VersionContextHolder.currentVersion();
        return proxyServiceList.stream()
                // 通过isVersionMatch方法找到匹配的service
                .filter(s -> s.isVersionMatch(version))
                .findFirst()
                .orElseThrow(IllegalArgumentException::new);
    }

}

接着再把上面这几个类在配置类中注册一下:

@Configuration
public class ProxyServiceConfig implements BeanFactoryPostProcessor, WebMvcConfigurer {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        // 获取所有需要代理的服务类
        Map<String, ProxyService> beans = beanFactory.getBeansOfType(ProxyService.class);
        // 注册代理对象
        beanFactory.registerResolvableDependency(ProxyService.class, new ProxyServiceBeanFactory(beans.values()));
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new ProxyVersionInterceptor());
    }
}

编写测试类TestController进行测试:

@RestController
public class TestController {

    @Resource
    private FlowService flowService;

    @Resource
    private HttpServletRequest request;

    @GetMapping("/test")
    public void test() {
        System.out.println(flowService.test());
    }

}

尝试启动项目进行测试,然后就会出现以下报错:

Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: 

No qualifying bean of type 'com.xxx.demos.service.FlowService' available: expected single matching bean but found 3: 

com.sun.proxy.$Proxy54@309028af,
FlowServiceImplV1,
FlowServiceImplV2

???这是为什么呢?

原来,在Spring进行依赖注入的时候,会去检查这个类型的bean在容器是否唯一,如果不是就会抛出这个异常。

if (matchingBeans.size() > 1) {
	autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
	if (autowiredBeanName == null) {
		if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {
            // 此处会进行bean唯一性的校验
			return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans);
		}
		else {
			// In case of an optional Collection/Map, silently ignore a non-unique case:
			// possibly it was meant to be an empty collection of multiple regular beans
			// (before 4.3 in particular when we didn't even look for collection beans).
			return null;
		}
	}
	instanceCandidate = matchingBeans.get(autowiredBeanName);
}

要想使用具体的实现类就要注入到容器中,可是一但注入,相同类型的bean就会存在多个,导致出现上面的异常,可是不注入又无法使用框架中用到的各种bean,还得做一大堆改造,令人浑身难受。那该怎么办呢?先不要急,我们冷静分析一下,其实我们只要能注入代理类就行,别的实现类不需要注入,那有没有办法屏蔽别的实现类呢?

有!Spring中提供了AutowireCandidateResolver进行扩展,它的isAutowireCandidate()方法可以判断当前bean定义是否符合注入的候选集,我们只要把ProxyService类型的实现类从候选集中剔除就行了,如下:

public class ProxyAutowireCandidateResolver implements AutowireCandidateResolver {

    @Override
    public boolean isAutowireCandidate(BeanDefinitionHolder bdHolder, DependencyDescriptor descriptor) {
        Class<?> dependencyType = descriptor.getDependencyType();
        // ProxyService类型的bean不放入候选集
        if (ProxyService.class.isAssignableFrom(dependencyType)) {
            return false;
        }
        return true;
    }

}

同时上面这个类也要注册一下,我们在原先的ProxyServiceConfig基础上改一下就行:

@Configuration
public class ProxyServiceConfig implements BeanFactoryPostProcessor, WebMvcConfigurer {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        if (beanFactory instanceof DefaultListableBeanFactory) {
            DefaultListableBeanFactory bf = (DefaultListableBeanFactory) beanFactory;
            // 注册到beanFactory中
            bf.setAutowireCandidateResolver(new ProxyAutowireCandidateResolver());
        }
        Map<String, ProxyService> beans = beanFactory.getBeansOfType(ProxyService.class);
        beanFactory.registerResolvableDependency(ProxyService.class, new ProxyServiceBeanFactory(beans.values()));
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new ProxyVersionInterceptor());
    }
}

再次启动,没有报错,切换version参数进行测试,测试通过:

我是:flowService1
我是:flowService2
我是:flowService1

小结

在本次开发过程中,我们经历了从硬编码到使用策略模式,再到研究动态注入的方式完成了多版本流程开发的改造。新的方式不但隔离了不同版本的业务逻辑,而且对原本代码的侵入性也比较低,改造的工作量也很小。当然,如果只是简单的业务调整,那也没必要进行大刀阔斧地改造,还是要结合使用场景具体问题具体分析。本文中简化了版本信息version的设置和获取方式,读者也可以结合自己的业务进行扩展改造。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值