Spring服务定制

问题总述

​ 我们都知道如果使用Spring来进行bean管理的时候。如果同一个接口的实现类存在两个,直接使用@Autowired注解来实现bean注入,会在启动的时候报异常。我们通常的做法是使用@Resource注解来执行bean的名称。不过通过@Resource注解类似于硬编码的方式,如果我们想修改接口的具体实现,必须修改代码。假设我们环境中针对所有接口,都有两套实现,一套在测试环境中使用,一个在生产环境中使用。那么当切换环境的时候,所有接口使用@Resource注入的地方都需要修改bean名称。

使用@Profile注解

​ 针对前面两套环境的情况,我们可以使用@Profile注解来轻松解决。具体代码示例如下:

public interface HelloService {
	
	void saySomething(String msg);
}

@Profile("kind1")
@Service
public class HelloServiceImpl1 implements HelloService {
	public void saySomething(String msg) {
		System.out.println("HelloServiceImpl1 say:" + msg);
	}
}

@Profile("kind2")
@Service
public class HelloServiceImpl2 implements HelloService {
	public void saySomething(String msg) {
		System.out.println("HelloServiceImpl2 say:" + msg);
	}
}

@EnableAspectJAutoProxy
@Configurable
@ComponentScan(basePackages="com.rampage.spring")
@EnableScheduling
public class ApplicationConfig {
}

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=ApplicationConfig.class)
@ActiveProfiles(profiles={"kind1"})		// 启用kind1注入的bean
public class HelloServiceTest {
	
	@Autowired
	private HelloService helloService;
	
	@Test
	public void testServiceSelector() {
		helloService.saySomething("I Love U!");
	}
}


​ 最终输出的结果为:

HelloServiceImpl1 say:I Love U!

多服务共存定制

​ 考虑这样一种情况,假设HelloService是针对全国通用的服务,对于不同的省市使用不同的方言来saySomething 😄 .假设系统都是使用一套,那么在使用Spring进行bean管理的时候要么针对不同的省市只打包对应的目录下的HelloService实现,要么同前面一样使用@Profile注解来区分不同的实现类型,最后针对不同的省市修改@ActiveProfiles的具体值。这两种方法都需要针对不同的地区来进行相应的代码修改,然后再重新打包。考虑到全国几百个市,如果一次统一全部升级,估计光打包可能都要打包一天。。。

​ 更进一步的情况,东北三省大部分城市都是说普通话,那么实际上只要使用一个默认的实现类就行了。换句话将,现在想实现这样一种定制: 每个接口有一个默认实现,不同的城市有一个定制实现的类型码。如果根据定制类型码能够找到对应的接口实现,则使用该实现类。如果未找到,则使用默认的实现类。

​ 很显然,上面要实现的是在代码运行过程中动态判断最后接口的具体实现类。其中定制的类型码可以通过数据库或者配置文件的方式指定,在代码运行的过程中根据定制码去获取对应的服务实现。

​ 该方案的一种实现如下:

public interface ServiceSelector {
	/**
	 * 得到定制码
	 * @return
	 */
	String getCustCode();
}

public interface HelloService extends ServiceSelector {
	
	void saySomething(String msg);
}

public abstract class ServiceProvider <T, S extends T> implements BeanFactoryAware  {

	private ConfigurableListableBeanFactory beanFactory;
	
	private Map<String, T> serviceMap;
	
	public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
		if (beanFactory instanceof DefaultListableBeanFactory) {  
            this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;  
        }  
	}
	
	@SuppressWarnings({"unchecked", "restriction"})
	@PostConstruct
	public void init(){
		ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
		Type[] types = pt.getActualTypeArguments();
		
		Class<T> interfaceClazz = (Class<T>)types[0];
		// Class<S> defaultImplClazz = (Class<S>)types[1];  // defaultImplClazz为默认实现
		
		Map<String, T> serviceBeanMap = beanFactory.getBeansOfType(interfaceClazz);
		serviceMap = new HashMap<String , T>(serviceBeanMap.size());
		for (T processor : serviceBeanMap.values()) {
			if (!(processor instanceof ServiceSelector)) {
				// 如果实现类没有实现OptionalServiceSelector接口,直接报错
				throw new RuntimeException("可选服务必须实现ServiceSelector接口!");
			}
			
			// 如果已经存在相同定制码的服务也直接抛异常
			ServiceSelector selector = (ServiceSelector)processor;
			if (null != serviceMap.get(selector.getCustCode())) {
				throw new RuntimeException("已经存在定制码为【" + selector.getCustCode() + "】的服务");
			}
			
			// 加入Map中
			serviceMap.put(selector.getCustCode(), processor);
		}
	}
	
	public T getService() {
		// 从配置文件或者数据库获取当前省市的定制码
		String custCode = "kind11";
		if (null != serviceMap.get(custCode)) {
			return serviceMap.get(custCode);
		}
		
		// 如果未找到则使用默认实现
		return serviceMap.get("DEFAULT");
	}
}

