I-o-C 一篇概览

一、ioC 容器和 Bean介绍

IoC(Inversion of Control )也被称之为 DI(dependency injection),名称侧重点略有不同。

所谓控制翻转即对象通过构造函数参数、工厂方法参数或者属性字段设置来定义依赖,然后容器在创建 bean 的时候注入依赖。这个过程和对象自己管理依赖是完全相反的。

org.springframework.beans 和 org.springframework.context 是 Spring 框架 IoC 容器的基础包。

BeanFactory 接口提供了丰富的配置机制来管理各种类型的对象。

ApplicationContext 是  BeanFactory 的子接口,在其原有基础上添加了如下特性:

  • 和 Spring AOP 集成更容易。

  • 消息处理(国际化应用)

  • 事件分发

  • 特有的应用层 contexts,例如 web 应用中的 WebApplicationContext。

BeanFactory 提供了配置框架和基本的功能,ApplicationContext 在此基础上添加了更多企业应用特性,是 BeanFactory 的超集

Spring 有很多对象,其中 IoC 容器管理的实例化的对象称之为 bean。

二、容器总览

org.springframework.context.ApplicationContext 接口相当于 Spring IoC 容器,负责实例化,配置及组装 bean 对象。

配置元数据可以是 XML、Java 注解或者 Java 代码。

如下图,概括的展示了 Spring 的作用过程:

container magic
配置元数据用于描述 bean 及其依赖的关系,IoC 容器基于此来实例化,配置及组装 bean 对象。

配置元数据和IoC 容器是相互独立,彼此不耦合的,它可以有多种形式,包括基于 XML配置、基于注解配置基于Java配置等。

三、Bean 总览

容器内部 bean 通过 BeanDefinition 定义。

BeanDefinition 包含如下元数据:全限定类名(包含包名)、Bean 行为特性(作用域、生命周期回调等)、依赖描述及其它设置。

这些元数据可以通过如下一系列属性来描述:类名、名称、作用域、构造函数参数、属性、Autowire、懒加载、初始化方法、销毁方法。

四、Bean 作用域

a)singleton

默认,每个 IoC 容器一个 Bean 实例。应用于无状态 Bean 场景

附:对于 singleton 类型 bean 依赖 prototype 类型 bean 的场景,因为容器实例化对象时只会处理一次依赖,所以 singleton 实例依赖的 prototype 对象只是其一

b)prototype

每次需要 Bean 对象时即创建新的实例。应用于有状态 Bean 场景

附:对于 prototype 类型 bean,Spring 并没有管理其完整地生命周期,容器只负责实例化、配置及组装依赖。配置的销毁,生命周期回调并不会被调用。

可以通过自定义 bean post-processor 来处理。

c)request

Spring web 应用,对应每次 HTTP 请求生命周期,不同请求之间是隔离的

d)session

Spring web 应用,对应每次 HTTP Session 生命周期,不同 Session 之间是隔离的

e)application

Spring web 应用,对应 ServletContext 生命周期,不同 ServletContext 之间是隔离的

f)websocket

Spring web 应用,对应 WebSocket Session 生命周期。 

五、自定义 Bean 特性

1、生命周期回调

可以通过实现 Spring InitializingBean 和 DisposableBean 接口来和容器 bean 生命周期管理过程进行交互。

  • bean 初始化:InitializingBean() -> afterPropertiesSet() 调用。
  • bean 销毁:DisposableBean() -> destroy() 调用。

同注解应用:@PostConstruct、@PreDestroy

我们也可以通过实现 BeanPostProcessor 来处理任何回调接口。

除了初始化和销毁回调,Spring 管理的对象也可以通过实现 Lifecycle 接口来参与启动及关闭过程回调。

a)Initialization Callbacks

org.springframework.beans.factory.InitializingBean 接口可以让 bean 对象在容器设置完所有必要的属性后执行初始化操作。他只有一个方法:

void afterPropertiesSet() throws Exception;

通常我们不建议使用此接口,因为它会使得我们的代码和 Spring 代码产生耦合。推荐使用 @PostConstruct 注解或者声明一个 POLO 初始化方法。

初始化方法声明如下:

<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
// 或者
@Bean(initMethod = "")

如果存在多种声明机制,则执行顺序为:@PostConstruct -> InitializingBean.afterPropertiesSet() -> 自定义初始化方法

b)Destruction Callbacks

org.springframework.beans.factory.DisposableBean 接口可以使得 bean 在容器销毁时收到一个回调,它也只有一个方法:

void destroy() throws Exception;

同样不建议使用,原因如 InitializingBean,推荐使用 @PreDestroy 注解或者定义销毁方法。如下:

<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
或者
@Bean(destroyMethod = "")

如果存在多种声明机制,则执行顺序为:@PreDestroy -> DisposableBean.destroy() -> 自定义销毁方法

c)Startup and Shutdown Callbacks

Lifecycle 接口定义如下:

public interface Lifecycle {
    void start();
    void stop();
    boolean isRunning();
}

ApplicationContext 容器在接收到启动或者关闭信号之后,会顺序的把这一消息传递给所有容器内 Spring 管理的实现了 Lifecycle 接口的对象。

这一过程会委托代理给 LifecycleProcessor 进行处理,其定义如下:

public interface LifecycleProcessor extends Lifecycle {
    void onRefresh();
    void onClose();
}

LifecycleProcessor 是对 Lifecycle 接口的拓展,引入了处理 context 刷新及关闭的两个方法。

对于存在依赖关系的不同对象,启动及关闭的相应调用顺序就要遵循一定的规则。

如果是直接依赖:依赖方要先于被依赖方启动,并后于被依赖方关闭。

对于非直接依赖关系,如只知道一类类型的对象需要依赖另一类类型的对象,以上的接口将无法满足使用。因此这里需要引入另外一个接口 SmartLifecycle,它的定义如下:

public interface Phased {
    int getPhase();
}

public interface SmartLifecycle extends Lifecycle, Phased {
    boolean isAutoStartup();
    void stop(Runnable callback);
}

对于实现了 SmartLifecycle 接口的对象,启动时,phase 小的先启动,关闭时,phase 大的先关闭。

对于未实现 SmartLifecycle 接口的对象,我们可以认定它的 phase 为 0,如此,phase 小于 0 的对象则将先于其启动,后于其关闭。

SmartLifecycle 接口 stop() 方法会接收一个回调,所有实现此接口的对象都需要在其关闭过程执行完毕后调用一次 run() 方法。 

LifecycleProcessor 接口的默认实现 DefaultLifecycleProcessor, 会等待所有对象执行完回调(可以通过 timeoutPerShutdownPhase 设置超时),藉由此机制,我们可以在需要的时候的时候实现应用异步关闭逻辑。

d)非 web 应用 IoC 容器的优雅关闭

注册 JVM shutdown hook:ConfigurableApplicationContext.registerShutdownHook(),实例如下:

public final class Boot {

    public static void main(final String[] args) throws Exception {
        ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");

        // add a shutdown hook for the above context...
        ctx.registerShutdownHook();

        // app runs here...

        // main method exits, hook is called prior to the app shutting down...
    }
}

2、ApplicationContextAware 和 BeanNameAware

a)ApplicationContextAware

对象可以通过实现 org.springframework.context.ApplicationContextAware 接口来获取 ApplicationContext 资源。接口定义如下:

public interface ApplicationContextAware {
    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

藉由此,ApplicationContext 创建的 bean 也可以反过来对它执行特定的操作。例如,对其它 bean 的访问。

这种机制虽然在某些场景会很有用,但是它引发了代码的耦合,破坏了 IoC 机制,因此并不推荐。

另外一种获取 ApplicationContext 资源的方法是通过自动装配的方式引入 ApplicationContext 对象依赖,如构造函数或者 setter。 推荐使用 @Autowired 注解,更加灵活方便。

b)BeanNameAware

接口定义如下:

public interface BeanNameAware {

    void setBeanName(String name) throws BeansException;
}

其方法调用时机为:bean 属性组装完毕,初始化方法(InitializingBean.afterPropertiesSet() 或自定义初始化方法)调用之前。

3、其它 Aware 资源接口

