spring面试题


spring面试题

Spring是一种轻量化开发框架,旨在提高开发人员的开发效率及系统的可维护性

Spring的优势是什么

轻量:Spring是轻量的,基本的版本大约有2M,如果需要的话再进行整合

控制反转:Spring通过控制反转实现了松散耦合,当创建出一个对象,对对象的属性进行赋值,从整个容器空间直接进行赋值,而不是通过getter和setter进行注入

AOP:Spring支持面向切面的编程,并且把业务逻辑和系统业务分开

容器:Spring管理并保存对象中的配置和生命周期

MVC框架:是一种设计思想,提供web框架下面的一个服务

事务管理:声明式事务和编程式事务,可以自己配置事务的开启、关闭、提交、回滚等操作,都不需要人为控制,而由事务管理器管理,使用注解就是@Transactional

异常处理:提供一种统一处理异常的机制,而用户只需要简单配置就行了

Spring七大组件

Spring框架是一个分层架构,由七大模块组成:

  • 核心容器(Spring core):核心容器提供Spring框架的基本功能,容器的主要组件是BeanFactory,它是工厂模式的实现。BeanFactory使用控制反转(IOC)模式架构将应用程序的配置和依赖性规范与实际的应用程序代码分开
  • Spring上下文(Spring Context):是一个配置文件,向spring框架提供上下文信息,包括:企业服务,例如JNDI(Java Naming and Directory Interface) java命名与接口目录 、EJB、电子邮件、国际化、校验和调度功能
  • Spring面向切面模块(Spring AOP):通过配置管理特性,Spring AOP模块直接将面向切面的编程功能集成到了Spring框架中,可以将一些通用任务,如安全、事务、日志等集中进行管理,提高了复用性和挂管理的便捷性
  • Spring DAO模块(Spring DAO):提供了异常层次结构,可以用来管理异常处理和不同数据库抛出的错误信息
  • Spring ORM模块(Spring ORM):提供了ORM的对象关系工具,包括JDO、Hibernate和ibatis SQL Map
  • Spring Web模块(Spring Web):建立在应用程序上下文之上,为基于web的应用程序提供上下文
  • Spring MVC框架(Spring MVC):MVC框架是一个全功能构建web程序的MVC实现

对于Spring IOC的理解

什么是IOC

IOC,Inversion of control,控制反转,就是指将对象的控制权转移给spring框架,有spring来负责控制对象的生命周期和对象的依赖关系

以前创建对象需要自己手动创建,如果在一个对象中使用另外的对象,需要new创建一个依赖对象,使用完后还得销毁。而IOC则是由专门的容器来帮忙创建对象,并将所有的类登记在spring容器中,当需要某个对象,不在需要自己去手动new对象了,只需要在spring的配置文件说明就可以了,spring会在系统运行到适当的时机将对象主动给你

什么是DI

DI,Dependency Injection,依赖注入,就是应用程序在运行时依赖Ioc容器来动态注入对象所需的外部依赖。而Spring的DI是通过反射实现注入的,反射允许程序在运行时动态生成对象、执行对象的方法、改变对象的属性

依赖注入的三种方式

1、构造器注入

 public CatDaoImpl(String message){ 
 this. message = message; 
 } 
<bean id="CatDaoImpl" class="com.CatDaoImpl"> 
    <constructor-arg value=" message "></constructor-arg> 
</bean>

2、Set方式注入

 public class Id { 
 private int id; 
 public int getId() { return id; } 
 public void setId(int id) { this.id = id; } 
} 
<bean id="id" class="com.id "> <property name="id" value="123"></property> </bean>

3、拓展方式注入

使用P命令方式和C命令方式

<!--    p命名空间注入,可以直接注入属性的值:property-->
    <bean id="user" class="com.bai.pojo.User" p:name="拜拜" p:age="15"></bean>

<!--        c命名空间注入,通过构造器注入;construct-args-->
    <bean id="user1" class="com.bai.pojo.User" c:name="shaha" c:age="16"></bean>