@Service
public class DefaultHelloService implements HelloService {

	public String getCustCode() {
		return "DEFAULT";
	}

	public void saySomething(String msg) {
		System.out.println("DefaultHelloService say:" + msg);
	}

}

@Service
public class HelloServiceImpl1 implements HelloService {
	public void saySomething(String msg) {
		System.out.println("HelloServiceImpl1 say:" + msg);
	}

	public String getCustCode() {
		return "kind1";
	}
}

@Service
public class HelloServiceImpl2 implements HelloService {
	public void saySomething(String msg) {
		System.out.println("HelloServiceImpl2 say:" + msg);
	}

	public String getCustCode() {
		return "kind2";
	}
}

@Service
public class HelloServiceProvider extends ServiceProvider<HelloService, DefaultHelloService> {
	
}

@EnableAspectJAutoProxy
@Configurable
@ComponentScan(basePackages="com.rampage.spring")
@EnableScheduling
public class ApplicationConfig {
}

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=ApplicationConfig.class)
public class HelloServiceTest {
	
    // 注入接口服务提供器,而不是接口
	@Autowired
	private HelloServiceProvider helloServiceProvider;
	
	@Test
	public void testServiceSelector() {
		helloServiceProvider.getService().saySomething("I Love U!");
	}
}

​ 上例的最终输出为:

DefaultHelloService say:I Love U!

使用BFP来优雅定制服务实现

​ 上面的服务定制通过各种绕路实现了服务定制,但是不能看出上面的实现非常不优雅,存在很多问题:

  • 想实现一个接口的定制至少需要新增三个类。定制接口实现ServiceSelector接口,一个默认接口实现类,一个特定的定制服务实现类
  • 即使最终针对一个省市只使用一个实现类,在spring初始化的时候也会初始化定制接口的所有实现类,必须通过代码去判断针对特定的定制码是否只存在一个实现类

​ 那么针对这种情况,有没有一个优雅的实现。既能满足前面所说的业务场景需求,又能够不初始化多余的类?当然是有的,其中的一套实现方案如下:

// 定制服务的注解声明,支持多个定制码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface Customized {
	String[] custCodes() default {"DEFAULT"};
}


public interface HelloService {
	void saySomething(String msg);
}

@Component
public class CustomizedServiceBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered {

	private static String custCode;

	private static final Logger LOGGER = LoggerFactory.getLogger(CustomizedServiceBeanFactoryPostProcessor.class);

	static {
		Properties properties = new Properties();
		/*try {
			// 读取配置文件定制码
			properties.load(CustomizedServiceBeanFactoryPostProcessor.class.getClassLoader()
			        .getResource("app-config.properties").openStream());
		} catch (Exception e) {
			throw new RuntimeException("读取配置文件失败!", e);
		}*/
		// 这里假设取默认定制码
		custCode = properties.getProperty("custCode", "DEFAULT");
	}

	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		String[] beanDefNames = beanFactory.getBeanDefinitionNames();
		if (ArrayUtils.isEmpty(beanDefNames)) {
			return;
		}
		Class<?> beanClass = null;
		BeanDefinition beanDef = null;
		Customized customized = null;
		Set<Class<?>> foundCustomizedServices = new HashSet<Class<?>>();
		Map<String, Class<?>> waitDestroiedBeans = new HashMap<String, Class<?>>();
		String[] defaultCustCodes = {"DEFAULT"};
		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		for (String name : beanDefNames) {
			beanDef = beanFactory.getBeanDefinition(name);
			if (beanDef == null || StringUtils.isEmpty(beanDef.getBeanClassName())) {
				continue;
			}
			try {
				// 加载类得到其上的注解的定义
				beanClass = classLoader.loadClass(beanDef.getBeanClassName());
			} catch (ClassNotFoundException e) {
				// 发生了异常,这里直接跳过
			}
			if (beanClass == null) {
				continue;
			}
			customized = this.getCustomizedAnnotations(beanClass);

			// 非定制类直接跳过
			if (customized == null) {
				continue;
			}
			if (ArrayUtils.contains(customized.custCodes(), custCode)) {
				foundCustomizedServices.addAll(this.getCustomizedServices(beanClass));
				LOGGER.info("定制码【{}】下装载到定制服务实现类【{}】......", custCode, beanClass);
			} else {
				if (!ArrayUtils.isEquals(customized.custCodes(), defaultCustCodes)) {
					((DefaultListableBeanFactory) beanFactory).removeBeanDefinition(name);
					LOGGER.info("定制码【{}】下卸载定制服务实现类【{}】......", custCode, beanClass);
				} else {
					// 默认实现类暂时不知道是否需要销毁,先暂存
					waitDestroiedBeans.put(name, beanClass);
				}
			}
		}

		// 没有需要检测的是否需要销毁的默认实现类则直接返回
		if (MapUtils.isEmpty(waitDestroiedBeans)) {
			return;
		}

