ru.saidgadjiev.proxymaker.ProxyMaker Maven / Gradle / Ivy
The newest version!
package ru.saidgadjiev.proxymaker;
import ru.saidgadjiev.logger.Log;
import ru.saidgadjiev.logger.LoggerFactory;
import ru.saidgadjiev.proxymaker.bytecode.ByteCodeUtils;
import ru.saidgadjiev.proxymaker.bytecode.CodeAttribute;
import ru.saidgadjiev.proxymaker.bytecode.FieldInfo;
import ru.saidgadjiev.proxymaker.bytecode.MethodInfo;
import ru.saidgadjiev.proxymaker.bytecode.constantpool.ConstantPool;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import static ru.saidgadjiev.proxymaker.ProxyFactoryHelper.*;
/**
* Class for create dynamic proxy classes.
*/
public class ProxyMaker {
/**
* Logger.
*/
private static final Log LOGGER = LoggerFactory.getLogger(ProxyMaker.class);
/**
* Object class {@link Class}.
*/
private static final Class> OBJECT_CLASS = Object.class;
/**
* Proxy method invocation handler field name in dynamic proxy class.
*/
private static final String HANDLER_FIELD_NAME = "handler";
/**
* Proxy invocation handler type for bytecode.
*/
private static final String HANDLER_TYPE = 'L' + MethodHandler.class.getName().replace('.', '/') + ';';
/**
* Invocation handler setter method name {@link Proxy#setHandler(MethodHandler)}.
*/
private static final String HANDLER_SETTER_METHOD_NAME = "setHandler";
/**
* Invocation handler setter method type.
*/
private static final String HANDLER_SETTER_METHOD_TYPE = "(" + HANDLER_TYPE + ")V";
/**
* Handler getter method name {@link Proxy#getHandler()}.
*/
private static final String HANDLER_GETTER_METHOD_NAME = "getHandler";
/**
* Handler getter method type.
*/
private static final String HANDLER_GETTER_METHOD_TYPE = "()" + HANDLER_TYPE;
/**
* Proxied methods list holder field name.
*/
private static final String HOLDER_FIELD_NAME = "methods";
/**
* Proxied methods list holder field type. It is {@link List}.
*/
private static final String HOLDER_FIELD_TYPE = "L" + List.class.getName().replace('.', '/') + ";";
/**
* Proxy invocation handler method name.
*
* @see MethodHandler#invoke
*/
private static final String PROXY_INTERFACE_INVOKE_METHOD_NAME = "invoke";
/**
* Proxy invocation handler method description.
*/
private static final String PROXY_INTERFACE_INVOKE_METHOD_DESC =
"(Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;";
/**
* Invocation handler method definition count {@link MethodHandler#invoke(Method, Object[])}.
*/
private static final int PROXY_INTERFACE_INVOKE_METHOD_DEFINITION_COUTN = 3;
/**
* Super class.
*/
private Class> superClass;
/**
* Interfaces.
*/
private Class[] interfaces;
/**
* Fully qualified super class name.
*/
private String superClassName;
/**
* Fully qualified dynamic proxy class name.
*/
private String className;
/**
* Proxied methods list.
*/
private List methods = new ArrayList<>();
/**
* Cached dynamic proxy classes.
*/
private static final Map>> PROXY_CACHE = new WeakHashMap<>();
/**
* Unique integer index generator. Used for make dynamic proxy class name unique.
*/
private final UIDGenerator uidGenerator = new UIDGenerator() {
private AtomicInteger uid = new AtomicInteger(0);
@Override
public int nextUID() {
return uid.getAndIncrement();
}
};
/**
* Ignore override methods.
*/
private static final Map> OVERRIDE_IGNORE_METHODS = new HashMap>() {{
put("getClass", Object.class);
put("notify", Object.class);
put("notifyAll", Object.class);
put("wait", Object.class);
put("finalize", Object.class);
put("clone", Object.class);
}};
/**
* Provide dynamic proxy super class. If not provided it will be {@code OBJECT_CLASS}
*
* @param superClass target super class
* @return current instance
*/
public ProxyMaker superClass(Class> superClass) {
this.superClass = superClass;
return this;
}
/**
* Resolve super class and dynamic proxy class names.
* If super class is null super class will be {@code OBJECT_CLASS}.
* Class name make with concatenate {@code superClassName} and {@code uidGenerator} nextUID.
*/
private void resolveSuperClassAndClassName() {
if (interfaces == null) {
interfaces = new Class[0];
}
if (superClass == null) {
superClass = OBJECT_CLASS;
superClassName = OBJECT_CLASS.getName();
} else {
superClassName = superClass.getName();
}
className = superClassName + uidGenerator.nextUID();
if (className.startsWith("java.")) {
className = "ru.proxy.maker.tmp." + className;
}
}
/**
* Resolve dynamic proxy override methods.
* Ignore methods @{code OVERRIDE_IGNORE_METHODS}
*/
private void resolveMethods() {
this.methods = getMethods(superClass, interfaces);
}
/**
* Resolve override methods.
*
* @param superClass target super class
* @param interfaces target interfaces
* @return resolved methods
*/
private List getMethods(Class> superClass,
Class>[] interfaces) {
Map methods = new LinkedHashMap<>();
Set> visitedClasses = new HashSet<>();
for (Class> targetClass : interfaces) {
getMethods(methods, targetClass, visitedClasses);
}
getMethods(methods, superClass, visitedClasses);
return new ArrayList<>(methods.values());
}
/**
* Recursively resolve methods by target class.
*
* @param methods map for save resolved methods
* @param targetClass target class {@link Class}
* @param visitedClasses visited classes. This both speeds up scanning by avoiding duplicate interfaces and
* is needed to ensure that superinterfaces are always scanned before subinterfaces.
*/
private void getMethods(Map methods,
Class> targetClass,
Set> visitedClasses) {
if (!visitedClasses.add(targetClass)) {
return;
}
Class>[] interfaces = targetClass.getInterfaces();
for (Class> interfaceClass : interfaces) {
getMethods(methods, interfaceClass, visitedClasses);
}
Class> parentClass = targetClass.getSuperclass();
if (parentClass != null) {
getMethods(methods, parentClass, visitedClasses);
}
for (Method method : targetClass.getDeclaredMethods()) {
if (!Modifier.isPrivate(method.getModifiers())
&& !(OVERRIDE_IGNORE_METHODS.containsKey(method.getName())
&& OVERRIDE_IGNORE_METHODS.get(method.getName()).equals(method.getDeclaringClass()))) {
String key = method.getName() + ":" + ByteCodeUtils.makeDescriptor(method);
methods.put(key, method);
}
}
}
/**
* Add default constructor to class file {@link ClassFile}.
*
* @param classFile target class file
*/
private void addDefaultConstructor(ClassFile classFile) {
MethodInfo methodInfo = new MethodInfo(classFile.getConstantPool(), MethodInfo.NAME_INIT, "()V");
methodInfo.setAccessFlags(AccessFlag.PUBLIC);
CodeAttribute codeAttribute = new CodeAttribute(classFile.getConstantPool(), 0, 1);
codeAttribute.addAload(0);
codeAttribute.addInvokeSpecial(superClass.getName(), MethodInfo.NAME_INIT, "()V");
codeAttribute.addOpcode(CodeAttribute.Opcode.RETURN);
methodInfo.setCodeAttribute(codeAttribute);
classFile.addMethodInfo(methodInfo);
}
/**
* Create new dynamic proxy class instance.
*
* @param handler method handler for the proxy class
* @return new dynamic proxy class instance
* @throws InvocationTargetException throws in {@link Constructor#newInstance(Object...)}
* @throws IllegalAccessException throws in {@link Constructor#newInstance(Object...)}
* @throws NoSuchMethodException throws in {@link Class#getConstructor(Class[])}}
* @throws InstantiationException throws in {@link Constructor#newInstance(Object...)}
*/
public Object make(MethodHandler handler) throws
InvocationTargetException,
IllegalAccessException,
NoSuchMethodException,
InstantiationException {
synchronized (PROXY_CACHE) {
ClassLoader classLoader = getClassLoader();
Map> cacheForTheLoader = PROXY_CACHE.computeIfAbsent(classLoader, k -> new HashMap<>());
String key = getKey();
if (cacheForTheLoader.containsKey(key)) {
Class> cachedClass = cacheForTheLoader.get(key);
Object proxy = createInstance(cachedClass, handler);
LOGGER.debug("Retrieve class " + cachedClass + " from cache " + key);
return proxy;
} else {
LOGGER.debug("Cache miss for " + key);
}
Class> proxyClass = createClass(classLoader);
cacheForTheLoader.put(key, proxyClass);
LOGGER.debug("Put to cache " + key + " class " + proxyClass);
return createInstance(proxyClass, handler);
}
}
/**
* Create new dynamic proxy class instance.
*
* @param classLoader proxy class loader
* @return new dynamic proxy class
*/
private Class> createClass(ClassLoader classLoader) {
resolveSuperClassAndClassName();
resolveMethods();
ClassFile classFile = new ClassFile(className, superClassName);
setInterfaces(classFile, interfaces, Proxy.class);
classFile.setAccessFlags(AccessFlag.PUBLIC);
addDefaultConstructor(classFile);
addDefaultClassFields(classFile);
addClassInitializer(classFile);
addHandlerSetter(classFile);
addHandlerGetter(classFile);
overrideMethods(classFile);
return ProxyFactoryHelper.toClass(classFile, classLoader);
}
/**
* Get classloader.
*
* @return new class classloader
*/
private ClassLoader getClassLoader() {
ClassLoader loader = null;
if (superClass != null && !superClass.getName().equals("java.lang.Object")) {
loader = superClass.getClassLoader();
}
if (loader == null) {
loader = getClass().getClassLoader();
if (loader == null) {
loader = Thread.currentThread().getContextClassLoader();
if (loader == null) {
loader = ClassLoader.getSystemClassLoader();
}
}
}
return loader;
}
/**
* Create new instance of dynamic proxy class {@code proxyClass} by default constructor.
*
* @param proxyClass target class
* @param handler dynamic proxy invocation handler
* @return new dynamic proxy class instance
* @throws NoSuchMethodException throws in {@link Class#getConstructor(Class[])}
* @throws IllegalAccessException throws in {@link Constructor#newInstance(Object...)
* @throws InvocationTargetException throws in {@link Constructor#newInstance(Object...)
* @throws InstantiationException throws in {@link Constructor#newInstance(Object...)
*/
private Object createInstance(Class> proxyClass, MethodHandler handler) throws
NoSuchMethodException,
IllegalAccessException,
InvocationTargetException,
InstantiationException {
Constructor> constructor = proxyClass.getConstructor();
Object proxyInstance = constructor.newInstance();
((Proxy) proxyInstance).setHandler(handler);
return proxyInstance;
}
/**
* Return key for cached proxy class.
*
* @return this dynamic proxy class cached ky
*/
private String getKey() {
StringBuilder builder = new StringBuilder();
builder.append(superClass.getName());
if (interfaces != null && interfaces.length > 0) {
builder.append(":");
for (Class> interfaceClass : interfaces) {
builder.append(interfaceClass.getName());
}
}
return builder.toString();
}
/**
* Add default dynamic proxy class fields.
* Add field for holder proxied methods, field for invocation handler {@link MethodHandler}.
*
* @param classFile target class file
*/
private void addDefaultClassFields(ClassFile classFile) {
FieldInfo handlerField = new FieldInfo(classFile.getConstantPool(), HANDLER_FIELD_NAME, HANDLER_TYPE);
handlerField.setAccessFlags(AccessFlag.PRIVATE);
classFile.addFieldInfo(handlerField);
FieldInfo holderField = new FieldInfo(classFile.getConstantPool(), HOLDER_FIELD_NAME, HOLDER_FIELD_TYPE);
holderField.setAccessFlags(AccessFlag.PRIVATE | AccessFlag.STATIC);
classFile.addFieldInfo(holderField);
}
/**
* Add invocation handler setter method {@link Proxy#setHandler(MethodHandler)}.
*
* @param classFile target class file
*/
private void addHandlerSetter(ClassFile classFile) {
MethodInfo methodInfo = new MethodInfo(
classFile.getConstantPool(),
HANDLER_SETTER_METHOD_NAME,
HANDLER_SETTER_METHOD_TYPE
);
methodInfo.setAccessFlags(AccessFlag.PUBLIC);
CodeAttribute codeAttribute = new CodeAttribute(classFile.getConstantPool(), 2, 2);
codeAttribute.addAload(0);
codeAttribute.addAload(1);
codeAttribute.addPutField(className, HANDLER_FIELD_NAME, HANDLER_TYPE);
codeAttribute.addOpcode(CodeAttribute.Opcode.RETURN);
methodInfo.setCodeAttribute(codeAttribute);
classFile.addMethodInfo(methodInfo);
}
/**
* Add invocation handler getter method {@link Proxy#getHandler()}.
*
* @param classFile target class file
*/
private void addHandlerGetter(ClassFile classFile) {
MethodInfo methodInfo = new MethodInfo(
classFile.getConstantPool(),
HANDLER_GETTER_METHOD_NAME, HANDLER_GETTER_METHOD_TYPE
);
methodInfo.setAccessFlags(AccessFlag.PUBLIC);
CodeAttribute code = new CodeAttribute(classFile.getConstantPool(), 1, 1);
code.addAload(0);
code.addGetField(className, HANDLER_FIELD_NAME, HANDLER_TYPE);
code.addOpcode(CodeAttribute.Opcode.ARETURN);
methodInfo.setCodeAttribute(code);
classFile.addMethodInfo(methodInfo);
}
/**
* Add static block to dynamic proxy class for resolve proxy method refs.
*
* @param classFile target class file.
*/
private void addClassInitializer(ClassFile classFile) {
MethodInfo methodInfo = new MethodInfo(classFile.getConstantPool(), MethodInfo.NAME_CLINIT, "()V");
methodInfo.setAccessFlags(AccessFlag.STATIC);
CodeAttribute codeAttribute = new CodeAttribute(classFile.getConstantPool(), 0, 0);
codeAttribute.addNew(ArrayList.class.getName());
codeAttribute.addOpcode(CodeAttribute.Opcode.DUP);
codeAttribute.addInvokeSpecial(ArrayList.class.getName(), MethodInfo.NAME_INIT, "()V");
codeAttribute.addPutStatic(className, HOLDER_FIELD_NAME, HOLDER_FIELD_TYPE);
codeAttribute.addGetstatic(className, HOLDER_FIELD_NAME, HOLDER_FIELD_TYPE);
for (Iterator methodIterator = methods.iterator(); methodIterator.hasNext();) {
addCallFindMethod(codeAttribute, methodIterator.next());
if (methodIterator.hasNext()) {
codeAttribute.addGetstatic(className, HOLDER_FIELD_NAME, HOLDER_FIELD_TYPE);
}
}
codeAttribute.addOpcode(CodeAttribute.Opcode.RETURN);
methodInfo.setCodeAttribute(codeAttribute);
classFile.addMethodInfo(methodInfo);
}
/**
* Add static call {@link ProxyRuntimeHelper#findMethod(String, String, String[])} for find method in super class.
*
* @param code target code attribute
* @param method target method
*/
private void addCallFindMethod(CodeAttribute code, Method method) {
String findClass = ProxyRuntimeHelper.class.getName();
String findDesc
= "(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)Ljava/lang/reflect/Method;";
code.addLdc(superClassName);
code.addLdc(method.getName());
code.addIconst(method.getParameterTypes().length);
code.addAnewarray(String.class.getName());
if (method.getParameterTypes().length > 0) {
int iconstNumber = 0;
code.addOpcode(CodeAttribute.Opcode.DUP);
for (Iterator> parametrTypeIterator = Arrays.asList(method.getParameterTypes()).iterator();
parametrTypeIterator.hasNext();) {
code.addIconst(iconstNumber++);
code.addLdc(parametrTypeIterator.next().getName());
code.addOpcode(CodeAttribute.Opcode.AASTORE);
if (parametrTypeIterator.hasNext()) {
code.addOpcode(CodeAttribute.Opcode.DUP);
}
}
}
code.addInvokeStatic(findClass, "findMethod", findDesc);
code.addInvokeInterface(List.class.getName(), "add", "(Ljava/lang/Object;)Z", 2);
code.addOpcode(CodeAttribute.Opcode.POP);
}
/**
* Override methods in dynamic proxy class.
*
* @param classFile target class file
*/
private void overrideMethods(ClassFile classFile) {
int methodIndex = 0;
for (Method method : methods) {
MethodInfo methodInfo = new MethodInfo(
classFile.getConstantPool(),
method.getName(),
ByteCodeUtils.makeDescriptor(method)
);
methodInfo.setAccessFlags(Modifier.FINAL | Modifier.PUBLIC
| (method.getModifiers() & ~(Modifier.PRIVATE
| Modifier.PROTECTED
| Modifier.ABSTRACT
| Modifier.NATIVE
| Modifier.SYNCHRONIZED)));
methodInfo.setCodeAttribute(toMethodCode(classFile.getConstantPool(), method, methodIndex));
classFile.addMethodInfo(methodInfo);
++methodIndex;
}
}
/**
* Create proxied method code in dynamic proxy class.
*
* @param constantPool target constant pool
* @param method target method
* @param methodIndex target method index from methods list field in dynamic proxy class
* @return proxied method code which proxy method call to invocation handler.
*/
private CodeAttribute toMethodCode(ConstantPool constantPool, Method method, int methodIndex) {
int argsSize = method.getParameterCount();
CodeAttribute codeAttribute = new CodeAttribute(constantPool, 0, argsSize + 2);
codeAttribute.addAload(0);
codeAttribute.addGetField(className, HANDLER_FIELD_NAME, HANDLER_TYPE);
codeAttribute.addGetstatic(className, HOLDER_FIELD_NAME, HOLDER_FIELD_TYPE);
codeAttribute.addIconst(methodIndex);
codeAttribute.addInvokeInterface(List.class.getName(), "get", "(I)Ljava/lang/Object;", 2);
codeAttribute.addCheckCast("java/lang/reflect/Method");
makeParameterList(codeAttribute, method.getParameterTypes());
codeAttribute.addInvokeInterface(
MethodHandler.class.getName(),
PROXY_INTERFACE_INVOKE_METHOD_NAME,
PROXY_INTERFACE_INVOKE_METHOD_DESC,
PROXY_INTERFACE_INVOKE_METHOD_DEFINITION_COUTN
);
Class retType = method.getReturnType();
addUnWrapper(codeAttribute, retType);
addReturn(codeAttribute, retType);
return codeAttribute;
}
/**
* Make invoke handler args array.
*
* @param code target method code
* @param params target arg types
*/
private static void makeParameterList(CodeAttribute code, Class[] params) {
int regno = 1;
int argsSize = params.length;
code.addIconst(argsSize);
code.addAnewarray("java/lang/Object");
for (int i = 0; i < argsSize; i++) {
code.addOpcode(CodeAttribute.Opcode.DUP);
code.addIconst(i);
Class type = params[i];
if (type.isPrimitive()) {
regno = makeWrapper(code, type, regno);
} else {
code.addAload(regno);
regno++;
}
code.addOpcode(CodeAttribute.Opcode.AASTORE);
}
}
/**
* Make wrapper for primitive types. Example: 2->new Integer(2)...
*
* @param code target method code
* @param type primitive type class {@link Class}
* @param regno arg index in args array
* @return regno + {@link ProxyFactoryHelper#DATA_SIZE}
*/
private static int makeWrapper(CodeAttribute code, Class type, int regno) {
int index = typeIndex(type);
String wrapper = WRAPPER_TYPES[index];
code.addNew(wrapper);
code.addOpcode(CodeAttribute.Opcode.DUP);
addLoad(code, regno, type);
code.addInvokeSpecial(wrapper, "",
WRAPPER_DESC[index]);
return regno + DATA_SIZE[index];
}
/**
* Push n to local variable.
*
* @param code target method code
* @param n target variable
* @param type primitive type class {@link Class}
*/
private static void addLoad(CodeAttribute code, int n, Class type) {
if (type.isPrimitive()) {
if (type == Long.TYPE) {
code.addLload(n);
} else if (type == Float.TYPE) {
code.addFload(n);
} else if (type == Double.TYPE) {
code.addDload(n);
} else {
code.addIload(n);
}
} else {
code.addAload(n);
}
}
/**
* Add unwrap methods for return value. For primitive type int it will be {@link Integer#intValue()}.
* Another add checkcast{@link CodeAttribute#addCheckCast(String)} instruction.
*
* @param code target method code
* @param type method arg type
*/
private static void addUnWrapper(CodeAttribute code, Class type) {
if (type.isPrimitive()) {
if (type == Void.TYPE) {
code.addOpcode(CodeAttribute.Opcode.POP);
} else {
int index = typeIndex(type);
String wrapper = WRAPPER_TYPES[index];
code.addCheckCast(wrapper);
code.addInvokeVirtual(wrapper,
UNWARP_METHODS[index],
UNWRAP_DESC[index]);
}
} else {
code.addCheckCast(type.getName());
}
}
/**
* Add return instruction to method.
*
* @param code target method code
* @param type arg type {@link Class}
*/
private static void addReturn(CodeAttribute code, Class type) {
if (type.isPrimitive()) {
if (type == Long.TYPE) {
code.addOpcode(CodeAttribute.Opcode.LRETURN);
} else if (type == Float.TYPE) {
code.addOpcode(CodeAttribute.Opcode.FRETURN);
} else if (type == Double.TYPE) {
code.addOpcode(CodeAttribute.Opcode.DRETURN);
} else if (type == Void.TYPE) {
code.addOpcode(CodeAttribute.Opcode.RETURN);
} else {
code.addOpcode(CodeAttribute.Opcode.IRETURN);
}
} else {
code.addOpcode(CodeAttribute.Opcode.ARETURN);
}
}
/**
* Set interfaces.
*
* @param cf target class file
* @param interfaces target interfaces
* @param proxyClass target proxy interface
*/
private static void setInterfaces(ClassFile cf, Class[] interfaces, Class proxyClass) {
Collection interfaceNames = new ArrayList<>();
for (Class> interfaceClass : interfaces) {
interfaceNames.add(interfaceClass.getName());
}
interfaceNames.add(proxyClass.getName());
cf.setInterfaces(interfaceNames);
}
/**
* Interface represent uid generator which would generate unique uids.
*/
private interface UIDGenerator {
/**
* This method will be called for generate next unique uid.
*
* @return next uid
*/
int nextUID();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy