抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

这里是有关框架的八股

1.基础

Spring 是一个轻量级、非入侵式的控制反转 (IoC) 和面向切面 (AOP) 的框架。

1.Spring用到哪些设计模式

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

2.Spring的基本特性

  1. IoC容器
  2. AOP
  3. 事务管理 :Spring提供了一致的事务管理接口,支持声明式和编程式事务。 开发者可以轻松地进行事务管理,而无需关心具体的事务API。
  4. MVC框架: Spring MVC是一个基于Servlet API构建的Web框架, 采用了模型-视图-控制器(MVC)架构。

3.常用的注解

1.Web开发相关注解

  1. @Controller:用于标注控制层组件。
  2. @RestController:是@Controller 和 @ResponseBody 的结合体,返回 JSON 数据时使用。
  3. @RequestMapping:用于映射请求 URL 到具体的方法上,还可以细分为:
    1. @GetMapping:只能用于处理 GET 请求
    2. @PostMapping:只能用于处理 POST 请求
    3. @DeleteMapping:只能用于处理 DELETE 请求
  4. @ResponseBody:直接将返回的数据放入 HTTP 响应正文中,一般用于返回 JSON 数据。
  5. @RequestBody:表示一个方法参数应该绑定到 Web 请求体。
  6. @PathVariable:用于接收路径参数,比如 @RequestMapping(“/hello/{name}”),这里的 name 就是路径参数。
  7. @RequestParam:用于接收请求参数。比如 @RequestParam(name = "key") String key,这里的 key 就是请求参数。

2.容器相关注解

  1. @Component:标识一个类为 Spring 组件,使其能够被 Spring 容器自动扫描和管理。
  2. @Service:标识一个业务逻辑组件(服务层)。比如 @Service("userService"),这里的 userService 就是 Bean 的名称。
  3. @Repository:标识一个数据访问组件(持久层)。
  4. @Autowired:按类型自动注入依赖。
  5. @Configuration:用于定义配置类,可替换 XML 配置文件。

xml配置和注解配置bean

3.AOP相关注解

  1. @After:在方法执行之后执行。
  2. @Before:在方法执行之前执行。
  3. @Around:方法前后均执行。
  4. @PointCut:定义切点,指定需要拦截的方法。

4.事务相关注解

主要就是 @Transactional,用于声明一个方法需要事务支持。

2.IOC

1.IOC和DI

IOC(lnversion Of Controll,控制反转)是一种设计思想,就是将原本在程序中手动创建对象的控制权,交由给Spring框架来管理

IOC 负责创建对象,管理对象(通过依赖注入(DI),装配对象,配置对象,并且管理这些对象的整个生命周期。

IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种对象。

Spring 中的 IoC 的实现原理就是工厂模式加反射机制。

依赖注入:依赖注入和控制反转恰恰相反,它是一种具体的编码技巧。我们不通过 new 的方式在类内部创建依赖类的对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等方式传递(或注入)给类来使用。

什么是Spring Bean?

简单来说,Bean 代指的就是那些被 IoC 容器所管理的对象。

我们需要告诉 IoC 容器帮助我们管理哪些对象,这个是通过配置元数据来定义的。配置元数据可以是 XML 文件、注解或者 Java 配置类。

以下是牛客一个回答

spring ioc是spring两大核心之一,spring为我们提供了一个ioc容器,也就是beanFactory,同时,ioc有个非常强大的功能,叫做di,也就是依赖注入,我们可以通过配置或者xml文件的方式将bean所依赖的对象通过name(名字)或者type(类别)注入进这个beanFactory中,正因为这个依赖注入,实现类与依赖类之间的解耦,如果在一个复杂的系统中,类之间的依赖关系特别复杂,首先,这非常不利于后期代码的维护,ioc就很好的帮助我们解决了这个问题,它帮助我们维护了类与类之间的依赖关系,降低了耦合性,使我们的类不需要强依赖于某个类,而且,在spring容器启动的时候,spring容器会帮助我们自动的创建好所有的bean,这样,我们程序运行的过程中就不需要花费时间去创建这些bean,速度就快了许多。

2.注入Bean的方式

依赖注入 (Dependency Injection, DI) 的常见方式:

  1. 构造函数注入:通过类的构造函数来注入依赖项。
  2. Setter 注入:通过类的 Setter 方法来注入依赖项。
  3. Field(字段) 注入:直接在类的字段上使用注解(如 @Autowired@Resource)来注入依赖项。
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
1、构造函数注入
@Service
public class UserService {

private final UserRepository userRepository;

public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}

//...
}