		// 看定制服务的默认实现类是否已经找到特定的实现类,如果找到了则需要销毁默认实现类
		for (Entry<String, Class<?>> entry : waitDestroiedBeans.entrySet()) {
			// 直接继承定制服务类实现
			if (foundCustomizedServices.contains(entry.getValue())) {
				((DefaultListableBeanFactory) beanFactory).removeBeanDefinition(entry.getKey());
				LOGGER.info("定制码【{}】下卸载默认定制服务实现类【{}】......", custCode, entry.getValue());
			} else {
				// 通过定制服务接口实现定制
				Set<Class<?>> defaultCustServices = getCustomizedServices(entry.getValue());
				for (Class<?> clazz : defaultCustServices) {
					if (foundCustomizedServices.contains(clazz)) {
						((DefaultListableBeanFactory) beanFactory).removeBeanDefinition(entry.getKey());
						LOGGER.info("定制码【{}】下卸载默认定制服务实现类【{}】......", custCode, entry.getValue());
						break;
					}
				}
			}
		}
	}

	/**
	 * 得到定制类的定制注解(一旦找到立即返回)
	 * 
	 * @param clazz
	 *            传入的定制类
	 * @return 得到定制类的定制注解
	 */
	private Customized getCustomizedAnnotations(Class<? extends Object> clazz) {
		// 递归遍历寻找定制注解
		Annotation[] annotations = null;
		Class<?>[] interfaces = null;
		while (clazz != Object.class) {
			// 先直接找该类上的注解
			annotations = clazz.getAnnotations();
			if (annotations != null && annotations.length > 0) {
				for (Annotation one : annotations) {
					if (one.annotationType() == Customized.class) {
						return (Customized) one;
					}
				}
			}

			// 再找该类实现的接口上的注解
			interfaces = clazz.getInterfaces();
			if (interfaces != null && interfaces.length > 0) {
				for (Class<?> intf : interfaces) {
					annotations = intf.getAnnotations();
					if (annotations != null && annotations.length > 0) {
						for (Annotation one : annotations) {
							if (one.annotationType() == Customized.class) {
								return (Customized) one;
							}
						}
					}
				}
			}

			// 未找到继续找父类上的注解
			clazz = clazz.getSuperclass();
		}

		return null;
	}

	/**
	 * 得到定制的服务类列表(即有Customized注解的类列表)
	 * 
	 * @param orginalClass
	 *            传入的原始类
	 * @return 定制服务类的列表
	 */
	private Set<Class<?>> getCustomizedServices(Class<?> orginalClass) {
		Class<?> class1 = orginalClass;
		Set<Class<?>> customizedInterfaces = new HashSet<Class<?>>();
		Class<?>[] interfaces = null;
		while (class1 != Object.class) {
			// 类也进行判断,这样能实现直接不通过接口的,而通过service继承实现定制服务
			if (class1.getAnnotation(Customized.class) != null) {
				customizedInterfaces.add(class1);
			}

			// 遍历接口,看接口是是定制服务接口
			interfaces = class1.getInterfaces();
			if (interfaces == null || interfaces.length == 0) {
				class1 = class1.getSuperclass();
				continue;
			}

			// 接口的实现只能有一个,所以一旦找到带有注解的实现类的接口,都作为定制服务接口
			for (Class<?> clazz : interfaces) {
				customizedInterfaces.add(clazz);
			}

			// 寻找父类定制服务
			class1 = class1.getSuperclass();
		}

		return customizedInterfaces;
	}

	public int getOrder() {
		return Integer.MIN_VALUE;
	}
}

@Customized		// 默认服务实现类
@Service
public class DefaultHelloService implements HelloService {

	public void saySomething(String msg) {
		System.out.println("DefaultHelloService say:" + msg);
	}

}

@Customized(custCodes="kind1")
@Service
public class HelloServiceImpl1 implements HelloService {
	public void saySomething(String msg) {
		System.out.println("HelloServiceImpl1 say:" + msg);
	}
}

@Customized(custCodes="kind2")
@Service
public class HelloServiceImpl2 implements HelloService {
	public void saySomething(String msg) {
		System.out.println("HelloServiceImpl2 say:" + msg);
	}

	public String getCustCode() {
		return "kind2";
	}
}

@EnableAspectJAutoProxy
@Configurable
@ComponentScan(basePackages="com.rampage.spring")
@EnableScheduling
public class ApplicationConfig {
}

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=ApplicationConfig.class)
public class HelloServiceTest {
	
	@Autowired
	private HelloService helloService;
	
	@Test
	public void testServiceSelector() {
		helloService.saySomething("I Love U!");
	}
	
}

​ 最终输出的结果为:

DefaultHelloService say:I Love U!

总结

​ 关于服务定制,其实前面所讲的方法都可以。只不过在特定的情况下各有各的优势,需要根据具体情况来选择合适的定制方案。而定制方案的选择,依赖于深入地理解Spring的类管理和加载过程,会用BPP、BFP等来定制类的加载过程。

posted @ 2018-03-20 18:43  Secondworld  阅读(755)  评论(1编辑  收藏  举报