Статьи

Идиомы платформы NetBeans: сменный верхний компонент (часть 2)

Продолжая мой предыдущий пост , на этот раз мы увидим три простых класса, которые мне понадобятся в моем следующем посте. Они могут использоваться для создания набора взаимодействующих классов (которые я назвал «поведением» в моем предыдущем посте) как в декларативном, так и в программном режиме, и выполняют очень простое внедрение зависимостей на основе аннотаций.

Я вспоминаю последний пример кода из моего предыдущего поста — вот как выглядит простое поведение:

import javax.annotation.Resource;
import javax.annotation.PostConstruct;

public class MyBehaviour
{
@Resource
private AnotherService anotherService;

public void doSomething()
{
anotherService.doSomething2();
}

@PostConstruct
private void initialize()
{
// some initialization stuff
}
}

 

BehaviourSet

Этот класс представляет собой набор поведений, которые могут быть установлены двумя способами: декларативным и программным. Первая группа устанавливается один раз и никогда не изменяется, вторая группа состоит из объектов, которые можно добавлять и удалять в любое время. Таким образом, я называю их «статичным» и «динамическим» поведением.

Реализация проста — это может быть полезным упражнением для чтения листинга кода, так как он использует изменяемые экземпляры Lookup, зверя, который встречается реже, чем неизменные версии:

package it.tidalwave.netbeans.behaviour;

import javax.annotation.Nonnull;
import javax.annotation.concurrent.NotThreadSafe;
import java.util.ArrayList;
import java.util.Collection;
import org.openide.util.Lookup;
import org.openide.util.lookup.AbstractLookup;
import org.openide.util.lookup.InstanceContent;
import org.openide.util.lookup.ProxyLookup;
import it.tidalwave.logger.Logger;
import it.tidalwave.netbeans.behaviour.util.BehaviourInjector;
import it.tidalwave.netbeans.behaviour.util.PostConstructorCaller;

@NotThreadSafe
public class BehaviourSet implements Lookup.Provider
{
private final InstanceContent dynamicBehaviours = new InstanceContent();

private final InstanceContent staticBehaviours = new InstanceContent();

private final AbstractLookup dynamicBehavioursLookup = new AbstractLookup(dynamicBehaviours);

private final AbstractLookup staticBehavioursLookup = new AbstractLookup(staticBehaviours);

private final Lookup lookup = new ProxyLookup(dynamicBehavioursLookup, staticBehavioursLookup);

private boolean setupPerformed = false;

private Lookup injectedLookup;

public void setInjectedLookup (final @Nonnull Lookup injectedLookup)
{
this.injectedLookup = injectedLookup;
}

@Override
@Nonnull
public Lookup getLookup()
{
return lookup;
}

public void addBehaviour (@Nonnull final Object behaviour)
{
//
// It is allowed to call this method at any time, still Behaviours will be all initialized
// in initialize(). Once initialization has been completed, Behaviours are installed as they
// are added.
//
if (setupPerformed)
{
initialize(behaviour);
}

dynamicBehaviours.add(behaviour);
}

public void removeBehaviour (@Nonnull final Object behaviour)
{
dynamicBehaviours.remove(behaviour);
}

public void setStaticBehaviours (final @Nonnull Collection<?> staticBehaviours)
{
for (final Object behaviour : staticBehaviours)
{
this.staticBehaviours.add(behaviour);
}
}

public void initialize()
{
final Collection<Object> behaviourToInitialize = new ArrayList<Object>();
behaviourToInitialize.addAll(staticBehavioursLookup.lookupAll(Object.class));
behaviourToInitialize.addAll(dynamicBehavioursLookup.lookupAll(Object.class));

for (final Object behaviour : behaviourToInitialize)
{
initialize(behaviour);
}

setupPerformed = true;
}

private void initialize (final @Nonnull Object behaviour)
{
BehaviourInjector.injectLookup(behaviour, injectedLookup);
PostConstructorCaller.callPostConstructors(behaviour);
}
}

Изменяемый Lookup создается путем переноса InstanceContent, поэтому у нас просто есть две пары InstanceContent + Lookup для статического и динамического поведения; ProxyLookup используется, чтобы объединить два набора и сделать их доступными как один общедоступный поиск.

Типичная последовательность использования может быть:

BehaviourSet behaviourSet = new BehaviourSet();
Lookup lookup = new ProxyLookup(behaviourSet.getLookup(),
/* other Lookup instances if needed */);
behaviourSet.setInjectedLookup(lookup);
behaviourSet.setStaticBehaviours(
Lookups.forPath("/Behaviours/foobar").lookupAll(Object.class));
behaviourSet.initialize();
...
behaviourSet.addBehaviour(behaviourA);
...

Lookups.forPath () отвечает за создание декларативного поведения, например, из раздела layer.xml, такого как:

