我们都知道如果使用Spring来进行bean管理的时候。如果同一个接口的实现类存在两个,直接使用 @Autowired
注解来实现bean注入,会在启动的时候报异常。我们通常的做法是使用 @Resource
注解来执行bean的名称。不过通过 @Resource
注解类似于硬编码的方式,如果我们想修改接口的具体实现,必须修改代码。假设我们环境中针对所有接口,都有两套实现,一套在测试环境中使用,一个在生产环境中使用。那么当切换环境的时候,所有接口使用 @Resource
注入的地方都需要修改bean名称。
针对前面两套环境的情况,我们可以使用 @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
:smile: .假设系统都是使用一套,那么在使用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!
上面的服务定制通过各种绕路实现了服务定制,但是不能看出上面的实现非常不优雅,存在很多问题:
ServiceSelector
那么针对这种情况,有没有一个优雅的实现。既能满足前面所说的业务场景需求,又能够不初始化多余的类?当然是有的,其中的一套实现方案如下:
// 定制服务的注解声明,支持多个定制码 @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等来定制类的加载过程。
Linux公社的RSS地址 : https://www.linuxidc.com/rssFeed.aspx
本文永久更新链接地址: https://www.linuxidc.com/Linux/2019-03/157251.htm