Spring复习

Spring

  • 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

工作流程

  1. IOC容器初始化

将xml,注解,配置类用加载器加载生成BeanDefinition注册到IOC容器(保存到BeanDefinitionMap集合)

ClassPathBeanDefinitionScanner 的核心方法是 doScan,它执行以下步骤:

  1. 加载类路径资源:根据基础包名,加载类路径下的所有资源(通常是 .class 文件)。
  2. 过滤资源:使用先前设置的过滤器(如 AnnotationTypeFilter)来过滤资源,只保留符合条件的类。
  3. 创建 bean 定义:对于每个过滤后的类,创建相应的 bean 定义。这通常涉及读取类的元数据(如注解、字段等)并设置到 bean 定义中。
  4. 注册 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();

// ... 其他方法 ...
}

image-20231124105055071

  1. bean的初始化和依赖注入

bean实时初始化:

在容器启动过程中被创建组装好的bean,称为实时初始化的bean,spring中默认定义的bean都是实时初始化的bean,这些bean默认都是单例的,在容器启动过程中会被创建好,然后放在spring容器中以供使用

延迟初始化的bean:

这里的lazy-init其实是bean延迟初始化,需要使用的时候才会去加载

image-20231124105641469

@Autowired注入方式

先根据类型查找,再根据name查找

1
2
3
4
5
6
7
8
9
10
11
12
13
@Autowied
@Qualifier(value = "aService")
Service AService;

@Autowied
@Qualifier(value = "bService")
Service BService;

@Test
void contextLoads() {
AService.say();
BService.say();
}
  • 对于字段上的 @Autowired,Spring 会尝试自动装配匹配类型的 bean。如果有多个相同类型的 bean,它会根据名字进行匹配,或者如果没有找到匹配的 bean,则会抛出异常(除非设置了 @Autowired(required = false))。
  • 对于构造器上的 @Autowired,Spring 会尝试找到与构造器参数类型匹配的 bean,并使用这些 bean 来调用构造器创建 bean 实例。
  • 对于方法上的 @Autowired,Spring 会找到与方法参数类型匹配的 bean,并调用该方法进行注入。这通常用于 setter 方法注入。

@Resource注入方式

先根据name,再类型!

1
2
3
4
5
6
7
8
9
10
11
@Resource
Service AService;

@Resource
Service BService;

@Test
void contextLoads() {
AService.say();
BService.say();
}

简单总结一下:

  • @Autowired 是 Spring 提供的注解,@Resource 是 JDK 提供的注解。
  • Autowired 默认的注入方式为byType(根据类型进行匹配),@Resource默认注入方式为 byName(根据名称进行匹配)。
  • 当一个接口存在多个实现类的情况下,@Autowired@Resource都需要通过名称才能正确匹配到对应的 Bean。Autowired 可以通过 @Qualifier 注解来显式指定名称,@Resource可以通过 name 属性来显式指定名称。
  • @Autowired 支持在构造函数、方法、字段和参数上使用。@Resource 主要用于字段和方法上的注入,不支持在构造函数或参数上使用。

什么情况下用构造器注入?

构造方法注入(Constructor Injection)是依赖注入(Dependency Injection, DI)的一种形式,它通过类的构造函数来传递依赖项。在Spring框架中,你可以使用构造方法注入来自动装配bean的依赖。以下是一些使用构造方法注入的场景:

  1. 不可变依赖:(构造方法一旦执行完毕,依赖项就被最终确定)
    当bean的某个依赖项在对象创建后不应该改变时,使用构造方法注入是合适的。构造方法一旦执行完毕,依赖项就被最终确定,之后无法更改。这有助于确保对象的内部状态一致性和线程安全。
  2. 强制依赖:(启动时就被注入)
    如果某个bean必须依赖于另一个bean才能正常工作,那么使用构造方法注入可以确保在bean创建时这些依赖项就已经被注入。这有助于在启动阶段就捕获配置错误,而不是在运行时。
  3. Final字段注入:(final字段只能用构造器注入)
    如果你希望将依赖项注入到final字段中,那么只能使用构造方法注入,因为final字段必须在声明时或构造函数中初始化。

AOP

