提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、简单回顾
- 二、run方法的入参
- 三、ComponentScan的扫描范围
- 总结
前言
提示:这里可以添加本文要记录的大概内容:
springboot启动流程有很多文章都介绍得很详细了,今天我们换种方式来讨论下启动类。
提示:以下是本篇文章正文内容,下面案例可供参考
一、简单回顾
1、首先快速创建一个springboot项目,编写一个测试接口。
@RestController
public class UserController {
@RequestMapping("/test")
public String getNameById(String id){
System.out.println("测试接口入参,id="+id);
return "lili";
}
}
接口正常,这是我们按默认方式创建项目。我们的启动类也很简单就是默认创建的。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
2、下面我们来改造下启动类,demo1:将启动类的注解去掉,run方法入参换一个配置类
import com.jy.demo.config.AopConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
//@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(AopConfig.class, args);
}
}
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.*;
/**
* @Author jy
* @Date 2022/4/2 15:38
* @Version 1.0
*/
@Configuration
@ComponentScan(basePackages = "com.jy.demo.controller")
//@EnableAspectJAutoProxy
@EnableAutoConfiguration
//@Import(JyAfterFilter.class)
public class AopConfig {
}
启动服务测试接口,发现服务正常启动,接口调用也是正常
从目前来看这样操作是没有问题。那么我们再来试试demo2的方式修改,先新建两个类具体位置如图
从图片中可以看出,main所在的类在app包下,配置类在最外层,这时我用TestApplication启动,服务也是正常运行,测试接口也是正常调用成功
3、我们再修改下代码,demo3:配置类也改一下
import com.jy.demo.config.AopConfig;
import org.springframework.boot.SpringApplication;
/**
* @Author jy
* @Date 2022/7/20 15:43
* @Version 1.0
*/
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(AopConfig.class, args);
}
}
服务启动依然正常,接口测试也正常
我们几种修改都能正常启动,接口也正常调用,那么为什么可以呢?
二、run方法的入参
1、首先我们还是回顾下spring的注解模式
public class Demo9 {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AopConfig.class);
applicationContext.publishEvent(new ApplicationEvent("监听事件推送") {
});
Apple bean = applicationContext.getBean(Apple.class);
bean.setId(1);
System.out.println(bean);
}
}
public void register(Class<?>... componentClasses) {
Assert.notEmpty(componentClasses, "At least one component class must be specified");
StartupStep registerComponentClass = this.getApplicationStartup().start("spring.context.component-classes.register")
.tag("classes", () -> Arrays.toString(componentClasses));
this.reader.register(componentClasses);//注意这行代码!!!!
registerComponentClass.end();
}
传入的参数就是一个配置类,spring先将这个配置类注入容器,然后在BeanFactoryPostProccsor的实现中处理配置类带来的该处理的对象。具体的操作可以看看Configuration注解解析,而springboot的启动类中的run方法入参是什么呢?其实也是一个配置类,那么springboot是怎么用这个配置类的呢?
首先是将这个配置类放在SpringApplication的成员变量里,然后在run方法中的上下文应用准备的时候对配置类进行了处理。
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
public Set<Object> getAllSources() {
Set<Object> allSources = new LinkedHashSet<>();
if (!CollectionUtils.isEmpty(this.primarySources)) {
allSources.addAll(this.primarySources);
}
if (!CollectionUtils.isEmpty(this.sources)) {
allSources.addAll(this.sources);
}
return Collections.unmodifiableSet(allSources);
}
往下一步就是在load()方法中加载
这里因为我们看的配置类,所以进入第一个if中,再往里走就到了我们前面spring处理时的类似代码了
圈上的这行代码和前面提示注意的那行代码就是一个意思了,到目前为止说清楚了,这个配置类被注册到容器的过程,那么和我们启动类的位置有什么关系呢?
三、ComponentScan的扫描范围
细心的朋友可能已经发现,我们几次修改的时候配置类的ComponentScan的value值是有所不同的,@SpringBootApplication的value值是默认空,而自己写的配置类上的ComponentScan注解是加了路径值的。两者的区别在于,默认空的话,扫描路径是按配置类所在的位置扫描同级及下级;有值的话就按填写的值进行扫描同级及下级。这个操作在ConfigurationClassPostProcessor类处理配置类信息时处理的。而配置类的信息在前面已经说了是怎么注册到容器中的,所以spring能处理到我们的启动配置类,也顺其自然的能处理到配置类上的扫描范围。所以虽然我们修改了main方法文件所在的位置,修改了配置类的位置服务依然能正常启用,接口也正常调用。
总结
唉,前面屁话了一堆,第三点就这么点东西说明了原因。最后再多一嘴,
既然启动类的位置可以不在最外层,那springboot为什么要放在最外层呢?因为springboot不知道我们会写些什么包路径,所以放最外层从最外层往下扫描,将对象是否注入容器交给开发者(需要注入就加注解)。以上就是我的一点个人理解,欢迎大家斧正。大家一起加油!!!