Статьи

КАК: Стереотипирование Java-класса

В этой статье я покажу, как мы можем стереотипировать класс с другим классом. Почему это полезно?

  • Когда в вашем проекте происходит много BCI, не стоит позволять каждому разработчику писать код BCI.
    • С одной стороны, это не абстрагирует используемую библиотеку BCI. Таким образом, изменение самой библиотеки становится трудным.
    • BCI сложен. Вероятность появления ошибок возрастает, если каждый разработчик должен понимать BCI.
    • Было бы лучше написать каркас, который позволяет разработчику написать интерфейс и класс, который можно использовать для BCI’ing.
  • Учитывая, что Java не поддерживает множественное наследование, стереотипирование может использоваться для достижения множественного наследования без делегирования. Проверьте здесь для нескольких вариантов наследования.
  • Существуют такие аспекты кода, как профилирование, которые должны присутствовать, только если код тестируется. Производственный код лучше всего без разбрызгивания кода отладки. В этом случае можно использовать стереотипирование, полностью изменив загрузчик классов, используемый для загрузки классов: тот, который добавляет код профиля, а другой — нет.

StereoTyping означает …

Скажем, был интерфейс:

1
2
3
4
5
6
public interface PerfInterface
{
    public void start(String nm);
    public void end();
    public String getValue(String value);
}

Вы пишете реализацию для этого интерфейса:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import java.util.Stack;
 
public class PerfTemplate implements PerfInterface
{
    private Stack _stats;
 
    public void start(String nm)
    {
        PerfStats stat = new PerfStats();
        stat.start(nm);
        if (_stats == null)
            _stats = new Stack();
 
        _stats.push(stat);
    }
 
    public void end()
    {
        try
        {
            PerfStats stat = (PerfStats)_stats.pop();
            stat.end();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
 
    public String getValue(String val)
    {
        return "PerfTemplate:Modified:" + val;
    }
}

Вы хотите, чтобы все классы, которым требуется реализовать интерфейс, использовали вышеописанную реализацию без фактического кодирования разработчиком. Во время выполнения вы хотите, чтобы класс адаптировал это поведение. Например, вы хотите закодировать класс как:

1
2
3
4
public class ClassToStereoType
{
    .....
}

Который не реализует PerfInterface. Но когда вы запускаете его для производительности, вы хотите, чтобы класс был:

1
2
3
4
public class ClassToStereoType implements PerfInterface
{
    .....
}

со всеми реализованными функциями PerfInterface. Это называется стереотипами.

Как стереотипировать?

Здесь мы будем использовать библиотеку asm для стереотипирования. Мы будем следовать тем же шагам, что и в « BCI во время выполнения ». Мы создадим объект ClassNode из класса, из которого мы должны стереотипировать, как показано ниже:

1
2
3
4
InputStream nstr = new FileInputStream("PerfTemplate.class");
        ClassReader n = new ClassReader(nstr);
        ClassNode cn = new ClassNode();
        n.accept(cn, ClassReader.EXPAND_FRAMES);

Здесь мы читаем класс PerfTemplate.class и принимаем его в ClassNode, который теперь содержит все поля и методы из класса PerfTemplate.

Мы напишем ClassVisitor, который переопределяет visitEnd, чтобы добавить поля и методы из созданного ClassNode.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
public void visitEnd()
        {
            System.out.println("In visit End. Adding Fields");
 
            for (Iterator it = _cn.fields.iterator(); it.hasNext();)
            {
                ((FieldNode) it.next()).accept(cv);
            }
 
            for(Iterator it = _cn.methods.iterator(); it.hasNext();)
            {
                MethodNode mn = (MethodNode) it.next();
                if (!mn.name.equals("")) //ignore constructor
                {
                    String[] exceptions = new String[mn.exceptions.size()];
                    mn.exceptions.toArray(exceptions);
                    MethodVisitor mv = cv.visitMethod( mn.access, mn.name, mn.desc, mn.signature, exceptions);
                    mn.instructions.resetLabels();
                    mn.accept(new RemappingMethodAdapter( mn.access, mn.desc, mv, new SimpleRemapper(_cn.name, _name)));
                }
            }            super.visitEnd();
        }

В приведенном выше коде мы перебираем поля и добавляем его в изменяемый класс (т. Е. ClassToStereoType). При добавлении методов мы должны быть осторожны, чтобы все ссылки на класс PerfTemplate были изменены на ClassToStereoType. Для этого мы используем RemappingMethodAdapter, который является классом, предоставленным asm.

Чтобы добавить интерфейсы из PerfTemplate в ClassToStereoType, мы переопределяем метод посещения. Здесь мы добавляем интерфейсы из ClassNode в текущий класс.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
public void visit (int version, int access, String name, String signature, String superName, String[] interfaces)
        {
            System.out.println("Class Name is: " + name + ":" + signature + ":" + superName);
            int len = 0;
            List ndeints = _cn.interfaces;
            if (interfaces != null) len = interfaces.length;
            String[] modinterfaces = new String[len + ndeints.size()];
            int cnt = 0;
            for (cnt = 0; (interfaces != null) && ( cnt < interfaces.length); cnt++)
            {
                modinterfaces[cnt] = interfaces[cnt];
            }
 
            for (String inter : ndeints)
                modinterfaces[cnt++] = inter;
            cv.visit(version, Opcodes.ACC_PUBLIC, name, signature, superName, modinterfaces);
            _name = name;
        }

В приведенном выше коде мы добавляем все интерфейсы из ClassNode, полученные путем вызова _cn.interfaces к интерфейсам ClassToStereoType. Мы используем этот модифицированный список интерфейсов для посещения класса. Это гарантирует, что интерфейсы реализованы в загруженном классе.

Теперь ClassVisitor, реализованный с этими изменениями, можно использовать для изменения байтов в загрузчике классов, чтобы стереотипировать класс.

Код этого HOWTO можно найти здесь . Запустите команды в compiletst.sh, чтобы опробовать пример.

Ссылка: КАК: Стереотипирование Java-класса от нашего партнера JCG Раджи Санкара в блоге Reflections .