Aspect-Oriented Programming

AOP代理则可分为静态代理(例如:原生AspectJ)和动态代理(例如:spring aop)两大类

快速入门

引入依赖:

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.6.3</version>
</dependency>

image-20231108184500260

AOP核心概念

连接点,通知,切入点

image-20231108184815555

通知类型

image-20231108192330666

声明切入点(@Pointcut)简便开发:

image-20231108192551727

切入点表达式

image-20231108193225178

  1. @execution使用方法

image-20231108193353662

image-20231108193522950

  1. @annotation使用方法

image-20231108193716525

使用此方法需要定义注解:

image-20231108194105984

image-20231108194002872

连接点

  1. 在@Around注解里面只能使用ProceedingJoinPoint
  2. 其他四种通知可以使用joinPoint

image-20231108194232662

日志记录实现

视频讲解

image-20231108201633369

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
38
/**
* 请求日志 AOP
*
**/
@Aspect
@Component
@Slf4j
public class LogInterceptor {

/**
* 执行拦截
*/
@Around("execution(* com.yupi.springbootinit.controller.*.*(..))")
public Object doInterceptor(ProceedingJoinPoint point) throws Throwable {
// 计时
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 获取请求路径
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
HttpServletRequest httpServletRequest = ((ServletRequestAttributes) requestAttributes).getRequest();
// 生成请求唯一 id
String requestId = UUID.randomUUID().toString();
String url = httpServletRequest.getRequestURI();
// 获取请求参数
Object[] args = point.getArgs();
String reqParam = "[" + StringUtils.join(args, ", ") + "]";
// 输出请求日志
log.info("request start,id: {}, path: {}, ip: {}, params: {}", requestId, url,
httpServletRequest.getRemoteHost(), reqParam);
// 执行原方法
Object result = point.proceed();
// 输出响应日志
stopWatch.stop();
long totalTimeMillis = stopWatch.getTotalTimeMillis();
log.info("request end, id: {}, cost: {}ms", requestId, totalTimeMillis);
return result;
}
}

代理模式

  • JDK动态代理:只能对实现接口的类实现代理,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void main(String[] args) {
Subject realSubject = new RealSubject();
Subject proxyInstance = (Subject) Proxy.newProxyInstance(realSubject.getClass().getClassLoader(),
realSubject.getClass().getInterfaces(), new MyInvocationHandler(realSubject));
proxyInstance.request();
proxyInstance.response();
}
static class MyInvocationHandler implements InvocationHandler {
Subject subject;
public MyInvocationHandler(Subject realSubject) {
this.subject = realSubject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("连接数据库成功!");
Object result = method.invoke(subject, args);
System.out.println("释放连接!");
return result;
}
}
  • Cglib动态代理:对为实现接口的类也可以代理,实际上是生成了目标类的子类来增强
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String[] args) {
RealSubject1 subject = new RealSubject1();
// 创建cglib核心对象
Enhancer enhancer = new Enhancer();
// 设置父类
enhancer.setSuperclass(subject.getClass());
enhancer.setCallback(new MethodInterceptor(){
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("cglib代理开始");
Object result = methodProxy.invoke(subject, objects);
System.out.println("cglib代理结束");
return result;
}
});
RealSubject1 proxy = (RealSubject1) enhancer.create();
proxy.request();
}

面试思考题

  1. AOP 的应用场景有哪些?

    日志记录、接口限流、事务管理,性能统计

  • 日志记录:自定义日志记录注解,利用 AOP,一行代码即可实现日志记录。
  • 性能统计:利用 AOP 在目标方法的执行前后统计方法的执行时间,方便优化和分析。
  • 事务管理:@Transactional 注解可以让 Spring 为我们进行事务管理比如回滚异常操作,免去了重复的事务管理逻辑。@Transactional注解就是基于 AOP 实现的。
  • 权限控制:利用 AOP 在目标方法执行前判断用户是否具备所需要的权限,如果具备,就执行目标方法,否则就不执行。例如,SpringSecurity 利用@PreAuthorize 注解一行代码即可自定义权限校验。
  • 接口限流:利用 AOP 在目标方法执行前通过具体的限流算法和实现对请求进行限流处理。
  • 缓存管理:利用 AOP 在目标方法执行前后进行缓存的读取和更新