Spring创建对象的三种方式

  • 使用默认的构造函数创建,在spring的配置文件使用bean标签,配置id和class属性后,没有其他事项的标签

    • 无参构造方法进行创建,然后使用set方法对属性进行注入
    <bean id="helloBean" class="com.bai.HelloWorld">
        <property name="name" value="baibai"/>
    </bean>
    
    • 有参构造方法进行创建
    <bean id="helloBean" class="com.taoj.entity.HelloWorld">
        <constructor-arg name="name" value="火星"/>
    </bean>
    
  • 使用普通工厂的方法创建,使用某个类中的方法创建对象并存入spring容器

    • 创建一个静态工厂类

      public class HelloWorldFactory {
          public static HelloWorld getInstance(){
              return new HelloWorld();
          }
      }
      
    • xml配置

      <bean id="hello2" class="com.bai.HelloWorldFactory" factory-method="getInstance"></bean>
      
    • 创建对象

      
      public class HelloWorldSpring2 {
          public static void main(String[] args) {
              ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext2.xml");
              HelloWorld hello = (HelloWorld) context.getBean("hello2");
              hello.setName("火星");
              hello.sqyHello();
              System.out.println("用静态工厂方法创建对象结束");
       
          }
      }
      
  • 使用工厂的静态方法创建,使用某个类中的静态方法创建对象并存入spring容器

    • 新建一个实例工厂类

      public class HelloWorldFactory3 {
          public HelloWorld getInstance(){
              return new HelloWorld();
          }
      }
      
    • 配置文件

      <bean id="helloWorldFactory3" class="com.taoj.factory.HelloWorldFactory3"></bean>
      <bean id="hello3" factorybean="helloWorldFactory3" factory-method="getInstance"></bean>
      
    • 测试类

      
      public class HelloWorldSpring3 {
       
          public static void main(String[] args) {
              ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext3.xml");
              HelloWorld hello3 = (HelloWorld) context.getBean("hello3");
              hello3.setName("火星我爱你");
              hello3.sqyHello();
          }
      }
      

IOC创建对象的方式

  • 使用无参构造创建对象,默认

  • 使用有参构造创建对象

    • 下标赋值

      <bean id="user" class="com.bai.pojo.Hello">
          <constructor-arg index="0" value="baibai"/>
      </bean>
      
    • 类型

      <bean id="user" class="com.bai.pojo.Hello">
          <constructor-arg type="java.lang.String" value="baibai"/>
      </bean>
      
    • 参数名

      <bean id="user" class="com.bai.pojo.Hello">
          <constructor-arg name="str" value="baibai"/>
      </bean>
      

对于Spring AOP的理解

AOP,一般称为面向切面,作为面向对象的一种补充,用于将那些与业务无关的,但却对多个对象产生影响的公共行为和逻辑,抽取并封装成一个可用的模块,这个模块称为切面

AOP是OOP的延续,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

代理模式

AOP实现的关键是代理模式,主要分为静态代理和动态代理。静态代理的代表是AspectJ;动态代理以Spring AOP为代表

  • AspectJ是静态代理,也称为编译时增强,AOP框架会在编译阶段生成AOP代理类,并将AspectJ切面编织到java字节码中,运行之后就是增强后的AOP对象
  • Spring AOP使用的动态代理,就是AOP框架不会去修改字节码,而是每次运行的时在内存中临时方法生成AOP对象,这个对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调对象的方法

名词概念

  • 连接点(Join point):程序运行时所执行的方法,在AOP,一个连接点代表一个方法的执行
  • 切面(Aspect):被抽取出来的公共模块,可以用来横切多个对象。Aspect切面可以看成Pointcut切点和Advice通知的结合,一个切面有可以有多个切点和通知组成。AOP中,切面可以在类上用@AspectJ注解实现
  • 切点(Pointcut):要对哪些连接点进行拦截
    • execution方式:用路径表达式指定对哪些方法进行拦截,比如指定拦截add *、search *
    • annotation方式:指定被哪些注解修饰的代码进行拦截
  • 通知(advice):要在连接点上执行的动作,即逻辑的增强,比如权限校验和日志记录等
  • 目标对象(target):包含连接点的对象,也称为被通知的对象
  • 织入(weaving):通过动态代理,在目标对象的方法(连接点)中执行增强逻辑(通知)的过程
  • 引入(introduction):添加额外的方法或者字段到被通知的类

image-20220517192319208

AOP各个概念所处场景

image-20220517192428582

Spring通知(Advice)有哪些类型

  • 前置通知(Before Advice):在连接点之前执行的通知
  • 后置通知(After Advice):当连接点退出时执行的通知
  • 环绕通知(Around Advice):包围一个连接点的通知,可以在方法调用前后完成自定义行为,也可以选择是否继续执行或者直接返回他们自己的返回值或者抛出异常结束执行
  • 返回后通知(AfterReturning Advice):在连接点正常完成之后执行的通知
  • 抛出异常后通知(AfterThrowing Advice):在方法抛出异常退出时执行的通知

BeanFactory和ApplicationContext的区别

