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.

image-20211013221116846

当你为PushServiceImpl2也加上@Service注解后,你会发现,IDEA并不能帮助你跳转了,同时,可恶的红色波浪线也出来了.

image-20211013221335202

image-20211013221406972

IDEA都这样提醒你了,你还去启动程序?启动后便开始给你报错.

image-20211013221721314

描述的很简洁:

  • 咱这是单例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-IntelliJIDEAWorksyang-spring	argetclassescnyang37serviceLService.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 场景分析

原文地址:https://www.cnblogs.com/yang37/p/15404558.html