Bean扩展

当然,以下是一些Spring Bean生命周期扩展点的应用实例:

BeanPostProcessor的应用实例

假设我们需要在一个Bean的初始化前后加入日志功能。可以通过实现BeanPostProcessor接口来实现这个需求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
public class CustomBeanPostProcessor implements BeanPostProcessor {

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("Before initialization of bean: " + beanName);
return bean;
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("After initialization of bean: " + beanName);
return bean;
}
}

在Spring配置文件中,需要声明这个BeanPostProcessor

1
<bean class="com.example.CustomBeanPostProcessor" />

现在,每当Spring容器初始化一个Bean时,CustomBeanPostProcessorpostProcessBeforeInitializationpostProcessAfterInitialization方法都会被调用,输出相应的日志。

InitializingBean和自定义初始化方法的应用实例

假设我们有一个数据库连接池Bean,在初始化时需要加载数据库配置并创建连接池:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component
public class DatabaseConnectionPool implements InitializingBean {

private DataSource dataSource;

// 省略getter和setter方法

@Override
public void afterPropertiesSet() throws Exception {
// 加载数据库配置
// 创建数据库连接池
System.out.println("Database connection pool initialized.");
}
}

同时,我们也可以在DatabaseConnectionPool类中定义一个自定义的初始化方法:

1
2
3
4
public void customInit() {
// 执行额外的初始化逻辑
System.out.println("Custom initialization logic executed.");
}

Spring会在设置完所有属性后调用afterPropertiesSet方法,并在之后调用customInit方法。

DisposableBean和销毁回调方法的应用实例

假设我们有一个需要释放资源的Bean,如文件句柄或数据库连接:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
public class ResourceHolder implements DisposableBean {

private Resource resource;

// 省略getter和setter方法

@Override
public void destroy() throws Exception {
// 释放资源
System.out.println("Resource released.");
}
}

同时,我们也可以在ResourceHolder类中定义一个自定义的销毁方法:

1
2
3
4
public void customDestroy() {
// 执行额外的销毁逻辑
System.out.println("Custom destroy logic executed.");
}

当Spring容器关闭时,它会调用destroy方法,并在之后调用customDestroy方法,确保资源得到正确释放。

Aware接口的应用实例

假设我们有一个Bean需要访问Spring的ApplicationContext来动态获取其他Bean:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component
public class ApplicationContextAwareBean implements ApplicationContextAware {

private ApplicationContext applicationContext;

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}

public Object getBean(String name) {
return applicationContext.getBean(name);
}
}

由于ApplicationContextAware接口的实现,Spring会自动将ApplicationContext注入到setApplicationContext方法中。这样,ApplicationContextAwareBean就可以使用applicationContext来获取其他Bean了。

这些实例展示了Spring Bean生命周期扩展点的实际应用,开发者可以根据具体需求选择合适的扩展点来实现特定的逻辑。

异步处理

Spring提供了多种线程池

  1. SimpleAsyncTaskExecutor:不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程。

  2. SyncTaskExecutor:这个类没有实现异步调用,只是一个同步操作。只适用于不需要多线程的地

  3. ConcurrentTaskExecutor:Executor的适配类,不推荐使用。如果ThreadPoolTaskExecutor不满足要求时,才用考虑使用这个类

  4. ThreadPoolTaskScheduler:可以使用cron表达式

  5. ThreadPoolTaskExecutor:最常使用,推荐。其实质是对java.util.concurrent.ThreadPoolExecutor的包装

注解开发

@BeforeEach@AfterEach 是 JUnit 5 框架中用于测试方法的注解。这些注解使得开发者能够在每个测试方法运行之前和之后执行一些公共的初始化或清理任务。

@BeforeEach

@BeforeEach 注解的方法会在每个测试方法执行之前运行。

这非常有用,因为你可以在这里设置测试环境、创建对象或执行其他需要在每个测试之前完成的任务。

