問題總述
我們都知道如果使用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!");
}
}
總結
關于服務定制,其實前面所講的方法都可以。隻不過在特定的情況下各有各的優勢,需要根據具體情況來選擇合适的定制方案。而定制方案的選擇,依賴于深入地了解Spring的類管理和加載過程,會用BPP、BFP等來定制類的加載過程。
黎明前最黑暗,成功前最絕望!