Java路径问题最终解决方案




                                             Java路径问题最终解决方案
                                                                                                            —可定位所有资源的相对路径寻址
 
转自:  http://blog.csdn.net/shendl/article/details/1427637
 
前言
Java的路径问题,非常难搞。最近的工作涉及到创建和读取文件的工作,这里我就给大家彻底得解决Java路径问题。
我编写了一个方法,比ClassLoader.getResource(String 相对路径)方法的能力更强。它可以接受“../”这样的参数,允许我们用相对路径来定位classpath外面的资源。这样,我们就可以使用相对于classpath的路径,定位所有位置的资源!
 
Java路径
Java中使用的路径,分为两种:绝对路径和相对路径。具体而言,又分为四种:
一、URI形式的绝对资源路径
如:file:/D:/java/eclipse32/workspace/jbpmtest3/bin/aaa.b
URL是URI的特例。URL的前缀/协议,必须是Java认识的。URL可以打开资源,而URI则不行。
URL和URI对象可以互相转换,使用各自的toURI(),toURL()方法即可!
 
二、本地系统的绝对路径
D:/java/eclipse32/workspace/jbpmtest3/bin/aaa.b
Java.io包中的类,需要使用这种形式的参数。
但是,它们一般也提供了URI类型的参数,而URI类型的参数,接受的是URI样式的String。因此,通过URI转换,还是可以把URI样式的绝对路径用在java.io包中的类中。
 
三、相对于classpath的相对路径
如:相对于
file:/D:/java/eclipse32/workspace/jbpmtest3/bin/这个路径的相对路径。其中,bin是本项目的classpath。所有的Java源文件编译后的.class文件复制到这个目录中。
 
 
四、相对于当前用户目录的相对路径
就是相对于System.getProperty("user.dir")返回的路径。
对于一般项目,这是项目的根路径。对于JavaEE服务器,这可能是服务器的某个路径。这个并没有统一的规范!
所以,绝对不要使用“相对于当前用户目录的相对路径”。然而:
默认情况下,java.io 包中的类总是根据当前用户目录来分析相对路径名。此目录由系统属性 user.dir 指定,通常是 Java 虚拟机的调用目录。
这就是说,在使用java.io包中的类时,最好不要使用相对路径。否则,虽然在J2SE应用程序中可能还算正常,但是到了J2EE程序中,一定会出问题!而且这个路径,在不同的服务器中都是不同的!
 
相对路径最佳实践
推荐使用相对于当前classpath的相对路径
因此,我们在使用相对路径时,应当使用相对于当前classpath的相对路径。
ClassLoader类的getResource(String name), getResourceAsStream(String name)等方法,使用相对于当前项目的classpath的相对路径来查找资源。
读取属性文件常用到的ResourceBundle类的getBundle(String path)也是如此。
通过查看ClassLoader类及其相关类的源代码,我发现,它实际上还是使用了URI形式的绝对路径。通过得到当前classpath的URI形式的绝对路径,构建了相对路径的URI形式的绝对路径。(这个实际上是猜想,因为JDK内部调用了SUN的源代码,而这些代码不属于JDK,不是开源的。)
 
相对路径本质上还是绝对路径
因此,归根结底,Java本质上只能使用绝对路径来寻找资源。所有的相对路径寻找资源的方法,都不过是一些便利方法。不过是API在底层帮助我们构建了绝对路径,从而找到资源的!
 
得到classpath和当前类的绝对路径的一些方法
    下面是一些得到classpath和当前类的绝对路径的一些方法。你可能需要使用其中的一些方法来得到你需要的资源的绝对路径。
1,FileTest.class.getResource("")
得到的是当前类FileTest.class文件的URI目录。不包括自己!
如: file:/D:/java/eclipse32/workspace/jbpmtest3/bin/com/test/
2,FileTest.class.getResource("/")
得到的是当前的classpath的绝对URI路径。
如: file:/D:/java/eclipse32/workspace/jbpmtest3/bin/
3,Thread.currentThread().getContextClassLoader().getResource("")
得到的也是当前ClassPath的绝对URI路径。
如: file:/D:/java/eclipse32/workspace/jbpmtest3/bin/
4,FileTest.class.getClassLoader().getResource("")
得到的也是当前ClassPath的绝对URI路径。
如: file:/D:/java/eclipse32/workspace/jbpmtest3/bin/
5,ClassLoader.getSystemResource("")
得到的也是当前ClassPath的绝对URI路径。
如: file:/D:/java/eclipse32/workspace/jbpmtest3/bin/
   
我推荐使用Thread.currentThread().getContextClassLoader().getResource("")来得到当前的classpath的绝对路径的URI表示法。
 
Web应用程序中资源的寻址
    上文中说过,当前用户目录,即相对于System.getProperty("user.dir")返回的路径。
对于JavaEE服务器,这可能是服务器的某个路径,这个并没有统一的规范!
而不是我们发布的Web应用程序的根目录!
这样,在Web应用程序中,我们绝对不能使用相对于当前用户目录的相对路径。
在Web应用程序中,我们一般通过ServletContext.getRealPath("/")方法得到Web应用程序的根目录的绝对路径。
这样,我们只需要提供相对于Web应用程序根目录的路径,就可以构建出定位资源的绝对路径。
这是我们开发Web应用程序时一般所采取的策略。
 
通用的相对路径解决办法
Java中各种相对路径非常多,不容易使用,非常容易出错。因此,我编写了一个便利方法,帮助更容易的解决相对路径问题。
 
Web应用程序中使用JavaSE运行的资源寻址问题
在JavaSE程序中,我们一般使用classpath来作为存放资源的目的地。但是,在Web应用程序中,我们一般使用classpath外面的WEB-INF及其子目录作为资源文件的存放地。
在Web应用程序中,我们一般通过ServletContext.getRealPath("/")方法得到Web应用程序的根目录的绝对路径。这样,我们只需要提供相对于Web应用程序根目录的路径,就可以构建出定位资源的绝对路径。
Web应用程序,可以作为Web应用程序进行发布和运行。但是,我们也常常会以JavaSE的方式来运行Web应用程序的某个类的main方法。或者,使用JUnit测试。这都需要使用JavaSE的方式来运行。
这样,我们就无法使用ServletContext.getRealPath("/")方法得到Web应用程序的根目录的绝对路径。
而JDK提供的ClassLoader类,
它的getResource(String name), getResourceAsStream(String name)等方法,使用相对于当前项目的classpath的相对路径来查找资源。
读取属性文件常用到的ResourceBundle类的getBundle(String path)也是如此。
它们都只能使用相对路径来读取classpath下的资源,无法定位到classpath外面的资源。
Classpath外配置文件读取问题
如,我们使用测试驱动开发的方法,开发Spring、Hibernate、iBatis等使用配置文件的Web应用程序,就会遇到问题。
尽管Spring自己提供了FileSystem(也就是相对于user,dir目录)来读取Web配置文件的方法,但是终究不是很方便。而且与Web程序中的代码使用方式不一致!
至于Hibernate,iBatis就更麻烦了!只有把配置文件移到classpath下,否则根本不可能使用测试驱动开发!
 
    这怎么办?
 
通用的相对路径解决办法
面对这个问题,我决定编写一个助手类 ClassLoaderUtil,提供一个便利方法[ public static URL getExtendResource(String relativePath)]。在Web应用程序等一切Java程序中,需要定位classpath外的资源时,都使用这个助手类的便利方法,而不使用Web应用程序特有的ServletContext.getRealPath("/")方法来定位资源。
 
利用classpath的绝对路径,定位所有资源
这个便利方法的实现原理,就是“利用classpath的绝对路径,定位所有资源”。
ClassLoader类的getResource("")方法能够得到当前classpath的绝对路径,这是所有Java程序都拥有的能力,具有最大的适应性!
而目前的JDK提供的ClassLoader类的getResource(String 相对路径)方法,只能接受一般的相对路径。这样,使用ClassLoader类的getResource(String 相对路径)方法就只能定位到classpath下的资源。
如果,它能够接受“../”这样的参数,允许我们用相对路径来定位classpath外面的资源,那么我们就可以定位位置的资源!
当然,我无法修改ClassLoader类的这个方法,于是,我编写了一个助手类ClassLoaderUtil类,提供了[ public static URL getExtendResource(String relativePath)]这个方法。它能够接受带有“../”符号的相对路径,实现了自由寻找资源的功能。
 
