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博客

相关推荐
万琛几秒前
【java-Neo4j 5开发入门篇】-最新Java开发Neo4j
java·neo4j
算家云2 分钟前
快速识别模型:simple_ocr,部署教程
开发语言·人工智能·python·ocr·数字识别·检测模型·英文符号识别
Thomas_Cai14 分钟前
Python后端flask框架接收zip压缩包方法
开发语言·python·flask
霍先生的虚拟宇宙网络16 分钟前
webp 网页如何录屏?
开发语言·前端·javascript
温吞-ing18 分钟前
第十章JavaScript的应用
开发语言·javascript·ecmascript
Bald Baby19 分钟前
JWT的使用
java·笔记·学习·servlet
魔道不误砍柴功24 分钟前
实际开发中的协变与逆变案例:数据处理流水线
java·开发语言
鲤籽鲲32 分钟前
C# MethodTimer.Fody 使用详解
开发语言·c#·mfc
亚图跨际36 分钟前
Python和R荧光分光光度法
开发语言·python·r语言·荧光分光光度法