小白学java-spring相关知识笔记

⼀句话概括:Spring 是⼀个轻量级、⾮⼊侵式的控制反转 (IoC) 和⾯向切⾯ (AOP) 的框架。

2003 年,⼀个⾳乐家 Rod Johnson 决定发展⼀个轻量级的 Java 开发框架, Spring 作为 Java 战场
的龙骑兵渐渐崛起,并淘汰了 EJB 这个传统的重装骑兵。

 

1. IOC DI 的⽀持
Spring 的核⼼就是⼀个⼤的⼯⼚容器,可以维护所有对象的创建和依赖关系, Spring ⼯⼚⽤
于⽣成 Bean ,并且管理 Bean 的⽣命周期,实现 ⾼内聚低耦合 的设计理念。
2. AOP 编程的⽀持
Spring 提供了 ⾯向切⾯编程 ,可以⽅便的实现对程序进⾏权限拦截、运⾏监控等切⾯功能。
3. 声明式事务的⽀持
⽀持通过配置就来完成对事务的管理,⽽不需要通过硬编码的⽅式,以前重复的⼀些事务提
交、回滚的 JDBC 代码,都可以不⽤⾃⼰写了。
4. 快捷测试的⽀持
Spring Junit 提供⽀持,可以通过 注解 快捷地测试 Spring 程序。
5. 快速集成功能
⽅便集成各种优秀框架, Spring 不排斥各种优秀的开源框架,其内部提供了对各种优秀框架
(如: Struts Hibernate MyBatis Quartz 等)的直接⽀持。
6. 复杂 API 模板封装
Spring JavaEE 开发中⾮常难⽤的⼀些 API JDBC JavaMail 、远程调⽤等)都提供了模板
化的封装,这些封装 API 的提供使得应⽤难度⼤⼤降低。

2.Spring 有哪些模块呢?
Spring 框架是分模块存在,除了最核⼼的 Spring Core Container 是必要模块之外,其他
模块都是 可选 ,⼤约有 20 多个模块。

最主要的七⼤模块:
1. Spring Core Spring 核⼼,它是框架最基础的部分,提供 IOC 和依赖注⼊ DI 特性。
2. Spring Context Spring 上下⽂容器,它是 BeanFactory 功能加强的⼀个⼦接⼜。
3. Spring Web :它提供 Web 应⽤开发的⽀持。
4. Spring MVC :它针对 Web 应⽤中 MVC 思想的实现。
5. Spring DAO :提供对 JDBC 抽象层,简化了 JDBC 编码,同时,编码更具有健壮性。
6. Spring ORM :它⽀持⽤于流⾏的 ORM 框架的整合,⽐如: Spring + Hibernate Spring +
iBatis Spring + JDO 的整合等。
7. Spring AOP :即⾯向切⾯编程,它提供了与 AOP 联盟兼容的编程实现。

Web :
@Controller :组合注解(组合了 @Component 注解),应⽤在 MVC 层(控制层)。
@RestController :该注解为⼀个组合注解,相当于 @Controller @ResponseBody 的组合,
注解在类上,意味着,该 Controller 的所有⽅法都默认加上了 @ResponseBody
@RequestMapping :⽤于映射 Web 请求,包括访问路径和参数。如果是 Restful 风格接⼜,
还可以根据请求类型使⽤不同的注解:
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@ResponseBody :⽀持将返回值放在 response 内,⽽不是⼀个页⾯,通常⽤户返回 json
据。
@RequestBody :允许 request 的参数在 request 体中,⽽不是在直接连接在地址后⾯。
@PathVariable :⽤于接收路径参数,⽐如 @RequestMapping(“/hello/{name}”) 申明的路径,
将注解放在参数中前,即可获取该值,通常作为 Restful 的接⼜实现⽅法。
@RestController :该注解为⼀个组合注解,相当于 @Controller @ResponseBody 的组合,
注解在类上,意味着,该 Controller 的所有⽅法都默认加上了 @ResponseBody
容器 :
@Component :表⽰⼀个带注释的类是⼀个 组件 ,成为 Spring 管理的 Bean 。当使⽤基于
注解的配置和类路径扫描时,这些类被视为⾃动检测的候选对象。同时 @Component 还是
⼀个元注解。
@Service :组合注解(组合了 @Component 注解),应⽤在 service 层(业务逻辑层)。
@Repository :组合注解(组合了 @Component 注解),应⽤在 dao 层(数据访问层)。
@Autowired Spring 提供的⼯具(由 Spring 的依赖注⼊⼯具( BeanPostProcessor
BeanFactoryPostProcessor )⾃动注⼊)。
@Qualifier :该注解通常跟 @Autowired ⼀起使⽤,当想对注⼊的过程做更多的控制,
@Qualifier 可帮助配置,⽐如两个以上相同类型的 Bean Spring ⽆法抉择,⽤到此注解
@Configuration :声明当前类是⼀个配置类(相当于⼀个 Spring 配置的 xml ⽂件)
@Value :可⽤在字段,构造器参数跟⽅法参数,指定⼀个默认值,⽀持 #{} ${} 两个
⽅式。⼀般将 SpringbBoot 中的 application.properties 配置的属性值赋值给变量。
@Bean :注解在⽅法上,声明当前⽅法的返回值为⼀个 Bean 。返回的 Bean 对应的类中可
以定义 init() ⽅法和 destroy() ⽅法,然后在 @Bean(initMethod=”init”,destroyMethod=”destroy”)
定义,在构造之后执⾏ init ,在销毁之前执⾏ destroy
@Scope: 定义我们采⽤什么模式去创建 Bean (⽅法上,得有 @Bean ) 其设置类型包括:
Singleton Prototype Request Session GlobalSession
AOP :
@Aspect: 声明⼀个切⾯(类上) 使⽤ @After @Before @Around 定义建⾔( advice ),
可直接将拦截规则(切点)作为参数。
@After :在⽅法执⾏之后执⾏(⽅法上)。
@Before : 在⽅法执⾏之前执⾏(⽅法上)。
@Around : 在⽅法执⾏之前与之后执⾏(⽅法上)。
@PointCut : 声明切点 在 java 配置类中使⽤ @EnableAspectJAutoProxy 注解开启
Spring AspectJ 代理的⽀持(类上)。
事务:
@Transactional :在要开启事务的⽅法上使⽤ @Transactional 注解,即可声明式开启事务。