<!DOCTYPE filesystem PUBLIC "-//NetBeans//DTD Filesystem 1.1//EN" "http://www.netbeans.org/dtds/filesystem-1_1.dtd">
<filesystem>
<folder name="Behaviours">
<folder name="foobar">
<file name="Behaviour1.instance">
<attr name="instanceClass" stringvalue="my.behaviours.Behaviour1"/>
</file>
<file name="Behaviour2.instance">
<attr name="instanceClass" stringvalue="my.behaviours.Behaviour2"/>
</file>
</folder>
</folder>
</filesystem>
 

Если вы думаете о аналогии с Spring, вы правы. По сути, BehaviourSet работает аналогично Spring BeanFactory — и благодаря использованию аннотации @Resource мы даже наслаждаемся (ограниченной) совместимостью нашего кода.

Действительно, когда мы посмотрим на реальные примеры интеграции (в следующих публикациях), мы увидим, что эта комбинация с layer.xml намного лучше, чем Spring во многих отношениях.

 

BehaviourInjector

Это простой служебный класс, который принимает объект и экземпляр Lookup; он анализирует объект, ища поля, помеченные как @Resource, и вставляет в них значения, взятые из Lookup. Сам экземпляр Lookup может быть внедрен.

package it.tidalwave.netbeans.behaviour.util;

import javax.annotation.Nonnull;
import javax.annotation.Resource;
import java.lang.reflect.Field;
import org.openide.util.Lookup;
import org.openide.util.lookup.ProxyLookup;
import it.tidalwave.logger.Logger;

public final class BehaviourInjector
{
private final static String CLASS = BehaviourInjector.class.getName();
private final static Logger logger = Logger.getLogger(CLASS);

public static void injectLookup (final @Nonnull Object behaviour,
final @Nonnull Lookup injectedLookup)
{
injectLookup(behaviour, behaviour.getClass(), new ProxyLookup(injectedLookup, Lookup.getDefault()));
}

private static void injectLookup (final @Nonnull Object behaviour,
final @Nonnull Class<?> clazz,
final @Nonnull Lookup injectedLookup)
{
final Class<?> superclass = clazz.getSuperclass();

if (superclass != null)
{
injectLookup(behaviour, superclass, injectedLookup);
}

for (final Field field : clazz.getDeclaredFields())
{
if (field.getAnnotation(Resource.class) != null)
{
final Class<?> fieldType = field.getType();

try
{
field.setAccessible(true);

if (fieldType.equals(Lookup.class))
{
field.set(behaviour, injectedLookup);
}
else
{
final Object resource = injectedLookup.lookup(fieldType);

if (resource == null)
{
throw new RuntimeException("Can't lookup resource: " + fieldType);
}

field.set(behaviour, resource);
}
}
catch (Exception e)
{
logger.severe("While injecting Lookup to %s: %s", behaviour, e);
logger.throwing(CLASS, "injectLookup()", e);
}
}
}
}
}

 

PostConstructorCaller

Последний листинг кода, который мы рассмотрим сегодня, — это еще один простой служебный класс, который анализирует объект для методов, аннотированных @PostConstruct, и вызывает их. @PostConstruct часто используется с @Resource для первого вызова метода после выполнения внедрения ресурса, поэтому можно выполнить инициализацию в зависимости от введенных полей. Еще раз, список прост:

package it.tidalwave.netbeans.behaviour.util;

import javax.annotation.Nonnull;
import javax.annotation.PostConstruct;
import java.lang.reflect.Method;
import it.tidalwave.logger.Logger;

public final class PostConstructorCaller
{
private final static String CLASS = PostConstructorCaller.class.getName();
private final static Logger logger = Logger.getLogger(CLASS);

public static void callPostConstructors (final @Nonnull Object behaviour)
{
callPostConstructors(behaviour, behaviour.getClass());
}

private static void callPostConstructors (final @Nonnull Object behaviour,
final @Nonnull Class<?> clazz)
{
final Class<?> superclass = clazz.getSuperclass();

if (superclass != null)
{
callPostConstructors(behaviour, superclass);
}

for (final Method method : clazz.getDeclaredMethods())
{
if (method.getAnnotation(PostConstruct.class) != null)
{
try
{
method.setAccessible(true);
method.invoke(behaviour);
}
catch (Exception e)
{
logger.severe("While initializing %s: %s", behaviour, e);
logger.throwing(CLASS, "callPostConstructors()", e);
}
}
}
}
}

В моем следующем посте я покажу, как это можно интегрировать с TopComponent для фактической реализации идиомы Pluggable TopComponent.

Рабочий код можно найти здесь:

hg clone https://kenai.com/hg/forceten~src
hg update -C dzone-20091020

в модуле модулей / OpenBlueSky / Поведение.