Spring(补档)


Spring

Spring是什么

Spring 它包括许多框架,例如 Spring framework、SpringMVC、SpringBoot、Spring Cloud、Spring Data、Spring Security 等,所以有人将它们亲切的称之为:Spring 全家桶。

Spring framework 就是我们平时说的 Spring 框架。Spring 框架是全家桶内其它框架的基础和核心。

Spring是轻量级的开源的JavaSE/EE容器框架。以 IoC(Inverse of Control,控制反转)和 AOP(Aspect Oriented Programming,面向切面编程)为内核。

在 Spring 中,认为一切 Java 类都是资源,而资源都是类的实例对象(Bean), Spring 所提供的 IoC 容器容纳并管理这些 Bean,Spring 是一种基于 Bean 的编程。

Spring 致力于 Java EE 应用各层的解决方案,对每一层都提供了技术支持。在表现层提供了与 Spring MVC、Struts2 框架的整合,在业务逻辑层可以管理事务和记录日志等,在持久层可以整合 MyBatis、Hibernate 和 JdbcTemplate 等技术。

Spring 框架充当了黏合剂和润滑剂的角色,能够将相应的 Java Web 系统柔顺地整合起来,并让它们更易使用。

Spring 框架具有以下几个特点

1)方便解耦,简化开发

Spring 就是一个大工厂,可以将所有对象的创建和依赖关系的维护交给 Spring Ioc容器管理。

2)方便集成各种优秀框架

Spring 不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如 Struts2、Hibernate、MyBatis 等)的直接支持。

3)降低 Java EE API 的使用难度

Spring 对 Java EE 开发中非常难用的一些 API(JDBC、JavaMail、远程调用等)都提供了封装,使这些 API 应用的难度大大降低。

4)方便程序的测试

Spring 支持 JUnit4,可以通过注解方便地测试 Spring 程序。

5)AOP 编程的支持

Spring 提供面向切面编程,可以方便地实现对程序进行权限拦截和运行监控等功能。

6)声明式事务的支持

只需要通过配置就可以完成对事务的管理,而无须手动编程。

Spring包含的模块有哪些

1. Spring Core

框架的最基础部分,提供 IoC 容器,对 bean 进行管理。

2. Spring Context

基于 bean,提供上下文信息,扩展出JNDI、EJB、电子邮件、国际化、校验和调度等功能。

3. Spring DAO

提供了JDBC的抽象层,它可消除冗长的JDBC编码和解析数据库厂商特有的错误代码,还提供了声明性事务管理方法。

4. Spring ORM

提供了常用的“对象/关系”映射APIs的集成层。 其中包括JPA、JDO、Hibernate、MyBatis 等。

5. Spring AOP

提供了符合AOP Alliance规范的面向方面的编程实现。并提供常用的拦截器,供用户自定义和配置。

6. Spring Web

提供了基础的 Web 开发的上下文信息,可与其他 web (Struts1,WEBWORK(Struts 2))进行集成。

7. Spring Web MVC

提供了 Web 应用的 Model-View-Controller 全功能实现。

Spring容器是什么

容器是Spring框架实现功能的核心,容器不只是帮我们创建了对象那么简单,它负责了Bean的整个的生命周期的管理——创建、装配、销毁。

怎么向容器中放入我们需要容器代为管理的对象呢?这就涉及到Spring的应用上下文了。

应用上下文就是将需要IoC容器帮我们管理的bean、bean 与 bean之间的协作关系,基于xml 或 Java注解的形式配置好,通过Spring应用上下文对象将其加载进IoC容器中,这样容器就能提供我们需要的对象管理服务。

Spring中有的两种容器类型,分别是:BeanFactoryApplicationContext

BeanFactory是最简单的容器,只能提供基本的DI功能;ApplicationContext继承了BeanFactory

对于ApplicationContext接口,Spring也为我们提供了多种类型的容器实现,供我们在不同的应用场景选择:

  • AnnotationConfigApplicationContext:从一个或多个基于java的配置类中加载上下文定义,适用于java注解的方式;
  • ClassPathXmlApplicationContext:从类路径下的一个或多个xml配置文件中加载上下文定义,适用于xml配置的方式;
  • FileSystemXmlApplicationContext:从文件系统下的一个或多个xml配置文件中加载上下文定义,也就是说系统盘符中加载xml配置文件;
  • AnnotationConfigWebApplicationContext:专门为web应用准备的,适用于注解方式;
  • XmlWebApplicationContext:从web应用下的一个或多个xml配置文件加载上下文定义,适用于xml配置方式。

BeanFactoryApplicationContext的不同点:

1.对bean的加载方式不同

BeeanFactory是使用的懒加载的方式,只有在调用getBean()时才会进行实例化。
ApplicationContext是使用预加载的方式,即在应用启动后就实例化所有的bean。

2.特性不同:

BeanFactory接口只提供了IOC/DI的支持,常用的API是XMLBeanFactory。

ApplicationContext是整个Spring应用中的中央接口,它继承了BeanFactory接口,具备BeanFactory的所有特性,还有一些其他特性比如:AOP的功能事件发布/响应(ApplicationEvent)、国际化信息支持等。

3.应用场景不同:

BeanFactory适合系统资源(内存)较少的环境中使用延迟实例化,比如运行在移动应用中。
ApplicationContext适合企业级的大型web应用,将耗时的内容在系统启动的时候就完成。

Spring容器启动流程

  1. 扫描并注册Bean定义:Spring容器会扫描指定的包或目录,查找带有特定注解(如@Component、@Service、@Repository等)的类。它会创建对应的BeanDefinition对象,包含了Bean的元数据信息,并将这些BeanDefinition对象存储在一个Map中。

  2. 预处理Bean定义:在注册Bean定义后,Spring容器会对Bean定义进行预处理操作。这包括合并父子类的BeanDefinition,解析属性占位符,处理自动装配等。

  3. 实例化非懒加载的单例Bean:Spring容器会遍历所有非懒加载的单例BeanDefinition,并根据BeanDefinition创建相应的Bean实例。这涉及到实例化Bean、属性填充和依赖注入等操作。

  4. 初始化Bean:在单例Bean实例化后,Spring容器会调用各个Bean的初始化方法。这可以包括自定义的初始化逻辑,如初始化数据库连接、加载配置文件等。还可以执行BeanPostProcessor的前置初始化方法。

  5. 注册BeanPostProcessor:Spring容器会注册实现了BeanPostProcessor接口的Bean后处理器。这些后处理器可以在Bean实例化和初始化的过程中对Bean进行增强,如AOP代理、属性填充等。注册后处理器之后,会执行BeanPostProcessor的后置初始化方法。

  6. 完成启动过程:当所有非懒加载的单例Bean都被实例化、初始化完成后,Spring容器的启动过程结束。此时,可以发布容器启动事件,通知相关的监听器。

Spring用到了哪些设计模式

  • 工厂设计模式 : Spring 使用工厂模式通过 BeanFactoryApplicationContext 创建 bean 对象。
  • 代理设计模式 : Spring AOP 功能的实现。
  • 单例设计模式 : Spring 中的 Bean 默认都是单例的。
  • 模板方法模式 : Spring 中 jdbcTemplatehibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
  • 包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
  • 观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
  • 适配器模式 : Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller

Spring是怎么解决循环依赖的

首先,需要明确的是spring对循环依赖的处理有三种情况:

  1. 构造器的循环依赖:这种依赖spring是处理不了的,直接抛出BeanCurrentlylnCreationException异常。
  2. 单例模式下的setter循环依赖:通过“三级缓存”处理循环依赖。
  3. 非单例循环依赖:无法处理。

接下来,我们具体看看spring是如何处理第二种循环依赖的。

Spring单例对象的初始化大略分为三步:

  1. createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象;
  2. populateBean:填充属性,这一步主要是多bean的依赖属性进行填充;
  3. initializeBean:初始化,调用spring xml中的init 方法。

从上面讲述的单例bean初始化步骤我们可以知道,循环依赖主要发生在第一步、第二步。也就是构造器循环依赖和field循环依赖。 Spring为了解决单例的循环依赖问题,使用了三级缓存。

这三级缓存的作用分别是:

  • singletonFactories : 进入实例化阶段的单例对象工厂的cache (三级缓存);
  • earlySingletonObjects :完成实例化但是尚未初始化的,提前暴光的单例对象的Cache (二级缓存);
  • singletonObjects:完成初始化的单例对象的cache(一级缓存)。

这样做有什么好处呢?让我们来分析一下

“A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象”这种循环依赖的情况。A首先完成了初始化的第一步,并且将自己提前曝光到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对象完成了初始化。

