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