黑马程序员Spring视频教程,深度讲解spring5底层原理
容器接口
- Bean Factory 能做哪些事
- 提供了getBean
- 控制反转、基本的依赖注入、Bean的生命周期的各种功能,由它的实现类提供
- ApplicationContext有哪些扩展功能
- 国际化、通配符匹配资源、事件发布、环境信息及配置文件获取
- 事件解耦
- 概念:
- 事件解耦是一种设计模式,用于将模块间的关联度降至最低,使它们之间的通信更加松散。
- 发布者(Producer)负责发布事件,而订阅者(Consumer)监听并响应这些事件。
- 示例:
Producer发布PublishEvent事件,Consumer监听并响应这个事件。Producer和Consumer之间不直接耦合,它们通过事件通信,提高了系统的灵活性。
容器实现
- BeanFactory实现的特点
- 不会主动调用 BeanFactory 后处理器: BeanFactory 在容器启动时不会主动触发注册的 BeanFactory 后处理器的执行。
- 不会主动添加 Bean 后处理器: BeanFactory 不会自动注册和调用 Bean 后处理器。开发者需要手动配置后处理器,使其在 bean 的生命周期中得以执行。
- 懒加载单例 bean: BeanFactory 在容器启动时,不会主动实例化和初始化单例 bean。单例 bean 在第一次被请求时才会被创建,从而实现懒加载的效果。
- 不会解析占位符和表达式: 默认情况下,BeanFactory 不会自动解析占位符(如
${})和 Spring 表达式(如#{})。如果需要这些功能,需要addEmbeddedValueResolver适当的解析器。 - ApplicationContext的常见实现和用法
- ClassPathXmlApplicationContext:
- 这是通过类路径(Classpath)上的 XML 配置文件来创建 Spring 容器的一种方式。
- 使用 XML 配置文件定义 Bean、依赖关系、切面、AOP配置等。
- 配置文件通常放在 classpath 下的某个目录,可以在 Java 项目中方便地管理和维护。
- 示例:
javaCopy code
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
- FileSystemXmlApplicationContext:
- 这种方式也是通过 XML 配置文件来创建 Spring 容器,不过它的配置文件是从文件系统中读取的,而不是从类路径中。
- 适用于非 Web 环境下的应用,或者希望从文件系统中读取配置文件的场景。
- 示例:
javaCopy code
ApplicationContext context = new FileSystemXmlApplicationContext("/path/to/applicationContext.xml");
- AnnotationConfigApplicationContext:
- 这种方式不使用 XML 配置文件,而是基于 Java 类来配置 Spring 容器。
- 使用
@Configuration和@Bean注解来声明 Bean 和配置,更适合基于代码的配置,而不是 XML。 - 在 Spring Boot 项目中广泛使用,支持注解扫描和自动装配。
- 示例:
javaCopy code ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
- AnnotationConfigServletWebServerApplicationContext:
- 这是一个专为 Servlet 环境设计的
ApplicationContext,适用于 Web 应用程序。 - 基于 Java 类的配置,通常用于 Spring Boot Web 项目。
- 除了提供 Bean 的配置,还可以支持 Web MVC、数据源配置等。
- 示例:
javaCopy code ApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(AppConfig.class);
- 内嵌容器、注册DispatcherServlet
- 内嵌容器:
- 内嵌容器是指将 Web 服务器嵌入到 Spring 应用中的一种技术。它允许我们在应用中直接运行一个轻量级的 Web 服务器,而不需要依赖外部的 Web 服务器(如 Tomcat 或 Jetty)。这种方式在开发和测试阶段非常有用,它使应用的部署和启动更加简单,也方便在独立环境中运行。
- 注册 DispatcherServlet:
- DispatcherServlet 是 Spring MVC 框架中的核心组件,它充当了前端控制器的角色,接收客户端的请求并将请求分发到合适的控制器进行处理。在 Web 应用中,需要将 DispatcherServlet 注册到 Web 容器中,这样它才能接收并处理请求。
Bean的生命周期
- Spring Bean 生命周期的各个阶段
- 实例化:
- 在 Spring 容器启动时,根据配置信息或注解等方式,实例化 Bean 对象,标志着 Bean 生命周期的起始。
- 实例化通过构造函数完成。
- 设置属性:
- 在 Bean 实例化后,容器会进行依赖注入(DI),即注入 Bean 的属性值。
- 这包括使用 setter 方法、注解等方式,将所需的属性值设置到 Bean 中。
- 初始化:
- 在 Bean 的属性设置完成后,容器调用 Bean 的初始化方法(如果有定义)。
- 如果 Bean 实现了
InitializingBean接口,会调用其afterPropertiesSet方法;或者可以在配置文件中使用init-method属性指定初始化方法。 - 销毁:
- 当容器关闭或销毁时,触发 Bean 的销毁前方法(如果有定义)。
- 如果 Bean 实现了
DisposableBean接口,会调用其destroy方法;或者可以在配置文件中使用destroy-method属性指定销毁方法。 - 模板设计模式
- 模板设计模式是一种软件设计模式,适用于处理某个方法中包含固定不变的部分,以及可以变动的部分,但这些变动部分又具体实现不确定的情况。通过抽象成接口,可以将这些不确定部分的实现留给实现类来完成,从而实现代码重构和提高灵活性。
- 核心思想:
- 促进代码重用: 模板设计模式通过将通用的算法骨架放在超类中,使多个子类可以共享这个骨架。这有助于避免在不同类中重复编写相同的代码,提高了代码的重用性。
- 允许扩展和修改: 通过在子类中覆盖具体步骤,你可以根据需要扩展或修改算法的行为,而无需修改整个算法。这使得代码更加灵活,能够适应不同的需求变化。
- 提供清晰结构和易理解性: 模板设计模式为算法的整体流程和各个步骤之间的关系提供了一种清晰的结构。这使得代码更易于理解和维护,因为每个步骤的责任和作用都被明确定义。
- 示例
public class TestMethodTemplate {
public static void main(String[] args) {
MyBeanFactory factory = new MyBeanFactory();
factory.addPostProcessor(new BeanPostProcessor() {
@Override
public void inject() {
System.out.println("解析@Autowired");
}
});
factory.getBean();
}
static class MyBeanFactory {
List<BeanPostProcessor> beanPostProcessors = new ArrayList<>();
public void getBean() {
System.out.println("构造");
System.out.println("依赖注入");
for (BeanPostProcessor beanPostProcessor : beanPostProcessors) {
beanPostProcessor.inject();
}
System.out.println("初始化");
}
public void addPostProcessor(BeanPostProcessor processor) {
beanPostProcessors.add(processor);
}
}
interface BeanPostProcessor {
void inject();
}
}
Bean后处理器
- Bean后处理器的作用:为Bean生命周期各个阶段提供扩展
- Spring Bean 后置处理器的作用
- 实例化前后(
InstantiationAwareBeanPostProcessor):控制 Bean 实例化过程,甚至替换原始 Bean 实例。 - 依赖注入阶段:在属性注入时拦截或修改,实现特定的依赖注入逻辑。
- 初始化前后:在 Bean 初始化前后执行自定义操作,如执行初始化方法、配置属性初始化。
- 销毁前后:在 Bean 销毁前后执行清理操作,类似于
@PreDestroy。 - MyBeanPostProcessor 示例
MyBeanPostProcessor类实现了InstantiationAwareBeanPostProcessor和DestructionAwareBeanPostProcessor接口,可以通过实现相应的方法在 Bean 生命周期的不同阶段插入自定义逻辑。- 在
InstantiationAwareBeanPostProcessor接口中: postProcessBeforeInstantiation方法可以在实例化之前执行操作,甚至返回代理对象替换原始 Bean 实例。postProcessAfterInstantiation方法在实例化之后执行,可返回false跳过依赖注入阶段。- 在
DestructionAwareBeanPostProcessor接口中: postProcessBeforeDestruction方法在销毁之前执行,通常用于执行清理操作。- 常见的后处理器
- AutowiredAnnotationBeanPostProcessor:解析@Autowired
- processor.setBeanFactory //
- processor.postProcessProperties(null,bean1,"bean1"); //在依赖注入阶段会调用这个方法,在这个方法中完成@Autowired、@Value的解析
- postProcessProperties方法: 这是Spring框架中的一个后置处理方法,用于在Bean初始化过程中进行额外操作,特别是在属性注入之后的处理。
- findAutowiringMetadata方法: 在自动装配过程中,这个方法起到重要作用。它会寻找那些被注解为@Autowired的成员变量和方法,这意味着这些地方需要依赖注入。
- @Autowired注解: 这是Spring框架提供的一个注解,用于自动装配。它告诉Spring容器去自动查找并注入适当的依赖对象,减少了手动配置的繁琐性。
- InjectionMetadata类: 这个类用来封装自动装配的元数据信息,包括需要进行自动装配的目标成员变量和方法。
- InjectionMetadata.inject方法: 这个方法执行了实际的自动装配操作。它将找到的依赖对象注入到相应的成员变量中,并使用反射调用方法,完成了属性赋值和可能的其他操作。
- CommonAnnotationBeanPostProcessor:解析@Resource、初始化前解析@PostConstruct、PreDistroy
- ConfigurationPropertiesBindingPostProcessor:初始化前解析@ConfigurationProperties
BeanFactory后处理器
- BeanFactory后处理器的作用:为BeanFactory提供扩展
- 常见的BeanFactorh后处理器
- ConfigurationClassPostProcessor:解析@ComponentScan、@Bean、@Import、@ImportResource
- MapperScannerConfigurer:解析@Mapper
Aware接口
- Aware接口提供了一种内置的注入手段,可以注入BeanFactory、ApplicationContext
- InitializingBean接口提供了一种内置的初始化手段
- 内置的注入和初始化不受扩展功能的影响响,总会被执行,因此Spring框架内部的类常用它们
- @Autowired失效分析
- 配置类先加载,BeanFactoryPostProcessor后加载
初始化与销毁
- Spring提供了多种初始化和销毁手段
- 执行顺序
- 初始化
- @PostConstruct
- 实现InitialzingBean
- 指定初始化方法
- 销毁
- @PreDistory
- 实现DisposableBean
- 指定销毁方法
Scope
- Scope类型
- singleton
- 每次从容器获取的Bean都是同一个对象
- prototype
- 每次从容器获取的Bean都会产生新的对象
- request
- 有请求时创建Bean,请求结束时销毁Bean
- session
- 会话建立时创建Bean,会话结束时销毁Bean,会话超时也会销毁
- application
- 应用启动时创建Bean,应用关闭时销毁Bean
- 在singleton中使用其他几种scope的注意事项
- 加上@Lazy
- @scope(proxyMode=ScopedProxyMode.TARGET_CLASS)
- 用ObjectFactory<要注入的对象>
- 用ApplicationContext获取Bean
- 解决方法虽然不同,但理念上殊途同归:都是推迟其他scopeBean的获取
AOP实现之ajc编译器
- AOP的另一种实现及原理
- 代理方式实现 AOP: Spring AOP 默认使用代理方式实现。它基于 Java 动态代理或 CGLIB(Code Generation Library)来实现 AOP。当目标对象实现了接口时,Spring AOP 使用 JDK 动态代理;如果目标对象没有实现接口,Spring AOP 使用 CGLIB 动态代理来创建代理对象。代理对象将在运行时增加切面逻辑,以便于在特定的切点(Join Points)上执行横切逻辑。代理方式实现的 AOP 虽然灵活,但有些限制,例如只能代理公共方法。
- AspectJ 编译器: AspectJ 是一个强大的 AOP 框架,它可以在编译期和运行期实现 AOP。AspectJ 具有更强大的切面表达能力,支持更丰富的横切逻辑。AspectJ 编译器能够在编译期织入切面逻辑,因此性能更高,不会像运行时代理一样引入额外的运行开销。AspectJ 提供了自己的切面语法和注解,可以更精细地控制切点和通知(Advices)。
- aspectjweaver.jar:
aspectjweaver.jar是 AspectJ 框架的一部分,用于在运行时提供 AOP 的支持。它可以在应用程序加载期间动态织入切面逻辑,而不需要事先进行编译。aspectjweaver.jar支持更强大的 AOP 特性,可以在运行时对目标类进行增强,甚至可以修改字节码。使用 AspectJ Weaver 可以获得更高级的 AOP 功能,但同时也增加了一些复杂性。 - 总结:代理方式实现 AOP 是 Spring 默认的方式,相对简单且易于理解,适用于大部分的 AOP 需求。AspectJ 则提供了更强大和灵活的 AOP 功能,但在配置和使用上相对复杂。AspectJ Weaver 提供了在运行时织入切面逻辑的能力,可以实现更高级的 AOP 特性。
AOP实现之Proxy
- jdk动态代理实现及要点
- 接口: JDK 动态代理要求被代理的对象实现一个接口,代理对象会实现这个接口。这是因为 JDK 动态代理是基于接口来创建代理的。
- InvocationHandler 接口: JDK 提供了
java.lang.reflect.InvocationHandler接口,它是创建动态代理的核心。需要实现该接口的invoke方法,该方法在代理对象的方法被调用时会被触发。 - Proxy 类: JDK 提供了
java.lang.reflect.Proxy类,用于创建动态代理对象。通过Proxy.newProxyInstance()方法可以创建代理对象,需要传入类加载器、接口类型数组、InvocationHandler实例作为参数。 - 横切逻辑: 在
invoke方法中,可以添加横切逻辑,例如在方法调用前后记录日志、进行权限检查、添加事务等操作。 - cglib代理实现及要点
- 被代理类: CGLIB 动态代理不要求被代理的类实现接口,可以是普通的类。
- Enhancer 类: CGLIB 提供了
net.sf.cglib.proxy.Enhancer类,用于创建动态代理对象。通过Enhancer.create()方法可以创建代理对象,需要设置被代理的类、Callback(回调函数,用于在代理方法调用时添加横切逻辑)。 - MethodInterceptor 接口: CGLIB 提供了
net.sf.cglib.proxy.MethodInterceptor接口,用于实现回调逻辑。被代理类的方法调用时,MethodInterceptor的intercept()方法会被触发。 - 横切逻辑: 在
MethodInterceptor的intercept()方法中,可以添加横切逻辑,例如在方法调用前后记录日志、进行权限检查、添加事务等操作。
cglib避免反射调用
- MethodProxy如何避免反射调用
- MethodProxy 和 Signature: 当你创建一个
MethodProxy时,CGLIB 框架会生成一个对应的FastClass对象,并将方法签名封装成一个Signature对象。这个签名包括方法的名称、返回类型和参数类型。 - FastClass 和 方法索引:
FastClass有一个叫做getIndex的方法,根据之前生成的Signature获取特定方法的索引。CGLIB 为目标类中的每个方法分配了一个唯一的索引。 - methodProxy.invoke: 当你在 CGLIB 代理上调用一个方法(使用
methodProxy.invoke),代理会将代理对象和方法参数传递给FastClass的invoke方法。 - 执行目标方法:
FastClass使用从getIndex获取的索引来确定在目标对象上要调用的确切方法。然后执行相应的方法,传递提供的参数。 - 与jdk对比
- JDK 动态代理优化(JDK 17 以后)
- 独立代理类:在 JDK 17 之前,JDK 动态代理生成一个统一的代理类,所有被代理的方法共用一个逻辑。而从 JDK 17 开始,每个被代理的方法都会生成一个独立的代理类。
- 无需反射:这个优化带来了一个重要的好处,即被代理方法的逻辑不再需要通过反射来执行。每个独立的代理类直接包含了方法逻辑,从而提升了性能。这种优化特别有利于 AOP 等场景,使得动态代理在这些场景下更加高效。
- CGLIB 代理
- FastClass 代理类:CGLIB 是一个基于字节码的代理框架,它在创建代理时会立即生成 FastClass 代理类。
- 两种 FastClass 代理类:CGLIB 生成两种 FastClass 代理类。一种 FastClass 与目标类配合使用,另一种 FastClass 与代理类配合使用。这种设计使得 FastClass 代理类可以匹配多个方法。
- 减少代理类数量:相比 JDK 动态代理,CGLIB 生成的代理类更少。一个代理类会产生两个 FastClass 代理类,而 JDK 动态代理每个方法都会产生一个代理类。
jdk和cglib的统一
- Spring的代理选择规则
- proxyTargetClass = false:
- 如果目标类实现了接口,Spring 将使用 JDK 动态代理来创建代理对象。这是因为 JDK 动态代理要求目标类实现至少一个接口。
- 如果目标类没有实现接口,Spring 将使用 CGLIB 代理来创建代理对象,以确保能够代理目标类的方法调用。
- proxyTargetClass = true:
- 无论目标类是否实现接口,Spring 都将强制使用 CGLIB 代理来创建代理对象。这可以通过设置
proxyTargetClass属性为true来实现。 - 底层的切点实现
- 切点(Pointcut):切点定义了在目标类中哪些方法会被拦截。切点基于方法的名称、参数、注解等条件来定义。Spring AOP 提供了不同的切点表达式语法,允许您非常灵活地定义切点。在底层,Spring 使用 AopProxy 类来创建代理对象,其中包含切点信息。切点是连接切面和目标方法的桥梁。
- 底层的通知实现
- 通知(Advice):通知定义了在切点处执行的代码逻辑。通知可以在目标方法执行前、执行后、抛出异常时等时机触发执行。Spring AOP 提供了不同类型的通知:前置通知(Before)、后置通知(AfterReturning)、异常通知(AfterThrowing)、最终通知(After)和环绕通知(Around)。底层实现通知的关键是在代理对象的特定时机调用通知的方法。
- 底层的切面实现
- 切面(Aspect):切面是切点和通知的组合。它定义了在何处(切点)以及何时(通知)执行特定的操作。切面可以跨越多个类和方法,提供了一种将横切关注点与核心业务逻辑分离的方式。底层,切面是由 Spring 容器管理的 Bean。
- 在底层,Spring AOP 借助代理技术实现切面编程。代理对象拦截目标方法的调用,并根据切点和通知的配置执行相应的操作。Spring AOP 使用 JDK 动态代理和 CGLIB 代理来创建代理对象。JDK 动态代理基于接口,而 CGLIB 代理基于类。
切点匹配
- 常见切点匹配实现
- Spring AOP 中常见的切点匹配实现有以下几种:
- 基于方法名的切点匹配器(MethodSignaturePointcut):该匹配器可以通过指定方法名来匹配目标类中的方法。
MethodSignaturePointcut pointcut = new MethodSignaturePointcut();
pointcut.setMethodName("doSomething");
- 基于类的切点匹配器(ClassFilter):该匹配器可以通过指定类名来匹配目标类。
ClassFilter classFilter = clazz -> clazz.getName().startsWith("com.example");
- 基于方法参数的切点匹配器(AspectJExpressionPointcut):这是一种更灵活的切点匹配方式,它允许使用 AspectJ 表达式语言来定义切点,可以匹配方法的参数类型、数量、注解等。
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* com.example.service.*Service.*(..))");
- 基于注解的切点匹配器(AnnotationMatchingPointcut):该匹配器可以通过指定注解类型来匹配带有特定注解的方法。
AnnotationMatchingPointcut pointcut = AnnotationMatchingPointcut.forMethodAnnotation(MyAnnotation.class);
- 逻辑组合切点匹配器:Spring 还提供了逻辑组合切点匹配器,例如 AndPointcut、OrPointcut、NotPointcut,可以将多个切点匹配器组合起来形成更复杂的切点条件。
Pointcut pointcut1 = ...; // 第一个切点匹配器 Pointcut pointcut2 = ...; // 第二个切点匹配器 Pointcut combinedPointcut = new AndPointcut(pointcut1, pointcut2);
- 使用这些切点匹配器,您可以根据业务需求精确地定义要拦截的目标方法。注意,切点匹配的灵活性取决于所选的匹配器类型,AspectJ 表达式语言的切点匹配器是最灵活的一种方式。
从@Aspect到Advisor
- 高级的
@Aspect切面:@Aspect是 Spring AOP 中定义切面的一种方式。使用@Aspect注解的类可以包含切点(@Pointcut)和通知(@Before、@After、@Around等)方法。这种方式使用注解来描述切面,非常方便。切面类需要声明为 Spring bean,并在配置中开启 AspectJ 自动代理(使用<aop:aspectj-autoproxy>或者@EnableAspectJAutoProxy)。 - 低级的 Advisor 切面:Advisor 是 Spring AOP 中的另一种切面定义方式,它是一个接口,通常由实现类如
DefaultPointcutAdvisor实现。Advisor 由切点和通知组成,切点可以使用 Spring 提供的各种 Pointcut 实现,通知可以使用 MethodInterceptor 等实现。 - 高级转换为低级切面的时机及代理生成时机:
- 高级转换为低级切面的时机:Spring 会扫描标有
@Aspect注解的切面类,并将它们转化为Advisor对象,使切面的切点和通知能够无缝融入 Spring AOP。在高级切面中,每个通知都会转化为一个Advisor类,将切点和通知结合。这样,切面的逻辑能够在适当的时机织入到目标方法中。这个转换的时机是在 Spring 容器初始化过程中进行的。 - 代理生成时机:在存在 Bean 之间单向依赖的情况下,切面将会在目标 Bean 初始化之后生成代理。然而,在存在 Bean 循环依赖的情况下,切面将在目标 Bean 的依赖注入之前生成代理。
- 切面的顺序控制:
- 高级切面(@Aspect)的顺序控制:使用
@Order注解标记切面类,并为其指定优先级值。值越小,优先级越高,切面会在相同切点上优先执行。 - 低级切面的顺序控制:对于低级切面,使用
DefaultPointcutAdvisor的setOrder()方法来指定执行顺序。较小的优先级值表示更高的执行优先级。
静态通知调用
- 不同通知统一转换为环绕通知,适配器模式体现
- 适配器类:在 Spring AOP 中,适配器类是
MethodInterceptor接口的实现,用于将不同通知类型包装成环绕通知。 - 通知转换:不同类型的通知被转换为相应适配器对象,如前置通知转为前置通知适配器,后置通知转为后置通知适配器。
- 统一处理:通过适配器模式,所有通知都可以用环绕通知方式处理,达到一致性的效果。
- 无参数绑定通知链执行过程,责任链模式体现
- 责任链组织:通知按照顺序形成链式结构。
- 传递与处理:通知逐个被调用,可决定是否传递给下一个。
- 解耦与灵活性:解耦调用者与具体处理,易于调整和扩展。
- 动态组装:可在运行时动态调整顺序和组成。
动态通知调用
- 有参绑定通知执行过程
- 切入点表达式: 使用
args(arg1, arg2)定义切入点,指定目标方法的参数名字。 - 目标方法调用: 当目标方法被调用,AOP框架拦截调用。
- 参数匹配: AOP框架检查目标方法的参数,与切入点表达式中的参数名字匹配。
- 适配器: 如果匹配成功,AOP框架会将动态通知转换为
InterceptorAndDynamicMethodMatcher对象,其中包含切入点和通知内容。 - 传递参数:AOP框架会根据
InterceptorAndDynamicMethodMatcher对象的内容将目标方法的参数值传递给通知方法。通知方法可以利用这些参数值进行操作。 - 通知方法执行: 通知方法使用这些参数值执行逻辑,通常在目标方法执行前后进行操作。
- 动态与静态的区别
- 动态通知:
- 需要参数绑定,将实际参数与通知方法的参数关联。
- 执行时需要切点对象确定连接点。
- 静态通知:
- 不需要参数绑定,通常不传递实际参数。
- 执行时不考虑切点,因为作用连接点在编译时已确定。
RequestMappingHandlerMapping与RequestMappingHandlerAdapter
- DispatcherServlet初始化时机
- DispatcherServlet是用于处理Web请求的前端控制器。
- 默认情况下,当首次使用DispatcherServlet时,容器(如Tomcat)会在需要时自动进行初始化。
- 初始化过程涵盖配置加载、拦截器准备等操作。
- 初始化完成后,DispatcherServlet能够接收和处理请求。
- 生命周期与容器管理的Servlet相同。
- 也可以通过手动设置,在应用启动时通过代码指定其初始化时机,例如设置加载顺序,通过这种方式可以精确控制初始化。
- DispatcherServlet初始化都做了什么
DispatcherServlet在初始化时,核心逻辑位于onRefresh方法中,该方法调用了initStrategies方法。- 多部分解析器: 初始化处理文件上传的多部分解析器,如
CommonsMultipartResolver。 - 本地化解析器: 初始化解析请求中的语言信息,以选择国际化资源,通常使用
AcceptHeaderLocaleResolver。 - 主题解析器: 初始化解析请求中的主题信息,以选择合适的主题样式,通常使用
FixedThemeResolver。 - 请求映射处理器: 初始化将请求映射到处理器(控制器方法)的处理器映射,如
RequestMappingHandlerMapping。 - 请求处理适配器: 初始化调用具体处理器方法的适配器,如
RequestMappingHandlerAdapter。 - 异常处理器: 初始化处理控制器方法抛出的异常的异常处理器。
- 请求到视图名转换器: 初始化将请求转换为视图名的转换器,通常使用
DefaultRequestToViewNameTranslator。 - 视图解析器: 初始化解析控制器返回的逻辑视图名为实际视图的解析器,如
InternalResourceViewResolver。 - RequestMappingHandlerMapping基本用途
- URL映射: 将请求的URL与控制器方法建立映射关系,使得当一个请求到达时,能够根据请求的URL找到匹配的控制器方法。
- 注解映射: 处理基于注解的请求映射,支持 @RequestMapping、@GetMapping、@PostMapping 等,同时能够映射 RESTful 风格的请求方法(GET、POST、PUT、DELETE等)。
- 获取方法信息: 能够获得匹配的控制器方法的信息,包括方法名、参数、返回类型等。
- RequestMappingHandlerAdapter基本用途
- 方法调用执行: 负责调用控制器类中的处理器方法,将请求参数传递给方法并执行。
- 返回值处理: 将处理器方法的返回值转换为合适的响应内容,如将对象转换为JSON、XML等格式。
- 数据绑定: 将请求参数绑定到控制器方法的参数上,使得控制器方法能够访问请求中的数据。
- 参数解析: 解析控制器方法的参数,包括路径变量、请求参数、请求体等。
- 数据格式转换: 处理请求和响应的数据格式转换,例如将请求体中的JSON数据转换为对象,或将对象转换为响应的JSON格式。
- 验证处理: 执行方法参数的验证,如使用
@Valid注解进行数据验证。 - 异常处理: 处理方法执行过程中可能抛出的异常,将异常信息转换为适当的响应状态码或内容。
- 数据处理: 对请求和响应的数据进行处理,如压缩、加密等。
- 拦截器执行: 在方法调用前后执行拦截器(如
HandlerInterceptor)中的逻辑。 - 异步处理: 支持控制器方法的异步处理,允许方法在执行过程中可以异步地返回。
参数解析器
- 常见参数解析器作用
- RequestParam: 映射请求参数到方法参数。用于获取查询参数和表单参数。
- PathVariable: 从URL中提取路径参数,适用于RESTful URL。
- ModelAttribute: 绑定请求参数到Java对象,特别适用于表单提交。
- RequestBody: 绑定请求体数据到方法参数,常用于处理JSON、XML等格式数据。
- RequestHeader: 映射请求头信息到方法参数。
- SessionAttributes: 在会话中存储指定模型属性,便于数据在多个请求间共享。
- CookieValue: 映射请求中的Cookie值到方法参数。
- ModelAttribute方法: 在控制器类中定义的方法,在每个控制器方法前执行,可用于设置通用模型属性。
- 组合模式在Spring中的体现
- ${}#{}小技巧
- Web环境下的测试技巧
获取参数名
- 编译生成参数表
- 生成MethodParameters参数表
-parameters选项保留方法参数名信息- 通过反射获取参数名
- 编译生成调试信息
- 生成LocalvariableTable
-g选项会启用所有的调试信息,包括行号、局部变量表和方法参数名。在使用-g时,会保留方法参数名,这对获取方法参数名是必要的。- 对接口无效
- 在spring中获取
- DefaultParameterNameDiscoverer结合了反射和本地变量的获取方式
对象绑定与类型转换
- 两套底层转换接口,一套高层转换接口
- 底层第一套转换接口与实现
- Printer把其他类型转为String
- Parser把String转为其他类型
- Formatter综合Printer与Parser功能
- Converter把类型S转为类型T
- Printer、Parser、Converter经过适配转换成GenericConverter放入Converters集合
- 底层第二套转换接口
- PropertyEditor把String与其他类型相互转换
- PropertyEditorRegistry可以注册多个PropertyEditor对象
- 与第一套接口直接可以通过FormatterPropertyEditorAdapter来进行适配
- 高层接口
- TypeConverter在转换时,用到TypeConverterDelegate委派ConversionServer与PropertyEditorRegistey真正执行转换(Facada门面模式)
- 首先看PropertyEditorRegistry是否有自定义转换器
- 再找ConversionService中有没有合适的转换器
- 再用默认的PropertyEditor转换
- 都没有时会有一些特殊处理
- SimplTypeConverter仅做类型转换(由TypeConverterDelegate委派转换)
- BeanWrapperImpl为Bean的属性赋值(底层调用反射实现),当需要时做类型转换(由TypeConverterDelegate委派转换),走Property(get、set方法)
- DirectFieldAccessor为Bean的属性赋值(底层调用反射实现),当需要时做类型转换,走Field(走成员变量)
- ServletRequestDateBinder为Bean的属性执行绑定,当需要时做类型转换,根据directFieldAccess为ture走Filed为false走Property,具备校验与获取校验结果功能
- 类型转换和数据绑定
- InvocableHandlerMethod
- FormattingConversionService
- 需要通过ConfigurableWebBindingInitializer包装
- servletRequestDataBinderFactory
- 接收上面两种类型,InvocableHandlerMethod优先较高
- 类型转换扩展与绑定器工厂
- @DateTimeFormat注解谁来解析
- 由DefaultFormattingConversionService解析
- Spring提供的泛型操作技巧
GenericTypeResolver.resolveTypeArgument(子类,父类)
ContorllerAdvice之@InitBinder
- 绑定器工厂的扩展点:@InitBinder及来源
- 来源于@ContorllerAdvice中,全局的@InitBinder,由RequestMappingHandlerAdapter在初始化时解析并记录
- 来源于@Contorller中,由RequestMappingHandlerAdapter在控制器方法首次执行时解析并记录
- 编程技巧:缓存加速
- Method对象的获取利用了缓存进行加速
- 功能增强
- @InitBinder 给每个Contorller添加自定义转换器
- ExceptionHandler 处理全局异常
- ModelAttribute 添加模型数据
控制器方法执行流程
- 控制器方法执行流程
- HandlerMethod
- bean 即Controller
- method 即Controller中的方法
- ServletInvocableHandlerMethod
- WebDataBinderFactory 负责对象绑定,类型转换
- ParameterNameDiscoverer 负责参数名解析
- HandlerMethodArgumentResolverComposite 负责解析参数
- HandlerMethodReturnValueHandlerComposite 负责处理返回值
- RequestMappingHanlerAdpter准备WebDataBinderFactory和ModelFactory后把Modelfactory产生的Modle数据添加到ModelAndViewContainer
- RequestMappingHandlerAdapterMethod.invokerAndHandle调用ServletInvocableHandlerMethod
- 调用HandlerMethodArgumentResolverComposite解析参数
- 这个调用过程中有的解析器涉及RequestBodyAdvice
- 有的解析器涉及数据绑定生成的模型数据添加到ModelAndViewContainer中,结束调用返回参数
- 反射调用得到返回值
- 调用HandlerMethodReturnValueHanler处理返回值
- 这个调用过程中有的解析器涉及RequestBodyAdvice
- 添加Mode数据,处理视图名,是否渲染等,添加到ModelAndViewContainer中
- 从ModelAndViewContainer获取最终结果
ControllerAdvice之@ModelAttribute
- 模型数据的扩展点:@ModelAttribute及来源
- 来源于方法上,通过
RequestMappingHandlerAdpter解析,执行控制器方法之前模型工厂调用带有@ModelAttribute注解的方法,将结果作为模型数据放置在ModelAndViewContainer中。 - 来源于参数中,通过
ServletModeAttributeMethodProcessor解析,该解析器会调用对象的构造方法来创建一个空对象,然后使用数据绑定工厂进行数据绑定。最终,将结果作为模型数据放置在ModelAndViewContainer中。
返回值处理器
- 常见返回值处理器作用
- MappingJackson2HttpMessageConverter: 用于将 Java 对象转换为 JSON 格式的响应。
- ModelAndViewMethodReturnValueHandler: 处理返回值为 ModelAndView 对象,负责渲染视图和传递模型数据。
- ViewNameMethodReturnValueHandler: 处理返回值为视图名称的方法,将其解析为视图对象。
- ServletModelAttributeMethodProcessor(false): 处理返回值为
@ModelAttribute注解修饰的方法参数或方法级属性,将其添加到模型中。 - HttpEntityMethodProcessor: 处理返回值为
HttpEntity或ResponseEntity类型的方法,允许设置响应状态码和头部信息。 - HttpHeadersReturnValueHandler: 处理返回值为
HttpHeaders类型的方法,用于设置响应头部信息。 - RequestResponseBodyMethodProcessor: 处理返回值为
@RequestBody或@ResponseBody注解标记的方法参数,将其转换为请求体或响应内容。 - ServletModelAttributeMethodProcessor(true): 处理返回值为
@ModelAttribute注解修饰的方法参数,将其添加到模型中。
MessageConverter
- MessageConverter作用
- RequestResponseBodyMethodProcessor解析@ResponseBody,由MessageConverter转换
- Spring MVC 中实现请求和响应体之间的数据转换。
- 常见的
MessageConverter实现包括: - MappingJackson2HttpMessageConverter: 将 Java 对象转换为 JSON 格式的响应,或将 JSON 请求体转换为 Java 对象。
- MappingJackson2XmlHttpMessageConverter: Java 对象到 XML 格式响应的转换,或将 XML 请求体转换为 Java 对象。
- 如何选择MediaType
- 优先级
- 首先看@RequestMapping上有没有指定
- 其次看request的Accept头有没有指定
- 最后按照MessageConverter的顺序,谁先谁转换
- 根据数据是 JSON、XML、文本还是二进制等,选择相应的
MediaType。 - application/json: 用于表示 JSON 格式的数据,适用于 RESTful API 的响应和请求。
- application/xml: 用于表示 XML 格式的数据,适用于 RESTful API 的响应和请求。
- text/plain: 用于表示纯文本数据,适用于简单的文本响应或请求。
- image/jpeg、image/png、image/gif: 用于表示图片数据的媒体类型,适用于图片的响应或请求。
ControllerAdivce之ResponseBodyAdvice
- 消息转换的扩展点:ResponseBodyAdvice
ResponseBodyAdvice是 Spring MVC 提供的消息转换扩展接口,允许在控制器方法的返回值转换为响应消息体之前进行全局性处理。- supports 方法:
- 决定是否对特定控制器方法和消息转换器的组合应用该实现。
- 判断方法:根据返回类型、消息转换器等条件。
- beforeBodyWrite 方法:
- 在将控制器方法返回值转换为响应消息体之前调用。
- 可用于修改、格式化、添加信息或替换返回值。
- 最终返回经过处理的值,将被转换为响应消息体。
异常处理
- @ExceptionHandler异常处理
- ExceptionHandlerExceptionResolver 负责解析@ExceptionHandler,能够重用参数解析器、返回值处理器、实现组件重用
- 嵌套异常的支持
Tomcat异常处理
- Tomcat的错误处理手段
- Springboot中BasicErrorController如何工作
- 处理错误请求:当应用程序中出现错误(如404、500等),
BasicErrorController会捕获这些错误请求。 - 查找错误视图:根据错误的状态码,
BasicErrorController会查找合适的错误视图。默认情况下,Spring Boot提供了一组默认的错误视图,例如error/404、error/500等。 - 渲染错误视图:一旦找到了合适的错误视图,
BasicErrorController将使用Thymeleaf、JSP、FreeMarker或其他视图模板引擎来渲染错误视图。 - 生成错误响应:
BasicErrorController还会根据错误状态码生成相应的HTTP响应,包括状态码、响应头、响应体等。

