spring.aop 随笔4 如何借助jdk代理类实现aop

news2024/10/6 21:36:25

0. 下了有一个月的雨,这对鼻炎来说来吗?不好

其实这也算6月份的博客,之前一直疏于整理


  • 本文仅关注jdk代理所实现的spring.aop下,两者的关系
  • 完整的aop源码走读请移步相关 spring.aop 的其他随笔

1. 反编译追踪源码

1.1 jdk代理类

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

import com.weng.cloud.sample.aop.dto.Order;
import com.weng.cloud.sample.aop.service.OrderService;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements OrderService, Serializable {
    private static Method m1;
    private static Method m4;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

	// 接口实现类
    public final Order queryOrder(String var1) throws  {
        try {
        	// step into super class(invocationHandler) ...
            return (Order)super.h.invoke(this, m4, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

	// 接口实现类
    public final Order createOrder(String var1, String var2) throws  {
        try {
            return (Order)super.h.invoke(this, m3, new Object[]{var1, var2});
        } catch (RuntimeException | Error var4) {
            throw var4;
        } catch (Throwable var5) {
            throw new UndeclaredThrowableException(var5);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m4 = Class.forName("com.weng.cloud.sample.aop.service.OrderService").getMethod("queryOrder", Class.forName("java.lang.String"));
            m3 = Class.forName("com.weng.cloud.sample.aop.service.OrderService").getMethod("createOrder", Class.forName("java.lang.String"), Class.forName("java.lang.String"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

1.2 java.lang.reflect.InvocationHandler.invoke() spring.aop组装 jdk代理所需的参数

在这里插入图片描述

	/**
	 * Implementation of {@code InvocationHandler.invoke}.
	 * <p>Callers will see exactly the exception thrown by the target,
	 * unless a hook method throws an exception.
	 */
	@Override
	@Nullable
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		Object oldProxy = null;
		boolean setProxyContext = false;

		TargetSource targetSource = this.advised.targetSource;
		Object target = null;

		try {
			if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
				// The target does not implement the equals(Object) method itself.
				return equals(args[0]);
			}
			else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
				// The target does not implement the hashCode() method itself.
				return hashCode();
			}
			else if (method.getDeclaringClass() == DecoratingProxy.class) {
				// There is only getDecoratedClass() declared -> dispatch to proxy config.
				return AopProxyUtils.ultimateTargetClass(this.advised);
			}
			else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
					method.getDeclaringClass().isAssignableFrom(Advised.class)) {
				// Service invocations on ProxyConfig with the proxy config...
				return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
			}

			Object retVal;

			if (this.advised.exposeProxy) {
				// Make invocation available if necessary.
				oldProxy = AopContext.setCurrentProxy(proxy);
				setProxyContext = true;
			}

			// Get as late as possible to minimize the time we "own" the target,
			// in case it comes from a pool.
			target = targetSource.getTarget();
			Class<?> targetClass = (target != null ? target.getClass() : null);

			// 获取增强逻辑的advices
			// Get the interception chain for this method.
			List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

			// Check whether we have any advice. If we don't, we can fallback on direct
			// reflective invocation of the target, and avoid creating a MethodInvocation.
			if (chain.isEmpty()) {
				// We can skip creating a MethodInvocation: just invoke the target directly
				// Note that the final invoker must be an InvokerInterceptor so we know it does
				// nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
				Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
				retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
			}
			else {
				// proxy:方法外部 Proxy.newInstance() 产出的代理实例
				// target:targetSource中保存的目标实例(在aop调用过程中维护Class实例)
				// method:java.lang.reflect.Method
				// args:方法参数
				// targetClass:target.getClass()
				// chain:增强逻辑advices
				// We need to create a method invocation...
				MethodInvocation invocation =
						new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
				// step into ...
				// Proceed to the joinpoint through the interceptor chain.
				retVal = invocation.proceed();
			}

			// Massage return value if necessary.
			Class<?> returnType = method.getReturnType();
			if (retVal != null && retVal == target &&
					returnType != Object.class && returnType.isInstance(proxy) &&
					!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
				// Special case: it returned "this" and the return type of the method
				// is type-compatible. Note that we can't help if the target sets
				// a reference to itself in another returned object.
				retVal = proxy;
			}
			else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
				throw new AopInvocationException(
						"Null return value from advice does not match primitive return type for: " + method);
			}
			return retVal;
		}
		finally {
			if (target != null && !targetSource.isStatic()) {
				// Must have come from TargetSource.
				targetSource.releaseTarget(target);
			}
			if (setProxyContext) {
				// Restore old proxy.
				AopContext.setCurrentProxy(oldProxy);
			}
		}
	}

1.3 spring.aop.MethodInvocation.proceed() 递归调用 advices

在这里插入图片描述

	@Override
	@Nullable
	public Object proceed() throws Throwable {
		// We start with an index of -1 and increment early.
		if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
			return invokeJoinpoint();
		}

		Object interceptorOrInterceptionAdvice =
				this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
		if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
			// Evaluate dynamic method matcher here: static part will already have
			// been evaluated and found to match.
			InterceptorAndDynamicMethodMatcher dm =
					(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
			Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
			if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
				return dm.interceptor.invoke(this);
			}
			else {
				// Dynamic matching failed.
				// Skip this interceptor and invoke the next in the chain.
				return proceed();
			}
		}
		else {
			// It's an interceptor, so we just invoke it: The pointcut will have
			// been evaluated statically before this object was constructed.
			return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
		}
	}

到这基本结束了,下面内容属于加餐选项

2. cglib代理类反编译源码

其实cglib除了生成代理类的字节码以外,还生成了 Enhancer$EnhancerKey$$KeyFactoryByCGLIB$$xxxx 的字节码。初略的看了一下,果不其然,跟jdk代理一样,使用了 WeakCache 一类的弱缓存技术,只不过是cglib自己实现的。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.weng.cloud.sample.ctx;

import java.lang.reflect.Method;
import org.springframework.cglib.core.ReflectUtils;
import org.springframework.cglib.core.Signature;
import org.springframework.cglib.proxy.Callback;
import org.springframework.cglib.proxy.Factory;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.cglib.proxy.NoOp;

public class SingleService$$EnhancerBySpringCGLIB$$cc775f74 extends SingleService implements Factory {
    private boolean CGLIB$BOUND;
    public static Object CGLIB$FACTORY_DATA;
    private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
    private static final Callback[] CGLIB$STATIC_CALLBACKS;
    private NoOp CGLIB$CALLBACK_0;
    private MethodInterceptor CGLIB$CALLBACK_1;
    private MethodInterceptor CGLIB$CALLBACK_2;
    private static Object CGLIB$CALLBACK_FILTER;
    private static final Method CGLIB$protoService$0$Method;
    private static final MethodProxy CGLIB$protoService$0$Proxy;
    private static final Object[] CGLIB$emptyArgs;

    static void CGLIB$STATICHOOK3() {
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        Class var0 = Class.forName("com.weng.cloud.sample.ctx.SingleService$$EnhancerBySpringCGLIB$$cc775f74");
        Class var1;
        CGLIB$protoService$0$Method = ReflectUtils.findMethods(new String[]{"protoService", "()Lcom/weng/cloud/sample/ctx/ProtoService;"}, (var1 = Class.forName("com.weng.cloud.sample.ctx.SingleService")).getDeclaredMethods())[0];
        CGLIB$protoService$0$Proxy = MethodProxy.create(var1, var0, "()Lcom/weng/cloud/sample/ctx/ProtoService;", "protoService", "CGLIB$protoService$0");
    }

    final ProtoService CGLIB$protoService$0() {
        return super.protoService();
    }

	// 被增强的方法
    public final ProtoService protoService() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_1;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_1;
        }

		// 调用增强逻辑 methodInterceptor.intercept()
        return var10000 != null ? (ProtoService)var10000.intercept(this, CGLIB$protoService$0$Method, CGLIB$emptyArgs, CGLIB$protoService$0$Proxy) : super.protoService();
    }

    public static MethodProxy CGLIB$findMethodProxy(Signature var0) {
        String var10000 = var0.toString();
        switch(var10000.hashCode()) {
        case 1187820471:
            if (var10000.equals("protoService()Lcom/weng/cloud/sample/ctx/ProtoService;")) {
                return CGLIB$protoService$0$Proxy;
            }
        }

        return null;
    }

	// 构造器
    public SingleService$$EnhancerBySpringCGLIB$$cc775f74() {
    	// 初始化绑定关系(MethodInterceptor)
        CGLIB$BIND_CALLBACKS(this);
    }

    public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] var0) {
        CGLIB$THREAD_CALLBACKS.set(var0);
    }

    public static void CGLIB$SET_STATIC_CALLBACKS(Callback[] var0) {
        CGLIB$STATIC_CALLBACKS = var0;
    }

    private static final void CGLIB$BIND_CALLBACKS(Object var0) {
        SingleService$$EnhancerBySpringCGLIB$$cc775f74 var1 = (SingleService$$EnhancerBySpringCGLIB$$cc775f74)var0;
        if (!var1.CGLIB$BOUND) {
            var1.CGLIB$BOUND = true;
            Object var10000 = CGLIB$THREAD_CALLBACKS.get();
            if (var10000 == null) {
                var10000 = CGLIB$STATIC_CALLBACKS;
                if (var10000 == null) {
                    return;
                }
            }

            Callback[] var10001 = (Callback[])var10000;
            var1.CGLIB$CALLBACK_2 = (MethodInterceptor)((Callback[])var10000)[2];
            var1.CGLIB$CALLBACK_1 = (MethodInterceptor)var10001[1];
            var1.CGLIB$CALLBACK_0 = (NoOp)var10001[0];
        }

    }

    public Object newInstance(Callback[] var1) {
        CGLIB$SET_THREAD_CALLBACKS(var1);
        SingleService$$EnhancerBySpringCGLIB$$cc775f74 var10000 = new SingleService$$EnhancerBySpringCGLIB$$cc775f74();
        CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
        return var10000;
    }

    public Object newInstance(Callback var1) {
        throw new IllegalStateException("More than one callback object required");
    }

    public Object newInstance(Class[] var1, Object[] var2, Callback[] var3) {
        CGLIB$SET_THREAD_CALLBACKS(var3);
        SingleService$$EnhancerBySpringCGLIB$$cc775f74 var10000 = new SingleService$$EnhancerBySpringCGLIB$$cc775f74;
        switch(var1.length) {
        case 0:
            var10000.<init>();
            CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
            return var10000;
        default:
            throw new IllegalArgumentException("Constructor not found");
        }
    }

    public Callback getCallback(int var1) {
        CGLIB$BIND_CALLBACKS(this);
        Object var10000;
        switch(var1) {
        case 0:
            var10000 = this.CGLIB$CALLBACK_0;
            break;
        case 1:
            var10000 = this.CGLIB$CALLBACK_1;
            break;
        case 2:
            var10000 = this.CGLIB$CALLBACK_2;
            break;
        default:
            var10000 = null;
        }

        return (Callback)var10000;
    }

    public void setCallback(int var1, Callback var2) {
        switch(var1) {
        case 0:
            this.CGLIB$CALLBACK_0 = (NoOp)var2;
            break;
        case 1:
            this.CGLIB$CALLBACK_1 = (MethodInterceptor)var2;
            break;
        case 2:
            this.CGLIB$CALLBACK_2 = (MethodInterceptor)var2;
        }

    }

    public Callback[] getCallbacks() {
        CGLIB$BIND_CALLBACKS(this);
        return new Callback[]{this.CGLIB$CALLBACK_0, this.CGLIB$CALLBACK_1, this.CGLIB$CALLBACK_2};
    }

    public void setCallbacks(Callback[] var1) {
        this.CGLIB$CALLBACK_0 = (NoOp)var1[0];
        this.CGLIB$CALLBACK_1 = (MethodInterceptor)var1[1];
        this.CGLIB$CALLBACK_2 = (MethodInterceptor)var1[2];
    }

    static {
        CGLIB$STATICHOOK3();
    }
}

