Spring笔记 - IoC容器

1. IoC容器

负责Bean的实例化、注入、生命周期方法管理,包括以下类型接口,前者更强大更常用

  • ApplicationContext

  • BeanFactory (本文忽略此类型)


1.1 ApplicationContext

- AppCtx负责实例化、配置和装配托管Bean

- AppCtx间接继承了BeanFactory,其继承的接口有:EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver

1.1.1 ApplicationContext类型

  • AnnotationConfig...

  • ClassPathXml...

  • FileSystemXml...

  • AnnotationConfigWeb...

  • XmlWeb...

1.1.2 启动ApplicationContext容器

- Java代码启动

ApplicationContext context =
    new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"});
    
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class, OtherConfig.class);

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppConfig.class, OtherConfig.class);
ctx.register(AdditionalConfig.class);
ctx.refresh();

- 随其他容器例如servlet容器启动,在web.xml定义ContextLoaderListener或DispatchServlet,最终产生WebApplicationContext。[参考]

<!-- 不指定ctxCfg,默认为/WEB-INF/applicationContext.xml -->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/**/*Context.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

1.1.3 关闭ApplicationContext容器

- Java代码关闭

//在JVM注册关闭钩子,JVM容器关闭时,IoC容器调用Bean的destroy方法释放资源
context.registerShutdownHook();

- Web容器里的IoC容器会自动关闭

1.1.4 在托管Bean里面使用ApplicationContext

// 方法1: 让Bean实现ApplicationContext接口
class Car implements ApplicationContextAware {
    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

// 方法2: 直接注入
@Component
class Car implements ApplicationContextAware {
    private ApplicationContext applicationContext;

    @Autowired
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }
}


1.2 Bean的生命周期

Spring笔记 - IoC容器_第1张图片

XML定义例如:init-method, destroy-method
注解例如JSR250的@PostConstruct and @PreDestroy


1.3 定义Bean

1.3.1 定义方法

- 设置方法有XML、JavaConfig、自动(Annotation)

- 三种方式可以混用

- Spring In Action推荐:如果可以为Bean代码加上注解,优先自动;否则优先JavaConfig;最后XML。

- XML

<!-- 基本用法 -->
<bean id="car" name="car,auto" class="x.y.z.Car"/>
<bean alias="sedan" name="car" />
<!-- 静态工厂方法 -->
<bean id="logger" class="x.y.z.Logger" factoryMetohd="getInstance" />
<!-- 实例工厂方法 -->
<bean id="iOReader" factoryBean="x.y.z.ReaderFactory" factoryMetohd="createIOReader" />

- JavaConfig

//基本用法
@Bean(name={"car","auto"}, initMethod = "init")
@Scope("prototype")
@Description("It's a car Bean")
public Car car(@Value("#{}") String name) {
    return new Car();
}
//静态工厂方法
@Bean(name="logger")
public Logger logger() {
    return Logger.getInstance();
}
//实例工厂方法
@Bean(name="iOReader")
public IOReader iOReader() {
    return new ReaderFactory().createIOReader();
}

- 自动(Annotation)

//通过扫描有注解的Bean,自动发现

//JavaConfig扫描设置
//默认扫描配置类所在的包及下级包
@Configuration
@ComponentScan(basePackages = "x.y.z")
public class AppConfig  {
}
//XML扫描设置
<context:component-scan>会自动启动<context:annotation-config>
//注解
@Component, @Service, @Controller, @Repository, @Named(JSR330),可以用@Scope、@Qualifier修饰

1.3.2 Bean的作用范围

  • singleton,在容器内唯一,默认范围

  • prototype,每次实例化时创建不同实例

  • request,web环境可用

  • session,web环境可用

  • global session,web portlet环境可用

  • application,web环境可用

  • 自定义

1.3.3 定义导入

XML和JavaConfig的定义可以互相导入

<import resource="services.xml"/>
@Configuration
@Import(ConfigA.class)
public class ConfigB {
}

1.3.4 抽象Bean

用作配置项,不可以实例化,可以被继承

<bean id="auto" class="x.y.z.Auto" abstract="true">
    <!-- ... 若干配置 ... -->
</bean>
<!-- car继承所有auto的配置,可以覆盖 -->
<bean id="car" class="x.y.z.Car" parent="auto" />

1.3.5 定义依赖/继承

- depends-on和ref都有依赖关系,前者不必引用被依赖者

- 被依赖者,先行实例化,先行destroy(单例)

<bean id="car" depends-on="driver1,model1">
@DependsOn


1.4 依赖注入

1.4.1 注入方式

- 方式有:构造器注入、Setter方法注入、成员变量注入、自动注入

- Spring Reference推荐必须的依赖用构造器注入,非必须的可用其他方法注入

- 可以注入基本类型、collection、Bean实例

- XML方式例如

<bean id="car" class="x.y.z.Car" p:brand="BENZ" p:model-ref="model1" lazy-init="true" scope="singleton">
    <constructor-arg value="1"/>
    <property name="driver" ref="driver1" required="false" />
    <!-- 注入的是park1的Bean Id -->
    <property name="destName" idref="park1" />
    <property name="packages">
        <list>
            <value>瓶装水</value>
            <value>面巾纸</value>
        </list>
    </property>
    <property name="color" value=""/>
    <property name="tent">
        <null/>
    </property>
    <property name="engine">
        <!-- 嵌入Bean,scope=prototype -->
        <bean class="x.y.z.Engine" autowire="byName" />
    </property>
</bean>

- 注解有

@Autowired(required=false)、@Resource、@Inject(JSR330);可用@Qualifier、@Named(JSR330)、@Value修饰

1.4.2 方法注入

