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\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 场景分析


__EOF__

本文作者羊37
本文链接https://www.cnblogs.com/yang37/p/15404558.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   羊37  阅读(140)  评论(0编辑  收藏  举报
编辑推荐:
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示