1. ⼯⼚模式 : Spring 容器本质是⼀个⼤⼯⼚,使⽤⼯⼚模式通过 BeanFactory
ApplicationContext 创建 bean 对象。
2. 代理模式 : Spring AOP 功能功能就是通过代理模式来实现的,分为动态代理和静态代
理。
3. 单例模式 : Spring 中的 Bean 默认都是单例的,这样有利于容器对 Bean 的管理。
4. 模板模式 : Spring JdbcTemplate RestTemplate 等以 Template 结尾的对数据库、⽹络等
等进⾏操作的模板类,就使⽤到了模板模式。
5. 观察者模式 : Spring 事件驱动模型就是观察者模式很经典的⼀个应⽤。
6. 适配器模式 :Spring AOP 的增强或通知 (Advice) 使⽤到了适配器模式、 Spring MVC 中也
是⽤到了适配器模式适配 Controller
7. 策略模式 : Spring 中有⼀个 Resource 接⼜,它的不同实现类,会根据不同的策略去访问资
源。

 

IOC
5. 说⼀说什么是 IOC ?什么是 DI?
Java 是⾯向对象的编程语⾔,⼀个个实例对象相互合作组成了业务逻辑,原来,我们都是在
代码⾥创建对象和对象的依赖。
所谓的 IOC (控制反转):就是由容器来负责控制对象的⽣命周期和对象间的关系。以前是
我们想要什么,就⾃⼰创建什么,现在是我们需要什么,容器就给我们送来什么。
也就是说,控制对象⽣命周期的不再是引⽤它的对象,⽽是容器。对具体对象,以前是它控
制其它对象,现在所有对象都被容器控制,所以这就叫 控制反转

DI (依赖注⼊) :指的是容器在实例化对象的时候把它依赖的类注⼊给它。有的说法 IOC
DI 是⼀回事,有的说法是 IOC 是思想, DI IOC 的实现。
为什么要使⽤ IOC 呢?
最主要的是两个字 解耦 ,硬编码会造成对象间的过度耦合,使⽤ IOC 之后,我们可以不⽤关
⼼对象间的依赖,专⼼开发应⽤就⾏。

6. 能简单说⼀下 Spring IOC 的实现机制吗?
PS: 这道题⽼三在⾯试中被问到过,问法是 你有⾃⼰实现过简单的 Spring 吗?
Spring IOC 本质就是⼀个⼤⼯⼚,我们想想⼀个⼯⼚是怎么运⾏的呢?

Bean 定义:
Bean 通过⼀个配置⽂件定义,把它解析成⼀个类型。
beans.properties
偷懒,这⾥直接⽤了最⽅便解析的 properties ,这⾥直接⽤⼀个 <key,value> 类型的配置来代
Bean 的定义,其中 key beanName value class
userDao : cn . fighter3 . bean . UserDao
BeanDefinition.java
bean 定义类,配置⽂件中 bean 定义对应的实体
public class BeanDefinition {
private String beanName ;
private Class beanClass ;
// 省略 getter setter
}
ResourceLoader.java
资源加载器,⽤来完成配置⽂件中配置的加载
public class ResourceLoader {
public static Map < String , BeanDefinition > getResource () {
Map < String , BeanDefinition > beanDefinitionMap = new HashMap <>
( 16 );
Properties properties = new Properties ();
try {
InputStream inputStream =
ResourceLoader . class . getResourceAsStream ( "/beans.properties" );
properties . load ( inputStream );
Iterator < String > it =
properties . stringPropertyNames (). iterator ();
while ( it . hasNext ()) {
String key = it . next ();
String className = properties . getProperty ( key );
BeanDefinition beanDefinition = new BeanDefinition ();
beanDefinition . setBeanName ( key );
Class clazz = Class . forName ( className );
beanDefinition . setBeanClass ( clazz );
beanDefinitionMap . put ( key , beanDefinition );
}
inputStream . close ();
} catch ( IOException | ClassNotFoundException e ) {
e . printStackTrace ();
}
return beanDefinitionMap ;
}
}
BeanRegister.java
对象注册器,这⾥⽤于单例 bean 的缓存,我们⼤幅简化,默认所有 bean 都是单例的。可以
看到所谓单例注册,也很简单,不过是往 HashMap ⾥存对象。
public class BeanRegister {
// 单例 Bean 缓存
private Map < String , Object > singletonMap = new HashMap <> ( 32 );
/*
* 获取单例 Bean
*
* @param beanName bean 名称
* @return
*/
public Object getSingletonBean ( String beanName ) {
return singletonMap . get ( beanName );
}
/**
* 注册单例 bean
*
* @param beanName
* @param bean
*/
public void registerSingletonBean ( String beanName , Object bean ) {
if ( singletonMap . containsKey ( beanName )) {
return ;
}
singletonMap . put ( beanName , bean );
}
}

 

对象⼯⼚,我们最 核⼼ 的⼀个类,在它初始化的时候,创建了 bean 注册器,完成了资
源的加载。
获取 bean 的时候,先从单例缓存中取,如果没有取到,就创建并注册⼀个 bean