ApplicationEventPublisherAware、BeanClassLoaderAware、BeanFactoryAware、LoadTimeWeaverAware、MessageSourceAware、NotificationPublisherAware、ResourceLoaderAware、ServletConfigAware、ServletContextAware。

六、容器扩展

1、BeanPostProcessor 自定义 bean 特性

BeanPostProcessor 接口通过提供回调方法来实现用户自定义逻辑。可以根据需要配置多个不同的实现,调用顺序通过 Ordered 设置。

org.springframework.beans.factory.config.BeanPostProcessor 接口包含两个回调方法:

package org.springframework.beans.factory.config;

import org.springframework.beans.BeansException;
import org.springframework.lang.Nullable;

public interface BeanPostProcessor {
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Nullable
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

分别对应初始化方法(InitializingBean.afterPropertiesSet() 或自定义初始化方法)前后需要执行的逻辑。

ApplicationContext 能够自动检测实现了 BeanPostProcessor 接口的 bean,并把它们注册为前置处理器,以便之后在其它 bean 创建的时候调用。

需要注意的是:如果是使用 @Bean 注解工厂方法。那么返回的结果需要是实现了 BeanPostProcessor 接口的类对象类型。否则 ApplicationContext 在创建它前无法自动检测其类型。

附:编程方式注册 BeanPostProcessor 实例(不推荐)

可以通过 addBeanPostProcessor 方法向 ConfigurableBeanFactory 注册。通过此方式注册实例 Ordered 接口作用将失效,会按照注册的顺序执行,并且优先于所有自动检测注册的前置处理器。

应用实例:AutowiredAnnotationBeanPostProcessor

2、BeanFactoryPostProcessor 自定义 bean 定义

BeanFactoryPostProcessor 用于操作 bean 配置元数据

可以配置多个 BeanFactoryPostProcessor 实例并通过实现 Ordered 接口设置调用顺序。

应用:PropertySourcesPlaceholderConfigurer、PropertyOverrideConfigurer

七、基于注解的容器配置

基于注解的注入先于 XML 配置。同样的注入会产生覆盖。

1、@Autowired 自动装配

用在哪里注入装配哪里。byType。

a)构造器:

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

对象只有多个构造器的时候,用以标明哪个构造器供容器使用。

b)setter 方法:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

c)任意方法,任意参数:

public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public void prepare(MovieCatalog movieCatalog,
            CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

d)属性字段:

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    private MovieCatalog movieCatalog;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

e)获取容器内特定类型对象集合

默认需要至少有一个特定类型对象,否则会发生装配失败。

数据和列表

public class MovieRecommender {

    @Autowired
    private MovieCatalog[] movieCatalogs;

    // ...
}

或

public class MovieRecommender {

    private Set<MovieCatalog> movieCatalogs;

    @Autowired
    public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }

    // ...
}

如果需要列表里的元素排序,则可以对收集的对象应用 Ordered 接口,或者添加 @Order 注解。

Map 类型收集:key 为 bean 名称,value 为 bean 对象。

public class MovieRecommender {

    private Map<String, MovieCatalog> movieCatalogs;

    @Autowired
    public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }

    // ...
}

f)默认装配行为

对于方法及属性字段的注解默认行为为必须。可以通过配置变更:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired(required = false)
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}
也可以通过 java.util.Optional(>= Java 8) 或者 @Nullable(Spring Framework 5.0) 来标示可选。
public class SimpleMovieLister {

    @Autowired
    public void setMovieFinder(Optional<MovieFinder> movieFinder) {
        ...
    }
}

或者

public class SimpleMovieLister {

    @Autowired
    public void setMovieFinder(@Nullable MovieFinder movieFinder) {
        ...
    }
}

g)装配使用特定已知框架资源

例如 BeanFactory、ApplicationContext、Environment、ResourceLoader、ApplicationEventPublisher 及 MessageSource 等。

如上接口的扩展接口,如ConfigurableApplicationContext、ResourcePatternResolver 等。

如下:

public class MovieRecommender {

    @Autowired
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...
}

h)附注

@Autowired、@Inject、@Value  及@Resource 注解都是通过 BeanPostProcessor 接口实现处理的。因此不能应用于自定义的 BeanPostProcessor 或者 BeanFactoryPostProcessor 类型实现。