BeanFactory和ApplicationText是spring的核心接口,都可以当做spring的容器

1、BeanFactory是spring中最底层的接口,IOC的核心,定义了IOC的基本功能,包含了各种bean的定义、加载、实例化、依赖注入、生命周期管理。ApplicationContext是BeanFactory的子类,除了提供BeanFactory的功能外,还提供了更完整的框架功能

2、BeanFactory采用的是延迟加载形式来注入Bean的,只有使用到某个bean时,才会对该bean进行实例化,所以我们不能提前发现存在的spring配置问题。如果bean的某个属性没有注入,BeanFactory加载后,直到bean被调用时才会抛出异常

AppliactionContext是在容器启动时,就一次性创建所有的bean,因此启动时,能够发现spring 的配置问题,有利于检查依赖属性是否注入

ApplicationContext在启动后预加载所有的单实例bean,在运行时速度较快,相较于BeanFactory,ApplicationContext唯一的不足就是占用内存空间,当应用程序配置bean较多时,程序启动较慢

3、BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor,之间的区别是BeanFactory需要手动注册,而Application则是自动注册

4、BeanFactory通常以编程的方式被创建,ApplicationContext还以声明的方式创建

Spring Bean的生命周期

Spring Bean的生命周期有四个阶段:

  • 实例化(Instantiation)
  • 属性赋值(Populate)
  • 初始化(Initialization)
  • 销毁(Destruction)

image-20220519173752790

1、实例化Bean

对于BeanFactory容器,当客户向容器请求一个未初始化的bean或者初始化bean时需要注入未初始化的依赖时,容器就会调用createBean进行实例化

对于ApplicationContext,当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean

2、设置对象属性

实例化的对象被封装在BeanWrapper中,然后spring会根据BeanDefinition中的信息和通过BeanWrapper提供的设置属性的接口完成属性设置与依赖注入

3、处理Aware接口

spring会检测对象是否实现了xxxxAware接口,通过Aware类型的接口,拿到容器中一些资料

  • 如果这个bean实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,传入bean的名字
  • 如果这个bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例
  • 如果这个bean实现了BeanFactoryAware接口,调用它实现的setBeanFactroy()方法,传递的是spring本身
  • 如果这个bean实现了ApplicationContextAware接口,调用setApplicationContext(ApplicationContxet)方法,传入spring上下文

4、BeanPostProcessor前置处理

如果对bean进行自定义的前置处理,可以让bean实现了BeanPostProcessor接口,会调用postProcessBeforeInitialization(Obj obj,String s)方法

5、InitializationBean

如果实现了bean实现了InitializationBean接口,执行afterPropertiesSet()方法

6、init-method

如果bean在spring的配置文件中配置了init-method属性,会自动调用其配置的初始化方法

7、BeanPostProcessor后置处理

如果这个bean实现了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法,由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术

bean已经被正确创建了,之后可以使用bean

8、DisposableBean

当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用其实现的destroy()方法

9、destroy-method

最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。

Spring中bean的作用域

  • singleton:单例模式,Spring IOC容器中只会存在一个共享的bean实例,无论有多少个bean引用他,始终指向同一对象。该模式在多线程下是不安全的,singleton作用域是spring中的缺省作用域,也可以显示的将bean定义为singleton模式

    <bean id="userDao" class="com.ioc.UserDaoImpl" scope="singleton"/>
    
  • prototype:原型模式,每次通过spring容器获取prototype定义的bean时,容器都将创建一个新的bean实例,每个bean实例都有自己的属性和状态,而singleton只有一个全局对象。对有状态的bean使用prototype作用域,而对无状态的bean使用singleton作用域

  • request:在一次 Http 请求中,容器会返回该 Bean 的同一实例。而对不同的 Http 请求则会产生新的 Bean,而且该 bean 仅在当前 Http Request 内有效,当前 Http 请求结束,该 bean实例也将会被销毁。

    <bean id="loginAction" class="com.cnblogs.Login" scope="request"/>
    
  • session:在一次 Http Session 中,容器会返回该 Bean 的同一实例。而对不同的 Session 请

    求则会创建新的实例,该 bean 实例仅在当前 Session 内有效。同 Http 请求相同,每一次

    session 请求创建新的实例,而不同的实例之间不共享属性,且实例仅在自己的 session 请求

    内有效,请求结束,则实例将被销毁。

    <bean id="userPreference" class="com.ioc.UserPreference" scope="session"/>
    
  • global session:在一个全局的 Http Session 中,容器会返回该 Bean 的同一个实例,仅在

    使用 portlet context 时有效。