7. 说说 BeanFactory ApplicantContext?
可以这么形容, BeanFactory Spring ⼼脏 ApplicantContext 是完整的 ⾝躯
BeanFactory Bean ⼯⼚)是 Spring 框架的基础设施,⾯向 Spring 本⾝。
ApplicantContext (应⽤上下⽂)建⽴在 BeanFactoty 基础上,⾯向使⽤ Spring 框架的开发
者。
BeanFactory 接⼜
BeanFactory 是类的通⽤⼯⼚,可以创建并管理各种类的对象。
Spring BeanFactory 提供了很多种实现,最常⽤的是 XmlBeanFactory ,但在 Spring 3.2 中已被
废弃,建议使⽤ XmlBeanDefinitionReader DefaultListableBeanFactory
BeanFactory 接⼜位于类结构树的顶端,它最主要的⽅法就是 getBean(String var1) ,这个⽅法从
容器中返回特定名称的 Bean
BeanFactory 的功能通过其它的接⼜得到了不断的扩展,⽐如
AbstractAutowireCapableBeanFactory 定义了将容器中的 Bean 按照某种规则(⽐如按名字匹配、
按类型匹配等)进⾏⾃动装配的⽅法。
这⾥看⼀个 XMLBeanFactory (已过期) 获取 bean 的例⼦:
public class HelloWorldApp {
public static void main( String [] args) {
BeanFactory factory = new XmlBeanFactory ( new
ClassPathResource( "beans.xml" ));
HelloWorld obj = (HelloWorld) factory.getBean( "helloWorld" );
obj.getMessage();
}
}
ApplicationContext 接⼜
ApplicationContext BeanFactory 派⽣⽽来,提供了更多⾯向实际应⽤的功能。可以这么说,
使⽤ BeanFactory 就是⼿动档,使⽤ ApplicationContext 就是⾃动档。
ApplicationContext 继承了 HierachicalBeanFactory ListableBeanFactory 接⼜,在此基础上,还
通过其他的接⼜扩展了 BeanFactory 的功能,包括:
Bean instantiation/wiring
Bean 的实例化 / 串联
⾃动的 BeanPostProcessor 注册
⾃动的 BeanFactoryPostProcessor 注册
⽅便的 MessageSource 访问( i18n
ApplicationEvent 的发布与 BeanFactory 懒加载的⽅式不同,它是预加载,所以,每⼀个
bean 都在 ApplicationContext 启动之后实例化
这是 ApplicationContext 的使⽤例⼦:

8. 你知道 Spring 容器启动阶段会⼲什么吗?
PS :这道题⽼三⾯试被问到过
Spring IOC 容器⼯作的过程,其实可以划分为两个阶段: 容器启动阶段 Bean 实例化阶
其中容器启动阶段主要做的⼯作是加载和解析配置⽂件,保存到对应的 Bean 定义中。
容器启动开始,⾸先会通过某种途径加载 Congiguration MetaData ,在⼤部分情况下,容器需
要依赖某些⼯具类( BeanDefinitionReader )对加载的 Congiguration MetaData 进⾏解析和分
析,并将分析后的信息组为相应的 BeanDefinition
最后把这些保存了 Bean 定义必要信息的 BeanDefinition ,注册到相应的 BeanDefinitionRegistry
这样容器启动就完成了。

9. 能说⼀下 Spring Bean ⽣命周期吗?
可以看看: Spring Bean ⽣命周期,好像⼈的⼀⽣。。
Spring 中,基本容器 BeanFactory 和扩展容器 ApplicationContext 的实例化时机不太⼀样,
BeanFactory 采⽤的是延迟初始化的⽅式,也就是只有在第⼀次 getBean() 的时候,才会实例化
Bean ApplicationContext 启动之后会实例化所有的 Bean 定义。
Spring IOC Bean 的⽣命周期⼤致分为四个阶段: 实例化 Instantiation )、 属性赋值
Populate )、 初始化 Initialization )、 销毁 Destruction )。
我们再来看⼀个稍微详细⼀些的过程:
实例化 :第 1 步,实例化⼀个 Bean 对象
属性赋值 :第 2 步,为 Bean 设置相关属性和依赖
初始化 :初始化的阶段的步骤⽐较多, 5 6 步是真正的初始化,第 3 4 步为在初始化前
执⾏,第 7 步在初始化后执⾏,初始化完成之后, Bean 就可以被使⽤了
销毁 :第 8~10 步,第 8 步其实也可以算到销毁阶段,但不是真正意义上的销毁,⽽是先
在使⽤前注册了销毁的相关调⽤接⼜,为了后⾯第 9 10 步真正销毁 Bean 时再执⾏相应的
⽅法

 

 

 

 

10.Bean 定义和依赖定义有哪些⽅式?
有三种⽅式: 直接编码⽅式 配置⽂件⽅式 注解⽅式
直接编码⽅式:我们⼀般接触不到直接编码的⽅式,但其实其它的⽅式最终都要通过直接
编码来实现。
配置⽂件⽅式:通过 xml propreties 类型的配置⽂件,配置相应的依赖关系, Spring 读取
配置⽂件,完成依赖关系的注⼊。
注解⽅式:注解⽅式应该是我们⽤的最多的⼀种⽅式了,在相应的地⽅使⽤注解修饰,
Spring 会扫描注解,完成依赖关系的注⼊。
11. 有哪些依赖注⼊的⽅法?
Spring ⽀持 构造⽅法注⼊ 属性注⼊ ⼯⼚⽅法注⼊ , 其中⼯⼚⽅法注⼊,又可以分为 静态⼯
⼚⽅法注⼊ ⾮静态⼯⼚⽅法注⼊

 

什么是⾃动装配?
Spring IOC 容器知道所有 Bean 的配置信息,此外,通过 Java 反射机制还可以获知实现类的结构
信息,如构造⽅法的结构、属性等信息。掌握所有 Bean 的这些信息后, Spring IOC 容器就可以
按照某种规则对容器中的 Bean 进⾏⾃动装配,⽽⽆须通过显式的⽅式进⾏依赖配置。
Spring 提供的这种⽅式,可以按照某些规则进⾏ Bean 的⾃动装配,元素提供了⼀个指定⾃动装
配类型的属性: autowire="< ⾃动装配类型 >"
byName :根据名称进⾏⾃动匹配,假设 Boss 又⼀个名为 car 的属性,如果容器中刚好有⼀
个名为 car bean Spring 就会⾃动将其装配给 Boss car 属性
byType :根据类型进⾏⾃动匹配,假设 Boss 有⼀个 Car 类型的属性,如果容器中刚好有⼀
Car 类型的 Bean Spring 就会⾃动将其装配给 Boss 这个属性
constructor :与 byType 类似, 只不过它是针对构造函数注⼊⽽⾔的。如果 Boss 有⼀个构
造函数,构造函数包含⼀个 Car 类型的⼊参,如果容器中有⼀个 Car 类型的 Bean ,则 Spring
将⾃动把这个 Bean 作为 Boss 构造函数的⼊参;如果容器中没有找到和构造函数⼊参匹配类
型的 Bean ,则 Spring 将抛出异常。
autodetect :根据 Bean 的⾃省机制决定采⽤ byType 还是 constructor 进⾏⾃动装配,如果 Bean
提供了默认的构造函数,则采⽤ byType ,否则采⽤ constructor

 

