0%

面试题-Spring

本章属于持续学习、长期更修。

谈谈对Spring理解

  • Spring是一个轻量级的IoC和AOP容器框架。是为Java应用程序提供基础性服务的一套框架,目的是用于简化企业应用程序的开发,它使得开发者只需要关心业务需求。
  • 主要由以下几个模块组成:
    • Spring Core:核心类库,提供IOC服务
    • Spring Context:提供框架式的Bean访问方式,以及企业级功能(JNDI、定时任务等)
    • Spring AOP:AOP服务切面编程
    • Spring DAO:对JDBC的抽象,简化了数据访问异常的处理
    • Spring ORM:对现有的ORM框架的支持
    • Spring Web:提供了基本的面向Web的综合特性,例如多方文件上传
    • Spring MVC:提供面向Web应用的Model-View-Controller实现

谈谈对 IoC 和 DI 的理解

  • IoC(Inversion of Control,翻译为“控制反转”)不是一个具体的技术,而是一种设计思想。与传统控制流相比,IoC 会颠倒控制流,在传统的编程中需要开发者自行创建并销毁对象,而在 IoC 中会把这些操作交给框架来处理,这样开发者就不用关注具体的实现细节了,拿来直接用就可以了,这就是控制反转。
  • IoC 很好的体现出了面向对象的设计法则之一——好莱坞法则:“别找我们,我们找你”。即由 IoC 容器帮对象找到相应的依赖对象并注入,而不是由对象主动去找。
  • DI(Dependency Injection,翻译为“依赖注入”)表示组件间的依赖关系交由容器在运行期自动生成,也就是说,由容器动态的将某个依赖关系注入到组件之中,这样就能提升组件的重用频率。通过依赖注入机制,我们只需要通过简单的配置,就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心资源来自哪里、由谁实现等问题。
  • IoC 和 DI 其实是同一个概念从不同角度的描述的,由于控制反转这个概念比较含糊(可能只理解成了容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以 2004 年被开发者尊称为“教父”的 Martin Fowler(世界顶级专家,敏捷开发方法的创始人之一)又给出了一个新的名字“依赖注入”,相对 IoC 而言,“依赖注入”明确描述了“被注入对象依赖 IoC 容器配置依赖对象”。

Spring 中的单例 bean 的线程安全问题了解吗?

  • 单例 bean 存在线程问题,主要是因为当多个线程操作同⼀个对象的时候,对这个对象的⾮静态成员变量的写操作会存在线程安全问题。在类中定义⼀个ThreadLocal成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的⼀种⽅式)。

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

  • 工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例
  • 单例模式:Bean默认为单例模式
  • 代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术
  • 模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate
  • 观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现–ApplicationListener,Spring 事件驱动模型就是观察者模式很经典的⼀个应⽤。
  • 适配器模式 :Spring AOP 的增强或通知(Advice)使⽤到了适配器模式、spring MVC 中也是⽤到了适配器模式适配 Controller 。

Spring中Autowired和Resource关键字的区别?

  • @Resource和@Autowired都是做bean的注入时使用,其实@Resource并不是Spring的注解,它的包是javax.annotation.Resource,需要导入,但是Spring支持该注解的注入。
  • Autowired注解是按照类型(byType)装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它的required属性为false。如果我们想使用按照名称(byName)来装配,可以结合@Qualififier注解一起使用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class TestServiceImpl { 
// 下面两种@Autowired只要使用一种即可
@Autowired
private UserDao userDao; // 用于字段上

@Autowired // 用于属性的方法上
public void setUserDao(UserDao userDao)
{
this.userDao = userDao;
}
}
// @Autowired注解是按照类型(byType)装配依赖对象,默认情况下它要求依赖对象必须存在,如果允
// 许null值,可以设置它的required属性为false。如果我们想使用按照名称(byName)来装配,可以结
// 合@Qualifier注解一起使用。如下:
public class TestServiceImpl {
@Autowired
@Qualifier("userDao") private UserDao userDao;
}
  • Resource默认按照ByName自动注入,由J2EE提供,需要导入包javax.annotation.Resource。@Resource有两个重要的属性:name和type,而Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以,如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不制定name也不制定type属性,这时将通过反射机制使用byName自动注入策略。
1
2
3
4
5
6
7
8
9
10
11
12
13
14

public class TestServiceImpl {
// 下面两种@Resource只要使用一种即可

// 用于字段上
@Resource(name="userDao")
private UserDao userDao;

// 用于属性的setter方法上
@Resource(name="userDao")
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}