2、Setter注入
@Service
public class UserService {

private UserRepository userRepository;

// 在 Spring 4.3 及以后的版本,特定情况下 @Autowired 可以省略不写
@Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}

//...
}

3、字段注入
@Service
public class UserService {

@Autowired
private UserRepository userRepository;

//...
}

构造器注入:推荐使用的方式,适用于所有必需的依赖项,确保依赖项在对象创建时已被注入。构造函数注入适合处理必需的依赖项

Setter 注入:适用于可选的依赖项,灵活性较高,但不如构造器注入安全。 Setter 注入 则更适合可选的依赖项,这些依赖项可以有默认值或在对象生命周期中动态设置。

字段注入:最简洁的方式,适用于小项目或快速开发,但不推荐用于大规模项目,因其可维护性差。

3.@Resource和@Autowired 的区别

这两个都是注入Bean的注解

都是用来自动装配的,都可以放在属性的字段上

  • @Autowired: 是 Spring 提供的注解, 默认通过 byType(根据类型匹配)的方式注入,而且必须要求这个对象存在!
  • @Resource:是 JDK 提供的注解,默认通过 byName(根据名称进行匹配)的方式实现,如果找不到名字,则通过 byType注入
    • @Resource 有两个比较重要且日常开发常用的属性:name(名称)、type(类型)。

当一个接口存在多个实现类的情况下,@Autowired 和@Resource都需要通过名称才能正确匹配到对应的Bean。

当容器中存在多个相同类型的 bean,并希望仅使用属性装配其中一个 bean 时, 可以使用@Qualifier 注解和 @Autowired 通过指定应该装配哪个确切的 bean 来消除歧义。

或者通过@Resource的 name 属性来显式指定名称。

4.Bean的作用域

重点关注前两种

  • singleton : IoC 容器中只有唯一的 bean 实例。Spring 中的 bean 默认都是单例的,是对单例设计模式的应用。
  • prototype : 每次获取都会创建一个新的 bean 实例。也就是说,连续 getBean() 两次,得到的是不同的 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。

5.Bean是线程安全的吗

总结:默认singleton作用域下是单例的,但如果bean是有状态的,可能存在线程安全问题。如果是prototype作用域,不是单例的,每次获取的Bean不同,不会有线程安全问题。

Spring 框架中的 Bean 是否线程安全,取决于其作用域和状态。

我们这里以最常用的两种作用域 prototype 和 singleton 为例介绍。几乎所有场景的 Bean 作用域都是使用默认的 singleton ,重点关注 singleton 作用域即可。

prototype 作用域下,每次获取都会创建一个新的 bean 实例,不存在资源竞争问题,所以不存在线程安全问题。singleton 作用域下,IoC 容器中只有唯一的 bean 实例,可能会存在资源竞争问题(取决于 Bean 是否有状态)。如果这个 bean 是有状态的话,那就存在线程安全问题(有状态 Bean 是指包含可变的成员变量的对象)。

有状态 Bean 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 定义了一个购物车类,其中包含一个保存用户的购物车里商品的 List
@Component
public class ShoppingCart {
private List<String> items = new ArrayList<>();

public void addItem(String item) {
items.add(item);
}

public List<String> getItems() {
return items;
}
}

不过,大部分 Bean 实际都是无状态(没有定义可变的成员变量)的(比如 Dao、Service),这种情况下, Bean 是线程安全的。

对于有状态单例 Bean 的线程安全问题,常见的三种解决办法是:

  1. 避免可变成员变量: 尽量设计 Bean 为无状态。

  2. 使用ThreadLocal: 将可变成员变量保存在 ThreadLocal 中,确保线程独立。

  3. 使用同步机制: 利用 synchronizedReentrantLock 来进行同步控制,确保线程安全。

