介绍
简单介绍一下你对Spring的理解。
Spring是一个一站式的轻量级的java开发框架;
两个特性:控制反转(IOC)面向切面编程(AOP);
针对于WEB层(springMVC)、业务层(IOC)、持久层(jdbcTemplate)等都提供了多种配置解决方案。
为什么要用Spring?
- 方便解耦。所有对象的创建和依赖关系的维护工作都交给Spring容器的管理。
- 低侵入式设计,代码污染极低,同时令代码对框架的依赖最小化。
- 支持AOP。减少系统重复代码,增加复用性。
- 声明式事务。只需要通过配置就可以完成对事物的管理。
为什么说Spring是一个容器?
用来形容它用来存储单例的bean对象这个特性。
AOP
⭐️⭐️什么是AOP?
AOP(Aspect-Oriented Programming,面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可扩展性和可维护性。
Spring AOP是基于动态代理的,如果要代理的对象实现了某个接口,那么Spring AOP就会使用JDK动态代理
去创建代理对象;而对于没有实现接口的对象,就无法使用JDK动态代理,转而使用CGlib动态代理
生成一个被代理对象的子类来作为代理。
当然也可以使用AspectJ(静态代理),Spring AOP中已经集成了AspectJ
。
JDK动态代理和cglib动态代理有什么区别?
JDK动态代理生成的代理类是继承了Proxy类,由于java不能多继承,只能由接口来完成对代理的实现,所以代理类必须传入被代理类实现的接口。(基于反射机制,只能针对接口编程)
cglib动态代理比JDK快,基于继承机制,继承被代理类,所以方法不要声明为final,然后重写父类方法达到增强了类的作用。需要额外引入cglib的依赖包。
Jdk动态代理要求被代理的类要实现接口,而cglib不需要,cglib能根据内存中为其创建子类(代理对象)
Spring Aop底层原理详解(利用spring后置处理器实现AOP)
Spring AOP和AspectJ AOP有什么区别?
Spring AOP是属于运行时增强,而AspectJ是编译时增强。Spring AOP基于代理(Proxying),而AspectJ基于字节码操作(Bytecode Manipulation)。
Spring AOP已经集成了AspectJ,AspectJ应该算得上是Java生态系统中最完整的AOP框架了。AspectJ相比于Spring AOP功能更加强大,但是Spring AOP相对来说更简单。
如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择AspectJ,它比SpringAOP快很多。
解释下Spring AOP里的几个名词
连接点(Join Point)
:就是spring允许增强的地方,和方法有关的前前后后都是连接点。通知、增强处理(Advice)
:就是你想要的功能,也就是安全、事物、日志等。你给先定义好,然后再想用的地方用一下。包含切面的一段处理代码。切入点(Pointcut)
:织入增强的连接点 就叫切入点。简单说就是不是所有的连接点都有机会成为切入点,只有那些受临幸的才可以。切面(Aspect)
: 切面是通知和切入点的结合。通知说明了干什么和什么时候干(什么时候通过规范的方法名中的before,after,around等就能知道);切入点说明了在哪干(指定到底是哪个方法)。这就是一个完整的切面定义。引入(introduction)
:允许我们向现有的类添加新方法属性,也被称为内部类型声明。其实就是把切面用到目标类中了。目标(target)
:引入中所提到的目标类,也就是要被通知的对象,也就是真正的业务逻辑,他可以在毫不知情的情况下,被咱们织入切面。而自己专注于业务本身的逻辑。由于SpringAOP是通过运行时代理实现的,所以这个对象永远是一个被代理对象。织入(weaving)
:把切面应用到目标对象来创建新的代理对象的过程。有三种方式,spring采用的是运行时。
拦截器了解吗?有哪些应用场景?
spring拦截器是spring Aop的一种应用,在不修改源码的情况下,执行一段代码,以增强现有方法。写自己的拦截器一般有两种方式。
- 实现
HandlerInterceptor
接口 - 继承
HandlerInterceptorAdapter
抽象类
应用场景:
-
记录接口响应时间
-
判断用户是否登录
-
判断用户的权限
-
接口权限校验
IOC
谈一下你对Spring的IOC的理解。
IOC称之为控制反转。
之前是我们自己写代码new出来,而现在则是Spring帮我们new出来,我们无需再new。底层原理是Spring有一个容器为IOC,这个容器中开辟了很多个很重要的注解,其主要的为四大注解@Service
,@Controller
,@Respository
,@Component
。这四个注解会在他们的容器中开辟四个空间,还有一个注解为**@Autowired
**,称之为DI,他会去找Spring容器中的bean,如果找到了就拿出来,找不到则会报一个查找不到的错误。当Spring第一次扫描,注解会帮我们new出想要的实现类,而下次使用autowired则直接拿来就用就ok了。
IOC(Inversion Of Controll,控制反转)是一种设计思想,就是将原本在程序中手动创建对象的控制权,交由给Spring框架来管理。IOC在其他语言中也有应用,并非Spring特有。IOC容器是Spring用来实现IOC的载体,IOC容器实际上就是一个Map(key, value),Map中存放的是各种对象。
将对象之间的相互依赖关系交给IOC容器来管理,并由IOC容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。IOC容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。在实际项目中一个Service类可能由几百甚至上千个类作为它的底层,假如我们需要实例化这个Service,可能要每次都搞清楚这个Service所有底层类的构造函数,这可能会把人逼疯。如果利用IOC的话,你只需要配置好,然后在需要的地方引用就行了,大大增加了项目的可维护性且降低了开发难度。
Spring时代我们一般通过XML文件来配置Bean,后来开发人员觉得用XML文件来配置不太好,于是Sprng Boot注解配置就慢慢开始流行起来。
其他问题
Spring框架中都用到了哪些设计模式
-
设计模式
-
工厂模式:Spring使用工厂模式通过
BeanFactory
和ApplicationContext
创建bean对象。 -
单例模式:
Bean
默认单例模式。 -
代理模式:SpringAOP的实现。
-
模板方法模式:大量的模板方法,例如
JdbcTemplate
等一些对数据库操作的类。 -
观察者模式:Spring 事件驱动模型就是观察者模式很经典的一个应用。
-
适配器模式:SpringMVC 中
DispatcherServlet
调用HandlerAdapter
找到实际调用的Controller就是使用此模式,Spring AOP 的增强或通知(Advice
)使用到了适配器模式。包装器设计模式:我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
……
使用单例模式有什么好处?
-
对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销;
-
由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间。
Spring是如何实现事务的,原理如何。
首先要说明的是,事务一般是在操作数据库时提及。而Spring实现事务,本质上就是数据库对事务的支持。
- 编程式事务:在代码中硬编码(不推荐使用)。
- 声明式事务:在配置文件中配置(推荐使用),分为基于XML的声明式事务和基于注解的声明式事务。
Spring事务中的隔离级别有哪几种?
常量 | 解释 |
---|---|
ISOLATION_DEFAULT(默认) | 使用后端数据库默认的隔离级别,Mysql默认采用的可重复读隔离级别; Oracle默认采用的读已提交隔离级别。 |
ISOLATION_READ_UNCOMMITTED(读未提交) | 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。 |
ISOLATION_READ_COMMITTED(读已提交) | 允许读取并发事务已经提交的数据,可以阻止脏读,仍有可能幻读或不可重复读。 |
ISOLATION_REPEATABLE_READ(可重复读) | 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但仍有可能幻读。 |
ISOLATION_SERIALIZABLE(序列化) | 最高的隔离级别,完全服从ACID的隔离级别。所有的事务顺序执行,将严重影响程序的性能。通常情况下也不会用到该级别。 |
Spring事务隔离级别与Mysql InnoDB事务隔离级别冲突了怎么办
- Spring会在事务开始时,根据你程序中设置的隔离级别,调整数据库隔离级别与你的设置一致。
- 当使用
Serializable
级别时,Mysql在执行SQL时会自动修改为select .... lock in share mode
, 即使用共享锁。此时允许同时读,但只允许一个事务写,且锁的是行而不是整张表。
这意味着:
- 如果数据库不支持某种隔离级别,那么Spring设置了也无效。
- 当使用
Serializable
级别时,如果两个事务读写的不是同一行,那么它们是互不影响的。如果操作同一行记录,那么允许同时读,但如果出现一个对此行的写操作,则在写事务没有提交之前,所有的读事务都会被block。
@Service
注解是单例还是多例,如何做到多例?
单例在spring中是默认的,我们常用的service和dao层的对象通常都是单例的,但service或dao并不一定是单例,要产生多例,则在配置文件的bean中添加scope="prototype"
。
Spring中的单例bean的线程安全问题了解吗?
大部分时候我们并没有在系统中使用多线程,所以很少有人会关注这个问题。单例bean存在线程问题,主要是因为当多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操作会存在线程安全问题。
有两种常见的解决方案:
1.在bean对象中尽量避免定义可变的成员变量(不太现实)。
2.在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在ThreadLocal中(推荐的一种方式)。
Spring中的bean生命周期?
-
Bean容器找到配置文件中Spring Bean的定义。
-
Bean容器利用反射创建一个Bean的实例。
-
如果涉及到一些属性值,利用set()方法设置一些属性值。
-
当要销毁Bean的时候,如果Bean实现了DisposableBean接口,执行destroy()方法。
完整版 -->
- Spring IoC容器找到关于Bean的定义并实例化该Bean。
- Spring IoC容器对Bean进行依赖注入。
- 如果Bean实现了BeanNameAware接口,则将该Bean的id传给setBeanName方法。
- 如果Bean实现了BeanFactoryAware接口,则将BeanFactory对象传给setBeanFactory方法。
- 如果Bean实现了BeanPostProcessor接口,则调用其postProcessBeforeInitialization方法。
- Bean实现了InitializingBean接口,则调用其afterPropertySet方法。
- 和Bean关联的BeanPostProcessors对象,则这些对象的postProcessAfterInitialization方法被调用。
- 销毁Bean实例时,如果Bean实现了DisposableBean接口,则调用其destroy方法。
Bean的作用域
类型 | 说明 |
---|---|
singleton | 默认值,当IoC容器一创建就会创建bean的实例,Spring中的bean默认都是单例的 |
prototype | 每次请求都会创建一个新的bean实例 |
request | 每次HTTP请求都会创建一个新的Bean,该bean仅在当前HTTP request内有效 |
session | 每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP session内有效 |
globalSession | 全局session作用域,仅仅在基于Portlet的Web应用中才有意义,Spring5中已经没有了。Portlet是能够生成语义代码(例如HTML)片段的小型Java Web插件。它们基于Portlet容器,可以像Servlet一样处理HTTP请求。但是与Servlet不同,每个Portlet都有不同的会话。 |