Spring筆記 - IoC容器

1. IoC容器

負責Bean的實例化、注入、生命週期方法管理,包括如下類型接口,前者更強大更經常使用html

  • ApplicationContextjava

  • BeanFactory (本文忽略此類型)web


1.1 ApplicationContext

- AppCtx負責實例化、配置和裝配託管Beanspring

- AppCtx間接繼承了BeanFactory,其繼承的接口有:EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver編程

1.1.1 ApplicationContext類型

  • AnnotationConfig...session

  • ClassPathXml...app

  • FileSystemXml...ide

  • AnnotationConfigWeb...測試

  • XmlWeb...ui

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的生命週期

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

- 鍵值對存儲對象

//建立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屬性,文件編碼等)

- 用法以下

//建立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) {
        //處理事件
    }
}
相關文章
相關標籤/搜索