1. 程式人生 > 其它 >Java中的多型1

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("掃描部分失敗");
        }
    }

2.2 場景分析