文章只记录技术方面,本人只有一年多经验没有包装简历,面的岗位要求是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

6.aop原理

7.动态代理原理

8.sql优化操作

9.有分库分表嘛