Продолжая мой предыдущий пост , на этот раз мы увидим три простых класса, которые мне понадобятся в моем следующем посте. Они могут использоваться для создания набора взаимодействующих классов (которые я назвал «поведением» в моем предыдущем посте) как в декларативном, так и в программном режиме, и выполняют очень простое внедрение зависимостей на основе аннотаций.
Я вспоминаю последний пример кода из моего предыдущего поста — вот как выглядит простое поведение:
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 / Поведение.