代理模式

说起代理模式,我们想到的就是Spring AOP的实现,主要场景有记录日志,事务管理等。
而Spring AOP实现代理模式有两种方式,一种是基于接口的JDK动态代理,一种是基于类的CGlib,今天我们要说的就是基于接口的JDK动态代理。主要从两个方面介绍,一个是使用,一个是原理。
 
JDK的动态代理如何使用呢?比如我们有这样的需求,要统计Service层的各个方法的开始时间,结束时间,及每个方法的运行时间。如果用常规做法呢就是在每个方法里加startTime,endTime然后相减,这太累了,那方法多得去了。下面来看用JDK的动态代理如何实现这个功能:
以UserService举例吧:
接口:
public interface UserService {
	
	void addUser();
	
	void editUser();

}
实现类:
public class UserServiceImpl implements UserService {

	@Override
	public void addUser() {
		System.out.println("---------- addUser success");
	}

	@Override
	public void editUser() {
		System.out.println("---------- editUser success");
	}

}
生成代理对象的Handler,要实现InvocationHandler接口并实现invoke方法:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class LoggerProxyHandler implements InvocationHandler {
	
	private Object object;	//被代理对象
	
	public LoggerProxyHandler(Object object) {
		this.object = object;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		long start = System.currentTimeMillis();
		System.out.println("++++++++++++ startTime: "+start);
		
		Object obj = method.invoke(object, args);
		
		long end = System.currentTimeMillis();
		System.out.println("++++++++++++ endTime: "+end+", spendTime: "+(end-start)+"ms");
		
		return obj;
	}

}
调用(测试)类:
import java.lang.reflect.Proxy;

public class ProxyTest {
	
	public static void main(String[] args) {
		//被代理对象	
		UserService userService = new UserServiceImpl();
		//生成代理对象的Handler
		LoggerProxyHandler handler = new LoggerProxyHandler(userService);
		//生成代理对象	
		UserService userServiceProxy = (UserService)Proxy.newProxyInstance(ProxyTest.class.getClassLoader(), new Class[]{UserService.class}, handler);
		userServiceProxy.addUser();
		System.out.println();
		userServiceProxy.editUser();
	}

}
运行结果为:
++++++++++++ startTime: 1521983621301
---------- addUser success
++++++++++++ endTime: 1521983621302, spendTime: 1ms

++++++++++++ startTime: 1521983621302
---------- editUser success
++++++++++++ endTime: 1521983621302, spendTime: 0ms
以上就是用JDK动态代理实现的记录日志,主要实现InvocationHandler接口并实现invoke方法,还有一点就是JDK动态代理只能针对接口进行代理,不能对类,要对类进行代理可以使用CGlib代理。
 
使用还是比较简单的,背后的原理我们可以一起去了解下,这里摘自网友的博客 代理模式详解(包含原理详解),他从源码的角度进行了详细分析,他的博客文章写得很好,大家可以关注下: 
 
首先当然就是要进入Proxy的newProxyInstance方法,这里是产生代理的入口,源码如下。
public static Object newProxyInstance(ClassLoader loader,
                      Class<?>[] interfaces,
                      InvocationHandler h)
    throws IllegalArgumentException
    {
    if (h == null) {
        throw new NullPointerException();
    }

    /*
     * Look up or generate the designated proxy class.
     */
    Class cl = getProxyClass(loader, interfaces);

    /*
     * Invoke its constructor with the designated invocation handler.
     */
    try {
        Constructor cons = cl.getConstructor(constructorParams);
        return (Object) cons.newInstance(new Object[] { h });
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString());
    } catch (IllegalAccessException e) {
        throw new InternalError(e.toString());
    } catch (InstantiationException e) {
        throw new InternalError(e.toString());
    } catch (InvocationTargetException e) {
        throw new InternalError(e.toString());
    }
    }
