Java 八股文
Java 八股文
Java 基础
基础概念与常识
Java 语言的特点
- 简单易学
- 面向对象(封装、继承、多态)
- 安全性(Java语言提供了多种资源权限访问修饰符、限制程序直接访问操作系统资源)
- 可靠性(异常处理和自动内存管理机制)
- 跨平台性(依靠JVM,Java语言可以实现一次编译,处处运行的机制)
Java SE vs Java EE
- Java SE:Java编程语言的标准版,包含支持了应用程序开发和运行的核心类库和虚拟机等核心组件,用于构建桌面应用程序和简单的服务器应用程序。
- Java EE:Java编程语言的企业版,包含支持了企业级应用程序的开发和部署规范,如Servlet、JSP、JDBC、JPA等,用于构建企业级应用程序和 Web 应用。
JVM vs JDK vs JRE
- JVM(Java virtual machine):Java 虚拟机,是运行 Java 字节码的虚拟机,JVM对于不同的操作系统有特定的实现,以保证相同的字节码文件给出相同的结果。
- JDK(Java Development Kit):Java开发工具包。是提供开发者使用的Java SDK,能够创建和编译 Java 程序的开发套件。它不仅包含了 JRE ,还包含了编译 Java 源码的编译器 javac 以及一些其他工具比如 javadoc(文档注释工具)、jdb(调试器)、jconsole(基于 JMX 的可视化监控工具)、javap(反编译工具)等等。
- JRE(Java Runtime Environment):是 Java 运行环境。它是运行已编译 Java 程序所需所有内容的集合,其中包括 JVM 和 Java 基础类库。
总结:也就是说,JRE 是 Java 运行时环境,仅包含 Java 应用程序的运行时环境和必要的类库。而 JDK 则包含了 JRE,同时还包括了 javac、javadoc、jdb、jconsole、javap 等工具,可以用于 Java 应用程序的开发和调试。如果需要进行 Java 编程工作,比如编写和编译 Java 程序、使用 Java API 文档等,就需要安装 JDK。而对于某些需要使用 Java 特性的应用程序,如 JSP 转换为 Java Servlet、使用反射等,也需要 JDK 来编译和运行 Java 代码。因此,即使不打算进行 Java 应用程序的开发工作,也有可能需要安装 JDK。
什么是字节码?采用字节码的好处?
字节码就是 JVM 能够理解的代码(.class 后缀文件),它不面向任何特定的处理器,只面向虚拟机。Java 语言采用字节码的方式在一定程度上解决了传统解释型语言运行慢的问题,同时还能达到一次编译处处运行的效果。
Java 程序从源代码到运行的过程:
为什么说 Java 语言编译和解释并存?
因为 Java 语言同时具有编译型语言和解释型语言的特性,简单来说执行 Java 程序过程中经历了编译阶段和解释阶段。因为 javac 将 Java 源代码编译为字节码文件,后通过 JVM 解释字节码文件和该平台的机器码。
JIT 编译模式
JIT 编译器将字节码转换为本地机器码,并缓存以提高程序的性能。这种编译技术允许 Java 程序在运行时达到接近本地编译程序的性能。
AOT 有什么优点?为什么不全部使用 AOT?
AOT 优点
JDK 9 引入了新的编译模式 AOT(Ahead of Time Compilation)。它会在程序执行前将其编译成机器码,属于静态编译。AOT 在一定程度上避免了 JIT 在预热等方面的开销,提高 Java 程序的启动速度,避免预热时间长,而且 AOT 编译后的代码不易被反编译和修改,特别适合云原生场景。
为什么不全部使用 AOT?
虽然 AOT 优点有很多且特别适合云原生场景,但是其无法支持 Java 的一些动态特性如 反射、动态代理、动态加载等。然而很多框架和库都用到了此特性,如果全部使用 AOT 就无法使用这些强大的框架和库了。
举个例子,CGLIB 动态代理使用的是 ASM 技术,而这种技术大致原理是运行时直接在内存中生成并加载修改后的字节码文件也就是
.class
文件,如果全部使用 AOT 提前编译,也就不能使用 ASM 技术了。为了支持类似的动态特性,所以选择使用 JIT 即时编译器。
Java 和 C++ 的区别?
- Java 不提供指针直接访问内存,程序内存更安全
- Java 仅支持单继承,C++支持多继承,但是 Java 可以通过接口实现多继承
- Java 有内存管理垃圾回收机制,开发者无需手动释放无用内存
- Java仅支持方法重载,C++支持方法和操作符重载
基本语法
Java 注解有几种形式
- 单行注释
- 多行注释
- 文档注释
标识符和关键字的区别是什么?
- 标识符就是名字
- 关键字是被语言赋予了特殊含义的标识符
Java 语言关键字有哪些?
分类 | 关键字 | ||||||
---|---|---|---|---|---|---|---|
访问控制 | private | protected | public | ||||
类,方法和变量修饰符 | abstract | class | extends | final | implements | interface | native |
new | static | strictfp | synchronized | transient | volatile | enum | |
程序控制 | break | continue | return | do | while | if | else |
for | instanceof | switch | case | default | assert | ||
错误处理 | try | catch | throw | throws | finally | ||
包相关 | import | package | |||||
基本类型 | boolean | byte | char | double | float | int | long |
short | |||||||
变量引用 | super | this | void | ||||
保留字 | goto | const |
default
关键字既属于程序控制,也属于类、方法、变量修饰符、访问控制(添加 default 访问修饰符就会报错)- 虽然
true
,false
, 和null
看起来像关键字但实际上他们是字面值,同时你也不可以作为标识符来使用。
自增自减运算符
- 运算符在前面:先 +1 / -1,再使用
- 运算符在后面:先使用,后 +1 / -1
移位运算符
移位运算符是最基本的运算符之一。移位操作中,被操作的数据被视为二进制数,移位就是将其向左或向右移动若干位的运算。
1 | JAVA |
在 Java 代码里使用 <<
(左移)、 >>
(带符号右移) 和>>>
(无符号右移)转换成的指令码运行起来会更高效些。
<<
:左移运算符,向左移若干位,高位丢弃,低位补零。x << 1
,相当于 x 乘以 2(不溢出的情况下)。>>
:带符号右移,向右移若干位,高位补符号位,低位丢弃。正数高位补 0,负数高位补 1。x >> 1
,相当于 x 除以 2。>>>
:无符号右移,忽略符号位,空位都以 0 补齐。由于
double
,float
在二进制中的表现比较特殊,因此不能来进行移位操作。移位操作符实际上支持的类型只有
int
和long
,编译器在对short
、byte
、char
类型进行移位前,都会将其转换为int
类型再操作。
如果移位的位数超过数值所占有的位数会怎样?
当 int 类型左移/右移位数大于等于 32 位操作时,会先求余(%)后再进行左移/右移操作。也就是说左移/右移 32 位相当于不进行移位操作(32%32=0),左移/右移 42 位相当于左移/右移 10 位(42%32=10)。当 long 类型进行左移/右移操作时,由于 long 对应的二进制是 64 位,因此求余操作的基数也变成了 64。
也就是说:x<<42
等同于x<<10
,x>>42
等同于x>>10
,x >>>42
等同于x >>> 10
。
continue、break 和 return 的区别是什么?
continue
:指跳出当前的这一次循环,继续下一次循环。break
:指跳出整个循环体,继续执行循环下面的语句。return
JAVA /**1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
用于跳出所在方法,结束该方法的运行
- `return;`:直接使用 return 结束方法执行,用于没有返回值函数的方法
- `return value;`:返回一个特定值,用于有返回值函数的方法
#### 基本数据类型
Java 中一共有8中基本数据类型:
- 6 种数字类型:
- 4 种整数型:`byte`、`short`、`int`、`long`
- 2 种浮点型:`float`、`double`
- 1 种字符类型:`char`
- 1 种布尔型:`boolean`。
## Spring 框架篇
### 基础
#### Spring 是什么?特性?有哪些模块?
Spring 是一个轻量级、非侵入式的控制反转(IOC)和面向切面(AOP)编程的框架。
##### Spring 特性
1. IOC 和 DI 支持
2. AOP 编程支持
3. 声明式事务支持
4. 快捷测试支持
5. 快速集成功能
6. 复杂 API 模块封装
##### Spring 有哪些模块?
[![Spring模块划分](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/sidebar/sanfene/spring-bb7c13ea-3174-4b32-84b8-821849ddc377.png)](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/sidebar/sanfene/spring-bb7c13ea-3174-4b32-84b8-821849ddc377.png)
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:面向切面编程
#### Spring 常用注解
[![Spring常用注解](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/sidebar/sanfene/spring-8d0a1518-a425-4887-9735-45321095d927.png)](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/sidebar/sanfene/spring-8d0a1518-a425-4887-9735-45321095d927.png)
##### Web:
##### 容器:
- @Component:声明此类是一个组件,成为 Spring 管理的 Bean,由 Spring 指定和管理其初始化和生命周期。当使用基于注解的配置和类路径扫描时,这些类被视为自动检测的候选对象
- @Service:应用在 service 层,结合了 @Component注解
- @Respository:应用在 dao 层,结合了 @Component注解
- @Autowired:Spring 提供的工具(由 Spring 依赖注入工具 BeanPostProcessor、BeanFactoryPostProcessor 自动注入)
- @Qualifier:
- @Configuration:声明当前类为配置类,相当于 Spring 配置的 xml 文件
- @Value:通常与 @Autowired 注解一起使用,当出现了两个类型一样的 Bean,通过此注解声明要使用 Bean 的名称以选择想用的 Bean
- @Bean:
- @Scope:指定采用什么模式去创建 Bean(前提有 @Bean),其设置类型包括:Singleton、Prototype、Request、Session、GlobalSession
##### AOP:
@Aspect:声明一个切面(类上),使用@After、@Before、@Around 定义建言(advice),可直接将拦截规则(切点)作为参数。
- @After:方法执行之后执行(方法上)
- @Before:方法执行之前执行(方法上)
- @Around:方法执行之前和之后执行(方法上)
- @PointCut:声明切点,在配置类上添加 @EnableAspectJAutoProxy 注解开启 Spring 对 AspectJ 的代理支持
##### 事务:
@Transactional:在要开启事务的方法上添加此注解即可开启声明式事务
#### Spring 中用了哪些设计模式
1. 工厂模式:Spring 本身就是一个巨大的工厂,通过 BeanFactory、ApplicationContext 创建 Bean 对象
2. 代理模式:Spring AOP 就是通过代理模式实现的,分为动态代理和静态代理
3. 单例模式:Spring 中的 Bean 默认都是单例的,便于管理
4. 模板模式:Spring 中 JbbcTemplate、RestTemplate 等以 Template 结果的对数据库、网络操作的模板类,都用到了模板模式
5. 观察者模式:Spring 事件驱动模型就是观察者模式很经典的一个应用
6. 策略模式:Spring 中有一个 Resource 接口,它的不同实现类会根据不同的策略去访问资源
7. 适配器模式:Spring AOP 的增强和通知使用到了适配器模式、Spring MVC 中也是用到了适配器模式适配 Controller
### IoC
#### 什么是 IoC?什么是 DI?
- IoC 即控制反转,是一种设计思想,就是由容器控制对象的生命周期和对象之间的关系。(控制对象的生命周期不再是引用它的对象而是容器,将对象的控制权由程序员转变为 Spring 框架)
- DI 是实现 IoC 的一种方式,通过容器注入对象之间的依赖关系,而不是在对象内部自行创建依赖对象。
##### 依赖注入的 4 种实现方式
1. 接口注入
2. 构造方法注入
3. 注解注入
4. 字段注入
####
#### Spring IOC 的实现机制
#### @Component 和 @Bean 的区别?
- @Component 作用于类上,@Bean作用于方法上
- @Component 通过类路径扫描来自动侦测和自动装配到 Spring 容器中,而 @Bean 是我们在某个方法上添加此注解以定义和产生这个 Bean,告诉 Spring 这是某个类的实例
- @Bean 比 @Component 自定义性更强(更灵活),比如我们引入第三方库中的类时只能通过 @Bean 来注册
###
#### @Autowired 和 @Resource 的区别
- @Autowired 属于 Spring 内置的注解,默认注入方式为 byType,但是可以通过 @Qualifier 注解显式指明要注入 Bean 的名称
- @Resource 属于 JDK 注解,默认注入方式为 byName,如果无法通过名称找到对应的 Bean,注入方式会变为 byType
- @Autowired 支持在构造方法、方法、字段和参数上使用,而 @Resource 仅支持在字段和方法上使用
#### Bean 的作用域有哪些?
- singleton:IoC 容器中只有唯一的 bean 实例。(Spring 中的 bean 默认都是单例的)
- prototype:每次获取都会创建一个新的 bean
- request(仅 Web 应用可用):每一次 HTTP 请求都会产生一个新的 bean(请求 bean),该 bean 仅在当前 HTTP request 内有效
- session(仅 Web 应用可用):每一次来自新 session 的 HTTP 请求都会产生一个新的 bean(会话 bean),该 bean 仅在当前 HTTP session 内有效
- application/global-session(仅 Web 应用可用):每个 web 应用在启动时创建一个 bean(应用 bean),该 bean 仅在当前应用启动时间内有效
- websocket(仅 Web 应用可用):每一次 websocket 会话产生一个新的的 bean
#### Bean 是线程安全的吗?
Spring 框架中的 Bean 是否安全取决于其作用于和状态。
以 singleton 和 prototype 两种作用域为例,prototype 作用域不存在线程安全问题,因为每次获取都会创建一个新的 Bean。
而有状态的 bean(包含可变成员变量的对象) 存在线程安全问题,反之不存在线程安全问题。
**常见有两种解决有状态 bean 的线程安全问题的方法:**
1. 在 Bean 中尽量避免定义可变的成员变量
2. 在类中定义一个 ThreadLocal 成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐)
#### Bean 的生命周期
1. **实例化(Instantiation):** 在这个阶段,Spring 容器根据配置信息创建 Bean 的实例。通常情况下,Spring 使用反射机制来实例化 Bean,但也可以通过工厂方法或其他方式创建 Bean 实例。
2. **设置属性(Population):** 在实例化后,Spring 容器会通过依赖注入或其他方式为 Bean 设置属性,包括基本类型属性、引用类型属性以及其他配置属性。
3. 初始化(Initialization):
初始化阶段是 Bean 生命周期中的关键阶段,包括以下两个步骤:
- **Bean 初始化方法调用(Initialization callback):** 在这个步骤中,Spring 调用 Bean 的初始化方法,可以通过配置 XML、注解或接口来指定初始化方法。常用的初始化方法包括 `@PostConstruct` 注解、`InitializingBean` 接口的 `afterPropertiesSet()` 方法以及自定义的初始化方法。
- **BeanPostProcessor 处理(BeanPostProcessor callback):** 在初始化方法调用之前和之后,Spring 容器会调用注册的 `BeanPostProcessor` 实现类对 Bean 进行处理。`BeanPostProcessor` 接口提供了 `postProcessBeforeInitialization()` 和 `postProcessAfterInitialization()` 两个方法,允许开发者在 Bean 初始化前后进行一些自定义的处理逻辑,如增强、代理等。
4. **使用(In Use):** 在初始化完成后,Bean 就处于可用状态,可以被其他 Bean 或应用程序组件使用。
5. 销毁(Destruction):
当应用程序关闭或者不再需要某个 Bean 时,Spring 容器会执行 Bean 的销毁操作。销毁阶段包括以下两个步骤:
- **Bean 销毁方法调用(Destruction callback):** 类似于初始化方法,在 Bean 销毁前,Spring 容器会调用 Bean 的销毁方法,可以通过配置 XML、注解或接口来指定销毁方法。常用的销毁方法包括 `@PreDestroy` 注解、`DisposableBean` 接口的 `destroy()` 方法以及自定义的销毁方法。
- **BeanPostProcessor 处理(BeanPostProcessor callback):** 在销毁方法调用之前和之后,Spring 容器同样会调用注册的 `BeanPostProcessor` 实现类对 Bean 进行处理,允许开发者在销毁前后进行一些自定义的处理逻辑。
#### Spring 怎么解决循环依赖?
> 循环依赖仅发生在 singleton 作用域的 bean 之间,如果是 prototype 作用域的 bean 就会抛异常
Bean 的初始化步骤:
[![三分恶面渣逆袭:Bean初始化步骤](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/sidebar/sanfene/spring-867066f1-49d1-4e57-94f9-4c66a3a8797e.png)](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/sidebar/sanfene/spring-867066f1-49d1-4e57-94f9-4c66a3a8797e.png)
1. **实例化 Bean:** 当容器初始化 Bean 的时候,首先会实例化 Bean,但还未进行属性注入。
2. **属性注入:** 在实例化 Bean 后,Spring 容器会将 Bean 的引用注入到其他 Bean 的属性中。
3. 解决循环依赖:
当容器在注入属性时发现循环依赖时,Spring 会采取以下步骤来解决循环依赖:
- **提前暴露(Early Reference):** Spring 将正在创建的 Bean 提前暴露给第三级缓存,以便其他 Bean 可以提前引用。这样,即使 Bean 还未完全创建完成,其他 Bean 也可以获取到对它的引用。
- **使用代理(Proxy):** 当发现循环依赖时,Spring 会创建一个代理对象,代替真正的 Bean 对象注入到其他 Bean 中。这个代理对象负责延迟获取真正的 Bean 对象,从而打破循环依赖。
- **后处理器处理(Post-processing):** Spring 使用 BeanPostProcessor 后处理器对 Bean 进行处理,确保循环依赖问题得到解决。后处理器会在 Bean 的初始化前后进行一些额外的处理,以确保 Bean 的正确创建和初始化。
##### 为什么要三级缓存?二级不行吗?
不行,主要是为了生成**代理对象**
#### @Autowired 的实现原理?
通过后置处理器:AutowiredAnnotationBeanPostProcessor 完成的。
- Spring 在创建 bean 的过程中,最终会调用到 doCreateBean()方法,在 doCreateBean()方法中会调用 populateBean()方法,来为 bean 进行属性填充,完成自动装配等工作。
- 在 populateBean()方法中一共调用了两次后置处理器,第一次是为了判断是否需要属性填充,如果不需要进行属性填充,那么就会直接进行 return,如果需要进行属性填充,那么方法就会继续向下执行,后面会进行第二次后置处理器的调用,这个时候,就会调用到 AutowiredAnnotationBeanPostProcessor 的 postProcessPropertyValues()方法,在该方法中就会进行@Autowired 注解的解析,然后实现自动装配。
- 属性赋值
**/
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
//…………
if (hasInstAwareBpps) {
if (pvs == null) {
pvs = mbd.getPropertyValues();
}
PropertyValues pvsToUse;
for(Iterator var9 = this.getBeanPostProcessorCache().instantiationAware.iterator(); var9.hasNext(); pvs = pvsToUse) {
InstantiationAwareBeanPostProcessor bp = (InstantiationAwareBeanPostProcessor)var9.next();
pvsToUse = bp.postProcessProperties((PropertyValues)pvs, bw.getWrappedInstance(), beanName);
if (pvsToUse == null) {
if (filteredPds == null) {
filteredPds = this.filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
}
//执行后处理器,填充属性,完成自动装配
//调用InstantiationAwareBeanPostProcessor的postProcessPropertyValues()方法
pvsToUse = bp.postProcessPropertyValues((PropertyValues)pvs, filteredPds, bw.getWrappedInstance(), beanName);
if (pvsToUse == null) {
return;
}
}
}
}
//…………
}
1 |
|
JAVA
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
//@Autowired注解、@Inject和@Value注解的属性和方法
InjectionMetadata metadata = this.findAutowiringMetadata(beanName, bean.getClass(), pvs);
try {
//属性填充
metadata.inject(bean, beanName, pvs);
return pvs;
} catch (BeanCreationException var6) {
throw var6;
} catch (Throwable var7) {
throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", var7);
}
}
1 |
|
JAVA
public class AccountService {
private TransactionTemplate transactionTemplate;
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
public void transfer(final String out, final String in, final Double money) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
// 转出
accountDao.outMoney(out, money);
// 转入
accountDao.inMoney(in, money);
}
});
}
}
在上面的代码中,我们使用了 TransactionTemplate 来实现编程式事务,通过 execute 方法来执行事务,这样就可以在方法内部实现事务的控制。
##### 声明式事务管理
本质通过 AOP 功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,即在方法执行前开启事务,执行后提交或回滚事务。
缺点:无法像编程式事务那样作用到代码块级别,细粒度只能作用到方法级别。
#### Spring 的事务隔离级别?
1. ISOLATION_DEFAULT:使用后端数据库默认的隔离级别,MySQL 默认可重复读,Oracle 默认读已提交
2. ISOLATION_READ_UNCOMMITTED:读未提交
3. ISOLATION_READ_COMMITTED:读已提交
4. ISOLATION_REPEATABLE_READ:可重复读
5. ISOLATION_SERIALIZABLE:串行化
#### Spring 的事务传播机制
- REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。Spring 的默认传播行为。
- SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。
- MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
- REQUIRES_NEW:总是启动一个新的事务,如果当前存在事务,则将当前事务挂起。
- NOT_SUPPORTED:总是以非事务方式执行,如果当前存在事务,则将当前事务挂起。
- NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前事务不存在,则行为与 REQUIRED 一样。嵌套事务是一个子事务,它依赖于父事务。父事务失败时,会回滚子事务所做的所有操作。但子事务异常不一定会导致父事务的回滚。
**protected 和 private 修饰的方法能加事务吗?**
不能,因为 @Transactional 注解主要是通过 Spring AOP 实现的,而Spring AOP 又是通过 JDK 动态代理和 CGLib 动态代理实现的,它们只能代理公开方法
#### 声明式事务实现原理了解吗?
Spring 的声明式事务管理是通过 AOP(面向切面编程)和代理机制实现的。
第一步,**在 Bean 初始化阶段创建代理对象**:
Spring 容器在初始化单例 Bean 的时候,会遍历所有的 BeanPostProcessor 实现类,并执行其 postProcessAfterInitialization 方法。
在执行 postProcessAfterInitialization 方法时会遍历容器中所有的切面,查找与当前 Bean 匹配的切面,这里会获取事务的属性切面,也就是 `@Transactional` 注解及其属性值。
然后根据得到的切面创建一个代理对象,默认使用 JDK 动态代理创建代理,如果目标类是接口,则使用 JDK 动态代理,否则使用 Cglib。
第二步,**在执行目标方法时进行事务增强操作**:
当通过代理对象调用 Bean 方法的时候,会触发对应的 AOP 增强拦截器,声明式事务是一种环绕增强,对应接口为`MethodInterceptor`,事务增强对该接口的实现为`TransactionInterceptor`,类图如下:
[![图片来源网易技术专栏](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/sidebar/sanfene/spring-97493c7f-c596-4e98-a6a8-dab254d6d1ab.png)](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/sidebar/sanfene/spring-97493c7f-c596-4e98-a6a8-dab254d6d1ab.png)图片来源网易技术专栏
事务拦截器`TransactionInterceptor`在`invoke`方法中,通过调用父类`TransactionAspectSupport`的`invokeWithinTransaction`方法进行事务处理,包括开启事务、事务提交、异常回滚等。
#### 声明式事务何时会失效?
- @Transactional 应用在非 public 修饰的方法上
- @Transactional 注解属性 propagation、rollbackFor 设置错误
- 同一个类中方法调用,导致 @Transactional 失效
### Spring MVC
#### 对于Spring MVC 的理解
MVC 是模型、视图、控制器的简写,其核心思想是将业务逻辑、数据、显示分离来组织代码
#### Spring MVC 的核心组件有哪些?
1. **DispatcherServlet**:**核心的中央处理器,负责接收请求、分发,并给予客户端响应。**(前置控制器,是整个流程控制的**核心**,控制其他组件的执行,进行统一调度,降低组件之间的耦合性,相当于总指挥。)
2. **Handler**:处理器,完成具体的业务逻辑,相当于 Servlet 或 Action。
3. **HandlerMapping**:DispatcherServlet 接收到请求之后,通过 HandlerMapping 将不同的请求映射到不同的 Handler。
4. **HandlerInterceptor**:处理器拦截器,是一个接口,如果需要完成一些拦截处理,可以实现该接口。
5. **HandlerExecutionChain**:处理器执行链,包括两部分内容:Handler 和 HandlerInterceptor(系统会有一个默认的 HandlerInterceptor,如果需要设置额外拦截,可以添加拦截器)。
6. **HandlerAdapter**:处理器适配器,Handler 执行业务方法之前,需要进行一系列的操作,包括表单数据的验证、数据类型的转换、将表单数据封装到 Java Bean 等,这些操作都是由 HandlerApater 来完成,开发者只需将注意力集中业务逻辑的处理上,DispatcherServlet 通过 HandlerAdapter 执行不同的 Handler。
7. **ModelAndView**:装载了模型数据和视图信息,作为 Handler 的处理结果,返回给 DispatcherServlet。
8. **ViewResolver**:视图解析器,DispatcheServlet 通过它将逻辑视图解析为物理视图,最终将渲染结果响应给客户端。
#### Spring MVC 的工作流程?
① **发起请求**:客户端向服务器发送 HTTP 请求
② **前端控制器**:DispatcherServlet 接收到请求并将请求转发到对应的 Controller
③ **处理器映射**:DispatcherServlet 调用 HandlerMapping 并根据 URL 匹配对应的 Handler(Controller)
④ **处理器适配器**:一旦找到目标 Controller,DispatcherServlet 会使用 HandlerAdapter 来调用 Controller 的方法来处理请求。
⑤ **执行处理器**:Handler 完成用户请求后会返回 ModelAndView 对象,其中包括模型层和视图层
⑥ **视图解析器**:DispatcherServlet 接收到 ModelAndView 后,会使用 ViewResolver 来解析视图名称,找到具体的视图页面。
⑦ **渲染视图**:DispatcherServlet 把返回的 Model 传给 View
⑧ **响应结果**:DispatcherServlet 将视图结果返回给客户端。
**Spring MVC** 虽然整体流程复杂,但是实际开发中很简单,大部分的组件不需要我们开发人员创建和管理,真正需要处理的只有 **Controller** 、**View** 、**Model**。
> 在前后端分离的情况下,步骤 ⑥、⑦、⑧ 会略有不同,后端通常只需要处理数据,并将 JSON 格式的数据返回给前端就可以了,而不是返回完整的视图页面。
#### SpringMVC Restful 风格的接口的流程是什么样的呢?
1. 客户端向服务器发送 HTTP 请求
2. DispacherServlet 接收请求并将请求转发至对应的 Handler
3. DispacherServlet 调用 HandlerAdapter 根据 URL 找到对应的 Handler
4. Handler(Controller)被封装成了 ServletInvocableHandlerMethod,HandlerAdapter 执行 invokeAndHandler 方法,完成对 Controller 的请求处理
5. HandlerAdapter 执行完成对 Controller 的请求,会调用 HandlerMethodReturnValueHandler 处理返回值
- 调用 RequestResponseBodyMethodProcessor,创建 ServletServerHttpResoponse (Spring 对原生 ServerHttpResponse 的封装)实例
- 调用 HttpMessageConverter 的 write 方法,将返回值写入 ServletServerHttpResponse 的输出流中
6. 执行完请求后,返回的 ModelAndView 为 null,ServletServerHttpResponse 里已写入了响应,所以无需关心 View 的处理
### Spring Boot
#### Spring Boot 是什么?有什么优点?
Spring Boot 是一个开源的,用于快速初始化和构建 Spring 应用的脚手架,通过 yml/properties 文件即可配置 Spring、Spring MVC 等许多优秀框架
**优点?**
1. Spring Boot 内嵌 Tomcat、Jetty、Undertow 等容器,不需要在服务器上部署 WAR 包,直接执行 Jar 包就可以启动项目
2. 只需配置 yml/properties 一个配置文件就可以对多种框架进行配置,无需像传统配置 spring.yml、web.xml,Spring Boot 帮我们做了大部分初始工作。(例如只需在项目中引入 spring-boot-starter-web 依赖即可自动引入 tomcat 和 Spring MVC)
- 通过 yaml 管理约束应用的配置,properties 文件更加便捷和清晰
3. 提供了一系列的 starter 方便集成常用框架,如 Spring Data JPA、Spring Security、MyBatis 、Spring Cloud 等
#### Spring Boot 和 Spring MVC 的区别?
Spring Boot 是一个快速初始化、构建 Spring 的脚手架,能够内嵌 web 容器和集成第三方框架,简化开发步骤提升开发效率
Spring MVC 是 Spring 的一个模块,提供了 MVC 的开发模式,帮助我们构建健壮完善的 web 项目
#### SpringBoot 自动配置原理?
> - 在 Spring 中,自动装配是指容器利用反射机制根据 Bean 的名称、类型(可通过 xml 文件注解来指定装配模式)等自动注入所需的依赖
> - 可以通过 @Autowired、@Resource 等注解来指定成员变量或方法需要被装配
> - @EnableAutoConfiguration 开启自动装配(@SpringBootApplication 包含乐此注解)
1. @EnableAutoConfiguration 的背后是一个非常复杂的自动装配机制,其核心是 AutoConfigurationImportSelector 类
2. AutoConfigurationImportSelector 类实现了 ImportSelector 接口,这个接口的功能主要就是收集需要导入的配置类,配合 @Import 将相应的类导入到 Spring 中
3. 通过 selectImports 方法获取注入类,其实际调用的是 getAutoConfigurationEntry,这个方法是获取自动装配类的关键
[![三分恶面渣逆袭:SpringBoot自动配置原理](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/sidebar/sanfene/spring-df77ee15-2ff0-4ec7-8e65-e4ebb8ba88f1.png)](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/sidebar/sanfene/spring-df77ee15-2ff0-4ec7-8e65-e4ebb8ba88f1.png)
#### 如何自定义一个 Spring Boot starter?
#### Spring Boot 的启动原理?
1. 判断项目是普通项目还是 web 项目
2. 查找并加载所有可用的初始化器,设置到 initializers 属性中
3. 查找所有的应用程序监听器,设置到 listeners 属性中
4. 找到运行的主类 => Spring Boot 启动类