Как вмешаться в частную жизнь Spring бина

В интернете полно картинок типа той, что слева, о жизненном цикле Spring бина и как им пользоваться. И почти к каждой такой картинке прилагается длинная статья, рассказывающая о этапах создания бина и как там всё устроено.

Мне кажется, что начинающему Spring разработчику гораздо полезнее знать, как пользоваться этими этапами создания, чем о их существовании.

Подготовка

Возьмём пустой maven проект с JUnit, Hamcrest, Spring и Grovvy для конфигурации контекста:

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <javaee.version>7.0</javaee.version>
        <org.springframework.version>4.1.7.RELEASE</org.springframework.version>
        <groovy.version>2.4.4</groovy.version>
        <junit.version>4.12</junit.version>
        <hamcrest.version>1.3</hamcrest.version>
    </properties>




    <dependencies>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <version>7.0</version>
        </dependency>


        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${org.springframework.version}</version>
            <scope>test</scope>
        </dependency>


        <dependency>
            <groupId>org.codehaus.groovy</groupId>
            <artifactId>groovy-all</artifactId>
            <version>${groovy.version}</version>
        </dependency>


        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-library</artifactId>
            <version>${hamcrest.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

Создание бина

Начну с самого интересного: выполняем код после создания или перед уничтожением бина. Вообще с созданием может возникнуть непонимание, так как вроде бы есть конструктор, который и занимается созданием эксземпляра класса (=созданием бина) и вроде бы в нём и надо делать инициализацию, нет?

На самом деле нет. Конструктор будущего бина вызывается на ранних стадиях его жизненного пути, когда его зависимости ещё могут быть не установлены или не готовы. Для специализированного же метода инициализации гарантируется, что он будет вызван после того, как все зависимости бина готовы к употреблению.

Впрочем меньше слов, больше кода. Существует три возможности для указания метода инициализации бина, аннотация, реализация особого интерфейса, задание метода в конфигурации:

    @PostConstruct
    public void init() {
        LOGGER.info("I'm init function");
        if (service == null) {
            LOGGER.info("service is NOT set in init function");
        } else {
            LOGGER.info("service is set in init function");
        }
    }
@Service
public class DependencyInitWithInterface implements Dependency, InitializingBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        LOGGER.info("I'm init function");
        if (service == null) {
            LOGGER.info("service is NOT set in init function");
        } else {
            LOGGER.info("service is set in init function");
        }
    }
}
    dependencyWithConfig(DependencyWithConfig) { bean ->
        bean.initMethod = 'init'
    }

Для последнего случая никаких особенных дополнений в коде не требуется. В случае, если вы предпочитаете xml, а не groovy, задание init метода выглядит несколько иначе:

<bean id="dependencyWithConfig" class="ru.easyjava.spring.DependencyWithConfig" init-method="init">

Результат во всех трёх случаях одинаков:

ru.easyjava.spring.DependencyInitWithAnnotation <init>
INFO: I'm a constructor
ru.easyjava.spring.DependencyInitWithAnnotation <init>
INFO: service is NOT set in the constructor
ru.easyjava.spring.DependencyInitWithAnnotation init
INFO: I'm init function
ru.easyjava.spring.DependencyInitWithAnnotation init
INFO: service is set in init function
ru.easyjava.spring.DependencyInitWithInterface <init>
INFO: I'm a constructor
ru.easyjava.spring.DependencyInitWithInterface <init>
INFO: service is NOT set in the constructor
ru.easyjava.spring.DependencyInitWithInterface afterPropertiesSet
INFO: I'm init function
ru.easyjava.spring.DependencyInitWithInterface afterPropertiesSet
INFO: service is set in init function
ru.easyjava.spring.DependencyWithConfig <init>
INFO: I'm a constructor
ru.easyjava.spring.DependencyWithConfig <init>
INFO: service is NOT set in the constructor
ru.easyjava.spring.DependencyWithConfig init
INFO: I'm init function
ru.easyjava.spring.DependencyWithConfig init
INFO: service is set in init function
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.952 sec - in ru.easyjava.spring.LifecycleIT

Уничтожение бина

Spring предоставляет возможность выполнения кода перед уничтожением бина. И, так же как и с finalize(), не гарантирует, что код будет вызван. Как и с инициализацией бина, есть три метода вызова кода при унчитожении бина:

    @PreDestroy
    public void destroy() {
        LOGGER.info("I'm destroy function. I have nothing to do");
    }
@Service
public class DependencyInitWithInterface implements Dependency, InitializingBean, DisposableBean {
    @Override
    public void destroy() throws Exception {
        LOGGER.info("I'm destroy function. I have nothing to do");
    }
}
    dependencyWithConfig(DependencyWithConfig) { bean ->
        bean.initMethod = 'init'
        bean.destroyMethod = 'destroy'
    }

Для последнего случая никаких особенных дополнений в коде не требуется. В случае, если вы предпочитаете xml, а не groovy, задание destroy метода выглядит несколько иначе:

<bean id="dependencyWithConfig" class="ru.easyjava.spring.DependencyWithConfig" destroy-method="destroy">

В отличие от init() методов, с destroy() методами есть проблема с вызовом. Методы destroy() не вызываются автоматически при интеграционном тестировании с SpringJUnit4ClassRunner, они не вызываются автоматически, если контекст создаётся вручную. В последнем случае контекст надо и завершать тоже вручную:

    public static void main(final String[] args) {
        AbstractApplicationContext context =
                new GenericGroovyApplicationContext("/applicationContext.groovy");
        System.out.println("Destroy the context");
        context.close();
    }

Также destroy() методы не будут вызываны для prototype scope бинов.

Имя бина

Используя интерфейсы создания бина можно узнать, какое бину досталось имя:

public class NamedBean implements BeanNameAware {
    String name;


    public final String getName() {
        return name;
    }


    @Override
    public final void setBeanName(final String s) {
        this.name = s;
    }
}
beans {
    namedBean(NamedBean)


    anotherNamedBean(NamedBean)
}
@ContextConfiguration("/NamedBeanContext.groovy")
@RunWith(SpringJUnit4ClassRunner.class)
public class NamedBeanIT {
    @Inject
    private ApplicationContext context;


    @Test
    public void testBeanNames() {
        context.getBeansOfType(NamedBean.class).keySet().forEach(name -> System.out.println("Bean name in context is: " + name));
        context.getBeansOfType(NamedBean.class).values().forEach(bean -> System.out.println("Bean name is: " + bean.getName()));
    }
}
Bean name in context is: namedBean
Bean name in context is: anotherNamedBean
Bean name is: namedBean
Bean name is: anotherNamedBean

*Aware интерфейсы

Spring определяет огромное количество *Aware интерфейсов, внедряющих в бины тот или иной объект. Большая часть этих объектов может быть так же внедрена и с использованием @Inject/@Autowired. Например вместо использования интерфейсов ApplicationContextAware и BeanFactoryAware можно просто написать:

 @Inject
    private ApplicationContext applicationContext;


    @Inject
    private BeanFactory beanFactory;

Список *Aware интерфейсов весьма длинный и достаточно однообразный, поэтому вместо того, чтобы описывать каждый интерфейс с примером, я обойдусь сводным списком:

  • ApplicationContextAware.setApplicationContext() — позволяет узнать ApplicationContext, в котором создан бин.
  • ApplicationEventPublisherAware.setApplicationEventPublisher() — позволяет узнать ApplicationEventPublisher, с которым исполняется бин.
  • BeanClassLoaderAware.setBeanClassLoader() — позволяет узнать BeanClassLoader, с котором создан бин
  • BeanFactoryAware.setBeanFactory() — позволяет узнать BeanFactory, с которой создан бин
  • BootstrapContextAware.setBootstrapContext() —  позволяет узнать BootstrapContext, с которым исполняется бин
  • LoadTimeWeaverAware.setLoadTimeWeaver() — позволяет узнать LoadTimeWeaver, связанный с ApplicationContext, в котором создан бин
  • MessageSourceAware.setMessageSource() — позволяет узнать MessageSource, с которым исполняется бин
  • NotificationPublisherAware.setNotificationPublisher() — позволяет узнать NotificationPublisher для текущего экземпляра управления ресурсами
  • PortletConfigAware.setPortletConfig() — позволяет узнать PortletConfig, с которым исполняется бин
  • PortletContextAware.setPortletContext() — позволяет узнать PortletContext, с которым исполняется бин
  • ResourceLoaderAware.setResourceLoader() — позволяет узнать ResourceLoader, с которым исполняется бин
  • ServletConfigAware.setServletConfig() — позволяет узнать  ServletConfig, с которым исполняется бин
  • ServletContextAware.setServletContext() — позволяет узнать ServletContext, с которым исполняется бин

Скачать код примера

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *