Spring面试点总结

一、Bean

Spring 3 中为 Bean 定义了 5 种作用域,分别为

singleton(单例):全局唯一。线程不安全,默认类型。bean是无状态时,线程安全。

如何避免线程不安全?

    Bean中避免定义可变的成员变量;

   类中定义ThreadLocal,将可变的成员变量放到threadLocal中。)

prototype(原型):每次使用创建。通过Spring容器获取bean时,新建一个。线程安全;适合有状态的bean使用.

有状态会话bean/有状态:一个对像处于不同的状态,调用相同的方法时产生不同的行为

e.g. 每个用户有自己特有的一个实例,在用户的生存期内,bean保持了用户的信息;即有数据存储功能。

数据库链接

request:一次http请求创建一个,请求结束实例销毁。

session :一次http session中,容器返回bean的同一个实例。仅在当前session内有效。,请求结束实例销毁。

session vs request

  session可包含多个request请求

  http无状态协议,为实现会话跟踪,服务端使用session保存上下文,对每个用户创建一个sessionId,区分不同用户,并把ID传回给cookie。当下次访问时带着sessionID,服务端校验是哪一个用户。Cookie可以保存到硬盘上,即使关闭浏览器也不影响,除非删除,当sessionID过期时服务端删除session,下次重新创建。

global session:一个全局的http session中,容器会返回统一实例。

仅使用 portlet context 时有效。portlet:java web组件

二、IOC

2.1 IOC:控制反转(Inversion of Control)

指的是对象的创建和生命周期的管理,全部托管给Spring容器,而传统对象的创建都是通过业务方使用关键字new或反射来创建的;控制反转是把控制权从业务方交给了Spring容器,这样做的最大好处就是实现解耦和面向接口编程。

Spring提供IOC两种实现方式:BeanFactory和ApplicationContext接口。

 BeanFactory:IOC容器基本实现,是Spring里面一个内部使用的接口,不提供给开发人员使用。(加载配置文件的时候不会去创建对象,在获取对象(使用)的时候才会创建对象)。

 ApplicationContext:BeanFactory接口的子接口,提供更多更强大的功能,一般是由开发人员使用的。(在加载配置文件时就会创建对象)

2.2 DI:依赖注入(dependcy Injection)

指的是获得依赖对象的过程由自身管理变为由IOC容器主动注入,就是由IOC容器在运行期间,动态的将某种依赖关系注入到对象中。

bean对象需要依赖其他组件或对象,依赖关系由容器运行时确定。  两种方式注入:基于配置文件, 基于注解

 Spring如何解决循环依赖?

使用三级缓存,通过构造方法创建半成品解决

  •  一级 singletonObjects,即存放最终bean
  •  二级 earlySingeltonObjects
  •  三级 singletonFactory

 如果构造方法中依赖实例对象,则无法解决;

不支持prototype(原型)bean注入循环依赖,因为Spring容器只有在需要时才会初始化prototype

参考:一文告诉你Spring是如何利用"三级缓存"巧妙解决Bean的循环依赖问题的【享学Spring】-腾讯云开发者社区-腾讯云

Autowired注解与Resource注解的区别

这两个注解的作用都一样,都是在做bean的注入,在使用过程中,两个注解有时候可以替换使用。

相同点:

  • 1.@Resource注解和@Autowired注解都可以用作bean的注入;
  • 2.在接口只有一个实现类的时候,两个注解可以互相替换,效果相同

不同点:

  • @Resource注解是Java自身的注解;@Autowired注解是Spring的注解;
  • @Resource注解有两个重要的属性,分别是name和type,如果name属性有值,则使用byName的自动注入策略,将值作为需要注入bean的名字;如果type有值,则使用byType自动注入策略,将值作为需要注入bean的类型。如果既不指定name也不指定type属性,这时将通过反射机制使用byName自动注入策略,即@Resource注解默认按照名称进行匹配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名,按照名称查找,当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
  • @Autowired注解是spring的注解,此注解只根据type进行注入,不会去匹配name。但是如果只根据type无法辨别注入对象时,就需要配合使用@Qualifier注解或者@Primary注解使用。
  • 举个栗子:若有一个UserService接口,同时创建了两个此接口的实现类userServiceImpl01和userServiceImpl02,然后用UserController类来测试两个注解的不同用法。那么此时,需要@Autowired注解与@Qualifier注解一起使用,以@Qualifier(“userServiceImpl01”)指定注入的bean的名称;而Resource有name属性(@Resource(name = “userServiceImpl01”)),可以区分要注入哪一个实现类。

三、AOP

AOP 把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事务。AOP 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。

织入(weave):将切面应用到目标对象并导致代理对象创建的过程

引入(introduction):在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段

3.1 Spring AOP vs AspectJ AOP

AspectJ 是一个基于 Java 语言的 AOP 框架,提供了强大的 AOP 功能,AspectJ 中的很多语法结构基本上已成为 AOP 领域的标准。

Spring AOP 是Spring实现的AOP框架,采用动态代理的模式进行切面的织入,目前Spring 同时支持原生的Spring AOP与AspectJ的AOP

Spring AOPAspectJ AOP
简介通过Spring IoC提供简单的AOP实现最原始,最完善,更健壮,更复杂
织入方式

基于动态代理的AOP。要实现目标对象的切面,就要创建目标对象的代理类。

2种实现方式:

JDK动态代理——实现相应的接口,底层基于反射

CGLib代理 ——未实现接口的使用该方式

运行时织入

三种类型织入:

1.编译期:切面和目标类放一起用ajc编译

2.编译后:目标类/切面已经打成jar包,此时仍可用ajc命令对jar包再一次编织

3.类加载时:jvm加载目标类时,做字节码替换

使用方法无须额外配置,Spring自带功能需要使用AJC编译器进行编译
性能

AspectJ一般采用编译期织入,而Spring AOP一般采用类加载期使用动态代理织入,由于切面代码执行的逻辑是一样的,因此少量切面的时间开销会很接近,但考虑到动态代理产生的开销,一般认为AspectJ的性能要优于Spring AOP。

 有人做过相关的性能分析,大家可以参考一下AOP 底层技术性能测试与比较

注意:

1). AspectJ compiler(AJC) 编译器不支持lombok 注解

2). Spring AOP采用的是动态代理模式,因此仅支持Public方法的切面,私有方法的aop请使用AspectJ

3.2 AOP两种代理方式

Spring提供了两种方式来生成代理对象: JDKProxy和Cglib,具体使用哪种方式生成由AopProxyFactory根据AdvisedSupport对象的配置来决定。默认的策略是如果目标类是接口,则使用JDK动态代理技术,否则使用Cglib来生成代理。

3.2.1 JDK动态接口代理

JDK 动态代理主要涉及到 java.lang.reflect 包中的两个类:Proxy 和 InvocationHandler。InvocationHandler是一个接口,通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编制在一起。Proxy 利用 InvocationHandler 动态创建一个符合某一接口的实例,生成目标类的代理对象.

3.2.2  CGLib 动态代理

CGLib 全称为 Code Generation Library,是一个强大的高性能,高质量的代码生成类库,可以在运行期扩展 Java 类与实现 Java 接口,CGLib 封装了 asm,可以再运行期动态生成新的 class。和 JDK 动态代理相比较:JDK 创建代理有一个限制,就是只能为接口创建代理实例,而对于没有通过接口定义业务方法的类,则可以通过 CGLib 创建动态代理。

3.3 多数据源

Spring 提供了两种事务管理方式,编程式事务管理和声明式事务管理,声明式事务管理是通过 AOP 实现的,代码侵入性最小,使用 @Transactional 声明。

四、Spring事务

4.1 传播机制(7

事务传播行为是为了解决业务层方法之间互相调用的事务问题。当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。

Spring 定义了以下几种事务传播行为:

嵌套事务:外部事务的子事务,外部主事务回滚的话,子事务也会回滚,而内部子事务可以单独回滚而不影响外部主事务和其他子事务。

4.2 事务隔离级别(4种+1默认)

TransactionDefinition.ISOLATION_DEFAULT:

     使用后端数据库默认的隔离级别,MySQL 默认采用的 REPEATABLE_READ 隔离级别。

TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 读为提交

      最低的隔离级别,使用这个隔离级别很少,因为它允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。

TransactionDefinition.ISOLATION_READ_COMMITTED: 读已提交

   允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。

TransactionDefinition.ISOLATION_REPEATABLE_READ:  可重复读

     对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。

TransactionDefinition.ISOLATION_SERIALIZABLE: 序列化

     最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

4.3 @Transactional事务不生效的场景

1).事务方法内部调用同一类内事务方法

  方法拥有事务能力是因为 Spring AOP 生成代理了对象,但是这种方法直接调用了 this 对象的方法,所以内部方法不会生成事务。

2).非public方法

Spring 要求被代理方法必须是 public 的。@Transactional修饰的方法必需是public

3). 方法被定义成 final 的

 方法被定义成了 final 的,这样会导致 Spring AOP 生成的代理对象不能覆写该方法,而导致事务失效。

4). 当前实体没有被 Spring 管理

5).错误的Spring事务传播特性 @Transactional(propagation = Propagation.NEVER)

  这种类型的传播特性不支持事务,如果有事务则会抛异常

6).数据库不支持事务

  mysql的myISAM引擎不支持事务,使用myISAM引擎的相关表事务失效。

7).自己吞掉异常

  @Transactional修饰的方法体里,使用try catch捕获异常,没有再抛出,则事务的 AOP 无法捕获异常,事务也不会回滚。

8).抛出异常不正确

   @Transactional(rollbackFor = Exception.class)

如果没有声明rollbackFor的异常类型,开发人员自己捕获了异常,又抛出了异常 Exception,事务也不会回滚。因为 Spring 事务默认情况下只会回滚 RuntimeException(运行时异常)和 Error(错误),不会回滚 Exception。

9).多线程调用

@Transactional

    public void add(UserModel userModel) throws Exception {

        userMapper.insertUser(userModel);

        new Thread(() -> {

            roleService.doAnotherThing();

        }).start();

}


@Service

public class RoleService {

    @Transactional(propagation = Propagation.NESTED)

    public void doAnotherThing() {

        System.out.println("保存role表数据");

    }

}

Spring 的事务是通过数据库连接来实现的。当前线程中保存了一个 map,key 是数据源,value 是数据库连接:

private static final ThreadLocal<Map<Object, Object>> resources =
 new NamedThreadLocal<>("Transactional resources");

我们说的同一个事务,其实是指同一个数据库连接,只有拥有同一个数据库连接才能同时提交和回滚。如果在不同的线程,拿到的数据库连接是不一样的,所以是不同的事务。因此当doAnotherThing发生异常回滚时,不会引起add()方法回滚。

10). 嵌套事务,如何只回滚一部分

   在代码中手动把内部嵌套事务放在 try/catch 中,并且不继续往外抛出异常

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值