面试题-8Spring

面试背会

Ioc 通过map来存对象

控制反转,控制是指对对象创建的权利,反转是将控制权交给容器进行管理。 传统开发都是通过new
关键字来创建对象的,但是使用了ioc容器后,就可以 用它来帮我们实例化对象
Ioc帮助我们解决了对象之间的耦合度或者说是依赖程度降低,资源变得容易管理
,比如现在有一个针对user的操作,利用service和dao两层结构去开发,没用ioc 之前,需要new
关键字在userServiceImpl 中手动new出userDao的具体实现类UserDaoImpl
这种方式实现起来很容易,但是,如果开发过程中突然针对userdao 接口开发另一个实现类 ,因为service
层依赖了userdao的具体实现,所以我们需要修改userserviceimpl
中new的对象,如果只有一个还好,可以直接修改,但是多了,就非常难受 通过ioc就可以轻易解决,因为ioc底层是通过
xml+反射+工厂解耦来实现的,
具体来说,先通过xml解析获取对象的属性值,然后class.forname()将属性值放入得到具体的class类,最后通过class.newInstance()得到具体的对象。

Aop

面向切面编程,在不改变原来的业务逻辑的情况下,增强横切逻辑代码,从根本上解耦,避免代码的重复,aop的实现方式有三种,使用原生的springapi接口,自定义切面类,还有就是用注解的方式去实现,用的最多的也就是用注解去实现,首先,我们创建一个类,里面写上增强方法,before
after around等,然后在类上添加注解@aspact,表明这个类是配置类,然后里面的增强方法加@before @after等注解。
Aop底层是基于动态代理实现的,动态代理有两种,jdk和cglib,
Jdk的代理是基于接口实现的,核心接口是invocationHandler接口和proxy类,invocationHandler接口通过invoke()方法反射来调用目标类的代码,动态的将增强代码和业务逻辑编织在一起,然后proxy利用invocationHandler动态的创建一个符合某一接口的实例生成目标类的代理对象。
如果代理类没有实现invocationHandler接口,那么aop会选择使用cglib来动态代理目标类。
Cglib是通过继承目标对象,通过重写目标方法的方式进行方法的增强,实现动态代理。 首先先床架增强类
enhancer,然后设置父类,也就是目标类,serSuprerClass(targe.class),
通过setCallBack来设置回调,然后里面进行对目标对象的增强,最后通过enhancer.creat()得到代理类。

什么是 IoC

IoC (Inversion of control )控制反转/反转控制。它是一种思想不是一个技术实现。描述的是:Java
开发领域对象的创建以及管理的问题。

例如:现有类 A 依赖于类 B

传统的开发方式 :往往是在类 A 中手动通过 new 关键字来 new 一个 B 的对象出来 使用 IoC 思想的开发方式 :不通过 new
关键字来创建对象,而是通过 IoC 容器(Spring 框架) 来帮助我们实例化对象。我们需要哪个对象,直接从 IoC 容器里面过去即可。
从以上两种开发方式的对比来看:我们 “丧失了一个权力”
(创建、管理对象的权力),从而也得到了一个好处(不用再考虑对象的创建、管理等一系列的事情)

为什么叫控制反转

控制 :指的是对象创建(实例化、管理)的权力

反转 :控制权交给外部环境(Spring 框架、IoC 容器)

在这里插入图片描述

IoC 解决了什么问题

对象之间的耦合度或者说依赖程度降低; 资源变的容易管理;比如你用 Spring 容器提供的话很容易就可以实现一个单例。 例如:现有一个针对
User 的操作,利用 Service 和 Dao 两层结构进行开发

在没有使用 IoC 思想的情况下,Service 层想要使用 Dao 层的具体实现的话,需要通过 new
关键字在UserServiceImpl 中手动 new 出 IUserDao 的具体实现类 UserDaoImpl(不能直接 new
接口类)。

在这里插入图片描述

很完美,这种方式也是可以实现的,但是我们想象一下如下场景:

开发过程中突然接到一个新的需求,针对对IUserDao 接口开发出另一个具体实现类。因为 Server
层依赖了IUserDao的具体实现,所以我们需要修改UserServiceImpl中 new
的对象。如果只有一个类引用了IUserDao的具体实现,可能觉得还好,修改起来也不是很费力气,但是如果有许许多多的地方都引用了IUserDao的具体实现的话,一旦需要更换IUserDao
的实现方式,那修改起来将会非常的头疼。