Bean注册方式有几种?

  • XML 配置文件的注册方式
1
2
3
4
<bean id="person" class="org.springframework.beans.Person">
   <property name="id" value="1"/>
   <property name="name" value="Java"/>
</bean>
  • Java 注解的注册方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 可以使用 @Component 注解方式来注册 Bean
@Component
public class Person {
   private Integer id;
   private String name
   // 忽略其他方法
}

// @Bean 注解方式来注册 Bean
@Configuration
public class Person {
   @Bean
   public Person  person(){
      return new Person();
   }
   // 忽略其他方法
}
  • Java API 的注册方式
1
2
3
4
5
6
7
8
9
10
11
12
// 使用 BeanDefinitionRegistry.registerBeanDefinition() 方法的方式注册 Bean	
public class CustomBeanDefinitionRegistry implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
RootBeanDefinition personBean = new RootBeanDefinition(Person.class);
// 新增 Bean
registry.registerBeanDefinition("person", personBean);
}
}

Spring Bean 的作用域有哪些?

  • singleton
    • 表示在 Spring 容器中只有一个 Bean 实例,以单例的形式存在,是默认的 Bean 作用域。
  • prototype
    • 原型作用域,每次调用 Bean 时都会创建一个新实例,也就是说每次调用 getBean() 方法时,相当于执行了 new Bean()。
  • request
    • 每次 Http 请求时都会创建一个新的 Bean,该作用域仅适应于 WebApplicationContext 环境。
  • session
    • 同一个 Http Session 共享一个 Bean 对象,不同的 Session 拥有不同的 Bean 对象,仅适用于 WebApplicationContext 环境。
  • application
    • 全局的 Web 作用域,类似于 Servlet 中的 Application。

Bean 生命周期

  • 对于 Spring Bean 来说,并不是启动阶段就会触发 Bean 的实例化,只有当客户端通过显式或者隐式的方式调用 BeanFactory 的 getBean() 方法时,它才会触发该类的实例化方法。当然对于 BeanFactory 来说,也不是所有的 getBean() 方法都会实例化 Bean 对象,例如作用域为 singleton 时,只会在第一次,实例化该 Bean 对象,之后会直接返回该对象。但如果使用的是ApplicationContext 容器,则会在该容器启动的时候,立即调用注册到该容器所有 Bean 的实例化方法。getBean() 方法是属于 BeanFactory 接口的,它的真正实现是AbstractAutowireCapableBeanFactory 的 createBean() 方法,而 createBean() 是通过 doCreateBean() 来实现的,具体源码实现。

  • 实例化Bean
    • 对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。对于ApplicationContext容器,当容器启动结束后,通过获取BeanDefifinition对象中的信息,实例化所有的bean。
  • 设置对象属性(依赖注入)
    • 实例化后的对象被封装在BeanWrapper对象中,紧接着,Spring根据BeanDefifinition中的信息 以及 通过BeanWrapper提供的设置属性的接口完成依赖注入。
  • 处理Aware接口
    • 接着,Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给Bean。
      • 如果这个Bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,此处传递的就是Spring配置文件中Bean的id值。
      • 如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现的setBeanFactory()方法,传递的是Spring工厂自身。
      • 如果这个Bean已经实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文。
  • BeanPostProcessor
    • 如果想对Bean进行一些自定义的处理,那么可以让Bean实现了BeanPostProcessor接口,那将会调用postProcessBeforeInitialization(Object obj, String s)方法。
  • InitializingBean 与 init-method
    • 如果实现相关方法或者配置了 init-method则会自动调用其配置的初始化方法
  • BeanPostProcessor
    • 如果这个Bean实现了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法;由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术。
  • getBean
    • 可以正常使用这个bean了。
  • DisposableBean
    • 当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用其实现的destroy()方法。
  • destroy-method
    • 如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。

Spring中Filter和Interceptor的区别

  • Filter是基于函数回调(doFilter()方法)的,而Interceptor则是基于Java反射的(AOP思想)。
  • Filter依赖于Servlet容器,而Interceptor不依赖于Servlet容器。
  • Filter对几乎所有的请求起作用,而Interceptor只能对action请求起作用。
  • Interceptor可以访问Action的上下文,值栈里的对象,而Filter不能。
  • 在action的生命周期里,Interceptor可以被多次调用,而Filter只能在容器初始化时调用一次。
  • Filter在过滤是只能对request和response进行操作,而interceptor可以对request、response、handler、modelAndView、exception进行操作。
  • interceptor 的执行顺序大致为:
    • 请求到达 DispatcherServlet
    • DispatcherServlet 发送至 Interceptor ,执行 preHandle
    • 请求达到 Controller
    • 请求结束后,postHandle 执行
  • Spring 中主要通过 HandlerInterceptor 接口来实现请求的拦截,实现 HandlerInterceptor 接口需要实现下面三个方法:
    • preHandle() – 在handler执行之前,返回 boolean 值,true 表示继续执行,false 为停止执行并返回。
    • postHandle() – 在handler执行之后, 可以在返回之前对返回的结果进行修改
    • afterCompletion() – 在请求完全结束后调用,可以用来统计请求耗时等等
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

public class ExecuteTimeInterceptor extends HandlerInterceptorAdapter{

private static final Logger logger = Logger.getLogger(ExecuteTimeInterceptor.class);

//before the actual handler will be executed
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler)
throws Exception {

long startTime = System.currentTimeMillis();
request.setAttribute("startTime", startTime);

return true;
}

//after the handler is executed
public void postHandle(
HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView)
throws Exception {

long startTime = (Long)request.getAttribute("startTime");

long endTime = System.currentTimeMillis();

long executeTime = endTime - startTime;

//modified the exisitng modelAndView
modelAndView.addObject("executeTime",executeTime);

//log it
if(logger.isDebugEnabled()){
logger.debug("[" + handler + "] executeTime : " + executeTime + "ms");
}
}
}
  • Servlet 的 Filter 接口需要实现如下方法:
    • void init(FilterConfig paramFilterConfig) – 当容器初始化 Filter 时调用,该方法在 Filter 的生命周期只会被调用一次,一般在该方法中初始化一些资源,FilterConfig 是容器提供给 Filter 的初始化参数,在该方法中可以抛出 ServletException 。init 方法必须执行成功,否则 Filter 可能不起作用,出现以下两种情况时,web 容器中 Filter 可能无效: 1)抛出 ServletException 2)超过 web 容器定义的执行时间。
    • doFilter(ServletRequest paramServletRequest, ServletResponse paramServletResponse, FilterChain paramFilterChain) – Web 容器每一次请求都会调用该方法。该方法将容器的请求和响应作为参数传递进来, FilterChain 用来调用下一个 Filter。
    • void destroy() – 当容器销毁 Filter 实例时调用该方法,可以在方法中销毁资源,该方法在 Filter 的生命周期只会被调用一次。

Spring 事务

  • 编程式事务 代码中硬编码开启事物。
  • 声明式事务
    • 基于注解开启事物。
    • 基于xml配置文件开启事物。

Spring 事务中的隔离级别有哪⼏种?

TransactionDefinition 接⼝中定义了五个表示隔离级别的常量:

  • TransactionDefinition.ISOLATION_DEFAULT: 使⽤后端数据库默认的隔离级别,Mysql 默认采⽤的 REPEATABLE_READ隔离级别 Oracle 默认采⽤的 READ_COMMITTED隔离级别。

  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔离级别,允许读取尚未提交的阻⽌脏读,但是幻读或不可重复读仍有可能发⽣。

  • TransactionDefinition.ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据,可以阻⽌脏读,但是幻读或不可重复读仍有可能发⽣。

  • TransactionDefinition.ISOLATION_REPEATABLE_READ: 对同⼀字段的多次读取结果都是⼀致的,除⾮数据是被本身事务⾃⼰所修改,可以阻⽌脏读和不可重复读,但幻读仍有可能发⽣。

  • TransactionDefinition.ISOLATION_SERIALIZABLE: 最⾼的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执⾏,这样事务之间就完全不可能产⽣⼲扰,也就是说,该级别可以防⽌脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会⽤到该级别。

Spring 事务中哪⼏种事务传播⾏为?

  • ⽀持当前事务的情况:
    • TransactionDefinition.PROPAGATION_REQUIRED: 如果当前存在事务,则加⼊该事务;如果当前没有事务,则创建⼀个新的事务。
    • TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加⼊该事务;如果当前没有事务,则以⾮事务的⽅式继续运⾏。
    • TransactionDefinition.PROPAGATION_MANDATORY: 如果当前存在事务,则加⼊该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)
  • 不⽀持当前事务的情况:
    • TransactionDefinition.PROPAGATION_REQUIRES_NEW: 创建⼀个新的事务,如果当前存在事务,则把当前事务挂起。
    • TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以⾮事务⽅式运⾏,如果当前存在事务,则把当前事务挂起。
    • TransactionDefinition.PROPAGATION_NEVER: 以⾮事务⽅式运⾏,如果当前存在事务,则抛出异常。
  • 其他情况:
    • TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来运⾏;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。

