面试精简版

1、springboot自动配置

  @EnableAutoConfiguration上有@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})两个注解。
  AutoConfigurationImportSelector.class加载/META-INF/spring.factories文件的信息,然后筛选出以EnableAutoConfiguration为key的数据,加载到IOC容器中,实现自动配置功能。具体细节如下:

  • 1、AutoConfigurationImportSelector实现DeferredImportSelector接口,而DeferredImportSelector接口又继承了ImportSelector。
  • 2、在AutoConfigurationImportSelector中调用SpringFactoriesLoader.loadFactoryNames方法。
    1. 从当前项目的类路径中获取所有 META-INF/spring.factories 这个文件下的信息。
    2. 将上面获取到的信息封装成一个 Map 返回,EnableAutoConfiguration为key。
    3. 从返回的Map中通过刚才传入的 EnableAutoConfiguration.class参数,获取该 key 下的所有值

到此xxxAutoConfiguration都配置完成了,那么这些类的属性值从哪里来呢?Mybatis为例:

@EnableConfigurationProperties({MybatisProperties.class})
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class})
public class MybatisAutoConfiguration implements InitializingBean{}

@ConfigurationProperties(
    prefix = "mybatis"
)
public class MybatisProperties {
	private String[] mapperLocations;
    private String typeAliasesPackage;
    ……
}