singleton : Spring 容器仅存在⼀个 Bean 实例, Bean 以单实例的⽅式存在,是 Bean 默认的
作⽤域。
prototype : 每次从容器重调⽤ Bean 时,都会返回⼀个新的实例。
以下三个作⽤域于只在 Web 应⽤中适⽤:
request : 每⼀次 HTTP 请求都会产⽣⼀个新的 Bean ,该 Bean 仅在当前 HTTP Request 内有
效。
session : 同⼀个 HTTP Session 共享⼀个 Bean ,不同的 HTTP Session 使⽤不同的 Bean
globalSession :同⼀个全局 Session 共享⼀个 Bean ,只⽤于基于 Protlet Web 应⽤, Spring5
中已经不存在了。

14.Spring 中的单例 Bean 会存在线程安全问题吗?
⾸先结论在这: Spring 中的单例 Bean 不是线程安全的
因为单例 Bean ,是全局只有⼀个 Bean ,所有线程共享。如果说单例 Bean ,是⼀个⽆状态的,
也就是线程中的操作不会对 Bean 中的成员变量执⾏ 查询 以外的操作,那么这个单例 Bean 是线
程安全的。⽐如 Spring mvc Controller Service Dao 等,这些 Bean ⼤多是⽆状态的,只关
注于⽅法本⾝。
假如这个 Bean 是有状态的,也就是会对 Bean 中的成员变量进⾏写操作,那么可能就存在线程
安全的问题。
单例 Bean 线程安全问题怎么解决呢?
常见的有这么些解决办法:
1. Bean 定义为多例
这样每⼀个线程请求过来都会创建⼀个新的 Bean ,但是这样容器就不好管理 Bean ,不能
这么办。
2. Bean 对象中尽量避免定义可变的成员变量
削⾜适履了属于是,也不能这么⼲。
3. Bean 中的成员变量保存在 ThreadLocal
我们知道 ThredLoca 能保证多线程下变量的隔离,可以在类中定义⼀个 ThreadLocal 成员变
量,将需要的可变成员变量保存在 ThreadLocal ⾥,这是推荐的⼀种⽅式。
只有单例的 Bean 才存在循环依赖的情况, 原型 (Prototype) 情况下, Spring 会直接抛出异常。原
因很简单, AB 循环依赖, A 实例化的时候,发现依赖 B ,创建 B 实例,创建 B 的时候发现需要
A ,创建 A1 实例 …… ⽆限套娃,直接把系统⼲垮。
Spring 可以解决哪些情况的循环依赖?
Spring 不⽀持基于构造器注⼊的循环依赖,但是假如 AB 循环依赖,如果⼀个是构造器注⼊,
⼀个是 setter 注⼊呢?
看看⼏种情形:

 

第四种可以⽽第五种不可以的原因是 Spring 在创建 Bean 时默认会根据⾃然排序进⾏创建,
所以 A 会先于 B 进⾏创建。
所以简单总结,当循环依赖的实例都采⽤ setter ⽅法注⼊的时候, Spring 可以⽀持,都采⽤构造
器注⼊的时候,不⽀持,构造器注⼊和 setter 注⼊同时存在的时候,看天。

16. Spring 怎么解决循环依赖的呢?
PS :其实正确答案是开发⼈员做好设计,别让 Bean 循环依赖,但是没办法,⾯试官不想
听这个。
我们都知道,单例 Bean 初始化完成,要经历三步:

注⼊就发⽣在第⼆步, 属性赋值 ,结合这个过程, Spring 通过 三级缓存 解决了循环依赖:
1. ⼀级缓存 : Map<String,Object> singletonObjects ,单例池,⽤于保存实例化、属性赋值
(注⼊)、初始化完成的 bean 实例
2. ⼆级缓存 : Map<String,Object> earlySingletonObjects ,早期曝光对象,⽤于保存实例化完
成的 bean 实例
3. 三级缓存 : Map<String,ObjectFactory<?>> singletonFactories ,早期曝光对象⼯⼚,⽤于保
bean 创建⼯⼚,以便于后⾯扩展有机会创建代理对象。

我们来看⼀下三级缓存解决循环依赖的过程:
A B 两个类发⽣循环依赖时:

 

 

A 实例的初始化过程:
1. 创建 A 实例,实例化的时候把 A 对象⼯⼚放⼊三级缓存,表⽰ A 开始实例化了,虽然我这
个对象还不完整,但是先曝光出来让⼤家知道

2. A 注⼊属性时,发现依赖 B ,此时 B 还没有被创建出来,所以去实例化 B
3. 同样, B 注⼊属性时发现依赖 A ,它就会从缓存⾥找 A 对象。依次从⼀级到三级缓存查询
A ,从三级缓存通过对象⼯⼚拿到 A ,发现 A 虽然不太完善,但是存在,把 A 放⼊⼆级缓
存,同时删除三级缓存中的 A ,此时, B 已经实例化并且初始化完成,把 B 放⼊⼀级缓存。

4. 接着 A 继续属性赋值,顺利从⼀级缓存拿到实例化且初始化完成的 B 对象, A 对象创建也
完成,删除⼆级缓存中的 A ,同时把 A 放⼊⼀级缓存
5. 最后,⼀级缓存中保存着实例化、初始化都完成的 A B 对象

 

所以,我们就知道为什么 Spring 能解决 setter 注⼊的循环依赖了,因为实例化和属性赋值是分开
的,所以⾥⾯有操作的空间。如果都是构造器注⼊的化,那么都得在实例化这⼀步完成注
⼊,所以⾃然是⽆法⽀持了。

17. 为什么要三级缓存?⼆级不⾏吗?
不⾏,主要是为了 ⽣成代理对象 。如果是没有代理的情况下,使⽤⼆级缓存解决循环依赖也
OK 的。但是如果存在代理,三级没有问题,⼆级就不⾏了。
因为三级缓存中放的是⽣成具体对象的匿名内部类,获取 Object 的时候,它可以⽣成代理对
象,也可以返回普通对象。使⽤三级缓存主要是为了保证不管什么时候使⽤的都是⼀个对
象。
假设只有⼆级缓存的情况,往⼆级缓存中放的显⽰⼀个普通的 Bean 对象, Bean 初始化过程
中,通过 BeanPostProcessor 去⽣成代理对象之后,覆盖掉⼆级缓存中的普通 Bean 对象,那么
可能就导致取到的 Bean 对象不⼀致了。

 

 