6.bean 的生命周期

概括:实例化 —> 属性赋值 —> 初始化 —> 销毁

beanLife

  1. 实例化:实例化一个 bean 对象;Bean 容器首先会找到配置文件中的 Bean 定义,然后使用 Java 反射 API 来创建 Bean 的实例。

  2. 属性赋值:为 Bean 设置相关属性和依赖,例如@Autowired 等注解注入的对象、@Value 注入的值、setter方法或构造函数注入依赖和值、@Resource注入的各种资源。

  3. 初始化:第 3~7 步,步骤较多,其中第 5、6 步为初始化操作,第 3、4 步为在初始化前执行,第 7 步在初始化后执行,该阶段结束,才能被用户使用;

    • Aware

      • BeanNameAware:注入当前 bean 对应 beanName;

      • BeanClassLoaderAware:注入加载当前 bean 的 ClassLoader;

      • BeanFactoryAware:注入 当前BeanFactory容器 的引用。

        如果 Bean 实现了 BeanNameAware 接口,调用 setBeanName()方法,传入 Bean 的名字。

        如果 Bean 实现了 BeanClassLoaderAware 接口,调用 setBeanClassLoader()方法,传入 ClassLoader对象的实例。

        如果 Bean 实现了 BeanFactoryAware 接口,调用 setBeanFactory()方法,传入 BeanFactory对象的实例。

    • 如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行postProcessBeforeInitialization() 方法

    • 如果 Bean 实现了InitializingBean接口,执行afterPropertiesSet()方法。

    • 如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。

    • 如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行postProcessAfterInitialization() 方法。

  4. 销毁:第 8~10步,第8步不是真正意义上的销毁(还没使用呢),而是先在使用前注册了销毁的相关调用接口,为了后面第9、10步真正销毁 bean 时再执行相应的方法。

    • 如果 Bean 实现了 DisposableBean 接口,执行 destroy() 方法。
    • 如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的 Bean 销毁方法。

如果想在Bean加载/销毁前后,实现某些逻辑,可以在Bean中实现init-methoddestory-method

记忆:

初始化这一步涉及到的步骤比较多,包含 Aware 接口的依赖注入、BeanPostProcessor 在初始化前后的处理以及 InitializingBeaninit-method 的初始化操作。

销毁这一步会注册相关销毁回调接口,最后通过DisposableBeandestory-method 进行销毁。

7、如果让你设计一个SpringIoc,你觉得会从哪些方面考虑这个设计?

  • Bean的生命周期管理:需要设计Bean的创建、初始化、销毁等生命周期管理机制,可以考虑使用工厂模式和单例模式来实现。
  • 依赖注入:需要实现依赖注入的功能,包括属性注入、构造函数注入、方法注入等,可以考虑使用反射机制和XML配置文件来实现。
  • Bean的作用域:需要支持多种Bean作用域,比如单例、原型、会话、请求等,可以考虑使用Map来存
    诸不同作用域的Bean实例。
  • AOP功能的支持:需要支持AOP功能,可以考虑使用动态代理机制和切面编程来实现。
  • 异常处理:需要考虑异常处理机制,包括Bean创建异常、依赖注入异常等,可以考虑使用try-catch机制来处理异常。
  • 配置文件加载:需要支持从不同的配置文件中加载Bean的相关信息,可以考虑使用XML、注解或者Java配置类来实现。

7.spring循环依赖

循环依赖是指 Bean 对象循环引用,是两个或多个 Bean 之间相互持有对方的引用,例如 CircularDependencyA → CircularDependencyB → CircularDependencyA。

1
2
3
4
5
6
7
8
9
10
11
@Component
public class CircularDependencyA {
@Autowired
private CircularDependencyB circB;
}

@Component
public class CircularDependencyB {
@Autowired
private CircularDependencyA circA;
}

Spring 框架通过使用三级缓存来解决这个问题,确保即使在循环依赖的情况下也能正确创建 Bean。

三级缓存分别是什么