示例:

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.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class MyTestClass {

private MyObject myObject;

@BeforeEach
void setUp() {
myObject = new MyObject();
// 进行其他必要的初始化
}

@Test
void testMethod1() {
// 使用 myObject 进行测试
}

@Test
void testMethod2() {
// 使用 myObject 进行测试
}
}

在上面的示例中,setUp 方法会在 testMethod1testMethod2 运行之前执行,确保 myObject 已经被正确初始化。

@AfterEach

@AfterEach 注解的方法会在每个测试方法执行之后运行。这用于清理资源、重置状态或执行其他需要在每个测试之后完成的任务。

示例:

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
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;

class MyTestClass {

private MyObject myObject;

@BeforeEach
void setUp() {
myObject = new MyObject();
}

@Test
void testMethod1() {
// 使用 myObject 进行测试
}

@Test
void testMethod2() {
// 使用 myObject 进行测试
}

@AfterEach
void tearDown() {
myObject.cleanUp(); // 假设这是清理资源的方法
// 进行其他必要的清理
}
}

在这个示例中,tearDown 方法会在 testMethod1testMethod2 运行之后执行,用于清理 myObject 使用的资源。

通过使用 @BeforeEach@AfterEach,你可以确保测试环境的一致性和测试代码的整洁性,减少因遗漏初始化或清理步骤而导致的测试失败。

@SpringBootApplication

根据 SpringBoot 官网,这三个注解的作用分别是:

  • @EnableAutoConfiguration:启用 SpringBoot 的自动配置机制
  • @ComponentScan:扫描被@Component (@Repository,@Service,@Controller)注解的 bean,注解默认会扫描该类所在的包下所有的类。
  • @Configuration:允许在 Spring 上下文中注册额外的 bean 或导入其他配置类

@PathVariable和 @RequestParam

@PathVariable用于获取路径参数,@RequestParam用于获取查询参数。

举个简单的例子:

1
2
3
4
5
6
@GetMapping("/klasses/{klassId}/teachers")
public List<Teacher> getKlassRelatedTeachers(
@PathVariable("klassId") Long klassId,
@RequestParam(value = "type", required = false) String type ) {
...
}

如果我们请求的 url 是:/klasses/123456/teachers?type=web

那么我们服务获取到的数据就是:klassId=123456,type=web

@ConfigurationProperties(常用)

1
2
3
4
5
6
7
8
9
10
library:
location: 湖北武汉加油中国加油
books:
- name: 天才基本法
description: 二十二岁的林朝夕在父亲确诊阿尔茨海默病这天,得知自己暗恋多年的校园男神裴之即将出国深造的消息——对方考取的学校,恰是父亲当年为她放弃的那所。
- name: 时间的秩序
description: 为什么我们记得过去,而非未来?时间“流逝”意味着什么?是我们存在于时间之内,还是时间存在于我们之中?卡洛·罗韦利用诗意的文字,邀请我们思考这一亘古难题——时间的本质。
- name: 了不起的我
description: 如何养成一个新习惯?如何让心智变得更成熟?如何拥有高质量的关系? 如何走出人生的艰难时刻?

通过@ConfigurationProperties读取yaml配置信息并与 bean 绑定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component
@ConfigurationProperties(prefix = "library")
class LibraryProperties {
@NotEmpty
private String location;
private List<Book> books;

@Setter
@Getter
@ToString
static class Book {
String name;
String description;
}
省略getter/setter
......
}

你可以像使用普通的 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {

@NotNull(message = "classId 不能为空")
private String classId;

@Size(max = 33)
@NotNull(message = "name 不能为空")
private String name;

@Pattern(regexp = "((^Man$|^Woman$|^UGM$))", message = "sex 值不在可选范围")
@NotNull(message = "sex 不能为空")
private String sex;

@Email(message = "email 格式不正确")
@NotNull(message = "email 不能为空")
private String email;

}

校验请求体

我们在需要验证的参数上加上了@Valid注解,如果验证失败,它将抛出MethodArgumentNotValidException

1
2
3
4
5
6
7
8
9
@RestController
@RequestMapping("/api")
public class PersonController {

@PostMapping("/person")
public ResponseEntity<Person> getPerson(@RequestBody @Valid Person person) {
return ResponseEntity.ok().body(person);
}
}

校验请求参数

