Spring源码分析-2
参考文章:https://blog.csdn.net/weixin_48033662/article/details/120000961
核心注解
注解 | 功能 |
---|---|
@Bean | 容器中注册组件 |
@Primary | 同类组件如果有多个,标注主组件 |
@DependsOn | 组件之间声明依赖关系 |
@Lazy | 组件懒加载(最后使用的时候才创建) |
@Scope | 声明组件的作用范围(SCOPE_PROTOTYPE,SCOPE_SINGLETON) |
@Configuration | 声明这是一个配置类,替换以前配置文件 |
@Component | @Controller、@Service、@Repository |
@Indexed | 加速注解,所有标注了 @Indexed 的组件,直接会启动快速加载 |
@Order | 数字越小优先级越高,越先工作 |
@ComponentScan | 包扫描 |
@Conditional | 条件注入 |
@Import | 导入第三方jar包中的组件,或定制批量导入组件逻辑 |
@ImportResource | 导入以前的xml配置文件,让其生效 |
@Profile | 基于多环境激活 |
@PropertySource | 外部properties配置文件和JavaBean进行绑定.结合ConfigurationProperties |
@PropertySources | @PropertySource组合注解 |
@Autowired | 自动装配 |
@Qualifier | 精确指定 |
@Value | 取值、计算机环境变量、JVM系统。xxxx。@Value(“${xx}”) |
@Lookup | 单例组件依赖非单例组件,非单例组件获取需要使用方法 |
XML和注解
1 | public static void main(String[] args) { |
@Bean
注册bean
想ioc中主动注入bean
原始xml配置:
1 | <bean id="person" class="top.lvxiaoyi.bean.Person"> |
使用注解方式向Spring的IOC容器中注入JavaBean时
如果没有在@Bean注解中明确指定bean的名称,那么就会使用当前方法的名称来作为bean的名称;
1
2
3
4
5
6
public Person person01() {
return new Person("lvxiaoyi", 20);
}
// 获取benaName
// applicationContext.getBeanNamesForType(Person.class);这个时候的beanName为person01
如果在@Bean注解中明确指定了bean的名称,那么就会使用@Bean注解中指定的名称来作为bean的名称。
1
2
3
4
public Person person01() {
return new Person("lvxiaoyi", 20);
}这个时候的beanName为person
自定义初始化和销毁方法
使用
在Spring中,我们可以自己来指定bean的初始化和销毁的方法。我们指定了bean的初始化和销毁方法之后,当容器在bean进行到当前生命周期的阶段时,会自动调用我们自定义的初始化和销毁方法。
xml:
1 | <bean id="person" class="top.lvxiaoyi.bean.Person" init-method="init" destroy-method="destroy"> |
==注意:在我们自己写的Person类中,需要存在init()方法和destroy()方法。而且Spring中还规定,这里的init()方法和destroy()方法必须是无参方法,但可以抛出异常。==
1 | public void init() { |
bean注解:
1 | @Bean(initMethod="init", destroyMethod="destroy") |
执行过程
bean的销毁方法是在容器关闭的时候被调用的。
1 |
|
- bean对象的初始化方法调用的时机:对象创建完成,如果对象中存在一些属性,并且这些属性也都赋好值之后,那么就会调用bean的初始化方法。对于单实例bean来说,在Spring容器创建完成后,Spring容器会自动调用bean的初始化方法;对于多实例bean来说,在每次获取bean对象的时候,调用bean的初始化方法。
- bean对象的销毁方法调用的时机:对于单实例bean来说,在容器关闭的时候,会调用bean的销毁方法;对于多实例bean来说,Spring容器不会管理这个bean,也就不会自动调用这个bean的销毁方法了。可以(必须)手动调用多实例bean的销毁方法。
使用场景
一个典型的使用场景就是对于数据源的管理。例如,在配置数据源时,在初始化的时候,会对很多的数据源的属性进行赋值操作;在销毁的时候,我们需要对数据源的连接等信息进行关闭和清理。这个时候,我们就可以在自定义的初始化和销毁方法中来做这些事情了!
@ComponentScan
基本使用
指定包扫描路径,指定路径后该路径下的包及其子包都会被扫描到,且标注了@Repository、@Service、@Controller、@Component注解的类会自动注入到Spring容器中。
原始xml配置:
1 | <context:component-scan base-package="top.lvxiaoyi"></context:component-scan> |
@ComponentScan配置:
在配置上或启动类上添加
1 | @ComponentScan(value="top.lvxiaoyi") |
指定包
1 | Filter[] includeFilters() default {}; |
这个一个数组,而且参数名为includeFilters
使用:
==注意:当使用includeFilters()方法指定只包含哪些组件时,需要禁用掉默认的过滤规则。==
1 |
xml:
1 | <context:component-scan base-package="com.meimeixia" use-default-filters="false"></context:component-scan> |
自定义过滤规则:MyTypeFilter
1 | public class MyTypeFilter implements TypeFilter { |
具体Filter的使用,可以查看api文档,或者看源码
1 | public Filter { |
小知识
1 |
|
@Repeatable注解是java8提供的注解,标志着这个注解可以在重复使用在你指定的类型上
排除包
1 |
|
@Component
凡是在指定的包或其子包中的类上标注了@Repository、@Service、@Controller、@Component注解的类都会被扫描到,并将这个类注入到Spring容器中。
@Scope
@Scope注解能够设置组件的作用域
1 | ConfigurableBeanFactory#SCOPE_PROTOTYPE |
xml:
1 | <bean id="person" class="top.lvxiaoyi.bean.Person" scope="prototype" > |
1 |
|
@Lazy
组件懒加载(最后使用的时候才创建)
1 |
|
==懒加载,也称延时加载,仅针对单实例bean生效==。 单实例bean是在Spring容器启动的时候加载的,添加@Lazy注解后就会延迟加载,在Spring容器启动的时候并不会加载,而是在第一次使用此bean的时候才会加载,但当你多次获取bean的时候并不会重复加载,只是在第一次获取的时候才会加载,这不是延迟加载的特性,而是单实例bean的特性。
若bean是多实例,则Spring容器启动时,不会实例化bean,也不会将bean注册到IOC容器中,只是在以后每次从IOC容器中获取bean的时候,都会创建一个新的bean返回。
@Conditional
@Conditional注解可以按照一定的条件进行判断,满足条件向容器中注册bean,不满足条件就不向容器中注册bean。需要自定义匹配方法,这个方法需要实现Condition 接口
1 | public class LinuxCondition implements Condition { |
使用:
1 |
|
@Conditional与@Profile这俩注解的对比
Spring 3.0也有一些和@Conditional相似的注解,它们是Spring SPEL表达式和Spring Profiles注解,但是Spring 4.0之后的@Conditional注解要比@Profile注解更加高级。@Profile注解用来加载应用程序的环境,该注解仅限于根据预定义属性编写条件检查,而@Conditional注解则没有此限制。
Spring中的@Profile和@Conditional这俩注解都是用来检查If…then…else的语义。然而,Spring 4.0之后的@Conditional注解是@Profile注解的更新用法。
Spring 3.0中的@Profile仅用于编写基于Environment变量的条件检查。配置文件可用于基于环境加载应用程序配置。
Spring 4.0之后的@Conditional注解允许开发人员为条件检查定义用户定义的策略。此外,@Conditional注解还可以用于条件bean注册。
@Import
我们自己写的类,自然是可以通过包扫描+给组件标注注解(@Controller、@Servcie、@Repository、@Component)的形式将其注册到IOC容器中,但这种方式比较有局限性,局限于我们自己写的类,比方说我们自己写的类,我们当然能把以上这些注解标注上去了。
我们在项目中会经常引入一些第三方的类库,我们需要将这些第三方类库中的类注册到Spring容器中,此时,我们就可以使用@Bean和@Import注解将这些类快速的导入Spring容器中。
@Import注解的使用方式
@Import注解的三种用法主要包括:
直接填写class数组的方式
1
2
3
4
// @Import({Color.class, Red.class}) 可以多个
// @Import快速地导入组件,id默认是组件的全类名
public class MainConfig2ImportSelector接口的方式,即批量导入,这是重点
1
2
3
public class MainConfig21
2
3
4
5
6
7
8
9
10public class MyImportSelector implements ImportSelector {
// 返回值:就是要导入到容器中的组件的全类名
// AnnotationMetadata:当前标注@Import注解的类的所有注解信息,也就是说不仅能获取到@Import注解里面的信息,还能获取到其他注解的信息
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.lvxiaoyi.bean.Bule", "com.lvxiaoyi.bean.Yellow"};
}
}ImportBeanDefinitionRegistrar接口方式,即手工注册bean到容器中
Spring官方在动态注册bean时,大部分套路其实是使用ImportBeanDefinitionRegistrar接口。
所有实现了该接口的类都会被ConfigurationClassPostProcessor处理,ConfigurationClassPostProcessor实现了BeanFactoryPostProcessor接口,所以ImportBeanDefinitionRegistrar中动态注册的bean是优先于依赖其的bean初始化的,也能被aop、validator等机制处理。
ImportBeanDefinitionRegistrar需要配合@Configuration和@Import这俩注解,其中,@Configuration注解定义Java格式的Spring配置文件,@Import注解导入实现了ImportBeanDefinitionRegistrar接口的类。
1
2
3
public class MainConfig21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
/**
* AnnotationMetadata:当前类的注解信息
* BeanDefinitionRegistry:BeanDefinition注册类
*
* 我们可以通过调用BeanDefinitionRegistry接口中的registerBeanDefinition方法,手动注册所有需要添加到容器中的bean
*/
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 查看是否包含这个bean,上面@Conditional的时候提到过
boolean definition = registry.containsBeanDefinition("com.lvxiaoyi.bean.Red");
boolean definition2 = registry.containsBeanDefinition("com.lvxiaoyi.bean.Bule");
if (definition && definition2) {
// 指定bean的定义信息,包括bean的类型、作用域等等
// RootBeanDefinition是BeanDefinition接口的一个实现类
RootBeanDefinition beanDefinition = new RootBeanDefinition(RainBow.class); // bean的定义信息
// 注册一个bean,并且指定bean的名称
registry.registerBeanDefinition("rainBow", beanDefinition);
}
}
}
非注解
FactoryBean注册bean
一般情况下,Spring是通过反射机制利用bean的class属性指定实现类来实例化bean的。在某些情况下,实例化bean过程比较复杂,如果按照传统的方式,那么则需要在标签中提供大量的配置信息,配置方式的灵活性是受限的,这时采用编码的方式可以得到一个更加简单的方案。Spring为此提供了一个org.springframework.bean.factory.FactoryBean的工厂类接口,用户可以通过实现该接口定制实例化bean的逻辑。
FactoryBean接口对于Spring框架来说占有非常重要的地位,Spring自身就提供了70多个FactoryBean接口的实现。它们隐藏了实例化一些复杂bean的细节,给上层应用带来了便利。从Spring 3.0开始,FactoryBean开始支持泛型,即接口声明改为FactoryBean
1 | package org.springframework.beans.factory; |
1 | public class ColorFactoryBean implements FactoryBean<Color> { |
使用FactoryBean注解的时候,获取的name为类名(ColorFactoryBean),但是类型为你实际的注册bean的类型(top.lvxiaoyi.bean.Color)
因为注册bean的时候,是注册的ColorFactoryBean,而不是Color,那么我们如果想要拿到ColorFactoryBean类型的bean怎么获取?
只需要在获取工厂Bean本身时,在id前面加上&符号即可,例如&colorFactoryBean。
1 | Object bean4 = applicationContext.getBean("&colorFactoryBean"); |
为什么?
因为我们所有的bean都是通过BeanFactory生产的,我们去查看BeanFactory
1 | public interface BeanFactory { |
我们看到BeanFactory的第一个Filed就是这个”&”。
InitializingBean和DisposableBean初始化和销毁
InitializingBean接口
概述
为bean提供了属性初始化后的处理方法,它只包括afterPropertiesSet方法,凡是继承该接口的类,在bean的属性初始化后都会执行该方法。
1 | public interface InitializingBean { |
何时调用
我们定位到Spring中的org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory这个类里面的invokeInitMethods()方法中,来查看Spring加载bean的方法。
1 | protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd) |
分析上述代码后,我们可以初步得出如下信息:
Spring为bean提供了两种初始化的方式,实现InitializingBean接口(也就是要实现该接口中的afterPropertiesSet方法),或者在配置文件或@Bean注解中通过init-method来指定,==两种方式可以同时使用==,==同时使用先调用afterPropertiesSet方法,后执行init-method指定的方法。==
实现InitializingBean接口是直接调用afterPropertiesSet()方法,与通过反射调用init-method指定的方法相比,效率相对来说要高点。但是init-method方式消除了对Spring的依赖。
如果调用afterPropertiesSet方法时出错,那么就不会调用init-method指定的方法了。
DisposableBean接口
概述
实现org.springframework.beans.factory.DisposableBean接口的bean在销毁前,Spring将会调用DisposableBean接口的destroy()方法。也就是说我们可以实现DisposableBean这个接口来定义咱们这个销毁的逻辑。
1 | public interface DisposableBean { |
注意
多实例bean的生命周期不归Spring容器来管理,这里的DisposableBean接口中的方法是由Spring容器来调用的,所以如果一个多实例bean实现了DisposableBean接口是没有啥意义的,因为相应的方法根本不会被调用,当然了,在XML配置文件中指定了destroy方法,也是没有任何意义的。所以,==在多实例bean情况下,Spring是不会自动调用bean的销毁方法==。
使用
1 |
|
1 | @Configuration |
BeanPostProcessor
Spring容器中的每一个bean对象初始化前后,都会执行BeanPostProcessor接口的实现类中的这两个方法。
1 | public interface BeanPostProcessor { |
postProcessBeforeInitialization
也就是,postProcessBeforeInitialization
方法会在bean实例化和属性设置之后,自定义初始化方法之前被调用,而postProcessAfterInitialization
方法会在自定义初始化方法之后被调用。
当容器中存在多个BeanPostProcessor的实现类时,会按照它们在容器中注册的顺序执行。对于自定义的BeanPostProcessor实现类,还可以让其实现Ordered接口自定义排序。
postProcessAfterInitialization
后置处理器可用于bean对象初始化前后进行逻辑增强。Spring提供了BeanPostProcessor接口的很多实现类,例如AutowiredAnnotationBeanPostProcessor用于@Autowired注解的实现,AnnotationAwareAspectJAutoProxyCreator用于Spring AOP的动态代理等等。
以AnnotationAwareAspectJAutoProxyCreato我们都知道spring AOP的实现原理是动态代理,最终放入容器的是代理类的对象,而不是bean本身的对象,那么Spring是什么时候做到这一步的呢?就是在AnnotationAwareAspectJAutoProxyCreator后置处理器的postProcessAfterInitialization方法中,即bean对象初始化完成之后,后置处理器会判断该bean是否注册了切面,若是,则生成代理对象注入到容器中。
定位到AbstractAutoProxyCreator抽象类中的postProcessAfterInitialization方法处
1 | public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { |
使用
1 | // 将后置处理器加入到容器中,这样的话,Spring就能让它工作了 |
1 | Constructor(构造方法)→postProcessBeforeInitialization→bean的afterPropertiesSet |
注解
@PostConstruct注解和@PreDestroy注解
@PostConstruct注解
是Java自己的注解,是JSR-250规范里面定义的一个注解。
@PostConstruct注解被用来修饰一个非静态的void()方法。
被@PostConstruct注解修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。
被@PostConstruct注解修饰的方法通常在构造函数之后,init()方法之前执行。
通常我们是会在Spring框架中使用到@PostConstruct注解的,该注解的方法在整个bean初始化中的执行顺序如下:
1 | Constructor(构造方法)→@Autowired(依赖注入)→@PostConstruct(注释的方法) |
@PreDestroy
@PreDestroy注解同样是Java提供的,它也是JSR-250规范里面定义的一个注解。
被@PreDestroy注解修饰的方法会在服务器卸载Servlet的时候运行,并且只会被服务器调用一次,类似于Servlet的destroy()方法。被@PreDestroy注解修饰的方法会在destroy()方法之后,Servlet被彻底卸载之前执行。执行顺序如下所示:
1 | 调用destroy()方法→@PreDestroy→destroy()方法→bean销毁 |
使用
1 |
|