关于百度面试问答
、自我介绍一下
介绍个人相关经历以及所做项目,尽可能介绍详细
说一下springboot 的启动流程
以下资料来源于网络(如侵权告知删除)
1、首先从main方法开始看:
/**
* This is application Boot, used to configure the scan paths for mapper, servlet and filter etc.
*
*/
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients(basePackages = {"com.yaic"})
public class ApplicationBoot
{
/**
* @param args
*/
public static void main(final String[] args)
{
//启动的入口
SpringApplication.run(ApplicationBoot.class, args);
}
}
public static ConfigurableApplicationContext run(Class<?> primarySource,
String... args) {
//这个里面调用了run() 方法,我们转到定义
return run(new Class<?>[] { primarySource }, args);
}
//这个run方法代码也很简单,就做了两件事情
//1、new了一个SpringApplication() 这么一个对象
//2、执行new出来的SpringApplication()对象的run()方法
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}
2、上面代码主要分以下两步
第一步new了一个SpringApplication对象
第二部调用了run()方法
3、接下来我们来看看new SpringApplication()代码
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
//1、先把主类保存起来
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//2、判断运行项目的类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//3、扫描当前路径下META-INF/spring.factories文件的
ApplicationContextInitializer并加载
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//4、同样也是扫描当前路径下META-INF/spring.factories文件下的
ApplicationListener并加载
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
这里面还是要说一下ApplicationContextInitializer和ApplicationListener
ApplicationContextInitializer 这个类当springboot上下文Context初始化完成后会调用
ApplicationListener 当springboot启动时事件change后都会触发
我们来看一个案例,就更好理解上面这两个类
/**
* Context初始化后调用类
* @author ShiMinChen
*
*/
public class StarterApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("applicationContext 初始化完成 ... ");
}
}
public class StarterApplicationListener implements ApplicationListener {
@Override
public void onApplicationEvent(ApplicationEvent event) {
System.out.println(event.toString());
System.out.println("ApplicationListener .... " + System.currentTimeMillis());
}
}
我们需要把这两个类集成到springboot里面去,其实操作也挺简单的
然后在META-INF/spring.factories 文件配置那两个类
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null)
return result;
try {
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
List<String> factoryClassNames = Arrays.asList(
StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
result.addAll((String) entry.getKey(), factoryClassNames);
}
}
cache.put(classLoader, result);
// 端点打在这里就行了
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
总结:上面就是SpringApplication初始化的代码,new SpringApplication()没做啥事情 ,主要加载了META-INF/spring.factories 下面定义的事件监听器接口实现类
5、接下来看看run()方法,这个里面感觉有一大堆东西
public ConfigurableApplicationContext run(String... args) {
<!--1、这个是一个计时器,没什么好说的-->
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
<!--2、这个也不是重点,就是设置了一些环境变量-->
configureHeadlessProperty();
<!--3、获取事件监听器SpringApplicationRunListener类型,并且执行starting()方法-->
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
<!--4、把参数args封装成DefaultApplicationArguments,这个了解一下就知道-->
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
<!--5、这个很重要准备环境了,并且把环境跟spring上下文绑定好,并且执行environmentPrepared()方法-->
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
<!--6、判断一些环境的值,并设置一些环境的值-->
configureIgnoreBeanInfo(environment);
<!--7、打印banner-->
Banner printedBanner = printBanner(environment);
<!--8、创建上下文,根据项目类型创建上下文-->
context = createApplicationContext();
<!--9、获取异常报告事件监听-->
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
<!--10、准备上下文,执行完成后调用contextPrepared()方法,contextLoaded()方法-->
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
<!--11、这个是spring启动的代码了,这里就回去里面就回去扫描并且初始化单实列bean了-->
//这个refreshContext()加载了bean,还启动了内置web容器,需要细细的去看看
refreshContext(context);
<!--12、啥事情都没有做-->
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
<!--13、执行ApplicationRunListeners中的started()方法-->
listeners.started(context);
<!--执行Runner(ApplicationRunner和CommandLineRunner)-->
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, listeners, exceptionReporters, ex);
throw new IllegalStateException(ex);
}
listeners.running(context);
return context;
}
我们还是重点来看refreshContext(context) 这个方法,这个方法启动spring的代码加载了bean,还启动了内置web容器
private void refreshContext(ConfigurableApplicationContext context) {
// 转到定义看看
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
//看看refresh()方法去
((AbstractApplicationContext) applicationContext).refresh();
}
转到AbstractApplicationContext - >refresh()方法里面发现这是spring容器启动代码
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
spring容器启动代码就不说了,这里主要看一下onRefresh() 这个方法,转到定义发现这个方法里面啥都没有,因为这个AbstractApplicationContext是一个抽象类,所以我们要找到继承AbstractApplicationContext的子类,去看子类里面的onRefresh()
protected void onRefresh() throws BeansException {
//这是一个空方法,AbstractApplicationContext 这个类是一个抽象类,
//所以我们要找到集成AbstractApplicationContext的子类,去看子类里面的onRefresh()
// For subclasses: do nothing by default.
}
我们这里是一个Web项目,所以我们就去看 ServletWebServerApplicationContext 这个类 ,我还是把类的关系图贴一下
我们就去看 ServletWebServerApplicationContext 这个类下面的 onRefresh() 方法
protected void onRefresh() {
super.onRefresh();
try {
//看到内置容器的影子了,进去看看
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
//1、这个获取webServerFactory还是要进去看看
ServletWebServerFactory factory = getWebServerFactory();
this.webServer = factory.getWebServer(getSelfInitializer());
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context",
ex);
}
}
initPropertySources();
}
我们继续看下getWebServletFactory() 这个方法,这个里面其实就是选择出哪种类型
protected ServletWebServerFactory getWebServerFactory() {
// Use bean names so that we don't consider the hierarchy
String[] beanNames = getBeanFactory()
.getBeanNamesForType(ServletWebServerFactory.class);
if (beanNames.length == 0) {
throw new ApplicationContextException(
"Unable to start ServletWebServerApplicationContext due to missing "
+ "ServletWebServerFactory bean.");
}
if (beanNames.length > 1) {
throw new ApplicationContextException(
"Unable to start ServletWebServerApplicationContext due to multiple "
+ "ServletWebServerFactory beans : "
+ StringUtils.arrayToCommaDelimitedString(beanNames));
}
return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}
我们再回头去看factory.getWebServer(getSelfInitializer()) ,转到定义就会看到很熟悉的名字tomcat
public WebServer getWebServer(ServletContextInitializer... initializers) {
//tomcat这位大哥出现了
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null ? this.baseDirectory
: createTempDir("tomcat"));
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
prepareContext(tomcat.getHost(), initializers);
return getTomcatWebServer(tomcat);
}
Tomcat 就在这里启动的
总结:1、run() 方法主要调用了spring容器启动方法扫描配置,加载bean到spring容器中
2、启动的内置Web容器
对象的创建与销毁流程
首先我们先说对象的创建
对象的创建过程为:加载->验证->准备->解析->初始化
加载:
在加载的过程虚拟机主要完成三件事
1.通过一个类的全限定名来获取定义此类的二进制字节流
2. 构
3.在java堆中生成代表这个类的java.lang.Class对象作为方法区域的数据访问入口
验证
验证阶段是保证Class文件字节流复合JVM规范,不会给JVM造成危害,如果验证失败就会抛出java.lang.VerifyErroy异常或者其子类异常,验证过程分为四个阶段:
1.文件格式验证:验证字节流文件是否符合Class文件格式的规范,并且能被当前虚拟机正确的处理
2.元数据验证:是对字节码描述信息进行语义分析以保证其描述信息符合java语言的规范要求
3.字节码验证:主要是进行数据流和控制流的分析,保证被校验类的方法不会危害虚拟机
4.符号引用验证: 符号引用验证发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在解析阶段发生
准备
准备阶段为变量分配内存,并设置变量的初始化,在这个阶段分配的仅为类的变量(static修饰的变量)而不包括类的实例变量对已非final的变量,JVM会将其设置为零值,而不是赋予其语句的值:pirvate static int size = 12;。那么在这个阶段,size的值为0,而不是12。但final修饰的类变量将会赋值成真实的值。
解析
解析过程是将常量池内的符号引用直接替换为直接引用,主要包括四种类型引用的解析 类和接口的解析,字段解析,方法解析,接口方法解析。
初始化
在准备阶段类变量已经初始化一次了,在这个阶段,则是通过程序制定的计划去初始化类的变量和其他资源,这些资源有static{}块,构造函数,父类的初始化等。