3. JOOR 封装jdk代理

简单带过一下,毕竟这个框架虽然好用,但并不受到广泛关注:

开源(目前在github维护)、轻量级(无第三方依赖)、支持链式调用 的 java反射库

3.1 先来个代理api的测试用例

    @DisplayName("官方MD中的代理用例")
    @Test
    void t1() {
        // 创建String的实例,同时作为 StringProxy的代理实例
        String stringProxy = Reflect.onClass(String.class.getName())
                .create(" hello world ")
                // step into ...
                // jdk代理api
                .as(StringProxy.class)
                .substring(6);
        System.err.println(stringProxy);
    }

3.2 源码

    /**
     * Create a proxy for the wrapped object allowing to typesafely invoke methods
     * on it using a custom interface.
     *
     * @param proxyType The interface type that is implemented by the proxy
     * @return A proxy for the wrapped object
     */
    public <P> P as(Class<P> proxyType) {
    	// step into ...
        return as(proxyType, new Class[0]);
    }

    /**
     * Create a proxy for the wrapped object allowing to typesafely invoke
     * methods on it using a custom interface.
     *
     * @param proxyType The interface type that is implemented by the proxy
     * @param additionalInterfaces Additional interfaces that are implemented by
     *            the proxy
     * @return A proxy for the wrapped object
     */
    @SuppressWarnings("unchecked")
    public <P> P as(final Class<P> proxyType, final Class<?>... additionalInterfaces) {
        final boolean isMap = (object instanceof Map);
        final InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                String name = method.getName();

                // Actual method name matches always come first
                try {
                    return on(type, object).call(name, args).get();
                }

                // [#14] Emulate POJO behaviour on wrapped map objects
                catch (ReflectException e) {
                    if (isMap) {
                        Map<String, Object> map = (Map<String, Object>) object;
                        int length = (args == null ? 0 : args.length);

                        if (length == 0 && name.startsWith("get")) {
                            return map.get(property(name.substring(3)));
                        }
                        else if (length == 0 && name.startsWith("is")) {
                            return map.get(property(name.substring(2)));
                        }
                        else if (length == 1 && name.startsWith("set")) {
                            map.put(property(name.substring(3)), args[0]);
                            return null;
                        }
                    }


                    if (method.isDefault()) {
                        Lookup proxyLookup = null;

                        // Java 9 version
                        if (CACHED_LOOKUP_CONSTRUCTOR == null) {







                            // Java 9 version for Java 8 distribution (jOOQ Open Source Edition)
                            if (proxyLookup == null)
                                proxyLookup = onClass(MethodHandles.class)
                                    .call("privateLookupIn", proxyType, MethodHandles.lookup())
                                    .call("in", proxyType)
                                    .<Lookup> get();
                        }

                        // Java 8 version
                        else
                            proxyLookup = CACHED_LOOKUP_CONSTRUCTOR.newInstance(proxyType);

                        return proxyLookup.unreflectSpecial(method, proxyType)
                            .bindTo(proxy)
                            .invokeWithArguments(args);
                    }


                    throw e;
                }
            }
        };

        Class<?>[] interfaces = new Class[1 + additionalInterfaces.length];
        interfaces[0] = proxyType;
        System.arraycopy(additionalInterfaces, 0, interfaces, 1, additionalInterfaces.length);
	
		// 原汁原味
        return (P) Proxy.newProxyInstance(proxyType.getClassLoader(), interfaces, handler);
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/706109.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