IoC

什么是IoC

IoC(Inversion of Control:控制反转) 是一种设计思想,而不是一个具体的技术实现。IoC 的思想就是将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。

Ioc容器:实际上就是个map(key,value),⾥⾯存的是各种对象(在xml⾥配置的bean节点、@repository、@mapper、@service、@controller、@component),在项⽬启动的时候通过读取XML配置⽂件或者扫描注解,使⽤反射创建对象放到map⾥。

map⾥有各种对象,在代码⾥需要⽤到⾥⾯的对象时,再通过DI注⼊(@autowired、@resource等注解,xml⾥bean节点内的ref属性)

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

@Component 和 @Bean 的区别

  1. @Component 注解作用于类,而@Bean注解作用于方法。

  2. @Component通常是通过类路径扫描来自动侦测以及自动装配到 Spring 容器中(我们可以使用 @ComponentScan 注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。@Bean 注解通常是我们在标有该注解的方法中定义产生这个 bean,@Bean告诉了 Spring 这是某个类的实例,当我需要用它的时候还给我。

  3. @Bean 注解比 @Component 注解的自定义性更强,而且很多地方我们只能通过 @Bean 注解来注册 bean。比如当我们引用第三方库中的类需要装配到 Spring容器时,则只能通过 @Bean来实现。

@Bean注解使用示例:

@Configuration
public class AppConfig {
    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl();
    }

}

下面这个例子是通过 @Component 无法实现的。

@Bean
public OneService getService(status) {
    case (status)  {
        when 1:
                return new serviceImpl1();
        when 2:
                return new serviceImpl2();
        when 3:
                return new serviceImpl3();
    }
}

@Autowired 和 @Resource 的区别

  1. @Autowired 是 Spring 提供的注解,@Resource 是 JDK 提供的注解。

  2. Autowired 默认的注入方式为byType(根据类型进行匹配),@Resource默认注入方式为 byName(根据名称进行匹配)。

  3. 当一个接口存在多个实现类的情况下,@Autowired@Resource都需要通过名称才能正确匹配到对应的 Bean。Autowired 可以通过 @Qualifier 注解来显式指定名称,@Resource可以通过 name 属性来显式指定名称。

常用的依赖注入(Bean的自动装配)的方式有哪些

1. 属性注入

属性注入是我们最熟悉,也是日常开发中使用最多的一种注入方式(@Autowired)

优点分析

属性注入最大的优点就是实现简单、使用简单,只需要给变量上添加一个注解(@Autowired)

缺点分析

  1. 功能性问题:无法注入一个不可变的对象(final 修饰的对象);
  2. 通用性问题:只能适应于 IoC 容器;
  3. 设计原则问题:更容易违背单一设计原则。

2. Setter 注入

优点分析

符合单一职责的设计原则

缺点分析

  1. 不能注入不可变对象(final 修饰的对象);
  2. 注入的对象可被修改。

3.构造方法注入

优点分析

  1. 可注入不可变对象;
  2. 注入对象不会被修改;
  3. 注入对象会被完全初始化;
  4. 通用性更好。

(Spring 官方推荐的是构造方法注入)

Spring⽀持的⼏种bean的作⽤域

  1. singleton : IoC 容器中只有唯一的 bean 实例。Spring 中的 bean 默认都是单例的,是对单例设计模式的应用。
  2. prototype : 每次获取都会创建一个新的 bean 实例。也就是说,连续 getBean() 两次,得到的是不同的 Bean 实例。
  3. request (仅 Web 应用可用): 每一次 HTTP 请求都会产生一个新的 bean(请求 bean),该 bean 仅在当前 HTTP request 内有效。
  4. session (仅 Web 应用可用) : 每一次来自新 session 的 HTTP 请求都会产生一个新的 bean(会话 bean),该 bean 仅在当前 HTTP session 内有效。
  5. application (仅 Web 应用可用): 每个 Web 应用在启动时创建一个 Bean(应用 Bean),该 bean 仅在当前应用启动时间内有效。
  6. websocket (仅 Web 应用可用):每一次 WebSocket 会话产生一个新的 bean。
  7. global-session: 全局 session 作用域,仅仅在基于 Portlet 的 web 应用中才有意义,Spring5 已经没有了。Portlet 是能够生成语义代码(例如:HTML)片段的小型 Java Web 插件。它们基于 portlet 容器,可以像 servlet 一样处理 HTTP 请求。但是,与 servlet 不同,每个 portlet 都有不同的会话

