java基础之动态代理原理分析

JDK动态代理

  1. 静态代理要为每个目标类创建一个代理类,当需要代理的对象太多,那么代理类也变得很多。同时代理类违背了可重复代理只写一次的原则。jdk给我们提供了动态代理,动态代理就是在程序运行的过程中,根据被代理的接口来动态生成代理类的class文件,并加载运行的过程。JDK从1.3开始支持动态代理

  2. Jdk的动态要求目标对象必须实现接口,因为它创建代理对象的时候是根据接口创建的。如果不实现接口,jdk无法给目标对象创建代理对象。被代理对象可以实现多个接口,创建代理时指定创建某个接口的代理对象就可以调用该接口定义的方法了。

  3. JDK提供了java.lang.reflect.Proxy类来实现动态代理的,可通过它的newProxyInstance来获得代理实现类。同时对于代理的接口的实际处理,是一个java.lang.reflect.InvocationHandler,它提供了一个invoke方法供实现者提供相应的代理逻辑的实现。

案例

  1. 定义接口和实现类
public interface UserService {
    public void addUser(String userId, String userName);
}
public class UserServiceImpl implements UserService {
    @Override
    public void addUser(String userId, String userName) {
        //Thread.currentThread().getStackTrace()[1]是你当前方法执行堆栈
        //Thread.currentThread().getStackTrace()[2]就是上一级的方法堆栈 以此类推
        System.out.printf("%s.%s,userId:%s,userName:%s\n",
                this.getClass().getName(),
                Thread.currentThread().getStackTrace()[1].getMethodName(),
                userId, userName);
    }
}
  1. 定义InvocationHandler实现类

    public class LogInvocationHandler implements InvocationHandler {
        private Object targertObject;
    
        public Object newInstance(Object targertObject) {
            this.targertObject = targertObject;
            Class targertClass = targertObject.getClass();
            return Proxy.newProxyInstance(targertClass.getClassLoader(), targertClass.getInterfaces(), this);
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) {
            System.out.println("Before...");
            Object result = null;
            try {
                result = method.invoke(targertObject, args);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("After...");
            return result;
        }
    
    
  2. 定义客户端调用类

    public class Client {
        public static void main(String[] args) {
           System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
            UserService userService = (UserService) new LogInvocationHandler().newInstance(new UserServiceImpl());
            userService.addUser("00001", "jannal");
        }
    }
    
    //输出
    Before...
    cn.jannal.proxy.UserServiceImpl.addUser,userId:00001,userName:jannal
    After...
    

源码分析

  1. JDK版本1.8.0 _45

  2. java.lang.reflect.Proxy#newProxyInstance

    private static final Class<?>[] constructorParams =
            { InvocationHandler.class };
    
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);
        //克隆实现的所有接口,为什么要复制??
        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }
    
        /*
         * Look up or generate the designated proxy class.
         * 查找或生成指定的代理类,名称为com.sun.proxy.$Proxy0
         */
        Class<?> cl = getProxyClass0(loader, intfs);
    
        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
            //获取InvocationHandler的构造参数
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            //判断构造器是否是public,如果不是设置暴力访问setAccessible(true)
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            //获取InvocationHandler的实例对象
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }
    
    
    private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        //实现的接口最多不能超过65535                                   
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }
    
        // If the proxy class defined by the given loader implementing
        // the given interfaces exists, this will simply return the cached copy;
        // otherwise, it will create the proxy class via the ProxyClassFactory
        // JDK对代理进行了缓存,如果已经存在相应的代理类,则直接返回,否则才会通过ProxyClassFactory来创建代理.WeakCache在get取不到值时会创建一个值
        return proxyClassCache.get(loader, interfaces);
    }
    
    
  3. java.lang.reflect.Proxy.ProxyClassFactory是Proxy静态私有的内部类,主要就是用来根据classLoader和接口数组来生成代理的Class对象

     public static final String PROXY_PACKAGE = "com.sun.proxy"
    
     private static final class ProxyClassFactory
         implements BiFunction<ClassLoader, Class<?>[], Class<?>>
     {
         // prefix for all proxy class names
         //所有代理类名字的前缀
         private static final String proxyClassNamePrefix = "$Proxy";
    
         // next number to use for generation of unique proxy class names
         //用于生成代理类名字的计数器
         private static final AtomicLong nextUniqueNumber = new AtomicLong();
    
         @Override
         public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
    
             Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
             for (Class<?> intf : interfaces) {
                 /*
                  * Verify that the class loader resolves the name of this
                  * interface to the same Class object.
                  */
                 Class<?> interfaceClass = null;
                 try {
                     //根据接口全限定类名和classLoader,获取到接口class对象 
                     interfaceClass = Class.forName(intf.getName(), false, loader);
                 } catch (ClassNotFoundException e) {
                 }
                 //猜测可能是为了避免不同类加载器加载相同的接口类
                 if (interfaceClass != intf) {
                     throw new IllegalArgumentException(
                         intf + " is not visible from class loader");
                 }
                 /*
                  * Verify that the Class object actually represents an
                  * interface.
                  * 判断创建出来的接口是不是接口类型
                  */
                 if (!interfaceClass.isInterface()) {
                     throw new IllegalArgumentException(
                         interfaceClass.getName() + " is not an interface");
                 }
                 /*
                  * Verify that this interface is not a duplicate.
                  */
                 if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                     throw new IllegalArgumentException(
                         "repeated interface: " + interfaceClass.getName());
                 }
             }
    
             String proxyPkg = null;     // package to define proxy class in
             int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
    
             /*
              * Record the package of a non-public proxy interface so that the
              * proxy class will be defined in the same package.  Verify that
              * all non-public proxy interfaces are in the same package.
              * 验证所有非public的代理接口是否都在同一个包下
              */
             for (Class<?> intf : interfaces) {
                 int flags = intf.getModifiers();
                 if (!Modifier.isPublic(flags)) {
                     accessFlags = Modifier.FINAL;
                     String name = intf.getName();
                     int n = name.lastIndexOf('.');
                     String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                     if (proxyPkg == null) {
                         proxyPkg = pkg;
                     } else if (!pkg.equals(proxyPkg)) {
                         throw new IllegalArgumentException(
                             "non-public interfaces from different packages");
                     }
                 }
             }
             //如果都是public接口设定全限定类名com.sun.proxy.$proxy0
             if (proxyPkg == null) {
                 // if no non-public proxy interfaces, use com.sun.proxy package
                 proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
             }
    
             /*
              * Choose a name for the proxy class to generate.
              */
             long num = nextUniqueNumber.getAndIncrement();
            // 默认情况下,代理类的完全限定名为:com.sun.proxy.$Proxy0,com.sun.proxy.$Proxy1……依次递增
             String proxyName = proxyPkg + proxyClassNamePrefix + num;
    
             /*
              * Generate the specified proxy class.
              * 根据代理类全限定类名,接口数组,访问修饰符,生成代理类的字节码
              */
             byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                 proxyName, interfaces, accessFlags);
             try {
                 //根据生成的字节码,创建class对象并返回。Native方法
                 return defineClass0(loader, proxyName,
                                     proxyClassFile, 0, proxyClassFile.length);
             } catch (ClassFormatError e) {
                 /*
                  * A ClassFormatError here means that (barring bugs in the
                  * proxy class generation code) there was some other
                  * invalid aspect of the arguments supplied to the proxy
                  * class creation (such as virtual machine limitations
                  * exceeded).
                  */
                 throw new IllegalArgumentException(e.toString());
             }
         }
     }
    
  4. sun.misc.ProxyGenerator#generateProxyClass:根据代理类全限定类名,接口数组,访问修饰符,生成代理类的字节码,默认不生成到文件,可以配置System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");开启。使用path.toAbsolutePath()查看生成的目录**[工程目录]/com/sun/proxy/$Proxy0.class**

    
    /**
     * Generate a proxy class given a name and a list of proxy interfaces.
     *
     * @param name        the class name of the proxy class
     * @param interfaces  proxy interfaces
     * @param accessFlags access flags of the proxy class
    */
    public static byte[] generateProxyClass(final String name,
                                            Class<?>[] interfaces,
                                            int accessFlags)
    {
        ProxyGenerator gen = new ProxyGenerator(name, interfaces, accessFlags);
        //真正生成字节码的方法
        final byte[] classFile = gen.generateClassFile();
        //如果saveGeneratedFiles为true 则生成字节码文件,也可以通过返回的bytes自己输出
        if (saveGeneratedFiles) {
            java.security.AccessController.doPrivileged(
            new java.security.PrivilegedAction<Void>() {
                public Void run() {
                    try {
                        int i = name.lastIndexOf('.');
                        Path path;
                        if (i > 0) {
                            Path dir = Paths.get(name.substring(0, i).replace('.', File.separatorChar));
                            Files.createDirectories(dir);
                            path = dir.resolve(name.substring(i+1, name.length()) + ".class");
                        } else {
                            path = Paths.get(name + ".class");
                        }
                        Files.write(path, classFile);
                        return null;
                    } catch (IOException e) {
                        throw new InternalError(
                            "I/O exception saving generated file: " + e);
                    }
                }
            });
        }
    
        return classFile;
    }
    
    
  5. 除了设置sun.misc.ProxyGenerator.saveGeneratedFiles,还可以手动将代理的类写到磁盘上

    //手动将代理的类写到磁盘上
    byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy1", UserServiceImpl.class.getInterfaces());
     FileOutputStream out = null;
     try {
         out = new FileOutputStream("$Proxy1.class");
         out.write(classFile);
         out.flush();
     } catch (Exception e) {
         e.printStackTrace();
     } finally {
         try {
             if (out != null) {
                 out.close();
             }
         } catch (IOException e) {
             e.printStackTrace();
         }
     }
    
  6. 生成的com.sun.proxy.$Proxy0反编译后的源码

    package com.sun.proxy;
    
    import cn.jannal.proxy.UserService;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.lang.reflect.UndeclaredThrowableException;
    //实现类UserService接口并继承Proxy
    public final class $Proxy0 extends Proxy implements UserService {
        private static Method m1;
        private static Method m3;
        private static Method m2;
        private static Method m0;
    
        static {
            try {
                m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
                m3 = Class.forName("cn.jannal.proxy.UserService").getMethod("addUser", new Class[]{Class.forName("java.lang.String"), Class.forName("java.lang.String")});
                m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
                m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            } catch (NoSuchMethodException localNoSuchMethodException) {
                throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
            } catch (ClassNotFoundException localClassNotFoundException) {
                throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
            }
        }
    
        public $Proxy0(InvocationHandler paramInvocationHandler) {
            super(paramInvocationHandler);
        }
        @Override
        public final void addUser(String paramString1, String paramString2) {
            try {
                this.h.invoke(this, m3, new Object[]{paramString1, paramString2});
                return;
              
            } catch (Error | RuntimeException localError) {
                throw localError;
            } catch (Throwable localThrowable) {
                throw new UndeclaredThrowableException(localThrowable);
            }
        }
    
        @Override
        public final String toString() {
            try {
                return (String) this.h.invoke(this, m2, null);
            } catch (Error | RuntimeException localError) {
                throw localError;
            } catch (Throwable localThrowable) {
                throw new UndeclaredThrowableException(localThrowable);
            }
        }
    
        @Override
        public final boolean equals(Object paramObject) {
            try {
                return ((Boolean) this.h.invoke(this, m1, new Object[]{paramObject})).booleanValue();
            } catch (Error | RuntimeException localError) {
                throw localError;
            } catch (Throwable localThrowable) {
                throw new UndeclaredThrowableException(localThrowable);
            }
        }
        
        @Override
        public final int hashCode() {
            try {
                return ((Integer) this.h.invoke(this, m0, null)).intValue();
            } catch (Error | RuntimeException localError) {
                throw localError;
            } catch (Throwable localThrowable) {
                throw new UndeclaredThrowableException(localThrowable);
            }
        }
    }
    

