一、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、生命周期
【流程概述】
- 通过BeanDefinition获取bean的定义信息;
- 调用构造函数实例化bean;
- bean的依赖注入;
- 处理Aware接口(BeanNameAware、BeanFactoryAware、ApplicationContextAware);
- Bean的后置处理器BeanPostProcessor-前置;
- 初始化方法(InitializingBean、init-method);
- Bean的后置处理器BeanPostProcessor-后置;(完成此步后,Bean对象创建完成)
- 销毁bean。
【具体流程】
- Spring 对 bean实例化;
- Spring 将值和 bean 的引入注入 到 bean 对应的属性中;
- 如果 bean 实现了 BeanNameAware 接口,Spring 将 bean 的 ID 传递给 setBeanName() 方法;
- 如果 bean 实现了 BeanFactoryAware 接口,Spring 将调用 setBeanFactory() 方法,将BeanFactory 容器实例传入;
- 如果 bean 实现了 ApplicationContextAware 接口,Spring 将调用 setApplicationContext() 方法,将 bean 所在的应用上下文传递进来;
- 如果 bean 实现了 ++BeanPostProcessor++ 接口,Spring 将调用它们的 postProcessBeforeInitialization( ) 方法;
- 如果 bean 实现了 InitializingBean 接口,Spring 将调用它们的 afterPropertiesSet( ) 方法;
- 如果 bean 实现了 ++BeanPostProcessor++ 接口,Spring 将调用它们的 postProcessAfterInitialization( ) 方法;
- 此时,bean 已经准备就绪,可以被程序使用了,它们将一直驻留在应用上下文中,直到该应用上下文被销毁;
- 如果 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)
【具体实现】
-
++编程式事务++管理需要在代码中调用beginTransaction()、commit()、rollback()等事务管理相关的方法(对基于 POJO 的应用来说是唯一选择);
-
基于 TransactionProxyFactoryBean的声明式事务管理;
-
基于 @Transactional 的声明式事务管理;
-
基于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实战》