如何配置 bean 的作用域

xml 方式:

<bean id="..." class="..." scope="singleton"></bean>

注解方式:

@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Person personPrototype() {
    return new Person();
}

Spring中Bean是线程安全的吗

Spring本身并没有针对Bean做线程安全的处理,所以:

  1. 如果Bean是⽆状态的,那么Bean则是线程安全的

  2. 如果Bean是有状态的,那么Bean则不是线程安全的

Bean是不是线程安全,跟Bean的作⽤域没有关系,Bean的作⽤域只是表示Bean的⽣命周期范围,对于任何⽣命周期的Bean都是⼀个对象,这个对象是不是线程安全的,还是得看这个Bean对象本身。

常见的有两种解决办法:

  1. 在 Bean 中尽量避免定义可变的成员变量。
  2. 在类中定义一个 ThreadLocal 成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐)。

大部分 Bean 实际都是无状态(没有实例变量)的(Dao、Service), Bean 是线程安全的。

Bean 的生命周期

  • 实例化 Instantiation
  • 属性赋值 Populate
  • 初始化 Initialization
  • 销毁 Destruction

实例化 -> 属性赋值 -> 初始化 -> 销毁

  1. 根据配置情况调用Bean构造方法或工厂方法实例化 Bean。
  2. 利用依赖注入完成Bean中所有属性值的配置注入。
  3. 如果Bean 实现了BeanNameAware 接口,则 Spring调用Bean的setBeanName()方法传入当前Bean的id值。
  4. 如果Bean实现了BeanFactoryAware 接口,则 Spring 调用setBeanFactory()方法传入当前工厂实例的引用。
  5. 如果Bean 实现了ApplicationContextAware 接口,则 Spring调用setApplicationContext()方法传入当前ApplicationContext 实例的引用。
  6. 如果BeanPostProcessor 和Bean关联,则 Spring将调用该接口的预初始化方法postProcessBeforelnitialzation()对 Bean进行加工操作,此处非常重要,Spring的AOP就是利用它实现的。
  7. 如果Bean实现了InitializingBean接口,则 Spring将调用afterPropertiesSet()方法。
  8. 如果在配置文件中通过 init-method属性指定了初始化方法,则调用该初始化方法。
  9. 如果BeanPostProcessor和 Bean关联,则 Spring将调用该接口的初始化方法、 postProcessAfterlntialization()。此时,Bean已经可以被应用系统使用了。
  10. 如果在 中指定了该Bean的作用范围为scope=“singleton”,则将该Bean放入Spring IOC的缓存池中,将触发Spring对该Bean 的生命周期管理;如果在中指定了该Bean的作用范围为scope=“prototype”,则将该Bean交给调用者,调用者管理该Bean的生命周期,Spring不再管理该 Bean。
  11. 如果Bean实现了DisposableBean接口,则 Spring 会调用destory()方法将Spring 中的 Bean销毁; 如果在配置文件中通过destory-method属性指定了Bean的销毁方法,则 Spring将调用该方法。

AOP

什么是AOP

AOP(Aspect Oriented Programing)是面向切面编程思想。简单来说,它可以统一解决一批组件的共性需求(如权限检查、记录日志、事务管理等)。在AOP思想下,我们可以将解决共性需求的代码独立出来,然后通过配置的方式,声明这些代码在什么地方、什么时机调用。当满足调用条件时,AOP会将该业务代码织入到我们指定的位置,从而统一解决了问题,又不需要修改这一批组件的代码。

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

AOP 切面编程设计到的一些专业术语:

(1)切面(Aspect)

切面是一个横切关注点的模块化(日志处理)。切面由切入点和通知组成,它既包含了横切逻辑的定义,也包括了切入点的定义。

可以简单地认为, 使用 @Aspect 注解的类就是切面

(2) 目标对象(Target)

目标对象指将要被增强的对象,即包含主业务逻辑的类对象。或者说是被一个或者多个切面所通知的对象。

(3) 连接点(JoinPoint)

程序执行过程中明确的点,如方法的调用或特定的异常被抛出。连接点由两个信息确定:

  • 方法(表示程序执行点,即在哪个目标方法)
  • 相对点(表示方位,即目标方法的什么位置,比如调用前,后等)

简单来说,连接点就是被拦截到的程序执行点,因为Spring只支持方法类型的连接点,所以在Spring中连接点就是被拦截到的方法。

@Before("pointcut()")
public void log(JoinPoint joinPoint) { //这个JoinPoint参数就是连接点
}

(4) 切入点(PointCut)

切入点是对连接点进行拦截的条件定义。切入点表达式如何和连接点匹配是AOP的核心

@Pointcut("execution(* com.test.aop.service..*(..))")
public void pointcut() {
}

上边切入点的匹配规则是 com.test.aop.service包下的所有类的所有函数。

(5) 通知(Advice)

通知是指拦截到连接点之后要执行的代码,包括了“around”、“before”和“after”等不同类型的通知。Spring AOP框架以拦截器来实现通知模型,并维护一个以连接点为中心的拦截器链。

// @Before说明这是一个前置通知,log函数中是要前置执行的代码,JoinPoint是连接点,
@Before("pointcut()")
public void log(JoinPoint joinPoint) { 
}

(6) 织入(Weaving)

织入是将切面和业务逻辑对象连接起来, 并创建通知代理的过程。织入可以在编译时,类加载时和运行时完成。在编译时进行织入就是静态代理,而在运行时进行织入则是动态代理。

CGLib和JDK Proxy的区别

  1. JDK Proxy 是 Java 语言内置的动态代理,必须要通过实现接口的方式来代理相关的类; CGLib 是第三方提供的基于 ASM 的高效动态代理类,它通过实现被代理类的子类来实现动态代理的功能,因此被代理的类不能使用 final 修饰。
  2. JDK 动态代理的实现方式是反射;CGLib 实现动态代理是基于 ASM(一个 Java 字节码操作框架)而非反射实现的。

Spring AOP 和 AspectJ AOP 的区别

  1. Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。
  2. Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。
  3. 性能上,由于Spring AOP是基于动态代理来实现的,在容器启动时需要生成代理实例,在方法调用上也会增加栈的深度,使得Spring AOP的性能不如AspectJ的那么好。
  4. Spring AOP需要依赖IOC容器来管理,并且只能作用于Spring容器,AspectJ可以在所有域对象上实现。
  5. Spring AOP仅支持方法执行切入点,AspectJ支持所有切入点。
  6. Spring AOP功能不强-仅支持方法级编织,AspectJ更强大 - 可以编织字段、方法、构造函数、静态初始值设定项、最终类/方法等……。
  7. Spring AOP则致力于解决企业级开发中最普遍的AOP(方法织入,AspectJ是AOP编程的完全解决方案。

AspectJ 的通知类型

  1. 前置通知 Before:目标对象的方法调用之前触发
  2. 后置通知 After :目标对象的方法调用之后触发
  3. 返回通知 AfterReturning:目标对象的方法调用完成,在返回结果值之后触发
  4. 异常通知 AfterThrowing :目标对象的方法运行中抛出 / 触发异常后触发。
  5. 环绕通知 Around :编程式控制目标对象的方法调用。环绕通知是所有通知类型中可操作范围最大的一种,因为它可以直接拿到目标对象,以及要执行的方法。

Spring aop 的通知类型

  1. 前置通知 Before:在连接点前面执行,前置通知不会影响连接点的执行,除非此处抛出异常
  2. 后置通知 After returning :在连接点正常执行完成后执行,如果连接点抛出异常,则不会执行
  3. 异常通知 After throwing :在连接点抛出异常后执行
  4. 最终通知 After (finally) :在连接点执行完成后执行,不管是正常执行完成,还是抛出异常,都会执行返回通知中的内容
  5. 环绕通知 Around :环绕通知围绕在连接点前后,能在方法调用前后自定义一些操作

Spring MVC

什么是Spring MVC

MVC 是模型(Model)、视图(View)、控制器(Controller)的简写,其核心思想是通过将业务逻辑、数据、显示分离来组织代码。

MVC 是一种设计模式,Spring MVC 是一款很优秀的 MVC 框架,可以帮助我们进行更简洁的 Web 层的开发,并且它天生与 Spring 框架集成。

Spring MVC 下我们一般把后端项目分为 Service 层(处理业务)、Dao 层(数据库操作)、Entity 层(实体类)、Controller 层(控制层,返回数据给前台页面)。

Spring MVC的核心组件

  1. DispatcherServlet核心的中央处理器,负责接收请求、分发,并给予客户端响应。
  2. HandlerMapping处理器映射器,根据 uri 去匹配查找能处理的 Handler ,并会将请求涉及到的拦截器和 Handler 一起封装。
  3. HandlerAdapter处理器适配器,根据 HandlerMapping 找到的 Handler ,适配执行对应的 Handler;因为SpringMVC中的Handler可以是任意的形式,只要能处理请求就行,但是Servlet需要的处理⽅法的结构却是固定的,都是以request和response为参数的⽅法,所以需要适配器。
  4. Handler请求处理器,处理实际请求的处理器。在Controller层中@RequestMapping标注的所有⽅法都可以看成是⼀个Handler。
  5. ViewResolver视图解析器,根据 Handler 返回的逻辑视图 / 视图,解析并渲染真正的视图View(View是⽤来渲染⻚⾯的,也就是将程序返回的参数填⼊模板⾥,⽣成html(也可能是其它类型)⽂件。),并传递给 DispatcherServlet 响应客户端

Spring MVC ⼯作流程

1)⽤户发送请求⾄前端控制器 DispatcherServlet。

2)DispatcherServlet 收到请求调⽤ HandlerMapping 处理器映射器。

3)处理器映射器找到具体的处理器(可以根据 xml 配置、注解进⾏查找),⽣成处理器及处理器拦截器(如果有则⽣成)⼀并返回给 DispatcherServlet。

4)DispatcherServlet 调⽤ HandlerAdapter 处理器适配器。

5)HandlerAdapter 经过适配调⽤具体的处理器(Controller,也叫后端控制器)

6)Controller 执⾏完成返回 ModelAndView。

7)HandlerAdapter 将 controller 执⾏结果 ModelAndView 返回给 DispatcherServlet。

8)DispatcherServlet 将 ModelAndView 传给 ViewReslover 视图解析器。

9)ViewReslover 解析后返回具体 View。

10)DispatcherServlet 根据 View 进⾏渲染视图(即将模型数据填充⾄视图中)。

11)DispatcherServlet 响应⽤户。

Spring MVC注解

@Controller

Spring MVC框架中,@Controller注解用于标识一个类是控制器,主要用于处理HTTP请求和响应。当一个类被标注了@Controller注解后,Spring将会对其进行如下操作:

Spring会遍历扫描出来的所有bean,过滤出那些添加了注解@Controller的bean,将Controller中所有添加了注解@RequestMapping的方法解析出来封装成RequestMappingInfo存储到RequestMappingHandlerMapping中的mappingRegistry。后续请求到达时,会从mappingRegistry中查找能够处理该请求的方法。

@Repository 与 @Mapper的区别

@Repository 是 Spring 的注解,用于声明一个 Bean。@Repository注解放在mapper接口上本来没只是为了标识,要想真正是这个接口被扫描,必须使用@MapperScannerConfigurer

<!-- 配置 Mapper 扫描器 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.test.mapper"/>
</bean>

这段配置会扫描com.test.mapper包下所有的接口,然后创建各自的动态代理类。

@Mapper是mybatis自身带的注解。在spring程序中,mybatis需要找到对应的mapper,在编译时生成动态代理类,与数据库进行交互,这时需要用到@Mapper注解

相同点:
@Mapper和@Repository都是作用在dao层接口,使得其生成代理对象bean,交给spring 容器管理

不同点:

  1. @Mapper不需要配置扫描地址,可以单独使用,如果有多个mapper文件的话,可以在项目启动类中加入@MapperScan(“mapper文件所在包”)
  2. @Repository不可以单独使用,否则会报错误,要想用,必须配置扫描地址(@MapperScannerConfigurer)
@RequestMapping

作用:该注解的作用就是用来处理请求地址映射的,也就是说将其中的处理器方法映射到url路径上。

属性:

  • method:是让你指定请求的method的类型,比如常用的有get和post。
  • value:是指请求的实际地址,如果是多个地址就用{}来指定就可以了。
  • produces:指定返回的内容类型,当request请求头中的Accept类型中包含指定的类型才可以返回的。
  • consumes:指定处理请求的提交内容类型,比如一些json、html、text等的类型。
  • headers:指定request中必须包含那些的headed值时,它才会用该方法处理请求的。
  • params:指定request中一定要有的参数值,它才会使用该方法处理请求。
@RequestParam

作用:是将请求参数绑定到你的控制器的方法参数上,是Spring MVC中的接收普通参数的注解。

属性:

  • value是请求参数中的名称。
  • required是请求参数是否必须提供参数,它的默认是true,意思是表示必须提供。
@ResponseBody

作用:如果作用在方法上,就表示该方法的返回结果是直接按写入的Http body中(一般在异步获取数据时使用的注解)。

属性:required,是否必须有请求体。它的默认值是true,在使用该注解时,值得注意的当为true时get的请求方式是报错的,如果你取值为false的话,get的请求是null。

@RequestBody

作用:作用在方法的参数前,将前端传来的json格式的数据转为自己定义好的javabean对象

@PathVaribale

作用:该注解是用于绑定url中的占位符,但是注意,spring3.0以后,url才开始支持占位符的,它是Spring MVC支持的rest风格url的一个重要的标志。

Spring MVC的拦截器

拦截器会对处理器进行拦截,这样通过拦截器就可以增强处理器的功能。

Spring MVC中,所有的拦截器都需要实现HandlerInterceptor接口,该接口包含如下三个方法:preHandle()、postHandle()、afterCompletion()。

Spring MVC拦截器的开发步骤如下:

  1. 开发拦截器: 实现handlerInterceptor接口,从三个方法中选择合适的方法,实现拦截时要执行的具体业务逻辑。
  2. 注册拦截器: 定义配置类,并让它实现WebMvcConfigurer接口,在接口的addInterceptors方法中,注册拦截器,并定义该拦截器匹配哪些请求路径。

拦截器和过滤器的区别是什么

  1. 位置不同:拦截器是在应用程序内部具体方法级别的组件。过滤器位于应用程序外部,处于请求和响应的路径之间
  2. 作用不同:拦截器主要用于在方法调用前后执行特定的操作,如日志记录、性能监视、安全检查等。过滤器主要用于在请求进入应用程序或响应离开应用程序之前进行预处理或后处理操作,如身份验证、请求转发、响应包装等。
  3. 原理不同:拦截器通常是基于面向切面编程(AOP)的概念。过滤器则是基于Servlet规范的一部分,它们通过在web.xml文件中进行配置,并按照一定的顺序进行调用。
  4. 作用范围不同:拦截器作用范围局限于特定的方法或一组方法。过滤器是作用范围可以覆盖整个应用程序,甚至整个Web容器。

拦截器适用于对方法级别的操作和控制,而过滤器更适用于对请求和响应进行全局的预处理和后处理。

统一异常处理

推荐使用注解的方式统一异常处理,具体会使用到 @ControllerAdvice + @ExceptionHandler 这两个注解 。

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

Spring Boot、Spring MVC 和 Spring 有什么区别

spring是⼀个IOC容器,⽤来管理Bean,使⽤依赖注⼊实现控制反转,可以很⽅便的整合各种框架,提供AOP机制弥补OOP的代码重复问题、更⽅便将不同类不同⽅法中的共同处理抽取成切⾯、⾃动注⼊给⽅法执⾏,⽐如⽇志、异常等

Spring MVC 是 Spring 中的一个很重要的模块,主要赋予 Spring 快速构建 MVC 架构的 Web 程序的能力。MVC 是模型(Model)、视图(View)、控制器(Controller)的简写,其核心思想是通过将业务逻辑、数据、显示分离来组织代码。

Spring Boot是spring提供的⼀个快速开发⼯具包,让程序员能更⽅便、更快速的开发Spring+Spring MVC应⽤,简化了配置(约定了默认配置),整合了⼀系列的解决⽅案(starter机制)、redis、mongodb、es,可以开箱即⽤

Spring 事务

Spring 事务的实现方式

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

Spring事务的实现原理

事务这个概念是数据库层⾯的,Spring只是基于数据库中的事务进⾏了扩展,以及提供了⼀些能让程序员更加⽅便操作事务的⽅式。

通过在某个⽅法上增加@Transactional注解,就可以开启事务,这个⽅法中所有的sql都会在⼀个事务中执⾏,统⼀成功或失败。

在⼀个⽅法上加了@Transactional注解后,Spring会基于这个类⽣成⼀个代理对象,会将这个代理对象作为bean,当在使⽤这个代理对象的⽅法时,如果这个⽅法上存在@Transactional注解,那么代理逻辑会先把事务的⾃动提交设置为false,然后再去执⾏原本的业务逻辑⽅法,如果执⾏业务逻辑⽅法没有出现异常,那么代理逻辑中就会将事务进⾏提交,如果执⾏业务逻辑⽅法出现了异常,那么则会将事务进⾏回滚。