一定一定不要忘记在类上加上 @Validated 注解了,这个参数可以告诉 Spring 去校验方法参数。

1
2
3
4
5
6
7
8
9
10
@RestController
@RequestMapping("/api")
@Validated
public class PersonController {

@GetMapping("/person/{id}")
public ResponseEntity<Integer> getPersonByID(@Valid @PathVariable("id") @Max(value = 5,message = "超过 id 的范围了") Integer id) {
return ResponseEntity.ok().body(id);
}
}

全局处理 Controller 异常

介绍一下我们 Spring 项目必备的全局处理 Controller 层异常。

相关注解:

  1. @ControllerAdvice :注解定义全局异常处理类
  2. @ExceptionHandler :注解声明异常处理方法

1
2
3
4
5
6
7
8
9
10
11
12
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {

/**
* 请求参数异常处理
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, HttpServletRequest request) {
......
}
}

格式化 json 数据

@JsonFormat一般用来格式化 json 数据。

比如:

1
2
@JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone="GMT")
private Date date;

过滤 json 数据

@JsonIgnoreProperties 作用在类上用于过滤掉特定字段不返回或者不解析。

1
2
3
4
5
6
7
8
9
//生成json时将userRoles属性过滤
@JsonIgnoreProperties({"userRoles"})
public class User {

private String userName;
private String fullName;
private String password;
private List<UserRole> userRoles = new ArrayList<>();
}

@JsonIgnore一般用于类的属性上,作用和上面的@JsonIgnoreProperties 一样。

1
2
3
4
5
6
7
8
9
public class User {

private String userName;
private String fullName;
private String password;
//生成json时将userRoles属性过滤
@JsonIgnore
private List<UserRole> userRoles = new ArrayList<>();
}

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把渲染后的视图响应给客户端

拦截器配置

  1. 编写拦截器并继承HandlerInterceptor
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
@Component
public class ProjectOwnerCheckInterceptor implements HandlerInterceptor {

private final ProjectService projectService;

public ProjectOwnerCheckInterceptor(ProjectService projectService) {
this.projectService = projectService;
}

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
try {
Long userId = UserContextHolder.getUserId();

String projectIdStr = request.getParameter("projectId");
if (StringUtils.isEmpty(projectIdStr)) {
response.getWriter().write("projectId is not present in the request parameter");
response.setStatus(400);
return false;
}

long projectId = Long.parseLong(projectIdStr);

// check if the user is owner of project
Project project = projectService.getByIdCheckPermission(projectId);
if (project==null || !Objects.equals(userId, project.getOwner())) {
throw new BusinessException(ErrorCode.PROJECT_NO_OWNER_PERMISSION);
}
} catch (NumberFormatException e) {
response.getWriter().write("invalid request");
response.setStatus(400);
return false;
}
return true;
}
}
  1. 编写MVC配置类,继承WebMvcConfigurer,重写 addInterceptors(InterceptorRegistry registry) 方法
1
2
3
4
5
6
7
8
@Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(new ProjectOwnerCheckInterceptor(projectService))
.addPathPatterns("/users/**") // 拦截对用户的具体操作
.excludePathPatterns("/users") // 不拦截查看用户的功能
.pathMatcher(new AntPathMatcher()) //定义拦截上面配置拦截路径的规范
.order(3); // 拦截器顺序
}

拦截器参数详解:

拦截器的定义是需要重写HandlerInterceptor,以下是该类重写方法的执行流程解释:

  • preHandle:方法执行之前
  • postHandle:方法执行完之后,模型渲染之前
  • afterHandle:方法执行完、模型渲染之后

面试题

讲⼀讲 Spring 框架中 Bean 的⽣命周期?

官⽅解析

在 Spring 框架中,Bean 的⽣命周期包括以下⼏个阶段:

  1. 实例化(Instantiation):在这个阶段,Spring 将根据配置⽂件或注解等⽅式创建 Bean 实例,并将其存储在容器中。
  2. 属性赋值(Populate Properties):在这个阶段,Spring 将会⾃动将 Bean 的属性值从配置⽂件或注解等⽅式中注⼊到 Bean 实例中。
  3. 初始化(Initialization):在这个阶段,Spring 会调⽤ Bean 实例的 init-method ⽅法,完成⼀些初始化的操作,例如建⽴数据库连接等。
  4. 销毁(Destruction):容器关闭时触发,在这个阶段,Spring 会调⽤ Bean 实例的 destroy-method ⽅法,完成⼀些资源的释放和清理操作,例如关闭数据库连接等。

具体的实现⽅式可以通过实现 BeanPostProcessorBeanFactoryPostProcessor 接⼝来进⾏扩展。其中,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有哪些模块

Spring常见面试题总结 | JavaGuide

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的工作原理

流程说明(重要):

  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 返回给请求者(浏览器)

自前后端分离之后,第五步就无了,直接把model发送给前端,前端再根据model渲染出页面!


img


image-20240421212744983

非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
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/**
* spring工具类 方便在非spring管理环境中获取bean
*
* @author ruoyi
*/
@Component
public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware
{
/** Spring应用上下文环境 */
private static ConfigurableListableBeanFactory beanFactory;

private static ApplicationContext applicationContext;

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException
{
SpringUtils.beanFactory = beanFactory;
}

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
{
SpringUtils.applicationContext = applicationContext;
}

/**
* 获取对象
*
* @param name
* @return Object 一个以所给名字注册的bean的实例
* @throws org.springframework.beans.BeansException
*
*/
@SuppressWarnings("unchecked")
public static <T> T getBean(String name) throws BeansException
{
return (T) beanFactory.getBean(name);
}

/**
* 获取类型为requiredType的对象
*
* @param clz
* @return
* @throws org.springframework.beans.BeansException
*
*/
public static <T> T getBean(Class<T> clz) throws BeansException
{
T result = (T) beanFactory.getBean(clz);
return result;
}

/**
* 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true
*
* @param name
* @return boolean
*/
public static boolean containsBean(String name)
{
return beanFactory.containsBean(name);
}

/**
* 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
*
* @param name
* @return boolean
* @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
*
*/
public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException
{
return beanFactory.isSingleton(name);
}

/**
* @param name
* @return Class 注册对象的类型
* @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
*
*/
public static Class<?> getType(String name) throws NoSuchBeanDefinitionException
{
return beanFactory.getType(name);
}

/**
* 如果给定的bean名字在bean定义中有别名,则返回这些别名
*
* @param name
* @return
* @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
*
*/
public static String[] getAliases(String name) throws NoSuchBeanDefinitionException
{
return beanFactory.getAliases(name);
}

/**
* 获取aop代理对象
*
* @param invoker
* @return
*/
@SuppressWarnings("unchecked")
public static <T> T getAopProxy(T invoker)
{
return (T) AopContext.currentProxy();
}

/**
* 获取当前的环境配置,无配置返回null
*
* @return 当前的环境配置
*/
public static String[] getActiveProfiles()
{
return applicationContext.getEnvironment().getActiveProfiles();
}

/**
* 获取当前的环境配置,当有多个环境配置时,只获取第一个
*
* @return 当前的环境配置
*/
public static String getActiveProfile()
{
final String[] activeProfiles = getActiveProfiles();
return StringUtils.isNotEmpty(activeProfiles) ? activeProfiles[0] : null;
}

/**
* 获取配置文件中的值
*
* @param key 配置文件的key
* @return 当前的配置文件的值
*
*/
public static String getRequiredProperty(String key)
{
return applicationContext.getEnvironment().getRequiredProperty(key);
}
}

1
private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService");

方式二

  1. 在需要注入Bean的方法继承applicationContext接口,实现注入方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Service
@AllArgsConstructor
public class ObjectTest implements ApplicationContextAware {

ApplicationContext applicationContext;


private void test2(){
applicationContext.getBean(ObjectTest.class);
}

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}

传统mvc实现

Spring MVC 是一个基于 Java 的 Web 框架,它使用 MVC(Model-View-Controller)设计模式来开发 Web 应用程序。但是,如果你想要实现一个类似 Spring MVC 的功能,而不直接使用 Spring MVC 框架,你需要手动编写代码来处理 HTTP 请求、响应、路由、视图渲染等。

以下是一个简化的例子,展示了如何使用 Java Servlet API 来实现一个简单的 MVC 应用程序,类似于 Spring MVC 的工作方式:

  1. 定义 Model(模型)

模型通常包含业务数据和业务逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class User {
private String name;
private String email;

// getters and setters
}

public class UserService {
public User getUserByName(String name) {
// 模拟从数据库获取用户
User user = new User();
user.setName(name);
user.setEmail(name + "@example.com");
return user;
}
}
  1. 定义 Controller(控制器)

控制器处理 HTTP 请求并调用模型来获取数据,然后选择要渲染的视图。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/user")
public class UserController extends HttpServlet {
private UserService userService = new UserService();

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String name = req.getParameter("name");
User user = userService.getUserByName(name);
req.setAttribute("user", user);
req.getRequestDispatcher("/WEB-INF/views/user.jsp").forward(req, resp);
}
}
  1. 定义 View(视图)