spring框架中的Bean是线程安全的嘛?如果线程不安全,如何处理?

spring本身并没有提供bean的安全线程策略,所以bean本身不具备线程安全的特性,但是具体的情况还是要结合bean的作用域来讨论

1、对于prototype作用域的bean,每次都创建一个新的对象,也就是线程之间不存在bean共享,因此不会有线程安全问题

2、对于singleton作用域的bean,所有的线程都共享一个单例实例的bean,因此存在线程安全的问题。但是单例bean是一个无状态的bean,也就是线程中的操作不会对bean的成员执行查询之外的操作,那么这个单例bean是安全的。举例Controller类、Service类和Dao类,这些类大多是无状态的,只关注方法本身

  • 有状态bean:有实例变量的对象,可以保存数据,是非线程安全的
  • 无状态bean:没有实例变量的对象,不能保存数据,是不变类,线程安全

对于有状态bean保证线程安全

  1. 将有状态bean的作用域改为prototype
  2. 采用ThreadLocal解决线程安全问题,为每一个线程提供一个独立的变量副本,不同线程只操作自己线程的副本变量

spring的自动装配

Spring装配包括手动装配和自动装配,手动装配是由有基于xml装配、构造方法、setter方法等

在Spring框架下xml配置中共有5种自动装配

  • no:默认的方式是不进行自动装配,通过手动设置ref属性来进行装配bean
  • byName:通过bean的名称进行自动装配,如果一个bean的property与另一个bean的name相同,就进行自动装配
  • byType:通过参数的类型进行自动装配
  • constructor:利用构造函数进行装配,并且构造函数的参数通过byType进行装配
  • autodetect:自动探测,如果有构造方法,通过construct的方式自动装配,否则使用byType的方式自动装配

spring事务的实现方式和实现原理

Spring事务的本质其实是数据库对事务的支持,没有数据库的支持,spring是无法提供功能的。spring只提供统一事务管理接口,具体实现都是各数据库自己实现,数据库事务的提交和回滚是通过redo log和undo log实现的。Spring会在事务开始时,根据当前环境设置的隔离级别,调整数据库隔离级别,由此保持一致

Spring事务的种类

spring支持编程事务管理和声明事务管理两种方式

1、编程事务管理使用Transaction Template

2、声明事务管理建立在AOP上。本质是通过AOP功能,对方法前后进行拦截,将事务的功能编织到拦截方法中,也就是在目标方法开始之前的一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务

声明事务的优点不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明或通过@Transactional注解的方式,便可以将事务规则应用到业务逻辑中,减少业务代码的污染。唯一不足地方是,最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。

spring事务的传播机制

spring事务的传播机制指的是,当多个事务同时存在时,spring如何处理这些事务的行为。事务传播机制实际上是使用简单的ThreadLocal实现的,所有调用的方法是在新线程调用的,事务传播实际上是会失效的

  • PROPAGATION_REQUIRED:(默认传播行为)如果当前没有事务,就创建一个新事务;如果当前存在事务,就加入该事务。
  • PROPAGATION_REQUIRES_NEW:无论当前存不存在事务,都创建新事务进行执行。
  • PROPAGATION_SUPPORTS:如果当前存在事务,就加入该事务;如果当前不存在事务,就以非事务执行。‘
  • PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则按REQUIRED属性执行。
  • PROPAGATION_MANDATORY:如果当前存在事务,就加入该事务;如果当前不存在事务,就抛出异常。
  • PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。

spring中隔离机制

  • ISOLATION_DEFAULT:这是个 PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别。

  • ISOLATION_READ_UNCOMMITTED:读未提交,允许事务在执行过程中,读取其他事务未提交的数据。

  • ISOLATION_READ_COMMITTED:读已提交,允许事务在执行过程中,读取其他事务已经提交的数据。

  • ISOLATION_REPEATABLE_READ:可重复读,在同一个事务内,任意时刻的查询结果都是一致的。

  • ISOLATION_SERIALIZABLE:所有事务逐个依次执行。

注解的原理

java注解就是代码中的一些特殊标记,用于编译、类加载、运行时进行解析和使用,并进行响应的处理。

  • 本质是继承了Annotation的特殊接口,具体实现类时JDK动态代理生成的代理类,通过反射获取注解时,返回的也是java运行时生成的动态代理对象$Proxy1
  • 通过代理对象调用的自定义注解的方法,最终会调用AnnotationInvocationHandler的invoke方法,该方法会从memberValues这个map中查询出对应的值,而memberValues的值来自于java常量池