BPMN2.0规范简介

1 概述 BPMN(Business Process Model & Notation)&#xff0c;中文名为业务流程模型与符号。BPMN2.0是OMG(Object Management Group&#xff0c;对象管理组织)制定的&#xff0c;其主要目的是既给用户提供一套简单的、容易理解的机制&#xff0c;以便用户创建流程模型&…

项目性能优化-内存泄漏检测与修改

最近终于有空优化一波项目的性能了&#xff0c;第一波借助Android Studio自带的Profiler工具检测内存泄漏。 第一步、创建Profiler的SESSIONS 第二步、进入MEMORY内存监控 右侧带有绿色原点的就是此时运行的Profiler的SESSION,点击右侧MEMORY进入内存监控的详情模块 第三步…

缓存三击-缓存穿透、缓存雪崩、缓存击穿

缓存三击-缓存穿透、缓存雪崩、缓存击穿 ⭐⭐⭐⭐⭐⭐ Github主页&#x1f449;https://github.com/A-BigTree 笔记链接&#x1f449;https://github.com/A-BigTree/Code_Learning ⭐⭐⭐⭐⭐⭐ Spring专栏&#x1f449;https://blog.csdn.net/weixin_53580595/category_12279…

【产品设计】掌握“4+X”模型,从0到1构建B端产品