这个方法其实很简单,首先获取了代理类的运行时Class引用,然后调用了这个Class中的构造方法,这个构造方法只有一个参数,正是InvocationHandler接口,由此产生了一个代理类的实例。那么关键的地方就在于如何获取的代理类运行时的class信息的呢?我们进入getProxyClass方法看一下。为了方便起见,我直接加注释,这个方法需要解释的地方比较多。
public static Class<?> getProxyClass(ClassLoader loader,
            Class<?>... interfaces) throws IllegalArgumentException {
        //如果传入的接口长度大于65535就抛出异常,我去你妹。。。
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }
        
        Class proxyClass = null;

        /* collect interface names to use as key for proxy class cache */
        String[] interfaceNames = new String[interfaces.length];

        Set interfaceSet = new HashSet(); // for detecting duplicates

        for (int i = 0; i < interfaces.length; i++) {
            /*
             * Verify that the class loader resolves the name of this interface
             * to the same Class object.
             */
            String interfaceName = interfaces[i].getName();
            Class interfaceClass = null;
            try {
                //加载每一个接口的运行时Class信息
                interfaceClass = Class.forName(interfaceName, false, loader);
            } catch (ClassNotFoundException e) {
            }
            //如果采用你传入的类加载器载入的Class和你传入的Class不相等则抛出异常
            if (interfaceClass != interfaces[i]) {
                throw new IllegalArgumentException(interfaces[i]
                        + " 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.contains(interfaceClass)) {
                throw new IllegalArgumentException("repeated interface: "
                        + interfaceClass.getName());
            }
            interfaceSet.add(interfaceClass);

            interfaceNames[i] = interfaceName;
        }

        /*
         * Using string representations of the proxy interfaces as keys in the
         * proxy class cache (instead of their Class objects) is sufficient
         * because we require the proxy interfaces to be resolvable by name
         * through the supplied class loader, and it has the advantage that
         * using a string representation of a class makes for an implicit weak
         * reference to the class.
         */
        Object key = Arrays.asList(interfaceNames);

        /*
         * Find or create the proxy class cache for the class loader.
         */
        Map cache;
        synchronized (loaderToCache) {
            //这个是为了存储每一个类加载器所载入过的代理接口的代理类
            cache = (Map) loaderToCache.get(loader);
            if (cache == null) {
                cache = new HashMap();
                loaderToCache.put(loader, cache);
            }
            /*
             * This mapping will remain valid for the duration of this method,
             * without further synchronization, because the mapping will only be
             * removed if the class loader becomes unreachable.
             */
        }

        /*
         * Look up the list of interfaces in the proxy class cache using the
         * key. This lookup will result in one of three possible kinds of
         * values: null, if there is currently no proxy class for the list of
         * interfaces in the class loader, the pendingGenerationMarker object,
         * if a proxy class for the list of interfaces is currently being
         * generated, or a weak reference to a Class object, if a proxy class
         * for the list of interfaces has already been generated.
         */
        synchronized (cache) {
            /*
             * Note that we need not worry about reaping the cache for entries
             * with cleared weak references because if a proxy class has been
             * garbage collected, its class loader will have been garbage
             * collected as well, so the entire cache will be reaped from the
             * loaderToCache map.
             */
            do {
                //检查是否有生成好的代理
                Object value = cache.get(key);
                if (value instanceof Reference) {
                    proxyClass = (Class) ((Reference) value).get();
                }
                //有的话直接返回
                if (proxyClass != null) {
                    // proxy class already generated: return it
                    return proxyClass;
                //否则看一下这个代理类是不是正在构造中,是的话就在cache对象上等待
                } else if (value == pendingGenerationMarker) {
                    // proxy class being generated: wait for it
                    try {
                        cache.wait();
                    } catch (InterruptedException e) {
                        /*
                         * The class generation that we are waiting for should
                         * take a small, bounded time, so we can safely ignore
                         * thread interrupts here.
                         */
                    }
                    continue;
                //如果没有现成的,也没有创造中的,那就开始创造代理类
                } else {
                    /*
                     * No proxy class for this list of interfaces has been
                     * generated or is being generated, so we will go and
                     * generate it now. Mark it as pending generation.
                     */
                    //将当前代理类置为正在构造中,并直接退出循环
                    cache.put(key, pendingGenerationMarker);
                    break;
                }
            } while (true);
        }

        try {
            String proxyPkg = null; // package to define proxy class in

            //这一段是看你传入的接口中有没有不是public的接口,如果有,这些接口必须全部在一个包里定义的,否则抛异常
            /*
             * 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.
             */
            for (int i = 0; i < interfaces.length; i++) {
                int flags = interfaces[i].getModifiers();
                if (!Modifier.isPublic(flags)) {
                    String name = interfaces[i].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");
                    }
                }
            }

            if (proxyPkg == null) { // if no non-public proxy interfaces,
                proxyPkg = ""; // use the unnamed package
            }

            {
                /*
                 * Choose a name for the proxy class to generate.
                 */
                long num;
                synchronized (nextUniqueNumberLock) {
                    num = nextUniqueNumber++;
                }
                //生成一个随机代理类名
                String proxyName = proxyPkg + proxyClassNamePrefix + num;
                /*
                 * Verify that the class loader hasn't already defined a class
                 * with the chosen name.
                 */
                
                //这一句就是重中之重了,生成代理类的class文件,这就是JDK动态代理的原理了,通过动态生成class文件来产生的代理类
                //这个generateProxyClass方法下面会着重介绍
                /*
                 * Generate the specified proxy class.
                 */
                byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                        proxyName, interfaces);
                try {
                    //得到class文件二进制流后,直接载入代理类
                    proxyClass = 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());
                }
            }
            //proxyClasses这个Map是为了来判断是不是代理的Class
            // add to set of all generated proxy classes, for isProxyClass
            proxyClasses.put(proxyClass, null);

        } finally {
            /*
             * We must clean up the "pending generation" state of the proxy
             * class cache entry somehow. If a proxy class was successfully
             * generated, store it in the cache (with a weak reference);
             * otherwise, remove the reserved entry. In all cases, notify all
             * waiters on reserved entries in this cache.
             */
            synchronized (cache) {
                if (proxyClass != null) {
                    //最终将生成的代理用弱引用包装起来放到cache当中
                    cache.put(key, new WeakReference(proxyClass));
                } else {
                    //如果代理类是空则移除代理的接口所代表的key值
                    cache.remove(key);
                }
                //通知正在等待在cache对象上的线程,告诉他们可以继续了,代理Class加载完毕了
                cache.notifyAll();
            }
        }
        return proxyClass;
    }
上面基本上已经解释的很清楚了,下面就是去看一下byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces)这句话是如何处理的,也就是如何得到的代理类的class文件的,我们进去源码看一下,我依然会加上注释。
public static byte[] generateProxyClass(String paramString, Class[] paramArrayOfClass) {
        //新建一个ProxyGenerator实例,传入类名和接口数组
        ProxyGenerator localProxyGenerator = new ProxyGenerator(paramString, paramArrayOfClass);
        //这个才是真正生成class文件的地方
        final byte[] arrayOfByte = localProxyGenerator.generateClassFile();
        //看保存生成文件的标志是否为真,如果是就将class文件生成到本地,生成时要检查权限
        if (saveGeneratedFiles) {
            AccessController.doPrivileged(new PrivilegedAction() {
                public Object run() {
                    try {
                        FileOutputStream localFileOutputStream = new FileOutputStream( ProxyGenerator.dotToSlash(this.val$name) + ".class");
                        localFileOutputStream.write(arrayOfByte);
                        localFileOutputStream.close();
                        return null;
                    } catch (IOException localIOException) {
                        throw new InternalError( "I/O exception saving generated file: " + localIOException);
                    }
                }

            });
        }

        return arrayOfByte;
    }
我们继续跟踪到localProxyGenerator.generateClassFile()这一句当中,依然会加上注释。
private byte[] generateClassFile() {
        //addProxyMethod方法,就是将方法都加入到一个列表中,并与对应的class对应起来
        //这里给Object对应了三个方法hashCode,toString和equals
        addProxyMethod(hashCodeMethod, Object.class);
        addProxyMethod(equalsMethod, Object.class);
        addProxyMethod(toStringMethod, Object.class);

        //同样将接口与接口下的方法对应起来
        for (int i = 0; i < this.interfaces.length; i++) {
            localObject1 = this.interfaces[i].getMethods();
            for (int k = 0; k < localObject1.length; k++) {
                addProxyMethod(localObject1[k], this.interfaces[i]);
            }

        }
        //检查所有代理方法的返回类型
        for (Iterator localIterator1 = this.proxyMethods.values().iterator(); localIterator1
                .hasNext();) {
            localObject1 = (List) localIterator1.next();
            checkReturnTypes((List) localObject1);
        }

        Object localObject2;
        try {
            //方法中加入构造方法,这个构造方法只有一个,就是一个带有InvocationHandler接口的构造方法
            //这个才是真正给class文件,也就是代理类加入方法了,不过还没真正处理,只是先加进来等待循环,构造方法在class文件中的名称描述是<init>
            this.methods.add(generateConstructor());
            //循环代理方法
            for (localIterator1 = this.proxyMethods.values().iterator(); localIterator1
                    .hasNext();) {
                localObject1 = (List) localIterator1.next();
                for (localIterator2 = ((List) localObject1).iterator(); localIterator2
                        .hasNext();) {
                    localObject2 = (ProxyMethod) localIterator2.next();
                    //给每一个代理方法加一个Method类型的属性,数字10是class文件的标识符,代表这些属性都是private static的
                    this.fields.add(new FieldInfo(((ProxyMethod) localObject2).methodFieldName,"Ljava/lang/reflect/Method;", 10));
                    //将每一个代理方法都加到代理类的方法中
                    this.methods.add(((ProxyMethod) localObject2).generateMethod());
                }
            }
            Iterator localIterator2;
            //加入一个静态初始化块,将每一个属性都初始化,这里静态代码块也叫类构造方法,其实就是名称为<clinit>的方法,所以加到方法列表
            this.methods.add(generateStaticInitializer());
        } catch (IOException localIOException1) {
            throw new InternalError("unexpected I/O Exception");
        }
        //方法和属性个数都不能超过65535,包括刚才的接口个数也是这样,
        //这是因为在class文件中,这些个数都是用4位16进制表示的,所以最大值是2的16次方-1
        if (this.methods.size() > 65535) {
            throw new IllegalArgumentException("method limit exceeded");
        }
        if (this.fields.size() > 65535) {
            throw new IllegalArgumentException("field limit exceeded");
        }
        //这里是将类名中的.转成成斜线为了写入class文件。
        this.cp.getClass(dotToSlash(this.className));
        this.cp.getClass("java/lang/reflect/Proxy");
        for (int j = 0; j < this.interfaces.length; j++) {
            this.cp.getClass(dotToSlash(this.interfaces[j].getName()));
        }

        this.cp.setReadOnly();
        //这里开始真正的写class文件
        ByteArrayOutputStream localByteArrayOutputStream = new ByteArrayOutputStream();
        Object localObject1 = new DataOutputStream(localByteArrayOutputStream);
        try {
            //写入class文件的标识号,标识这是一个class文件
            ((DataOutputStream) localObject1).writeInt(-889275714);
            //次版本号0
            ((DataOutputStream) localObject1).writeShort(0);
            //主版本号,49代表的是JDK1.5
            ((DataOutputStream) localObject1).writeShort(49);
            //这里写入的是常量池,包括一些属性名称,类名称,方法描述符,属性描述符等等,常量池在加载时会被放到方法区或者说永久代。
            this.cp.write((OutputStream) localObject1);
            //这里写入的是这个类的访问标识,49代表的是public final,也就是说JDK动态代理生成的代理类都是final的
            ((DataOutputStream) localObject1).writeShort(49);
            //写入代理类的类名
            ((DataOutputStream) localObject1).writeShort(this.cp
                    .getClass(dotToSlash(this.className)));
            //写入代理类的父类类名,也就是Proxy类,这个位置的类如果说是JAVA文件,相当于extend后面的类,也就是父类
            ((DataOutputStream) localObject1).writeShort(this.cp
                    .getClass("java/lang/reflect/Proxy"));
            //写入代理类所实现的接口数量
            ((DataOutputStream) localObject1)
                    .writeShort(this.interfaces.length);
            //写入代理类所实现的接口类名,同样的,对于JAVA文件来说,相当于implements后面的接口,也就是实现的接口
            for (int m = 0; m < this.interfaces.length; m++) {
                ((DataOutputStream) localObject1).writeShort(this.cp
                        .getClass(dotToSlash(this.interfaces[m].getName())));
            }
            //写入属性个数
            ((DataOutputStream) localObject1).writeShort(this.fields.size());
            //写入属性描述
            for (Iterator localIterator3 = this.fields.iterator(); localIterator3
                    .hasNext();) {
                localObject2 = (FieldInfo) localIterator3.next();
                ((FieldInfo) localObject2)
                        .write((DataOutputStream) localObject1);
            }
            //写入方法个数
            ((DataOutputStream) localObject1).writeShort(this.methods.size());
            //写入方法描述,方法的code属性,以及构造方法和类构造方法都在这里被写入了。
            for (localIterator3 = this.methods.iterator(); localIterator3
                    .hasNext();) {
                localObject2 = (MethodInfo) localIterator3.next();
                ((MethodInfo) localObject2)
                        .write((DataOutputStream) localObject1);
            }
            //结束
            ((DataOutputStream) localObject1).writeShort(0);
        } catch (IOException localIOException2) {
            throw new InternalError("unexpected I/O Exception");
        }

        return localByteArrayOutputStream.toByteArray();
    }