一级缓存(singletonObjects):存放最终形态的 Bean(已经实例化、属性填充、初始化),单例池,为“Spring 的单例属性”⽽⽣。一般情况我们获取 Bean 都是从这里获取的,但是并不是所有的 Bean 都在单例池里面,例如原型 Bean 就不在里面。

二级缓存(earlySingletonObjects):存放过渡 Bean(半成品,尚未属性填充),也就是三级缓存中ObjectFactory产生的对象,与三级缓存配合使用的,可以防止 AOP 的情况下,每次调用ObjectFactory#getObject()都是会产生新的代理对象的。

三级缓存(singletonFactories):存放ObjectFactoryObjectFactorygetObject()方法(最终调用的是getEarlyBeanReference()方法)可以生成原始 Bean 对象或者代理对象(如果 Bean 被 AOP 切面代理)。三级缓存只会对单例 Bean 生效。

Spring 创建 Bean 的流程

  1. 先去 一级缓存 singletonObjects 中获取,存在就返回;
  2. 如果不存在或者对象正在创建中,于是去 二级缓存 earlySingletonObjects 中获取;
  3. 如果还没有获取到,就去 三级缓存 singletonFactories 中获取,通过执行 ObjectFacotrygetObject() 就可以获取该对象,获取成功之后,从三级缓存移除,并将该对象加入到二级缓存中。

举例

1
2
3
4
5
6
7
8
class A {
// 使用了 B
private B b;
}
class B {
// 使用了 A
private A a;
}
  1. 当 Spring 创建 A 之后,发现 A 依赖了 B ,又去创建 B,B 依赖了 A ,又去创建 A;
  2. 在 B 创建 A 的时候,那么此时 A 就发生了循环依赖,由于 A 此时还没有初始化完成,因此在 一二级缓存 中肯定没有 A;
  3. 那么此时就去三级缓存中调用 getObject() 方法去获取 A 的 前期暴露的对象 ,也就是调用上边加入的 getEarlyBeanReference() 方法,生成一个 A 的 前期暴露对象
  4. 然后就将这个 ObjectFactory 从三级缓存中移除,并且将前期暴露对象放入到二级缓存中,那么 B 就将这个前期暴露对象注入到依赖,来支持循环依赖。

只用两级缓存够吗? 在没有 AOP 的情况下,确实可以只使用一级和三级缓存来解决循环依赖问题。但是,当涉及到 AOP 时,二级缓存就显得非常重要了,因为它确保了即使在 Bean 的创建过程中有多次对早期引用的请求,也始终只返回同一个代理对象,从而避免了同一个 Bean 有多个代理对象的问题。

总结一下 Spring 如何解决三级缓存

在三级缓存这一块,主要记一下 Spring 是如何支持循环依赖的即可,也就是如果发生循环依赖的话,就去 三级缓存 singletonFactories 中拿到三级缓存中存储的 ObjectFactory 并调用它的 getObject() 方法来获取这个循环依赖对象的前期暴露对象(虽然还没初始化完成,但是可以拿到该对象在堆中的存储地址了),并且将这个前期暴露对象放到二级缓存中,这样在循环依赖时,就不会重复初始化了!

不过,这种机制也有一些缺点,比如增加了内存开销(需要维护三级缓存,也就是三个 Map),降低了性能(需要进行多次检查和转换)。并且,还有少部分情况是不支持循环依赖的,比如非单例的 bean 和@Async注解的 bean 无法支持循环依赖。

3.AOP

1.基础概念

AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

  • SpringAOP的实现原理:

    Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理

  • Spring AOP 和 AspectJ AOP 有什么区别?

    Spring AOP 属于运行时增强,而 AspectJ AOP是编译时增强。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。

    Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单,

    如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比 Spring AOP 快很多。

  • 术语:

术语 含义
目标(Target) 被通知的对象
代理(Proxy) 向目标对象应用通知之后创建的代理对象
连接点(JoinPoint) 目标对象的所属类中,定义的所有方法均为连接点
切入点(Pointcut) 被切面拦截 / 增强的连接点(切入点一定是连接点,连接点不一定是切入点)
通知(Advice) 增强的逻辑 / 代码,也即拦截到目标对象的连接点之后要做的事情
切面(Aspect) 切入点(Pointcut)+通知(Advice)
Weaving(织入) 将通知应用到目标对象,进而生成代理对象的过程动作

