Spring复习
Spring复习
FANSEASpring
- Spring 包含了多个功能模块(上面刚刚提到过),其中最重要的是 Spring-Core(主要提供 IoC 依赖注入功能的支持) 模块, Spring 中的其他模块(比如 Spring MVC)的功能实现基本都需要依赖于该模块。
- Spring MVC 是 Spring 中的一个很重要的模块,主要赋予 Spring 快速构建 MVC 架构的 Web 程序的能力。MVC 是模型(Model)、视图(View)、控制器(Controller)的简写,其核心思想是通过将业务逻辑、数据、显示分离来组织代码。
- Spring Boot:使用 Spring 进行开发各种配置过于麻烦比如开启某些 Spring 特性时,需要用 XML 或 Java 进行显式配置。于是,Spring Boot 诞生了!
IOC
Inversion of Control
工作流程
- IOC容器初始化
将xml,注解,配置类用加载器加载生成BeanDefinition注册到IOC容器(保存到BeanDefinitionMap集合)
ClassPathBeanDefinitionScanner 的核心方法是
doScan
,它执行以下步骤:
- 加载类路径资源:根据基础包名,加载类路径下的所有资源(通常是
.class
文件)。- 过滤资源:使用先前设置的过滤器(如
AnnotationTypeFilter
)来过滤资源,只保留符合条件的类。- 创建 bean 定义:对于每个过滤后的类,创建相应的 bean 定义。这通常涉及读取类的元数据(如注解、字段等)并设置到 bean 定义中。
- 注册 bean 定义:将创建的 bean 定义注册到 Spring 容器中,以便后续可以创建和管理这些 bean 的实例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.core.type.filter.AnnotationTypeFilter;
public class MyScanner {
public static void main(String[] args) {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner();
// 添加过滤器,只扫描带有 @MyComponent 注解的类
scanner.addIncludeFilter(new AnnotationTypeFilter(MyComponent.class));
// 设置扫描的基础包名
String basePackage = "com.example.myapp";
// 执行扫描并注册找到的 bean 定义
Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackage);
// 输出扫描到的 bean 定义信息(仅作示例)
for (BeanDefinitionHolder holder : beanDefinitions) {
System.out.println("Found bean definition: " + holder.getBeanDefinition().getBeanClassName());
}
}
}BeanDefinition:是Spring框架中的一个核心接口,它主要用于描述Bean以及存储Bean的相关信息。这些信息主要包括Bean的属性、是否单例、延迟加载、Bean的名称、构造方法等。
BeanFactory:维护保存真正的Bean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
String SCOPE_SINGLETON = "singleton";
String SCOPE_PROTOTYPE = "prototype";
// 获取 Bean 的类名
String getBeanClassName();
// 获取 Bean 的作用域
String getScope();
// 是否是懒加载的
boolean isLazyInit();
// 获取构造函数的参数
ConstructorArgumentValues getConstructorArgumentValues();
// 获取属性的值
MutablePropertyValues getPropertyValues();
// ... 其他方法 ...
}
- bean的初始化和依赖注入
bean实时初始化:
在容器启动过程中被创建组装好的bean,称为实时初始化的bean,spring中默认定义的bean都是实时初始化的bean,这些bean默认都是单例的,在容器启动过程中会被创建好,然后放在spring容器中以供使用。
延迟初始化的bean:
这里的lazy-init其实是bean延迟初始化,需要使用的时候才会去加载
@Autowired注入方式
先根据类型查找,再根据name查找
1 |
|
- 对于字段上的
@Autowired
,Spring 会尝试自动装配匹配类型的 bean。如果有多个相同类型的 bean,它会根据名字进行匹配,或者如果没有找到匹配的 bean,则会抛出异常(除非设置了@Autowired(required = false)
)。 - 对于构造器上的
@Autowired
,Spring 会尝试找到与构造器参数类型匹配的 bean,并使用这些 bean 来调用构造器创建 bean 实例。 - 对于方法上的
@Autowired
,Spring 会找到与方法参数类型匹配的 bean,并调用该方法进行注入。这通常用于 setter 方法注入。
@Resource注入方式
先根据name,再类型!
1 |
|
简单总结一下:
@Autowired
是 Spring 提供的注解,@Resource
是 JDK 提供的注解。Autowired
默认的注入方式为byType
(根据类型进行匹配),@Resource
默认注入方式为byName
(根据名称进行匹配)。- 当一个接口存在多个实现类的情况下,
@Autowired
和@Resource
都需要通过名称才能正确匹配到对应的 Bean。Autowired
可以通过@Qualifier
注解来显式指定名称,@Resource
可以通过name
属性来显式指定名称。 @Autowired
支持在构造函数、方法、字段和参数上使用。@Resource
主要用于字段和方法上的注入,不支持在构造函数或参数上使用。
什么情况下用构造器注入?
构造方法注入(Constructor Injection)是依赖注入(Dependency Injection, DI)的一种形式,它通过类的构造函数来传递依赖项。在Spring框架中,你可以使用构造方法注入来自动装配bean的依赖。以下是一些使用构造方法注入的场景:
- 不可变依赖:(构造方法一旦执行完毕,依赖项就被最终确定)
当bean的某个依赖项在对象创建后不应该改变时,使用构造方法注入是合适的。构造方法一旦执行完毕,依赖项就被最终确定,之后无法更改。这有助于确保对象的内部状态一致性和线程安全。 - 强制依赖:(启动时就被注入)
如果某个bean必须依赖于另一个bean才能正常工作,那么使用构造方法注入可以确保在bean创建时这些依赖项就已经被注入。这有助于在启动阶段就捕获配置错误,而不是在运行时。 - Final字段注入:(final字段只能用构造器注入)
如果你希望将依赖项注入到final字段中,那么只能使用构造方法注入,因为final字段必须在声明时或构造函数中初始化。
AOP
Aspect-Oriented Programming
AOP代理则可分为静态代理(例如:原生AspectJ)和动态代理(例如:spring aop)两大类
快速入门
引入依赖:
1 | <dependency> |
AOP核心概念
连接点,通知,切入点
通知类型
声明切入点(@Pointcut)简便开发:
切入点表达式
- @execution使用方法
- @annotation使用方法
使用此方法需要定义注解:
连接点
- 在@Around注解里面只能使用ProceedingJoinPoint
- 其他四种通知可以使用joinPoint
日志记录实现
1 | /** |
代理模式
- JDK动态代理:只能对实现接口的类实现代理,
1 | public static void main(String[] args) { |
- Cglib动态代理:对为实现接口的类也可以代理,实际上是生成了目标类的子类来增强
1 | public static void main(String[] args) { |
面试思考题
AOP 的应用场景有哪些?
日志记录、接口限流、事务管理,性能统计
- 日志记录:自定义日志记录注解,利用 AOP,一行代码即可实现日志记录。
- 性能统计:利用 AOP 在目标方法的执行前后统计方法的执行时间,方便优化和分析。
- 事务管理:
@Transactional
注解可以让 Spring 为我们进行事务管理比如回滚异常操作,免去了重复的事务管理逻辑。@Transactional
注解就是基于 AOP 实现的。 - 权限控制:利用 AOP 在目标方法执行前判断用户是否具备所需要的权限,如果具备,就执行目标方法,否则就不执行。例如,SpringSecurity 利用
@PreAuthorize
注解一行代码即可自定义权限校验。 - 接口限流:利用 AOP 在目标方法执行前通过具体的限流算法和实现对请求进行限流处理。
- 缓存管理:利用 AOP 在目标方法执行前后进行缓存的读取和更新
Bean扩展
当然,以下是一些Spring Bean生命周期扩展点的应用实例:
BeanPostProcessor的应用实例
假设我们需要在一个Bean的初始化前后加入日志功能。可以通过实现BeanPostProcessor
接口来实现这个需求:
1 |
|
在Spring配置文件中,需要声明这个BeanPostProcessor
:
1 | <bean class="com.example.CustomBeanPostProcessor" /> |
现在,每当Spring容器初始化一个Bean时,CustomBeanPostProcessor
的postProcessBeforeInitialization
和postProcessAfterInitialization
方法都会被调用,输出相应的日志。
InitializingBean和自定义初始化方法的应用实例
假设我们有一个数据库连接池Bean,在初始化时需要加载数据库配置并创建连接池:
1 |
|
同时,我们也可以在DatabaseConnectionPool
类中定义一个自定义的初始化方法:
1 | public void customInit() { |
Spring会在设置完所有属性后调用afterPropertiesSet
方法,并在之后调用customInit
方法。
DisposableBean和销毁回调方法的应用实例
假设我们有一个需要释放资源的Bean,如文件句柄或数据库连接:
1 |
|
同时,我们也可以在ResourceHolder
类中定义一个自定义的销毁方法:
1 | public void customDestroy() { |
当Spring容器关闭时,它会调用destroy
方法,并在之后调用customDestroy
方法,确保资源得到正确释放。
Aware接口的应用实例
假设我们有一个Bean需要访问Spring的ApplicationContext
来动态获取其他Bean:
1 |
|
由于ApplicationContextAware
接口的实现,Spring会自动将ApplicationContext
注入到setApplicationContext
方法中。这样,ApplicationContextAwareBean
就可以使用applicationContext
来获取其他Bean了。
这些实例展示了Spring Bean生命周期扩展点的实际应用,开发者可以根据具体需求选择合适的扩展点来实现特定的逻辑。
异步处理
Spring提供了多种线程池
SimpleAsyncTaskExecutor:不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程。
SyncTaskExecutor:这个类没有实现异步调用,只是一个同步操作。只适用于不需要多线程的地
ConcurrentTaskExecutor:Executor的适配类,不推荐使用。如果ThreadPoolTaskExecutor不满足要求时,才用考虑使用这个类
ThreadPoolTaskScheduler:可以使用cron表达式
ThreadPoolTaskExecutor:最常使用,推荐。其实质是对java.util.concurrent.ThreadPoolExecutor的包装
注解开发
@BeforeEach
和 @AfterEach
是 JUnit 5 框架中用于测试方法的注解。这些注解使得开发者能够在每个测试方法运行之前和之后执行一些公共的初始化或清理任务。
@BeforeEach
@BeforeEach
注解的方法会在每个测试方法执行之前运行。
这非常有用,因为你可以在这里设置测试环境、创建对象或执行其他需要在每个测试之前完成的任务。
示例:
1 | import org.junit.jupiter.api.BeforeEach; |
在上面的示例中,setUp
方法会在 testMethod1
和 testMethod2
运行之前执行,确保 myObject
已经被正确初始化。
@AfterEach
@AfterEach
注解的方法会在每个测试方法执行之后运行。这用于清理资源、重置状态或执行其他需要在每个测试之后完成的任务。
示例:
1 | import org.junit.jupiter.api.AfterEach; |
在这个示例中,tearDown
方法会在 testMethod1
和 testMethod2
运行之后执行,用于清理 myObject
使用的资源。
通过使用 @BeforeEach
和 @AfterEach
,你可以确保测试环境的一致性和测试代码的整洁性,减少因遗漏初始化或清理步骤而导致的测试失败。
@SpringBootApplication
根据 SpringBoot 官网,这三个注解的作用分别是:
@EnableAutoConfiguration
:启用 SpringBoot 的自动配置机制@ComponentScan
:扫描被@Component
(@Repository
,@Service
,@Controller
)注解的 bean,注解默认会扫描该类所在的包下所有的类。@Configuration
:允许在 Spring 上下文中注册额外的 bean 或导入其他配置类
@PathVariable和 @RequestParam
@PathVariable
用于获取路径参数,@RequestParam
用于获取查询参数。
举个简单的例子:
1 |
|
如果我们请求的 url 是:/klasses/123456/teachers?type=web
那么我们服务获取到的数据就是:klassId=123456,type=web
@ConfigurationProperties(常用)
1 | library: |
通过@ConfigurationProperties
读取yaml配置信息并与 bean 绑定。
1 |
|
你可以像使用普通的 Spring bean 一样,将其注入到类中使用。
校验注解
校验字段合法性
注:更新版本的 spring-boot-starter-web 依赖中不再有 hibernate-validator 包(如 2.3.11.RELEASE),需要自己引入 spring-boot-starter-validation
依赖。
@NotEmpty
被注释的字符串的不能为 null 也不能为空@NotBlank
被注释的字符串非 null,并且必须包含一个非空白字符@Null
被注释的元素必须为 null@NotNull
被注释的元素必须不为 null@AssertTrue
被注释的元素必须为 true@AssertFalse
被注释的元素必须为 false@Pattern(regex=,flag=)
被注释的元素必须符合指定的正则表达式@Email
被注释的元素必须是 Email 格式。@Min(value)
被注释的元素必须是一个数字,其值必须大于等于指定的最小值@Max(value)
被注释的元素必须是一个数字,其值必须小于等于指定的最大值@DecimalMin(value)
被注释的元素必须是一个数字,其值必须大于等于指定的最小值@DecimalMax(value)
被注释的元素必须是一个数字,其值必须小于等于指定的最大值@Size(max=, min=)
被注释的元素的大小必须在指定的范围内@Digits(integer, fraction)
被注释的元素必须是一个数字,其值必须在可接受的范围内@Past
被注释的元素必须是一个过去的日期@Future
被注释的元素必须是一个将来的日期
1 |
|
校验请求体
我们在需要验证的参数上加上了@Valid
注解,如果验证失败,它将抛出MethodArgumentNotValidException
。
1 |
|
校验请求参数
一定一定不要忘记在类上加上 @Validated
注解了,这个参数可以告诉 Spring 去校验方法参数。
1 |
|
全局处理 Controller 异常
介绍一下我们 Spring 项目必备的全局处理 Controller 层异常。
相关注解:
@ControllerAdvice
:注解定义全局异常处理类@ExceptionHandler
:注解声明异常处理方法
1 |
|
格式化 json 数据
@JsonFormat
一般用来格式化 json 数据。
比如:
1 |
|
过滤 json 数据
@JsonIgnoreProperties
作用在类上用于过滤掉特定字段不返回或者不解析。
1 | //生成json时将userRoles属性过滤 |
@JsonIgnore
一般用于类的属性上,作用和上面的@JsonIgnoreProperties
一样。
1 | public class User { |
SpringMVC
- M 为模型,包括实体类模型和业务类模型,实现数据封装的重用性
- V 视图,指前端页面,由JSP发展为现在的前端
- C 控制器,根据客户端请求响应数据
核心组件
- DispatcherServlet:前置控制器,负责调度其他组件的执行,可以降低不同组件之间的耦合性,是整个Spring MVC的核心模块
- Handler:处理器,完成具体的业务逻辑,相当于Servlet
- HandlerMapping:DispatcherServlet是通过 HandlerMapping把请求映射到不同的Handler
- HandlerInterceptor:处理器拦截器,是一个接口,如果我们需要进行一些拦截处理,可以通过实现该接口完成
- HandlerExecutionChain:处理器执行链,包括两部分内容:Handler和HandlerInterceptor(系统会有一个默认的HandlerInterceptor,如果有额外拦截处理,可以添加拦截器进行设置)
- HandlerAdapter:处理器适配器,Handler执行业务方法之前,需要进行一系列的操作包括表单的数据验证、数据类型转换、把表单数据封装到POJO等,这些一系列的操作都是由HandlerAdapter完成,DispatcherServlet通过HandlerAdapter执行不同的Handler
- ModelAndView:封装了模型数据和视图信息,作为Handler的处理结果,返回给DispatcherServlet
- ViewResolver:视图解析器,DispatcherServlet通过它把逻辑视图解析为物理视图,最终把渲染的结果响应给客户端
工作流程
- 客户端请求被DispatcherServlet接收
- 根据HandlerMapping映射到Handler
- 生成Handler和HandlerInterceptor
- Handler和HandlerInterceptor以HandlerExecutionChain的形式一并返回给DispatcherServlet
- DispatcherServlet通过HandlerAdapter调用Handler的方法完成业务逻辑处理
- 返回一个ModelAndView对象给DispatcherServlet
- DispatcherServlet把获取的ModelAndView对象传给ViewResolver视图解析器,把逻辑视图解析成物理视图
- ViewResolver返回一个View进行视图渲染(把模型填充到视图中)
- DispatcherServlet把渲染后的视图响应给客户端
拦截器配置
- 编写拦截器并继承
HandlerInterceptor
1 |
|
- 编写MVC配置类,继承
WebMvcConfigurer
,重写addInterceptors(InterceptorRegistry registry)
方法
1 |
|
拦截器参数详解:
拦截器的定义是需要重写
HandlerInterceptor
,以下是该类重写方法的执行流程解释:
preHandle
:方法执行之前postHandle
:方法执行完之后,模型渲染之前afterHandle
:方法执行完、模型渲染之后
面试题
讲⼀讲 Spring 框架中 Bean 的⽣命周期?
官⽅解析
在 Spring 框架中,Bean 的⽣命周期包括以下⼏个阶段:
- 实例化(Instantiation):在这个阶段,Spring 将根据配置⽂件或注解等⽅式创建 Bean 实例,并将其存储在容器中。
- 属性赋值(Populate Properties):在这个阶段,Spring 将会⾃动将 Bean 的属性值从配置⽂件或注解等⽅式中注⼊到 Bean 实例中。
- 初始化(Initialization):在这个阶段,Spring 会调⽤ Bean 实例的
init-method
⽅法,完成⼀些初始化的操作,例如建⽴数据库连接等。 - 销毁(Destruction):容器关闭时触发,在这个阶段,Spring 会调⽤ Bean 实例的
destroy-method
⽅法,完成⼀些资源的释放和清理操作,例如关闭数据库连接等。
具体的实现⽅式可以通过实现 BeanPostProcessor
和 BeanFactoryPostProcessor
接⼝来进⾏扩展。其中,BeanPostProcessor
接⼝定义了两个⽅法postProcessBeforeInitialization
和 postProcessAfterInitialization
,分别在 Bean 的初始化前后被调⽤,⽤于扩展 Bean 初始化的过程;BeanFactoryPostProcessor 接⼝则定义了⼀个⽅法 postProcessBeanFactory,⽤于在 Bean ⼯⼚实例化 Bean 定义后对其进⾏修改。
在面向对象编程中,对象的生命周期通常包括几个主要阶段:实例化(Instantiation)、属性赋值(Populate)、初始化(Initialization)以及销毁(Destruction)。下面我将逐一解释这些概念,并结合IoC(Inversion of Control,控制反转)容器的上下文进行说明。
1. 实例化 (Instantiation)
定义:实例化是指根据类创建具体对象的过程。在这个过程中,对象占用的内存会被分配,并且构造函数可能会被执行。
IoC 容器中的作用:在IoC容器中,实例化通常是自动完成的。当应用程序请求某个类型的对象时,IoC容器会负责创建这个对象的实例。例如,在Spring框架中,可以通过配置文件或注解指定哪些类应该由框架管理,并且框架会在运行时自动创建这些类的实例。
2. 属性赋值 (Populate)
定义:属性赋值是指为对象的成员变量(字段)设置初始值的过程。这通常发生在对象实例化之后。
IoC 容器中的作用:在IoC容器中,属性赋值可以通过依赖注入(Dependency Injection, DI)来实现。DI是一种设计模式,它允许将对象所需的外部资源或服务传递给该对象,而不是让对象自己创建或寻找这些资源。IoC容器可以自动处理依赖关系,并在创建对象时注入必要的依赖项。
3. 初始化 (Initialization)
定义:初始化是在对象创建并设置了所有必要的属性之后,进行额外的配置或准备工作的过程。这一步可能涉及执行一些特定的逻辑,如设置状态或建立连接。
IoC 容器中的作用:IoC容器可以提供初始化方法的支持,比如Spring框架中的
@PostConstruct
注解,用于标记那些在依赖注入完成后需要执行的方法。这使得开发者可以在对象完全准备好使用之前执行必要的初始化操作。4. 销毁 (Destruction)
定义:销毁是指释放对象所占用的资源,并回收其内存的过程。在一些场景下,如管理数据库连接或文件句柄时,正确的销毁步骤非常重要。
IoC 容器中的作用:IoC容器可以帮助管理对象的生命周期,包括它们的销毁。例如,在Spring框架中,可以使用
@PreDestroy
注解来指定对象销毁前需要执行的方法。这有助于确保资源被适当地清理。总结
IoC容器不仅能够帮助管理对象的创建,还能简化对象的初始化和销毁过程。通过依赖注入,IoC容器可以自动处理对象之间的依赖关系,减少代码耦合度,并提高系统的可测试性和灵活性。
Spring有哪些模块
IOC、AOP、Data Access、Message、Spring Web、Spring Test
Spring的Bean的作用域
Bean 的作用域有哪些?
singleton、session、request、prototype
Spring 中 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。
AOP的核心术语
AOP 切面编程涉及到的一些专业术语:
术语 | 含义 |
---|---|
目标(Target) | 被通知的对象 |
代理(Proxy) | 向目标对象应用通知之后创建的代理对象 |
连接点(JoinPoint) | 目标对象的所属类中,定义的所有方法均为连接点 |
切入点(Pointcut) | 被切面拦截 / 增强的连接点(切入点一定是连接点,连接点不一定是切入点) |
通知(Advice) | 增强的逻辑 / 代码,也即拦截到目标对象的连接点之后要做的事情 |
切面(Aspect) | 切入点(Pointcut)+通知(Advice) |
Weaving(织入) | 将通知应用到目标对象,进而生成代理对象的过程动作 |
SpringMVC的工作原理
流程说明(重要):
- 客户端(浏览器)发送请求,
DispatcherServlet
拦截请求。 DispatcherServlet
根据请求信息调用HandlerMapping
。HandlerMapping
根据 URL 去匹配查找能处理的Handler
(也就是我们平常说的Controller
控制器) ,并会将请求涉及到的拦截器和Handler
一起封装。DispatcherServlet
调用HandlerAdapter
适配器执行Handler
。Handler
完成对用户请求的处理后,会返回一个ModelAndView
对象给DispatcherServlet
,ModelAndView
顾名思义,包含了数据模型以及相应的视图的信息。Model
是返回的数据对象,View
是个逻辑上的View
。ViewResolver
会根据逻辑View
查找实际的View
。DispaterServlet
把返回的Model
传给View
(视图渲染)。- 把
View
返回给请求者(浏览器)
自前后端分离之后,第五步就无了,直接把model发送给前端,前端再根据model渲染出页面!
非Spring管理的环境下注入Bean
1 | /** |
1 | private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService"); |
方式二
- 在需要注入Bean的方法继承
applicationContext
接口,实现注入方法
1 |
|
传统mvc实现
Spring MVC 是一个基于 Java 的 Web 框架,它使用 MVC(Model-View-Controller)设计模式来开发 Web 应用程序。但是,如果你想要实现一个类似 Spring MVC 的功能,而不直接使用 Spring MVC 框架,你需要手动编写代码来处理 HTTP 请求、响应、路由、视图渲染等。
以下是一个简化的例子,展示了如何使用 Java Servlet API 来实现一个简单的 MVC 应用程序,类似于 Spring MVC 的工作方式:
- 定义 Model(模型)
模型通常包含业务数据和业务逻辑。
1 | public class User { |
- 定义 Controller(控制器)
控制器处理 HTTP 请求并调用模型来获取数据,然后选择要渲染的视图。
1 | import javax.servlet.ServletException; |
- 定义 View(视图)
视图负责渲染数据给用户。在这个例子中,我们使用 JSP 作为视图技术。
/WEB-INF/views/user.jsp
:
1 | <%@ page contentType="text/html;charset=UTF-8" language="java" %> |
- 配置 web.xml(可选)
虽然上面的 UserController
使用了 @WebServlet
注解来定义 Servlet 映射,但你也可以在 web.xml
文件中进行配置。
1 | <web-app> |
DispatcherServlet
在Spring MVC中加入DispatcherServlet相较于传统MVC模式具有显著的优势,这些优势主要体现在以下几个方面:
职责明确与灵活性:
- Spring MVC分工明确,包括控制器、验证器、命令对象、模型对象、处理程序映射视图解析器等,每一个功能实现都由一个专门的对象负责完成。
- DispatcherServlet作为前端控制器,负责接收所有的客户端请求,并将请求分发给合适的Controller进行处理,从而实现了职责的明确划分和灵活扩展。
自动绑定与数据类型转换:
- Spring MVC可以自动绑定用户输入,并正确地转换数据类型。例如,它能自动解析字符串,并将其设置为模型的int或float类型的属性,大大简化了开发过程。
灵活的数据传输与校验:
- 使用名称/值Map对象实现更加灵活的模型数据传输。
- 内置了常见的校验器,可以校验用户输入,并支持编程方式及声明方式的校验,提供了数据验证的灵活性。
国际化支持:
- Spring MVC支持国际化,可以根据用户区域显示多国语言,并且国际化的配置非常简单。
多种视图技术支持:
- 支持多种视图技术,如JSP、Velocity和FreeMarker等,为开发者提供了丰富的选择空间。
强大的JSP标签库:
- 提供了简单而强大的JSP标签库,支持数据绑定功能,使得编写JSP页面更加容易。
拦截器与异常处理:
- 提供拦截器(Interceptor)机制,可以在请求处理的不同阶段进行自定义处理。
- 如果执行过程中遇到异常,将交给HandlerExceptionResolver来解析,提供了异常处理的统一机制。
与Spring的无缝集成:
- 与Spring框架的无缝集成是Spring MVC的一个重要优势。由于Spring MVC是Spring框架的一部分,因此它可以轻松地与Spring的其他组件(如Spring Security、Spring Data等)集成,从而提供更加完整的解决方案。
配置简单与可定制性:
- Spring MVC的配置相对简单,通过XML、Java配置或注解等方式可以方便地配置各种组件。
- HandlerMapping、ViewResolver等能够非常简单地定制,以满足不同项目的需求。
综上所述,Spring MVC中加入DispatcherServlet相较于传统MVC模式在职责明确、灵活性、自动绑定、数据传输与校验、国际化支持、视图技术支持、JSP标签库、拦截器与异常处理、与Spring的无缝集成以及配置简单与可定制性等方面都具有显著的优势。这些优势使得Spring MVC成为现代Web开发中广泛使用的MVC框架之一。
Bean的循环依赖
依赖闭环造成死循环依赖,增加对象混乱性,对象依赖关系变的错综复杂
解决方案:
- 使用
@Lazy
,属性延迟注入 - 将共同依赖的 Bean 抽取出来单独做个 Bean
- 使用 Spring 的 getBean 方法延迟注入
- 配置开启循环依赖
为什么maven打包的jar可以直运行
普通的依赖jar都是封装了一些公共方法,所以无法启动,但是在SpringBoot项目中maven插件可以指定jar依赖的jar包,并可以指定程序入口,回归本质java -jar就是运行jar包的main方法为入口,而启动整个应用
1 | <build> |
maven打包出来的jar包含以下配置文件:
JarLauncher
用于加载jar包中的jar包,并找到入口文件启动
1 | Manifest-Version: 1.0 |
自动装配原理
启动条件:
@SpringBootApplication
=>@EnableAutoConfiguration
核心点:约定由于配置
- JAR包中按照Spring的Bean定义机制去声明Bean
- 约定扫描Classpath*=META-INF/spring.factories
- 通过
importSelector
来完成动态装配
接收参数的注解
RequestParam
:接受Qurey请求参数,请求体的form表单参数(包括文件MultipartFile
)Qurey,multipart/form-data,x-www-form-urlencoded
ModelAttribute
:接受Qurey请求参数,请求体的form表单参数,并封装成对象RequestBody
:接收请求体对象PathVariable
:请求路径的参数
SpringBoot自动配置和约定优于配置体现在哪
自动配置:默认数据库连接池配置、日志配置、SpringMVC配置、Web服务器配置(这些自动配置项可以读取我们yaml配置文件的属性自动配置)
1
2
3spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=secret约定优于配置:静态资源存储在static包下、启动类在根包下、自动配置
DispatcherServlet
并映射到/
SpringBoot的Starter
spring-boot-starter
本质是一个实现自动注入的jar集合,里面包括的一些核心jar例如:autoconfigure、yaml、spring-core、jakarta.annotation
、
它的设计目的是为了实现某个功能通过打包成Starter基于自动注入、自动配置实现快速开发
形如
spring-boot-starter-<name>
,表示与该功能相关的所有必要的依赖每个
starter
包含了一组默认配置,可以让你快速开始使用特定的技术或框架,而不需要手动配置大量的依赖项。可以大大减少配置工作,使开发者能够更快地专注于业务逻辑的实现。
自动配置的核心注解:
我们可以将自动配置的关键几步以及相应的注解总结如下:
@Configuration与@Bean
:基于Java代码的bean配置@Conditional
:设置自动配置条件依赖@EnableConfigurationProperties与@ConfigurationProperties
:读取配置文件转换为bean@EnableAutoConfiguration与@Import
:实现bean发现与加载
1 | <dependencies> |
以spring-boot-starter-data-redis
为例,其包含了
spring-boot-starter
:导入自动注入、Yaml配置的jar包lettuce-core、spring-data-redis
:功能的核心,里面实现了自动注入的逻辑
AOP失效
被代理对象被final关键字修饰
被代理方法为private
调用内部方法(无法被拦截所以代理失效)
解决:(在
IVoucherOrderService
类下调用内部的createVoucherOrder
方法)
1 | IVoucherOrderService proxy =(IVoucherOrderService) AopContext.currentProxy(); |