application.properties文件中:
mybatis.mapper-locations=classpath:mybatis/*.xml
mybatis.type-aliases-package=com.example.demo.blue.domain

到此自动配置类以及其属性值都配置完成了。

总结:
  通过注解@SpringBootApplication里的@Import({AutoConfigurationImportSelector.class}),在AutoConfigurationImportSelector中用SpringFactoriesLoader.loadFactoryNames方法获取所有 META-INF/spring.factories 这个文件下的信息,后续会根据@ConditionOnClass这个注解exclude一些不要的配置类。最后根据@EnableConfigurationProperties加入该类所需的属性,属性类上都有@ConfigurationProperties注解。

2、Mybatis动态代理—MapperProxy

先来看段代码:

SqlSession sqlSession=sqlSessionFactory.openSession();
BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);

代理后的截图:
在这里插入图片描述
这里看一部分内容,如下目录结构:

  • Sqlsession
      - Configuration
        - environment
        - mappedStatements
        - resultMaps

如下图所示:
在这里插入图片描述在这里插入图片描述
  SqlSession本身不做任何事情,直接把任务甩给Configuration。Configuration又把任务甩给MapperRegistry。从类名就能看出来它的作用是用来注册接口和生成代理类实例的工具类。任务继续给到MapperProxyFactory.newInstance(sqlsession),它是创建Mapper代理类的工厂类。这个类里有两个newInstance方法。第一个是直接创建一个代理类并返回。第二类是创建一个MapperProxy类然后调用第一个newInstance方法。
  MapperProxy这个类实现了jdk动态代理接口InvocationHandler。在invoke方法中实现代理方法调用的细节。 在invoke方法里先获取MapperMethod类,然后调用mapperMethod.execute()。
  MapperMethod类是整个代理机制的核心类,对SqlSession中的操作进行了封装。 该类里有两个内部类SqlCommand和MethodSignature。 SqlCommand用来封装增删改查操作,也就是我们在xml中配置的select、update、delete、insert节点。每个节点都会生成一个MappedStatement类。MethodSignature用来封装方法的参数,返回类型。

3、Mybatis自动配置原理

  1. mybatis-spring-boot-starter将mybatis需要的依赖全部引入。
  2. starter通过SPI机制引入了一个配置的Class(MyBatisAutoConfiguration)。它负责注册SqlSessionFactory和SqlSessionTemplate到Spring容器中,使用MyBatis开发时绝大部分功能要使用这两个类来完成。
  3. 注入了AutoConfiguredMapperScannerRegistrar这个Bean到Spring容器,它负责将MapperScanner引入到Spring容器,然后MapperScanner会将工程中指定package下的Mapper转化为BeanDefinition并且注册到Spring容器中。
  4. 在开发中使用某个具体的Mapper时,Spring能够从容器中找到这个Mapper对应的BeanDefinition,将其实例化并且注入,这样开发者就可以使用了。
  5. SpringBoot+MyBatis自动配置涉及到Spring中两个自动注册Bean的关键接口(BeanDefinitionRegistryPostProcessor和ImportBeanDefinitionRegistrar),也是我们业务开发中需要重点关注的地方。

4、springboot内置tomcat启动原理

  前言:SpringBoot的启动主要是通过实例化SpringApplication来启动的,启动过程主要做了以下几件事情:配置属性、获取监听器,发布应用开始启动事件初、初始化输入参数、配置环境,输出banner、创建上下文、预处理上下文、刷新上下文、再刷新上下文、发布应用已经启动事件、发布应用启动完成事件。
  在SpringBoot中启动tomcat的工作在刷新上下这一步。而tomcat的启动主要是实例化两个组件:Connector、Container,一个tomcat实例就是一个Server,一个Server包含多个Service,也就是多个应用程序,每个Service包含多个Connector和一个Container,而一个Container下又包含多个子容器。

5、springboot启动流程

  SpringBoot应用程序的启动流程主要包括初始化SpringApplication和运行SpringApplication两个过程。
  初始化SpringApplication包括配置基本的环境变量、资源、构造器和监听器,为运行SpringApplication实例对象做准备。
  运行SpringApplication包括SpringAplicationRunListeners 引用启动监控模块、ConfigrableEnvironment 配置环境模块、prepareContext 配置应用上下文、refreshContext 刷新应用上下文(自动配置在这完成)和调用listeners.started(context)。

public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
        ConfigurableApplicationContext context = null;
        this.configureHeadlessProperty();
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting(bootstrapContext, this.mainApplicationClass);

        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
            Banner printedBanner = this.printBanner(environment);
            context = this.createApplicationContext();
            context.setApplicationStartup(this.applicationStartup);
            this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
            this.refreshContext(context);
            this.afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }

            listeners.started(context);
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, listeners);
            throw new IllegalStateException(var10);
        }

        try {
            listeners.running(context);
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var9);
        }
    }

6、spring data jpa

  spring在启动的时候会实例化一个Repositories,它会去扫描所有的class,然后找出由我们定义的并且继承自JpaRepository的接口,然后遍历这些接口,针对每个接口一次创建如下几个实例:

  1. SimpleJapRepository:默认进行默认的DAO操作,是所有Repository的默认实现。
  2. JpaRepositoryFactoryBean:装载了动态代理Proxy,会以对应的DAO的beanName为key注册到DefaultListableBeanFactory中,在需要被注入的时候从这个bean中取出对应的动态代理Proxy注入给DAO。
  3. JdkDynamicAopProxy:动态代理对应的InvocationHandler,负责拦截DAO接口的所有的方法调用,然后做相应处理。

总结:
  spring 会在启动的时候扫描所有继承自Repository接口的DAO接口,然后为其实例化一个动态代理,同时根据它的方法名、参数等为其装配一系列DB操作组件。在需要注入的时候为对应的接口注入这个动态代理。在DAO方法被调用时会走这个动态代理,然后经过一系列的方法拦截路由到最终的DB操作
执行器JpaQueryExecution,然后拼接sql,执行相关操作,返回结果。

7、spring AOP

  AOP是一种编程思想,通过预编译方式和动态代理的方式,在不修改源代码的情况下实现给程序动态添加功能的技术。
  AOP可以对业务逻辑各个部分进行隔离,从而使业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高开发效率。

AOP可以有多种实现方式,而spring AOP支持如下两种实现方式:

  • JDK动态代理:这是JAVA提供的动态代理技术,可以在运行时创建接口的代理实例,spring AOP默认采用这种方式,在接口的代理实例中织入代码。
  • CGLib动态代理:采用底层的字节码技术,在运行时创建子类代理的实例。当目标对象没有实现接口时,spring AOP就会采用这种方式,在子类实例中织入代码。

使用场景:权限认证、事务、日志、自动缓存、错误处理和调试。

8、反射

  Java反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且改变它的属性。
  反射机制允许程序在运行时取得任何一个已知名称的class的内部信息,包括其modifiers(修饰符)、fieds(属性)、methods(方法)等,并可在运行时改变fields内容或调用methods。好处就是:可以在运行时装配,无需再组件之间进行源代码链接,降低代码的耦合度,实现动态代理等。

得到class的三种方式:

// 1、通过对象调用 getClass() 方法来获取
User user = new User();
Class c1 = user.getClass();

// 2、直接通过 类名.class 的方式得到,该方法最为安全可靠,程序性能更高
class c2 = User.class;

// 3、通过Class对象的 forName() 静态方法获取,用的最多
class c3 = Class.forName(com.*****.User);

Class方法:

getName(): 获得类的完整名字。
getFields(): 获得类中public类型的属性。
getDeclaredFields(): 获得类中所有属性,包括privateprotected声明的属性。
getMethod(): 获得类中public类型的方法。
getDeclaredMethods(): 获得类的所有方法,包括privateprotected声明的方法。
getMethod(String name,Class[] parameterTypes): 获得类的特定方法,name参数指定方法的名字,parameterTypes参数指定方法的参数类型。
getConstructor(): 获得类中public类型的构造方法。
getConstructor(Class[] parameterTypes): 获得类的特定构造方法,parameterTypes参数指定构造方法的参数类型。
newInstance(): 通过类的不带参数的构造方法创建这个类的一个对象。

9、synchronized底层原理

  在jdk1.6之前,synchronized被称为重量锁,在jdk1.6中,为了减少获得锁和释放锁带来的性能开销,引入了偏向锁和轻量级锁。JAVA虚拟机是通过进入和退出Monitor对象来实现代码块同步和方法同步的,代码块同步使用的是monitorenter和monitorexit指令实现的,而方法同步是通过Access flags后面的标识来确定该方法是否为同步方法。

10、volatile底层原理

先介绍指令重排序:

  • 编译器优化重排:编译器咋不改变单线程程序语义的情况下,可以对语句的执行顺序进行重新排序。
  • 指令并行重排:现代处理器多采用指令级并行技术来将多条指令重叠执行。对于不存在数据依赖的程序,处理器可以对机器指令的执行顺序进行重新排序。
  • 内存系统重排:因为处理器使用缓存和读/写缓冲区,使得加载和储存看上去像是在乱序执行。

volatile实现原理:

  • 实现内存可见性原理:实现缓存一致性。被volatile修饰的变量,生成汇编指令时会比普通的变量多出一个Lock指令,这个Lock指令就是volatile关键字可以保证内存可见性的关键,它主要有两个作用:一、将当前处理器缓存的数据刷新到主内存;二、刷新到主内存时会使得其他处理器缓存的该内存地址的数据无效。
  • 实现有序性原理:编译器会生成字节码时通过插入内存屏障来禁止指令重排序。

Ps:内存屏障是一种CPU指令,它的作用是对该指令前和指令后的一些操作产生一定的约束,保证一些操作按顺序执行。

11、spring 生命周期

创建过程:

  • 实例化 Bean,并设置 Bean 的属性。
  • 根据其实现的 Aware(主要是 BeanFactoryAware,ApplicationContextAware 接口) 设置以来信息。
  • 调用 BeanPostProcess 的 postProcessBeforeInitialization 方法,完成 initial 前的自定义逻辑。
  • 初始化。
  • 调用 BeanPostProcess 的 postProcessAfterInitialization 方法去做一些 bean 初始化之后的自定义工作。

销毁过程:

  • 如果实现了 DisposableBean 的 destroy 方法,则调用它。
  • 如果实现了自定义的销毁方法,则调用之。

12、单例模式下 setter 循环依赖解决方案

spring 的单例对象的初始化主要分为三步:

  • createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象。
  • populateBean:填充属性,这一步主要是多 bean 的依赖属性进行填充。
  • initializeBean:调用 spring xml 中的 init 方法。
缓存用途
singletonObject用于存放完全初始化好的 bean,从该缓存中取出的 bean 可以直接使用
earlySingletonObjects存放原始的 bean 对象(尚未填充属性),用于解决循环依赖
singletonFactories存放 bean 工厂对象,用于解决循环依赖

如:A依赖B,B依赖A。A完成了实例化放入 singletonFactories,B也完成实例化发现需要B,这时A从 singletonObjects 缓存、 earlySingletonObjects缓存 找到 singletonFactories 缓存时、最终找到了A;A发现自己也需要B,直接在 singletonObjects 缓存中就找到了。

13、spring 框架中用到了哪些设计模式?

  • 工厂设计模式:spring 使用工厂模式通过BeanFactory、ApplicationContext 创建 bean 对象。
  • 代理设计模式:spring AOP 功能的实现。
  • 单例设计模式:spring 中的 Bean 默认都是单例的。
  • 模板方法模式:spring 中的 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
  • 包装器设计模式:我们的项目需要连接多个数据库,而且在不同的客户每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
  • 观察者模式:spring 事件驱动模型就是观察者模式很经典的一个应用。
  • 适配器模式:spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配 Controller。

14、Hibernate 和 MyBatis 的区别

相同点:都是对 jdbc 的封装,都是持久层的框架,都用于 dao 层的开发。
不同点:

  • 映射关系
    1. MyBatis 是一个半自动映射的框架,配置 JAVA 对象与 sql 语句执行结果的对应关系,多变关联关系配置简单。
    2. Hibernate 是一个全表映射的框架,配置 JAVA 对象与数据库表的对应关系,多表关联关系配置复杂。
  • SQL 优化和移植性
    1. Hibernate 对SQL语句封装,提供了日志、缓存、级联(级联比MyBatis强大)等特性,此外还提供 HQL (Hibernate Query Language)操作数据库,数据库无关性支持好,但会消耗性能。
    2. MyBatis 需要手动编写 SQL,支持动态 SQL、处理列表、动态生成表名、支持存储过程。
  • 开发难易程度和学习成本
    1. Hibernate 是重量级框架,学习使用门槛高,适合于需求相对稳定,中小型的项目。
    2. MyBatis 是轻量级框架,学习使用门槛低,适合于需求变化频繁,大型的项目。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值