基础
Spring 是一款主流的 Java EE 轻量级开源框架,其目的是用于简化 Java 企业级应用的开发难度和开发周期。Spring 的用途不仅限于服务器端的开发。
Spring 有哪些常⽤注解呢
Spring 中应用了哪些设计模式呢
JDK
动态代理 (接口)和 cglib
动态代理 (继承)
分层解耦
- 分层解耦: 高内聚,低耦合
内聚 : 软件各个功能模块内部的功能联系(瑞士军刀)
耦合 : 衡量各个层/模块之间的依赖关联程度
IOC 控制反转 Inversion of Control
控制反转主要通过依赖注入 Dependency Injection
,简称 DI
和依赖查找 Dependency Lookup
DL
来实现
ApplicationContext
是 BeanFactory
子接口,功能更多,而 BeanFactory
不提供给开发者使用
当 Spring
应用程序启动时,ApplicationContext
会扫描指定的包(通常通过配置类或 XML 配置文件指定),寻找带有 @Component
注解的类。找到这些类后,ApplicationContext
会使用反射机制实例化这些类,并将它们注册到其内部管理的 Bean 工厂中。
在这个过程中,ApplicationContext
实际上是使用 BeanFactory
来管理和创建这些 Bean 的。可以简单理解为,ApplicationContext
是一个功能更强大的 BeanFactory
,它不仅能管理 Bean 的创建和生命周期,还提供了更多的高级功能
控制反转就是把创建和管理 bean
的过程转移给了第三方。而这个第三方,就是 Spring IoC Container
,对于 IoC
来说,最重要的就是容器。
何为反转,反转了什么?
答:把这个权利交给了 Spring 容器,而不是自己去控制,就是反转。由之前的自己主动创建对象,变成现在被动接收别人给我们的对象的过程,这就是反转
BeanFactory
简单粗暴,可以理解为 HashMap
:
Key - bean name
Value - bean object
但它一般只有 get, put
两个功能,所以称之为“低级容器”。
而 ApplicationContext
多了很多功能,因为它继承了多个接口,可称之为“高级容器”。在下文的搭建项目中,我们会使用它。
![[../../IT/JAVA微服务核心/SpringBoot/assets/Spring/image-20230925191412032.png|assets/Spring/image-20230925191412032.png)
ApplicationContext
除了继承了 BeanFactory
外,还继承了 ApplicationEventPublisher
(事件发布器)、
ResouresPatternResolver
(资源解析器)、MessageSource
(消息资源)等。但是 ApplicationContext
的核心功能还是 BeanFactory
ApplicationContext
接口的实现类
![[../../IT/JAVA微服务核心/SpringBoot/assets/Spring/image-20230925153938775.png|assets/Spring/image-20230925153938775.png)
实现类会使用集合(如 ConcurrentHashMap
)来存储 Bean 的实例和相关信息
。这种设计允许快速查找和访问 Bean,同时也支持线程安全。
title:父子容器关系
- 在Spring中,可以创建一个层次结构的容器。子容器可以访问父容器中定义的Bean,这样可以实现Bean的复用和组织。
- 例如,父容器可以定义一些全局的Bean(如数据源、服务等),而子容器则可以定义特定于某个模块或功能的Bean。
web MVC容器是子容器,
父容器可以是整个应用的上下文`ApplicationContext`,而Web MVC容器(`WebApplicationContext`)可以视为子容器
子容器通常专注于某个特定的功能,可以访问父容器
![[assets/JavaWeb/image-20240122165133190.png)
[[../spring/SpringMvc#DispatcherServlet 执行流程)
中详细记录了`web `容器设置 `root` 容器为父容器
![[assets/JavaWeb/image-20240122165546371.png)
![[assets/JavaWeb/image-20240122164745927.png)
![[assets/Spring/file-20240829122732965.png)
![[assets/JavaWeb/image-20240122142751630.png)
```java
`ConfigurableWebApplicationContext`是一个用于Web应用的可配置的应用上下文接口。它扩展了`ConfigurableApplicationContext`,提供了一些特定于Web的功能。
ConfigurableWebApplicationContext wav
wav.setParent(parent)
将`parent`IOC容器设置为wav的父`IOC`容器
完整过程
BeanDefinition
相当于 Bean
的“蓝图”或“元数据”
① Bean 定义
@Component
、@Service
、@Repository
等 (xml
后来演变的)
使用以上四个注解都可以声明 bean
,但是在 springboot
集成 web
开发中,声明控制器 bean 只能用 @Controller
② Bean
注册到 IOC
(IOC
装载在对象创建之前)
注册
一个 Bean 是指将一个类声明为 Spring 容器管理的对象。这个过程包括将类的信息(如类名、类型、作用域等)添加到 Spring 容器的内部数据结构中,以便在需要时可以创建和管理这个对象。
@ComponentScan(lazyinit= true)
注册而不实例化,而是在第一次使用这些Bean时才进行实例化。
使用 @Lazy
注解,@ComponentScan
扫描到的Bean会延迟到第一次使用时才实例化。
③ Bean
实例化
@ComponentScan
扫描到的Bean会在容器启动时立即实例化
Bean
实例化方式可根据类自身可实例化的方式进行选择
- 使用该类的有参/无参构造方法 (默认)(
Spring
容器扫描到一个类被标记为@Component
时,它会使用该类的默认构造方法(无参构造方法)来实例化bean
) - 借助自身的静态/非静态工厂方法或自己生成一个工厂方法 (如
@Component
中使用@Bean
指定的) FactoryBean
接口实例化 (FactoryBean
是 Spring 提供的一个接口,允许你自定义Bean的创建逻辑)
当一个组件被注入时, Spring
会优先使用无参构造方法来实例化该类。如果该类没有无参构造方法,Spring
会尝试使用其他可用的构造方法,但这需要额外的配置。
@Import
定义+注册+实例化
@Import
注释允许从另一个配置类加载 @Bean
定义
![[../../IT/JAVA微服务核心/JAVAWEB/assets/JavaWeb/image-20240118205522980.png|assets/JavaWeb/image-20240118205522980.png)
@Bean
定义+注册+实例化 (相当于静态/非静态工厂实例化)
title: `@Bean`
当一个类被 `@Configuration` 注解时, 其 `@Bean` 方法返回的对象会被注册为 `Spring` 应用上下文中的 `Bean`, 并放入 `BeanFactory` 中。
当 `Spring` 扫描到带有 `@Configuration` 注解的类时,它会创建一个增强(代理)类。这个代理类确保在调用 `@Bean` 方法时,返回的是 `Spring` 容器中的单例 `Bean` 实例,而不是每次调用该方法时都创建一个新的实例。
如果配置的第三方 `bean` 中还需要第三方 `bean`,不需要自动导入,只需要声明形参列表 `DeptService deptService`, `IOC` 会自动装配 `IOC` 中的形参列表 (也就是在形参列表声明想要的组件,可以一个可以多个,但如果 IOC 中没有就会抛异常)
```java
@Configuration
public class CommonConfig {
@Bean//将当前方法返回值交给IOC容器管理,成为IOC容器bean
public SAXReader saxParser(DeptService deptService){
//第三方bean依赖注入不需要@autowired(不需要注入deptService)
//只需要指定对应方法形参,会根据指定的类型,自动从IOC容器中寻找,完成自动装配
//如果IOC中多个DeptService,遵循@autowired装配规则,设置对应名称即可
System.out.println(deptService);
return new SAXReader();
}
}
@Configuration
配置文件中的 @bean
都算是指定实例化方法的一种, 如果在一个配置类中有多个 @Bean
方法,Spring
会分别调用每个 @Bean
方法,并将返回的对象注册为不同的 Bean
。这样做可以方便地在一个地方集中管理和配置多个 Bean
。
@ConditionalOn
@ConditionalOn
注解系列用于条件化地创建和注册Bean。如果条件不满足,相关的Bean既不会被定义,也不会被注册到Spring的IOC容器中。以下是一些常见的
@ConditionalOn
@Configuration
@Bean
@Component
public class MyBeanFactory {
@Bean//指定实例化方法,如果不指定还会用默认无参
public static MyBean createMyBean() {
return new MyBean();
}
}
//`Spring` 容器扫描到一个类被标记为 `@Component` 时,它会使用该类的默认构造方法(无参构造方法)来实例化 bean。
//`MyBean`实例是通过`@Bean`注解的方法`createMyBean`来创建的,这属于**静态工厂方法实例化**
title:@Import
```java
@Configuration
public class ConfigA {
@Bean
public A a () {
return new A ();
}
}
@Configuration
@Import (ConfigA. class)
public class ConfigB {
@Bean
public B b () {
return new B ();
}
}
Bean 的实例化方式
title: 构造方法 `@Component/@Repository/@Service@Controller`
无参
![[assets/Spring面试/image-20240619152007192.png)
有参
![[assets/Spring面试/image-20240420143352223.png)
title: 静态工厂: 单例模式: 饿汉式
就是提供一个静态方法实例化
![[assets/Spring面试/image-20240420143407359.png)
title: 工厂实例化 : 通过工厂非静态方法创建
就是提供一个普通方法实例化
![[assets/Spring面试/image-20240420143611150.png)
title: `FactoryBean` 接口实例化
![[assets/Spring面试/image-20240420143735684.png|700)
实例化完成后就可以通过多汇总方法注入
④ DI
依赖注入方式
title:Spring 自动装配的方式
![[assets/Spring面试/image-20240627160949398.png)
[[JAVA微服务核心/spring/Spring#Autowired 注入)
自动装配: `byType/byName/constructor/autodetect`
![[assets/Spring面试/image-20240414215342744.png|700)
bean
注入方式 :
- 属性注入 (
@autowired
)(无法声明final
) - 构造器注入
setter
注入
springboot
不推荐属性注入,推荐构造器注入,因为属性注入无法声明 final
,这意味着属性可以在对象生命周期中被修改,破坏了对象的不可变性
@Autowired
(类型) (spring
框架提供)
@Qualifier ("beanname")
(多个实现类需要指定名称)(优先级高于 Primary
)
@Primary
加的类会提高注入优先级
@Resource(name="beanname")
(先名称后类型)
@Resource
与@Autowired
区别
^nswufh
@Autowired
是 ; 的注解,而 @Resource
是 JDK 提供的注解
@Autowired
设计理念是 自动装配
,默认是按照 类型
注入,类型找不到或者多个该接口有多个实现类则根据 名称
,如果声明的名称为实现类名则可以找到 ,否则就需要 @Qualifier ("beanname")
指定 名称
@Resource
设计理念是 资源引用
,默认是按照 名称
注入,否则 类型
(接口多个实现类,名称不同,直接名称查找了方便)(@autowired
注入的是 接口类型
)
title: Bean注入方式
```java
---属性注入---
@Component
public class MyService {
@Autowired
private MyRepository myRepository;
// 其他代码
}
---- 构造器注入----在Spring 4.3及其之后的版本,如果只有一个构造器,`@Autowired`注解可以省略,Spring会自动注入依赖
@Component
public class MyService {
private final MyRepository myRepository;
@Autowired
public MyService(MyRepository myRepository) {
this.myRepository = myRepository;
}
// 其他代码
}
---------Setter注入------------
@Component
public class MyService {
private MyRepository myRepository;
@Autowired
public void setMyRepository(MyRepository myRepository) {
this.myRepository = myRepository;
}
// 其他代码
}
⑤ 初始化回调
⑥ Bean
就绪
Spring IOC 的实现机制
spingIOC
是一个概念,springIOC
实现类是
BeanFactory
--> ApplicationContext
--> webapplicationContext
在现实中我们使用的大部分 SpringIoC
容器是
ApplicationContext
接口的实现类 (上面的实现类都算是 IOC 容器)
@Component@Service、@Repository和@Mapper
是标明哪个类被扫描进入 SpringIoC
容器
@ComponentScan
标明采用何种策略去扫描装配 Bean
(在 @SpringBootApplication
包含了)
Spring 容器启动阶段会干什么
启动阶段: 加载配置-->分析配置信息-->装配到 BeanDefinition
-->其他后处理
Bean
实例化阶段: 实例化对象-->装配依赖-->生命周期回调-->对象其他处理-->注册回调接口
Spring Bean ⽣命周期/IOC 加载流程
![[../../IT/Utility/Excalidraw/bean周期.excalidraw|../../Utility/Excalidraw/bean周期.excalidraw)
实例化 --> 属性设置--> 后置处理器 -->IOC 初始化对象 @PostConstuct
--> 后置处理器 --> 对象就绪可以使用 -->IOC 销毁对象 @PreDestroy
--> IOC 容器关闭
@Bean(name="yzl",initMethod="",destroyMethod="",)
[[../../IT/JAVA微服务核心/SpringBoot/Spring#2 2 11 bean 的生命周期|JAVA微服务核心/spring/Spring > 2 2 11 bean 的生命周期)
[[../../IT/JAVA微服务核心/SpringBoot/Spring#bean 的生命周期|JAVA微服务核心/spring/Spring > bean 的生命周期)
Bean 生命周期过程 ^kmf433
一文带你深入理解SpringBean生命周期之InitializingBean
- 对象 A创建实例化 (调用
无参构造器
)(相对 java 而言) 解析出每个 Bean 的定义信息,并将这些信息封装成BeanDefinition
对象 {三级缓存} - 对象 A设置属性、属性注入 (set 方法) (Spring 会根据配置文件或注解中的信息,将依赖注入到 Bean 中。)
【早期引用/暴露阶段】
如果此时 A发生循环依赖 B,会将另一个对象 B初始化,然后 B 拿到 A 的三级缓存,放入二级缓存,删除原来的三级缓存如果被 AOP, 放入二级缓存的则是代理类 A 了,然后 B 使用二级缓存 A 完成了初始化 - 后置处理器(初始化之前)(修改属性值、验证配置信息、甚至可以替换或返回新的 Bean 实例)
- 对象初始化 (相对
spring
而言的 )(init-method
是@Bean
的属性, 来指定的初始化方法) (@PostConstruct
自定义的类可以使用, 配置类时声明init
方法并指定@PostConstruct
)(如果你有一个普通的类,且无法修改它的源码来添加注解,可以在@Bean
注解中通过init-method
指定初始化方法) - 后置处理器(初始化之后)(一些自定义的初始化逻辑、注册监听器)(AOP)(此时如果 Bean 需要被代理,
Spring AOP
的基础设施会在这里创建代理对象,并返回这个代理对象代替原始 Bean
。){放入一级缓存}``Spring AOP
的基础设施会通过调用三级缓存中的ObjectFactory
生成代理对象,并放入一级缓存, 并从二级缓存和三级缓存中移除,如果只有二级缓存,就会导致二级缓存是普通 bean, 一级缓存时代理 bean, 没有被移除 - bean 对象就绪,然后放入单例池
- bean 对象销毁(
@PreDestroy
需在配置 bean 时指定销毁方法destory-method
) - IOC 容器关闭
title: 完整流程
![[assets/Spring面试/image-20240414213704059.png|700)
![[assets/Spring面试/file-20240912000452158.png)
![[assets/Spring面试/file-20240912000502256.png)
```java
public class MyBean {
private String property;
// 无参构造器
public MyBean() {
System.out.println("1 Bean 对象创建(调用无参构造器)");
}
// Setter 方法
public void setProperty(String property) {
this.property = property;
System.out.println("2给 Bean 对象设置属性");
}
// 初始化方法
//周期方法要求: 方法命名随意,但是要求方法必须是 public void 无形参列表
@PostConstruct
public void init() {
System.out.println("4 Bean 对象初始化");
}
// 销毁方法
@PreDestroy
public void destroy() {
System.out.println("6 Bean 对象销毁");
}
}
-----后置处理器-----
AOP 代理对象的创建是在 Bean 初始化之后,但在后置处理器(初始化之后)阶段。具体来说,`Spring AOP` 使用 `BeanPostProcessor` 接口的实现类,在 `postProcessAfterInitialization` 方法中创建代理对象并返回,以替换原始的 `Bean` 对象
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("3 Bean 的后置处理器(初始化之前): " + beanName);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("5 Bean 的后置处理器(初始化之后): " + beanName);
return bean;
}
}
不同于周期方法
[[../../IT/JAVA微服务核心/JAVAWEB/JavaWeb#bean 的周期方法|JAVA微服务核心/JAVAWEB/JavaWeb > bean 的周期方法)
title:简介
![[assets/Spring面试/image-20240414213754028.png|700)
![[assets/Spring面试/image-20240414213748052.png|700)
![[assets/Spring面试/image-20240414213805545.png|700)
SpringMVC 中有几个 IOC 容器
![[../../IT/JAVA微服务核心/JAVAWEB/assets/JavaWeb/image-20240122164745927.png|assets/JavaWeb/image-20240122164745927.png)
Bean
bean 默认为类名首字母小写,可以通过 value、name指定
@Scope("prototype")
默认 singleton
,另外常用 prototype
(每次请求都是新的bean)
title: 获取 Bean 对象
IOC 容器
IOC 容器对象
IOC 容器获取 Bean 对象 (`DeptController Bean `对象为例)
拿到 IOC 容器只需要注入` (@autowired) IOC `容器对象
```java
@Autowired
private ApplicationContext applicationContext;//IOC容器对象
@Test
public void testGetBean(){
//根据bean名称获取
DeptController bean1 = (DeptController) applicationContext.getBean("deptController");
System.out.println(bean1);
//根据bean类型获取
DeptController bean2 = applicationContext.getBean(DeptController.class);
System.out.println(bean2);
//根据bean的名称和类型
DeptController bean3 = applicationContext.getBean("deptController", DeptController.class);
System.out.println(bean3);
}
com.ithiema.controller.DeptController@6475e778
com.ithiema.controller.DeptController@6475e778
com.ithiema.controller.DeptController@6475e778
地址一样,说明 IOC 容器中 bean 对象只有一个,是单例
上述所说的 【Spring 项目启动时,会把其中的 bean 都创建好】还会受到作用域及延迟初始化影响,这里主要针对于默认的单例非延迟加载的 bean 而言。
Bean 作用域
singlton/prototype/request/session/global-session
[[../../IT/JAVA微服务核心/JAVAWEB/JavaWeb#Bean 作用域 Scope|JAVA微服务核心/JAVAWEB/JavaWeb > Bean 作用域 Scope)
实际开发当中,绝大部分的 Bean 是单例的,也就是说绝大部分 Bean 不需要配置 scope 属性。
作用域 @Scope ("prototype") | interpret |
---|---|
singleton | 容器内同名称的 bean 只有一个实例**(单例)**(容器启动就会实例化)(默认) |
prototype | 每次使用该 bean 时会创建新的实例(非单例) |
request | 每个请求范围内会创建新的实例(web 环境中,了解) |
session | 每个会话范围内会创建新的实例(web 环境中,了解) |
application | 每个应用范围内会创建新的实例(web 环境中,了解) |
@Component
类会被注册为单实例(Singleton)Bean
@Lazy
: 延迟初始化,延迟到第一次使用初始化而不是容器启动就会实例化
Spring 中的单例 Bean 会存在线程安全问题吗
存在
解决
循环依赖问题
循环依赖(Circular Dependency)
是指两个或多个 Bean 相互依赖,形成了一个闭环。例如,Bean A 依赖于 Bean B,而 Bean B 又依赖于 Bean A。这种情况会导致 Spring 在创建 Bean 时出现问题
可以使用 @lazy
注解
Spring
不支持基于构造器注入的循环依赖
构造器注入意味着刚刚开始,实例化就需要
AB
都是构造器注入不可以
A
中的 B
为构造器注入不可以,为 setter 注入可以
Spring
怎么解决循环依赖的呢?
任何已经完全创建并初始化好的单例(singleton)Bean 都会被存储在一级缓存中。
一级缓存是 Spring 容器中最主要的缓存,用于存储已经完全初始化并可以直接使用的单例 Bean 实例。
就是 3 中不同实例化阶段的 Bean
- 一级缓存(singletonObjects):存放完全初始化的单例Bean
- 二级缓存(earlySingletonObjects):早期引用,实例化完成的
Bean
- 三级缓存(singletonFactories):存放
Bean
工厂
当 Spring 检测到循环依赖时,会将尚未完全初始化的 Bean 提前暴露到二级缓存中,以便其他 Bean 可以引用它。这种机制可以解决大部分单例模式下的循环依赖问题,包括属性自动注入的情况。
如果不互相依赖会直接创建放入一级缓存
title: 循环依赖
![[assets/Spring面试/image-20240619190243384.png)
![[assets/Spring面试/image-20240619190254480.png)
![[assets/Spring面试/image-20240619190320457.png)
![[assets/Spring面试/image-20240619190349980.png)
![[assets/Spring面试/image-20240619192841970.png)
![[assets/Spring面试/image-20240619190329693.png)
![[assets/Spring面试/image-20240619190533532.png)
![[assets/Spring面试/image-20240619190338400.png)
### 三级缓存解决循环依赖的详细步骤
**三级缓存(singletonFactories)**存储的是`ObjectFactory`,用于创建Bean的早期引用(early reference),以便放入二级缓存
如果发现在creating set就说明出现了循环依赖
12. **实例化Bean A**:
- Spring开始创建Bean A的实例。将Bean A的工厂对象放入三级缓存(`singletonFactories`)。
- 在创建Bean A的过程中,Spring发现Bean A依赖于Bean B。
13. **提前暴露Bean A的早期引用**:
- Spring通过工厂对象获取Bean A的早期引用,并将其放入二级缓存(`earlySingletonObjects`)。
14. **实例化Bean B**:
- Spring开始创建Bean B的实例。将Bean B的工厂对象放入三级缓存(`singletonFactories`)。
- 在创建Bean B的过程中,Spring发现Bean B依赖于Bean A。从A的三级缓存执行A的lambda表达式获取对象进行注入(AOP则是提前AOP的代理对象),然后将A存入二级缓存,然后B执行后续流程完成创建,然后再创建A,删除残留对象
尽管早期引用的Bean尚未完全初始化,但在依赖注入阶段,这种提前暴露的引用是足够的。Spring通过三级缓存机制,可以有效地解决单例模式下的循环依赖问题,确保所有Bean在使用前都能完成初始化。对于大多数场景,这种机制是安全且有效的,不会对Bean的初始化和使用产生负面影响。
为什么需要 3 级缓存,二级缓存不行吗
在初始化过程中需要处理代理对象(如 AOP 代理)
某个 Bean 被提前暴露到二级缓存中,但此时它还没有经过代理处理。如果在初始化过程中需要生成代理对象,在 BeanPostProcessor
生成代理对象之后,生成的代理对象会覆盖二级缓存中的原始对象。这会导致在获取到的 Bean 对象不一致的问题。
三级缓存会直接提前进行 AOP, 进入二级缓存的就是代理类,避免了不一致问题
将 代理bean
和 普通bean
都存了进去,无论是否存在都会存入三级缓存,出现了再执行进行 AOP
三级缓存内部会判断是否被 AOP
@autowired
的实现原理
title:简介
![[assets/Spring面试/image-20240619193031687.png)
![[assets/Spring面试/image-20240619193323369.png)
![[assets/Spring面试/image-20240619193333139.png)
![[assets/Spring面试/image-20240619193353644.png)
![[assets/Spring面试/image-20240619193402912.png)
AOP
AOP 本质上就是对 代理模式
的简化
切面类包括:
切面类 : 切面=切入点表达式+通知
目标对象=连接点+切入点
连接点:JoinPoint(除@Around的通知)/ProceedingJoinPoint(@Around)(是JoinPoint子类)
![[../../IT/JAVA微服务核心/JAVAWEB/assets/JavaWeb/image-20230907110339042.png|1200)
说说 JDK 动态代理和 CGLIB 代理
[[../../IT/JAVA微服务核心/JAVAWEB/JavaWeb#动态代理|JAVA微服务核心/JAVAWEB/JavaWeb > 动态代理)
类 : JDK
动态代理 (接口)和 cglib
动态代理 (继承)
JDK原生的实现方式
:有接口时 , 目标类实现了接口 , JDK
创建的 代理类
也会自动实现接口,且代理类和目标类之间没有继承关系,无法代理没有实现接口的类
cglib(spring中)
:没有接口时, 目标类不需要实现 接口
,cglib
创建的 代理类
会继承于目标类来实现代理,
说说 Spring AOP
和 Aspect AOP
区别?
代理失效情况
- 代理对象的类型 :
- AOP 代理只能对被 Spring 管理的 Bean 生效 (没有交给 IOC 容器的)。同样如果你直接调用一个对象的内部方法(不是通过 Spring 容器获取的),那么 AOP 不会生效。
- 访问修饰符 :
- private 方法 :由于 Java 的访问控制,private 方法不能被外部代理调用,因此 AOP 对 private 方法的切面不会生效。
- final 方法 :同样,final 方法不能被子类重写,AOP 代理也无法应用。
- static方法 ; 静态方法是属于类本身,而不是类的实例
- Bean 的作用域 :
- 如果 Bean 的作用域是 prototype,而你又在 singleton 的 Bean 中注入了这个 prototype Bean,可能导致 AOP 失效。
这些失效适用于 @Transactional/@Cacheable
等注解,因为这些也依靠 AOP 来实现
失效
public boolean testcall(int id)throws Exception {
return testcompileException2(id);
}
testcompileException2上虽然加了如@Transactional/@Cacheable,但是不会生效,因为这是内部调用
解决:return springUtil.getBean(this.getclass()).testcompileException2(id)
失效
@Transactiona
private boolean testspecialException(int id)throws Exception
事物
事物管理底层也是 AOP
sping 事物分类
编程式(代码块级别)
声明式: xml
或注解 transactional
(方法级别)
事物传播机制
@Transactional(readOnly = true)
@Transactional(timeout = 3)
[[../数据库/mysql/Mysql面试#^sz3dj3|Mysql/Mysql面试 > ^sz3dj3)
声明式事物实现原理
就是通过 AOP/动态代理
声明式事务在哪些情况下会失效
MVC
![[../../IT/JAVA微服务核心/JAVAWEB/assets/JavaWeb/image-20240122142832335.png|assets/JavaWeb/image-20240122142832335.png)
M 模型 V 视图 C 控制层,接受和响应
springMVC
将软件按照模型,视图,控制器来划分
Spring MVC
是 Spring
框架中用于构建基于 Java
的 Web
应用程序的模块,提供了基于 MVC
架构的方式来处理 HTTP
请求并返回响应。
MVC 执行流程
![[../../IT/Utility/Excalidraw/springmvc执行流程.excalidraw|../../Utility/Excalidraw/springmvc执行流程.excalidraw)
DispacherServlet
-> HandlerMapping
-> Interceptor
-> HandlerAadapter
->调用前准备 handleMethodArgumentResolver(参数解析器)
和 handleMethodReturnValueHandler(返回值处理器)
handleMethodReturnValueHandler(返回值处理器)
RequestMappingHandlerAdapter
准备好 handleMethodArgumentResolver(参数解析器)
和 handleMethodReturnValueHandler(返回值处理器)
后,来调用 invokeHandlerMethod()
来执行目标方法(自己写的 handler),然后生成返回值对象,根据前面准备好的 handleMethodReturnValueHandler
进行返回值的处理
内容协商
RequestResponseBodyMethodProcessor
在 Spring 框架中既是一个返回值处理器,也是一个参数解析器。
使得 HandlerAadapter
可以调用 HttpMessageConverter
将 请求报文转换为 Java 对象
,或将 Java 对象转换为响应报文
HttpMessageConverter
是 RequestResponseBodyMethodProcessor
返回值处理器所调用的 , 调用writeWithMessageConverters
利用messageConverter
把返回值写出去
自定义转换器与 ObjectMapper
HttpMessageConverter
是所有转换器的接口
转换器 ->
消息转换器 messageConvert
ObjectMapper
是 Jackson 库的核心类,负责将 Java 对象和各种数据格式(如 JSON、YAML
)之间进行转换。
ObjectMapper.writeValue(OutputStream out, Object value)
:这是 ObjectMapper
的一个方法,用于将 Java 对象 value
序列化为指定的数据格式,并写入到输出流 out
中
如果你的项目中没有特殊的需求,使用默认的 ObjectMapper
就足够了。Jackson 的默认配置已经能够处理大多数基本的 YAML 序列化和反序列化需求。
在配置类中声明一个方法, 的方法命名是固定的 extendMessageConverters/configureMessageConverters
![[../../IT/Utility/Excalidraw/自定义转换器.excalidraw|../../Utility/Excalidraw/自定义转换器.excalidraw)
为 configureMessageConverters
时,-该方法用于替换 Spring MVC
的默认消息转换器列表。
为 extendMessageConverters
时,该方法用于在保留 Spring MVC
默认消息转换器的基础上,添加自定义的消息转换器。
自定义 JSON 转换器 (JacksonObjectMapper
)
由于默认的JSON
转换器虽然存在,但无法满足需求,所以需要自定义ObjectMapper
如果你的项目中没有特殊的需求,使用默认的 ObjectMapper
就足够了。Jackson 的默认配置已经能够处理大多数基本的 YAML 序列化和反序列化需求。
title: 自定义 JSON 转换器(JacksonObjectMapper)
为消息转换器`MappingJackson2HttpMessageConverter`设置对象转换器`JacksonObjectMapper`
这行代码的作用是为 `MappingJackson2HttpMessageConverter` 设置一个自定义的 `ObjectMapper`,`ObjectMapper` 是 Jackson 库中的一个核心类,负责将Java对象和JSON数据之间进行转换。
![[assets/Spring面试/image-20240629181507086.png)
![[assets/苍穹外卖/image-20231029215203117.png)
![[assets/苍穹外卖/image-20231029215121733.png)
不使用消息转换器时, 数据库的时间类型(2022-02-17 09:16:20)直接被序列化为字符串(202221791620), 没有经过格式化。
![[assets/苍穹外卖/image-20231029215138915.png)
```java
/**
* 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
* 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
* 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
*/
public class JacksonObjectMapper extends ObjectMapper {
public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
//public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm";
public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
public JacksonObjectMapper() {
super();
//收到未知属性时不报异常
this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
//反序列化时,属性不存在的兼容处理
this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
SimpleModule simpleModule = new SimpleModule()
.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))
.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
//注册功能模块 例如,可以添加自定义序列化器和反序列化器
this.registerModule(simpleModule);
}
}
自定义 Yaml
转换器
如果你的项目中没有特殊的需求,使用默认的 ObjectMapper
就足够了。Jackson
的默认配置已经能够处理大多数基本的 YAML 序列化和反序列化需求。所以无需自定义ObjectMapper
,只需要引入yaml
依赖后自定义一个MyYamlHttpMessageConverter
即可,因为Spring MVC
默认并不支持 YAML
格式的请求和响应
title: 自定义Yaml转换器
将Yaml依赖传入了ObjectMapper
将`@responseBody/@requestBody`通过自己的转换器
直接使用方式
![[assets/Spring面试/file-20240830152039615.png)
改对象被转为了Yaml
```java
自定义Yaml转换器
- 依赖加入
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
</dependency>
- 配置文件加入
#新增一种媒体类型
spring.mvc.contentnegotiation.media-types.yaml=text/yaml
---------------配置文件中注册转换器------------
@Configuration
public class Myconfig implements WebMvcConfigurer {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new MyYamlHttpMessageConverter());
WebMvcConfigurer.super.extendMessageConverters(converters);
}
}
---------------自定义一个转换器------------
public class MyYamlHttpMessageConverter extends AbstractHttpMessageConverter<Object> {
private ObjectMapper objectMapper;
public MyYamlHttpMessageConverter(){
//声明当前转换器媒体类型,和配置文件应该对应
super(new MediaType("text","yaml", Charset.forName("UTF-8")));
// 创建一个 YAMLFactory 并禁用 WRITE_DOC_START_MARKER 特性
YAMLFactory yamlFactoryfactory=new YAMLFactory().disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER);
// 用该 YAMLFactory 创建一个 ObjectMapper
this.objectMapper = new ObjectMapper(yamlFactoryfactory);
}
@Override
//只要是对象类型,不是基本类型,就要为true
protected boolean supports(Class<?> clazz) {
return true;
}
@Override//@RequestBody读的方式
// 这个方法用于处理从HTTP请求中读取数据并将其转换为Java对象。这里暂时返回 `null`,表示还没有实现具体的读取逻辑。
protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return null;
}
@Override//@Response返回的方式
//`Object o`:要写入HTTP响应的Java对象,通常为处理器handler的执行结果
//`HttpOutputMessage outputMessage`:HTTP响应消息,包含了响应的头部和主体
//`writeValue(OutputStream out, Object value)`:这是`ObjectMapper`的一个方法,用于将Java对象`value`序列化为指定的数据格式(在这里是YAML),并写入到输出流`out`中
protected void writeInternal(Object o, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
//将Object要返回的对象写入
try (OutputStream os = outputMessage.getBody()) {
this.objectMapper.writeValue(os,o);
}
}
}
请求设置
@RequestMapping(
value = {"/testRequestMapping", "/test"}
,method = {RequestMethod.GET, RequestMethod.POST}
,params = {"username","password!=123456"}
,header={"Host=localhost:8081"}
,consumes={"multipart/form-data"}//用于指定请求的 `Content-Type`
,produces={"application/json"}//用于指定响应的 `Content-Type`
,name={"testHandler"}//用于为映射提供一个逻辑名称。通常用于工具或框架需要根据名称进行查找的情况。
)
处理get请求的映射-->`@GetMapping`
处理post请求的映射-->`@PostMapping`
处理put请求的映射-->`@PutMapping`
处理 delete 请求的映射--> `@DeleteMapping`
获取数据
[[../../IT/JAVA微服务核心/SpringBoot/SpringMvc#通过控制器方法的形参获取请求参数|../spring/SpringMvc > 通过控制器方法的形参获取请求参数)
@RequestParam
@DateTimeFormat(pattern= "")
@DateTimeFormat(pattern = "yyyy-MM-dd")
@RequestBody
@pathVariable
@RequestHeader
@CookieValue
使用 jwt 则不会创建会话
除了上面的还可以获取
HttpServletRequest
HttpServletResponse
HttpSession
title: HttpServletRequest
- **核心作用:**
- HttpServletRequest 代表一个客户端(通常是浏览器)发送给服务器的 HTTP 请求。
- 它封装了请求的所有信息,包括请求方法、请求 URL、请求头、请求参数、请求体等。
- 通过 HttpServletRequest,服务端可以获取客户端传递的数据,并进行相应的处理。
- **主要功能:**
- **获取请求方法:** getMethod() (例如 "GET", "POST", "PUT", "DELETE")
- **获取请求 URL:**
- getRequestURL(): 获取完整的 URL (例如: http://localhost:8080/myapp/users?id=123).
- getRequestURI(): 获取 URL 的路径部分 (例如: /myapp/users).
- getContextPath(): 获取上下文路径 (例如: /myapp).
- getServletPath(): 获取 Servlet 的路径 (例如: /users).
- **获取请求参数:**
- getParameter(String name): 获取指定名称的请求参数的值,返回 String 类型。适用于简单参数。
- getParameterValues(String name): 获取指定名称的所有请求参数的值,返回 String[] 类型。适用于同一个参数有多个值的情况。
- getParameterMap(): 获取所有请求参数的 Map,Key 是参数名,Value 是 String[]。
- getQueryString(): 获取 URL 中的查询字符串(? 之后的部分)。
- **获取请求头:**
- getHeader(String name): 获取指定名称的请求头的值。
- getHeaders(String name): 获取指定名称的所有请求头的值,返回 Enumeration<String>。
- getHeaderNames(): 获取所有请求头的名称,返回 Enumeration<String>。
- **获取请求体:**
- getInputStream(): 获取请求体的输入流,用于读取二进制数据 (例如上传文件)。
- getReader(): 获取请求体的字符输入流,用于读取文本数据。
- **注意:** 如果使用 @RequestBody 注解, Spring MVC 会自动处理请求体,您不需要手动读取输入流。
- **获取客户端信息:**
- getRemoteAddr(): 获取客户端的 IP 地址。
- getRemoteHost(): 获取客户端的主机名。
- getRemotePort(): 获取客户端的端口号。
- **获取会话信息:**
- getSession(): 获取当前请求的会话对象,如果不存在则创建新的会话。
- getSession(boolean create): 获取当前请求的会话对象。create=true则如果不存在则创建新的会话,create=false 如果不存在返回null。
- **获取 Cookie:**
- getCookies(): 获取当前请求的所有Cookie对象,返回Cookie[]
title: **HttpServletResponse**
- **核心作用:**
- HttpServletResponse 代表服务器发送给客户端的 HTTP 响应。
- 它封装了响应的所有信息,包括响应状态码、响应头、响应体等。
- 通过 HttpServletResponse,服务端可以向客户端返回数据,例如 HTML、JSON、图片等。
- **主要功能:**
- **设置响应状态码:** setStatus(int sc) (例如: 200 表示成功, 404 表示找不到资源, 500 表示服务器内部错误)。
- **设置响应头:**
- setHeader(String name, String value): 设置响应头的键值对。
- addHeader(String name, String value): 添加一个响应头的键值对。
- setContentType(String type): 设置响应内容的 MIME 类型(例如: text/html, application/json, image/jpeg)。
- **设置响应体:**
- getWriter(): 获取字符输出流,用于写入文本数据。
- getOutputStream(): 获取字节输出流,用于写入二进制数据。
- **注意:** Spring MVC 通常会自动处理响应体,您不需要直接使用输出流。 例如使用@ResponseBody 注解,会由 Spring MVC 自动将返回值转换成 JSON 或 XML。
- **设置 Cookie:** addCookie(Cookie cookie): 向客户端添加一个 Cookie。
- **重定向:** sendRedirect(String location): 将客户端重定向到新的 URL。
- **使用场景:**
- 设置响应状态码,通知客户端请求处理的结果。
- 设置响应头,例如 Content-Type,告诉浏览器如何处理响应体。
- 写入响应体,将数据返回给客户端。
- 添加 Cookie,用于跟踪用户状态或存储用户偏好。
- 重定向到其他页面。
- 设置缓存策略,减少请求量。
title:HttpSession
- **用户首次访问:** 当一个用户第一次访问你的 Web 应用时,服务器(通常是Servlet 容器,比如 Tomcat)会执行以下操作:
4. **检查请求头/Cookie:** 服务器首先会检查客户端的请求头中是否包含名为 JSESSIONID 的 Cookie (默认 Session Cookie 名)。
5. **没有 Cookie:** 如果请求头中没有 JSESSIONID Cookie 或者 Cookie 中的 JSESSIONID 值与服务器端已有的任何会话都不匹配,那么服务器会认为这是一个新的用户会话。
6. **创建会话:** 服务器会创建一个新的 HttpSession 对象,并生成一个唯一的会话 ID(session ID),这个 ID 是一个随机字符串。
7. **设置 Cookie:** 服务器会将这个新的会话 ID 通过一个 Cookie(默认名为 JSESSIONID)设置到响应头中发送给客户端。浏览器收到响应后,会自动存储这个 Cookie,以便在后续请求中发送给服务器。
- **核心作用:**
- HttpSession 代表一个用户的会话,用于在多个请求之间存储和共享用户特定的数据。
- HTTP 协议是无状态的,因此需要 HttpSession 来跟踪用户的状态。
- **主要功能:**
- **存储数据:** setAttribute(String name, Object value): 将数据存储到会话中。
- **获取数据:** getAttribute(String name): 从会话中获取数据。
- **删除数据:** removeAttribute(String name): 从会话中删除数据。
- **使会话失效:** invalidate(): 使当前会话失效,会话中的数据将被删除。
- **获取会话 ID:** getId(): 获取当前会话的唯一标识符。
- **判断会话是否是新的:** isNew(): 判断当前会话是否是新的。
- **设置会话过期时间:** setMaxInactiveInterval(int interval):设置会话的最大不活动时间,单位为秒。 如果超过这个时间没有新的请求,会话将自动失效。
- **使用场景:**
- 存储用户的登录信息,例如用户名、用户 ID 等。
- 存储用户的购物车信息。
- 跟踪用户的访问状态。
- 存储用户偏好设置。
- 实现多步骤表单的中间数据存储。
过滤器和拦截器的区别
[[../../IT/JAVA微服务核心/JAVAWEB/JavaWeb#过滤器 Filter|JAVA微服务核心/JAVAWEB/JavaWeb > 过滤器 Filter)
filter
实现 Filter
接口
filterChain.doFilter(servletRequest,servletResponse)
可以设置过滤器链
interceptor
实现 HandlerInterceptor
接口
[[../../IT/JAVA微服务核心/JAVAWEB/JavaWeb#拦截器 Interceptor|JAVA微服务核心/JAVAWEB/JavaWeb > 拦截器 Interceptor)
实现接口,创建 WebMvcConfigurer
的配置类,重写 addInterceptors
方法,注册 HandlerInterceptor
实现类
[[../../IT/JAVA微服务核心/JAVAWEB/JavaWeb#^trzlo0|拦截路径)
RestFul
[[../../IT/JAVA微服务核心/SpringBoot/SpringMvc#RESTful|../spring/SpringMvc > RESTful)
保存 Post, 更新 PUT
删除 Delete, 查询 Get
![[../../IT/JAVA微服务核心/SpringBoot/assets/SpringMvc/image-20231001113139263.png|../spring/assets/SpringMvc/image-20231001113139263.png)
Springboot 3
![[../../IT/Utility/Excalidraw/springboot自动装配|../../Utility/Excalidraw/springboot自动装配)
自动配置原理
自动配置对启动时间影响,如何优化
使用配置文件
- 方式一 (繁琐):-
@Value ("${配置文件中的KEY}")
(@Value
注解只能一个一个的进行外部属性的注入) (适合偶尔使用) - 方式二: 创建一个 Bean 对象, 加上注解
@Data,@Component,@ConfigurationProperties (prefix = "aliyun. oss")[自动注入]
注解,其他类需要使用时只需要@Autowired
获取该 bean 对象 - 类中属性名必须与 yml 文件中 key 保持一致
其他
proxyBeanMethods = true(默认值)
是 @Configuration
注解的一个属性
proxyBeanMethods
参数设置为 false 时即为:Lite
模式。该模式下注入容器中的同一个组件无论被取出多少次都是不同的 bean
实例,直接返回新实例对象,在该模式下 SpringBoot
每次启动会跳过检查容器中是否存在该组件,每次调用配置类中的@Bean 方法都会创建一个新的 Bean 实例。
@autowire(required=false)
- 如果将
@Autowired
注解的required
属性设置为false
(默认为true
),则表示注入这个bean
是非强制性的,如果找不到匹配的bean
,Spring
不会抛出异常,而是将目标字段或方法参数设置为null
。
@autowire
注解在方法上则代表注入形参声明的类型
开发
![[../../IT/JAVA微服务核心/JAVAWEB/assets/JavaWeb/file-20241010014048986.png|assets/JavaWeb/file-20241010014048986.png)