Маленькие секреты Spring

8 апреля 2014
Василий Савицкий, JAVA Developer
Блог DataArt, Апрель 2014, Маленькие секреты Spring

В киевском офисе DataArt прошел тренинг «Spring 3 — копаем до самого ядра», нацеленный на Java-разработчиков, которые хотели бы получить теоретические и практические навыки работы с популярным фреймворком Spring и разобраться в принципах и деталях его устройства.

Маленькие секреты Spring.

У Spring есть четкий порядок инициализации объектов:

  1. Формируется Configuration Metadata, она может быть создана из XML-контекста, из конфигурации с помощью Annotations либо Java Configuration.
  2. Все объекты, которые имплементируют интерфейс BeanFactoryPostProcessor, читают Metadata и изменяют ее в соответствии со своим предназначением.
  3. Вся Metadata, которую модифицировали и нет, передается в BeanFactory, которая непосредственно и создает spring beans.
  4. Все объекты, которые имплементируют интерфейс BeanPostProcessor, производят pre initializing- и post initialization-действия.
  5. Все бины, которые уже были инициализированы, отдаются в IoC Container.

Spring — довольно гибкая система, позволяющая вмешиваться в многие внутренние процессы, конкретно сейчас мы будем говорить о шагах 2 и 4.

Сразу оговорюсь, что последующие примеры будут сильно надуманы и не сильно применимы практически, но дадут понимание о самом процессе работы Spring и, вполне возможно, ответы на некоторые ваши вопросы.

Пример 1. Простой инжект.

Задача: нужно написать аннотацию которая бы в поле типа Integer инжектила рандомное число от min до max;

Все, что для этого понадобится:

1) Написать аннотацию.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface InjectRandomInt {
int min();
int max();}

2) Написать свой BeanPostProcessor

  public class InjectRandomIntBeanPostProcessor implements BeanPostProcessor {
private Random random = new Random();
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
Field[] fields = bean.getClass().getDeclaredFields();
for (Field field : fields) {
InjectRandomInt annotation = field.getAnnotation(InjectRandomInt.class);
if (annotation != null) {
if(!field.getType().equals(int.class))
throw new RuntimeException("don't put @InjectRandomInt above " + field.getType());
if (Modifier.isFinal(field.getModifiers())) {
throw new RuntimeException("can't inject to final fields");
}
int min = annotation.min();
int max = annotation.max();
int randomInt = min + random.nextInt(max - min);
try {
field.setAccessible(true);
field.set(bean,randomInt);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}}

3) Зарегистрировать свой InjectRandomIntBeanPostProcessor в контексте (если используем XML конфигурацией ).

Пример 2. Магия с использованием proxy.

Всем известно, что Spring возвращает proxy-объекты, чтобы на ходу модифицировать бин и добавить в него свою функциональность. Но, т. к. Spring довольно гибкий, мы можем проделать то же самое.

Задача: нужно написать аннотацию для измерения времени, за которое исполнялся бы аннотированный метод, и вывода информацию в консоль.

Все, что для этого понадобится:

1. Написать аннотацию.

  @Retention(RetentionPolicy.RUNTIME)
public @interface Benchmark {
}

2. Написать свой BeanPostProcessor где мы будем делать “proxy magic”

  public class BenchmarkBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {
Class type = bean.getClass();
if (type.isAnnotationPresent(Benchmark.class)) {
Object proxy = Proxy.newProxyInstance(type.getClassLoader(),type.getInterfaces(),new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long before = System.nanoTime();
Object retVal = method.invoke(bean, args);
long after = System.nanoTime();
System.out.println("метод работал: "+(after-before)+" наносекунд");
return retVal;
}
});
return proxy;
} else {
return bean;
}
}
@Override
public Object postProcessAfterInitialization(final Object bean, String beanName) throws BeansException {
return bean;
}
}

3. Зарегистрировать свой BenchmarkBeanPostProcessor

Пример 3. «Хачим» bean definition в рантайме.

Еще до BeanPostProcessor который возвращает обьект, есть возможность изменить bean definition с которого и будет создаваться конечный обьект.

Задача: нужно написать аннотацию которая бы помечала класс как устаревший и подменяла его новым который подается в аннотации;

1. Написать аннотацию

    @Retention(RetentionPolicy.RUNTIME)
public @interface MyDeprecated {
public Class newImpl() default Class.class;
}

2. Написать свой BeanFactoryPostProcessor

  public class MyDeprecatedBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
String[] names = configurableListableBeanFactory.getBeanDefinitionNames();
for (String name : names) {
BeanDefinition beanDefinition = configurableListableBeanFactory.getBeanDefinition(name);
String className = beanDefinition.getBeanClassName();
try {
Class originalClass = Class.forName(className);
MyDeprecated annotation = originalClass.getAnnotation(MyDeprecated.class);
if(annotation != null){
Class newClass = annotation.newImpl();
beanDefinition.setBeanClassName(newClass.getName());
}
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
}

3. Зарегистрировать BeanFactoryPostProcessor как bean в конфигурации.

После тренинга участники смогли пообщаться, обсудить услышанное и задать вопросы. Спасибо DataArt за возможность получения полезных знаний.


Блог DataArt, Апрель 2014, Маленькие секреты Spring Блог DataArt, Апрель 2014, Маленькие секреты Spring