Spring IOC
一 背景
Spring是一个非侵入的、轻量级的构建企业级应用的解决方案。它采用的是模块化设计,能够简单的开箱即用,它的无侵入设计,使得开发人员能够专注于业务逻辑的开发,而这些都是建立在IOC容器基础上的。Spring是一个轻量级控制反转(IoC)和面向切面(AOP)的容器框架。
二 IOC
IoC 作为 Spring 第一个最核心的概念:IoC 全称为 Inversion of Control
,翻译为 “控制反转”,它还有一个别名为 DI(Dependency Injection
),即依赖注入。
控制反转——Spring通过一种称作控制反转(IoC)的技术促进了松耦合。当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。你可以认为IoC与JNDI相反——不是对象从容器中查找依赖,而是容器在对象初始化时不等对象请求就主动将依赖传递给它。
了解上述的IoC 我们需要理解组件时如何注册到IoC容器中的?
2.1 组件注册
Spring以往版本中会有如下诸多中方式支持组件的注册
2.1.1 XML
早期使用Spring时我们使用XML去驱动Spring,通常我们会如下使用:
使用XML中的<bean>标签去注册组件,然后使用
ClassPathXmlApplicationContext启动Spring.
SpringContext.xml :
<bean class="org.mzw.spring.annotation.entity.Person" >
<property name="age" value="15"></property>
<property name="name" value="maozw"></property>
</bean>
Main
/**
* 通过xml 配置加载SpringContext
*/
private static void starterSpringrForXml() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("SpringContext.xml");
Person bean = applicationContext.getBean(Person.class);
System.out.println(bean);
}
2.1.2 注解方式注册
Spring提供很多注解的方式来注册组件。
比如:@Controller
,@Servixw
,@Repository
,@Component
...
一般规则如下:
- 如果组件时我们自定义的组件我们可以显示使用
@Controller
,@Servixw
,@Repository
,@Component
。在我需要注册Spring中的组件上加上以上注解即可, 然后配置使用componentScan指定扫描路径。
示例代码
@Controller public class AddressController {
}
@Repository public @Data class AddressDao {
private String label = "1";
}
@Service public class AddressService {
}
如果使用xml方式驱动Spring, 如要在xml配置文件中指定扫描路径
<context:component-scan base-package="org.mzw.spring"/>
使用配置类的方式驱动
下文都是采用配置类的方式驱动Spring
@Configuration
public class SpringConfig {
}
Main
/**
* 通过Annotation配置加载SpringContext
*/
private static void starterSpringrForAnnotation() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
Person bean = applicationContext.getBean(Person.class);
String[] beanNamesForType = applicationContext.getBeanNamesForType(Person.class);
Arrays.asList(beanNamesForType).forEach(beanNames -> System.out.println(beanNames));
System.out.println(bean);
}
- 如果组件并不是我们自定义的,所以就没法显示的网组件上添加上面的注解,这个时候我们可以使用如下方式来注册组件给Spring:
- 使用@Bean 组件注册
@Bean("person")
public Person person2(){
return new Person("maozw",28);
}
-
@Import 快速导入注册组件
参数是个数组:需要导入的组件,导入后默认组件名称为类的全类名。
配置类:
@Configuration
@Import({Person.class})//组件数组
public class SpringConfig {
}
-
实现ImportSelector注册组件
实现ImportSelector,重写selectImports方法,返回需要导入的组件数组。
public class MyImportSelector implements ImportSelector {
/**
*
* Select and return the names of which class(es) should be imported based on
* the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
*
*
* 返回值就是注册容器的所有组件的全类名数组
* AnnotationMetadata: 标注@Import注解类的所有注解信息
* @param importingClassMetadata
*/
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"org.mzw.spring.annotation.entity.Person","org.mzw.spring.annotation.entity.Person1"};
}
}
配置类
@Configuration
@Import({MyImportSelector.class})
public class SpringConfig {
}
-
实现ImportBeanDefinitionRegistrar
ImportBeanDefinitionRegistrar,重写registerBeanDefinitions方法手动注册组件到IOC容器
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
/**
*
* BeanDefinitionRegistry : BeanDefinition注册类,通过BeanDefinitionRegistry可以把所有需要的bean注册到容器中
* 调用BeanDefinitionRegistry#registerBeanDefinition进行手动注册
*
* @param importingClassMetadata annotation metadata of the importing class
* @param registry current bean definition registry
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
boolean definition = registry.containsBeanDefinition("org.mzw.spring.annotation.entity.Person");
if (definition) {
// 第一个参数指定 beanName
// 第二个参数 指定Bean的定义信息
registry.registerBeanDefinition("person", new RootBeanDefinition(Person.class));
}
}
}
配置类
@Configuration
@Import({MyImportBeanDefinitionRegistrar.class})
public class SpringConfig {
}
-
创建一个Spring定义的FactoryBean注册组件
使用Spring定义的FactoryBean来注册组件, 然后使用上面的诸多方法把FactoryBean注册给Spring也可以完成我们的容器调用。
public class PersonFactoryBean implements FactoryBean<Person> {
/**
* Return an instance (possibly shared or independent) of the object
* managed by this factory.
*/
@Override
public Color getObject() throws Exception {
//返回一个Color对象 注册到IOC容器中
System.out.println("PersonFactoryBean...");
return new Person();
}
/**
* Return the type of object that this FactoryBean creates,
* or {@code null} if not known in advance.
*/
@Override
public Class<?> getObjectType() {
return Person.class;
}
/**
* Is the object managed by this factory a singleton? That is,
* will {@link #getObject()} always return the same object
*/
@Override
public boolean isSingleton() {
//是否是单例
return true;
}
}
2.1.3 按条件注解方式注册组件
Spring提供一种机制,我们可以通过某种条件判断来选择某个组件进行注册给Spring。可通过@Conditional
匹配注册组件。
比如自定义注册规则,根据不同的操作系统注册不同的bean
@Configuration
public class SpringConfig {
@Bean("person")
public Person person() {
System.out.println("person Bean 注册IOC ...");
return new Person("maozw", 28);
}
@Bean("macOs")
@Conditional(value = {MacOsCondition.class})
public Person person1() {
System.out.println("macOs Bean 注册IOC ...");
return new Person("macOs", 48);
}
@Bean("linux")
@Conditional(value = {LinuxCondition.class})
public Person person2() {
System.out.println("linux Bean 注册IOC ...");
return new Person("linux", 58);
}
}
public class MacOsCondition implements Condition {
private static final String macOs = "Mac OS X";
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();
String property = environment.getProperty("oss.name");
if (macOs.equals(property)){
return true;
}
return false;
}
}
public class LinuxCondition implements Condition {
private static final String linux = "linux-"; @Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();
String property = environment.getProperty("oss.name");
if (linux.equals(property)){
return true;
}
return false;
}
}
Main
private static void starterSpringrForAnnotationAndCondition() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfigCondition.class);
ConfigurableEnvironment environment = (ConfigurableEnvironment) applicationContext.getEnvironment();
System.out.println(environment.getProperty("oss.name"));
String[] beanDefinitionNames = applicationContext.getBeanNamesForType(Person.class);
Arrays.asList(beanDefinitionNames).forEach(beanDefinitionName -> System.out.println(beanDefinitionName));
Map<String, Person> beansOfType = applicationContext.getBeansOfType(Person.class);
System.out.println(beansOfType);
}
2.1.4 注册组件作用域
- SCOPE_SINGLETON : 是在容器启动的时候创建对象注册给IOC容器,以后每次获取都是从容器中获取
- SCOPE_PROTOTYPE : 对象不是在容器启动的时候注册,而是在通过容器获取对象实例时进行动态生成。
- SCOPE_REQUEST 同一次请求创建, 不常用
- SCOPE_SESSION 同一个session创建, 不常用
@Configuration
public class SpringConfig {
/**
*
* @see ConfigurableBeanFactory#SCOPE_PROTOTYPE
* @see ConfigurableBeanFactory#SCOPE_SINGLETON
* @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST
* @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION
* @return
*/
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Bean("person")
public Person person2() {
System.out.println("Person Bean 注册IOC ...");
return new Person("maozw", 28);
}
}
2.1.5 延迟注解方式注册组件
Spring 延迟注册,使用组件时才进行注册。
在需要的组件上加上@Lazy
注解即可
配置示例
@Configuration
public class SpringConfig {
@Lazy
@Bean("person")
public Person person2() {
System.out.println("Person Bean 注册IOC ...");
return new Person("maozw", 28);
}
}
2.2 组件注册生命周期管理
bean的生命周期管理主要是初始化bean,以及移除bean,可以通过如下方式:
2.2.1 通过@Bean注解指定
通过@Bean注解属性指定initMethod = "init",destroyMethod = "destory" 方法
@Configuration
public class SpringConfig {
@Bean(initMethod = "init",destroyMethod = "destory")
public Person person(){
return new Person();
}
}
public @Data class Person {
private String name;
private int age;
public void init() {
System.out.println("Person...init...");
this.age = 10;
this.name = "mzw";
}
public void destory() {
System.out.println("Person...destroy...");
}
}
2.2.2 实现:InitializingBean , DisposableBean接口
让Bean实现两个接口:InitializingBean , DisposableBean并重写下面两个方法
- InitializingBean#afterPropertiesSet
- DisposableBean#destroy
@NoArgsConstructor
@AllArgsConstructor
public @Data class Person implements InitializingBean , DisposableBean {
private String name;
private int age;
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("Person...InitializingBean...afterPropertiesSet");
this.age = 10;
this.name = "mzw";
}
@Override
public void destroy() throws Exception {
System.out.println("Person...DisposableBean..destroy...");
}
}
2.2.3 使用JSR250规范定义注解 @PostConstruct @PreDestroy
- @PostConstruct 在bean创建完成并且属性赋值完成后调用
- @PreDestroy 在容器移除bean'前回调通知
@NoArgsConstructor
@AllArgsConstructor
public @Data class Person {
private String name;
private int age;
@PostConstruct
public void init() throws Exception {
System.out.println("Person...PostConstruct...init");
this.age = 10;
this.name = "mzw";
}
@PreDestroy
public void destroy() throws Exception {
System.out.println("Person...PreDestroy...destroy...");
}
}
2.2.4 使用BeanPostProcessor-后置处理器
BeanPostProcessor-后置处理器 在bean初始化前后(init,InitializingBean,PostConstruct)做一些准备工作 。
Spring底层对BeanPostProcessor的使用;bean赋值,注入其他组件:@Autowired,以及生命周期中的其他组件@PostContrustor,@PreDestroy @Async...
- postProcessBeforeInitialization:
- postProcessAfterInitialization:
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessBeforeInitialization ... ");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessAfterInitialization ... ");
return bean;
}
}
源码分析:
- applyBeanPostProcessorsBeforeInitialization 遍历容器所有的BeanPostProcessors,挨个执行postProcessBeforeInitialization,直到方法一旦返回null,则终止循环,后续的BeanPostProcessors则不再执行。
- invokeInitMethods 初始化方法
- applyBeanPostProcessorsAfterInitialization 遍历容器所有的BeanPostProcessors,postProcessAfterInitialization,直到方法一旦返回null,则终止循环,后续的BeanPostProcessors则不再执行。
- 上述三个方法 即initializeBean内部调用逻辑整体是populateBean之后进行调用,populateBean是给对象赋值。
- 注意单例的bean 容器创建对象是调用init方法,销毁在容器关闭是调用
- 多实例prototype:容器创建对象是调用init方法,销毁方法容器不会调用
2.3 组件自动装配
Spring利用依赖注入(DI),完成对IOC容器中的各个组件的依赖关系赋值
2.3.1 通过@AutoWired 自动注入
- a. 默认优先按照类型去IOC容器中查找对应的组
- b. 如果通过类型查找找到的组件大于1,则再次
- c. @Qualifier("xxxx") 通过该注解指定需要装配的bean,比较麻烦 @see e;
- d. 默认自动装配的组件一定需要将属性赋值好(即一定要在容器中找到该组件),否则就会报错;可以使用@Autowired(required = false)
- e. @Primary: 让Spirng进行自动装载的时候默认使用首选的Bean即该注解修饰的Bean
- f.位置说明:
- ElementType.CONSTRUCTOR, : 特别用法 如果组件只有一个有参构造器,这个有参构造器的@AutoWired可以省略, 参数位置的组件还是可以从容器中获取
- ElementType.METHOD, : 特别用法 @Bean + 方法参数,参数中的组件也可以从容器中获取,不需要显示添加@AutoWired
- ElementType.PARAMETER,
- ElementType.FIELD,
- ElementType.ANNOTATION_TYPE
2.3.2 通过@Resource(JSR250) @Inject(JSR330)
spring支持这两种注入方式:
- a. @Resource: Java规范,默认按照组件名称进行装配,不支持和Spring的组件合用 如@Qualifier,@Primary,required = false
- b. @Inject: 需要导入javax.Inject 依赖; 和AutoWired功能一样,支持@Primary,但是不支持required=false
2.3.3 自定义组件使用Spring的组件
自定义组件使用Spring容器的一些组件(ApplicationContext,BeanFactory,xxx), 自定义组件需实现xxxAware接口(ApplicationContextAware); xxxAware:功能使用都是xxxProcessor实现的: 比如ApplicationContextAware:
网友评论