Spring 5 设计模式 - bean生命周期和使用的模式

Spring容器管理的每个bean都有自己的生命周期和scope。

生命周期和阶段

Spring加载bean以后,Spring容器处理bean的生成和实例化。可以把生命周期分成三个阶段:

  • 初始化阶段
  • 使用阶段
  • 销毁阶段
    three phases

Spring管理的bean在每个阶段执行的操作,都是依赖于配置的。

初始化阶段

首先,从XML文件、注解和Java配置加载全部配置。本阶段完成以后,程序才可以使用。在本阶段,生成bean,并为bean分配资源。Spring使用ApplicationContext加载bean配置,程序上下文增加完成,本阶段也就执行完了。
Spring提供了ApplicationContext的多个实现,以加载配置文件:

ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

或者

ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

initialization phase

初始化阶段又可分成两步:

  • 加载bean定义
  • 初始化bean实例

加载bean定义

在这一步,所有的配置文件 - 带@Configuration注解的类和XML文件 - 都被处理了。对于基于注解的配置,所有使用@Components注解的类都被扫描,来加载bean定义。所有的XML文件被合并,bean的定义被加到BeanFactory。每个bean都用它的ID做索引。Spring提供了多个BeanFactoryPostProcessor,它被调用以解决运行时依赖(比如从外部属性文件读值)。Spring程序里的BeanFactoryPostProcessor可以修改任何bean的定义。
loads

可以看到,首先加载bean定义,然后调用BeanFactoryProcessor修改某些bean的定义。让我们看一个例子:

@Configuration
public class AppConfig {
    @Bean
    public TransferService transferService() { 
        //
    }
    @Bean
    public AccountRepository accountRepository(DataSource dataSource) {
        //
    }
}


@Configuration
public class InfraConfig {
    @Bean
    public DataSource dataSource () {
        //
    }
}

这些Java配置由ApplicationContext加载到容器,使用他们的ID做索引,如下图所示:
modify bean

Spring bean被索引之后放进BeanFactory,然后,BeanFactory被当作一个参数传入BeanFactoryPostProcessor的postProcess()方法。BeanFactoryPostProcessor能修改一些bean的定义;这依赖于bean的配置。我们看看BeanFactoryPostProcessor是如何工作的“

  • 在实际增加bean之前,BeanFactoryPostProcessor处理bean的定义或者配置元数据
  • BeanFactoryPostProcessor有几个实现,比如读属性文件、注册自定义scope
  • 可以实现自己的BeanFactoryPostProcessor
  • 如果你在一个容器内定义BeanFactoryPostProcessor,它就只作用于这个容器内的bean定义
public interface BeanFactoryPostProcessor {
    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory);
}

我们看看它怎么读属性文件database.properties:

jdbc.driver = org.hsqldb.jdbcDriver
jdbc.url = jdbc:hsqldb:hsql://production:9002
jdbc.username = dojjdbc.password=doj@123

对应的配置类:

@Configuration
@PropertySource("classpath:/config/database.properties")
public class InfraConfig {
    @Bean
    public DataSource dataSource(
            @Value("${jdbc.driver}") String driver,
            @Value("${jdbc.url}") String url,
            @Value("${jdbc.user}") String user,
            @Value("${jdbc.password}") String pwd) {
        DataSource ds = new BasicDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUser(user);
        ds.setPassword(pwd);
        return ds;
    }
}

对于@Value,使用PropertySourcesPlaceholderConfigurer处理,它是一个BeanFactoryPostProcessor。

初始化bean实例

把bean的定义加载到BeanFactory之后,Spring IoC容器实例化bean。见下图:
instantiates the beans

  • bean以正确的顺序和它的依赖注入被生成,除非它是延迟的
  • 有多个BeanPostProcessor,可以修改bean实例
  • 执行完本阶段,bean已经初始化完毕,准备使用了。除了prototype bean,都通过ID跟踪,直到被销毁

使用BeanPostProcessor自定义bean

BeanPostProcessor是个重要的扩展点。可以用来修改bean的视力。可以写自己的BeanPostProcessor来增加自定义的post-processor。Spring提供了它的几个实现。我们先看看BeanPostProcessor接口的定义:

public interface BeanPostProcessor {
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

可以实现这两个方法实现自定义逻辑。可以在Spring容器里增加多个BeanPostProcessor的实现,来增加自定义逻辑。也可以管理这些BeanPostProcessor的执行顺序。BeanPostProcessor在bean已经在Spring容器中实例化之后工作。
Spring容器在容器初始化方法(Initializing Bean的afterPropertiesSet()和bean的init方法)执行之前调用postProcessBeforeInitialization()方法。并在bean初始化之后调用postProcessAfterInitialization()方法。Spring AOP使用post-processor提供proxy-wrapping逻辑(代理设计模式)。Spring的ApplicationContext自动探测实现了BeanPostProcessor接口的bean,注册成post-processors。这些bean在其他任何增加的时候被调用。

我们自定义一个post-processor bean:

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("In After bean Initialization method.Bean name is" + beanName);
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("In Before bean Initialization method. Bean name is" + beanName);
        return bean;
    }
}