总结

  1. 动态生成的代理类有如下特点
    • 继承了Proxy类,实现了代理的接口。因为java不能多继承,所以JDK动态代理不支持对实现类的代理,只支持接口的代理
    • 代理类只有一个构造方法,参数为InvocationHandler
    • 生成静态代码块来初始化接口中方法的Method对象,以及Object类的equals、hashCode、toString方法
    • 覆写了Object类的equals、hashCode、toString,它们都只是简单的调用了InvocationHandler的invoke方法,即JDK的动态代理还可以代理上述三个方法
    • 代理类的名称com.sun.proxy.$Proxy+递增的数字
    • JDK代理底层使用的也是字节码技术,并不是反射

Cglib

  1. CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。因为没有实现接口该类无法使用JDK代理,CGLIB通过继承方式实现代理

  2. 依赖版本

    compile group: 'cglib', name: 'cglib', version: '3.3.0'
    

案例

  1. 定义实现类

    public class UserServiceImpl {
        public void addUser(String userId, String userName) {
            //Thread.currentThread().getStackTrace()[1]是你当前方法执行堆栈
            //Thread.currentThread().getStackTrace()[2]就是上一级的方法堆栈 以此类推
            System.out.printf("%s.%s,userId:%s,userName:%s\n",
                    this.getClass().getName(),
                    Thread.currentThread().getStackTrace()[1].getMethodName(),
                    userId, userName);
        }
    }
    
  2. 自定义MethodInterceptor实现类

    public class LogMethodInterceptor implements MethodInterceptor {
        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            System.out.println("Before...");
            //调用原始对象
            Object result = proxy.invokeSuper(obj, args);
            System.out.println("After...");
            return result;
        }
    }
    
  3. 客户端调用类

    public class CgLibClient {
        public static void main(String[] args) {
            //生成字节码文件,配置生成目录
            System.getProperties().put(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, ".");
            //System.getProperties().put("cglib.debugLocation", ".");
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(UserServiceImpl.class);
            enhancer.setCallback(new LogMethodInterceptor());
            UserServiceImpl userService = (UserServiceImpl) enhancer.create();
            userService.addUser("00002", "jannal");
        }
    }
    Before...
    cn.jannal.cglib.UserServiceImpl$$EnhancerByCGLIB$$c52b7540.addUser,userId:00002,userName:jannal
    After...
    
  4. 生成的class

    UserServiceImpl$$EnhancerByCGLIB$$c52b7540$$FastClassByCGLIB$$d5e2d5ae.class
    UserServiceImpl$$EnhancerByCGLIB$$c52b7540.class
    UserServiceImpl$$FastClassByCGLIB$$6a79be94.class  
    

源码分析

  1. cn.jannal.cglib.UserServiceImpl$$EnhancerByCGLIB$$c52b7540反编译代码

    public class UserServiceImpl$$EnhancerByCGLIB$$c52b7540 extends UserServiceImpl 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 MethodInterceptor CGLIB$CALLBACK_0;
      
      private static Object CGLIB$CALLBACK_FILTER;
      
      private static final Method CGLIB$addUser$0$Method;
      
      private static final MethodProxy CGLIB$addUser$0$Proxy;
      
      private static final Object[] CGLIB$emptyArgs;
      
      private static final Method CGLIB$equals$1$Method;
      
      private static final MethodProxy CGLIB$equals$1$Proxy;
      
      private static final Method CGLIB$toString$2$Method;
      
      private static final MethodProxy CGLIB$toString$2$Proxy;
      
      private static final Method CGLIB$hashCode$3$Method;
      
      private static final MethodProxy CGLIB$hashCode$3$Proxy;
      
      private static final Method CGLIB$clone$4$Method;
      
      private static final MethodProxy CGLIB$clone$4$Proxy;
       //这里通过静态代码块初始化上面用到的静态变量,主要使用到反射
      static void CGLIB$STATICHOOK1() {
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        Class clazz1 = Class.forName("cn.jannal.cglib.UserServiceImpl$$EnhancerByCGLIB$$c52b7540");
        Class clazz2;
        CGLIB$equals$1$Method = ReflectUtils.findMethods(new String[] { "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;" }, (clazz2 = Class.forName("java.lang.Object")).getDeclaredMethods())[0];
        CGLIB$equals$1$Proxy = MethodProxy.create(clazz2, clazz1, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1");
        CGLIB$toString$2$Method = ReflectUtils.findMethods(new String[] { "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;" }, (clazz2 = Class.forName("java.lang.Object")).getDeclaredMethods())[1];
        CGLIB$toString$2$Proxy = MethodProxy.create(clazz2, clazz1, "()Ljava/lang/String;", "toString", "CGLIB$toString$2");
        CGLIB$hashCode$3$Method = ReflectUtils.findMethods(new String[] { "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;" }, (clazz2 = Class.forName("java.lang.Object")).getDeclaredMethods())[2];
        CGLIB$hashCode$3$Proxy = MethodProxy.create(clazz2, clazz1, "()I", "hashCode", "CGLIB$hashCode$3");
        CGLIB$clone$4$Method = ReflectUtils.findMethods(new String[] { "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;" }, (clazz2 = Class.forName("java.lang.Object")).getDeclaredMethods())[3];
        CGLIB$clone$4$Proxy = MethodProxy.create(clazz2, clazz1, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4");
        ReflectUtils.findMethods(new String[] { "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;" }, (clazz2 = Class.forName("java.lang.Object")).getDeclaredMethods());
        CGLIB$addUser$0$Method = ReflectUtils.findMethods(new String[] { "addUser", "(Ljava/lang/String;Ljava/lang/String;)V" }, (clazz2 = Class.forName("cn.jannal.cglib.UserServiceImpl")).getDeclaredMethods())[0];
        CGLIB$addUser$0$Proxy = MethodProxy.create(clazz2, clazz1, "(Ljava/lang/String;Ljava/lang/String;)V", "addUser", "CGLIB$addUser$0");
        ReflectUtils.findMethods(new String[] { "addUser", "(Ljava/lang/String;Ljava/lang/String;)V" }, (clazz2 = Class.forName("cn.jannal.cglib.UserServiceImpl")).getDeclaredMethods());
      }
       //这个方法就是直接调用原来的被代理类(父类)的方法
      final void CGLIB$addUser$0(String paramString1, String paramString2) { super.addUser(paramString1, paramString2); }
       //这个方法就是通过方法代理进行回调,里面用到了Callback实例
      public final void addUser(String paramString1, String paramString2) {
        if (this.CGLIB$CALLBACK_0 == null) {
          this.CGLIB$CALLBACK_0;
          CGLIB$BIND_CALLBACKS(this);
        } 
        if (this.CGLIB$CALLBACK_0 != null) {
          new Object[2][0] = paramString1;
          new Object[2][1] = paramString2;
          return;
        } 
        super.addUser(paramString1, paramString2);
      }
    
     ...省略...
    
    }
    
    
    

总结

  1. cgblib代理如下特性

    • CGLIB也会进行代理hashCode()equals()toString()clone(),但是getClass()wait()等方法不会,因为它是final方法,CGLIB无法代理

    • CGLIB在类生成期间的操作会相对耗时,而且生成的类数目比较多,会占据大量永久代或者元空间的内存。子类一旦生成,后面的方法调用就会变成搜索方法索引和直接调用,这样的操作在特定的条件下效率会比JDK的反射高

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【优质项目推荐】 1、项目代码均经过严格本地测试,运行OK,确保功能稳定后才上传平台。可放心下载并立即投入使用,若遇到任何使用问题,随时欢迎私信反馈与沟通,博主会第一时间回复。 2、项目适用于计算机相关专业(如计科、信息安全、数据科学、人工智能、通信、物联网、自动化、电子信息等)的在校学生、专业教师,或企业员工,小白入门等都适用。 3、该项目不仅具有很高的学习借鉴价值,对于初学者来说,也是入门进阶的绝佳选择;当然也可以直接用于 毕设、课设、期末大作业或项目初期立项演示等。 3、开放创新:如果您有一定基础,且热爱探索钻研,可以在此代码基础上二次开发,进行修改、扩展,创造出属于自己的独特应用。 欢迎下载使用优质资源!欢迎借鉴使用,并欢迎学习交流,共同探索编程的无穷魅力! 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值