针对哪些异常回滚事务是可以配置的,可以利⽤@Transactional注解中的rollbackFor属性进⾏配置,默认情况下会对RuntimeException和Error进⾏回滚。

Spring 事务中的隔离级别

  1. 默认级别 ISOLATION_DEFAULT :默认的隔离级别,MySQL 默认采用的 REPEATABLE_READ 隔离级别 Oracle 默认采用的 READ_COMMITTED 隔离级别.
  2. 未提交读 ISOLATION_READ_UNCOMMITTED :最低的隔离级别,它允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
  3. 不可重复读 ISOLATION_READ_COMMITTED : 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
  4. 可重复读 ISOLATION_REPEATABLE_READ : 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
  5. 可串⾏化 ISOLATION_SERIALIZABLE : 最高的隔离级别,事务之间完全不产生干扰,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。

(数据库的配置隔离级别是Read Commited,⽽Spring配置的隔离级别是Repeatable Read,这时以Spring配置的为准,如果spring设置的隔离级别数据库不⽀持,效果取决于数据库)

Spring事务传播行为

多个事务⽅法相互调⽤时,事务如何在这些⽅法间传播,⽅法A是⼀个事务的⽅法,⽅法A执⾏过程中调⽤了⽅法B,那么⽅法B有⽆事务以及⽅法B对事务的要求不同都会对⽅法A的事务具体执⾏造成影响,同时⽅法A的事务对⽅法B的事务执⾏也有影响,这种影响具体是什么就由两个⽅法所定义的事务传播类型所决定。

  1. REQUIRED(Spring默认的事务传播类型):如果当前没有事务,则⾃⼰新建⼀个事务,如果当前存在事务,则加⼊这个事务

  2. SUPPORTS:当前存在事务,则加⼊当前事务,如果当前没有事务,就以⾮事务⽅法执⾏

  3. MANDATORY:当前存在事务,则加⼊当前事务,如果当前事务不存在,则抛出异常。

  4. REQUIRES_NEW:创建⼀个新事务,如果存在当前事务,则挂起该事务。

  5. NOT_SUPPORTED:以⾮事务⽅式执⾏,如果当前存在事务,则挂起当前事务

  6. NEVER:不使⽤事务,如果当前事务存在,则抛出异常

  7. NESTED:如果当前事务存在,则在嵌套事务中执⾏,否则REQUIRED的操作⼀样(开启⼀个事务)

Spring的事务实现流程

  1. Spring事务底层是基于数据库事务和AOP机制的

  2. ⾸先对于使⽤了@Transactional注解的Bean,Spring会创建⼀个代理对象作为Bean

  3. 当调⽤代理对象的⽅法时,会先判断该⽅法上是否加了@Transactional注解

  4. 如果加了,那么则利⽤事务管理器创建⼀个数据库连接

  5. 并且修改数据库连接的autocommit属性为false,禁⽌此连接的⾃动提交,这是实现Spring事务⾮常重要的⼀步

  6. 然后执⾏当前⽅法,⽅法中会执⾏sql

  7. 执⾏完当前⽅法后,如果没有出现异常就直接提交事务

  8. 如果出现了异常,并且这个异常是需要回滚的就会回滚事务,否则仍然提交事务

  9. Spring事务的隔离级别对应的就是数据库的隔离级别

  10. Spring事务的传播机制是Spring事务⾃⼰实现的,也是Spring事务中最复杂的

  11. Spring事务的传播机制是基于数据库连接来做的,⼀个数据库连接⼀个事务,如果传播机制配置为需要新开⼀个事务,那么实际上就是先建⽴⼀个数据库连接,在此新数据库连接上执⾏sql

Spring中什么时候@Transactional会失效

因为Spring事务是基于代理来实现的,如果不是被代理对象来调⽤这个⽅法,@Transactional会失效。

如果某个⽅法是private的,那么@Transactional也会失效,因为底层cglib是基于⽗⼦类来实现的,⼦类是不能重载⽗类的private⽅法的,所以⽆法很好的利⽤代理,也会导致@Transactianal失效


文章作者: Aiaa
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Aiaa !
  目录