14

Is there any way to make a sort of "supermethod" that is called every time a method is called, even for non-defined methods? Sort of like this:

public void onStart() {
    System.out.println("Start");
}

public void onEnd() {
    System.out.println("End");
}

public SuperMethod superMethod() {
    System.out.println("Super");
}

// "Start"
// "Super"
onStart();

// "End"
// "Super"
onEnd();

// "Super"
onRun();

Edit - Specifics: I have a library that updates a lot and gets reobfuscated on each update. To make my workflow easier I am making my program automatically update the library (required to do what I want it to do, I won't go that specific on why, but my program will work with future updates) and I have the obfuscation mappings download with the library, I want to make a sort of proxy called Library for example and then when I call Library.getInstance() it will get the obfuscation mapping for getInstance() and call the library's method getInstance() or abz as it is mapped to at this current moment in time.

ROMANIA_engineer
  • 54,432
  • 29
  • 203
  • 199
Jordan Doyle
  • 2,976
  • 4
  • 22
  • 38
  • 1
    Why would you like to do this? – user1983983 Nov 08 '13 at 14:17
  • 1
    Note that it's confusing to use the word `super`, since it already means something else in java. – keyser Nov 08 '13 at 14:17
  • 9
    You need to get familiar with Aspects. – Konstantin Yovkov Nov 08 '13 at 14:18
  • It's not possible in plain Java but with AOP (Aspect Oriented Programming) it's possible to invoke methods before/after methods are called. An example of an AOP framework would be Spring AOP (http://docs.spring.io/spring/docs/2.5.3/reference/aop.html). – helpermethod Nov 08 '13 at 14:18
  • AOP or Filters come to mind. Either should work here. Filters might be slightly less of a footprint. – EdgeCase Nov 08 '13 at 14:20
  • I need to run some reflection code to run a method in a library (the library is always changing and I have the method and class mappings in a local file - it's obfuscated) with reflection. @user1983983 – Jordan Doyle Nov 08 '13 at 14:21
  • 1
    What about [Proxy](http://docs.oracle.com/javase/7/docs/api/java/lang/reflect/Proxy.html)? – tom Nov 08 '13 at 14:22
  • I have added specifics on what I need this for @user1983983 – Jordan Doyle Nov 08 '13 at 14:34

5 Answers5

9

Sure you can do this, not with standard java but with AspectJ

Here is a simple example:

Aspect with an after-advice

package net.fsa.aspectj.test;


public aspect SuperMethdAspect {

    pointcut afterPointCut() : execution(public * com.my.pack.age.MyClass.*(..));

    after() : afterPointCut() {
        System.out.println("Super");
    }
}

You target class

package com.my.pack.age;

public class MyClass {

    public void onStart() {
        System.out.println("Start");
    }

    public void onEnd() {
        System.out.println("End");
    }
}

And finally some test app

package net.fsa.aspectj.test;

import com.my.pack.age.MyClass;

public class MyApp {

    public static void main(String... args) {
        MyClass myClass = new MyClass();
        myClass.onStart();
        myClass.onEnd();
    }
}

Output

Start
Super
End
Super
A4L
  • 17,353
  • 6
  • 49
  • 70
  • Thanks for this. Is there anyway to make `com.my.pack.age.MyClass` dynamic? The library's mappings are changing all the time. – Jordan Doyle Nov 08 '13 at 14:47
  • @JordanDoyle the [expression for the pointcut](http://www.eclipse.org/aspectj/doc/next/progguide/semantics-pointcuts.html) allows to do a lot of combinations. If you don't knwo the set of packages/classes you want to intercet then you might need more that one aspect, one per library for example with the actual functionality placed in some other class. If you want to target alls classes in a package then you can use `com.my.pack.age.*.*` – A4L Nov 08 '13 at 14:58
  • @JordanDoyle If you can't compile target classes along with the aspect then you absolutely need to consider [load time weaving](http://www.eclipse.org/aspectj/doc/next/devguide/ltw.html) – A4L Nov 08 '13 at 15:02
9

Here is an implementation in pure Java using the Proxy class:

import java.lang.reflect.*;
import java.util.*;

public class Demo
{
    public static void main(String[] args)
    {
        Map<String, String> map = new HashMap<String, String>();
        map.put("onStart", "abc");
        map.put("onEnd", "def");
        Library library = new LibraryProxy(map, new LibraryImpl()).proxy();
        library.onStart();
        library.onEnd();
        library.onRun();
    }
}

interface Library
{
    void onStart();
    void onEnd();
    void onRun();
}

class LibraryImpl
{
    public void abc() { System.out.println("Start"); }
    public void def() { System.out.println("End"); }
}

class LibraryProxy implements InvocationHandler
{
    Map<String, String> map;
    Object impl;

    public LibraryProxy(Map<String, String> map, Object impl)
    {
        this.map = map;
        this.impl = impl;
    }

    public Library proxy()
    {
        return (Library) Proxy.newProxyInstance(Library.class.getClassLoader(),
            new Class[] { Library.class }, this);
    }

    @Override
    public Object invoke(Object proxy, Method m, Object[] args) throws Throwable
    {
        Object res = null;
        String name = map.get(m.getName());
        if (name == null) {
            System.out.println("[" + m.getName() + " is not defined]");
        } else {
            m = impl.getClass().getMethod(name, m.getParameterTypes());
            res = m.invoke(impl, args);
        }
        System.out.println("super duper");
        return res;
    }
}

Output:

Start
super duper
End
super duper
[onRun is not defined]
super duper
tom
  • 21,844
  • 6
  • 43
  • 36
  • That's absolutely perfect and exactly what I required. Thanks =D – Jordan Doyle Nov 08 '13 at 18:14
  • Is there any way to proxy fields @tom? Thanks. – Jordan Doyle Nov 08 '13 at 21:16
  • 1
    @JordanDoyle Not directly, but you can add getters/setters to the interface and have the proxy translate them to field access. – tom Nov 09 '13 at 02:22
  • what if Library is a class with parameterized constructors? – Venkatesh Jun 15 '17 at 06:45
  • @Venkatesh: Constructors don't have names so they won't be obfuscated. You can create an instance of the obfuscated class normally and then pass it to the proxy. Using my example, if `LibraryImpl` had a constructor such as `LibraryImpl(String s, int i)` you would use `new LibraryProxy(map, new LibraryImpl("x", 1)).proxy()`. – tom Jun 15 '17 at 21:59
  • @Venkatesh: This post is over three years old. If you need more help, please ask a separate question. – tom Jun 15 '17 at 22:04
4

Java doesn't really allow magic like this. In order for a call to happen, it has to appear within your (compiled) code. So the answer is no, not without explicitly adding a call to the relevant methods. However, you can hide that somewhat by using a preprocessor or runtime code generation.

I think AspectJ might be what you want.

Antimony
  • 37,781
  • 10
  • 100
  • 107
0

I guess that it is not exactly what you want, but you could wrap all code in methods into try {}finally {supermethod ()}. That would garantuee that supermethod is called.

Theolodis
  • 4,977
  • 3
  • 34
  • 53
0

The Term behind this concept is called interceptor. You need a container that does this like a ApplicationServer, CDI Container, Spring or AOP. It works like this

1. call your Method
2. pause your Method
3. call intercepter
4. do interceptor stuff
5. resume your Method inside the interceptor
Lukas Eichler
  • 5,689
  • 1
  • 24
  • 43