2、@Primary

当存在多个同类型装配对象时,可以通过 @Primary 来标示使用哪个对象。如下:

@Configuration
public class MovieConfiguration {

    @Bean
    @Primary
    public MovieCatalog firstMovieCatalog() { ... }

    @Bean
    public MovieCatalog secondMovieCatalog() { ... }

    // ...
}

。。。

public class MovieRecommender {

    @Autowired
    private MovieCatalog movieCatalog; //firstMovieCatalog
// ... }

3、Qualifiers

@Qualifier 缩小符合装配的对象范围。

public class MovieRecommender {

    @Autowired
    @Qualifier("main")
    private MovieCatalog movieCatalog;

    // ...
}

或

public class MovieRecommender {

    private final MovieCatalog movieCatalog;

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public void prepare(@Qualifier("main") MovieCatalog movieCatalog,
            CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

符合 Qualifier 值得对象可以不唯一。

4、@Resource

 JSR-250 注解。byName。

注解 bean 属性字段或者 setter 方法。

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Resource(name="myMovieFinder") 
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

如果不指定名称,丢与属性字段则使用字段名,对于 setter 方法,则使用 bean 属性名。

首先通过名称查找,如果找不到则通过类型查找。

5、@Value

用于注入外部配置。

配置文件:application.properties

catalog.name=MovieCatalog

配置:

@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { }

使用:

@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("${catalog.name}") String catalog) {
        this.catalog = catalog;
    }
}

八、基于 Java 代码的容器配置

1、核心概念

基于 @Configuration 注解的类 和 基于 @Bean 注解的方法。

@Bean 注解作用于方法,用于实例化,配置及初始化一个新对象,并且新对象将由 Spring 容器管理。其和基于 XML 配置的 <bean/> 元素作用相同。

@Bean 通常和 @Configuration 结合使用(也可以用在任何 Spring @Component 注解管理的对象内)。 

@Configuration 注解的类主要用作 bean 定义。包括相关依赖 bean 的定义。简单实例如下:

@Configuration
public class AppConfig {

    @Bean
    public MyServiceImpl myService() {
        return new MyServiceImpl();
    }
} 

等同于:

<beans>
    <bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>

2、AnnotationConfigApplicationContext

ApplicationContext 的一种实现,Spring 3.0 引入。可以同时处理 @Configuration、@Component 及 JSR-330 注解的类。

@Configuration 注解的类:包括其内所有 @Bean 注解的方法都被注册为 bean 定义。

对于 @Component 及 JSR-330 注解的类:除了类本身会被注册为 bean 定义,并且会处理其内 @Autowired 或者 @Inject 注解相关的注入逻辑。

a)如下基于构造器示例:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
} 

//

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}  

b)如下基于编码注册处理示例:

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.register(AppConfig.class, OtherConfig.class);
    ctx.register(AdditionalConfig.class);
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

c)基于自动扫描处理示例:

@Configuration
@ComponentScan(basePackages = "com.acme") (1)
public class AppConfig  {
    // ...
}

// 或

<beans>
    <context:component-scan base-package="com.acme"/>
</beans>

AnnotationConfigApplicationContext 暴露了相应的 scan 接口,用于编码方式执行扫描操作:

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.scan("com.acme");
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
}

d、AnnotationConfigWebApplicationContext

Spring web 应用。包括注册 Spring ContextLoaderListener servlet listener、Spring MVC DispatcherServlet 等等。

3、@Bean 注解

a)bean 定义

注解方法,用于 bean 定义注册。方法名称默认为 bean 名称。返回值类型为 bean 对象类型(具体实现类或者对应接口类型)。

示例见 1、基础概念处。

也可以通过接口默认方法定义:

public interface BaseConfig {

    @Bean
    default TransferServiceImpl transferService() {
        return new TransferServiceImpl();
    }
}

@Configuration
public class AppConfig implements BaseConfig {

}

b)bean 依赖

bean 定义方法可以有任意多个参数,通过如下方式定义依赖:

@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService(AccountRepository accountRepository) {
        return new TransferServiceImpl(accountRepository);
    }
} 

c)生命周期回调

public class BeanOne {

    public void init() {
        // initialization logic
    }
}

public class BeanTwo {

    public void cleanup() {
        // destruction logic
    }
}

@Configuration
public class AppConfig {

    @Bean(initMethod = "init")
    public BeanOne beanOne() {
        return new BeanOne();
    }

    @Bean(destroyMethod = "cleanup")
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}

c)bean 作用域定义

@Configuration
public class MyConfiguration {

    @Bean
    @Scope("prototype")
    public Encryptor encryptor() {
        // ...
    }
} 

scope-procy:

@Configuration
public class MyConfiguration {

    @Bean
    @Scope(proxyMode = ScopedProxyMode.INTERFACES)
    public Encryptor encryptor() {
        // ...
    }
}

d)自定义 bean 名称

定义单个名称或者多个:

@Configuration
public class AppConfig {

    @Bean("myThing")
    public Thing thing() {
        return new Thing();
    }
}

//

@Configuration
public class AppConfig {

    @Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
    public DataSource dataSource() {
        // instantiate, configure and return DataSource bean...
    }
}

4、@Configuration 注解

如下:通过方法调用注入依赖

@Configuration
public class AppConfig {

    @Bean
    public ClientService clientService1() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }

    @Bean
    public ClientService clientService2() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }

    @Bean
    public ClientDao clientDao() {
        return new ClientDaoImpl();
    }
}

默认单例模式管理的 bean 定义。如上,两次 clientDao() 方法调用并不会产生两个 ClientDao 对象。在对象实例化时会首先检查容器相应 bean 实例对象缓存,然后再决定是否需要调用相应的实例化方法。

查找方法注入:

public abstract class CommandManager {
    public Object process(Object commandState) {
        // grab a new instance of the appropriate Command interface
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    // okay... but where is the implementation of this method?
    protected abstract Command createCommand();
}

//

@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
    AsyncCommand command = new AsyncCommand();
    // inject dependencies here as required
    return command;
}

@Bean
public CommandManager commandManager() {
    // return new anonymous implementation of CommandManager with createCommand()
    // overridden to return a new prototype Command object
    return new CommandManager() {
        protected Command createCommand() {
            return asyncCommand();
        }
    }
}

5、Import 注解

用于引入配置类,如下:

@Configuration
public class ConfigA {

    @Bean
    public A a() {
        return new A();
    }
}
//
@Configuration @Import(ConfigA.class) public class ConfigB { @Bean public B b() { return new B(); } }

如上,容器实例化时,只需要处理 ConfigB 即可:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);

    // now both beans A and B will be available...
    A a = ctx.getBean(A.class);
    B b = ctx.getBean(B.class);
}  

Spring Framework 4.2 开始,除了 @Configuration 注解对象,@Import 可以同时处理其它类型对象。

当需要精细处理对象依赖引入时,可以使用此注解,避免大包扫描。

九、环境抽象

Environment 接口为容器环境资源抽象,主要包括:profiles 和 properties 两方面。

profile:一种命名的逻辑组。可以设置 bean 定义率属于哪个特定 profile,从而在相应 profile 被激活时进行注册。profile 可以设置当前活跃及默认活跃。

Properties:资源服务接口,提供获取及配置相应资源能力。 源包括:properties 文件、JVM system properties、system environment variables、JNDI、servlet context parameters、ad-hoc Properties objects、Map objects等。

1、Bean Definition Profiles

提供机制实现容器不同环境注册不同的 bean。藉由此,我们可以实现一些类似如下场景功能:

  • 测试环境使用基于内存的数据源,QA 及 生产环境使用 JNDI 数据源。

  • 只在线上环境启动监控功能。

  • 针对不同用户注册不同的功能 bean 对象。

@Profile 注解

如下:JndiDataConfig 配置类只在 profile 为 production 时进行容器注册,使用

@Configuration
@Profile("production")
public class JndiDataConfig {

    @Bean(destroyMethod = "") (1)
    public DataSource dataSource() throws Exception {
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
    }
}

注解 value 可以使单一 profile 值,也可以是多个值得数组,亦或者为组合表达式。

@Profile("production")
@Profile({"QA", "production"})
@Profile("QA&production")

 自定义 profile 环境注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}

