知识体系总结(八)SSM框架体系

Spring基础

1-1、Spring、SpringMVC、Mybatis与SpringBoot的区别

  • Spring是一个轻量级的Java企业级开发框架,提供了很多功能和特性,如:
    • 控制反转(IOC)
    • 依赖注入(DI)
    • 面向切面编程(AOP)
    • Spring事务管理
  • Spring MVC则是Spring框架的一部分,专注于Web应用程序的开发,提供如下功能:
    • 接收请求
    • 设置请求拦截器
    • 响应数据
    • 全局异常处理
  • Mybatis则是数据持久层框架,与数据库打交道,可以使用简单的XML或者注解配置和映射数据库表格中的原始信息,完成系统中Java实体类与数据库中记录的映射
  • Spring Boot是一个简化了Spring应用开发的框架,通过起步依赖和自动配置,大大简化了spring配置的流程,使得开发者可以更专注于业务逻辑的实现。

在这里插入图片描述

1-2、Spring中常用的注解及作用

  1. 配置Bean相关:
  • @Autowire:让Spring容器自动装配Bean到类中。
  • @ComponentScan:通过自动扫描机制来发现和注册满足条件的组件。
  • @Configuration:用于指定配置类,替代传统的XML配置文件。
  • @Bean:用于在@Configuration类中声明Bean定义,供Spring容器管理。
  • @Qualifier:与@Autowired一起使用,通过指定Bean名称解决自动装配的歧义性。
  • @Value:用于注入简单值或表达式到Bean的属性。
  • @Scope:用于指定Bean的作用域,如单例(Singleton)或原型(Prototype)。
  • @ConfigurationProperties:用于绑定配置文件中的属性到Java类的字段上,支持批量绑定。
  1. SpringMVC相关:
  • @Component:通用注解,标明该类是应该交由Spring管理的。如果一个Bean不知道属于哪个层,使用该注解。
  • @RequestMapping:用于将HTTP请求映射到控制器的处理方法,例如:@RequestMapping(“/books”)
  • @PathVariable:用于获取URL路径变量值并映射到方法参数。
    // 处理URL: /books/{id}
    @GetMapping("/{id}")
    public String getBookById(@PathVariable Long id) {
    
    
        // 处理方法逻辑
        return "book_details";
    }
  • @RequestParam:用于获取请求参数的值并映射到方法参数。
    // 处理URL: /books?category=fiction
    @GetMapping
    public String getBooksByCategory(@RequestParam("category") String category) {
    
    
        // 使用category进行业务逻辑处理
        // ...
        return "books_by_category";
    }
  • @ResponseBody:用于将方法返回值直接作为HTTP响应的内容返回。
@RestController
@RequestMapping("/api")
public class BookController {
    
    

    @PostMapping("/books")
    public ResponseEntity<String> createBook(@RequestBody Book book) {
    
    
        // 处理从请求体中解析出来的 Book 对象
        // ...
        return new ResponseEntity<>("Book created successfully", HttpStatus.CREATED);
    }
}
  • @Controller:控制层注解,处理HTTP请求并返回响应。此外还有@RestController注解,它是@Controller和@ResponseBody的合集。
  • @Service:用于标识服务层类,通常用于注解Service组件。
  • @Repository:用于标识数据访问层类,通常用于注解DAO组件。
  • @Transactional:用于标识事务方法,启用方法级别的事务管理。

1-3、Spring 框架中用到了哪些设计模式?

  1. 工厂模式:Spring中使用BeanFactory或者ApplicationContext创建Bean对象。工厂模式对客户隐藏构造细节,只需要客户提供产品参数,即返回给客户产品对象。
  2. 单例模式:Spring中Bean的默认作用域都是singleton(单例),Spring中通过ConcurrentHashMap 实现单例注册表的特殊方式实现单例模式。使用单例模式的好处在于:对于频繁使用的对象,可以省略创建对象所花费的时间,减少系统开销,并减轻GC压力。
  3. 代理模式:Spring AOP基于动态代理实现。代理对象实现增强的业务逻辑。如果待增强对象实现了接口,采用JDK 动态代理实现。如果待增强对象没有实现接口,则使用CGlib动态代理。
  4. 模板方法:模板方法模式是一种行为设计模式,它定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。 模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤的实现方式。Spring框架中大量应用了模板方法,如HibernateTemplate 等以 Template 结尾的对数据库操作的类。JdbcTemplate、
  5. 监听者模式:Spring事件发布 - 监听,应用到了监听者模式,但事件发生时,监听器判断是否是自身监听的对象,从而决定是否执行监听回调。
  6. 适配器模式:Spring AOP中,通知类型有多种,前置、后置、环绕、销毁前、异常时,所有的非环绕通知都需要调用AdvisorAdapter转为环绕通知,然后再交给方法拦截器(MethodInterceptor)。
  7. 责任链模式:Spring AOP中 一个对象配置了多个通知,是按照责任链模式来执行。责任链模式中,每个节点都可以选择处理任务或者直接将其移交给下一个节点。与此类似的还有SpringMVC中的拦截器、JDK中的过滤器。

Spring IoC 、 DI、Bean

2-1、Spring IoC是什么,有什么好处,Spring中是怎么实现的?

IoC(Inversion of Control:控制反转) 是一种设计思想,是将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理,即将对象的创建和管理的权力交由Spring容器。

将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。 IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/=或者注解即可,完全不用考虑对象是如何被创建出来的。简化应用开发,利用实现分层解耦。

在这里插入图片描述

在 Spring 中, IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个 Map(key,value)注册表,Map 中存放的是各种Bean对象,key就是Bean的名称(或ID),value 是 Bean 的定义信息,包括类名、依赖关系、初始化方法、销毁方法等。

2-2、声明Bean的注解有哪些?

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

Bean的配置可以通过XML文件配置,也可以通过注解在设置。比如:

  • @Component:通用的注解,可标注任意类为 Spring 组件。如果一个 Bean 不知道属于哪个层,可以使用* @Component 注解标注。
  • @Controller : 对应 Spring MVC 控制层,主要用于接受用户请求并调用 Service 层返回数据给前端页面。
  • @Service : 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。
  • @Repository(仓库) : 对应持久层即 Dao 层,主要用于数据库相关操作。

2-3、@Component 和 @Bean 的区别是什么?

  1. @Component 注解作用于类,而@Bean注解作用于方法。
  2. @Component通常是通过类路径扫描来自动侦测以及自动装配到 Spring 容器中(我们可以使用 @ComponentScan 注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。@Bean 注解通常是我们在标有该注解的方法中定义产生这个 bean,@Bean告诉了 Spring 这是某个类的实例,当我需要用它的时候还给我。
  3. @Bean 注解比 @Component 注解的自定义性更强,而且很多地方我们只能通过 @Bean 注解来注册 bean。比如当我们引用第三方库中的类需要装配到 Spring容器时,则只能通过 @Bean来实现。

2-4、注入Bean的注解有哪些,@Autowired与@Resource的区别在哪?

Spring 内置的 @Autowired 以及 JDK 内置的 @Resource 和 @Inject 都可以用于注入 Bean。

  • @Autowired 是 Spring 提供的注解,@Resource 是 JDK 提供的注解。Autowired 默认的注入方式为byType(根据类型进行匹配),
  • @Resource默认注入方式为 byName(根据名称进行匹配)。

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

扫描二维码关注公众号,回复: 16405554 查看本文章
  • @Autowired 可以通过 @Qualifier 注解来显式指定名称
  • @Resource可以通过 name 属性来显式指定名称。

举个例子,SmsService 接口有两个实现类: SmsServiceImpl1和 SmsServiceImpl2,且它们都已经被 Spring 容器所管理。

// 报错,byName 和 byType 都无法匹配到 bean
@Autowired
private SmsService smsService;
// 正确注入 SmsServiceImpl1 对象对应的 bean
@Autowired
private SmsService smsServiceImpl1;
// 正确注入  SmsServiceImpl1 对象对应的 bean
// smsServiceImpl1 就是我们上面所说的名称
@Autowired
@Qualifier(value = "smsServiceImpl1")
private SmsService smsService;


// 报错,byName 和 byType 都无法匹配到 bean
@Resource
private SmsService smsService;
// 正确注入 SmsServiceImpl1 对象对应的 bean
@Resource
private SmsService smsServiceImpl1;
// 正确注入 SmsServiceImpl1 对象对应的 bean(比较推荐这种方式)
@Resource(name = "smsServiceImpl1")
private SmsService smsService;

2-5、Bean是线程安全的吗?

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

在多例(prototype)的作用域下,每次获取都创建一个新的bean实例,不存在资源竞争问题,所有没有线程安全问题。

在单例(singleton)作用域下,IoC容器只有唯一的bean实例,可能存在资源竞争问题,如果bean有状态即包含可变的成员变量,那么就存在线程安全问题。

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

  1. 在Bean中尽量避免定义可变的成员 变量
  2. 在类中定义ThreadLocal,将需要的成员变量保存在TheadLoacl使其线程私有化。

2-6、Bean的生命周期是怎么样的?

  1. IoC容器找到配置文件中Spring Bean的定义
  2. IoC容器利用Java 反射创建一个Bean的实例
  3. 如果涉及到一些Bean的属性赋值如使用了@Value注解,则利用set()方法设置属性值。
  4. 如果Bean实现了BeanNameAware接口,则调用setBeanName方法,传入Bean的名字。Aware接口是在编译阶段内置在Bean中,用来与IoC容器打交道,获取Bean的相关信息时用的。4 - 7 都是一些Aware接口,了解Aware接口的概念即可,不用记得那么细致。
  5. 如果Bean实现了BeanClassLoaderAware 接口,调用 setBeanClassLoader()方法,传入 ClassLoader对象的实例。
  6. 如果 Bean 实现了 BeanFactoryAware 接口,调用 setBeanFactory()方法,传入 BeanFactory对象的实例。
  7. 如果实现了其他 *.Aware接口,就调用相应的方法。
  8. 如果有和加载这个Bean的 Spring 容器相关的 BeanPostProcessor 对象,执行postProcessBeforeInitialization() 方法。即初始化前的Bean后处理器
  9. 如果 Bean 实现了InitializingBean接口,执行afterPropertiesSet()方法。InitializingBean是与Aware类似 的接口,是与Bean耦合的,InitializingBean接口会执行初始化逻辑
  10. 如果 Bean 在配置文件中的定义包含 init-method 属性,则执行init-method 指定的方法。8 - 10 是Bean初始化时可以设置的三种回调,它们的顺序分别是 Bean初始化前的后处理器、InitializingBean内置接口、配置属性init - method
  11. 如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行postProcessAfterInitialization() 方法即销毁前的Bean后处理器
  12. 如果 Bean 实现了 DisposableBean 接口,执行 destroy() 方法。DisposableBean 接口是与InitializingBean类似的耦合接口
  13. 如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的方法。11-13是与初始化时相对应的,在销毁前的3种回调,它们的执行顺序与初始化是一样的,即 后处理器 、耦合接口、配置属性

上边列举的Bean的生命周期有点儿过长了:
简单来记忆:

Bean生命周期的主要脉络包括:

  • 1、Bean配置
  • 2、Bean实例化
  • 3、Bean初始化
  • 5、Bean的使用
  • 4、Bean销毁

展开来讲包括:

  • 1、找到配置文件中Bean定义
  • 2、基于反射实例化Bean
  • 3、如果Bean实现了Aware相关耦合接口,则执行对应实现方法
  • 4、Bean初始化:
    • Bean后处理器:前置处理
    • 耦合接口: InitializingBean
    • Bean配置属性:init-method指定的方法
    • Bean后处理器:后置处理
  • 5、Bean的使用
  • 6、Bean销毁:
    • Bean后处理器:销毁前置处理
    • 耦合接口: DisposableBean
    • Bean配置属性:destroy-method指定的方法
      在这里插入图片描述

2-7、Spring如何解决循环依赖问题?

循环依赖有三种形态:

  1. 相互依赖,也就是 A 依赖 B,B 又依赖 A,它们之间形成了循环依赖。
  2. 间接依赖,也就是 A 依赖 B,B 依赖 C,C 又依赖 A,形成了循环依赖。
  3. 自我依赖,也是 A 依赖 A 形成了循环依赖。

Spring设计了三级缓存来解决循环依赖问题,一级缓存存放完整可用的Bean实例,二级缓存存储实例化以后,还没设置属性值的Bean实例,即Bean中的依赖注入还没有处理,三级缓存用来存放Bean工厂,它主要用来生成原始Bean对象,并且将其放入二级缓存中,三级缓存的核心思想就是把Bean的依赖注入和Bean的实例化进行分离,采用一级缓存缓存完全可用的实例,采用二级缓存缓存尚未属性填充的实例,将未属性填充的实例提前暴露。

举例:A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象”这种循环依赖的情况。

  • A首先完成了初始化的第一步(createBeanINstance实例化),并且将自己提前曝光到singletonFactories中。
  • 此时进行初始化的第二步,发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以走create流程,B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A),尝试一级缓存singletonObjects(肯定没有,因为A还没初始化完全),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,由于A通过ObjectFactory将自己提前曝光了,所以B能够通过ObjectFactory.getObject拿到A对象(虽然A还没有初始化完全,但是总比没有好呀),B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。
  • 此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,进去了一级缓存singletonObjects中,而且更加幸运的是,由于B拿到了A的对象引用,所以B现在hold住的A对象完成了初始化。

Spring AoP

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

3-1、Spring AoP是怎么实现的?

Spring AoP是基于动态代理的方式实现,具有实现方式又分为了:

  • 1、基于JDK 的动态代理:对于实现了接口的目标对象,Spring使用JDK动态代理来创建代理对象。JDK动态代理是通过Java原生的Proxy类来实现的。代理对象实现了目标接口,并在InvocationHandler的invoke方法中添加了增强逻辑,原方法基于反射实现,method.invoke(‘待增强对象’, ‘方法参数’);

    • 具体实现原理:
      • 1、代理对象必须实现InvocationHandler接口,JDK在实现时是继承了Proxy类,提供了InvocationHandler的构建方法,在执行时通过super,传入InvocationHandler。
      • 2、使用Proxy.newProxyInstance产生代理对象,代理对象是基于ASM(字节码操作框架)技术,动态生成的字节码,因此需要传入类加载器,将字节码加载为对象。
      • 3、接口方法的获取是通过反射的方式,即从类的字节码中调用getMethod方法得到的,为了避免多次调用的getMethod的开销,代理对象内部使用静态成员保存了方法,并通过静态代码块进行了初始化赋值。
      • 4、为了处理有返回值的方法,代理对象中的返回值为Object对象。方法中对于异常的捕获和处理,分为了运行时异常和受检时异常,对于运行时异常直接抛出,对于受检异常,将其包装为UndeclaredThrowableException后,再抛出。
      • 5、jdk反射优化,接口方法增强时,采用了反射的方式调用方法,这样性能是有损耗的,jdk内部对此进行了优化,当调用次数到第17次时,jdk内部会生成一个有该方法的代理对象,直接调用该代理对象的方法,而不再使用反射的形式调用。
  • 2、基于CGLib的动态代理:CGLib动态代理采用继承 + 方法拦截器的方式,针对可继承的目标类进行增强。

    • 具体实现原理:
      • 1、生成代理类:当目标类需要被代理时,CGLIB会在运行时生成一个代理类,该代理类继承自目标类,成为目标类的子类。
      • 2、拦截器:在CGLIB中,代理的逻辑由一个拦截器(MethodInterceptor)来实现,拦截器负责在代理类的方法调用前后执行额外的逻辑。拦截器类似于JDK动态代理中的InvocationHandler。
      • 3、方法调用的重定向:在代理类的方法调用时,CGLIB会将方法的调用重定向到拦截器的intercept()方法中。在intercept()方法中,可以实现对目标方法的增强逻辑,并调用目标方法。
      • 4、代理对象创建:通过字节码生成技术,CGLIB将代理类的定义转换为字节码,并使用ClassLoader加载字节码,最终生成代理对象。

