美文网首页
Spring IOC使用手札

Spring IOC使用手札

作者: 花神子 | 来源:发表于2019-10-09 19:49 被阅读0次

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:
  1. 使用@Bean 组件注册
@Bean("person")
public Person person2(){
    return new Person("maozw",28);
}
  1. @Import 快速导入注册组件
    参数是个数组:需要导入的组件,导入后默认组件名称为类的全类名。
    配置类:
@Configuration 
@Import({Person.class})//组件数组 
public class SpringConfig {
}
  1. 实现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 {
}
  1. 实现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 {
}
  1. 创建一个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;
    }
}

源码分析:

    1. applyBeanPostProcessorsBeforeInitialization 遍历容器所有的BeanPostProcessors,挨个执行postProcessBeforeInitialization,直到方法一旦返回null,则终止循环,后续的BeanPostProcessors则不再执行。
    1. invokeInitMethods 初始化方法
    1. applyBeanPostProcessorsAfterInitialization 遍历容器所有的BeanPostProcessors,postProcessAfterInitialization,直到方法一旦返回null,则终止循环,后续的BeanPostProcessors则不再执行。
    1. 上述三个方法 即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:

相关文章

网友评论

      本文标题:Spring IOC使用手札

      本文链接:https://www.haomeiwen.com/subject/lhfnpctx.html