“4X”模型是什么 4个阶段&#xff1a;规划阶段&#xff0c;设计阶段&#xff0c;实现阶段&#xff0c;迭代阶段 X:项目管理&#xff0c;数据分析&#xff0c;产品运营 1、规划阶段 这是一个产品的开始&#xff0c;它决定了产品的设计方向和基调。主要包括用户分析、市场分…

爬虫入门指南(4): 使用Selenium和API爬取动态网页的最佳方法

文章目录 动态网页爬取静态网页与动态网页的区别使用Selenium实现动态网页爬取Selenium 的语法及介绍Selenium简介安装和配置创建WebDriver对象页面交互操作 元素定位 等待机制页面切换和弹窗处理截图和页面信息获取关闭WebDriver对象 使用API获取动态数据未完待续.... 动态网页…

JVM-垃圾回收-基础知识

基础知识 什么是垃圾 简单说就是没有被任何引用指向的对象就是垃圾。后面会有详细说明。 和C的区别 java&#xff1a;GC处理垃圾&#xff0c;开发效率高&#xff0c;执行效率低 C&#xff1a;手工处理垃圾&#xff0c;如果忘记回收&#xff0c;会导致内存泄漏问题。如果回…

Linux Mint 21.2“Victoria”Beta 发布

导读近日消息&#xff0c;Beta 版 Linux Mint 21.2 “Victoria” 于今天发布&#xff0c;用户可以访问官网下载镜像。 Linux Mint 21.2 代号 “Victoria” &#xff0c;基于 Canonical 长期支持的 Ubuntu 22.04 LTS&#xff08;Jammy Jellyfish&#xff09;操作系统&#xff0…