其实代理类的class文件并不复杂,还是有很多规律可循的,所以上述过程基本上可以让各位了解下JDK动态代理生成代理类时都生成了什么东西。

                下面我们可以调用下JDK中生成Class文件的方法,并且写入到本地文件,然后使用反编译工具来看一下生成的代理类到底是什么样子的。下面是生成文件的测试类。我们暂且将生成的类名写成TestProxy,代理的接口就是我们上面的TestInterface。如下。
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

import sun.misc.ProxyGenerator;



public class CreateClassTest {

    public static void main(String[] args) throws IOException {
        byte[] classFile = ProxyGenerator.generateProxyClass("TestProxy", new Class[]{TestInterface.class});
        File file = new File("F:/TestProxy.class");
        FileOutputStream fos = new FileOutputStream(file);
        fos.write(classFile);
        fos.flush();
        fos.close();
    }
    
}
生成后,我们反编译过来会是如下格式的JAVA文件。我加入了注释,大致说明了下文件中生成的部分与刚才分析的时候写入的过程的对应关系。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

//public final的,继承Proxy,实现你传入的接口
public final class TestProxy extends Proxy
  implements TestInterface
{
  //private static 的Method属性,对应所有方法
  private static Method m1;
  private static Method m5;
  private static Method m3;
  private static Method m4;
  private static Method m0;
  private static Method m2;
  //唯一的构造方法,需要一个InvocationHandler接口传入
  public TestProxy(InvocationHandler paramInvocationHandler)
    throws 
  {
    super(paramInvocationHandler);
  }
  //重写Object的三个方法
  public final boolean equals(Object paramObject)
    throws 
  {
    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);
    }
  }

  public final void method3()
    throws 
  {
    try
    {
      this.h.invoke(this, m5, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  //代理的三个方法,回调传入的InvocationHandler的invoke方法
  public final void method1()
    throws 
  {
    try
    {
      this.h.invoke(this, m3, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final void method2()
    throws 
  {
    try
    {
      this.h.invoke(this, m4, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final int hashCode()
    throws 
  {
    try
    {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final String toString()
    throws 
  {
    try
    {
      return (String)this.h.invoke(this, m2, null);
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  //这个就是刚才this.methods.add(generateStaticInitializer());这一句话所加入的静态初始化块,初始化每一个属性
  static
  {
    try
    {//每一个属性所代表的Method都是与上面加入代理方法列表时与固定类绑定的,这是class文件中的格式,方法要与固定的类绑定
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m5 = Class.forName("TestInterface").getMethod("method3", new Class[0]);
      m3 = Class.forName("TestInterface").getMethod("method1", new Class[0]);
      m4 = Class.forName("TestInterface").getMethod("method2", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
}
看到这里就知道invoke方法是干嘛的了,其实就是 生成的代理类对每一个方法的处理就是回调invoke方法 。从生成的代理类源文件中也可以发现,每一个Method除了hashCode,toString和equals外,都是与所属的接口绑定的,所以这也就解释了为什么我们不实现这个接口,只传入进入的话,不能直接使用method.invoke,而是要转成source对应的method才可以调用。
 
以上源码分析,说实话我只看懂大概,先转存,以后慢慢看     
     
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值