19. 说说什么是 AOP
AOP :⾯向切⾯编程。简单说,就是把⼀些业务逻辑中的相同的代码抽取到⼀个独⽴的模块
中,让业务逻辑更加清爽。

我们可以把 ⽇志记录 数据校验 可重⽤的功能模块分离出来,然后在程序的执⾏的合适的地
⽅动态地植⼊这些代码并执⾏。这样就简化了代码的书写。

 

业务逻辑代码中没有参和通⽤逻辑的代码,业务模块更简洁,只包含核⼼业务代码。实现了
业务逻辑和通⽤逻辑的代码分离,便于维护和升级,降低了业务逻辑和通⽤逻辑的耦合性。
AOP 可以将遍布应⽤各处的功能分离出来形成可重⽤的组件。在编译期间、装载期间或运⾏
期间实现在不修改源代码的情况下给程序动态添加功能。从⽽实现对业务逻辑的隔离,提⾼
代码的模块化能⼒。

 

AOP 的核⼼其实就是 动态代理 ,如果是实现了接⼜的话就会使⽤ JDK 动态代理,否则使⽤
CGLIB 代理,主要应⽤于处理⼀些具有横切性质的系统级服务,如⽇志收集、事务管理、安
全检查、缓存、对象池管理等。
AOP 有哪些核⼼概念?
切⾯ Aspect ):类是对物体特征的抽象,切⾯就是对横切关注点的抽象
连接点 Joinpoint ):被拦截到的点,因为 Spring 只⽀持⽅法类型的连接点,所以在
Spring 中连接点指的就是被拦截到的⽅法,实际上连接点还可以是字段或者构造器
切点 Pointcut ):对连接点进⾏拦截的定位
通知 Advice ):所谓通知指的就是指拦截到连接点之后要执⾏的代码,也可以称作
⽬标对象 Target ):代理的⽬标对象
织⼊ Weabing ):织⼊是将增强添加到⽬标类的具体连接点上的过程。
编译期织⼊:切⾯在⽬标类编译时被织⼊
类加载期织⼊:切⾯在⽬标类加载到 JVM 时被织⼊。需要特殊的类加载器,它可以在
⽬标类被引⼊应⽤之前增强该⽬标类的字节码。
运⾏期织⼊:切⾯在应⽤运⾏的某个时刻被织⼊。⼀般情况下,在织⼊切⾯时, AOP
容器会为⽬标对象动态地创建⼀个代理对象。 SpringAOP 就是以这种⽅式织⼊切⾯。
Spring 采⽤运⾏期织⼊,⽽ AspectJ 采⽤编译期织⼊和类加载器织⼊。
引介 introduction ):引介是⼀种特殊的增强,可以动态地为类添加⼀些属性和⽅法
AOP 有哪些环绕⽅式?
AOP ⼀般有 5 环绕⽅式:
前置通知 (@Before)
返回通知 (@AfterReturning)
异常通知 (@AfterThrowing)
后置通知 (@After)
环绕通知 (@Around)
多个切⾯的情况下,可以通过 @Order 指定先后顺序,数字越⼩,优先级越⾼。

 

21. 说说 JDK 动态代理和 CGLIB 代理 ?
Spring AOP 是通过 动态代理 来实现的,动态代理主要有两种⽅式 JDK 动态代理和 Cglib 动态
代理,这两种动态代理的使⽤和原理有些不同。
JDK 动态代理
1. Interface :对于 JDK 动态代理,⽬标类需要实现⼀个 Interface
2. InvocationHandler InvocationHandler 是⼀个接⼜,可以通过实现这个接⼜,定义横切逻
辑,再通过反射机制( invoke )调⽤⽬标类的代码,在次过程,可能包装逻辑,对⽬标⽅
法进⾏前置后置处理。
3. Proxy Proxy 利⽤ InvocationHandler 动态创建⼀个符合⽬标类实现的接⼜的实例,⽣成⽬
标类的代理对象。
CgLib 动态代理
1. 使⽤ JDK 创建代理有⼀⼤限制,它只能为接⼜创建代理实例,⽽ CgLib 动态代理就没有这
个限制。
2. CgLib 动态代理是使⽤字节码处理框架 ASM ,其原理是通过字节码技术为⼀个类创建⼦
类,并在⼦类中采⽤⽅法拦截的技术拦截所有⽗类⽅法的调⽤,顺势织⼊横切逻辑。
3. CgLib 创建的动态代理对象性能⽐ JDK 创建的动态代理对象的性能⾼不少,但是 CGLib
在创建代理对象时所花费的时间却⽐ JDK 多得多,所以对于单例的对象,因为⽆需频繁
创建对象,⽤ CGLib 合适,反之,使⽤ JDK ⽅式要更为合适⼀些。同时,由于 CGLib
于是采⽤动态创建⼦类的⽅法,对于 final ⽅法,⽆法进⾏代理。

22. 说说 Spring AOP AspectJ AOP 区别 ?
Spring AOP
Spring AOP 属于 运⾏时增强 ,主要具有如下特点:
1. 基于动态代理来实现,默认如果使⽤接⼜的,⽤ JDK 提供的动态代理实现,如果是⽅法
则使⽤ CGLIB 实现
2. Spring AOP 需要依赖 IOC 容器来管理,并且只能作⽤于 Spring 容器,使⽤纯 Java 代码实
3. 在性能上,由于 Spring AOP 是基于 动态代理 来实现的,在容器启动时需要⽣成代理实
例,在⽅法调⽤上也会增加栈的深度,使得 Spring AOP 的性能不如 AspectJ 的那么好。
4. Spring AOP 致⼒于解决企业级开发中最普遍的 AOP( ⽅法织⼊ )
AspectJ
AspectJ 是⼀个易⽤的功能强⼤的 AOP 框架,属于 编译时增强 , 可以单独使⽤,也可以整合
到其它框架中,是 AOP 编程的完全解决⽅案。 AspectJ 需要⽤到单独的编译器 ajc
AspectJ 属于 静态织⼊ ,通过修改代码来实现,在实际运⾏之前就完成了织⼊,所以说它⽣成
的类是没有额外运⾏时开销的,⼀般有如下⼏个织⼊的时机:
1. 编译期织⼊( Compile-time weaving ):如类 A 使⽤ AspectJ 添加了⼀个属性,类 B 引⽤
了它,这个场景就需要编译期的时候就进⾏织⼊,否则没法编译类 B
2. 编译后织⼊( Post-compile weaving ):也就是已经⽣成了 .class ⽂件,或已经打成 jar
了,这种情况我们需要增强处理的话,就要⽤到编译后织⼊。
3. 类加载后织⼊( Load-time weaving ):指的是在加载类的时候进⾏织⼊,要实现这个时期
的织⼊,有⼏种常见的⽅法