Spring框架中都用到了哪些设计模式

  • 工厂模式:Spring通过BeanFactory和ApplicationContext来创建对象
  • 单例模式:bean为单例模式
  • 策略模式:Recource的实现类,针对不同的资源文件,实现额不同的资源获取策略
  • 代理模式:Spring的AOP功能使用到了JDK的动态代理和CGLIB字节码生成技术
  • 模板方法:将相同的代码放在父类,而将不同的代码放在不同的子类中,用来解决代码重复的问题
  • 适配器模式:Spring的AOP的增强或者通知(advice)使用到了适配器模式;springMVC用到了适配器模式适配Controller
  • 观察者模式:Spring事件驱动模型
  • 桥接模式:可以根据客户的需求能够动态切换不同的数据源。比如我们的项目需要连接多个数据库,客户在每次访问中根据需要会去访问不同的数据库

@Autowired和@Recource的区别

定义

@Autowired:对类成员变量、方法及构造函数进行标注,完成自动装配的工作

@Recource:通过其唯一的名称来标识特定的组件,

不同

@Autowired是spring内置的注解,按类型注入,若是多个相同类型可以配合@Qualifier指定bean

@Recource是JDK提供的注解,默认按名称注入,没有指定名称按变量名称注入,变量名找不到在按类型注入

1、包含的属性不同

@Autowired只包含一个参数required,表示是否开启自动注入,默认是true

@Recource包含七个参数,其中两个参数name和type

2、自动装配方式不同

@Autowired是spring内置的注解,默认按类型(ByType)注入,若是多个相同类型可以配合@Qualifier指定bean

@Recource是JDK提供的注解,默认按名称(ByName)注入,没有指定名称按变量名称注入,变量名找不到在按类型注入

3、注解的位置不同

@Autowired能够在构造器、方法、参数、成员变量和注解上

@Recource能用在类、成员变量和方法上

4、兼容性不同

@Autowired只能在Spring框架下使用,而@Recource可以在多个框架下使用

5、装配顺序不同

@Autowired默认先按照ByType的方式进行匹配,如果发现多个bean,则可以按照ByName的方式进行匹配,如果还有多个,则抛出异常

@Recource的装配顺序

  • 同时指定name和type:查找name和type唯一匹配的bean,如果找到,则自动匹配,否则抛出异常
  • 只指定name:查找name唯一匹配的bean,如果找到则自动装配,否则抛出异常
  • 只指定type:查找name唯一匹配的bean,如果找到且找到一个则自动装配,如找到多个或者找不到抛出异常
  • 没有指定name和type:默认按照ByName方式进行查找,如果没有找到符合的bean,则回退为一个原始类型进行查找,找到则注入

@Transactional什么时候会失效

@Transactional是声明式事务中的注解,添加位置在类或者接口实现方法上

@Transactional只能被应用到public方法中,有SpringAOP的本质决定的

@Transactional是指使用了JDBC的事务来控制的

  • 加了@Transactional的方法是被代理对象调用时,才会生效,因为spring是基于事务来实现的,

  • 添加位置的方法是私有的,也会失效,因为底层cglib是基于父子类来实现的,子类是不能重载父类的私有方法的,所以无法很好的利用代理

SpringBoot的优点

SpringBoot是基于Spring创建一个上层应用框架

  • 独立运行:不用单独的安装一个tomcat文件,然后将项目部署到tomcat中,只需要引入web-starter,会帮我们内嵌一个tomcat文件
  • 简化配置:约定优于配置的理念
  • 自动配置:SpringMVC、mybatis做整合有自动配置非常高效地完成整合
  • 无代码生成和xml配置
  • Actuator应用监控:健康检查、预警、每个节点的一些资源情况

SpringBoot的核心注解

@SpringBootApplication使用java config来配置,java config使用注解和java代码的方式代替xml配置文件

标注了@Configuration的java类都是一个javaconfig配置类

标注了@Bean的方法,其返回值将作为一个bean定义注册到spring的IOC容器

@SpringBootAppliaction注解是一个组合注解,其中有四个元注解

@ComponentScan:默认情况下的会扫描当前包及其子包下面所有被@Component注解修饰的java类

@SpringBootAppliaction:组合了@Configuration注解,也就是说明@SpringBootApplication所标识的java类就是一个java配置类

@EnableAutoConfiguration:打开自动装配的功能,在META-INF/springfactories文件中加载需要自动注入的java类

SpringBoot的自动装配原理


Author: baiwenhui
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source baiwenhui !
  TOC