通过相对classpath路径实现自由寻找资源的助手类的源代码:
import  java.io.IOException;
import  java.io.InputStream;
import  java.net.MalformedURLException;
import  java.net.URL;
import  java.util.Properties;
 
import  org.apache.commons.logging.Log;
import  org.apache.commons.logging.LogFactory;
 
/**
  * @author 沈东良 shendl_s@hotmail.com
  * Nov 29, 2006   10:34:34 AM
  * 用来加载类,classpath下的资源文件,属性文件等。
  * getExtendResource(String relativePath) 方法,可以使用../符号来加载classpath外部的资源。
  */
publicclass  ClassLoaderUtil {
     privatestatic  Log  log =LogFactory.getLog(ClassLoaderUtil. class );
     /**
      * Thread.currentThread().getContextClassLoader().getResource("")
      */
   
     /**
      * 加载Java类。   使用全限定类名
      * @param className
      * @return
      */
     publicstatic  Class loadClass(String className) {
         try  {
           return  getClassLoader().loadClass(className);
        }  catch  (ClassNotFoundException e) {
           thrownew  RuntimeException( "class not found '" +className+ "'" , e);
        }
     }
      /**
        * 得到类加载器
        * @return
        */
      publicstatic  ClassLoader getClassLoader() {
     
         return  ClassLoaderUtil. class .getClassLoader();
     }
      /**
        * 提供相对于classpath的资源路径,返回文件的输入流
        * @param relativePath 必须传递资源的相对路径。是相对于classpath的路径。如果需要查找classpath外部的资源,需要使用../来查找
        * @return   文件输入流
      * @throws IOException
      * @throws MalformedURLException
        */
      publicstatic  InputStream getStream(String relativePath)  throws  MalformedURLException, IOException {
          if (!relativePath.contains( "../" )){
              return  getClassLoader().getResourceAsStream(relativePath);
             
         } else {
              return  ClassLoaderUtil.getStreamByExtendResource(relativePath);
         }
       
     }
      /**
        *
        * @param url
        * @return
        * @throws IOException
        */
      publicstatic  InputStream getStream(URL url)  throws  IOException{
          if (url!= null ){
             
                 return  url.openStream();
           
             
         } else {
              returnnull ;
         }
     }
      /**
        *
        * @param relativePath 必须传递资源的相对路径。是相对于classpath的路径。如果需要查找classpath外部的资源,需要使用../来查找
        * @return
        * @throws MalformedURLException
        * @throws IOException
        */
      publicstatic  InputStream getStreamByExtendResource(String relativePath)  throws  MalformedURLException, IOException{
         return  ClassLoaderUtil.getStream(ClassLoaderUtil.getExtendResource(relativePath));
         
         
     }
     
       /**
        * 提供相对于classpath的资源路径,返回属性对象,它是一个散列表
        * @param resource
        * @return
        */
      publicstatic  Properties getProperties(String resource) {
        Properties properties =  new  Properties();
         try  {
          properties.load(getStream(resource));
        }  catch  (IOException e) {
           thrownew  RuntimeException( "couldn't load properties file '" +resource+ "'" , e);
        }
         return  properties;
     }
      /**
        * 得到本Class所在的ClassLoader的Classpat的绝对路径。
        * URL 形式的
        * @return
        */
      publicstatic  String getAbsolutePathOfClassLoaderClassPath(){
         
         
         ClassLoaderUtil. log .info(ClassLoaderUtil.getClassLoader().getResource( "" ).toString());
          return  ClassLoaderUtil.getClassLoader().getResource( "" ).toString();
         
     }
      /**
        *
        * @param relativePath   必须传递资源的相对路径。是相对于classpath的路径。如果需要查找classpath外部的资源,需要使用../来查找
        * @return 资源的绝对URL
      * @throws MalformedURLException
        */
      publicstatic  URL getExtendResource(String relativePath)  throws  MalformedURLException{
     
         ClassLoaderUtil. log .info( " 传入的相对路径:" +relativePath) ;
          //ClassLoaderUtil.log.info(Integer.valueOf(relativePath.indexOf("../"))) ;
          if (!relativePath.contains( "../" )){
              return  ClassLoaderUtil.getResource(relativePath);
             
         }
         String classPathAbsolutePath=ClassLoaderUtil.getAbsolutePathOfClassLoaderClassPath();
          if (relativePath.substring(0, 1).equals( "/" )){
             relativePath=relativePath.substring(1);
         }
         ClassLoaderUtil. log .info(Integer.valueOf(relativePath.lastIndexOf( "../" ))) ;
       
         String wildcardString=relativePath.substring(0,relativePath.lastIndexOf( "../" )+3);
        relativePath=relativePath.substring(relativePath.lastIndexOf( "../" )+3);
          int  containSum=ClassLoaderUtil.containSum(wildcardString,  "../" );
         classPathAbsolutePath= ClassLoaderUtil.cutLastString(classPathAbsolutePath,  "/" , containSum);
         String resourceAbsolutePath=classPathAbsolutePath+relativePath;
         ClassLoaderUtil. log .info( " 绝对路径:" +resourceAbsolutePath) ;
         URL resourceAbsoluteURL= new  URL(resourceAbsolutePath);
          return  resourceAbsoluteURL;
     }
      /**
       *
        * @param source
        * @param dest
        * @return
        */
      privatestaticint  containSum(String source,String dest){
          int  containSum=0;
          int  destLength=dest.length();
          while (source.contains(dest)){
             containSum=containSum+1;
             source=source.substring(destLength);
             
         }
         
         
          return  containSum;
     }
      /**
        *
        * @param source
        * @param dest
        * @param num
        * @return
        */
      privatestatic  String cutLastString(String source,String dest, int  num){
          // String cutSource=null;
          for ( int  i=0;i<num;i++){
             source=source.substring(0, source.lastIndexOf(dest, source.length()-2)+1);
             
             
         }
         
         
         
          return  source;
     }
      /**
        *
        * @param resource
        * @return
        */
       publicstatic  URL getResource(String resource){
      ClassLoaderUtil. log .info( " 传入的相对于classpath的路径:" +resource) ;
          return  ClassLoaderUtil.getClassLoader().getResource(resource);
     }
    
 
     
 
     /**
      * @param args
      * @throws MalformedURLException
      */
     publicstaticvoid  main(String[] args)  throws  MalformedURLException {
       
             //ClassLoaderUtil.getExtendResource("../spring/dao.xml");
         //ClassLoaderUtil.getExtendResource("../../../src/log4j.properties");
        ClassLoaderUtil.getExtendResource( "log4j.properties" );
       
        System. out .println(ClassLoaderUtil.getClassLoader().getResource( "log4j.properties" ).toString());
 
    }
 
}
 
后记
ClassLoaderUtil类的public static URL getExtendResource(String relativePath),虽然很简单,但是确实可以解决大问题。
不过这个方法还是比较简陋的。我还想在未来有空时,进一步增强它的能力。比如,增加Ant风格的匹配符。用**代表多个目录,*代表多个字符,?代表一个字符。达到Spring那样的能力,一次返回多个资源的URL,进一步方便大家开发。
 
总结:
1,尽量不要使用相对于System.getProperty("user.dir")当前用户目录的相对路径。这是一颗定时炸弹,随时可能要你的命。
2,尽量使用URI形式的绝对路径资源。它可以很容易的转变为URI,URL,File对象。
3,尽量使用相对classpath的相对路径。不要使用绝对路径。使用上面ClassLoaderUtil类的public static URL getExtendResource(String relativePath)方法已经能够使用相对于classpath的相对路径定位所有位置的资源。
4,绝对不要使用硬编码的绝对路径。因为,我们完全可以使用ClassLoader类的getResource("")方法得到当前classpath的绝对路径。
使用硬编码的绝对路径是完全没有必要的!它一定会让你死的很难看!程序将无法移植!
如果你一定要指定一个绝对路径,那么使用配置文件,也比硬编码要好得多!
当然,我还是推荐你使用程序得到classpath的绝对路径来拼资源的绝对路径!





JavaEE程序有一大路径陷阱,那就是ServletContext的getRealPath方法。我们常常使用getRealPath(“/”)来获得Web应用程序根目录的绝对路径。这是绝对要不得的!提供这个方法绝对是JavaEE API开发组的一大败笔。使用它,我们会万劫不复!
绝对不要使用ServletContext的getRealPath方法获取Web应用的路径!应该使用ServletContext的getResource()方法,直接使用相对于Web应用根目录的相对路径来获取资源。
ServletContext接口中定位资源的方法
getResource
java.net.URL getResource(java.lang.String path)
                         throws java.net.MalformedURLException
Returns a URL to the resource that is mapped to a specified path. The path must begin with a "/" and is interpreted as relative to the current context root.
This method allows the servlet container to make a resource available to servlets from any source. Resources can be located on a local or remote file system, in a database, or in a .war file.
The servlet container must implement the URL handlers and URLConnection objects that are necessary to access the resource.
This method returns null if no resource is mapped to the pathname.
Some containers may allow writing to the URL returned by this method using the methods of the URL class.
The resource content is returned directly, so be aware that requesting a .jsp page returns the JSP source code. Use a RequestDispatcher instead to include results of an execution.
This method has a different purpose than java.lang.Class.getResource, which looks up resources based on a class loader. This method does not use class loaders.
Parameters:
path - a String specifying the path to the resource
Returns:
the resource located at the named path, or null if there is no resource at that path
Throws:
java.net.MalformedURLException - if the pathname is not given in the correct form

getResourceAsStream
java.io.InputStream getResourceAsStream(java.lang.String path)
Returns the resource located at the named path as an InputStream object.
The data in the InputStream can be of any type or length. The path must be specified according to the rules given in getResource. This method returns null if no resource exists at the specified path.
Meta-information such as content length and content type that is available via getResource method is lost when using this method.
The servlet container must implement the URL handlers and URLConnection objects necessary to access the resource.
This method is different from java.lang.Class.getResourceAsStream, which uses a class loader. This method allows servlet containers to make a resource available to a servlet from any location, without using a class loader.
Parameters:
path - a String specifying the path to the resource
Returns:
the InputStream returned to the servlet, or null if no resource exists at the specified path
getRealPath
java.lang.String getRealPath(java.lang.String path)
Returns a String containing the real path for a given virtual path. For example, the path "/index.html" returns the absolute file path on the server's filesystem would be served by a request for "http://host/contextPath/index.html", where contextPath is the context path of this ServletContext..
The real path returned will be in a form appropriate to the computer and operating system on which the servlet container is running, including the proper path separators. This method returns null if the servlet container cannot translate the virtual path to a real path for any reason (such as when the content is being made available from a .war archive).
Parameters:
path - a String specifying a virtual path
Returns:
a String specifying the real path, or null if the translation cannot be performed
说明
可以看到,ServletContext接口中的getResource()等方法,可以找到任何从应用程序的根目录开始的资源。包括在.war包这样的压缩文件中。参数必须以/开头。
而我们常用的getRealPath(“/”)方法,在.war包发布时,就会失效。会返回null。
因此,我们应该避免使用getRealPath(“/”)这样的方法来获取应用程序的绝对路径。
如果你不想使用我在《Java路径问题最终解决方案—可定位所有资源的相对路径寻址》中提出的助手类ClassLoaderUtilpublic static URL getExtendResource(String relativePath)方法,那么你应该使用ServletContext接口的
java.net.URL getResource(java.lang.String path)
                         throws java.net.MalformedURLException
方法,URL对象可以方便的转为URI,和String对象。
    尽管没有ServletContext的源码,但是我可以猜想到getResource等方法一定在底层使用了ClassLoader的getResource方法。


                                   Java路径问题最终解决方案使用演示
 
 
前言
本文中,我给大家提供了一个在JavaEE程序中使用这个便利方法寻找相对路径的代码实例。
在《 JavaEE路径陷阱之getRealPath》一文中,探讨了JavaEE程序中资源寻址的问题,有兴趣的读者可以看看那篇文章。
 
Java路径问题最终解决方案使用演示
示例背景
使用ClassLoaderUtil. getExtendResource()方法进行寻址的这个示例,是一个JavaEE程序,使用了SpringMVC框架进行前台开发。上传文件部分,使用了Apache的commons upload技术。
这个模块的功能是,向服务器上传一个JBoss的工作流引擎Jbpm的工作流定义文件。然后把它部署到服务器上。同时,把上传的工作流定义文件保存到服务器的    Web应用程序根目录/WEB-INF/jbpm/upload/目录下,以备查阅!
 
源代码:
import  java.io.File;
import  java.net.URI;
import  java.util.Date;
 
import  javax.servlet.http.HttpServletRequest;
import  javax.servlet.http.HttpServletResponse;
 
import  org.springframework.web.multipart.MultipartFile;
import  org.springframework.web.servlet.ModelAndView;
 
import  com.withub.common.base.BaseController;
import  com.withub.common.util.ClassLoaderUtil;
import  com.withub.common.util.IDeployProcessDefinition;
 
import  com.withub.wcms.UrlMap;
import  com.withub.wcms.manage.deployProcessDefinition.jbpm.bean.FileUploadBean;
 
/**
  * @author 沈东良 shendl_s@hotmail.com
  * Nov 27, 2006   1:31:25 PM
  * 这个类负责上传并部署 Jbpm 工作流定义文件
  * 并且把已上传的文件 copy Web 应用程序根目录 /WEB - INF/jbpm/upload/ 目录下,以备查阅!
  *
  */
publicclass  UploadAndDeployJbpmProcessDefinition  extends  BaseController {
     /**
      * Service, 部署本地上传的 xml 业务程序定义文件到服务器端的数据库!
      * Bean 是单例。   运行时,不 set 这个变量。只在初始化载入 Spring 容器时调用 set 方法。注意同步资源!
      */
     private  IDeployProcessDefinition  deployProcessDefinition ;
     /**
      * 这个方法,直接返回上传、部署工作流定义页面。这是为了用 .page 控制上传页面的访问权。
      * @param request
      * @param response
      * @return
      * @throws Exception
      */
     public  ModelAndView list(HttpServletRequest request,HttpServletResponse response)  throws  Exception{
      
        returnnew  ModelAndView(UrlMap.map( "manage.deployProcessDefinition.list" ));
    }
   
     /**
      *
      * @param request
      * @param response
      * @param command
      * @return
      * @throws Exception
      */
     public ModelAndView onSubmit(HttpServletRequest request,HttpServletResponse response,FileUploadBean command) throwsException {
 
         
 
           // let's see if there's content there
           MultipartFile file = command.getFile();
           if (file == null) {
                // hmm, that's strange, the user did not upload anything
            thrownew RuntimeException("上传文件出错!未能成功上传文件!");
            
           }else{
            //部署上传的文件
              this.getDeployProcessDefinition().deployProcessDefinitionTransaction(file.getInputStream());
            File destFile=null;
            /**
             *使用自定义的方法,实现了相对于classpath的相对路径寻址。
             */
            String uploadPath=ClassLoaderUtil.getExtendResource("../jbpm/upload/").toString();
            String uploadFile=uploadPath+String.valueOf(new Date().getTime())+"_"+file.getOriginalFilename();
            destFile=new File(new URI(uploadFile));
            file.transferTo(destFile);
            
           }
 
            // well, let's do nothing with the bean for now and return
           //return super.onSubmit(request, response, command, errors);
           returnnew ModelAndView(UrlMap.map("manage.deployProcessDefinition.success"));
       }
 
   
 
     /**
      * @param args
      */
     publicstaticvoid  main(String[] args) {
        /**
         *
         */
 
    }
 
 
 
     /**
      * @return the deployProcessDefinition
      */
     public  IDeployProcessDefinition getDeployProcessDefinition() {
        return deployProcessDefinition ;
    }
 
 
 
     /**
      * @param deployProcessDefinition the deployProcessDefinition to set
      */
     publicvoid  setDeployProcessDefinition(
           IDeployProcessDefinition deployProcessDefinition) {
        this . deployProcessDefinition  = deployProcessDefinition;
    }
 
}
 
 
后记
这里,我使用了自己实现的 ClassLoaderUtil.getExtendResource()方法,实现了 相对于classpath的相对路径寻址。
没有使用ServletContext接口提供的寻址方法。这样的代码,不依赖于JavaEE环境,依赖的是标准的JavaSE,可以用在任何Java程序中!
如果你要使用ServletContext接口提供的寻址方法,那么请一定不要使用getRealPath(“/”)方法,而应该使用getResource()方法或者getResourceAsStream()方法寻址。参数应该是“/”开头的相对路径,相对的是Web应用程序根目录的相对路径,而不是classpath的相对路径。具体原因,在《 JavaEE路径陷阱之getRealPath》一文中作了详细的解释。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值