事务
Spring 事务的本质其实就是数据库对事务的⽀持,没有数据库的事务⽀持, Spring 是⽆法提
供事务功能的。 Spring 只提供统⼀事务管理接⼜,具体实现都是由各数据库⾃⼰实现,数据
库事务的提交和回滚是通过数据库⾃⼰的事务机制实现。
23.Spring 事务的种类?
Spring ⽀持 编程式事务 管理和 声明式 事务管理两种⽅式

 

1. 编程式事务
编程式事务管理使⽤ TransactionTemplate ,需要显式执⾏事务。
2. 声明式事务
3. 声明式事务管理建⽴在 AOP 之上的。其本质是通过 AOP 功能,对⽅法前后进⾏拦截,将
事务处理的功能编织到拦截的⽅法中,也就是在⽬标⽅法开始之前启动⼀个事务,在执⾏
完⽬标⽅法之后根据执⾏情况提交或者回滚事务
4. 优点是不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置⽂件中做相关的事务规
则声明或通过 @Transactional 注解的⽅式,便可以将事务规则应⽤到业务逻辑中,减少业
务代码的污染。唯⼀不⾜地⽅是,最细粒度只能作⽤到⽅法级别,⽆法做到像编程式事务
那样可以作⽤到代码块级别。
24.Spring 的事务隔离级别?
Spring 的接⼜ TransactionDefinition 中定义了表⽰隔离级别的常量,当然其实主要还是对应数据
库的事务隔离级别:
1. ISOLATION_DEFAULT :使⽤后端数据库默认的隔离界别, MySQL 默认可重复读,
Oracle 默认读已提交。
2. ISOLATION_READ_UNCOMMITTED :读未提交
3. ISOLATION_READ_COMMITTED :读已提交
4. ISOLATION_REPEATABLE_READ :可重复读
5. ISOLATION_SERIALIZABLE :串⾏化

 

25.Spring 的事务传播机制?
Spring 事务的传播机制说的是,当多个事务同时存在的时候 —— ⼀般指的是多个事务⽅法相
互调⽤时, Spring 如何处理这些事务的⾏为。
事务传播机制是使⽤简单的 ThreadLocal 实现的,所以,如果调⽤的⽅法是在新线程调⽤的,
事务传播实际上是会失效的。

Spring 默认的事务传播⾏为是 PROPAFATION_REQUIRED ,它适合绝⼤多数情况,如果多个
ServiceX#methodX() 都⼯作在事务环境下(均被 Spring 事务增强),且程序中存在调⽤链
Service1#method1()->Service2#method2()->Service3#method3() ,那么这 3 个服务类的三个⽅法通
Spring 的事务传播机制都⼯作在同⼀个事务中。

26. 声明式事务实现原理了解吗?
就是通过 AOP/ 动态代理。
Bean 初始化阶段创建代理对象 Spring 容器在初始化每个单例 bean 的时候,会遍历容器
中的所有 BeanPostProcessor 实现类,并执⾏其 postProcessAfterInitialization ⽅法,在执⾏
AbstractAutoProxyCreator 类的 postProcessAfterInitialization ⽅法时会遍历容器中所有的切
⾯,查找与当前实例化 bean 匹配的切⾯,这⾥会获取事务属性切⾯,查找 @Transactional
注解及其属性值,然后根据得到的切⾯创建⼀个代理对象,默认是使⽤ JDK 动态代理创建
代理,如果⽬标类是接⼜,则使⽤ JDK 动态代理,否则使⽤ Cglib
在执⾏⽬标⽅法时进⾏事务增强操作 :当通过代理对象调⽤ Bean ⽅法的时候,会触发对
应的 AOP 增强拦截器,声明式事务是⼀种环绕增强,对应接⼜
MethodInterceptor ,事务增强对该接⼜的实现为 TransactionInterceptor ,类
图如下:

 

1 @Transactional 应⽤在⾮ public 修饰的⽅法上
如果 Transactional 注解应⽤在⾮ public 修饰的⽅法上, Transactional 将会失效。
是因为在 Spring AOP 代理时, TransactionInterceptor (事务拦截器)在⽬标⽅法执⾏前后进
⾏拦截, DynamicAdvisedInterceptor CglibAopProxy 的内部类)的 intercept ⽅法 或
JdkDynamicAopProxy invoke ⽅法会间接调⽤ AbstractFallbackTransactionAttributeSource
computeTransactionAttribute ⽅法,获取 Transactional 注解的事务配置信息。
protected TransactionAttribute computeTransactionAttribute (Method method,
Class <?> targetClass) {
// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() &&
! Modifier.isPublic(method.getModifiers())) {
return null ;
}
此⽅法会检查⽬标⽅法的修饰符是否为 public ,不是 public 则不会获取 @Transactional 的属性
配置信息。
2 @Transactional 注解属性 propagation 设置错误
TransactionDefinition.PROPAGATION_SUPPORTS :如果当前存在事务,则加⼊该事务;
如果当前没有事务,则以⾮事务的⽅式继续运⾏。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED :以⾮事务⽅式运⾏,如果当前
存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NEVER :以⾮事务⽅式运⾏,如果当前存在事务,
则抛出异常。
3 @Transactional 注解属性 rollbackFor 设置错误
rollbackFor 可以指定能够触发事务回滚的异常类型。 Spring 默认抛出了未检查 unchecked 异常
(继承⾃ RuntimeException 的异常)或者 Error 才回滚事务,其他异常不会触发回滚事务。

 

