在我们学习SpringIOC和DI阶段的时候,我们学习了Spring是如何帮助我们管理对象的.
@Data
@Getter
public class Dog {
@Setter
public Integer age;
@Setter
public String name;
}
之后在DogBeanConfig类中创建一个对象,之后把对象放入IoC容器中.
@Component
public class DogBeanConfig {
@Bean
public Dog dog(){
Dog dog = new Dog();
dog.setAge(2);
dog.setName("哈士奇");
return dog;
}
}
之后从上下文中两次拿到dog对象.
@Test
void dogTest(){
Dog dog1 = applicationContext.getBean(Dog.class);
System.out.println(dog1);
Dog dog2 = applicationContext.getBean(Dog.class);
System.out.println(dog2);
}
@Test
@Test
void dogTest(){
Dog dog1 = applicationContext.getBean(Dog.class);
dog1.setName("金毛");
System.out.println(dog1);
System.out.println(dog1.getName());
Dog dog2 = applicationContext.getBean(Dog.class);
System.out.println(dog2);
System.out.println(dog2.getName());
}
观察运行结果,我们发现修改之后的Bean对象拿到名字的时候是修改之后的名字.
那么能不能将Bean对象设置为非单例的呢(每次获取Bean对象都是一个新对象)?我们这时候就要提到Bean的作用域了.
在Spring中支持6种作用域,其中后4种在SpringMVC环境中才会生效.
@Scope(翻译:范围)注解来指定.我们来定义几个作用域不同的Bean.@Component
public class DogBeanConfig {
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public Dog singleDog(){
Dog dog = new Dog();
dog.setAge(1);
dog.setName("萨摩耶");
return dog;
}
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Dog prototypeDog(){
Dog dog = new Dog();
dog.setAge(3);
dog.setName("柴犬");
return dog;
}
@Bean
@RequestScope
public Dog requestDog(){
Dog dog = new Dog();
dog.setAge(2);
dog.setName("哈士奇");
return dog;
}
@Bean
@SessionScope
public Dog sessionDog(){
Dog dog = new Dog();
dog.setAge(4);
dog.setName("柯基");
return dog;
}
}
测试不同的Bean获取的对象是否一样.
@RestController
public class DogController {
@Autowired
private Dog singleDog;
@Autowired
private Dog prototypeDog;
@Autowired
private Dog requestDog;
@Autowired
private Dog sessionDog;
@Autowired
private ApplicationContext applicationContext;
@RequestMapping("/single")
public String single(){
Dog contextDog = (Dog)applicationContext.getBean("singleDog");
return "dog:"+singleDog.toString()+",contextDog:"+contextDog;
}
@RequestMapping("/prototype")
public String prototype(){
Dog contextDog = (Dog)applicationContext.getBean("prototypeDog");
return "dog:"+prototypeDog.toString()+",contextDog:"+contextDog;
}
@RequestMapping("/request")
public String request(){
Dog contextDog = (Dog)applicationContext.getBean("requestDog");
return "dog:"+requestDog.toString()+",contextDog:"+contextDog.toString();
}
@RequestMapping("/session")
public String session(){
Dog contextDog = (Dog)applicationContext.getBean("sessionDog");
return "dog:"+sessionDog.toString()+",contextDog:"+contextDog.toString();
}
@RequestMapping("/application")
public String application(){
Dog contextDog = (Dog)applicationContext.getBean("applicationDog");
return "dog:"+applicationDog.toString()+",contextDog:"+contextDog.toString();
}
}
我们每次请求有两个对象,一个是通过@Autowired注入的Bean对象,一个是通过上下文获取的Bean对象.
单例作用域:
每一次访问都是同一个对象,并且@Autowired 和applicationContext.getBean() 也是同⼀个对象.
生命周期指的是一个对象从诞生到销毁的过程.
Bean的生命周期分为一下5个部分:
@Autowired) —> Setter方法注入BeanNameAware,BeanFactoryAware,ApplicationContextAware的接口方法和执行使用注解@PostConstruct 修饰的初始化方法.DisposableBean 接口方法和执行使用注解@PreDestroy修饰的销毁容器的方法.这个就好比我们想买一套房子:
- 需要先买房(实例化,分配内存空间)
- 装修,把毛坯房变为精装房(执行属性赋值,执行@Autowired注入赋值)
- 购买家电.(执行初始化方法,包括接口实现和注释修饰)
- 拎包入住(使用Bean)
- 寿命到期,拆迁(销毁Bean)
@Component
public class BeanLifeComponent implements BeanNameAware {
private Dog dog;
public BeanLifeComponent(){
System.out.println("实例化Bean...");
}
@Autowired
public void setDog(Dog dog1) {//set方法注入法
this.dog = dog1;
System.out.println("属性赋值Bean");
}
@Override
public void setBeanName(String name) {
System.out.println("执行了BeanNameAware接口的通知方法(初始化Bean)");
}
@PostConstruct
public void init(){
System.out.println("执行了PostConstruct注解修饰的方法(初始化Bean)");
}
public void use(){
System.out.println("使用Bean");
}
@PreDestroy
public void destroy(){
System.out.println("销毁Bean");
}
}
测试代码:
@Test
void beanLifeComponent(){
BeanLifeComponent beanLifeComponent = applicationContext.getBean(BeanLifeComponent.class);
beanLifeComponent.use();
}
测试结果如下:
即使类中的这些方法变换了顺序,也不会改变这些这些内容输出的顺序.
SpringBoot的自动配置就是当Spring容器启动后,⼀些配置类,bean对象等就自动存入到了IoC容器中,不需要我们手动去声明,从而简化了开发,省去了繁琐的配置操作.
com.jrj.springprincipledemo软件包之外新创建一个软件包spring_autoconfig,之后在spring_autoconfig引入第三方代码TestConfig.@Component
public class TestConfig {
public void print(){
System.out.println("打印...");
}
}
com.jrj.springprincipledemo目录下的运行类中通过上下文来获取这个Bean对象,我们发现是获取不到的.@SpringBootApplication
public class SpringPrincipleDemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringPrincipleDemoApplication.class, args);
TestConfig testConfig = context.getBean(TestConfig.class);
System.out.println(testConfig);
}
}
我们需要指定路径或者引入文件,告诉Spring,Spring扫描到.
常见的解决方案有两种:
@ComponentScan注解添加扫描路径.@Import注解导入@Import导入形式主要有以下两种:
@SpringBootApplication
@Import(TestConfig.class)
public class SpringPrincipleDemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringPrincipleDemoApplication.class, args);
TestConfig testConfig = context.getBean(TestConfig.class);
System.out.println(testConfig);
}
}
我们看到了启动类成功拿到了IoC容器中的Bean对象.
也可以使用大括号导入多个类:@Import({TestConfig1.class,TestConfig2.class})
- 导入ImportSelector接口实现类.
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"spring_autoconfig.TestConfig"};
}
}
需要注意的一点是,给返回值导入类的时候,需要导入类的全限定名称.
在启动类的上面直接导入实现ImportSelector的类.
@SpringBootApplication
@Import(MyImportSelector.class)
public class SpringPrincipleDemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringPrincipleDemoApplication.class, args);
TestConfig testConfig = context.getBean(TestConfig.class);
System.out.println(testConfig);
}
}
依然可以获取到对象:
问题:但是他们都有⼀个明显的问题,就是使用者需要知道第三方依赖中有哪些Bean对象或配置类.依赖中有哪些Bean,使用的时候需要配置哪些Bean,只有第三方最清楚,能否让第三方来做这件事情呢?
@EnableXxxx开头,其中封装的就是@Import注解.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(TestConfig.class)
public @interface EnableTestConfig {
}
使用@Target标签定义注解在哪里标记,使用@Retention注解定义注解的生命周期.之后使用@Import注解导入第三方的类对象.
2. 在启动类上提供第三方注解
@SpringBootApplication
@EnableTestConfig
public class SpringPrincipleDemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringPrincipleDemoApplication.class, args);
TestConfig testConfig = context.getBean(TestConfig.class);
System.out.println(testConfig);
}
}
Bea对象依然可以拿到:
那么Spring究竟是如何实现自动导入的呢?接下来我们就来查看Spring的源码,我们从@SpringBootApplication 开始看起.
这个直接也是Spring实现自动配置的核心
@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) })
public @interface SpringBootApplication {
....
}
@SpringBootAppliaction是一个组合注解,注解中包含了:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
里面其实就是一个@Configuration,只不过就是封装了一层而已.
3. @EnableAutoConfiguratio
这是Spring自动装配的核心机制,下面详细解释.
4. @ComponentScan
excludeFilter是自定义过滤器,通常用于排除一些类,注解等.
我们来观察@EnableAutoConfiguration注解的实现:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
/**
* Environment property that can be used to override when auto-configuration is
* enabled.
*/
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
Class<?>[] exclude() default {};
/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
String[] excludeName() default {};
}
这个注解主要包含两部分:
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered{
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
selectImports方法有调用了getAutoConfigurationEntry方法,获取可以自动配置的配置类信息集合:
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
其中getCandidateConfigurations方法获取在配置文件中所有自动配置类的集合.
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader())
.getCandidates();
Assert.notEmpty(configurations,
"No auto configuration classes found in "
+ "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
获取的是所有基于META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports配置文件和META-INF/spring.factories的配置文件.里面包含了很多第三方依赖的配置文件.
[注意]
@Conditional注解有关系,这个注解是Spring底层的一个注解,就是根据不同的条件来进行自己不同条件的判断,如果满足指定的条件,配置才会生效.@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}
这个注解主要是导入了配置文件AutoConfigurationPackages.Registrar.class
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
Registrar中实现了ImportBeanDefinitionRegistrar类,就可以被注解@Import导入到Spring容器中.其中PackageImports(metadata).getPackageNames().toArray(new String[0]))就是当前启动所在的包名.
所以:@AutoConfigurationPackage就是把启动类所在的包下面所有的组件全部都扫描注册操Spring容器中.
SpringBoot自动装配原理大致如下:
当Spring项目启动的时候,就会自动把这些配置文件中的配置类通过@Import注解全部加载到SpringIoC容器中.
这个问题其实可以换一种问法,Spring是如何解决循环依赖的?答案就是Spring使用了三级缓存.
简单来说,就是A对象依赖B对象,B对象又依赖A对象,代码如下:
@Component
public class A {
@Autowired
public B b;
}
@Component
public class B {
@Autowired
public A a;
}
我们运行之后会发现,程序出现了报错,这是由于在早些版本的时候,Spring会默认自动处理循环依赖,但是在新版本之后,Spring默认不会处理循环依赖.
Spring容器保存Bean的方式是采用缓存的方式,使用Map的结构,其中key为Bean的名称,value为Bean对象,使用的时候直接从缓存中获取.假如A和B相互依赖:
如此一来就会出现死循环.
Spring使用的是三级缓存的方式来解决循环依赖的问题.首先我们需要看一看什么叫做三级缓存.
三级缓存解决A,B循环依赖的过程如下:
Spring框架在处理循环依赖的时候存在一些,一下三种情况循环依赖不可以被默认解决.
因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- baijiahaobaidu.com 版权所有 湘ICP备2023023988号-9
违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务