在这里插入图片描述

使用 IoC 的思想,我们将对象的控制权(创建、管理)交有 IoC 容器去管理,我们在使用的时候直接向 IoC 容器 “要” 就可以了

在这里插入图片描述

IoC 和 DI 别再傻傻分不清楚
IoC(Inverse of Control:控制反转)是一种设计思想
或者说是某种模式。这个设计思想就是 将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。 IoC 在其他语言中也有应用,并非
Spring 特有。IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个 Map(key,value),Map
中存放的是各种对象。 IoC 最常见以及最合理的实现方式叫做依赖注入(Dependency Injection,简称 DI)。

ioc底层原理
xml 配置 + 反射 + 工厂解耦(IoC底层的实现)

可以看到,在这个工厂类中,并没有直接像上面那样直接new对象,而是使用了xml解析和反射方式创建对象,分析如下:

通过xml解析获取对象中属性的值

通过反射得到字节码文件

通过字节码文件创建对象

这样的话如果我们需要改UserDao的实现类的类型,我们可以直接在配置文件中修改,就不需要修改代码,这就是IoC的解耦。

在这里插入图片描述

什么是 AOP

AOP:Aspect oriented programming 面向切面编程,AOP 是 OOP(面向对象编程)的一种延续。

下面我们先看一个 OOP 的例子。

例如:现有三个类,Horse、Pig、Dog,这三个类中都有 eat 和 run 两个方法。

通过 OOP 思想中的继承,我们可以提取出一个 Animal 的父类,然后将 eat 和 run
方法放入父类中,Horse、Pig、Dog通过继承Animal类即可自动获得 eat() 和 run() 方法。这样将会少些很多重复的代码。

在这里插入图片描述

OOP 编程思想可以解决大部分的代码重复问题。但是有一些问题是处理不了的。比如在父类 Animal
中的多个方法的相同位置出现了重复的代码,OOP 就解决不了。

/**
 * 动物父类
 */
public class Animal {

    /** 身高 */
    private String height;

    /** 体重 */
    private double weight;

    public void eat() {
        // 性能监控代码
        long start = System.currentTimeMillis();

        // 业务逻辑代码
        System.out.println("I can eat...");

        // 性能监控代码
        System.out.println("执行时长:" + (System.currentTimeMillis() - start)/1000f + "s");
    }

    public void run() {
        // 性能监控代码
        long start = System.currentTimeMillis();

        // 业务逻辑代码
        System.out.println("I can run...");

        // 性能监控代码
        System.out.println("执行时长:" + (System.currentTimeMillis() - start)/1000f + "s");
    }
}

这部分重复的代码,一般统称为 横切逻辑代码。

在这里插入图片描述

横切逻辑代码存在的问题:

代码重复问题 横切逻辑代码和业务代码混杂在一起,代码臃肿,不变维护 AOP 就是用来解决这些问题的

AOP 另辟蹊径,提出横向抽取机制,将横切逻辑代码和业务逻辑代码分离

在这里插入图片描述

代码拆分比较容易,难的是如何在不改变原有业务逻辑的情况下,悄无声息的将横向逻辑代码应用到原有的业务逻辑中,达到和原来一样的效果。

AOP 解决了什么问题 通过上面的分析可以发现,AOP
主要用来解决:在不改变原有业务逻辑的情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复。

AOP 为什么叫面向切面编程 切 :指的是横切逻辑,原有业务逻辑代码不动,只能操作横切逻辑代码,所以面向横切逻辑

面 :横切逻辑代码往往要影响的是很多个方法,每个方法如同一个点,多个点构成一个面。这里有一个面的概念

实现aop的三大方式:

使用原生的Spring API接口

它就是先创建一个service接口,然后包含目标对象的功能,然后创建实现类去实现这个接口,其次,创建类去实现各种增强接口,比如前置增强的MethonBeforeAdvice
接口,后置增强的AfterReturningAdvice接口等,它们里面都是对目标方法的增强方法,最后就在xml配置文件中配置aop:config,其中的切入点就是service实现类

自定义切面类

在自定义切面类中,我们可以指定一些自定义的切面方法,before,after,around等
然后在applicationContext.xml中的aop进行配置 他需要标明切面类是哪一个,然后在通知中method里写名增强方法

注解方法实现

首先,我们建立一个类,里面写增强方法,before,after等,然后在类上添加注解 @Aspact
标明当前类是切面类,然后对增强方法进行注解,比如@before @after等,里面的参数就是要增强的类的路径,最后在配置文件中开启注解支持

底层 :有接口 用 jdk 没有 使用cglib

Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理:

①JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler
通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用
InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。
②如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation
Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
(3)静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring
AOP则无需特定的编译器处理。

3.spring事务

事务的执行顺序如下: 1、开启事务
2、设置事务的隔离级别(不设置的话就使用默认隔离级别)
3、执行SQL
4、提交事务

4.并发下事务会产生的问题
1、脏读

所谓脏读,就是指事务A读到了事务B还没有提交的数据,比如银行取钱,事务A开启事务,此时切换到事务B,事务B开启事务–>取走100元,此时切换回事务A,事务A读取的肯定是数据库里面的原始数据,因为事务B取走了100块钱,并没有提交,数据库里面的账务余额肯定还是原始余额,这就是脏读。

2、不可重复读

所谓不可重复读,就是指在一个事务里面读取了两次某个数据,读出来的数据不一致。还是以银行取钱为例,事务A开启事务–>查出银行卡余额为1000元,此时切换到事务B事务B开启事务–>事务B取走100元–>提交,数据库里面余额变为900元,此时切换回事务A,事务A再查一次查出账户余额为900元,这样对事务A而言,在同一个事务内两次读取账户余额数据不一致,这就是不可重复读。

3、幻读

所谓幻读,就是指在一个事务里面的操作中发现了未被操作的数据。比如学生信息,事务A开启事务–>修改所有学生当天签到状况为false,此时切换到事务B,事务B开启事务–>事务B插入了一条学生数据,此时切换回事务A,事务A提交的时候发现了一条自己没有修改过的数据,这就是幻读,就好像发生了幻觉一样。幻读出现的前提是并发的事务中有事务发生了插入、删除操作。

spring事务本质上使用数据库事务,而数据库事务本质上使用数据库锁,所以spring事务本质上使用数据库锁,开启spring事务意味着使用数据库锁;
spring事务只有捕获到异常才会终止或回滚,如果你在程序中try/catch后自己处理异常而没有throw,那么事务将不会终止或回滚,失去事务本来的作用;
spring事务会捕获所有的异常,但只会回滚数据库相关的操作,并且只有在声明了rollbackForClassName="Exception"之类的配置才会回滚;
spring事务会回滚同一事务中的所有数据库操作,本质上是回滚同一数据库连接上的数据库操作;

spring事务为什么不能保证数据一致性和业务逻辑正确性:

1.如果事务方法抛异常,此时会回滚数据库操作,但已经执行的其他方法不会回滚,因此无法保证业务逻辑正确性;
2.即使事务方法不抛异常,也不能保证数据一致性(因为事务接口里的数据库操作在整个接口逻辑执行结束后才提交到数据 库,在接口最后提交到数据库的前后很有可能带来数据一致性的问题),从而不能保证业务逻辑正确性;

隔离级别 隔离级别的值 导致的问题
Read-Uncommitted 0 导致脏读
Read-Committed 1 避免脏读,允许不可重复读和幻读
Repeatable-Read 2 避免脏读,不可重复读,允许幻读
Serializable 3 串行化读,事务只能一个一个执行,避免了脏读、不可重复读、幻读。执行效率慢,使用时慎重

隔离级别 隔离级别的值导致的问题
Read-Uncommitted 0导致脏读
Read-Committed 1避免脏读,允许不可重复读和幻读
Repeatable-Read 2避免脏读,不可重复读,允许幻读
Serializable 3串行化读,事务一个一个执行,避免脏读、不可重复读、幻读。

Spring中的隔离级别

ISOLATION_READ_UNCOMMITTED
这是事务最低的隔离级别,它充许另外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。
ISOLATION_READ_COMMITTED
保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务提交的数据。
ISOLATION_REPEATABLE_READ //可重复读
这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。
ISOLATION_SERIALIZABLE
这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。

5.@Autowired注解自动装配

使用@Autowired注解来自动装配指定的bean。在使用@Autowired注解之前需要在Spring配置文件进行配置,<context:annotation-config
/>。 在启动spring
IoC时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowied、@Resource或@Inject时,就会在IoC容器自动查找需要的bean,并装配给该对象的属性。在使用@Autowired时,首先在容器中查询对应类型的bean:
如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据;
如果查询的结果不止一个,那么@Autowired会根据名称来查找;
如果上述查找的结果为空,那么会抛出异常。解决方法时,使用required=false。

