Spring Framework IOC
in with 0 comment

Spring Framework IOC

in with 0 comment

基础依赖

Spring IOC 依赖关系构成:

IAHGJNIJQKSQ1KELDE6K.png

初始化容器(IOC)

@Configuration
@ComponentScan("org.example.demo")
public class TestApplication {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestApplication.class);

        TestService service = context.getBean("testService", TestService.class);
        service.running();

        context.close();
    }
}

在上述案例中,AnnotationConfigApplicationContext 读取 TestApplication.class 中的配置元数据,从而构造了一个 Spring IOC容器。

其中 @Configuration 表示该类是一个配置文件,@ComponentScan("org.example.demo") 表示需要IOC进行管理的类路径

context.getBean("testService", TestService.class); 表示,从IOC容器中获取指定的实例(当然,该实例是单例的)

依赖注入(DI)

由上一个案例推断,我们从容器中获取了 testService

@Service
public class TestService {

    @Resource
    private TestBean testBean;

    public void running () {
        System.out.println(testBean.getMessage());
    }

}

@Getter
@Component
public class TestBean {

    private String message;

}

首先,我们使用 @Service@Component 标明 TestServiceTestBean 需要被 IOC 扫描并管理

之后,@Resource 负责将 TestBean 使用反射的原理注入,当然,我们也可以使用方法、构造函数的方式注入,但这里不概述

读取配置文件(Property)

pre-destroy = pre destroy in configuration
post-construct = post construct in configuration
bean-message = this is a test bean message
@Configuration
@ComponentScan("org.example.demo")
@PropertySource("classpath:/application.properties")
public class TestConfiguration {

    @Value("${pre-destroy}")
    private String preDestroy;

    @Value("${post-construct}")
    private String postConstruct;

}

@Configuration
@Import(TestConfiguration.class)
public class TestApplication {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestApplication.class);

        TestService service = context.getBean("testService", TestService.class);
        service.running();

        context.close();
    }
}

这是一个相对而言较为复杂的案例,我们使用 @Import 导入了名叫 TestConfiguration 的配置类

而在 TestConfiguration 中,使用 @PropertySource("classpath:/application.properties") 读取了指定位置的 properties 文件

最终,我们可以使用 @Value("${pre-destroy}") 的方式,将 properties 中的配置项注入到指定变量

Bean 状态

我们将被 Spring IOC 管理的对象称为 Spring Bean

默认状态下,在构造 IOC 的时候,这些对象将被全部加载,并且全局只有一份,但这也可以被改变

@Service
@Scope("prototype")
public class TestService {

    @Lazy
    @Resource
    private TestBean testBean;

    public void running () {
        System.out.println(testBean.getMessage());
    }

}

上述案例中,@Scope("prototype") 表示 TestService 是非单例状态,即每次注入 TestService,均是全新的实例

@Lazy 表示懒加载,只有 TestBean 被使用时才会注入

自定义 Bean 性质

@Getter
@Component
public class TestBean implements ApplicationContextAware, BeanNameAware, BeanClassLoaderAware {

    @Value("${bean-message}")
    private String message;

    @Value("${pre-destroy}")
    private String preDestroy;

    @Value("${post-construct}")
    private String postConstruct;

    @PreDestroy
    private void preDestroy () {
        System.out.println(preDestroy);
    }

    @PostConstruct
    private void postConstruct () {
        System.out.println(postConstruct);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("Gets application context by implements ApplicationContextAware");
    }

    @Override
    public void setBeanName(String s) {
        System.out.println("Gets bean name by implements ApplicationContextAware");
    }

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        System.out.println("Gets class loader by implements BeanClassLoaderAware");
    }
}

这是一个较为特殊的案例,其中分为两部分,@PreDestroy@PostConstruct 表示该类的生命周期函数,即销毁前调用、构造后调用。