往单例的Bean里面注入非单例的Bean,需要使用Lookup进行方法注入

public abstract class CommandManager {
    public Object process(Object commandState) {
        Command command = createCommand();
        command.setState(commandState);
        return command.execute();
    }
    protected abstract Command createCommand();
}

- XML方式

<bean id="command" class="x.y.z.AsyncCommand" scope="prototype" />
<bean id="commandManager" class="x.y.z.CommandManager">
    <lookup-method name="createCommand" bean="command"/>
</bean>

- JavaConfig方式,继承抽象类

@Bean
public CommandManager commandManager() {
    return new CommandManager() {
        protected Command createCommand() {
            return new AsyncCommand();
        }
    }
}


1.5 实例化Bean

- 当Bean容器打开后,所有定义的Bean都会被实例化,除了指定了lazy-init

- 如果指定了lazy-init,将在依赖Bean实例化或getBean时实例化


1.6 环境Environment和剖面Profile

1.6.1 PropertySource

- 键值对存储对象

Spring笔记 - IoC容器_第2张图片

//创建MapPropertySource
Map<String, Object> map = new HashMap<>();
map.put("encoding", "gbk");
PropertySource ps1 = new MapPropertySource("map", map);
String encoding = (String) ps1.getProperty("encoding"));
env.getPropertySources().addFirst(ps1);

//创建ResourcePropertySource
ResourcePropertySource ps2 = new ResourcePropertySource("resource", "classpath:resources.properties"); 
encoding = (String) ps2.getProperty("encoding");

//注解@PropertySource,自动加入到Environment
@Configuration
@PropertySource(value = "classpath:/x/y/z/app.properties", ignoreResourceNotFound = false)
public class AppConfig {
    @Autowired
    Environment env;
    @Bean
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}

//Java启动参数可以设置env property
java -Dspring.profiles.active=dev JavaApp
<!-- web.xml -->
<context-param>
    <param-name>spring.profiles.active</param-name>
    <param-value>dev</param-value>
</context-param>
<!-- 占位符替换,不会加入到Environment -->
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

1.6.2 Environment

- 环境包含Profile配置和Properties属性

- 实现包括MockEnv和StdEnv。前者用于测试;后者可用来获取systemEnv (系统变量,例如JAVA_HOME等) 和 systemProp (系统属性,例如JVM属性,文件编码等)

Spring笔记 - IoC容器_第3张图片

- 用法如下

//创建StdEnv
StandardEnvironment environment = new StandardEnvironment();
Map<String, Object> systemEnvironment = environment.getSystemEnvironment();
Map<String, Object> systemProperties = environment.getSystemProperties();

//在托管Bean中直接注入
@Autowired
Environment environment;

//通过上下文获取
Environment environment = context.getEnvironment();
String property = environment.getProperty("");
String[] defaultProfiles = environment.getActiveProfiles();
String[] defaultProfiles = environment.getDefaultProfiles();

1.6.3 Profile

- 是类实例化或方法执行的先决条件之一

- 设置方法见1.6.1,此外,还有以下

//注解@Profile
@Configuration
@Profile("dev")
public class StandaloneDataConfig {
    @Bean
    public DataSource dataSource() {
        //...
    }
}

//Environment直接设置
env.setActiveProfiles("dev", "test");
<beans <!-- ... --> profile="dev">
    <!-- ... -->
</beans>

- 获取方法

String[] defaultProfiles = environment.getActiveProfiles();
String[] defaultProfiles = environment.getDefaultProfiles();
boolean acceptsProfiles = outerBean1.environment.acceptsProfiles("dev", "test");


1.7 国际化i18n

- 实现包括ResourceBundleMessageSource和StaticMessageSource,后者用得少,但可以用编程的方式增加message

- AppCtx接口继承了MessageSource接口

<beans>
    <bean id="messageSource"
            class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basenames">
            <list>
                <value>account</value>
                <value>exceptions</value>
            </list>
        </property>
    </bean>
</beans>

<!-- 定时刷新 -->
    <bean id="messageSource"
            class="org.springframework.context.support.ReloadableResourceBundleMessageSource"
            p:name="cacheSeconds" p:value="3000" >
        <property name="basenames">
            <!-- ... -->
        </property>
    </bean>
#account.properties文件内容
slogan=欢迎,{0}!

#account.properties_en_GB文件内容
slogan=Welcome, {0}!
MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
String message1 = resources.getMessage("slogan", new Object [] {"张三"}, "Default", null);         //欢迎,张三!
String message2 = resources.getMessage("slogan", new Object [] {"Peter"}, "Required", Locale.UK);  //Welcome, Peter!
//也可以直接使用AppCtx
String message3 = context.getMessage("slogan", new Object [] {"张三"}, "Default", null);         //欢迎,张三!

1.8 事件Events

- 标准事件有:ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent、ContextClosedEvent、RequestHandledEvent

- 自定义事件,更复杂的应用参考Spring Integration

//自定义黑名单事件
public class BlackListEvent extends ApplicationEvent {
    private final String address;
    public BlackListEvent(Object source, String address, String test) {
        super(source);
        this.address = address;
    }
}
//发布事件
public class EmailService implements ApplicationEventPublisherAware {
    private List<String> blackList;
    private ApplicationEventPublisher publisher;

    public void sendEmail(String address, String text) {
        if (blackList.contains(address)) {
            BlackListEvent event = new BlackListEvent(this, address, text);
            publisher.publishEvent(event);
            return;
        }
        // send email...
    }
}
//接收并处理事件
public class BlackListNotifier implements ApplicationListener<BlackListEvent> {
    public void onApplicationEvent(BlackListEvent event) {
        //处理事件
    }
}




你可能感兴趣的:(spring)