Spring Boot 启动流程

Spring Boot 程序的入口是 SpringApplication.run(Application.class, args) 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public ConfigurableApplicationContext run(String... args) {
// 1.创建并启动计时监控类
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 2.声明应用上下文对象和异常报告集合
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
// 3.设置系统属性 headless 的值
this.configureHeadlessProperty();
// 4.创建所有 Spring 运行监听器并发布应用启动事件
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();
Collection exceptionReporters;
try {
// 5.处理 args 参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 6.准备环境
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
this.configureIgnoreBeanInfo(environment);
// 7.创建 Banner 的打印类
Banner printedBanner = this.printBanner(environment);
// 8.创建应用上下文
context = this.createApplicationContext();
// 9.实例化异常报告器
exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
// 10.准备应用上下文
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 11.刷新应用上下文
this.refreshContext(context);
// 12.应用上下文刷新之后的事件的处理
this.afterRefresh(context, applicationArguments);
// 13.停止计时监控类
stopWatch.stop();
// 14.输出日志记录执行主类名、时间信息
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
// 15.发布应用上下文启动完成事件
listeners.started(context);
// 16.执行所有 Runner 运行器
this.callRunners(context, applicationArguments);
} catch (Throwable var10) {
this.handleRunFailure(context, var10, exceptionReporters, listeners);
throw new IllegalStateException(var10);
}
try {
// 17.发布应用上下文就绪事件
listeners.running(context);
// 18.返回应用上下文对象
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}
  • Spring Boot 的启动流程

    • 1、创建并启动计时监控类

      • 此计时器是为了监控并记录 Spring Boot 应用启动的时间的,它会记录当前任务的名称,然后开启计时器。
    • 2.声明应用上下文对象和异常报告集合

      • 此过程声明了应用上下文对象和一个异常报告的 ArrayList 集合。
    • 3.设置系统属性 headless 的值

      • 设置 Java.awt.headless = true,其中 awt(Abstract Window Toolkit)的含义是抽象窗口工具集。设置为 true 表示运行一个 headless 服务器,可以用它来作一些简单的图像处理。
    • 4.创建所有 Spring 运行监听器并发布应用启动事件

      • 此过程用于获取配置的监听器名称并实例化所有的类。
    • 5.初始化默认应用的参数类

      • 也就是说声明并创建一个应用参数对象。
    • 6.准备环境

      • 创建配置并且绑定环境(通过 property sources 和 profiles 等配置文件)。
    • 7.创建 Banner 的打印类

      • 就是出个那个logo也可以自己定义。
    • 8.创建应用上下文

      • 根据不同的应用类型来创建不同的 ApplicationContext 上下文对象。
    • 9.实例化异常报告器

      • 它调用的是 getSpringFactoriesInstances() 方法来获取配置异常类的名称,并实例化所有的异常处理类。
    • 10.准备应用上下文

      • 此方法的主要作用是把上面已经创建好的对象,传递给 prepareContext 来准备上下文,例如将环境变量 environment 对象绑定到上下文中、配置 bean 生成器以及资源加载器、记录启动日志等操作。
    • 11.刷新应用上下文

      • 此方法用于解析配置文件,加载 bean 对象,并且启动内置的 web 容器等操作。
    • 12.应用上下文刷新之后的事件处理

      • 这个方法的源码是空的,可以做一些自定义的后置处理操作。
    • 13.停止计时监控类

      • 停止此过程第一步中的程序计时器,并统计任务的执行信息。
    • 14.输出日志信息

      • 把相关的记录信息,如类名、时间等信息进行控制台输出。
    • 15.发布应用上下文启动完成事件

      • 触发所有 SpringApplicationRunListener 监听器的 started 事件方法。
    • 16.执行所有 Runner 运行器

      • 执行所有的 ApplicationRunner 和 CommandLineRunner 运行器。
    • 17.发布应用上下文就绪事件

      • 触发所有的 SpringApplicationRunListener 监听器的 running 事件。
    • 18.返回应用上下文对象

      • 到此为止 Spring Boot 的启动程序就结束了。

spring中获取代理对象

spring中获取代理对象的三种方式:

  1. 直接autowire基于接口注入。
  2. @Autowire applicationcontext,通过applicationcontext.getbean获取代理对象。
  3. 通过aopcontext.currentproxy方法获取(注意:需要启动类上增加注解@enableaspectjautoproxy(exposeproxy=true));
  • 如果spring启动时循环依赖导致报错,可以通过依赖注入属性增加@lazy注解解决。