Runtime Proxy Generator
This utility library helps to generate runtime proxies for non-final classes to inject additional actions or override method logic using inheritance mechanism.
Table of contents
Getting started
To add library to your project perform next steps:
Maven
Add the following dependency to your pom.xml:
<dependency>
<groupId>com.github.vladislavsevruk</groupId>
<artifactId>runtime-proxy-generator</artifactId>
<version>1.0.2</version>
</dependency>
Gradle
Add the following dependency to your build.gradle:
implementation 'com.github.vladislavsevruk:runtime-proxy-generator:1.0.2'
Usage
Implement ProxySourceCodeGenerator
First of all you need to implement ProxySourceCodeGenerator interface to provide source code of proxy class to generate. Library has BaseProxySourceCodeGenerator as base implementation with common logic of this interface (like generating constructors that match target type) so you can extend this class and override necessary methods for generating certain class members:
public class SimpleProxySourceGenerator extends BaseProxySourceCodeGenerator {
// overriding generators for proxy methods
@Override
protected Collection<ClassElementGenerator> getMethodsDeclaration(Class<?> clazz) {
return Collections.singletonList(new DelegateProxyMethodGenerator(clazz));
}
}
public class DelegateProxyMethodGenerator extends AbstractProxyMethodGenerator {
public DelegateProxyMethodGenerator(Class<?> delegatedClass) {
super(delegatedClass);
}
// simply delegate call to superclass
@Override
protected String getProxyMethodBodyContent(JavaClassGeneratorConfig config, Method originalMethod,
String delegateCall) {
return String.format("%s%s;", getReturnKeyWordIfRequired(originalMethod), delegateCall);
}
}
You may also find AbstractProxyMethodGenerator useful if all overridden methods should have common logic. You can simply implement its getProxyMethodBodyContent method to generate all methods of proxy class with similar behavior:
public class LoggingProxyMethodGenerator extends AbstractProxyMethodGenerator {
public LoggingProxyMethodGenerator(Class<?> delegatedClass) {
super(delegatedClass);
}
// add debug logging before method call
@Override
protected String getProxyMethodBodyContent(JavaClassGeneratorConfig config, Method originalMethod,
String delegateCall) {
StringBuilder stringBuilder = new StringBuilder("logger.debug(\"Calling '")
.append(originalMethod.getName()).append("' method.\");\n");
return doubleIndents(stringBuilder, config).append(getReturnKeyWordIfRequired(originalMethod))
.append(delegateCall).append(";").toString();
}
}
public class LoggingProxySourceGenerator extends BaseProxySourceCodeGenerator {
// overriding generators for imports
@Override
protected Collection<ClassImportGenerator> getImportsDeclaration(Class<?> clazz) {
return Collections.singletonList((config, schemaObject) -> Arrays
.asList("import org.apache.logging.log4j.LogManager;\n",
"import org.apache.logging.log4j.Logger;\n"));
}
// overriding generators for fields
protected Collection<ClassElementGenerator> getFieldsDeclaration(Class<?> clazz) {
return Collections.singletonList((config, schemaObject) -> String.format(
"%sprivate static final Logger logger = LogManager.getLogger(%s.class);%n%n",
config.getIndent().value(), schemaObject.getName()));
}
// overriding generators for proxy methods
@Override
protected Collection<ClassElementGenerator> getMethodsDeclaration(Class<?> clazz) {
return Collections.singletonList(new LoggingProxyMethodGenerator(clazz));
}
}
NOTE: AbstractProxyMethodGenerator generates source code that doesn't override static, final or methods from java.lang.Object
class.
Generate proxy class instance
To generate new proxy instance you need to use ProxyFactory class:
class Cake {
Cake() {
}
...
}
ProxyFactory<Cake> proxyFactory = new ProxyFactory<>(Cake.class, new LoggingProxySourceGenerator());
// result is proxy class instance that extends 'Cake' class
Cake cake = proxyFactory.newInstance();
Please note that proxy class cannot be generated for final classes:
final class Cake {
Cake() {
}
...
}
ProxyFactory<Cake> proxyFactory = new ProxyFactory<>(Cake.class, new LoggingProxySourceGenerator());
// result is 'Cake' class instance
Cake cake = proxyFactory.newInstance();
and for classes without any non-private constructor:
class Cake {
// the only constructor of class
private Cake() {
}
...
}
ProxyFactory<Cake> proxyFactory = new ProxyFactory<>(Cake.class, new LoggingProxySourceGenerator());
// 'IllegalArgumentException' is thrown as there is no any public constructor matching received parameters
Cake cake = proxyFactory.newInstance();
License
This project is licensed under the MIT License, you can read the full text here.