以下为举例说明,帮助理解

AOP核心概念

由此可见,切入点一定是连接点,连接点不一定是切入点

切面:描述的通知和切入点的关系,差不多是类似绑定的作用,因为可能有多个通知和切入点,切面就是负责绑定的这个作用

黑马程序员SSM框架教程_Spring+SpringMVC+Maven高级+SpringBoot+MyBatisPlus企业实用开发技术

P31-AOP入门案例:介绍了如果去写一个AOP

P32-AOP工作流程:介绍了AOP的工作原理流程

2.AOP通知的类型

  • 通知类型:

    • Before(前置通知):通知方法在目标方法调用之前执行
    • After (后置通知):通知方法在目标方法返回或异常后执行
      Around (环绕通知):通知方法会将目标方法封装起来 (重点)
      AfterReturning(返回通知):通知方法会在目标方法返回后调用 (了解)
      AfterThrowing(异常通知):通知方法会在目标方法抛出异常后调用 (了解)
  • SpringAOP织入时机:

    • 编译期织入
    • 类加载期织入
    • 运行时织入

    对比:

    织入方式 触发时机 性能影响 适用场景 限制条件
    编译期织入 .java → .class ★★★★☆ 性能敏感系统 需要特殊编译器
    类加载期织入 ClassLoader加载时 ★★★☆☆ 第三方库增强 需JVM参数支持
    运行时织入 Bean初始化完成后 ★★☆☆☆ 常规Spring应用 仅限Spring管理Bean

4.事务

Spring 事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,Spring 是无法提供事务功能的。Spring 只提供统一事务管理接口,具体实现都是由各数据库自己实现,数据库事务的提交和回滚是通过数据库自己的事务机制实现。

1.Spring中管理事务的方式

1.编程式事务:在代码中硬编码(在分布式系统中推荐使用) : 通过 TransactionTemplate或者 TransactionManager 手动管理事务,事务范围过大会出现事务未提交导致超时,因此事务要比锁的粒度更小。实际应用中很少使用,但是对于你理解 Spring 事务管理原理有帮助。

使用TransactionTemplate 进行编程式事务管理的示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Autowired
private TransactionTemplate transactionTemplate;
public void testTransaction() {

transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {

try {

// .... 业务代码
} catch (Exception e){
//回滚
transactionStatus.setRollbackOnly();
}

}
});
}

使用 TransactionManager 进行编程式事务管理的示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Autowired
private PlatformTransactionManager transactionManager;

public void testTransaction() {

TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
// .... 业务代码
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
}
}

2.声明式事务:在 XML 配置文件中配置或者直接基于注解(单体应用或者简单业务系统推荐使用) : 实际是通过 AOP 实现(基于@Transactional 的全注解方式使用最多)

使用 @Transactional注解进行事务管理的示例代码如下:

1
2
3
4
5
6
7
8
@Transactional(propagation = Propagation.REQUIRED)
public void aMethod {
//do something
B b = new B();
C c = new C();
b.bMethod();
c.cMethod();
}

声明式事务管理更加简洁,适合大多数情况;而编程式事务管理则提供了更高的灵活性,适用于更复杂的场景。选择合适的事务管理方式,可以有效地保证数据的一致性和完整性。

2.Spring事务的传播方式

这里简单说一下,因为Spring事务依赖连接的数据库事务支持,所以对于mysql数据库只有innodb引擎是支持事务的,然后Spring事务的隔离级别和对应数据库的事务隔离级别也是一样的。

事务传播行为是为了解决业务层方法之间互相调用的事务问题

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。

举个例子:我们在 A 类的aMethod()方法中调用了 B 类的 bMethod() 方法。这个时候就涉及到业务层方法之间互相调用的事务问题。如果我们的 bMethod()如果发生异常需要回滚,如何配置事务传播行为才能让 aMethod()也跟着回滚呢?

事务的传播行为

1.REQUIRED

使用的最多的一个事务传播行为,我们平时经常使用的@Transactional注解默认使用就是这个事务传播行为。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。也就是说:

1
2
3
4
5
6
7
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
methodB(); // 加入methodA的事务
}

@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {...}

2.REQUIRES_NEW

无论当前是否存在事务,都新建一个事务,暂停当前事务(如果存在)。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。

举个例子:如果我们上面的bMethod()使用PROPAGATION_REQUIRES_NEW事务传播行为修饰,aMethod还是用PROPAGATION_REQUIRED修饰的话。如果aMethod()发生异常回滚,bMethod()不会跟着回滚,因为 bMethod()开启了独立的事务。但是,如果 bMethod()抛出了未被捕获的异常并且这个异常满足事务回滚规则的话,aMethod()同样也会回滚,因为这个异常被 aMethod()的事务管理机制检测到了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Service
Class A {
@Autowired
B b;
@Transactional(propagation = Propagation.REQUIRED)
public void aMethod {
//do something
b.bMethod();
}
}

@Service
Class B {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void bMethod {
//do something
}
}

3.NESTED

如果当前存在事务,则在嵌套事务内执行;如果不存在,则表现同REQUIRED

  • 特点
    • 嵌套事务是外部事务的子事务
    • 子事务回滚不影响外部事务
    • 外部事务回滚会导致子事务回滚

举个例子:

  • 如果 aMethod() 回滚的话,作为嵌套事务的bMethod()会回滚。

  • 如果 bMethod() 回滚的话,aMethod()是否回滚,要看bMethod()的异常是否被处理:

    • bMethod()的异常没有被处理,即bMethod()内部没有处理异常,且aMethod()也没有处理异常,那么aMethod()将感知异常致使整体回滚。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @Service
    Class A {
    @Autowired
    B b;
    @Transactional(propagation = Propagation.REQUIRED)
    public void aMethod (){
    //do something
    b.bMethod();
    }
    }

    @Service
    Class B {
    @Transactional(propagation = Propagation.NESTED)
    public void bMethod (){
    //do something and throw an exception
    }
    }

    bMethod()处理异常或aMethod()处理异常,aMethod()不会回滚。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    @Service
    Class A {
    @Autowired
    B b;
    @Transactional(propagation = Propagation.REQUIRED)
    public void aMethod (){
    //do something
    try {
    b.bMethod();
    } catch (Exception e) {
    System.out.println("方法回滚");
    }
    }
    }

    @Service
    Class B {
    @Transactional(propagation = Propagation.NESTED)
    public void bMethod {
    //do something and throw an exception
    }
    }

4.MANDATORY

如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)

这个使用的很少,就不举例子来说了。

若是错误的配置以下 3 种事务传播行为,事务将不会发生回滚,这里不对照案例讲解了,使用的很少。

  • TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。

3.Spring的事务什么情况下会失效

1.@Transactional 注解只有作用到 public 方法上事务才生效

如果 Transactional 注解应用在非 public 修饰的方法上,Transactional 将会失效。