3-2、CGLIB和JDK动态代理的实现区别

  1. 原理和实现方式:
    JDK动态代理:JDK动态代理是通过Java原生的java.lang.reflect.Proxy类和InvocationHandler接口来实现的。在运行时,JDK动态代理通过生成目标接口的代理对象,并通过InvocationHandler的invoke()方法来实现对目标方法的拦截和增强。
    CGLIB动态代理:CGLIB动态代理是通过CGLIB库,利用字节码生成技术,在运行时生成目标类的子类作为代理类。在子类中,CGLIB通过生成MethodProxy对象实现对目标方法的拦截和增强。

  2. 代理类型:
    JDK动态代理:JDK动态代理只能代理实现了接口的目标类。如果目标类没有实现任何接口,就无法使用JDK动态代理进行代理。
    CGLIB动态代理:CGLIB动态代理可以代理没有实现接口的目标类。它通过生成目标类的子类来实现代理,因此对于没有接口的类也能够代理。

  3. 性能:
    JDK动态代理:JDK动态代理在调用代理方法时,涉及到反射调用,有一定的性能开销。代理效率相对较低,尤其在代理方法较多时性能下降较明显。
    CGLIB动态代理:CGLIB动态代理在调用代理方法时,通过直接调用生成的子类的方法,无需反射调用,因此相对于JDK动态代理,性能更高。特别适用于代理方法较多或代理对象创建较频繁的情况。

  4. 依赖和兼容性:
    JDK动态代理:JDK动态代理依赖于Java原生的java.lang.reflect.Proxy类,对于Java平台的兼容性较好,无需额外引入第三方库。
    CGLIB动态代理:CGLIB动态代理依赖于CGLIB库,使用时需要引入相应的依赖。CGLIB动态代理在Java平台上运行良好,但在其他Java虚拟机(JVM)上可能存在兼容性问题。

3-3、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 ,它比 Spring AOP 快很多。

Spring MVC

4-1 SpringMVC的核心组件有哪些?

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

4-2 SpringMVC的工作原理?

在这里插入图片描述
流程说明(重要):

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

4-3 SpringMVC 统一 / 全局异常处理怎么做?

使用@ControllerAdvice + @ExceptionHandler 这两个注解的统一异常处理。

@ControllerAdvice即控制器增强,用于处理控制层的异常处理逻辑,而其他层(Service、Dao)只需要不断往外抛出异常即可,不需要考虑异常处理代码。

@ExceptionHandler:可以指定所要拦截的异常类型,一般我们分为运行时异常和自定义异常。

@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
    
    

    @ExceptionHandler(BaseException.class)
    public ResponseEntity<?> handleAppException(BaseException ex, HttpServletRequest request) {
    
    
      //......
    }

    @ExceptionHandler(value = ResourceNotFoundException.class)
    public ResponseEntity<ErrorReponse> handleResourceNotFoundException(ResourceNotFoundException ex, HttpServletRequest request) {
    
    
      //......
    }
}

这种异常处理方式下,会给所有或者指定的 Controller 织入异常处理的逻辑(AOP),当 Controller 中的方法抛出异常的时候,由被@ExceptionHandler 注解修饰的方法进行处理。

ExceptionHandlerMethodResolver 中 getMappedMethod 方法决定了异常具体被哪个被 @ExceptionHandler 注解修饰的方法处理异常。

@Nullable
	private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
    
    
		List<Class<? extends Throwable>> matches = new ArrayList<>();
    //找到可以处理的所有异常信息。mappedMethods 中存放了异常和处理异常的方法的对应关系
		for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
    
    
			if (mappedException.isAssignableFrom(exceptionType)) {
    
    
				matches.add(mappedException);
			}
		}
    // 不为空说明有方法处理异常
		if (!matches.isEmpty()) {
    
    
      // 按照匹配程度从小到大排序
			matches.sort(new ExceptionDepthComparator(exceptionType));
      // 返回处理异常的方法
			return this.mappedMethods.get(matches.get(0));
		}
		else {
    
    
			return null;
		}
	}

从源代码看出:getMappedMethod()会首先找到可以匹配处理异常的所有方法信息,然后对其进行从小到大的排序,最后取最小的那一个匹配的方法(即匹配度最高的那个)。

4-4 拦截器和过滤器有什么区别?

在这里插入图片描述
Filter接口需要我们重写三个方法:

init:初始化,只执行一次
doFilter:拦截请求后调用,可以调用多次,可以在这里通过执行chain.doFilter()放行请求。
destroy:销毁方法,只调用一次。
在这里插入图片描述
在这里插入图片描述

  1. 从依赖关系上讲,拦截器属于SpringMVC框架,而过滤器属于Servlet体系。
  2. 从具体使用上讲,两者的接口规范不同,过滤器需实现Filter接口,过滤器执行有三个阶段 init()、doFilter()、destroy()。而拦截器需实现HandleInterceptor接口,也有三个阶段:preHandler()、postHandler()和afterCompletion()。
  3. 从执行顺序上讲:所有的请求都先被过滤器链Filter拦截,放行后,请求被DispatcherServlet分发,进入拦截器链。一个畅行的请求,它经历的阶段为:过滤器1 拦截前 -> 过滤器2 拦截前 …… Servlet分发器-> 拦截器1拦截前 -> 拦截器2 拦截前 …… 最后的拦截器 拦截后 ……->第一个拦截器 -> Servlet分发器 -> 最后的过滤器 拦截后 ……->第一个过滤器
  4. 从功能上讲:过滤器主要用于对请求和响应进行过滤和处理,例如字符编码处理、非法请求过滤、响应加密等。拦截器主要用于实现一些与业务逻辑相关的功能,例如身份认证、权限校验、日志记录、性能监控等。
public void init(FilterConfig filterConfig) throws ServletException;

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;

public void destroy();

boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception;

void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception;

Spring事务

编程式事务管理

编程式事务管理:通过 TransactionTemplate或者TransactionManager手动管理事务。

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

@Autowired
private TransactionTemplate transactionTemplate;
public void testTransaction() {
    
    

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

                try {
    
    

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

            }
        });
}

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

@Autowired
private PlatformTransactionManager transactionManager;

