Files
redkale/src/main/java/org/redkale/cache/spi/CacheAsmMethodBoost.java
2024-06-05 22:39:33 +08:00

254 lines
11 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
*
*/
package org.redkale.cache.spi;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.redkale.asm.AnnotationVisitor;
import org.redkale.asm.AsmMethodBean;
import org.redkale.asm.AsmMethodBoost;
import org.redkale.asm.Asms;
import org.redkale.asm.ClassWriter;
import org.redkale.asm.FieldVisitor;
import org.redkale.asm.Handle;
import org.redkale.asm.Label;
import org.redkale.asm.MethodVisitor;
import org.redkale.asm.Opcodes;
import static org.redkale.asm.Opcodes.*;
import org.redkale.asm.Type;
import org.redkale.cache.Cached;
import org.redkale.inject.ResourceFactory;
import org.redkale.service.LoadMode;
import org.redkale.util.RedkaleClassLoader;
import org.redkale.util.RedkaleException;
import org.redkale.util.ThrowSupplier;
import org.redkale.util.TypeToken;
/** @author zhangjx */
public class CacheAsmMethodBoost extends AsmMethodBoost {
private static final java.lang.reflect.Type FUTURE_VOID = new TypeToken<CompletableFuture<Void>>() {}.getType();
private static final List<Class<? extends Annotation>> FILTER_ANN = List.of(Cached.class, DynForCache.class);
private final Logger logger = Logger.getLogger(getClass().getSimpleName());
private Map<String, CacheAction> actionMap;
public CacheAsmMethodBoost(boolean remote, Class serviceType) {
super(remote, serviceType);
}
@Override
public List<Class<? extends Annotation>> filterMethodAnnotations(Method method) {
return FILTER_ANN;
}
@Override
public String doMethod(
ClassLoader classLoader,
ClassWriter cw,
String newDynName,
String fieldPrefix,
List filterAnns,
Method method,
final String newMethodName) {
Map<String, CacheAction> actions = this.actionMap;
if (actions == null) {
actions = new LinkedHashMap<>();
this.actionMap = actions;
}
Cached cached = method.getAnnotation(Cached.class);
if (cached == null) {
return newMethodName;
}
if (!LoadMode.matches(remote, cached.mode())) {
return newMethodName;
}
if (method.getAnnotation(DynForCache.class) != null) {
return newMethodName;
}
if (Modifier.isFinal(method.getModifiers()) || Modifier.isStatic(method.getModifiers())) {
throw new RedkaleException(
"@" + Cached.class.getSimpleName() + " cannot on final or static method, but on " + method);
}
if (!Modifier.isProtected(method.getModifiers()) && !Modifier.isPublic(method.getModifiers())) {
throw new RedkaleException(
"@" + Cached.class.getSimpleName() + " must on protected or public method, but on " + method);
}
if (method.getReturnType() == void.class || FUTURE_VOID.equals(method.getGenericReturnType())) {
throw new RedkaleException("@" + Cached.class.getSimpleName() + " cannot on void method, but on " + method);
}
final int actionIndex = fieldIndex.incrementAndGet();
final String rsMethodName = method.getName() + "_afterCache";
final String dynFieldName = fieldPrefix + "_" + method.getName() + "CacheAction" + actionIndex;
final AsmMethodBean methodBean = getMethodBean(method);
{ // 定义一个新方法调用 this.rsMethodName
final String cacheDynDesc = Type.getDescriptor(DynForCache.class);
final MethodVisitor mv = createMethodVisitor(cw, method, newMethodName, methodBean);
// mv.setDebug(true);
AnnotationVisitor av = mv.visitAnnotation(cacheDynDesc, true);
av.visit("dynField", dynFieldName);
Asms.visitAnnotation(av, cached);
visitRawAnnotation(method, newMethodName, mv, Cached.class, filterAnns);
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitVarInsn(ALOAD, 0);
List<Integer> insns = visitVarInsnParamTypes(mv, method, 0);
String dynDesc = methodBean.getDesc();
dynDesc = "(L" + newDynName + ";" + dynDesc.substring(1, dynDesc.lastIndexOf(')') + 1)
+ Type.getDescriptor(ThrowSupplier.class);
mv.visitInvokeDynamicInsn("get", dynDesc, Asms.createLambdaMetaHandle(), new Object[] {
org.redkale.asm.Type.getType("()Ljava/lang/Object;"),
new Handle(Opcodes.H_INVOKESPECIAL, newDynName, "lambda$" + actionIndex, methodBean.getDesc(), false),
org.redkale.asm.Type.getType("()" + Type.getDescriptor(method.getReturnType()))
});
mv.visitVarInsn(ASTORE, 1 + method.getParameterCount());
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, newDynName, dynFieldName, Type.getDescriptor(CacheAction.class));
mv.visitVarInsn(ALOAD, 1 + method.getParameterCount());
Asms.visitInsn(mv, method.getParameterCount());
mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
int insn = 0;
Class[] paramtypes = method.getParameterTypes();
for (int j = 0; j < paramtypes.length; j++) {
final Class pt = paramtypes[j];
mv.visitInsn(DUP);
insn++;
Asms.visitInsn(mv, j);
if (pt.isPrimitive()) {
if (pt == long.class) {
mv.visitVarInsn(LLOAD, insn++);
} else if (pt == float.class) {
mv.visitVarInsn(FLOAD, insn++);
} else if (pt == double.class) {
mv.visitVarInsn(DLOAD, insn++);
} else {
mv.visitVarInsn(ILOAD, insn);
}
Class bigclaz = TypeToken.primitiveToWrapper(pt);
mv.visitMethodInsn(
INVOKESTATIC,
bigclaz.getName().replace('.', '/'),
"valueOf",
"(" + Type.getDescriptor((Class) pt) + ")" + Type.getDescriptor(bigclaz),
false);
} else {
mv.visitVarInsn(ALOAD, insn);
}
mv.visitInsn(AASTORE);
}
String throwFuncDesc = Type.getDescriptor(ThrowSupplier.class);
mv.visitMethodInsn(
INVOKEVIRTUAL,
CacheAction.class.getName().replace('.', '/'),
"get",
"(" + throwFuncDesc + "[Ljava/lang/Object;)Ljava/lang/Object;",
false);
mv.visitTypeInsn(CHECKCAST, method.getReturnType().getName().replace('.', '/'));
mv.visitInsn(ARETURN);
Label l2 = new Label();
mv.visitLabel(l2);
mv.visitLocalVariable("this", "L" + newDynName + ";", null, l0, l2, 0);
visitParamTypesLocalVariable(mv, method, l0, l2, insns, methodBean);
mv.visitLocalVariable("_redkale_supplier", Type.getDescriptor(ThrowSupplier.class), null, l1, l2, ++insn);
mv.visitMaxs(20, 20);
mv.visitEnd();
CacheAction action = new CacheAction(
new CacheEntry(cached), method, serviceType, methodBean.paramNameArray(method), dynFieldName);
actions.put(dynFieldName, action);
}
{ // ThrowSupplier
final MethodVisitor mv = cw.visitMethod(
ACC_PRIVATE + ACC_SYNTHETIC, "lambda$" + actionIndex, methodBean.getDesc(), null, new String[] {
"java/lang/Throwable"
});
// mv.setDebug(true);
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitVarInsn(ALOAD, 0);
visitVarInsnParamTypes(mv, method, 0);
mv.visitMethodInsn(INVOKESPECIAL, newDynName, rsMethodName, methodBean.getDesc(), false);
mv.visitInsn(ARETURN);
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitLocalVariable("this", "L" + newDynName + ";", null, l0, l1, 0);
mv.visitMaxs(5, 5);
mv.visitEnd();
}
{ // 定义字段
FieldVisitor fv =
cw.visitField(ACC_PRIVATE, dynFieldName, Type.getDescriptor(CacheAction.class), null, null);
fv.visitEnd();
}
if (actions.size() == 1) {
cw.visitInnerClass(
"java/lang/invoke/MethodHandles$Lookup",
"java/lang/invoke/MethodHandles",
"Lookup",
ACC_PUBLIC + ACC_FINAL + ACC_STATIC);
}
return rsMethodName;
}
@Override
public void doInstance(ResourceFactory resourceFactory, Object service) {
Class clazz = service.getClass();
if (actionMap == null) { // 为null表示没有调用过doMethod 动态类在编译是已经生成好了
actionMap = new LinkedHashMap<>();
Map<String, AsmMethodBean> methodBeans = AsmMethodBoost.getMethodBeans(clazz);
for (final Method method : clazz.getDeclaredMethods()) {
DynForCache cached = method.getAnnotation(DynForCache.class);
if (cached != null) {
String dynFieldName = cached.dynField();
AsmMethodBean methodBean = AsmMethodBean.get(methodBeans, method);
CacheAction action = new CacheAction(
new CacheEntry(cached),
method,
serviceType,
methodBean.paramNameArray(method),
dynFieldName);
actionMap.put(dynFieldName, action);
}
}
}
actionMap.forEach((field, action) -> {
try {
resourceFactory.inject(action);
action.init(resourceFactory, service);
if (action.templetKey.indexOf('@') < 0
&& action.templetKey.indexOf('{') < 0
&& action.getMethod().getParameterCount() > 0) {
// 一般有参数的方法Cached.key应该是动态的
logger.log(
Level.WARNING,
action.getMethod() + " has parameters but @" + Cached.class.getSimpleName()
+ ".key not contains parameter");
}
Field c = clazz.getDeclaredField(field);
c.setAccessible(true);
resourceFactory.inject(action);
c.set(service, action);
RedkaleClassLoader.putReflectionField(clazz.getName(), c);
} catch (Exception e) {
throw new RedkaleException("field (" + field + ") in " + clazz.getName() + " set error", e);
}
});
// do nothing
}
}