是因为在 Spring AOP 代理时,TransactionInterceptor (事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource 的 computeTransactionAttribute方法,获取 Transactional 注解的事务配置信息。

1
2
3
4
5
6
7
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 的 rollbackFor 和 propagation 属性

@Transactional的也就是上述三种提到的情况

rollbackFor 用来指定能够触发事务回滚的异常类型。Spring 默认抛出未检查 unchecked 异常(继承自 RuntimeException 的异常)或者 Error 才回滚事务,其他异常不会触发回滚事务。

3.避免同一个类中调用 @Transactional 注解的方法,这样会导致事务失效

当一个方法被标记了@Transactional 注解的时候,Spring 事务管理器只会在被其他类方法调用的时候生效,而不会在一个类中方法调用生效。

这是因为 Spring AOP 工作原理决定的。因为 Spring AOP 使用动态代理来实现事务的管理,它会在运行的时候为带有 @Transactional 注解的方法生成代理对象,并在方法调用的前后应用事物逻辑。如果该方法被其他类调用我们的代理对象就会拦截方法调用并处理事务。但是在一个类中的其他方法内部调用的时候,我们代理对象就无法拦截到这个内部调用,因此事务也就失效了。

TODO

Spring中如何配置一个类加载一个类不加载

AOP使用的什么动态代理,AOP动态代理的具体实现过程,详细说明

@Transactional 事务注解原理

5.SpringMVC

MVC全名是Model View Controller,是模型(model)一视图(view)一控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。

springMVC

1.核心组件

  • DispatcherServlet核心的中央处理器,负责接收请求、分发,并给予客户端响应。
  • HandlerMapping处理器映射器,根据 URL 去匹配查找能处理的 Handler ,并会将请求涉及到的拦截器和 Handler 一起封装。
  • HandlerAdapter处理器适配器,根据 HandlerMapping 找到的 Handler ,适配执行对应的 Handler
  • Handler请求处理器,处理实际请求的处理器。
  • ViewResolver视图解析器,根据 Handler 返回的逻辑视图 / 视图,解析并渲染真正的视图,并传递给 DispatcherServlet 响应客户端

2.工作原理

mvc

  1. 客户端(浏览器)发送请求, DispatcherServlet拦截请求。
  2. DispatcherServlet 根据请求信息调用 HandlerMappingHandlerMapping 根据 URL 去匹配查找能处理的 Handler(也就是我们平常说的 Controller 控制器) ,并会将请求涉及到的拦截器和 Handler 一起封装。
  3. DispatcherServlet 调用 HandlerAdapter适配器执行 Handler
  4. Handler 完成对用户请求的处理后,会返回一个 ModelAndView 对象给DispatcherServletModelAndView 顾名思义,包含了数据模型以及相应的视图的信息。Model 是返回的数据对象,View 是个逻辑上的 View
  5. ViewResolver 会根据逻辑 View 查找实际的 View
  6. DispaterServlet 把返回的 Model 传给 View(视图渲染)。
  7. View 返回给请求者(浏览器)

6.SpringBoot

1.why springboot

  • 简化开发:Spring Boot通过提供一系列的开箱即用的组件和自动配置,简化了项目的配置和开发过程,开发人员可以更专注于业务逻辑的实现,而不需要花费过多时间在繁琐的配置上。
  • 快速启动:Spring Boot提供了快速的应用程序启动方式,可通过内嵌的Tomcat、Jetty或Undertow等容器快速启动应用程序,无需额外的部署步骤,方便快捷。
  • 自动化配置:Spring Boot通过自动配置功能,根据项目中的依赖关系和约定俗成的规则来配置应用程序,减少了配置的复杂性,使开发者更容易实现应用的最佳实践。

2.spring VS spring boot

  • Spring Boot 提供了自动化配置,大大简化了项目的配置过程。通过约定优于配置的原则,很多常用的配置可以自动完成,开发者可以专注于业务逻辑的实现。
  • Spring Boot 提供了快速的项目启动器,通过引入不同的 Starter,可以快速集成常用的框架和库(如数据库、消息队列、Web 开发等),极大地提高了开发效率。
  • Spring Boot 默认集成了多种内嵌服务器(如Tomcat、Jetty、Undertow),无需额外配置,即可将应用打包成可执行的 JAR 文件,方便部署和运行。

3.springboot怎么实现约定大于配置的

  • 自动化配置Spring Boot根据项目的依赖和环境自动配置应用程序, 无需手动配置大量的XML或Java配置文件。 例如,如果项目引入了Spring Web MVC依赖, Spring Boot会自动配置一个基本的Web应用程序上下文。
  • 起步依赖:Spring Boot提供了一系列起步依赖,这些依赖包含了常用的框架和功能, 可以帮助开发者快速搭建项目。

4.自动装配

可以从以下几个方面回答:

  1. 什么是 SpringBoot 自动装配?
  2. SpringBoot 是如何实现自动装配的?如何实现按需加载?
  3. 如何实现一个 Starter?

1.什么是自动装配?

没有 Spring Boot 的情况下,如果我们需要引入第三方依赖,需要手动配置,非常麻烦。但是,Sprimg Boot中,我们直接引入一个 starter 即可。比如你想要在项目中使用redis 的话,直接在项目中引入对应的starter 即可。

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

引入 starter 之后,我们通过少量注解和一些简单的配置就能使用第三方组件提供的功能了。

在我看来,自动装配可以简单理解为:通过注解或者一些简单的配置就能在 Spring Boot 的帮助下实现某块功能。

通俗来讲,自动装配就是通过注解或一些简单的配置就可以在SpringBoot的帮助下开启和配置各种功能,比如数据库访问、Web开发。

2.自动装配的原理

核心注解

1
2
@SpringBootApplication // 等价于:
// @Configuration + @EnableAutoConfiguration + @ComponentScan

4.说几个启动器(starter)

  • spring-boot-starter-web:这是最常用的起步依赖之一, 它包含了Spring MVC和Tomcat嵌入式服务器,用于快速构建Web应用程序。
  • mybatis-spring-boot-starter: 这个Starter是由MyBatis团队提供的,用于简化在Spring Boot应用中集成MyBatis的过程。它自动配置了MyBatis的相关组件, 包括SqlSessionFactory、MapperScannerConfigurer等, 使得开发者能够快速地开始使用MyBatis进行数据库操作。
  • spring-boot-starter-data-jpa 或 spring-boot-starter-jdbc: 如果使用的是Java Persistence APl (JPA)进行数据库操作,那么应该使用spring-boot-starter-data-jpa。这个Starter包含了Hibernate等JPA实现以及数据库连接池等必要的库,可以让你轻松地与MYSQL数据库进行交互。你需要在application.properties或application.yml中配置MySQL的连接信息。如果倾向于直接使用JDBC而不通过JPA,那么可以使用spring-boot-starter-jdbc,它提供了基本的JDBC支持。
  • spring-boot-starter-data-redis: 用于集成Redis缓存和数据存储服务。 这个Starter包含了与Redis交互所需的客户端(默认是Jedis客户端, 也可以配置为Lettuce客户端),以及Spring Data Redis的支持, 使得在Spring Boot应用中使用Redis变得非常便捷。 同样地,需要在配置文件中设置Redis服务器的连接详情。
  • spring-boot-starter-test:包含了单元测试和集成测试所需的库 ,如JUnit, Spring Test, AssertJ等,便于进行测试驱动开发(TDD)。

5.自己实现一个SpringBoot starter

6.过滤器和拦截器的区别⭐️

小林

过滤器(Filter)和拦截器(Interceptor)是用于处理请求和响应的两种不同机制。

7.Mybatis

1.Mybatis里的 # 和 $ 的区别?

  • Mybatis 在处理 #{} 时,会创建预编译的 SQL 语句,将 SQL 中的 #{} 替换为 ? 号,在执行 SQL 时会为预编译 SQL 中的占位符(?)赋值,预编译的 SQL 语句执行效率高,并且可以防止SQL 注入
  • Mybatis 在处理 ${} 时,只是创建普通的 SQL 语句,然后在执行 SQL 语句时 MyBatis 将参数直接拼入到 SQL 里,不能防止 SQL 注入

sql注入:

假设有一个简单的登录表单,其后端验证用户名和密码的SQL查询如下:

sql

1
SELECT * FROM users WHERE username = '输入的用户名' AND password = '输入的密码'

如果攻击者在用户名字段输入:

1
' OR '1'='1

那么完整的SQL查询将变为:

sql

1
SELECT * FROM users WHERE username = '' OR '1'='1' AND password = '输入的密码'

由于'1'='1'始终为真,这个查询将返回数据库中所有用户的记录,导致数据泄露。

2. MybatisPlus和Mybatis的区别?

3.MyBatis运用了哪些常见的设计模式?

4.ResultType 和 ResultMap 的区别

  • 如果数据库结果集中的列名和要封装实体的属性名完全一致的话用 resultType 属性。
  • 如果数据库结果集中的列名和要封装实体的属性名有不一致的情况用 resultMap 属 性,通过。resultMap 手动建立对象关系映射,resultMap 要配置一下表和类的–对应关系,所以说就算你的字段名和你的实体类的属性名不一样也没关系,都会给你映射出来

8.SpringCloud

了解SpringCloud吗,说一下他和SpringBoot的区别

微服务组件

负载均衡有哪些算法?

如何实现一直均衡给一个用户?

熔断降级(了解)

评论