diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/Aop.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/Aop.java new file mode 100644 index 00000000..d79d2718 --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/Aop.java @@ -0,0 +1,42 @@ +/** + * 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; + +import fun.asgc.neutrino.core.aop.proxy.Proxy; +import fun.asgc.neutrino.core.aop.proxy.ProxyFactory; +import fun.asgc.neutrino.core.aop.proxy.ProxyStrategy; +import fun.asgc.neutrino.core.util.Assert; + +/** + * + * @author: aoshiguchen + * @date: 2022/6/24 + */ +public class Aop { + private static final ProxyStrategy proxyStrategy = ProxyStrategy.SUB_CLASS_PROXY; + private static final ProxyFactory proxyFactory = Proxy.getProxyFactory(proxyStrategy); + + public static T get(Class clazz) { + Assert.notNull(proxyFactory, "代理工厂初始化异常!"); + return proxyFactory.get(clazz); + } +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/Intercept.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/Intercept.java new file mode 100644 index 00000000..0b059007 --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/Intercept.java @@ -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; + +import java.lang.annotation.*; + +/** + * + * @author: aoshiguchen + * @date: 2022/6/24 + */ +@Inherited +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface Intercept { + Class[] value(); +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/Interceptor.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/Interceptor.java new file mode 100644 index 00000000..8c646651 --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/Interceptor.java @@ -0,0 +1,34 @@ +/** + * 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/24 + */ +public interface Interceptor { + /** + * 拦截方法 + * @param inv + */ + void intercept(Invocation inv); +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/Invocation.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/Invocation.java new file mode 100644 index 00000000..0ac06204 --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/Invocation.java @@ -0,0 +1,112 @@ +/** + * 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; + +import fun.asgc.neutrino.core.aop.proxy.ProxyCache; +import fun.asgc.neutrino.core.util.ArrayUtil; +import lombok.extern.slf4j.Slf4j; + +import java.lang.reflect.Method; +import java.util.function.Supplier; + +/** + * + * @author: aoshiguchen + * @date: 2022/6/24 + */ +@Slf4j +public class Invocation { + private Class targetClass; + private Method targetMethod; + private Object proxy; + private Supplier callback; + private Object[] args; + private Interceptor[] interceptors; + private volatile int index = 0; + private Object returnValue; + + public Invocation(Long methodId, Object proxy, Supplier callback, Object... args) { + this.targetMethod = ProxyCache.getMethod(methodId); + this.targetClass = this.targetMethod.getDeclaringClass(); + this.proxy = proxy; + this.callback = callback; + this.targetClass = proxy.getClass().getSuperclass(); + this.args = args; + this.interceptors = getInterceptors(this.targetMethod); + } + + /** + * TODO 先简单实现为 newInstance + * @param targetMethod + * @return + */ + public static Interceptor[] getInterceptors(Method targetMethod) { + if (null == targetMethod) { + return null; + } + Intercept intercept = targetMethod.getAnnotation(Intercept.class); + if (null == intercept) { + intercept = targetMethod.getDeclaringClass().getAnnotation(Intercept.class); + } + if (null == intercept) { + return null; + } + Class[] classes = intercept.value(); + if (ArrayUtil.isEmpty(classes)) { + return null; + } + Interceptor[] interceptors = new Interceptor[classes.length]; + for (int i = 0; i < classes.length; i++) { + try { + interceptors[i] = classes[i].newInstance(); + } catch (Exception e) { + // ignore + } + } + return interceptors; + } + + public void invoke() { + if (ArrayUtil.notEmpty(this.interceptors) && index < this.interceptors.length) { + this.interceptors[index++].intercept(this); + } else { + returnValue = callback.get(); + } + } + + public T getReturnValue() { + return (T)returnValue; + } + + + public Class getTargetClass() { + return targetClass; + } + + public Method getTargetMethod() { + return targetMethod; + } + + public Object[] getArgs() { + return args; + } +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/Proxy.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/Proxy.java new file mode 100644 index 00000000..7ab27f5f --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/Proxy.java @@ -0,0 +1,39 @@ +/** + * 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.proxy; + +/** + * + * @author: aoshiguchen + * @date: 2022/6/24 + */ +public class Proxy { + private static final ProxyFactory subClassProxyFactory = new SubClassProxyFactory(); + + public static ProxyFactory getProxyFactory(ProxyStrategy strategy) { + switch (strategy) { + case SUB_CLASS_PROXY: return subClassProxyFactory; + default: return null; + } + } + +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/ProxyCache.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/ProxyCache.java new file mode 100644 index 00000000..283790eb --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/ProxyCache.java @@ -0,0 +1,52 @@ +/** + * 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.proxy; + +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; + +/** + * 代理缓存 + * @author: aoshiguchen + * @date: 2022/6/24 + */ +public class ProxyCache { + private static final AtomicLong methodId = new AtomicLong(); + private static final Map methodCache = Collections.synchronizedMap(new HashMap<>()); + + public static Long generateMethodId() { + return methodId.incrementAndGet(); + } + + public static Long setMethod(Method method) { + Long id = generateMethodId(); + methodCache.put(id, method); + return id; + } + + public static Method getMethod(Long id) { + return methodCache.get(id); + } +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/ProxyClass.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/ProxyClass.java new file mode 100644 index 00000000..3209f1c9 --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/ProxyClass.java @@ -0,0 +1,64 @@ +/** + * 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.proxy; + +import lombok.Data; + +import java.util.Map; + +/** + * + * @author: 代理类 + * @date: 2022/6/24 + */ +@Data +public class ProxyClass { + /** + * 被代理的目标 + */ + private Class target; + /** + * 包名 + */ + private String pkg; + /** + * 类名 + */ + private String name; + /** + * 源代码 + */ + private String sourceCode; + /** + * 字节码 + */ + private Map byteCode; + /** + * 字节码被加载后的代理类 + */ + private Class clazz; + + public ProxyClass(Class target) { + this.target = target; + this.pkg = target.getPackage().getName(); + } +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/ProxyClassLoader.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/ProxyClassLoader.java new file mode 100644 index 00000000..e6e51e6b --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/ProxyClassLoader.java @@ -0,0 +1,72 @@ +/** + * 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.proxy; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 代理类加载器 + * @author: aoshiguchen + * @date: 2022/6/24 + */ +public class ProxyClassLoader extends ClassLoader { + + protected Map byteCodeMap = new ConcurrentHashMap<>(); + + static { + registerAsParallelCapable(); + } + + public ProxyClassLoader() { + super(getParentClassLoader()); + } + + protected static ClassLoader getParentClassLoader() { + ClassLoader ret = Thread.currentThread().getContextClassLoader(); + return ret != null ? ret : ProxyClassLoader.class.getClassLoader(); + } + + public Class loadProxyClass(ProxyClass proxyClass) { + for (Map.Entry e : proxyClass.getByteCode().entrySet()) { + byteCodeMap.putIfAbsent(e.getKey(), e.getValue()); + } + + try { + return loadClass(proxyClass.getPkg() + "." + proxyClass.getName()); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + byte[] bytes = byteCodeMap.get(name); + if (bytes != null) { + Class ret = defineClass(name, bytes, 0, bytes.length); + byteCodeMap.remove(name); + return ret; + } + + return super.findClass(name); + } +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/ProxyCompiler.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/ProxyCompiler.java new file mode 100644 index 00000000..d7f0fb24 --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/ProxyCompiler.java @@ -0,0 +1,218 @@ +/** + * 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.proxy; + +import javax.tools.*; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URI; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.*; + +/** + * + * @author: aoshiguchen + * @date: 2022/6/24 + */ +public class ProxyCompiler { + protected volatile List options = null; + + protected List getOptions() { + if (options != null) { + return options; + } + + synchronized (this) { + if (options != null) { + return options; + } + + List ret = new ArrayList<>(); + ret.add("-target"); + ret.add("1.8"); + + String cp = getClassPath(); + if (cp != null && cp.trim().length() != 0) { + ret.add("-classpath"); + ret.add(cp); + } + + options = ret; + return options; + } + } + + /** + * 兼容 tomcat 丢失 class path,否则无法编译 + */ + protected String getClassPath() { + URLClassLoader classLoader = getURLClassLoader(); + if (classLoader == null) { + return null; + } + + int index = 0; + boolean isWindows = isWindows(); + StringBuilder ret = new StringBuilder(); + for (URL url : classLoader.getURLs()) { + if (index++ > 0) { + ret.append(File.pathSeparator); + } + + String path = url.getFile(); + + // 如果是 windows 系统,去除前缀字符 '/' + if (isWindows && path.startsWith("/")) { + path = path.substring(1); + } + + // 去除后缀字符 '/' + if (path.length() > 1 && (path.endsWith("/") || path.endsWith(File.separator))) { + path = path.substring(0, path.length() - 1); + } + + ret.append(path); + } + + return ret.toString(); + } + + protected boolean isWindows() { + String osName = System.getProperty("os.name", "unknown"); + return osName.toLowerCase().indexOf("windows") != -1; + } + + protected URLClassLoader getURLClassLoader() { + ClassLoader ret = Thread.currentThread().getContextClassLoader(); + if (ret == null) { + ret = ProxyCompiler.class.getClassLoader(); + } + return (ret instanceof URLClassLoader) ? (URLClassLoader)ret : null; + } + + public void compile(ProxyClass proxyClass) { + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + if (compiler == null) { + throw new RuntimeException("Can not get javax.tools.JavaCompiler, check whether \"tools.jar\" is in the environment variable CLASSPATH \n" + + "Visit https://jfinal.com/doc/4-8 for details \n"); + } + + DiagnosticCollector collector = new DiagnosticCollector<>(); + try (MyJavaFileManager javaFileManager = new MyJavaFileManager(compiler.getStandardFileManager(collector, null, null))) { + + MyJavaFileObject javaFileObject = new MyJavaFileObject(proxyClass.getName(), proxyClass.getSourceCode()); + Boolean result = compiler.getTask(null, javaFileManager, collector, getOptions(), null, Arrays.asList(javaFileObject)).call(); + outputCompileError(result, collector); + + Map ret = new HashMap<>(); + for (Map.Entry e : javaFileManager.fileObjects.entrySet()) { + ret.put(e.getKey(), e.getValue().getByteCode()); + } + + proxyClass.setByteCode(ret); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + protected void outputCompileError(Boolean result, DiagnosticCollector collector) { + if (! result) { + // collector.getDiagnostics().forEach(item -> log.error(item.toString())); + collector.getDiagnostics().forEach(item -> System.out.println(item.toString())); + } + } + + public ProxyCompiler setCompileOptions(List options) { + Objects.requireNonNull(options, "options can not be null"); + this.options = options; + return this; + } + + public ProxyCompiler addCompileOption(String option) { + Objects.requireNonNull(option, "option can not be null"); + options.add(option); + return this; + } + + public static class MyJavaFileObject extends SimpleJavaFileObject { + + private String source; + private ByteArrayOutputStream outPutStream; + + public MyJavaFileObject(String name, String source) { + super(URI.create("String:///" + name + JavaFileObject.Kind.SOURCE.extension), JavaFileObject.Kind.SOURCE); + this.source = source; + } + + public MyJavaFileObject(String name, JavaFileObject.Kind kind) { + super(URI.create("String:///" + name + kind.extension), kind); + source = null; + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) { + if (source == null) { + throw new IllegalStateException("source field can not be null"); + } + return source; + } + + @Override + public OutputStream openOutputStream() throws IOException { + outPutStream = new ByteArrayOutputStream(); + return outPutStream; + } + + public byte[] getByteCode() { + return outPutStream.toByteArray(); + } + } + + public static class MyJavaFileManager extends ForwardingJavaFileManager { + + public Map fileObjects = new HashMap<>(); + + public MyJavaFileManager(JavaFileManager fileManager) { + super(fileManager); + } + + @Override + public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String qualifiedClassName, JavaFileObject.Kind kind, FileObject sibling) throws IOException { + MyJavaFileObject javaFileObject = new MyJavaFileObject(qualifiedClassName, kind); + fileObjects.put(qualifiedClassName, javaFileObject); + return javaFileObject; + } + + // 是否在编译时依赖另一个类的情况下用到本方法 ? + @Override + public JavaFileObject getJavaFileForInput(JavaFileManager.Location location, String className, JavaFileObject.Kind kind) throws IOException { + JavaFileObject javaFileObject = fileObjects.get(className); + if (javaFileObject == null) { + javaFileObject = super.getJavaFileForInput(location, className, kind); + } + return javaFileObject; + } + } +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/ProxyFactory.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/ProxyFactory.java new file mode 100644 index 00000000..07e72e2c --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/ProxyFactory.java @@ -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.proxy; + +/** + * @author: aoshiguchen + * @date: 2022/6/24 + */ +public interface ProxyFactory { + + /** + * 获取一个类的代理实例 + * @param clazz + * @param + * @return + */ + T get(Class clazz); +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/ProxyStrategy.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/ProxyStrategy.java new file mode 100644 index 00000000..ba99341c --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/ProxyStrategy.java @@ -0,0 +1,41 @@ +/** + * 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.proxy; + +/** + * 代理策略 + * @author: aoshiguchen + * @date: 2022/6/24 + */ + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum ProxyStrategy { + JDK_DYNAMIC_PROXY(1, "JDK动态代理"), + SUB_CLASS_PROXY(2, "子类代理"); + + private Integer strategy; + private String desc; +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/SubClassProxyFactory.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/SubClassProxyFactory.java new file mode 100644 index 00000000..e8b2a913 --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/SubClassProxyFactory.java @@ -0,0 +1,124 @@ +/** + * 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.proxy; + +import fun.asgc.neutrino.core.aop.Invocation; +import fun.asgc.neutrino.core.util.StringUtil; +import lombok.extern.slf4j.Slf4j; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Parameter; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * + * @author: aoshiguchen + * @date: 2022/6/24 + */ +@Slf4j +public class SubClassProxyFactory implements ProxyFactory { + private static String classNameTemplate = "%sSubClassProxy$$%s"; + private static AtomicLong proxyClassCounter = new AtomicLong(); + private ProxyCompiler compiler = new ProxyCompiler(); + private ProxyClassLoader classLoader = new ProxyClassLoader(); + + @Override + public T get(Class clazz) { + try { + return doGet(clazz); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + private T doGet(Class clazz) throws ReflectiveOperationException { + ProxyClass proxyClass = new ProxyClass(clazz); + proxyClass.setName(generateClassName(clazz)); + String sourceCode = generateProxyClassSourceCode(clazz, proxyClass.getName()); + proxyClass.setSourceCode(sourceCode); + log.debug("类:{} 的代理类源码:\n{}", clazz.getName(), sourceCode); + + compiler.compile(proxyClass); + Class retClass = (Class)classLoader.loadProxyClass(proxyClass); + T obj = retClass.newInstance(); + return obj; + } + + private String generateClassName(Class clazz) { + return String.format(classNameTemplate, clazz.getSimpleName(), proxyClassCounter.incrementAndGet()); + } + + /** + * 生成代理类源代码 - 继承方式 + * 1、类不能有final修饰符 + * 2、被代理方法不能有final修饰符 + * @param clazz + * @return + */ + private String generateProxyClassSourceCode(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(" extends ").append(clazz.getSimpleName()).append("{\n"); + Method[] methods = clazz.getMethods(); + if (methods != null && methods.length > 0) { + for (Method method : methods) { + if (Modifier.isFinal(method.getModifiers())) { + continue; + } + Long methodId = ProxyCache.setMethod(method); + Class returnType = method.getReturnType(); + boolean isVoid = returnType == void.class; + String parametersString = buildParametersString(method.getParameters()); + String parameterNamesString = buildParameterNamesString(method.getParameters()); + + sb.append("\t").append("public").append(" ").append(returnType.getName()).append(" ").append(method.getName()).append("(").append(parametersString).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(isVoid ? "" : "return inv.getReturnValue();").append("\n") + .append("\t").append("}").append("\n"); + } + } + sb.append("}").append("\n"); + return sb.toString(); + } + + private String buildParametersString(Parameter[] parameters) { + if (null == parameters || parameters.length == 0) { + return ""; + } + return Stream.of(parameters).map(item -> item.getType().getName() + " " + item.getName()).collect(Collectors.joining(",")); + } + + private String buildParameterNamesString(Parameter[] parameters) { + if (null == parameters || parameters.length == 0) { + return ""; + } + return Stream.of(parameters).map(Parameter::getName).collect(Collectors.joining(",")); + } +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/util/ClassUtil.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/util/ClassUtil.java index 19123d4b..2871e203 100644 --- a/neutrino-core/src/main/java/fun/asgc/neutrino/core/util/ClassUtil.java +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/util/ClassUtil.java @@ -31,6 +31,7 @@ import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.lang.annotation.Annotation; +import java.lang.reflect.Modifier; import java.net.JarURLConnection; import java.net.URL; import java.net.URLClassLoader; diff --git a/neutrino-core/src/test/java/fun/asgc/neutrino/core/aop/Dog.java b/neutrino-core/src/test/java/fun/asgc/neutrino/core/aop/Dog.java new file mode 100644 index 00000000..7b77a7a2 --- /dev/null +++ b/neutrino-core/src/test/java/fun/asgc/neutrino/core/aop/Dog.java @@ -0,0 +1,32 @@ +/** + * Copyright (C) 2018-2022 Zeyi information technology (Shanghai) Co., Ltd. + *

+ * All right reserved. + *

+ * 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.aop; + +/** + * + * @author: wen.y + * @date: 2022/6/23 + */ +@Intercept({DogInterceptor.class}) +public class Dog { + public Dog() { + System.out.println("狗出生"); + } + public void call() { + System.out.println("汪汪汪"); + } + + public String say(String msg) { + return "狗说:" + msg; + } +} diff --git a/neutrino-core/src/test/java/fun/asgc/neutrino/core/aop/DogInterceptor.java b/neutrino-core/src/test/java/fun/asgc/neutrino/core/aop/DogInterceptor.java new file mode 100644 index 00000000..fdae2138 --- /dev/null +++ b/neutrino-core/src/test/java/fun/asgc/neutrino/core/aop/DogInterceptor.java @@ -0,0 +1,37 @@ +/** + * Copyright (C) 2018-2022 Zeyi information technology (Shanghai) Co., Ltd. + *

+ * All right reserved. + *

+ * 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.aop; + +import lombok.extern.slf4j.Slf4j; + +/** + * + * @author: wen.y + * @date: 2022/6/24 + */ +@Slf4j +public class DogInterceptor implements Interceptor { + + @Override + public void intercept(Invocation inv) { + try { + log.info("拦截器 class:{} method:{} args:{} before", inv.getTargetClass().getName(), inv.getTargetMethod().getName(), inv.getArgs()); + inv.invoke(); + log.info("拦截器 class:{} method:{} args:{} after", inv.getTargetClass().getName(), inv.getTargetMethod().getName(), inv.getArgs()); + } catch (Exception e) { + log.info("拦截器 class:{} method:{} args:{} error", inv.getTargetClass().getName(), inv.getTargetMethod().getName(), inv.getArgs()); + e.printStackTrace(); + } + } + +} diff --git a/neutrino-core/src/test/java/fun/asgc/neutrino/core/aop/Test1.java b/neutrino-core/src/test/java/fun/asgc/neutrino/core/aop/Test1.java new file mode 100644 index 00000000..b7458c83 --- /dev/null +++ b/neutrino-core/src/test/java/fun/asgc/neutrino/core/aop/Test1.java @@ -0,0 +1,35 @@ +/** + * Copyright (C) 2018-2022 Zeyi information technology (Shanghai) Co., Ltd. + *

+ * All right reserved. + *

+ * 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.aop; + +import org.junit.Test; + +/** + * + * @author: wen.y + * @date: 2022/6/24 + */ +public class Test1 { + + @Test + public void dogCall() { + Dog dog = Aop.get(Dog.class); + dog.call(); + } + + @Test + public void dogSay() { + Dog dog = Aop.get(Dog.class); + System.out.println(dog.say("hello")); + } +}