视图负责渲染数据给用户。在这个例子中,我们使用 JSP 作为视图技术。

/WEB-INF/views/user.jsp:

1
2
3
4
5
6
7
8
9
10
11
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>User Profile</title>
</head>
<body>
<h1>User Profile</h1>
<p>Name: ${user.name}</p>
<p>Email: ${user.email}</p>
</body>
</html>
  1. 配置 web.xml(可选)

虽然上面的 UserController 使用了 @WebServlet 注解来定义 Servlet 映射,但你也可以在 web.xml 文件中进行配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<web-app>
<!-- ... other configurations ... -->

<servlet>
<servlet-name>UserController</servlet-name>
<servlet-class>com.example.UserController</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>UserController</servlet-name>
<url-pattern>/user</url-pattern>
</servlet-mapping>

<!-- ... other configurations ... -->
</web-app>

DispatcherServlet

在Spring MVC中加入DispatcherServlet相较于传统MVC模式具有显著的优势,这些优势主要体现在以下几个方面:

  1. 职责明确与灵活性

    • Spring MVC分工明确,包括控制器、验证器、命令对象、模型对象、处理程序映射视图解析器等,每一个功能实现都由一个专门的对象负责完成。
    • DispatcherServlet作为前端控制器,负责接收所有的客户端请求,并将请求分发给合适的Controller进行处理,从而实现了职责的明确划分和灵活扩展。
  2. 自动绑定与数据类型转换

    • Spring MVC可以自动绑定用户输入,并正确地转换数据类型。例如,它能自动解析字符串,并将其设置为模型的int或float类型的属性,大大简化了开发过程。
  3. 灵活的数据传输与校验

    • 使用名称/值Map对象实现更加灵活的模型数据传输。
    • 内置了常见的校验器,可以校验用户输入,并支持编程方式及声明方式的校验,提供了数据验证的灵活性。
  4. 国际化支持

    • Spring MVC支持国际化,可以根据用户区域显示多国语言,并且国际化的配置非常简单。
  5. 多种视图技术支持

    • 支持多种视图技术,如JSP、Velocity和FreeMarker等,为开发者提供了丰富的选择空间。
  6. 强大的JSP标签库

    • 提供了简单而强大的JSP标签库,支持数据绑定功能,使得编写JSP页面更加容易。
  7. 拦截器与异常处理

    • 提供拦截器(Interceptor)机制,可以在请求处理的不同阶段进行自定义处理。
    • 如果执行过程中遇到异常,将交给HandlerExceptionResolver来解析,提供了异常处理的统一机制。
  8. 与Spring的无缝集成

    • 与Spring框架的无缝集成是Spring MVC的一个重要优势。由于Spring MVC是Spring框架的一部分,因此它可以轻松地与Spring的其他组件(如Spring Security、Spring Data等)集成,从而提供更加完整的解决方案。
  9. 配置简单与可定制性

    • Spring MVC的配置相对简单,通过XML、Java配置或注解等方式可以方便地配置各种组件。
    • HandlerMapping、ViewResolver等能够非常简单地定制,以满足不同项目的需求。

综上所述,Spring MVC中加入DispatcherServlet相较于传统MVC模式在职责明确、灵活性、自动绑定、数据传输与校验、国际化支持、视图技术支持、JSP标签库、拦截器与异常处理、与Spring的无缝集成以及配置简单与可定制性等方面都具有显著的优势。这些优势使得Spring MVC成为现代Web开发中广泛使用的MVC框架之一。

Bean的循环依赖

依赖闭环造成死循环依赖,增加对象混乱性,对象依赖关系变的错综复杂

解决方案:

  1. 使用@Lazy,属性延迟注入
  2. 将共同依赖的 Bean 抽取出来单独做个 Bean
  3. 使用 Spring 的 getBean 方法延迟注入
  4. 配置开启循环依赖

为什么maven打包的jar可以直运行

普通的依赖jar都是封装了一些公共方法,所以无法启动,但是在SpringBoot项目中maven插件可以指定jar依赖的jar包,并可以指定程序入口,回归本质java -jar就是运行jar包的main方法为入口,而启动整个应用

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
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.5.15</version>
<configuration>
<fork>true</fork> <!-- 如果没有该配置,devtools不会生效 -->
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
<warName>${project.artifactId}</warName>
</configuration>
</plugin>
</plugins>
<finalName>${project.artifactId}</finalName>
</build>

maven打包出来的jar包含以下配置文件:

JarLauncher用于加载jar包中的jar包,并找到入口文件启动

1
2
3
4
5
6
7
8
9
10
11
12
13
Manifest-Version: 1.0
Implementation-Title: XYIoT-admin
Implementation-Version: 1.0.0
Built-By: xiaoy
Implementation-Vendor-Id: com.xinye
Spring-Boot-Version: 2.1.1.RELEASE
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.xinye.iot.XinYeIoTApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Created-By: Apache Maven 3.6.2
Build-Jdk: 1.8.0_221
Implementation-URL: https://www.xinyetech.top:8900/XYIoT-admin/

自动装配原理

启动条件:@SpringBootApplication => @EnableAutoConfiguration

核心点:约定由于配置

  • JAR包中按照Spring的Bean定义机制去声明Bean
  • 约定扫描Classpath*=META-INF/spring.factories
  • 通过importSelector来完成动态装配

image-20241027102158623

接收参数的注解

  • RequestParam:接受Qurey请求参数,请求体的form表单参数(包括文件MultipartFile)

    Qurey,multipart/form-data,x-www-form-urlencoded

  • ModelAttribute:接受Qurey请求参数,请求体的form表单参数,并封装成对象

  • RequestBody:接收请求体对象

  • PathVariable:请求路径的参数

SpringBoot自动配置和约定优于配置体现在哪

  • 自动配置:默认数据库连接池配置、日志配置、SpringMVC配置、Web服务器配置(这些自动配置项可以读取我们yaml配置文件的属性自动配置)

    1
    2
    3
    spring.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包含了一组默认配置,可以让你快速开始使用特定的技术或框架,而不需要手动配置大量的依赖项。

可以大大减少配置工作,使开发者能够更快地专注于业务逻辑的实现。

自动配置的核心注解:
我们可以将自动配置的关键几步以及相应的注解总结如下:

  1. @Configuration与@Bean:基于Java代码的bean配置
  2. @Conditional:设置自动配置条件依赖
  3. @EnableConfigurationProperties与@ConfigurationProperties:读取配置文件转换为bean
  4. @EnableAutoConfiguration与@Import:实现bean发现与加载
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
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<version>3.2.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>3.2.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
<version>3.2.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.1.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>6.1.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>2.2</version>
<scope>compile</scope>
</dependency>

spring-boot-starter-data-redis为例,其包含了

  • spring-boot-starter:导入自动注入、Yaml配置的jar包
  • lettuce-core、spring-data-redis:功能的核心,里面实现了自动注入的逻辑

image-20241118172203148

AOP失效

  • 被代理对象被final关键字修饰

  • 被代理方法为private

  • 调用内部方法(无法被拦截所以代理失效)

    解决:(在IVoucherOrderService类下调用内部的createVoucherOrder方法)

1
2
IVoucherOrderService proxy =(IVoucherOrderService) AopContext.currentProxy();
proxy.createVoucherOrder(voucherId);