public void testTransaction() {
    
    

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

声明式事务管理

采用注解@Transactional通过 AOP 实现:

@Transactional(propagation = Propagation.REQUIRED)
public void aMethod {
    
    
	  //do something
	  B b = new B();
	  C c = new C();
	  b.bMethod();
	  c.cMethod();
}

5-1 Spring管理事务的方式有几种?

  • 编程式事务:在代码中硬编码(不推荐使用) : 通过 TransactionTemplate或者 TransactionManager 手动管理事务,实际应用中很少使用,但是对于你理解 Spring 事务管理原理有帮助。
  • 声明式事务:在 XML 配置文件中配置或者直接基于注解(推荐使用) : 实际是通过 AOP 实现(基于@Transactional 的全注解方式使用最多)

5-2 Spring事务中有哪几种事务传播(propagation)行为?

事务传播行为是为了解决业务层方法之间互相调用的事务问题。
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。

正确的事务传播行为可能的值如下:

  • 1.TransactionDefinition.PROPAGATION_REQUIRED:使用的最多的一个事务传播行为,我们平时经常使用的@Transactional注解默认使用就是这个事务传播行为。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。即:REQUIRED模式下,后来执行的事务融入之前的事务
  • 2.TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。即:REQUIRED_NEW模式下,利用线程同步机制,先阻塞之前事务的线程,另起一个线程执行新的事务,事务提交后,再通知唤醒之前的事务,这种方式避免了后边事务的回归导致的先前事务的回滚
  • 3.TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。底层数据库在实现时,采用了save point a,保存点,当新事务执行完毕,再恢复保存点处 back to a
  • 4.TransactionDefinition.PROPAGATION_MANDATORY如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常,即强制要求事务环境。(mandatory:强制性)这个使用的很少。

若是错误的配置以下 3 种事务传播行为,事务将不会发生回滚:

  1. TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。支持事务,有事务就加入支持,没有事务就按非事务方式执行,适合查询 / 只读业务
  2. TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。即不支持事务,如果当前存在事务,挂起阻塞
  3. TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。

5-3 Spring 事务中的隔离级别有哪几种?

TransactionDefinition 接口中定义了五个表示隔离级别的常量:
其实就是在数据库事务隔离级别的基础上,加了默认隔离级别与数据库事务隔离级别保持一致。

  • 1、TransactionDefinition.ISOLATION_DEFAULT: 使用后端数据库默认的隔离级别,Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别.
  • 2、TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
  • 3、TransactionDefinition.ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
  • 4、TransactionDefinition.ISOLATION_REPEATABLE_READ: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
  • 5、TransactionDefinition.ISOLATION_SERIALIZABLE: 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

5-4 声明式事务有什么缺点?

  1. 声明式@Transactional作用在方法上。也就是说,如果需要给哪部分代码增加事务的话,需要把这部分代码独立出来,作为一个方法。其他开发者容易忽略所使用的方法是被事务嵌套的,如果在事务方法中加入如下方法:
    • RPC调用
    • 文件写入
    • 缓存更新
    • 消息发布
      这些操作自身无法回归,就会导致数据不一致
  2. 当存在多个容器,需指定事务管理器。因此在Spring初始化的时候,很可能加载错误事务管理器导致事务失效因此在系统中使用事务管理器的时候,要指定事务管理器别名 ,如@Transactional(value = "emsTransactionManager")
  3. 即使挂对了@Transactional标签,事务失效的场景依然是非常多的,场景如下:
    • @Transactional标签应用在非public修饰的方法上
    • bean没有被Spring容器管理
    • 事务方法抛出非运行时异常(Spring 默认只为 RuntimeException 异常回滚事务,如果方法往外抛出 checked exception,该方法虽然不会再执行后续操作,但仍会提交已执行的数据操作。这样可能使得只有部分数据提交,造成数据不一致)
  4. 滥用切面导致失效:Spring事务就是基于AOP实现的。在实际工程中,我们也会用到非常多AOP处理不同事情,相互之间非常可能产生影响。
    假如某位同学在某种切面中对异常做了什么处理,很可能导致很多事务失效

5-5 事务失效场景?

  1. 数据库引擎不支持事务,例如MyISAM引擎不支持事务。
  2. Bean没有被Spring管理
  3. 方法用非public或者使用final修饰,因为spring事务底层使用了AOP,jdk代理获取cglib代理,会帮我们生成代理类,在代理类中实现事务功能。如果某个方法被final修饰了,那么在代理类中,就无法重新该方法,而添加事务功能。
  4. 方法内部调用:add()方法中直接调用了事务方法updateStatus();从前面的介绍可以直达,updateStatus()方法拥有事务的能力是因为Spring AOP生成了代理对象,但是这种方法直接调用了this对象的方法,所以updateStatus()方法不会生成事务。由此可见,在同一类中的方法直接调用,会导致事务失效。
@Service
public class UserService {
    
    
 @Autowired
 private UserMapper usermapper;

 public void add(User user){
    
    
  userMappper.insertUser(user);
  updateStatus(user);
 }
}

@Transactional
public void updateStatus(User user){
    
    
 doSomeThing();
}
  1. 多线程调用:两个方法不在一个线程中。获取的数据库连接也就不一致,从而是两个不同的事务。

SpringBoot

6-1 Spring启动时的自动配置原理

1、Spring启动类会加@SpringBootApplication注解,该注解可以看作是三个主要的注解@Configuration,@ComponentScan和@EnableAutoConfiguration的集合。
2、@Configuration声明了启动类为主配置类,@ComponentScan则会扫描主配置类及其所在包下所有类,将@Component注解标识的类或@Component的复合注解,如@Service、@Controller、@Repository等加入到Spring容器。
3、重点是@EnableAutoConfiguration注解,它可以看作是@AutoConfigurationPackage和@Import注解的集合,@AutoConfigurationPackage 作用是告诉Spring Boot 自动配置类的扫描范围,默认会限定在主类所在的包及其子包中搜索自动配置类。@Import注解中导入的AutoConfigurationImportSelector则会调用SpringFactoryLoader将META-INF目录下spring.factory文件中声明的Bean都加入到Spring容器中。

6-2 SpringBoot Starter(启动器)工作原理是什么?

Spring Boot在启动时:

  • Spring Boot 在启动时会去依赖的 Starter 包中寻找 resources/META-INF/spring.factories 文件,
    然后根据文件中配置的 Jar 包去扫描项目所依赖的 Jar 包。
  • 根据 spring.factories 配置加载 AutoConfigure 类
  • 根据 @Conditional 注解的条件,进行自动配置并将 Bean 注入 Spring容器

Spring Boot 在启动的时候,按照约定去读取 Spring Boot Starter 的配置信息,再根据配置信息对资源进行初始化,并注入到 Spring 容器中。这样 Spring Boot 启动完毕后,就已经准备好了一切资源,使用过程中直接注入对应 Bean 资源即可。

Mybatis

猜你喜欢

转载自blog.csdn.net/baiduwaimai/article/details/132007749