//等价于

@Profile("production") 

@Profile 作为顶层环境配置,控制所有组合使用的注解资源,如@Configuration、@Import等。

 profile 激活

直接编码方式设置:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();  

或环境变量属性配置:

-Dspring.profiles.active="profile1,profile2"    

2、PropertySource 抽象

Spring Environment 抽象提供属性查询操作(基于可配置的,层级的属性源),如下:

ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsMyProperty = env.containsProperty("my-property");
System.out.println("Does my environment contain the 'my-property' property? " + containsMyProperty);

PropertySource:k-v 组配置资源抽象。

StandardEnvironment 基于两类属性源配置:JVM 系统属性(System.getProperties())和系统环境变量(System.getenv())。

StandardServletEnvironment 除了上述两项配置外,还包括 servlet config、servlet context parameters 及 JndiPropertySource(如果存在 JNDI 资源需求)。

搜索操作是层级执行的,默认情况下,系统属性优先于环境变量,如果同一个属性在两个地方都有设置,则系统属性优先返回。其它的都会被忽略。

StandardServletEnvironment 的层次结构如下:

  1. ServletConfig parameters (if applicable — for example, in case of a DispatcherServlet context)

  2. ServletContext parameters (web.xml context-param entries)

  3. JNDI environment variables (java:comp/env/ entries)

  4. JVM system properties (-D command-line arguments)

  5. JVM system environment (operating system environment variables)

我们也可以添加自定义的 PropertySource 并将其添加当前环境 PropertySource 组,如下:

ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());

通过 MutablePropertySources 暴露的方法 addFirst(),将自定义的 MyPropertySource 添加到资源最上层位置,优先供给查询。 

@PropertySource 使用 

app.properties 文件:

testbean.name=myTestBean

注解引入:

@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {

    @Autowired
    Environment env;

    @Bean
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}

  

@PropertySource 位置 ${…​} 占位符会使用环境内其它已注册的 PropertySource 资源处理。如下:

@Configuration
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
public class AppConfig {

    @Autowired
    Environment env;

    @Bean
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}

先查询已注册 PropertySource 资源,查询不到则使用默认 "default/path",如果不存则抛出异常 IllegalArgumentException。

十、ApplicationContext 扩展功能

1、MessageSource 国际化(i18n)

ApplicationContext 通过实现 MessageSource 接口来提供国际化(i18n)功能。

Spring 同时也提供了 HierarchicalMessageSource 接口,用以逐层获取特定消息。

标准方法:

String getMessage(String code, Object[] args, String default, Locale loc) 

ApplicationContext 加载时会自动查找容器内定义的 MessageSource bean,且 bean 的名称必须为 messageSource。

当执行消息查找获取操作时,Spring 会将操作代理给命名为 messageSource 的 bean。如果不存在此 bean,则从父类中查找,如果找不到则实例化一个空的 DelegatingMessageSource 用以执行相应的方法操作。

Spring 提供了三种 MessageSource 接口实现:ResourceBundleMessageSource、ReloadableResourceBundleMessageSource 及 StaticMessageSource。它们也都实现了 HierarchicalMessageSource 接口用以执行嵌套消息查询。StaticMessageSource 很少使用,主要用于提供编码方式添加消息。

ResourceBundleMessageSource 使用如下:

定义:

@Bean
public ResourceBundleMessageSource resourceBundleMessageSource() {
    ResourceBundleMessageSource resourceBundleMessageSource = new ResourceBundleMessageSource();
    resourceBundleMessageSource.setBasenames("format", "exceptions", "windows")
return resourceBundleMessageSource; } // 或 <beans> <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basenames"> <list> <value>format</value> <value>exceptions</value> <value>windows</value> </list> </property> </bean> </beans>

format.properties:

message=Alligators rock!

exception.properties

argument.required=The {0} argument is required.  

使用:

public static void main(String[] args) {
    MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
    String message = resources.getMessage("message", null, "Default", Locale.ENGLISH);
    System.out.println(message);
}

对于不同的 Locale,则定义相应的不同资源文件:

Locale 示例:

public final class Locale implements Cloneable, Serializable {

    static private final  Cache LOCALECACHE = new Cache();

    /** Useful constant for language.
     */
    static public final Locale ENGLISH = createConstant("en", "");

    /** Useful constant for language.
     */
    static public final Locale JAPANESE = createConstant("ja", "");

    /** Useful constant for language.
     */
    static public final Locale CHINESE = createConstant("zh", "");

    /** Useful constant for language.
     */
    static public final Locale SIMPLIFIED_CHINESE = createConstant("zh", "CN");

    /** Useful constant for language.
     */
    static public final Locale TRADITIONAL_CHINESE = createConstant("zh", "TW");

... ...

资源文件示例:

exceptions_zh_CN.properties、exceptions_ja.properties

Spring MessageSource 是基于 JAVA MessageSource,所以并不会合并相同 basename 的 bundle。

Spring 另外提供了 ReloadableResourceBundleMessageSource 接口。除了可以提供如上基本功能外,它可以从任意 Spring 定义的资源位置读取文件,并且支持热加载。

2、标准的及自定义的事件

ApplicationContext 通过 ApplicationEvent 类及 ApplicationListener 接口来处理事件。容器内实现了 ApplicationListener 接口的对象能够获取任何 ApplicationEvent 发布的事件。 这种属于标准的观察者模式应用。

Spring 4.2 之后,事件框架做了显著升级,包括基于注解的实现及事件对象不再需要显式的继承 ApplicationEvent。

如下为 Spring 提供的标准事件:

ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent、ContextClosedEvent、RequestHandledEvent、ServletRequestHandledEvent。

自定义事件:

事件定义:

public class BlockedListEvent extends ApplicationEvent {

    private final String address;
    private final String content;

    public BlockedListEvent(Object source, String address, String content) {
        super(source);
        this.address = address;
        this.content = content;
    }

    // accessor and other methods...
}  

事件发布:

public class EmailService implements ApplicationEventPublisherAware {

    private List<String> blockedList;
    private ApplicationEventPublisher publisher;

    public void setBlockedList(List<String> blockedList) {
        this.blockedList = blockedList;
    }

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

    public void sendEmail(String address, String content) {
        if (blockedList.contains(address)) {
            publisher.publishEvent(new BlockedListEvent(this, address, content));
            return;
        }
        // send email...
    }
} 

事件监听:

public class BlockedListNotifier implements ApplicationListener<BlockedListEvent> {

    private String notificationAddress;

    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }

    public void onApplicationEvent(BlockedListEvent event) {
        // notify appropriate parties via notificationAddress...
    }
}

基于注解的事件监听:

public class BlockedListNotifier {

    private String notificationAddress;

    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }

    @EventListener
    public void processBlockedListEvent(BlockedListEvent event) {
        // notify appropriate parties via notificationAddress...
    }
}

异步监听:

@EventListener
@Async
public void processBlockedListEvent(BlockedListEvent event) {
    // BlockedListEvent is processed in a separate thread
}

顺序监听:

@EventListener
@Order(42)
public void processBlockedListEvent(BlockedListEvent event) {
    // notify appropriate parties via notificationAddress...
}

3、应用启动追踪

ApplicationContext 负责管理 Spring 应用的生命周期。应用启动追踪主要用于测量不同启动步骤时间花费。

AbstractApplicationContext 通过 ApplicationStartup 接口,收集各个启动阶段 StartupStep 数据:

  • application context lifecycle (base packages scanning, config classes management)

  • beans lifecycle (instantiation, smart initialization, post processing)

  • application events processing

AnnotationConfigApplicationContext 应用示例:

// create a startup step and start recording
StartupStep scanPackages = this.getApplicationStartup().start("spring.context.base-packages.scan");
// add tagging information to the current step
scanPackages.tag("packages", () -> Arrays.toString(basePackages));
// perform the actual phase we're instrumenting
this.scanner.scan(basePackages);
// end the current step
scanPackages.end();

可以通过实现 ApplicationStartup 接口,自定义启动追踪类。如下:

.setApplicationStartup(new MyApplicationStartup())

十一、附加订阅

   

posted @ 2023-05-05 09:39  WindWant  阅读(252)  评论(0编辑  收藏  举报
文章精选列表