问题总述
我们都知道如果使用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等来定制类的加载过程。
黎明前最黑暗,成功前最绝望!