文章只记录技术方面,本人只有一年多经验没有包装简历,面的岗位要求是3-5年要求技术栈就是常见那些,自研小规模公司hr说让我面面看,然后就很受打击,一上来就问的这些然后,就聊了8min,谈了下薪资不太满意就结束了
1.说一下eureka和nacos的区别?
2.redis、kafak等mq中间件有用过嘛
3.springboot的启动过程
4.jdbc的启动过程
5.stringbuffer和stringbuild的区别,共同继承的类和实现的接口
6.aop原理
7.动态代理原理
8.sql优化操作
9.有分库分表嘛
1.说一下eureka和nacos的区别
2.redis、kafak等mq中间件
这个一时半会更新不了,如果有资料可以分享下嘛
3.springboot启动过程
主要通过SpringApplication的static run方法进行SpringApplication的实例化操作
然后再针对实例化对象调用另外一个run方法完成项目初始化和启动。
先说实例化操作
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
/**
* Create a new {@link SpringApplication} instance. The application context will load
* beans from the specified primary sources (see {@link SpringApplication class-level}
* documentation for details. The instance can be customized before calling
* {@link #run(String...)}.
* @param resourceLoader the resource loader to use
* @param primarySources the primary bean sources
* @see #run(Class, String[])
* @see #setSources(Set)
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
1.赋值成员变量resourceLoader
资源加载接口,项目启动时打印banner信息,默认值DefaultResourceLoader,默认加载路径也是classpath:
2.赋值成员变量primarySources
这也是构造方法的第二个参数,默认传入springboot入口类,也可以是其他类,需要被@EnableAutoConfiguration标注,@SpringBootApplication包含该注解,所以也可以。如果传入不包含上述注解的普通类,那么springboot的自动配置不会开启
3.推断web应用类型
private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";
private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
servlet类都不存在就是reactive类型
当String数组中任意一个类不存在时,就是None(自己看吧,就在上面代码块,打字太多了)
最后返回servlet类型
4.加载并初识化ApplicationContextInitializer及相关实现类
package org.springframework.context;
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
void initialize(C var1);
}
这个接口就定义了一个方法,主要是为了初始化上下文(ConfigurableApplicationContext的ApplicationContext的refresh方法调用刷新之前对ConfigurableApplicationContext实例进行设置或者处理)
入参有限定符,他的子类
工作分为两步获得实例和设置实例
通过SpringFactoriesLoader类的loadFactoryNames方法获得META-INF/spring.factories文件中注册对应配置
获取到配置类全限定名后,调用createSpringFactoriesInstances方法进行实例化
然后调用setInitializers方法将实例化的集合添加到SpringApplication的成员变量Initializers中,直接new赋值的,每次调用会直接覆盖,目前开发没涉及到这个问题,小记一下
5.加载并初始化ApplicationListener及相关实现类
package org.springframework.context;
import java.util.EventListener;
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E var1);
}
事件的监听,最近写aop拦截第三方jar的sql遇到困难,写了全限定名甚至方法依然拦截不到,自己写的类都可以,不晓得这个监听可不可以借鉴一下
流程和上面一样,将结果赋值给listeners
6.推断main方法
private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
引用自身的deduceMainApplicationClass方法
先创建运行时异常,获取栈数组,进行遍历
遍历时如果有含有main方法的类就直接进行赋值,如果遍历时有异常会忽略
下一步就是run方法啦
public ConfigurableApplicationContext run(String... args) {
//创建StopWatch,用于统计run方法启动市场
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
//配置headless属性
configureHeadlessProperty();
//获得数组SpringApplicationRunListeners 并进行赋值
SpringApplicationRunListeners listeners = getRunListeners(args);
//遍历数组每个元素进行启动监听
listeners.starting();
try {
//获取arguments对象
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//加载属性配置
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
//打印Banner
Banner printedBanner = printBanner(environment);
//创建容器
context = createApplicationContext();
//准备容器
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//初始化容器
refreshContext(context);
//初始化之后执行,默认实现为空
afterRefresh(context, applicationArguments);
//停止统计
stopWatch.stop();
//打印启动日志
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
//通知监听器:容器启动完成
listeners.started(context);
//调用ApplicationRunner和CommandLineRunner的运行方法
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
//异常处理
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
//通知监听器,容器正在运行
listeners.running(context);
}
catch (Throwable ex) {
//异常处理
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}
第一步就是启动StopWatch的计时统计,就不解释了
1.获取SpringApplicationRunListener监听器
可以将SpringApplicationRunListeners理解为SpringApplicationRunListener的容器,并提供了各种遍历和对应starting、started、running方法
getRunListeners方法调用了“容器”的构造方法
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger,
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}
SpringApplicationRunListener为run方法提供了各个阶段的监听事件处理功能
2.启动所获取到的所有监听器
3.初始化ApplicationArguments
用于提供访问运行SpringApplication时的参数
构造方法入参是main方法传递进来的参数,然后再把这个参数封装成ApplicationArguments对象
4.初始化ConfigurableEnvironment
这是这个接口的代码
package org.springframework.core.env;
import java.util.Map;
public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {
//设置激活的组集合
void setActiveProfiles(String... var1);
//向当前激活的组集合中添加一个profile组
void addActiveProfile(String var1);
//设置默认激活的组集合。集合为空时使用这个方法
void setDefaultProfiles(String... var1);
//获取当前环境对象中的属性源集合,即应用环境变量
//该集合就是容纳PropertySource的容器
//该方法提供了直接配置属性源的入口
MutablePropertySources getPropertySources();
//获取操作系统的环境变量
//也提供了直接配置环境变量的入口
Map<String, Object> getSystemProperties();
//合并指定环境中的配置到当前环境中
Map<String, Object> getSystemEnvironment();
void merge(ConfigurableEnvironment var1);
}
这是方法的代码
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
//配置环境,主要包括PropertySources和acticeProfiles的配置
configureEnvironment(environment, applicationArguments.getSourceArgs());
//将ConfigurationPropertySourcess附加到指定环境中的第一位,并动态跟踪环境的添加或者删除
ConfigurationPropertySources.attach(environment);
//listeners环境准备
listeners.environmentPrepared(environment);
//绑定到SpringApplication
bindToSpringApplication(environment);
//判断是否为定制的环境没如果不是定制的环境就转换为StandarEnvironment
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
//和上面那个代码一样,如果真的转换了,需要更新一下,即使没转换也无所谓
ConfigurationPropertySources.attach(environment);
return environment;
}
主要几个步骤:
获取或创建环境:getOrCreateEnvironment();这个名字就很好理解了,如果获取不到环境就根据第一阶段推断的Web类型来创建一个指定环境
配置环境:
将ConfigurationPropertySourcess附加到指定环境中的第一位,并动态跟踪环境的添加或者删除
设置listener监听事件
绑定到SpringApplication:name为“spring.main”的目标上
判断是否为定制环境,如果不是则转换环境、
在进行一步将ConfigurationPropertySourcess附加到指定环境中的第一位,并动态跟踪环境的添加或者删除
5.忽略信息配置
spring.beaninfo.ignore的配置用来决定是否跳过BeanInfo类的扫描,如果为true则跳过
6.打印Banner图标
就是打印图标版本信息啥的
7.创建容器ConfigurableApplicationContext
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
代码中可以看到先判断contextClass 为null就会在根据web类型进行判断,
如果在创建SpringApplication之后,调用run方法之前,调用setApplicationContextClass方法进行设置,这个还会同时设置web应用类型
8.准备容器ConfigurableApplicationContext
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
//设置上下文配置环境
context.setEnvironment(environment);
//应用上下文后置处理
postProcessApplicationContext(context);
//在context刷新之前,ApplicationContextInitializer初识化context
applyInitializers(context);
通知监听器context准备完成,该方法以上为上下文准备阶段,以下为加载阶段
listeners.contextPrepared(context);
//打印日志,启动profile
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
//注册打印日志对象
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {、
//设置是否允许覆盖注册
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
//将source中的bean加载到context中
load(context, sources.toArray(new Object[0]));
//通知监听器context加载完成
listeners.contextLoaded(context);
}
准备容器过程中分为两步操作:准备和加载
准备:对context设置environment、应用上下文后置处理、ApplicationContextInitializer初始化context操作。
加载:打印日志和Profile的设置、设置是否允许覆盖注册、获取全部配置源、将配置源加载入上下文、通知监控器context加载完成
9.初始化容器ConfigurableApplicationContext
spring应用上下文刷新,通过refreshContext方法,应用上下文正式开启
再调用afterRefresh方法,默认为空,啥也不干
10.监听器通知容器启动完成
上述完成之后就会通知监听器
11.调用ApplicationRunner和CommandLineRunner
通过这两个接口实现在容器启动时执行的一些操作,多个实现类通过@Order控制执行顺序
12.监听器通知容器正在运行
上一步执行后就会通知监听器容器处于运行状态
4.JDBC启动过程
1.加载JDBC驱动
2.建立并获取数据库连接
3.创建JDBC Statements对象
4.设置SQL语句的传入参数
5.执行SQL语句并获得查询结果
6.对查询结果进行转换处理并将处理结果返回
7.关闭Connection,关闭Statement,关闭ResultSet
5.stringbuffer和stringbuild的区别,共同继承的类和实现的接口
区别:stringbuffer是线程安全的,stringbuild是线程不安全的
父类:AbstractStringBuilder抽象类
接口:java.io.Serializable、CharSequence