Java中的多型1
0.背景
我們熟知,Java語言的三大基本特性為:繼承、封裝與多型.
簡單的來說,Java通過在執行時使用不同的實現,達成了多型這一特性.
舉個簡單的例子:
...
1.設計
...
2.例項分析
2.1 SpringBoot中的@Service註解
在一開始,我們準備設計一個向Admin使用者推送訊息的服務.
我們先設計出一個介面.
public interface PushService { /** * @Description: 推送訊息至Admin使用者 * @Author: Yiang37 * @Date: 2021/10/13 21:41:36 * @Version: 1.0 */ boolean pushToAdminUser(String msgInfo); }
接著,我們完成這個介面的一個實現,在實現類上加上註解@Service
@Service public class PushServiceImpl implements PushService { private static final Logger LOGGER = LoggerFactory.getLogger(PushServiceImpl.class); @Override public boolean pushToAdminUser(String msgInfo) { LOGGER.info("推動的訊息內容: {}", msgInfo); return false; } }
在Controller中,我們觸發這個服務.
@RestController public class UserController { @Autowired private PushService pushService; private static final Logger LOGGER = LoggerFactory.getLogger(UserController.class); /** * @Description: 使用者關注 * @Author: Yiang37 * @Date: 2021/10/10 16:52:36 * @Version: 1.0 */ @RequestMapping("/user/attention") public void userAttention(@RequestBody UserAttentionDTO userAttentionDTO) { LOGGER.info("使用者關注回撥介面: begin"); boolean b = pushService.pushToAdminUser("訊息內容"); LOGGER.info("使用者關注回撥介面: end, 處理結果: {}", b); }
注意,此時我們在controller中並不是使用PushServiceImpl這個具體實現來調起pushToAdminUser()方法,而是使用的他的父介面,即:
@Autowired
private PushService pushService;
// ...
boolean b = pushService.pushToAdminUser("訊息內容");
當然,你也可以這樣寫:
@Autowired
private PushServiceImpl pushServiceImpl;
// ...
pushServiceImpl.pushToAdminUser("訊息內容");
現在問題來了,為什麼建議使用pushService來呼叫方法?即業內的那句"面向介面程式設計".
"開閉原則":軟體應當對擴充套件開放,對修改關閉.
簡單的理解,就是不要去動以前的程式碼,即我寫好了pushToAdminUser()這個方法後,就不要再去亂改它的程式碼了.
現在可能看起來沒啥複雜的業務邏輯,但是在實際開發中,可能連搞清楚這個方法是幹嘛的都很困難.
實際開發中,你也會發現,讓你自己新寫一個類很舒服,因為都是自己寫的,你很自信.
但是讓你去修改別人的程式碼,你可能就會犯難了,哪怕是以前自己寫的程式碼,因為你不知道隨便改改可能會出現什麼bug.
秉著不傷害原來程式的原則,我們儘量去新加程式碼,不動原來的,這樣即使出問題,原來的功能也很好恢復.
所以,在這裡,我們將介面作為變數型別,傳入方法的具體實現中,在使用時該介面的具體實現類是誰,程式的功能就會隨之改變.
你可能會疑惑,我這裡沒有指明這個PushService的具體實現類啊,它執行的時候怎麼知道實現類是誰?
還記得你在PushServiceImpl加上了@Service這個註解嗎,這個就表明了執行時PushService使用PushServiceImpl這個實現.
@Service
public class PushServiceImpl implements PushService {
我們可以簡單推測一下,在Controller載入時,Spring掃描到@Autowired註解,嘗試去例項化PushService這個成員變數.
@Autowired
private PushService pushService;
接著,它發現這是個介面,這玩意好像不能例項化啊?我去找找它的實現類吧.
然後,它嘗試著去尋找它的實現類,記錄下使用了@Service的這個實現類,對它做了例項化,並賦值給PushService這個變數.
當我寫了多個實現時,都加上@Service註解,它又怎麼知道選哪個?
比如我們再新加一個PushServiceImpl2實現類.
public class PushServiceImpl2 implements PushService {
private static final Logger LOGGER = LoggerFactory.getLogger(PushServiceImpl2.class);
@Override
public boolean pushToAdminUser(PushVxMsgDTO pushVxMsgDTO) {
System.out.println("aaa");
return false;
}
}
在它沒有加上@Service註解前,Spring知道,執行時我選擇有@Service註解的那個,即上面的PushServiceImpl.
如果你使用的是IDEA,此時你點選前面的引導標記,也會自動跳轉到具體的實現PushServiceImpl.
當你為PushServiceImpl2也加上@Service註解後,你會發現,IDEA並不能幫助你跳轉了,同時,可惡的紅色波浪線也出來了.
IDEA都這樣提醒你了,你還去啟動程式?啟動後便開始給你報錯.
描述的很簡潔:
- 咱這是單例bean,你給了我兩個選擇啊?
- 用下@Primary,改成原型bean,又或者用用@Qualifier?
所以你可以嘗試:
-
在你想要的實現類前加上@Primary
// 選擇PushServiceImpl2為具體的實現類 @Service @Primary public class PushServiceImpl2 implements PushService {
-
在變數PushService上加上@Qualifier
// 選擇選擇PushServiceImpl為具體的實現類,注意例項化變數名是小寫開頭的. @Autowired @Qualifier("pushServiceImpl") private PushService pushService;
如果你閒的無聊,用@Primary和@Qualifier分別註解兩個不同的,你會發現@Qualifier處的優先順序更高.
你也可以猜想下單例bean與原型bean的實現,下面是曾經仿照的一個簡單版demo.
/**
* @Description: 執行掃描,並將所有需要建立的bean資訊放入beanDefinitionMap中
* 1.獲取配置類上面的 @ComponentScan 註解,解析其中的掃描區域.
* 2.根據掃描區域,獲取其中包含的Class檔案,並載入.
* 3.如果載入的Class上有 @Component 註解,意味著需要建立該類的bean.
* (在本方法中並未建立bean物件,只是給BeanDefinition物件填入了值,下一步的建立物件方法中解析該值後做處理).
* 3.1 建立一個BeanDefinition物件,先放入Class屬性.
* 3.2 解析Class,判斷是否包含 @Scope 註解
* 3.2.1 有,則BeanDefinition物件的Scope屬性放入對應String值.
* 3.2.2 沒有,則BeanDefinition物件的Scope屬性放入預設的"singleton".
* @Author: Yiang37
* @Date: 2021/04/28 23:34:32
* @Version: 1.0
*/
private void scan(Class configClass) {
try {
// 獲取配置類上宣告的 "掃描註解"
YangComponentScan yangComponentScanAnnotation = (YangComponentScan) configClass.getDeclaredAnnotation(YangComponentScan.class);
// 獲取到掃描區域,eg: cn.yang37.service
String scanPackage = yangComponentScanAnnotation.value();
// 獲取ApplicationContext所在的類載入器
ClassLoader applicationClassLoader = YangApplicationContext.class.getClassLoader();
// 基於類載入器獲取絕對路徑,需要將.轉為/.
String scanPath = scanPackage.replace(".", "/");
URL resource = applicationClassLoader.getResource(scanPath);
//將URL轉為File路徑後 載入資料夾
File file = new File(resource.getFile());
if (file.isDirectory()) {
//路徑下所有的檔案
File[] files = file.listFiles();
//遍歷其中的每個檔案
for (File oneClassFile : files) {
String classFileAbsolutePath = oneClassFile.getAbsolutePath();
//C:\Projects\0-IntelliJIDEAWorks\yang-spring\target\classes\cn\yang37\service\LService.class
if (classFileAbsolutePath.endsWith(".class")) {
//從上方路徑中截取出cn.yang37.service.YangService
// 擷取的開始字元是 cn
String beginSubStr = scanPackage.split("\\.")[0];
// 結束字元是.class 然後\轉為.
String resStr = classFileAbsolutePath.substring(classFileAbsolutePath.indexOf(beginSubStr), classFileAbsolutePath.indexOf(".class")).replace("\\", ".");
//載入當前類: cn.yang37.service.LService
Class<?> oneClass = applicationClassLoader.loadClass(resStr);
//這個類上面有YangComponent註解 則需要載入bean
if (oneClass.isAnnotationPresent(YangComponent.class)) {
// oneClass是否實現BeanPostProcessor
if (YangBeanPostProcessor.class.isAssignableFrom(oneClass)) {
YangBeanPostProcessor yangBeanPostProcessor = (YangBeanPostProcessor) oneClass.getDeclaredConstructor().newInstance();
beanPostProcessorList.add(yangBeanPostProcessor);
}
//單例bean還是原型bean
//獲得類上面的註解
YangComponent yangComponentAnnotation = oneClass.getDeclaredAnnotation(YangComponent.class);
//類上面的beanName
String beanName = yangComponentAnnotation.value();
//建立YangBeanDefinition
YangBeanDefinition yangBeanDefinition = new YangBeanDefinition();
yangBeanDefinition.setClazz(oneClass);
//如果包含YangScope註解 填入對應的值
if (oneClass.isAnnotationPresent(YangScope.class)) {
YangScope yangScopeAnnotation = oneClass.getAnnotation(YangScope.class);
//設定scope註解的值
yangBeanDefinition.setScope(yangScopeAnnotation.value());
} else {
//沒有值,預設為單例
yangBeanDefinition.setScope("singleton");
}
// 放入bean資訊
beanDefinitionMap.put(beanName, yangBeanDefinition);
}
}
}
}
} catch (Exception e) {
System.out.println("掃描部分失敗");
}
}