SpringBoot启动时,若有依赖的jar包也是个Springboot服务,会启动依赖的jar包吗?
假设现在有两个服务A和B, 他们使用的数据库不同。正常情况下,服务A想调用服务B的方法,就需要服务B暴露出一个接口,然后A服务通过远程调用的方式调用以下是A服务的接口示例
@RestController
public class AServer {
@Autowired
LanguageMapper languageMapper;
@Autowired
Student student;
@PostMapping(value = "/language1")
public List a() {
List<CaffeineLanguageInfoDTO> list = languageMapper.selectList(new QueryWrapper<CaffeineLanguageInfoDTO>().eq("id", "301"));
System.out.println(list);
return list;
}
@PostMapping(value = "/language2")
public String test2() {
return student.getName() + "is a good boy";
}
}
@Configuration
public class GenerateStudent {
@Value("${123}")
String age;
@Bean
public Student ges() {
Student lm = new Student("李明");
lm.age = age;
return lm;
}
}
以下是B服务的接口示例
@RestController
public class BServer {
@Autowired
AServer aServer;
@PostMapping(value = "/language1")
public List test() {
List<CaffeineLanguageInfoDTO> list = HttpsUtils.httppost("http://localhost:8011/unique/language/language1");
System.out.println(list);
return list;
}
}
调用方式如下图所有但这个方式需要B服务先启动,并且查询到的数据是数据库B的数据。
那么如果将服务B的坐标添加到服务A的pom文件里,然后在A服务中使用Autowired注入B服务的接口B的bean对象,然后用b.method()调用,修改B服务接口的代码如下
@RestController
public class BServer {
@Autowired
AServer aServer;
@PostMapping(value = "/language1")
public List test() {
List<CaffeineLanguageInfoDTO> list = aServer.a();
System.out.println(list);
return list;
}
}
有以下疑问:
1. 这个时候不启动B,只启动A,能正常启动A吗答案:有以下特殊情况,若无的话即可正常启动A(1)bean冲突错误。如果A服务和B服务定义了两个相同的类名,并且都交给了Spring管理,那在启动时Spring会生成bean放到容器里,由于Spring创建bean的规则默认是类名首字母小写,所以它会识别到两个相同的bean,导致无法生成而报conflict bean错误。解决方法:生成bean时指定bean名称,尽量避开生成相同的bean(2)无法找到变量 - Could not resolve placeholder; 在B服务中,生成了一个自定义的bean对象Student, 该bean对象需要从配置文件里实时读取配置赋值。如果 A服务中没有这个配置名称,则会报Could not resolve placeholder。(3)ambiguous mapping. 如果A服务和B服务在对外暴露接口时,使用了相同的mapping路径,则会报这个错。
2. 这个时候B也会启动吗?答案:不会,但也有一种特殊情况,即在A里注入了B服务的启动类对象,然后显式调用了其main方法。
3. 若启动成功了,这个A时候调用了B的方法,查的是A服务配置的数据库信息,还是B服务配置的数据库信息。答案:由第一点可知,首先确保A服务里有B服务所需要的所有配置信息,如数据库配置,否则无法启动。启动后,通过依赖方式注入对象调用,实际上调用的是A服务使用的数据库。如果写的SQL的表名在数据库中不存在,就会报SQL Exception。
由以上3点推测,实际引入Jar包,并且注入对象,只是用了相应的壳子,里面的数据都来自于引入A服务的配置。但是作为resources目录下的东西,B服务配置文件application.properties没有生效,而mapper下的各个sql又生效了(一开始我还以为第3点会报SQL Bind Exceptioon)。这点要注意。
因此我们若要提供一个jar包服务作为公共调用,包括开发中,应尽量满足以下原则:
- 该jar包只用于提供公共方法,如hutuTool等。
- 若有用到配置项,应在@value注解里给个默认值,防止被引用的服务无法启动。目前在手册中尽量写明需要用哪些配置项。
- jar包只是一个壳子,他只是一系列方法的聚合,目的是为了防止重复造轮子,但这个轮子是什么数据,还是由被引用的服务提供。这也是引入jar包后注入对象调用这个方式 和 远程调用方式最显著的区别。一个是查自己的数据,一个是查别的数据。(终于解开了我多年的疑惑)
- jar包最好不要向外提供服务(为了防止mapping重复),也不要生成bean(为了防止bean重复,但这个好像很难做到,可以用接口+多态替代).应当只提供运算等。
由以上两点可知:通常能被当做jar包作为依赖的服务,
如果你调用的A服务的方法中包含A服务以springboot方式启动相关的逻辑,比如包含你的A服务的Aapplication.main()那A服务就是启动的,否则就是不启动的。spring启动过程中涉及到扫描启动类注解、根据类加载情况决定配置、依赖注入等等过程,这都不是调用一个简单的服务层方法能解决的。另外当你说调用A服务的方法时,调用的到底是什么,是A服务中的一个静态方法还是A服务实例的方法?一般用spring都是通过控制反转通过接口调用实例对象的方法实现而不是调用静态方法。那么你调用A服务时,A服务的实例是怎么初始化的?如果是你自己初始化的,应该能想到此时上下文里还没有初始化数据库相关的实例,比如连接池等等。
- 经过验证的有效解决办法
- 自己的经验指引,对解决问题有帮助
- 遵循 Markdown 语法排版,代码语义正确
- 询问内容细节或回复楼层
- 与题目无关的内容
- “赞”“顶”“同问”“看手册”“解决了没”等毫无意义的内容