// 希望⾃定义的异常可以进⾏回滚
@Transactional (propagation = Propagation.REQUIRED,rollbackFor =
MyException. class
若在⽬标⽅法中抛出的异常是 rollbackFor 指定的异常的⼦类,事务同样会回滚。
4 、同⼀个类中⽅法调⽤,导致 @Transactional 失效
开发中避免不了会对同⼀个类⾥⾯的⽅法调⽤,⽐如有⼀个类 Test ,它的⼀个⽅法 A A 再调
⽤本类的⽅法 B (不论⽅法 B 是⽤ public 还是 private 修饰),但⽅法 A 没有声明注解事务,⽽ B
⽅法有。则外部调⽤⽅法 A 之后,⽅法 B 的事务是不会起作⽤的。这也是经常犯错误的⼀个地
⽅。
那为啥会出现这种情况?其实这还是由于使⽤ Spring AOP 代理造成的,因为只有当事务⽅法
被当前类以外的代码调⽤时,才会由 Spring ⽣成的代理对象来管理。
//@Transactional
@GetMapping ( "/test" )
private Integer A () throws Exception {
CityInfoDict cityInfoDict = new CityInfoDict();
cityInfoDict.setCityName( "2" );
/**
* B 插⼊字段为 3 的数据
*/
this .insertB();
/**
* A 插⼊字段为 2 的数据
*/
int insert = cityInfoDictMapper.insert(cityInfoDict);
return insert;
}
@Transactional ()
public Integer insertB () throws Exception {
CityInfoDict cityInfoDict = new CityInfoDict();
cityInfoDict.setCityName( "3" );
cityInfoDict.setParentCityId( 3 );
return cityInfoDictMapper.insert(cityInfoDict);
}
这种情况是最常见的⼀种 @Transactional 注解失效场景
@Transactional
private Integer A () throws Exception {
int insert = 0 ;
try {
CityInfoDict cityInfoDict = new CityInfoDict();
cityInfoDict.setCityName( "2" );
cityInfoDict.setParentCityId( 2 );
/**
* A 插⼊字段为 2 的数据
*/
insert = cityInfoDictMapper.insert(cityInfoDict);
/**
* B 插⼊字段为 3 的数据
*/
b.insertB();
} catch (Exception e) {
e.printStackTrace();
}
}
如果 B ⽅法内部抛了异常,⽽ A ⽅法此时 try catch B ⽅法的异常,那这个事务就不能正常回滚
了,会抛出异常:
org.springframework.transaction.UnexpectedRollbackException: Transaction
rolled back because it has been marked as rollback - only

 

28.Spring MVC 的核⼼组件?
1. DispatcherServlet :前置控制器,是整个流程控制的 核⼼ ,控制其他组件的执⾏,进⾏
统⼀调度,降低组件之间的耦合性,相当于总指挥。
2. Handler :处理器,完成具体的业务逻辑,相当于 Servlet Action
3. HandlerMapping DispatcherServlet 接收到请求之后,通过 HandlerMapping 将不同的请求
映射到不同的 Handler
4. HandlerInterceptor :处理器拦截器,是⼀个接⼜,如果需要完成⼀些拦截处理,可以实现
该接⼜。
5. HandlerExecutionChain :处理器执⾏链,包括两部分内容: Handler HandlerInterceptor
(系统会有⼀个默认的 HandlerInterceptor ,如果需要额外设置拦截,可以添加拦截器)。
6. HandlerAdapter :处理器适配器, Handler 执⾏业务⽅法之前,需要进⾏⼀系列的操作,
包括表单数据的验证、数据类型的转换、将表单数据封装到 JavaBean 等,这些操作都是
HandlerApater 来完成,开发者只需将注意⼒集中业务逻辑的处理上, DispatcherServlet
通过 HandlerAdapter 执⾏不同的 Handler
7. ModelAndView :装载了模型数据和视图信息,作为 Handler 的处理结果,返回给
DispatcherServlet
8. ViewResolver :视图解析器, DispatcheServlet 通过它将逻辑视图解析为物理视图,最终
将渲染结果响应给客户端。
29.Spring MVC 的⼯作流程?

1. 客户端向服务端发送⼀次请求,这个请求会先到前端控制器 DispatcherServlet( 也叫中央控
制器 )
2. DispatcherServlet 接收到请求后会调⽤ HandlerMapping 处理器映射器。由此得知,该请求该
由哪个 Controller 来处理(并未调⽤ Controller ,只是得知)
3. DispatcherServlet 调⽤ HandlerAdapter 处理器适配器,告诉处理器适配器应该要去执⾏哪个
Controller
4. HandlerAdapter 处理器适配器去执⾏ Controller 并得到 ModelAndView( 数据和视图 ) ,并层层
返回给 DispatcherServlet
5. DispatcherServlet ModelAndView 交给 ViewReslover 视图解析器解析,然后返回真正的视
6. DispatcherServlet 将模型数据填充到视图中
7. DispatcherServlet 将结果响应给客户端
Spring MVC 虽然整体流程复杂,但是实际开发中很简单,⼤部分的组件不需要开发⼈员创
建和管理,只需要通过配置⽂件的⽅式完成配置即可,真正需要开发⼈员进⾏处理的只有
Handler Controller View Model
当然我们现在⼤部分的开发都是前后端分离, Restful 风格接⼜,后端只需要返回 Json 数据就⾏
了。

 

1. 客户端向服务端发送⼀次请求,这个请求会先到前端控制器 DispatcherServlet
2. DispatcherServlet 接收到请求后会调⽤ HandlerMapping 处理器映射器。由此得知,该请求该
由哪个 Controller 来处理
3. DispatcherServlet 调⽤ HandlerAdapter 处理器适配器,告诉处理器适配器应该要去执⾏哪个
Controller
4. Controller 被封装成了 ServletInvocableHandlerMethod HandlerAdapter 处理器适配器去执⾏
invokeAndHandle ⽅法,完成对 Controller 的请求处理
5. HandlerAdapter 执⾏完对 Controller 的请求,会调⽤ HandlerMethodReturnValueHandler 去处
理返回值,主要的过程:
5.1. 调⽤ RequestResponseBodyMethodProcessor ,创建 ServletServerHttpResponse Spring
原⽣ ServerHttpResponse 的封装)实例
5.2. 使⽤ HttpMessageConverter write ⽅法,将返回值写⼊ ServletServerHttpResponse
OutputStream 输出流中
5.3. 在写⼊的过程中,会使⽤ JsonGenerator (默认使⽤ Jackson 框架)对返回值进⾏ Json
列化
6. 执⾏完请求后,返回的 ModealAndView null ServletServerHttpResponse ⾥也已经写⼊了
响应,所以不⽤关⼼ View 的处理

