zoukankan      html  css  js  c++  java
  • 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 场景分析

  • 相关阅读:
    写在“开张”时
    上班真累
    版本控制
    电脑主板报警声音的故障现象对照表
    js页面打开倒计时
    js中的词法分析
    修改mysql数据库密码
    上班的感受
    能力是被逼出来的!!有压力才有动力
    js中绑定事件的三种方式
  • 原文地址:https://www.cnblogs.com/yang37/p/15404558.html
Copyright © 2011-2022 走看看