初始化扩展点

BPPs

Spring使用post-processor bean(BPPs)调用init方法(@PostConstruct)。上图是初始化和BPPs之间的关系图。
使用Java配置,可以这样写:

    @Bean(initMethod = "populateCache")
    public AccountRepository accountRepository(){
        return new JdbcAccountRepository();
    }

使用JSR-250注解,可以这样写:

    @PostConstruct
    void populateCache(){
        System.out.println("Called populateCache() method");
    }

第一阶段的概览图是这样的:
configuration life cycle

bean的使用阶段

Spring程序的bean,大部分时间都在本阶段。我们先看看如何从上下文获取bean:

    
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);

    TransferService transferService = context.getBean(TransferService.class);
    transferService.transfer("A", "B", 3000.1);

wrapped in a proxy

可以看到,在初始化阶段,bean被包装进一个动态代理。给bean透明地增加行为。这是一个Decorator和Proxy模式的实现。
Spring 使用两种类型的代理:

  • JDK:由JDK实现,被代理的类需要实现一个接口
  • CGLib:不实现接口的时候时候。它不能用于final类和方法。
    both the proxies

销毁阶段

在本阶段,Spring释放资源。关闭上下文的时候,销毁阶段完成。
我们看看,调用applicationContext.close()发生了什么:

  • 任何实现了DisposableBean的bean被回调,bean实例被销毁
  • 上下文销毁自己,该上下文不能被重新使用
  • GC实际上销毁对象和记忆

比如一个JdbcAccountRepository类:

public class JdbcAccountRepository implements AccountRepository {
    @Override
    public Account findByAccountId(Long accountId) {
        return new Account(accountId, "Arnav Rajput", new Amount(3000.0));
    }
    void clearCache(){
        System.out.println("Called clearCache() method");
    }
}

这样配置destroy方法:

    @Bean (destroyMethod="clearCache")
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository();
    }

更简单地,也可以使用一个注解:

public class JdbcAccountRepository {
    @PreDestroy
    void clearCache() {
        // close files, connections...
        // remove external resources...
    }
}

理解bean scopes

在Spring容器里,每个bean都有一个scope。
Spring的程序上下文总是使用singleton scope增加bean。
有时候,需要在对象里保存状态。比如这样一个需求,声明bean的scope为singleton是不安全的,因为以后重用的时候可能导致不期望的问题。Spring为了解决这样的需求,提供了prototype scope。

singleton

singleton

看上图,一个IoC容器内,同一个实例accountRepository,被注入其他合作bean。Spring使用缓存保存所有的singleton bean实例,通过该缓存,所有的合作bean获取依赖的对象。

prototype

prototype

Spring的任何定义为prototype scope的bean,每当注入合作bean的时候都生成新的实例。看上图,accountRepository有多个实例。

session

在web环境中使用,每个用户session增加一个实例。

request

在web环境中使用,每个请求增加一个实例。

其他scopes

  • WebSocket
  • Refresh
  • Thread

Spring也支持自定义scope。

自定义

先实现Scope接口,增加一个类:

import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;

public class MyThreadScope implements Scope {
    private final ThreadLocal<Object> myThreadScope =
            new ThreadLocal<Object>() {
                protected Map<String, Object> initialValue() {
                    System.out.println("initialize ThreadLocal");
                    return new HashMap<String, Object>();
                }
            };

    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        Map<String, Object> scope = (Map<String, Object>) myThreadScope.get();
        System.out.println("getting object from scope.");
        Object object = scope.get(name);
        if (object == null) {
            object = objectFactory.getObject();
            scope.put(name, object);
        }
        return object;
    }

    @Override
    public String getConversationId() {
        return null;
    }

    @Override
    public void registerDestructionCallback(String name, Runnable callback) {
    }

    @Override
    public Object remove(String name) {
        System.out.println("removing object from scope.");
        @SuppressWarnings("unchecked")
        Map<String, Object> scope = (Map<String, Object>) myThreadScope.get();
        return scope.remove(name);
    }

    @Override
    public Object resolveContextualObject(String name) {
        return null;
    }
}

然后

    @Bean
    public CustomScopeConfigurer configurer() {
        CustomScopeConfigurer csConfigurer = new CustomScopeConfigurer();
        csConfigurer.addScope("MyThreadScope", new MyThreadScope());
        return csConfigurer;
    }

猜你喜欢

转载自blog.csdn.net/weixin_43364172/article/details/84630770