Spring Boot 约定⼤于配置 核⼼思想开展⼯作,相⽐ Spring 具有如下优势:
1. Spring Boot 可以快速创建独⽴的 Spring 应⽤程序。
2. Spring Boot 内嵌了如 Tomcat Jetty Undertow 这样的容器,也就是说可以直接跑起来,⽤
不着再做部署⼯作了。
3. Spring Boot ⽆需再像 Spring ⼀样使⽤⼀堆繁琐的 xml ⽂件配置。
4. Spring Boot 可以⾃动配置 ( 核⼼ )Spring SpringBoot 将原有的 XML 配置改为 Java 配置,将
bean 注⼊改为使⽤注解注⼊的⽅式 (@Autowire) ,并将多个 xml properties 配置浓缩在⼀个
appliaction.yml 配置⽂件中。
5. Spring Boot 提供了⼀些现有的功能,如量度⼯具,表单数据验证以及⼀些外部配置这样
的⼀些第三⽅功能。
6. Spring Boot 可以快速整合常⽤依赖(开发库,例如 spring-webmvc jackson-json
validation-api tomcat 等),提供的 POM 可以简化 Maven 的配置。当我们引⼊核⼼依赖时,
SpringBoot 会⾃引⼊其他依赖。

32.SpringBoot ⾃动配置原理了解吗?
SpringBoot 开启⾃动配置的注解是 @EnableAutoConfiguration ,启动类上的注解
@SpringBootApplication 是⼀个复合注解,包含了 @EnableAutoConfiguration

EnableAutoConfiguration 只是⼀个简单的注解,⾃动装配核⼼功能的实现实际是通
AutoConfigurationImportSelector
@AutoConfigurationPackage // main 同级的包下的所有组件注册到容器中
@Import ({ AutoConfigurationImportSelector . class }) // 加载⾃动装配类
xxxAutoconfiguration
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY =
"spring.boot.enableautoconfiguration" ;
Class <?> [] exclude () default {};
String [] excludeName () default {};
}
AutoConfigurationImportSelector 实现了 ImportSelector 接⼜,这个接⼜的作
⽤就是收集需要导⼊的配置类,配合 @Import( 就可以将相应的类导⼊到 Spring 容器中
获取注⼊类的⽅法是 selectImports() ,它实际调⽤的是 getAutoConfigurationEntry
这个⽅法是获取⾃动装配类的关键,主要流程可以分为这么⼏步:
1. 获取注解的属性,⽤于后⾯的排除
2. 获取所有需要⾃动装配的配置类的路径 :这⼀步是最关键的,从 META
INF/spring.factories 获取⾃动配置类的路径
3. 去掉重复的配置类和需要排除的重复类,把需要⾃动加载的配置类的路径存储起来
protected AutoConfigurationImportSelector.AutoConfigurationEntry
getAutoConfigurationEntry (AnnotationMetadata annotationMetadata) {
if ( ! this .isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
//1. 获取到注解的属性
AnnotationAttributes attributes =
this .getAttributes(annotationMetadata);
//2. 获取需要⾃动装配的所有配置类,读取 META-INF/spring.factories ,获取
⾃动配置类路径
List < String > configurations =
this .getCandidateConfigurations(annotationMetadata, attributes);
//3.1. 移除重复的配置
configurations = this .removeDuplicates(configurations);
//3.2. 处理需要排除的配置
Set < String > exclusions =
this .getExclusions(annotationMetadata, attributes);
this .checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations =
this .getConfigurationClassFilter().filter(configurations);
this .fireAutoConfigurationImportEvents(configurations,
exclusions);
return new
AutoConfigurationImportSelector.AutoConfigurationEntry(configurations,
exclusions);
}
}
 
34.Springboot 启动原理?
SpringApplication 这个类主要做了以下四件事情:
1. 推断应⽤的类型是普通的项⽬还是 Web 项⽬
2. 查找并加载所有可⽤初始化器 , 设置到 initializers 属性中
3. 找出所有的应⽤程序监听器,设置到 listeners 属性中
4. 推断并设置 main ⽅法的定义类,找到运⾏的主类

什么是微服务?
1. 2014 Martin Fowler 提出的⼀种新的架构形式。微服务架构是⼀种 架构模式 ,提倡将
单⼀应⽤程序划分成⼀组⼩的服务,服务之间相互协调,互相配合,为⽤户提供最终价
值。每个服务运⾏在其独⽴的进程中,服务与服务之间采⽤轻量级的通信机制 ( HTTP
Dubbo) 互相协作,每个服务都围绕着具体的业务进⾏构建,并且能够被独⽴的部署到⽣产
环境中,另外,应尽量避免统⼀的,集中式的服务管理机制,对具体的⼀个服务⽽⾔,应
根据业务上下⽂,选择合适的语⾔、⼯具 ( Maven) 对其进⾏构建。
2. 微服务化的核⼼就是将传统的⼀站式应⽤,根据业务拆分成⼀个⼀个的服务,彻底地去耦
合,每⼀个微服务提供单个业务功能的服务,⼀个服务做⼀件事情,从技术⾓度看就是⼀
种⼩⽽独⽴的处理过程,类似进程的概念,能够⾃⾏单独启动或销毁,拥有⾃⼰独⽴的数
据库。

微服务架构主要要解决哪些问题?
1. 服务很多,客户端怎么访问,如何提供对外⽹关 ?
2. 这么多服务,服务之间如何通信 ? HTTP 还是 RPC?
3. 这么多服务,如何治理 ? 服务的注册和发现。
4. 服务挂了怎么办?熔断机制。

有哪些主流微服务框架?
1. Spring Cloud Netflix
2. Spring Cloud Alibaba
3. SpringBoot + Dubbo + ZooKeeper

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值