Java技术栈总结:Spring框架篇

一、SpringBean

1、定义方式

  • </bean>标签(XML文件)
  • @Bean注解
  • @Component注解
  • BeanDefinition方法
  • FactoryBean
  • Supplier

注:BeanDefinition方法为"</bean>标签、@Bean注解、@Component注解"的底层实现。

(1)</bean>标签。Spring容器实例化时,会将XML配置的</bean>信息封装成一个BeanDefinition对象,Spring根据BeanDefinition对象创建Bean对象。


【实例化bean的方法】:

  • 通过构造方法实例化Bean;

  • 通过静态方法实例化Bean;

  • 通过实例方法实例化Bean;

  • Bean的别名。

2、生命周期

【流程概述】

  1. 通过BeanDefinition获取bean的定义信息;
  2. 调用构造函数实例化bean;
  3. bean的依赖注入;
  4. 处理Aware接口(BeanNameAware、BeanFactoryAware、ApplicationContextAware);
  5. Bean的后置处理器BeanPostProcessor-前置;
  6. 初始化方法(InitializingBean、init-method);
  7. Bean的后置处理器BeanPostProcessor-后置;(完成此步后,Bean对象创建完成)
  8. 销毁bean。

【具体流程】

  1. Spring 对 bean实例化
  2. Spring 将值和 bean 的引入注入 到 bean 对应的属性中;
  3. 如果 bean 实现了 BeanNameAware 接口,Spring 将 bean 的 ID 传递给 setBeanName() 方法;
  4. 如果 bean 实现了 BeanFactoryAware 接口,Spring 将调用 setBeanFactory() 方法,将BeanFactory 容器实例传入;
  5. 如果 bean 实现了 ApplicationContextAware 接口,Spring 将调用 setApplicationContext() 方法,将 bean 所在的应用上下文传递进来;
  6. 如果 bean 实现了 ++BeanPostProcessor++ 接口,Spring 将调用它们的 postProcessBeforeInitialization( ) 方法;
  7. 如果 bean 实现了 InitializingBean 接口,Spring 将调用它们的 afterPropertiesSet( ) 方法;
  8. 如果 bean 实现了 ++BeanPostProcessor++ 接口,Spring 将调用它们的 postProcessAfterInitialization( ) 方法;
  9. 此时,bean 已经准备就绪,可以被程序使用了,它们将一直驻留在应用上下文中,直到该应用上下文被销毁;
  10. 如果 bean 实现了 DisposableBean 接口,Spring 将调用它的 destory( ) 接口方法。同样,如果 bean 使用了 destory-method 声明了销毁方法,该方法也会被调用。

注:实例化和初始化是两个完全不同的过程。实例化 只是给Bean分配了内存空间,而初始化是将程序的执行权,从系统级别转换到用户级别,并开始执行用户添加的业务代码。

3、线程安全

SpringBean类型(@Scope注解):

  • singleton(默认,单例): bean在每个Spring IOC容器中只有一个实例;
  • prototype:一个bean的定义可以有多个实例。

Q:Spring中的单例Bean是线程安全的吗?

A:不是线程安全的。(解决,多例模式prototype 或 加锁)

多个用户请求同一个服务,容器会给每个请求分配一个线程,这时多个线程会并发执行该请求对应的逻辑(成员方法)。如果该处理逻辑中有对单例状态的修改(单例的成员属性),则需要考虑线程同步问题。

Spring没有对单例bean进行任何多线程的封装处理,因此单例bean的线程安全及并发问题需要开发者自行处理。

注:通常项目中用到的Spring Bean都是不可变的状态,比如Service类和Dao类,所以在一定程度上可以说Spring的单例Bean是线程安全的。但是,如果用到的Bean有多种状态,比如View Model对象,就需要自行保证线程安全。一种解决方法是,将多态Bean的作用由 singleton 变为 prototype。

4、循环依赖

循环依赖其实就是循环引用,也就是两个或两个以上的bean互相持有对方,bean对象的创建依赖对方。例如,A依赖于B,B依赖于A。

(1)出现的情形

A、B同时被Spring容器管理,A、B互相依赖(或多个对象互相依赖,A - B - C - A)。

对象创建过程:

(2)三级缓存

Spring通过三级缓存解决循环依赖问题,对应的三级缓存如下所示:

java 复制代码
// 单实例对象注册器
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
    private static final int SUPPRESSED_EXCEPTIONS_LIMIT = 100;
    // 一级缓存
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
    // 二级缓存
    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);
    // 三级缓存
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
}

【各级缓存信息】:

【使用三级缓存,对象创建流程】:

A 创建过程中需要 B,于是 A 将自己放到三级缓里面 ,去实例化 B;

B 实例化的时候发现需要 A,于是 B 先查一级缓存,没有;再查二级缓存,还是没有,再查三级缓存,找到了!然后把三级缓存里面的这个 A 放到二级缓存里面(通过getObject方法创建对象),并删除三级缓存里面的 A;

B 初始化完成,将自己放到一级缓存里面(此时B里面的A依然是创建中状态);

然后回来接着创建 A,此时 B 已经创建结束,直接从一级缓存里面拿到 B ,然后完成创建,并将自己放到一级缓存里面。

(3)构造方法产生的循环依赖

java 复制代码
@Component
public class A {
    // 成员变量B
    private B b;
    
    public A(B b) {
        this.b = b;
    }
}

@Component
public class B {
    // 成员变量A
    private A a;

    public B(A a) {
        this.a = a;
    }
}

由于Spring Bean的创建过程,在实例化分配内存空间后,首先要调用构造方法,此阶段还无法创建出"半成品对象",因此无法通过三级缓存的方式解决。

解决:在构造方法参数上增加 "@Lazy" 注解。

java 复制代码
@Component
public class A {
    // 成员变量B
    private B b;
    
    public A(@Lazy B b) {
        this.b = b;
    }
}

二、IOC 控制反转 && DI 依赖注入

IoC 是一种设计思想,而 DI 是一种具体的实现技术。

1、IOC 控制反转

Inversion of Control 的缩写。将我们之前由客户端代码来创建的对象交由IOC容器来进行控制,对象的创建,初始化以及后面的管理都由IOC容器完成。

耦合:两个或两个以上对象存在依赖,当一方修改之后会影响另一方,那么就说这些对象间存在耦合。

解耦:解除两个或两个以上对象,修改一方影响另一方的问题。

new 的方式创建对象,对象的管理权是由当前类控制的;而有了 IoC 之后,对象的管理权就交给IoC 容器管理,而不是当前类了,此时对象的管理权就发生了反转和改变,这就是 IoC,即控制反转。

2、DI 依赖注入

Dependency Injection 的缩写。在 IoC 容器运行期间,动态地将某个依赖对象注入到当前对象的技术就叫做 DI(依赖注入)。


三、AOP

AOP (Aspect Oriented Programming)称为面向切面编程,用于将那些++与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块++,这个模块被命名为"切面"(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高系统的可维护性。

1、使用场景

  • 记录日志
  • 缓存处理
  • Spring内置的事务处理

2、实现原理

这部分待补充完善

JDK动态代理、CGlib动态代理;

  • 如果要使用++JDK动态代理,被代理的类必须实现了++ ++接口++,否则无法代理;
  • JDK动态代理是JDK原生的,不需要任何依赖即可使用;

四、事务

1、实现方式

Spring支持编程式事务管理和声明式事务管理两种方式。

  • 编程式事务控制:需使用TransactionTemplate来进行实现,对业务代码有侵入性,项目中很少使用
  • 声明式事务管理:声明式事务管理建立在AOP之上的。其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中。在目标方法开始之前加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。(@Transactional)

【具体实现】

  1. ++编程式事务++管理需要在代码中调用beginTransaction()、commit()、rollback()等事务管理相关的方法(对基于 POJO 的应用来说是唯一选择);

  2. 基于 TransactionProxyFactoryBean的声明式事务管理;

  3. 基于 @Transactional 的声明式事务管理;

  4. 基于Aspectj AOP配置事务。

Spring的事务管理是通过AOP代理实现的,对被代理对象的每个方法进行拦截,在方法执行前启动事务,在方法执行完成后根据是否有异常及异常的类型进行提交或回滚。

原理:当在某个类或者方法上使用@Transactional注解后,spring会基于该类生成一个代理对象,并将这个代理对象作为bean。当调用这个代理对象的方法时,如果有事务处理,则会先关闭事务的自动功能,然后执行方法的具体业务逻辑,如果业务逻辑没有异常,那么代理逻辑就会直接提交,如果出现任何异常,那么直接进行回滚操作。当然我们也可以控制对哪些异常进行回滚操作。

2、失效的场景

  • Spring事务注解"@Transactional"只能放在public修饰的方法上才能生效,放在其他非pubic方法上不会报错,但是不起作用;

  • 使用了try...catch..语句块对异常进行了捕获,而catch语句块没有throw new RuntimeExecption异常,事务也不会回滚;

  • 在业务代码中如果**抛出非RuntimeException异常,且注解为"@Transactional"**事务不回滚(可以通过修改注解为"@Transactional(rollbackFor=Exception.class)" 解决);

  • 数据库不支持事务的情况:如使用mysql且引擎是MyISAM,则事务会不起作用,原因是MyISAM不支持事务,可以改成InnoDB引擎;

  • "@Transactional"用在方法上,由该方法所属类的其他方法调用该方法;

    在类A里面有方法a 和方法b, 然后方法b上面用 @Transactional加了方法级别的事务,在方法a里面 调用了方法b, 方法b里面的事务不会生效。原因是在同一个类之中,方法互相调用,切面无效 ,而不仅仅是事务。这里事务之所以无效,是因为spring的事务是通过aop实现的。

  • 如果采用spring+spring mvc,则context:component-scan重复扫描问题可能会引起事务失败;

  • @Transactional 注解开启配置,必须放到listener里加载,如果放到DispatcherServlet的配置里,事务也不起作用;

  • Spring团队建议在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。在接口上使用 @Transactional 注解,只能当你设置了基于接口的代理时它才生效。因为注解是 不能继承 的,这就意味着如果正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装。


五、SpringBoot

1、配置&&配置文件

(1)读取配置的几种方式

可以通过"@PropertySource,@Value,@Environment, @ConfigurationProperties"注解绑定变量。

(2)核心配置文件

bootstrap (.yml 或者 .properties):boostrap 由父 ApplicationContext 加载的,比applicaton 优先加载,配置在应用程序上下文的引导阶段生效。一般来说我们在 Spring Cloud 配置就会使用这个文件。且 boostrap 里面的属性不能被覆盖;

application (.yml 或者 .properties): 由ApplicatonContext 加载,用于 spring boot 项目的自动化配置。

bootstrap.properties 和application.properties 有何区别 ?

单纯做 Spring Boot 开发,可能不太容易遇到 bootstrap.properties 配置文件,但是在结合Spring Cloud 时,这个配置就会经常遇到了,特别是在需要加载一些远程配置文件的时侯。

(3)JavaConfig

【Spring JavaConfig】是 Spring 社区的产品,Spring 3.0引入,它提供了配置 Spring IOC 容器的

纯Java 方法。因此它有助于避免使用 XML 配置。使用 JavaConfig 的优点在于:

  • 面向对象的配置。由于配置被定义为 JavaConfig 中的类,因此用户可以充分利用 Java 中的面向对象功能。一个配置类可以继承另一个,重写它的@Bean 方法等。
  • 减少或消除 XML 配置。基于依赖注入原则的外化配置的好处已被证明。但是,许多开发人员不希望在 XML 和 Java 之间来回切换。JavaConfig 为开发人员提供了一种纯 Java 方法来配置与 XML 配置概念相似的 Spring 容器。从技术角度来讲,只使用 JavaConfig 配置类来配置容器是可行的,但实际上很多人认为将JavaConfig 与 XML 混合匹配是理想的。
  • 类型安全和重构友好。JavaConfig 提供了一种类型安全的方法来配置 Spring容器。由于 Java5.0 对泛型的支持,现在可以按类型而不是按名称检索 bean,不需要任何强制转换或基于字符串的查找。

【常用的Java config】:

  • @Configuration:在类上打上写下此注解,表示这个类是配置类;
  • @ComponentScan:在配置类上添加 @ComponentScan 注解。该注解默认会扫描该类所在的包下所有的配置类,相当于之前的 <context:component-scan >;
  • @Bean:bean的注入:相当于以前的< bean id="objectMapper"class="org.codehaus.jackson.map.ObjectMapper" />;
  • @EnableWebMvc:相当于xml的 <mvc:annotation-driven >;
  • @ImportResource: 相当于xml的 < import resource="applicationContextcache.xml">。

(4) Spring Profiles

在项目的开发中,有些配置文件在开发、测试或者生产等不同环境中可能是不同的。例如,数据库连接、redis的配置等等。那我们如何在不同环境中自动实现配置的切换呢?Spring给我们提供了profiles机制给我们提供的就是来回切换配置文件的功能。Spring Profiles 允许用户根据配置文件(dev,test,prod 等)来注册 bean。因此,当应用程序在开发中运行时,只有某些 bean 可以加载,而在 PRODUCTION中,某些其他 bean 可以加载。

2、自动配置原理

主要是Spring Boot的启动类上的核心注解@SpringBootApplication注解主配置类,有了这个主配置类启动时就会为SpringBoot开启一个@EnableAutoConfiguration注解自动配置功能。有了这个EnableAutoConfiguration的话就会:

  • 从配置文件META_INF/Spring.factories加载可能用到的自动配置类;
  • 去重,并将exclude和excludeName属性携带的类排除;
  • 过滤,将满足条件(@Conditional)的自动配置类返回。

3、常见注解

(1)Spring注解

注解 对象作用 说明
@Component、@Controller、@Service、@Repository 用于实例化Bean
@Autowired 字段 用于根据类型依赖注入
@Qualifier 字段 结合@Autowired一起使用,根据名称进行依赖注入
@Scope 标注Bean的作用范围
@Configuration 指定当前类是一个配置类,创建容器时会从该类上加载注解
@ComponentScan 指定Spring初始化容器时需要扫描的包
@Bean 方法 将该方法的返回值存储到Spring容器中
@Import 使用@Import导入的类,会被加载到IOC容器中
@Aspect、@Before、@After、@Around、@Pointcut 类:@Aspect 方法:其他 切面编程AOP使用。 1、@Aspect,声明当前类为创建切面的类; 2、@Before、@After、@Around,用于invoke() 方法;分别对应前置、后置、环绕通知; 3、@PointCut,定义AOP生效的方法位置

(2)SpringMVC 注解

注解 对象作用 说明
@RequestMapping 类、方法 用在类上表示所有的方法都以此路径作为父路径
@RequestBody 方法参数 实现接收http请求的json数据,将json转为Java对象
@RequestParam 方法参数 指定请求参数的名称
@PathVariable 方法参数 从请求路径中获取请求参数(/user/{id}),传给方法的形参
@ResponseBody 方法参数 将Controller方法返回的对象转为json对象返回给客户端
@RequestHeader 方法 获取指定的请求头数据
@RestController @Controller + @RequestBody

(3)SpringBoot 注解

@SpringBootApplication 注解包括了@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan

注解 对象作用 说明
@SpringBootConfiguration 组合了@Configuration注解,实现配置文件功能
@EnableAutoConfiguration 打开自动配置的功能,也可以关闭某个自动配置的选项
@ComponentScan Spring组件扫描

参考内容:

B站视频课程:https://www.bilibili.com/video/BV1yT411H7YK

掘金:说一下 Spring 中 Bean 的生命周期

spring是如何解决循环依赖的?

书籍:《Spring实战》

CSDN:Spring事务失效的几种原因_spring事物失效-CSDN博客

相关推荐
quokka56几秒前
Springboot 整合 logback 日志框架
java·spring boot·logback
计算机学姐6 分钟前
基于SpringBoot+Vue的在线投票系统
java·vue.js·spring boot·后端·学习·intellij-idea·mybatis
救救孩子把22 分钟前
深入理解 Java 对象的内存布局
java
落落落sss24 分钟前
MybatisPlus
android·java·开发语言·spring·tomcat·rabbitmq·mybatis
万物皆字节30 分钟前
maven指定模块快速打包idea插件Quick Maven Package
java
夜雨翦春韭37 分钟前
【代码随想录Day30】贪心算法Part04
java·数据结构·算法·leetcode·贪心算法
简单.is.good42 分钟前
【测试】接口测试与接口自动化
开发语言·python
我行我素,向往自由43 分钟前
速成java记录(上)
java·速成
一直学习永不止步1 小时前
LeetCode题练习与总结:H 指数--274
java·数据结构·算法·leetcode·数组·排序·计数排序
邵泽明1 小时前
面试知识储备-多线程
java·面试·职场和发展