6.sprin自动装配 bean 有哪些方式

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

5、Spring AOP里面的几个名词的概念:

(1)连接点(Join point):指程序运行过程中所执行的方法。在Spring AOP中,一个连接点总代表一个方法的执行。
(2)切面(Aspect):被抽取出来的公共模块,可以用来会横切多个对象。Aspect切面可以看成 Pointcut切点 和
Advice通知 的结合,一个切面可以由多个切点和通知组成。 在Spring AOP中,切面可以在类上使用 @AspectJ 注解来实现。
(3)切点(Pointcut):切点用于定义 要对哪些Join point进行拦截。
切点分为execution方式和annotation方式。execution方式可以用路径表达式指定对哪些方法拦截,比如指定拦截add*、search*。annotation方式可以指定被哪些注解修饰的代码进行拦截。
(4)通知(Advice):指要在连接点(Join
Point)上执行的动作,即增强的逻辑,比如权限校验和、日志记录等。通知有各种类型,包括Around、Before、After、After
returning、After throwing。 (5)目标对象(Target):包含连接点的对象,也称作被通知(Advice)的对象。
由于Spring AOP是通过动态代理实现的,所以这个对象永远是一个代理对象。
(6)织入(Weaving):通过动态代理,在目标对象(Target)的方法(即连接点Join
point)中执行增强逻辑(Advice)的过程。
(7)引入(Introduction):添加额外的方法或者字段到被通知的类。Spring允许引入新的接口(以及对应的实现)到任何被代理的对象。例如,你可以使用一个引入来使bean实现
IsModified 接口,以便简化缓存机制。

6、Spring通知(Advice)有哪些类型?

(1)前置通知(Before Advice):在连接点(Join point)之前执行的通知。 (2)后置通知(After
Advice):当连接点退出的时候执行的通知(不论是正常返回还是异常退出)。 (3)环绕通知(Around
Advice):包围一个连接点的通知,这是最强大的一种通知类型。
环绕通知可以在方法调用前后完成自定义的行为。它也可以选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。
(4)返回后通知(AfterReturning Advice):在连接点正常完成后执行的通知(如果连接点抛出异常,则不执行)
(5)抛出异常后通知(AfterThrowing advice):在方法抛出异常退出时执行的通知

在这里插入图片描述

①BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。
②ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。
ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean
,确保当你需要的时候,你就不用等待,因为它们已经创建好了。

7.Spring Bean的生命周期?

首先说一下Servlet的生命周期:实例化,初始init,接收请求service,销毁destroy;

Spring上下文中的Bean生命周期也类似,如下:

  1. 实例化 Instantiation
  2. 属性赋值 Populate
  3. 初始化 Initialization
  4. 销毁 Destruction

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

8.Spring中bean的作用域:

(1)singleton:默认作用域,单例bean,每个容器中只有一个bean的实例。
(2)prototype:为每一个bean请求创建一个实例。
(3)request:为每一个request请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。
(4)session:与request范围类似,同一个session会话共享一个实例,不同会话使用不同的实例。
(5)global-session:全局作用域,所有会话共享一个实例。如果想要声明让所有会话共享的存储变量的话,那么这全局变量需要存储在global-session中。

9.Spring基于xml注入bean的几种方式

(1)Set方法注入; (2)构造器注入:①通过index设置参数的位置;②通过type设置参数类型;
(3)静态工厂注入;
(4)实例工厂;

10.@Autowired和@Resource之间的区别

(1) @Autowired默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置它required属性为false)。搭配@Qualifier(“名称”)来使用 查询名称
(2) @Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入。

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

(1)工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例; (2)单例模式:Bean默认为单例模式。
(3)代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术; (4)模板方法:用来解决代码重复的问题。比如.
RestTemplate, JmsTemplate, JpaTemplate。
(5)观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现–ApplicationListener。

14.BeanFactory和factoryBean的区别

BeanFactory是个Factory,也就是IOC容器或对象工厂,FactoryBean是个Bean。在Spring中,所有的Bean都是由BeanFactory(也就是IOC容器)来进行管理的。但对FactoryBean而言,这个Bean不是简单的Bean,而是一个能生产或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zyf_fly66

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值