Spring原理分为:
Bean的作用域和生命周期
了解SpringBoot自动配置流程
Bean的作用域
1.概念
Spring是通过DI依赖注入和AOP来管理对象,具体就是:
1)通过@Controller,@Service,@Repository,@Component,@Configuration,@Bean来声明对象。
2)通过ApplicationContext或者BeanFactory来获取对象。
3)通过@Autowired或者set方法来注入依赖对象。
下面是一个以“Dog”类为例子来说明Spring是如何管理对象的:
@Data
public class Dog {
private String name;
}
通过@Bean来将bean实例存储到Spring容器中
@Component
public class DogBeanConfig {
@Bean
public Dog dog() {
Dog dog = new Dog();
dog.setName("大黄");
return dog;
}
}
从Spring容器中获取Bean对象
@SpringBootApplication
public class Application {
public static void main(String[] args) {
//获取Spring的上下文
ApplicationContext context = SpringApplication.run(Application.class, args);
//从上下文中获取对象
Dog dog = context.getBean(Dog.class);
System.out.println(dog);
}
}
也可以通过在代码中直接注入ApplicationContext的方法来获取Spring容器
@SpringBootTest
class ApplicationTests {
@Autowired
private ApplicationContext context;
@Test
void contextLoads() {
DogBeanConfig bean = context.getBean(DogBeanConfig.class);
System.out.println(bean);
DogBeanConfig bean2 = context.getBean(DogBeanConfig.class);
System.out.println(bean2);
}
}
而且运行后可以发现输出的Bean的对象地址是一样的:
这也就是单例模式的实际使用场景,
默认情况下,Spring容器的bean都是单例的,这种行为方式,我们就称之为Bean的作用域。
Bean的作用域是指Bean在Spring框架中的某种行为方式。
2.Bean的作用域种类
在Sping中支持6种作用域,后四种在SpringMVC环境中才生效
1.singleton:单例作用域。
2.prototype:原型作用域(多例作用域)
3.request:请求作用域。
4.session:会话作用域。
5.Applicantion:全局作用域。
6.websocket:HTTPWebSocket作用域。
作用域类型 | 生效范围 | 生命周期 | 适用场景 | |
---|---|---|---|---|
singleton | 整个应用上下文 | 在应用启动时创建,仅创建一个实例,所有请求共享该实例。 | 无状态的服务、工具类等。 | |
prototype | 每次获取 Bean 时 | 每次请求都会创建一个新的实例,不同请求使用不同的实例。 | 有状态的 Bean、需要保持独立状态的组件。 | |
request | 单个 HTTP 请求 | 每个 HTTP 请求创建一个实例,请求处理完成后销毁。 | 保存单个请求上下文信息的组件,如请求参数处理器。 | |
session | 单个用户会话 | 每个用户会话创建一个实例,会话过期后销毁。 | 保存用户会话状态的组件,如用户会话信息管理。 | |
application | 整个 ServletContext(全局应用) | 与 ServletContext 生命周期绑定,应用启动时创建,应用停止时销毁。 | 全局数据共享组件,如应用统计信息。 | |
websocket | 单个 WebSocket 会话 | 每个 WebSocket 连接创建一个实例,连接关闭后销毁。 | WebSocket 通信处理组件,如消息处理器。 |
Spring中默认是singleton,如果要换为其他的,使用@Scope注解,例如:
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Dog dog2() {
Dog dog = new Dog();
dog.setName("黄黄");
return dog;
}
也可以使用@RequestScope等注解,其中@RequesScope对应request,@SessionScope=session,@ApplicationScope对应于application
各个作用域获取Bean的情况:
代码以多例作用域为例,其他的类似:
@Qualifier("prototypeDog")
@Autowired
private Dog prototypeDog;
@Autowired
ApplicationContext applicationContext;
@RequestMapping("/protype")
public String protype() {
Dog contextDog = (Dog) applicationContext.getBean("prototypeDog");
return "dog"+prototypeDog.toString()+"\n"+contextDog.toString();
}
1.单例作用域:多次访问,获取的Bean都是同一个对象,即使刷新浏览器。
2.多例作用域:每次获取的对象都不一样,但是注入的对象由于在Spring启动时就已经注入了,所以多次请求也不会发生变化。
3.请求作用域:在一次请求中,@Autowired和getBean相同,但是每次请求都会重新创建对象。
4.会话作用域:在一个session中,多次请求获取的都是一个对象,换一个浏览器,发现会重新创建对象。
5.Application作用域:在一个应用中,多次访问获取的都是一个对象。
Bean的生命周期
生命周期指的是一个对象从诞生到销毁的整个生命周期。
Bean的生命周期分为以下5个部分:
1.实例化(为Bean分配内存空间)对应构造方法。
2.属性赋值(Bean注入和装配,比如@Autowired)
3.初始化
a.执行各种通知,如BeanNameAware,BeanFactoryAware,ApplicationContextAware的接口方法。
b.执行初始化方法
xml定义init-method
使用注解的方式@PostConstruct
执行初始化后置方法BeanPostProcessor。
4.使用Bean
5.销毁Bean
销毁容器的各种方法,如@PreDestory,DispossableBean接口方法,destroy-method。
以下是执行流程:
代码演示:
@Component
public class BeanLifeComponent implements BeanNameAware {
private UserComponent userComponent;
public BeanLifeComponent() {
System.out.println("执行构造函数");
}
@Autowired
public void setUserComponent(UserComponent userComponent) {
System.out.println("设置属性userComponent");
this.userComponent = userComponent;
}
@Override
public void setBeanName(String name) {
System.out.println("执行setBeanName方法"+name);
}
//初始化
@PostConstruct
public void postConstruct() {
System.out.println("执行postConstruct方法");
}
public void use() {
System.out.println("执行力use方法");
}
//销毁前执行方法
@PreDestroy
public void preDestroy() {
System.out.println("执行:preDestroy");
}
}
执行结果:
SpringBoot自动配置
SpringBoot的自动配置就是当Spring容器启动后,bean等对象就自动存到了IoC容器中,不需要去手动配置。
SpringBoot自动配置就是指SpringBoot是如何将依赖jar包中的配置类以及Bean加载到SpingIoC容器中的。
1.Spring加载Bean
SpringBoot中比较常见的就是第三方依赖给我们提供一个注解,这个注解一般都是以@Enablexxxx开头的注解,注解中封装的就是@Import注解。
2.SpringBoot原理分析
源码阅读:
以@SpringBootApplication为例:
首先看源码上面的注解:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
@SpringBootApplication是一个组合注解,注解中包含了:
1.元注解
@Target 描述注解的使用范围。
@Retention 描述注解保留的时间范围。
@Documented 描述在使用 javadoc 工具为类生成帮助文档时是否要保留其注解信息。
@Inherited 使被它修饰的注解具有继承性。
2.@SpringBootConfiguration
里面就是@Configuration,标注当前类为注解类,其实只是做了一层封装改了个名字而已。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
3.@Indexed注解,一个用来加速应用启动的注解。
4.@EnableAutoConfiguration:开启自动配置,Spring自动配置的核心注解,下面会仔细说明。
5.@ComponentScan:包扫描。
可以通过basePackageClasses或basePackages来定义要扫描的特定包,如果没有定义特定的包,将从声明该注解的类的包开始扫描,这也就是为什么SpringBoot项目的声明的注解类必须要遭启动类的目录下。
excludeFilters自定义过滤器,通常用于排除一些类,注解等。
3.@EnableAutoConfiguration详解
看下@EnableAutoConfiguration注解的实现:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
这个注解包含两部分:
1.@Import({AutoConfigurationImportSelector.class})
使用@Import注解,导入了实现ImportSelector接口的实现类
源码中的一段代码:
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
其中“AutoConfigurationEntryautoConfigurationEntry=this.getAutoConfigurationEntry(annotationMetadata);”
就是获取自动配置的配置类信息。
点进getAutoConfigurationEntry方法:
其中有一个“getCandidateConfigurations”方法:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
ImportCandidates importCandidates = ImportCandidates.load(this.autoConfigurationAnnotation, this.getBeanClassLoader());
List<String> configurations = importCandidates.getCandidates();
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring/" + this.autoConfigurationAnnotation.getName() + ".imports. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
getCandidateConfigurations方法的作用:获取所以基于META——INF/Spring/org.Springframework.boot.autoconfigure.AutoConfiguration.imports文件,META-INF/Spring.factories文件中配置类的集合。
在引入的起步依赖中,通常都有包含以上两个文件。
这里面包含了很多第三方依赖的配置文件:
注意的是,在加载自动配置类的时候,并不是将所有的配置全部加载进来,而是通过@Conditional等注解的判断进行动态加载。
@Conditional是Spring底层注解,意思就是根据不同的条件,来进行自己不同的条件判断,如果满足指定的条件,那么配置类里面的配置才会生效。
4.@AutoConfigurationPackage
源码如下:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({AutoConfigurationPackages.Registrar.class})
public @interface AutoConfigurationPackage {
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}
这个注解主要是导入一个配置文件“AutoConfigurationPackages.Registrar.class”
查看Registrar的源码:
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
Registrar() {
}
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (String[])(new PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
Registrat实现了ImportBeanDefinitionRegistrar类,这时就可以被@Import导入到spring容器中。
(String[])(new PackageImports(metadata)).getPackageNames().toArray(new String[0])):启动类所在的包名。
所以:@AutoConfigurationPackage就是将启动类所在的包下面所有的组件都扫描注册到spring容器中。
SpringBoot配置的大致流程如下: