Aop子类代理异常处理优化,使得被代理方法对外throws异常时报错的问题

This commit is contained in:
aoshiguchen
2022-06-30 15:22:44 +08:00
parent 650d9b65be
commit eb656a9a93
13 changed files with 263 additions and 31 deletions
@@ -0,0 +1,36 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.aop;
/**
* aop回调
* @author: aoshiguchen
* @date: 2022/6/30
*/
@FunctionalInterface
public interface AopCallback<T> {
/**
* 回调
* @return
*/
T callback() throws Exception;
}
@@ -42,13 +42,13 @@ public class Invocation {
private Class<?> targetClass;
private Method targetMethod;
private Object proxy;
private Supplier callback;
private AopCallback callback;
private Object[] args;
private List<Interceptor> interceptors;
private volatile int index = 0;
private Object returnValue;
public Invocation(Long methodId, Object proxy, Supplier callback, Object... args) {
public Invocation(Long methodId, Object proxy, AopCallback callback, Object... args) {
this.targetMethod = ProxyCache.getMethod(methodId);
this.targetClass = this.targetMethod.getDeclaringClass();
this.proxy = proxy;
@@ -62,7 +62,7 @@ public class Invocation {
if (CollectionUtil.notEmpty(this.interceptors) && index < this.interceptors.size()) {
this.interceptors.get(index++).intercept(this);
} else {
returnValue = callback.get();
returnValue = callback.callback();
returnValue = TypeUtil.conversion(returnValue, this.targetMethod.getReturnType());
}
}
@@ -22,15 +22,15 @@
package fun.asgc.neutrino.core.aop.proxy;
import fun.asgc.neutrino.core.aop.Invocation;
import fun.asgc.neutrino.core.util.ArrayUtil;
import fun.asgc.neutrino.core.util.Assert;
import fun.asgc.neutrino.core.util.ClassUtil;
import fun.asgc.neutrino.core.util.StringUtil;
import fun.asgc.neutrino.core.util.*;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -103,10 +103,14 @@ public class AsgcProxyFactory implements ProxyFactory {
if (ClassUtil.isInterface(clazz)) {
return generateProxyClassSourceCodeForInterface(clazz, proxyClassName);
}
StringBuilder sb = new StringBuilder();
sb.append("package " + clazz.getPackage().getName() + ";").append("\n");
sb.append("import ").append(Invocation.class.getName()).append(";\n");
sb.append("public class ").append(proxyClassName).append(" extends ").append(clazz.getSimpleName()).append("{\n");
Set<Class<?>> importClasses = new HashSet<>();
StringBuilder header = new StringBuilder();
header.append("package " + clazz.getPackage().getName() + ";").append("\n");
appendImport(header, Invocation.class, importClasses);
appendImport(header, ProxyCache.class, importClasses);
StringBuilder body = new StringBuilder();
body.append("public class ").append(proxyClassName).append(" extends ").append(clazz.getSimpleName()).append("{\n");
Method[] methods = clazz.getMethods();
if (ArrayUtil.notEmpty(methods)) {
for (Method method : methods) {
@@ -115,29 +119,54 @@ public class AsgcProxyFactory implements ProxyFactory {
}
Long methodId = ProxyCache.setMethod(method);
Class<?> returnType = method.getReturnType();
Set<Class<?>> exceptionTypes = ReflectUtil.getExceptionTypes(method);
boolean isVoid = returnType == void.class;
boolean isThrow = CollectionUtil.notEmpty(exceptionTypes);
String parametersString = buildParametersString(method.getParameters());
String parameterNamesString = buildParameterNamesString(method.getParameters());
String throwString = isThrow ? "throws " + buildTypeNameString(exceptionTypes) : "";
if (isThrow) {
for (Class<?> c : exceptionTypes) {
appendImport(header, c, importClasses);
}
}
sb.append("\t").append("public").append(" ").append(returnType.getName()).append(" ").append(method.getName()).append("(").append(parametersString).append(") {").append("\n")
body.append("\t").append("public").append(" ").append(returnType.getName()).append(" ").append(method.getName()).append("(").append(parametersString).append(") ").append(throwString).append(" {").append("\n")
.append("\t\tInvocation inv = new Invocation(").append(methodId + "L,").append("this,").append("() -> {").append("\n")
.append("\t\t\t").append(isVoid ? "" : "return ").append("super.").append(method.getName()).append("(").append(parameterNamesString).append(");").append("\n")
.append(isVoid ? "\t\t\treturn null;\n" : "")
.append("\t\t").append("}").append(StringUtil.isEmpty(parameterNamesString) ? "" : "," + parameterNamesString).append(");").append("\n")
.append("\t\t").append("inv.invoke();").append("\n")
.append("\t\t").append("try {\n")
.append("\t\t\t").append("inv.invoke();").append("\n")
.append("\t\t").append("} catch (Exception e) {\n");
if (isThrow) {
body.append("\t\t\t").append("if (ProxyCache.checkMethodThrow(" + methodId + "L,e)) {").append("\n")
.append("\t\t\t\t").append("throw e;").append("\n")
.append("\t\t\t").append("} else {").append("\n")
.append("\t\t\t\t").append("e.printStackTrace();").append("\n")
.append("\t\t\t").append("}").append("\n");
} else {
body.append("\t\t\t").append("e.printStackTrace();").append("\n");
}
body.append("\t\t").append("}\n")
.append(isVoid ? "" : "\t\treturn inv.getReturnValue();\n")
.append("\t").append("}").append("\n");
}
}
sb.append("}").append("\n");
return sb.toString();
body.append("}").append("\n");
return header.toString().concat(body.toString());
}
private String generateProxyClassSourceCodeForInterface(Class<?> clazz, String proxyClassName) {
StringBuilder sb = new StringBuilder();
sb.append("package " + clazz.getPackage().getName() + ";").append("\n");
sb.append("import ").append(Invocation.class.getName()).append(";\n");
sb.append("public class ").append(proxyClassName).append(" implements ").append(clazz.getSimpleName()).append("{\n");
Set<Class<?>> importClasses = new HashSet<>();
StringBuilder header = new StringBuilder();
header.append("package " + clazz.getPackage().getName() + ";").append("\n");
appendImport(header, Invocation.class, importClasses);
appendImport(header, ProxyCache.class, importClasses);
StringBuilder body = new StringBuilder();
body.append("public class ").append(proxyClassName).append(" implements ").append(clazz.getSimpleName()).append("{\n");
Method[] methods = clazz.getMethods();
if (ArrayUtil.notEmpty(methods)) {
for (Method method : methods) {
@@ -146,21 +175,57 @@ public class AsgcProxyFactory implements ProxyFactory {
}
Long methodId = ProxyCache.setMethod(method);
Class<?> returnType = method.getReturnType();
Set<Class<?>> exceptionTypes = ReflectUtil.getExceptionTypes(method);
boolean isVoid = returnType == void.class;
boolean isThrow = CollectionUtil.notEmpty(exceptionTypes);
String parametersString = buildParametersString(method.getParameters());
String parameterNamesString = buildParameterNamesString(method.getParameters());
String throwString = isThrow ? "throws " + buildTypeNameString(exceptionTypes) : "";
if (isThrow) {
for (Class<?> c : exceptionTypes) {
appendImport(header, c, importClasses);
}
}
sb.append("\t").append("public").append(" ").append(returnType.getName()).append(" ").append(method.getName()).append("(").append(parametersString).append(") {").append("\n")
body.append("\t").append("public").append(" ").append(returnType.getName()).append(" ").append(method.getName()).append("(").append(parametersString).append(") ").append(throwString).append(" {").append("\n")
.append("\t\tInvocation inv = new Invocation(").append(methodId + "L,").append("this,").append("() -> {").append("\n")
.append("\t\t\treturn null;\n")
.append("\t\t").append("}").append(StringUtil.isEmpty(parameterNamesString) ? "" : "," + parameterNamesString).append(");").append("\n")
.append("\t\t").append("inv.invoke();").append("\n")
.append("\t\t").append("try {\n")
.append("\t\t\t").append("inv.invoke();").append("\n")
.append("\t\t").append("} catch (Exception e) {\n");
if (isThrow) {
body.append("\t\t\t").append("if (ProxyCache.checkMethodThrow(" + methodId + "L,e)) {").append("\n")
.append("\t\t\t\t").append("throw e;").append("\n")
.append("\t\t\t").append("} else {").append("\n")
.append("\t\t\t\t").append("e.printStackTrace();").append("\n")
.append("\t\t\t").append("}").append("\n");
} else {
body.append("\t\t\t").append("e.printStackTrace();").append("\n");
}
body.append("\t\t").append("}\n")
.append(isVoid ? "" : "\t\treturn inv.getReturnValue();\n")
.append("\t").append("}").append("\n");
}
}
sb.append("}").append("\n");
return sb.toString();
body.append("}").append("\n");
return header.toString().concat(body.toString());
}
/**
* 追加import语句,java.lang包下不需要import
* @param sb
* @param clazz
* @param importClasses
*/
private synchronized void appendImport(StringBuilder sb, Class<?> clazz, Set<Class<?>> importClasses) {
if (importClasses.contains(clazz) || clazz.getName().startsWith("java.lang.")) {
return;
}
importClasses.add(clazz);
sb.append("import ").append(clazz.getName()).append(";\n");
}
private String buildParametersString(Parameter[] parameters) {
@@ -176,4 +241,11 @@ public class AsgcProxyFactory implements ProxyFactory {
}
return Stream.of(parameters).map(Parameter::getName).collect(Collectors.joining(","));
}
private String buildTypeNameString(Set<Class<?>> classes) {
if (CollectionUtil.isEmpty(classes)) {
return "";
}
return classes.stream().map(Class::getName).collect(Collectors.joining(","));
}
}
@@ -27,7 +27,6 @@ import fun.asgc.neutrino.core.util.Assert;
import fun.asgc.neutrino.core.util.ClassUtil;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
@@ -79,11 +78,7 @@ public class JdkDynamicProxyFactory implements ProxyFactory {
Long methodId = ProxyCache.setMethod(method);
Invocation inv = new Invocation(methodId, proxy, () -> {
if (null != target) {
try {
return method.invoke(target, args);
} catch (Exception e) {
throw new RuntimeException(e);
}
return method.invoke(target, args);
}
return null;
}, args);
@@ -21,10 +21,14 @@
*/
package fun.asgc.neutrino.core.aop.proxy;
import fun.asgc.neutrino.core.util.CollectionUtil;
import fun.asgc.neutrino.core.util.ReflectUtil;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
/**
@@ -35,6 +39,7 @@ import java.util.concurrent.atomic.AtomicLong;
public class ProxyCache {
private static final AtomicLong methodId = new AtomicLong();
private static final Map<Long, Method> methodCache = Collections.synchronizedMap(new HashMap<>());
private static final Map<Long, Set<Class<?>>> methodExceptionTypesCache = Collections.synchronizedMap(new HashMap<>());
public static Long generateMethodId() {
return methodId.incrementAndGet();
@@ -43,10 +48,19 @@ public class ProxyCache {
public static Long setMethod(Method method) {
Long id = generateMethodId();
methodCache.put(id, method);
methodExceptionTypesCache.put(id, ReflectUtil.getExceptionTypes(method));
return id;
}
public static Method getMethod(Long id) {
return methodCache.get(id);
}
public static boolean checkMethodThrow(Long id, Exception e) {
Set<Class<?>> exceptionTypes = methodExceptionTypesCache.get(id);
if (CollectionUtil.isEmpty(exceptionTypes)) {
return false;
}
return exceptionTypes.contains(e.getClass());
}
}
@@ -27,7 +27,7 @@ import fun.asgc.neutrino.core.aop.Intercept;
* @author: aoshiguchen
* @date: 2022/6/28
*/
@Intercept(value = SqlMapperInterceptor.class, ignoreGlobal = true)
@Intercept(SqlMapperInterceptor.class)
public interface SqlMapper {
}
@@ -49,6 +49,7 @@ public class ReflectUtil {
private static Cache<Class<?>, Set<Method>> declaredMethodsCache = new MemoryCache<>();
private static Cache<Field,Method> getMethodCache = new MemoryCache<>();
private static Cache<Field,Method> setMethodCache = new MemoryCache<>();
private static Cache<Method, Set<Class<?>>> methodExceptionTypesCache = new MemoryCache<>();
/**
* 获取字段列表
@@ -154,6 +155,22 @@ public class ReflectUtil {
});
}
/**
* 获取异常集合
* @param method
* @return
*/
public static Set<Class<?>> getExceptionTypes(Method method) {
return kvProcess(methodExceptionTypesCache, method, c -> {
Class<?>[] classes = method.getExceptionTypes();
Set<Class<?>> classSet = new HashSet<>();
if (ArrayUtil.notEmpty(classes)) {
classSet = Stream.of(classes).collect(Collectors.toSet());
}
return classSet;
});
}
/**
* kv处理逻辑封装
* @param cache
@@ -31,4 +31,5 @@ public interface Animal {
int say(String msg);
void hello() throws Exception;
}
@@ -0,0 +1,37 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.aop;
/**
*
* @author: aoshiguchen
* @date: 2022/6/30
*/
public class Bear {
public Bear() {
System.out.println("熊出生");
}
public void up() throws Exception {
throw new Exception("飞得太高,摔死了");
}
}
@@ -21,9 +21,12 @@
*/
package fun.asgc.neutrino.core.aop;
import fun.asgc.neutrino.core.aop.proxy.Proxy;
import fun.asgc.neutrino.core.util.ReflectUtil;
import org.junit.Test;
import java.lang.reflect.Method;
/**
*
* @author: aoshiguchen
@@ -34,6 +37,7 @@ public class Test3 {
@Test
public void test1() {
Animal animal = Aop.get(Animal.class);
// Animal animal = Proxy.getProxyFactory(ProxyStrategy.ASGC_PROXY).get(Animal.class);
System.out.println(animal);
System.out.println(animal.say("aa"));
}
@@ -45,4 +49,10 @@ public class Test3 {
System.out.println(ReflectUtil.getInterfaceAll(Mammal.class));
}
@Test
public void test3() throws Exception {
Bear bear = Aop.get(Bear.class);
bear.up();
}
}
@@ -15,6 +15,9 @@ package fun.asgc.neutrino.core.db.mapper;
import com.alibaba.fastjson.JSONObject;
import fun.asgc.neutrino.core.annotation.Autowired;
import fun.asgc.neutrino.core.annotation.Component;
import fun.asgc.neutrino.core.annotation.Init;
import fun.asgc.neutrino.core.aop.interceptor.ExceptionHandler;
import fun.asgc.neutrino.core.aop.interceptor.InnerGlobalInterceptor;
import fun.asgc.neutrino.core.db.template.JdbcTemplateTest;
import fun.asgc.neutrino.core.runner.ApplicationRunner;
import lombok.extern.slf4j.Slf4j;
@@ -32,6 +35,14 @@ import java.util.List;
public class Test1 implements ApplicationRunner {
@Autowired
private UserMapper userMapper;
@Autowired
private TestGlobalExceptionHandler testGlobalExceptionHandler;
@Init
public void init() {
log.info("初始化,注册全局异常拦截器{}...", testGlobalExceptionHandler.hashCode());
InnerGlobalInterceptor.registerExceptionHandler(testGlobalExceptionHandler);
}
@Override
public void run(String[] args) {
@@ -27,7 +27,8 @@ public class TestExceptionHandler implements ExceptionHandler {
@Override
public boolean support(Exception e) {
return e instanceof SQLException;
// return e instanceof SQLException;
return false;
}
@Override
@@ -0,0 +1,38 @@
/**
* Copyright (C) 2018-2022 Zeyi information technology (Shanghai) Co., Ltd.
* <p>
* All right reserved.
* <p>
* This software is the confidential and proprietary
* information of Zeyi Company of China.
* ("Confidential Information"). You shall not disclose
* such Confidential Information and shall use it only
* in accordance with the terms of the contract agreement
* you entered into with Zeyi inc.
*/
package fun.asgc.neutrino.core.db.mapper;
import fun.asgc.neutrino.core.annotation.Component;
import fun.asgc.neutrino.core.aop.interceptor.ExceptionHandler;
import lombok.extern.slf4j.Slf4j;
/**
* 全局异常处理
* @author: aoshiguchen
* @date: 2022/6/30
*/
@Slf4j
@Component
public class TestGlobalExceptionHandler implements ExceptionHandler {
@Override
public boolean support(Exception e) {
return true;
}
@Override
public Object handle(Exception e) {
log.error("全局异常 {}" + this.hashCode(), e);
return null;
}
}