另一部分,是来自实现的各种 Aware 接口,Aware 翻译后的意思是知道,代表实现了这类型接口,即可知道其对应的内容,例如:实现 ApplicationContextAware 接口的 setApplicationContext 函数后,可以得到一个 applicationContext 的对象,该对象可以让你知道 IOC 的上下文内容。

这里仅列举了部分 Aware,更多请前往官网查阅

扩展 IOC

Spring 提供了接口可以用来扩展 IOC

自定义 Bean

Spring 提供了一个叫做 BeanPostProcessor 的接口,它可以让你在全局范围内对 Bean 进行自定义

@Component
public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {

    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        System.out.print("process bean of before initialization");
        System.out.println("'" + beanName + "' created : " + bean.toString());
        return bean;
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.print("process bean of after initialization");
        System.out.println("'" + beanName + "' created : " + bean.toString());
        return bean;
    }
}

自定义配置元数据

如果说 BeanPostProcessor 可以让我们在全局范围内处理 Bean 的话,那么 BeanFactoryPostProcessor 就是 BeanPostProcessor 的升级版,它允许你在全局范围内获取配置元数据

@Component
public class InstantiationTracingBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(
        ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
    }
}

自定义 Bean 工厂

在实现 FactoryBean 后,我们自定义 Bean 的创建逻辑,甚至可以自定义这个 Bean 是否为单例

@Component
public class TestFactoryBean implements FactoryBean {

    @Override
    public Object getObject() throws Exception {
        return null;
    }

    @Override
    public Class<?> getObjectType() {
        return null;
    }

    @Override
    public boolean isSingleton() {
        return false;
    }
}

附加功能

国际化

首先,在类路径下创建如下两个配置文件

# i18n/test_zn.properties
test = 这是一个 i18n 的测试信息 {0}
# i18n/test_en.properties
test = this is a i18n message

之后,我们配置一个 ResourceBundleMessageSource 用于实现国际化

@Configuration
@ComponentScan("org.example.demo")
@PropertySource("classpath:/application.properties")
public class TestConfiguration {

    @Bean
    public ResourceBundleMessageSource messageSource () {
        ResourceBundleMessageSource source = new ResourceBundleMessageSource();
        source.setBasenames("i18n/test");
        source.setDefaultEncoding("UTF-8");
        return source;
    }

}

@Service
public class TestService {

    @Resource
    private MessageSource messageSource;

    public void running () {
        System.out.println(messageSource.getMessage("test", null, Locale.ENGLISH));
        System.out.println(messageSource.getMessage("test", new String[] { "替换的文字" }, Locale.CHINESE));
    }

}

事件监听

首先,我们需要创建一个用于被触发的事件,和这个事件的监听器

public class TestEvent extends ApplicationEvent {

    public TestEvent(Object source) {
        super(source);
    }
}
@Component
public class TestListener implements ApplicationListener<TestEvent> {

    @Override
    public void onApplicationEvent(TestEvent event) {
        System.out.println("this is a test event listener");
    }
}

之后,我们需要利用 ApplicationEventPublisherAware 来获取 ApplicationEventPublisher 将事件发布出去

@Service
public class TestService implements ApplicationEventPublisherAware {

    private ApplicationEventPublisher publisher;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }

    public void running () {
        publisher.publishEvent(new TestEvent(this));
    }

}

当然,我们也可以有一个简化版

@Service
public class TestService {

    @Resource
    private ApplicationEventPublisher publisher;

    public void running () {
        publisher.publishEvent(new TestEvent(this));
    }

    @EventListener(TestEvent.class)
    public void testEventListener (TestEvent event) {
        System.out.println("this is a test event listener");
    }
}

在简化版中,ApplicationEventPublisher 使用 DI 依赖注入,并且不需要单独创建一个事件监听的类

基于 Web 创建 IOC

您可以使用 ContextLoaderListener 注册 ApplicationContext:

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

这里默认使用 /WEB-INF/**/*Context.xml 作为配置文件