Interstellar
Interstellar
发布于 2024-08-06 / 11 阅读
1
0

Spring总结

基础

Spring 是一款主流的 Java EE 轻量级开源框架,其目的是用于简化 Java 企业级应用的开发难度和开发周期。Spring 的用途不仅限于服务器端的开发。

Spring 有哪些常⽤注解呢

Spring 中应用了哪些设计模式呢

JDK 动态代理 (接口)和 cglib 动态代理 (继承)

分层解耦

  • 分层解耦: 高内聚,低耦合
    内聚 : 软件各个功能模块内部的功能联系(瑞士军刀)
    耦合 : 衡量各个层/模块之间的依赖关联程度

IOC 控制反转 Inversion of Control

控制反转主要通过依赖注入 Dependency Injection,简称 DI 和依赖查找 Dependency Lookup DL 来实现

ApplicationContextBeanFactory 子接口,功能更多,而 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 实例化方式可根据类自身可实例化的方式进行选择

  1. 使用该类的有参/无参构造方法 (默认)(Spring 容器扫描到一个类被标记为 @Component 时,它会使用该类的默认构造方法(无参构造方法)来实例化 bean
  2. 借助自身的静态/非静态工厂方法或自己生成一个工厂方法 (如 @Component 中使用 @Bean 指定的)
  3. 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 注入方式 :

  1. 属性注入 (@autowired)(无法声明 final)
  2. 构造器注入
  3. 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

  1. 对象 A创建实例化 (调用 无参构造器)(相对 java 而言) 解析出每个 Bean 的定义信息,并将这些信息封装成 BeanDefinition 对象 {三级缓存}
  2. 对象 A设置属性、属性注入 (set 方法) (Spring 会根据配置文件或注解中的信息,将依赖注入到 Bean 中。) 【早期引用/暴露阶段】
    如果此时 A发生循环依赖 B,会将另一个对象 B初始化,然后 B 拿到 A 的三级缓存,放入二级缓存,删除原来的三级缓存如果被 AOP, 放入二级缓存的则是代理类 A 了,然后 B 使用二级缓存 A 完成了初始化
  3. 后置处理器(初始化之前)(修改属性值、验证配置信息、甚至可以替换或返回新的 Bean 实例)
  4. 对象初始化 (相对 spring 而言的 )(init-method@Bean 的属性, 来指定的初始化方法) (@PostConstruct 自定义的类可以使用, 配置类时声明 init 方法并指定 @PostConstruct)(如果你有一个普通的类,且无法修改它的源码来添加注解,可以在 @Bean 注解中通过 init-method 指定初始化方法)
  5. 后置处理器(初始化之后)(一些自定义的初始化逻辑、注册监听器)(AOP)(此时如果 Bean 需要被代理,Spring AOP 的基础设施会在这里创建代理对象,并返回这个 代理对象代替原始 Bean。) {放入一级缓存}``Spring AOP 的基础设施会通过调用三级缓存中的 ObjectFactory 生成代理对象,并放入一级缓存, 并从二级缓存和三级缓存中移除,如果只有二级缓存,就会导致二级缓存是普通 bean, 一级缓存时代理 bean, 没有被移除
  6. bean 对象就绪,然后放入单例池
  7. bean 对象销毁( @PreDestroy 需在配置 bean 时指定销毁方法 destory-method
  8. 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

  1. 一级缓存(singletonObjects):存放完全初始化的单例Bean
  2. 二级缓存(earlySingletonObjects):早期引用,实例化完成的 Bean
  3. 三级缓存(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 AOPAspect AOP 区别?


代理失效情况

  1. 代理对象的类型 :
    • AOP 代理只能对被 Spring 管理的 Bean 生效 (没有交给 IOC 容器的)。同样如果你直接调用一个对象的内部方法(不是通过 Spring 容器获取的),那么 AOP 不会生效。
  2. 访问修饰符 :
    • private 方法 :由于 Java 的访问控制,private 方法不能被外部代理调用,因此 AOP 对 private 方法的切面不会生效。
    • final 方法 :同样,final 方法不能被子类重写,AOP 代理也无法应用。
    • static方法 ; 静态方法是属于类本身,而不是类的实例
  3. 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 MVCSpring 框架中用于构建基于 JavaWeb 应用程序的模块,提供了基于 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自动装配)

自动配置原理

自动配置对启动时间影响,如何优化



使用配置文件

  1. 方式一 (繁琐):- @Value ("${配置文件中的KEY}") (@Value 注解只能一个一个的进行外部属性的注入) (适合偶尔使用)
  2. 方式二: 创建一个 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 是非强制性的,如果找不到匹配的 beanSpring 不会抛出异常,而是将目标字段或方法参数设置为 null

@autowire 注解在方法上则代表注入形参声明的类型

开发

![[../../IT/JAVA微服务核心/JAVAWEB/assets/JavaWeb/file-20241010014048986.png|assets/JavaWeb/file-20241010014048986.png)


评论