Java 面试题超详细整理《Spring 篇》,Tencent 后台开发 Java 岗二面
RequestMapping 是一个用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。
RequestMapping 注解有六个属性:
value: 指定请求的实际地址,指定的地址可以是 URI Template 模式;
method: 指定请求的 method 类型, GET、POST、PUT、DELETE 等;
consumes: 指定处理请求的提交内容类型(Content-Type),例如 application/json,text/html;
produces: 指定返回的内容类型,仅当 request 请求头中的(Accept)类型中包含该指定类型才返回;
params: 指定 request 中必须包含某些参数值是,才让该方法处理。
headers: 指定 request 中必须包含某些指定的 header 值,才能让该方法处理请求
作用对象不同: @Component 注解作用于类,而 @Bean 注解作用于方法。
@Component 通常是通过类路径扫描来自动侦测以及自动装配到 Spring 容器中(我们可以使用 @ComponentScan 注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。@Bean 注解通常是我们在标有该注解的方法中定义产生这个 bean,@Bean 告诉了 Spring 这是某个类的示例,当我需要用它的时候还给我。
@Bean 注解比 Component 注解的自定义性更强,而且很多地方我们只能通过 @Bean 注解来注册 bean。比如当我们引用第三方库中的类需要装配到 Spring 容器时,则只能通过 @Bean 来实现。
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
//上面的代码相当于下面的 xml 配置
<beans>
<bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
@PathVariable 和 @RequestParam 的区别
请求路径上有个 id 的变量值,可以通过 @PathVariable 来获取
@RequestMapping(value =“/page/{id}”, method = RequestMethod.GET)
@RequestParam 用来获得静态的 URL 请求入参 spring 注解时 action 里用到。
@Autowired 可用于:构造函数、成员变量、Setter 方法,@Autowired 默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置它 required 属性为 false)。
@Resource 默认是按照名称来装配注入的,只有当找不到与名称匹配的 bean 才会按照类型来装配注入。
Bean Factory 和 Application Context 有什么区别?
Applicationcontext 是 Beanfactory 的子接口,ApplicationContext 提供了更完整的功能:
①继承 Messagesource,因此支持国际化
②统一的资源文件访问方式。
③提供在监听器中注册 bean 的事件。
④同时加载多个配置文件。
⑤载入多个(有继承关系)上下文,使得每个上下文都专注于一个特定的层次,比如应用的 web 层。
加载方式:
Bean Factroy 采用的是延迟加载形式来注入 Bean 的,即只有在使用到某个 Bean 时调用 getbean(),才对该 Bean 进行加载实例化。这样,我们就不能发现一些存在的 Spring 的配置问题。如果 Bean 的某一个属性没有注入, Beanfacotry 加载后,直至第一次使用调用 getbean 方法才会抛出异常。
Applicationcontext,它是在容器启动时,一次性创建了所有的 Bean。这样,在容器启动时,我们就可以发现 spring 中存在的配置错误,这样有利于检查所依赖属性是否注入。 Applicationcontext 启动后预载入所有的单实例 Bean,通过预载入单实例 bean,确保当你需要的时候,不需要等待,直接使用。
创建方式:
Bean Factory 通常以编程的方式被创建, Applicationcontext 还能以声明的方式创建,如使用 ContextLoader。
注册方式:
Bean Factory 和 Application Context,都支持 Bean Postprocessor、 Beanfactory Postprocessor 的使用,但两者之间的区别是: Beanfactory 需要手动注册,而 Applicationcontext 则是自动注册。
相对于基本的 Beanfactory, Applicationcontext 唯的不足是占用内存空间。当应用程序配置 Bean 较多时,程序启动较慢。
FileSystemXmlApplicationContext :此容器从一个 XML 文件中加载 beans 的定义,XML Bean 配置文件的全路径名必须提供给它的构造函数。
ClassPathXmlApplicationContext:此容器也从一个 XML 文件中加载 beans 的定义,这里,你需要正确设置 classpath 因为这个容器将在 classpath 里找 bean 配置。
**WebXmlApplicationContext:此容器加
载一个 XML 文件,此文件定义了一个 WEB 应用的所有 bean**
解析类-》加载对象-》依赖注入-》回调 Aware 方法-》后置处理器处理(初始化及初始化前后)-》使用-》销毁
Bean 容器解析类得到配置文件中 Spring Bean 的定义(BeanDefinition)。(解析)
Bean 容器利用 Java Reflection API 创建一个 Bean 的实例。(加载对象)
如果涉及到一些属性值(加了 @Autowride 注解的属性)利用 set()方法给对象设置属性。(依赖注入)
回调 Aware 方法:如果实现了其他
*.Aware
接口,就调用相应的方法。(回调 Aware 方法)
①如果 Bean 实现了 BeanNameAware 接口,调用 setBeanName()方法,传入 Bean 的名字。
②如果 Bean 实现了 BeanClassLoaderAware 接口,调用 setBeanClassLoader()方法,传入 ClassLoader 对象的实例。
如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行 postProcessBeforeInitialization() 方法。(后置处理器初始化前方法)
如果 Bean 实现了 InitializingBean 接口,执行 afterPropertiesSet()方法。(初始化方法)
如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。
如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行 postProcessAfterInitialization() 方法。(后置处理器初始化后方法,会进行 AOP)
当要销毁 Bean 的时候,如果 Bean 实现了 DisposableBean 接口,执行 destroy() 方法。
当要销毁 Bean 的时候,如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的方法。
图示:
后置处理器 BeanPostProcessor :在 Bean 对象在实例化和依赖注入完毕后,在显示调用初始化方法的前后添加我们自己的逻辑。注意是 Bean 实例化完毕后及依赖注入完成后触发的。后置处理器分为 Bean Factory 后置处理器(视线了 Spring 的扫描)和 Bean 后置处理器(实现了 @ Autowired 注解的属性自动赋值、AOP)
bean 标签有两个重要的属性(init-method 和 destroy-method)。用它们你可以自己定制初始化和注销方法。它们也有相应的注解
singleton(单例模式,默认) : 每个容器中只有一个 bean 的实例,单例的模式由 Beanfactory 自身来维护。该对象的生命周期是与 Spring IOC 容器一致的(但在第一次被注入时才会创建)。
prototype (原型模式): 每次请求都会创建一个新的 bean 实例。在每次注入时都会创建个新的对象
request:每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP request 内有效。
session:每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP session 内有效。
application:bean 被定义为在 Servletcontext 的生命周期中复用一个单例对象(可以实现跨容器)
global-session: 全局 session 作用域,仅仅在基于 portlet 的 web 应用中才有意义,Spring5 已经没有了。Portlet 是能够生成语义代码片段的小型 Java Web 插件。它们基于 portlet 容器,可以像 servlet 一样处理 HTTP 请求。但是,与 servlet 不同,每个 portlet 都有不同的会话。
存在安全问题的。Spring 中的 bean 默认是单例模式的,框架并没有对 bean 进行多线程的封装处理,当多个线程操作同一个对象的时候,对这个对象的成员变量的写操作会存在线程安全问题。
但是大多数 bean 都是无状态的(不具有数据存储功能),比如说 controller、 service 和 dao 层,我们一般只是调用里面的方法,多线程调用一个实例的方法,会在内存中复制变量,这是自己的线程的工作内存,是安全的。但是 dao 层会操作数据库 Connection, Connection 是带有状态的,比如说数据库事务, Spring 的事务管理器,需要我们进行处理。
因此,不要在 bean 中声明任何有状态的实例变量或类变量,如果必须如此,可以参考一下方案:
使用 Threadloca 把变量变为线程私有的,比如在 dao 层,可以使用 Threadlocal 为不同线程维护了一套独立的 connection 副本,保证线程之间不会互相影响。
改变 Bean 的作用域为 “prototype”:每次请求都会创建一个新的 bean 实例,自然不会存在线程安全问题。
如果 bean 的实例变量或类变量需要在多个线程之间共享,那么就只能使用 synchronized、OCK、CAS 等这些实现线程同步的方法了。
工厂设计模式: 由一个工厂类根据传入的参数,动态决定创建那个产品类。Spring 使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象。
单例设计模式 : 保证一个类仅有一个实例,并提供一个访问它的全局访问点。Spring 中的 Bean 默认都是单例的。
适配器模式 :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式(HandlerAdapter)适配 Controller,对应的每一种 Controller 都有一种对应的适配器实现类。
代理设计模式 : Spring AOP 功能的实现。AOP 把切面应用到对象并创建新的代理对象。
观察者模式:Spring 的事件驱动模型使用的是观察着模式,Spring 中 Observer 模式常用的地方是 Listener 的实现。
策略模式:Spring 框架的资源访问 Resource 接口。该接口提供了更强的资源访问能力,Spring 框架本身大量使用了 Resource 的接口来访问底层资源。
装饰器模式:动态地给一个对象添加额外的职责。Spring 中类名包括 Wrapper 或 Decorator 使用了装饰器模式。
模板方法模式 : Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
Spring 管理事务的方式有几种?
编程式事务,在代码中硬编码。(不推荐使用)
声明式事务,在配置文件中配置(推荐使用)
声明式事务又分为:
基于 XML 的声明式事务
基于注解的声明式事务
说一下 Spring 的事务机制
spring 事务底层是基于数据库事务和 AOP 机制的(事务这个概念是数据库层面的, Spring 只是基于数据库中的事务进行了扩展)
首先对于使用了 @Transactional 注解的 bean,Spring 会创建一个代理对象作为 Bean
当调用代理对象的方法时,会先判断该方法上是否加了 @ Transactional 注解
如果加了,那么则利用事务管理器创建一个数据库连接,并把事务的自动提交设置为 false,然后再去执行原本的业务逻辑方法(sql)。
如果执行业务逻辑方法没有出现异常,那么代理逻辑中就会将事务进行提交
如果执行业务逻辑方法出现了异常,那么则会将事务进行回滚
针对哪些异常回滚事务是可以配置的,可以利用 @Transactional 注解中的 rollbackfor 属性进行配置,默认情况下会对 Runtimeexception 和 Error 进行回滚。
Spring 事务的隔离级别对应的就是数据库的隔离级别
Spring 事务的传播机制是 Spring 事务自己实现的,也是 Spring 事务中最复杂的
Spring 事务的传播机制是基于数据库连接来做的,一个数据库连接一个事务,如果传播机制配置为需要新开个事务,那么实际上就是先建立一个数据库连接,在此新数据库连接上执行 sql
Exception 分为运行时异常 RuntimeException 和非运行时异常。事务管理对于企业应用来说是至关重要的,即使出现异常情况,它也可以保证数据的一致性。
当 @Transactional 注解作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。如果类或者方法加了这个注解,那么这个类里面的方法抛出异常,就会回滚,数据库里面的数据也会回滚。
@Transactional(rollbackFor = Exception.class):
在 @Transactional 注解中如果不配置 rollbackFor 属性,那么事务只会在遇到 RuntimeException 的时候才会回滚,加上 rollbackFor=Exception.class,可以让事务在遇到非运行时异常时也回滚。
spring 事务的原理是 AOP,进行了切面增强,那么失效的根本原因是这个 AOP 不起作用了。常见情况有如下几种:@Transactional 标识的方法只有被代理对象调用时,注解才会生效
发生自调用,类里面使用 this 调用本类的方法,此时这个 this 对象不是代理类,而是 Userservice 对象本身(解决方法很简单,让那个 this 变成 Euserservice 的代理类即可)
方法不是 public 的,Transactional 只能用于 public 的方法上,否则事务不会失效,如果要用在非 public 方法上,可以开启( Aspectj 代理模式)。
异常被吃掉,事务不会回滚或者抛出的异常没有被定义,默认为 RuntimeException。
spring 事务隔离级别就是数据库的隔离级别:外加一个默认级别
isolation_default(默认级别):使用后端数据库默认的隔离级别
read_uncommitted(读未提交读):最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
read_committed(读以提交、不可重复读):允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
repeatable_read(可重复读):对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
serializable(可串行化):最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
数据库的配置隔离级别是 Read Commited,而 Spring 配置的隔离级别是 Repeatable Read,请问这时隔离级别是以哪个为准?
以 Spring 配置的为准,如果 spring 设置的隔离级别数据库不支持,效果取决于数据库
多个事务方法相互调用时事务如何在这些方法间传播:
方法 A 是一个事务的方法,方法 A 执行过程中调用了方法 B,那么方法 B 有无事务以及方法 B 对事务的要求不同都会对方法 A 的事务具体执行造成影响,同时方法 A 的事务对方法 B 的事务执行也有影响,这种影响具体是什么就由两个方法所定义的事务传播类型所决定。
REQUIRED(Spring 默认的事务传播类型):如果当前没有事务,则自己新建一个事务,如果当前存在事务,则加入这个事务
SUPPORTS:当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行
MANDATORY:当前存在事务,则加入当前事务,如果当前事务不存在,则拋出异常
REQUIRES_NEW:创建一个新事务,如果存在当前事务,则挂起该事务
NOT_SUPPORTED:以非事务方式执行如果当前存在事务,则挂起当前事务
NEVER:不使用事务,如果当前事务存在,则抛出异常
NESTED:如果当前事务存在,则在嵌套事务中执行,否则 REQUIRED 的操作一样(开启一个事务)
NESTED 和 REQUIRES_NEW 的区别:
REQUIRES_NEW 是新建一个事务并且新开启的这个事务与原有事务无关,而 NESTED 则是当前存在事务时(我们把当前事务称之为父事务)会开启一个嵌套事务(称之为一个子事务)。在 NESTED 情况下父事务回滚时,子事务也会回滚,而在 REQUIRES_NEW 情况下,原有事务回滚,不会影响新开启的事务
NESTED 和 REQUIRED 的区别:
REQUIRED 情况下,调用方存在事务时,则被调用方和调用方使用同一事务,那么被调用方出现异常时,由于共用一个事务,所以无论调用方是否 catch 其异常,事务都会回滚而在 NESTED 情况下,被调用方发生异常时,调用方可以 catch 其异常,这样只有子事务回滚,父事务不受影响。
在 Spring 框架中,在配置文件中设定了 bean 的依赖关系,Spring 容器能够自动装配相互合作的 bean。这意味着容器不需要配置,能通过 Bean 工厂自动处理 bean 之间的协作,Spring 可以通过向 Bean Factory 中注入的方式自动搞定 bean 之间的依赖关系。自动装配可以设置在每个 bean 上,也可以设定在特定的 bean 上。
使用 @Autowired 注解自动装配的过程:
使用 @Autowired 注解来自动装配指定的 bean。在使用 @Autowired 注解之前需要在 Spring 配置文件进行配置,
<context:annotation-config />
。在启动 spring IoC 时,容器自动装载了一个 AutowiredAnnotationBeanPostProcessor 后置处理器,当容器扫描到 @Autowied、@Resource 或 @Inject 时,就会在 IoC 容器自动查找需要的 bean,并装配给该对象的属性。在使用 @Autowired 时,首先在容器中查询对应类型的 bean:
如果查询结果刚好为一个,就将该 bean 装配给 @Autowired 指定的数据;
如果查询的结果不止一个,那么 @Autowired 会根据名称来查找;
如果上述查找的结果为空,那么会抛出异常。解决方法时,使用 required=false。
在 spring 中,对象无需自己查找或创建与其关联的其他对象,由容器负责把需要相互协作的对象引用赋予各个对象,使用 Autowire 来配置自动装载模式。在 Spring 框架 xml 配置中共有 5 种自动装配:
no:默认的方式是不进行自动装配的,通过手工设置
ref
属性来进行装配 bean。
评论