2023年第三届工业自动化、机器人与控制工程国际会议

会议简介 Brief Introduction 2023年第三届工业自动化、机器人与控制工程国际会议&#xff08;IARCE 2023&#xff09; 会议时间&#xff1a;2023年10月27 -30日 召开地点&#xff1a;中国成都 大会官网&#xff1a;www.iarce.org 2023年第三届工业自动化、机器人与控制工程国际…

JAVA http

javahttp 请求数据格式servletservlet生命周期servletrequest获取请求数据解决乱码response相应字符&字节数据 请求数据格式 servlet servlet生命周期 servlet request获取请求数据 解决乱码 response相应字符&字节数据 response.setHeader("content-type",…

A. Portal(dp优化枚举)

Problem - 1580A - Codeforces CQXYM发现了一个大小为nm的矩形。矩形由n行m列的方块组成&#xff0c;每个方块可以是黑曜石方块或空方块。CQXYM可以通过一次操作将黑曜石方块变为空方块&#xff0c;或将空方块变为黑曜石方块。 一个大小为ab的矩形M被称为传送门&#xff0c;当…

【Linux】程序员的基本素养学习

这是目录 写在前面一、内存管理1、分段2、分页 二、线程管理三、静态库1、编译1.1、预处理1.2、编译1.3、汇编1.4、链接2、编译器3、目标文件**.text****.data****.bss****__attribute__** 3.1、符号3.2、兼容C语言 -- extern C4、链接 -- ld 写在前面 本文记录自己的学习生涯…

五.组合数据类型

目录 1、数组类型 声明数组 初始化数组 数组赋值 访问数组元素 2、切片类型 1、定义切片 2、切片初始化 3、访问 4、空(nil)切片 5、切片的增删改查操作&#xff1a; 3、指针类型 1、什么是指针 2、如何使用指针、指针使用流程&#xff1a; 3、Go 空指针 4、指…

chatgpt赋能python:如何将Python打包-一个SEO优化指南

如何将Python打包 - 一个SEO优化指南 作为一名拥有10年Python编程经验的工程师&#xff0c;我意识到很多Python开发者面临一个共同的问题&#xff1a;如何将他们的Python项目打包并发布到PyPI上&#xff1f;打包一个Python项目不仅可以让您的代码更加组织化&#xff0c;也可以…

如何拆分PDF?拆分PDF软件分享!​

那么如何拆分PDF&#xff1f;PDF是一种流行的电子文档格式&#xff0c;它可以在不同的操作系统和设备上进行查看和共享&#xff0c;而不会因为不同的软件或硬件而出现兼容性问题。同时&#xff0c;在使用的过程中&#xff0c;PDF拆分PDF文件是一个比较常见的需求&#xff0c;它…

threejs入门

个人博客地址: https://cxx001.gitee.io 前言 随着HTML5的发布&#xff0c;我们可以通过WebGL在浏览器上直接使用显卡资源来创建高性能的二维和三维图形&#xff0c;但是直接使用WebGL编程来创建三维场景十分复杂而且还容易出问题。而使用Three.js库可以简化这个过程&#xff…

机器学习——决策树1(三种算法)

要开始了…内心还是有些复杂的 因为涉及到熵…单纯的熵&#xff0c;可以单纯 复杂的熵&#xff0c;如何能通俗理解呢… 我也没有底气&#xff0c;且写且思考吧 1. 决策树分类思想 首先&#xff0c;决策树的思想&#xff0c;有点儿像KNN里的KD树。 KNN里的KD树&#xff0c;是每…

如何将非平稳的时间序列变为平稳的时间序列?

可以采用现代信号处理算法&#xff0c;比如小波分解&#xff0c;经验模态分解&#xff0c;变分模态分解等算法。 以经济金融领域的数据为例&#xff0c;经济金融领域的数据作为一种时间序列&#xff0c;和我们平常工程领域分析的信号具有相同特性。一般来说&#xff0c;信号是…

在 Maya、ZBrush 和 Arnold 中重塑来自邪恶西部的 Edgar Gravenor

今天瑞云渲染小编给大家带来Giancarlo Penton 介绍的Edgar Gravenor项目背后过程&#xff0c;展示了皮肤纹理和头发是如何制作的&#xff0c;并解释了详细的服装是如何设置的。 介绍 大家好&#xff0c;我的名字是Giancarlo Penton。我是一名3D角色艺术家&#xff0c;最近毕业…

从零开始 Spring Boot 53:JPA 属性转换器

从零开始 Spring Boot 53&#xff1a;JPA 属性转换器 图源&#xff1a;简书 (jianshu.com) 这篇文章介绍如何在 JPA&#xff08;Hibernate&#xff09;中使用属性转换器。 在前篇文章中&#xff0c;我介绍了如何使用Embedded和Embeddable将一个类型嵌入实体类&#xff0c;并映…

初识mysql之表内容的增删查改

目录 一、插入 1. 插入基础语法 2. 单行数据 全列插入 3. 多行数据 全列插入 4. 插入&#xff0c;失败则更新 5. 替换 二、基础查询 1. 查询基础语法 2. 全列查询 3. 指定列查询 4. 表达式查询 5. 结果去重 6. where条件 6.1 比较运算符与逻辑运算符 6.2 查询…