Reputation: 9741
Here's my use case:
I need to do some generic operation before and after each method of a given class, which is based on the parameter(s) of the method. For example:
void process(Processable object) {
LOGGER.log(object.getDesc());
object.process();
}
class BaseClass {
String method1(Object o){ //o may or may not be Processable(add process logic only in former case)
if(o intstanceof Prcessable){
LOGGER.log(object.getDesc());
object.process();
}
//method logic
}
}
My BaseClass
has a lot of methods and I know for a fact that the same functionality will be added to several similar classes as well in future.
Is something like the following possible?
@MarkForProcessing
String method1(@Process Object o){
//method logic
}
PS: Can AspectJ/guice be used? Also want to know how to implement this from scratch for understanding.
Edit: Forgot to mention, what I have tried.(Not complete or working)
public @interface MarkForProcessing {
String getMetadata();
}
final public class Handler {
public boolean process(Object instance) throws Exception {
Class<?> clazz = instance.getClass();
for(Method m : clazz.getDeclaredMethods()) {
if(m.isAnnotationPresent(LocalSource.class)) {
LocalSource annotation = m.getAnnotation(MarkForProcessing.class);
Class<?> returnType = m.getReturnType();
Class<?>[] inputParamTypes = m.getParameterTypes();
Class<?> inputType = null;
// We are interested in just 1st param
if(inputParamTypes.length != 0) {
inputType = inputParamTypes[0];
}
// But all i have access to here is just the types, I need access to the method param.
}
return false;
}
return false;
}
Upvotes: 2
Views: 2793
Reputation: 43797
Yes, it can be done. Yes, you can use AspectJ. No, Guice would only be tangentially related to this problem.
The traditional aspect approach creates a proxy which is basically a subclass of the class you've given it (e.g. a subclass of BaseClass) but that subclass is created at runtime. The subclass delegates to the wrapped class for all methods. However, when creating this new subclass you can specify some extra behavior to add before or after (or both) the call to the wrapped class. In other words, if you have:
public class Foo() {
public void doFoo() {...}
}
Then the dynamic proxy would be a subclass of Foo created at runtime that looks something like:
public class Foo$Proxy {
public void doFoo() {
//Custom pre-invocation code
super.doFoo();
//Custom post-invocation code
}
}
Actually creating a dynamic proxy is a magical process known as bytecode manipulation. If you want to to do that yourself you can use tools such as cglib or asm. Or you can use JDK dynamic proxies. The main downside to JDK proxies are that they can only wrap interfaces.
AOP tools like AspectJ provide an abstraction on top of the raw bytecode manipulation for doing the above (you can do a lot with bytecode manipulation, adding behavior before and after methods is all aspects allow). Typically they define 'Aspect's which are classes that have special methods called 'advice' along with a 'pointcut' which defines when to apply that advice. In other words you may have:
@Aspect
public class FooAspect {
@Around("@annotation(MarkForProcessing)")
public void doProcessing(final ProceedingJoinPoint joinPoint) throws Throwable
{
//Do some before processing
joinPoint.proceed(); //Invokes the underlying method
//Do some after processing
}
}
The aspect is FooAspect, the advice is doProcessing, and the pointcut is "@annotation(MarkForProcessing)" which matches all methods that are annotated with @MarkForProcessing. It's worth pointing out that the ProceedingJoinPoint will have a reference to the actual parameter values (unlike the java.lang.reflect.Method
)
The last step is actually applying your aspect to an instance of your class. Typically this is either done with a container (e.g. Guice or Spring). Most containers have some way of knowing about a collection of aspects and when to apply them to classes constructed by that container. You can also do this programmatically. For example, with AspectJ you would do:
AspectJProxyFactory factory = new AspectJProxyFactory(baseClassInstance);
factory.addAspect(FooAspect.class);
BaseClass proxy = factory.getProxy();
Last, but not least, there are AOP implementations which use compile-time "weaving" which is a second compilation step run on the class files that applies the aspects. In other words, you don't have to do the above or use a container, the aspect will be injected into the class file itself.
Upvotes: 3