01 log4j入门及xml配置详解

本篇文章中涉及到的所有代码都已经上传到gitee中: gitee.com/sss123a/log...

Hello world!

引入maven依赖:

xml 复制代码
<dependency>  
    <groupId>log4j</groupId>  
    <artifactId>log4j</artifactId>  
    <version>1.2.17</version>  
</dependency>

第一个log4j程序:

java 复制代码
import org.apache.log4j.Logger;  
  
public class HelloWorld {  
    public static void main(String[] args) {  
        Logger logger = Logger.getLogger(HelloWorld.class);  
        logger.info("Hello world!");  
    }  
}

输出结果如下: 这里红色提示其实是log4j内部使用LogLog工具类打印的,我们可以调用LogLog.setQuietMode(true); 来禁用log4j内部一切日志输出。具体可以参考LogLog类中的静态代码块支持更强大的功能配置。

那至于为什么log4j要输出这样的提示信息,那是因为还缺少对Logger的进一步配置。 比如我们可以调用BasicConfigurator.configure(); 。其本质是为该logger对象增加了一个ConsoleAppender,它负责将日志输出到console。原理就等同于jul中的java.util.logging.ConsoleHandler类。具体代入如下:

java 复制代码
package com.matio.log4j.helloworld;  
  
import org.apache.log4j.BasicConfigurator;  
import org.apache.log4j.Logger;  
  
public class HelloWorld {  
    public static void main(String[] args) {  
        BasicConfigurator.configure();  
        Logger logger = Logger.getLogger(HelloWorld.class);  
        logger.info("Hello world!");  
    }  
}

另外,log4j默认支持xml格式和properties格式的配置文件,对应log4j中的两个工具类:

  • DOMConfigurator:解析xml文件
  • PropertyConfigurator 解析properties文件

接下来我们以xml配置文件为例来配置我们的log4j:

1.如果xml文件在在resources目录下,且文件名为log4j.xml,那么log4j可以自动读取该文件,具体可以参考org.apache.log4j.LogManager的静态代码块

2.如果xml文件在resources目录下:

java 复制代码
 DOMConfigurator.configure(Loader.getResource("log4j-asyncappender.xml"))

3.如果xml文件在磁盘上任意位置:

java 复制代码
DOMConfigurator.configure("E:/WorkspaceIdea/01src/log/matio-log4j/src/main/resources/log4j-asyncappender.xml");

4.也可以把xml文件放到一个指定的位置,并且使用环境变量log4j.configuration 来完成配置文件的指定。注意,在log4j.configuration的值中,可以使用文件名称或者url的方式。比如:

log4j.configuration=log4jconfig.properties (resources目录下) log4j.configuration=file:/c:/log4jconfig.xml

log4j.xml配置文件解析

java 复制代码
package org.apache.log4j.spi;  
  
import java.io.InputStream;  
import java.net.URL;  

public interface Configurator {  

    void doConfigure(InputStream inputStream, LoggerRepository repository);  

    void doConfigure(URL url, LoggerRepository repository);  
}

该接口主要有两个核心实现类

  • DOMConfigurator:解析xml文件
  • PropertyConfigurator 解析properties文件

两者实现方式大同小异,我们以xml文件为例解剖log4j内部实现:

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">  
<log4j:configuration  
debug="${matio.log4j.debug}"  
reset="${matio.log4j.reset}"  
threshold="${matio.log4j.threshold}"  
xmlns:log4j='http://jakarta.apache.org/log4j/'>  
  
    <!-- debug:同configDebug标签: LogLog.setInternalDebugging(boolean)-->  
    <!-- reset:LoggerRepository.resetConfiguration()-->  
    <!-- threshold:设置LoggerRepository#setThreshold(String) -->  

    <!-- loggerFactory:同categoryFactory标签,可以同时存在多个,但是最后一个才生效 -->  
    <loggerFactory class="${matio.log4j.loggerFactory.class}">  
        <!-- param属性会通过反射注入到class对象中 -->  
        <param name="p1" value="v1"/>  
        <param name="p2" value="v2"/>  
        <!-- class类也可以实现UnrecognizedElementHandler接口然后自己去解析自定义的属性 -->  
        <age>30</age>  
        <name value="matio"/>  
    </loggerFactory>  

    <!-- appender: name和class属性必须-->  
    <appender name="myConsole" class="org.apache.log4j.ConsoleAppender">  
        <layout class="org.apache.log4j.PatternLayout">  
            <param name="ConversionPattern" value="[%d{dd HH:mm:ss,SSS\} %-5p] [%t] %c{2\} - %m%n"/>  
        </layout>  

        <!--过滤器设置输出的级别-->  
        <filter class="org.apache.log4j.varia.LevelRangeFilter">  
            <param name="levelMin" value="debug"/>  
            <param name="levelMax" value="warn"/>  
            <param name="acceptOnMatch" value="true"/>  
        </filter>  
    </appender>  

    <appender name="myFile" class="org.apache.log4j.RollingFileAppender">  
        <param name="File" value="D:/output.log"/><!-- 设置日志输出文件名 -->  
        <!-- 设置是否在重新启动服务时,在原有日志的基础添加新日志 -->  
        <param name="Append" value="true"/>  
        <param name="MaxBackupIndex" value="10"/>  
        <layout class="org.apache.log4j.PatternLayout">  
            <param name="ConversionPattern" value="%p (%c:%L)- %m%n"/>  
        </layout>  
    </appender>  

    <appender name="activexAppender" class="org.apache.log4j.DailyRollingFileAppender">  
        <param name="File" value="E:/activex.log"/>  
        <param name="DatePattern" value="'.'yyyy-MM-dd'.log'"/>  
        <layout class="org.apache.log4j.PatternLayout">  
            <param name="ConversionPattern" value="[%d{MMdd HH:mm:ss SSS\} %-5p] [%t] %c{3\} - %m%n"/>  
        </layout>  
    </appender>  

    <!-- 指定logger的设置,additivity指示是否遵循缺省的继承机制-->  
    <!-- logger:同category标签-->  
    <logger name="com.runway.bssp.activeXdemo" additivity="false">  
        <!-- level:同priority, -->  
        <level value="info"/>  
        <!-- appender-ref:可以存在多个,开始解析目标appender了, -->  
        <appender-ref ref="activexAppender"/>  
        <appender-ref ref="myFile"/>  
    </logger>  

    <logger name="com.matio.test1" class="com.matio.log4j.logger.LoggerUtil" additivity="false">  
        <priority value="info" class="com.matio.log4j.level.CusLevel"/>  
    </logger>  

    <!-- renderer:可以存在多个 -->  
    <!-- 如果打印的日志message类型是User,就调用CusObjectRenderer.doRender()二次处理生成新的message -->  
    <renderer renderingClass="com.matio.log4j.objectrenderer.CusObjectRenderer"  
    renderedClass="com.matio.log4j.User"/>  
    <renderer renderingClass="com.matio.log4j.objectrenderer.CusObjectRenderer2"  
    renderedClass="com.matio.log4j.Test1"/>  

    <!-- throwableRenderer:class类需要实现ThrowableRenderer接口 -->  
    <throwableRenderer class="com.matio.log4j.throwablerenderer.CusThrowableRenderer">  
        <!-- param属性会通过反射注入到当前class对象中 -->  
        <param name="p100" value="v100"/>  
        <!-- class类也可以实现UnrecognizedElementHandler接口然后自己去解析自定义的属性 -->  
        <lover>zcf</lover>  
    </throwableRenderer>  

    <!-- 根logger的设置-->  
    <root>  
        <priority value="debug"/>  
        <appender-ref ref="myConsole"/>  
        <appender-ref ref="myFile"/>  
    </root>
  
</log4j:configuration>

根节点:log4j:configuration

DOMConfigurator#parse(Element)

用法如下:

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">  
<log4j:configuration  
    debug="${matio.log4j.debug}"  
    reset="${matio.log4j.reset}"  
    threshold="${matio.log4j.threshold}"  
    xmlns:log4j='http://jakarta.apache.org/log4j/'>
    
</log4j:configuration>    

重点: 可以通过${}从系统属性中获取,也可以固定写死

类型 描述 默认值
log4j:configuration 根标签 同configuration
debug 属性 是否打印log4j内部debug日志,同configDebug,LogLog.setInternalDebugging(boolean); false
reset 属性 如果为true则调用LoggerRepository.resetConfiguration();
threshold 属性 默认的日志level,LoggerRepository.setThreshold(); Level.ALL

loggerFactory同categoryFactory:自定义LoggerFactory

如果配置了该标签,那么该xml文件中所有logger都会通过该loggerFactory去生成,不走默认的了

DOMConfigurator#parseCategoryFactory(Element)

可以同时存在多个,但是最后一个才生效

xml 复制代码
<!-- loggerFactory:同categoryFactory标签,可以同时存在多个,但是最后一个才生效 -->  
<loggerFactory class="${matio.log4j.loggerFactory.class}">  
    <!-- param属性会通过反射注入到class对象中 -->  
    <param name="p1" value="v1"/>  
    <param name="p2" value="v2"/>  
    <!-- class类也可以实现UnrecognizedElementHandler接口然后自己去解析自定义的属性 -->  
    <age>30</age>  
    <name value="matio"/>  
</loggerFactory>
类型 描述
loggerFactory 标签 用户可以自定义LoggerFactory,同categoryFactory标签
class 属性 LoggerFactory具体实现类,如果没有设置class属性,则直接跳过该标签
param 标签 通过反射将value设置给class类中的属性
param.name 属性 class类中的属性名称
param.value 属性 class类中的属性值

class属性:可以通过${}从系统属性中获取,比如

System.setProperty("matio.log4j.loggerFactory.class", "com.matio.log4j.loggerfactory.CusLoggerFactory");

也可以固定写死,比如class="com.matio.log4j.loggerfactory.CusLoggerFactory"

备注:如果class实现类实现了UnrecognizedElementHandler接口,那么我们还可以解析自定义xml标签,示例代码:

java 复制代码
package com.matio.log4j.loggerfactory;  
  
import org.apache.log4j.Logger;  
import org.apache.log4j.spi.LoggerFactory;  
import org.apache.log4j.xml.DOMConfigurator;  
import org.apache.log4j.xml.UnrecognizedElementHandler;  
import org.w3c.dom.Element;  
  
import java.util.Properties;  
  
public class CusLoggerFactory implements LoggerFactory, UnrecognizedElementHandler {  
  
    private String p1; // <param name="p1" value="v1"/>  
    private String p2; // <param name="p2" value="v2"/>  
    private int age;  
    private String name;  

    public Logger makeNewLoggerInstance(String name) {  
        return Logger.getLogger(name);  
    }  

    public boolean parseUnrecognizedElement(Element element, Properties props) throws Exception {  
        String nodeName = element.getNodeName();  
        if ("age".equals(nodeName)) { // 解析:<age>30</age>  
            String ageStr = DOMConfigurator.subst(element.getNodeValue(), props);  
            if (ageStr != null) {  
                age = Integer.parseInt(ageStr);  
            }  
        } else if ("name".equals(nodeName)) { // 解析:<name value="matio"/>  
            name = DOMConfigurator.subst(element.getAttribute("value"), props);  
        }  
        return true;  
    }  
    // 忽略getter()和setter()...
}

logger同category

定义一个logger对象

DOMConfigurator#parseCategory(Element)

可以同时存在多个logger标签

xml 复制代码
<logger name="com.runway.bssp.activeXdemo" additivity="false">  
    <!-- level:同priority, -->  
    <level value="info"/>  
    <!-- appender-ref:可以存在多个,开始解析目标appender了, -->  
    <appender-ref ref="activexAppender"/>  
    <appender-ref ref="myFile"/>  
</logger>  
  
<logger name="com.matio.test1" class="com.matio.log4j.logger.LoggerUtil" additivity="false">  
    <priority value="info" class="com.matio.log4j.level.CusLevel"/>  
</logger>
类型 描述
logger 标签 创建一个logger对象,同category标签
class 属性 通过该类名反射调用其内部的getLogger()方法去生成logger对象
name 属性 该logger的name
additivity 属性 是否遵循缺省的继承机制,默认true
appender-ref 标签 appender的name,这个时候才开始去解析目标appender标签
level 标签 该logger的日志level,可以通过class属性自定义level
priority 标签 同level
param 标签 通过反射将value设置给class类中的属性
param.name 属性 class类中的属性名称
param.value 属性 class类中的属性值

在每个logger标签被解析完成后,如果发现该logger类实现了org.apache.log4j.spi.OptionHandler接口,就会回调其activateOptions()方法

root

只能存在一个root标签

xml 复制代码
<!-- 根logger的设置-->  
<root>  
    <priority value="debug"/>  
    <appender-ref ref="myConsole"/>  
    <appender-ref ref="myFile"/>  
</root>
类型 描述
appender-ref 标签 appender的name,这个时候才开始去解析目标appender标签
level 标签 该logger的日志level,可以通过class属性自定义level
priority 标签 同level
param 标签 通过反射将value设置给class类中的属性
param.name 属性 class类中的属性名称
param.value 属性 class类中的属性值

在root标签被解析完成后,如果发现该logger类实现了org.apache.log4j.spi.OptionHandler接口,就会回调其activateOptions()方法

renderer

不知道大家有没有注意到logger.info();方法接收的参数类型是什么.其实都是Object类型(jul接受的都是string类型),这说明log4j可以打印任意类型的日志,比如:

java 复制代码
BasicConfigurator.configure(); // 日志输出到console上
Logger logger = Logger.getLogger("test");
logger.info(new User("best", "zcf", "matio"));  
logger.info(new Test1("aaaa"));

以上两行代码打印结果如下:

js 复制代码
0 [main] INFO test  - User{school='best', like='zcf', name='matio'}
1 [main] INFO test  - Test1{x='aaaa'}

我们都知道输出一个对象其实就是输出其string()方法,但是log4j提供了一个工具来用户根据指定的日志对象类型输出对应的日志,这个工具就是org.apache.log4j.or。ObjectRenderer,其接口定义是如下:

java 复制代码
public interface ObjectRenderer {  
 String doRender(Object o);  
}

参数o就是我们上面的User对象和Test1对象,返回值就是最终要打印的日志结果

这里为了更好的理解什么是ObjectRenderer,附带了一个DEMO,它不需要xml配置文件,示例如下:

java 复制代码
package com.matio.log4j.objectrenderer;  
  
import com.matio.log4j.Test1;  
import com.matio.log4j.User;  
import org.apache.log4j.BasicConfigurator;  
import org.apache.log4j.LogManager;  
import org.apache.log4j.Logger;  
import org.apache.log4j.or.RendererMap;  
import org.apache.log4j.spi.RendererSupport;  
  
public class ObjectRendererTest {  
    public static void main(String[] args) {  
        // 注册一个consoleAppender,等会日志打印在控制台上  
        BasicConfigurator.configure();  

        // 注册一个ObjectRenderer专门用于处理User类型的消息  
        {  
            // message的class  
            String renderedClassName = "com.matio.log4j.User";  
            // ObjectRenderer实现类  
            String renderingClassName = "com.matio.log4j.objectrenderer.CusObjectRenderer";  
            RendererMap.addRenderer((RendererSupport) LogManager.getLoggerRepository(), renderedClassName, renderingClassName);  
        }  

        // 再注册一个ObjectRenderer专门用于处理Test1类型的消息  
        {  
            // message的class  
            String renderedClassName = "com.matio.log4j.Test1";  
            // ObjectRenderer实现类  
            String renderingClassName = "com.matio.log4j.objectrenderer.CusObjectRenderer2";  
            RendererMap.addRenderer((RendererSupport) LogManager.getLoggerRepository(), renderedClassName, renderingClassName);  
        }  

        Logger logger = Logger.getLogger("test");  
        logger.info(new User("best", "zcf", "matio"));  
        logger.info(new Test1("aaaa"));  
    }  
}

自定义ObjectRenderer,用于处理User类型和Test1类型的消息:

java 复制代码
package com.matio.log4j.objectrenderer;  
  
import com.matio.log4j.User;  
import org.apache.log4j.or.ObjectRenderer;  
import org.apache.log4j.xml.UnrecognizedElementHandler;  
import org.w3c.dom.Element;  
import java.util.Properties;  
  
public class CusObjectRenderer implements ObjectRenderer, UnrecognizedElementHandler {  
  
    public String doRender(Object o) {  
        if (o instanceof User) {  
            User user = (User) o;  
            return "测试ObjectRenderer : " + user.toString();  
        }  
        return o.toString();  
    }  

    // 这个只在xml配置生效  
    public boolean parseUnrecognizedElement(Element element, Properties props) throws Exception {  
        return true;  
    }  
}

public class CusObjectRenderer2 implements ObjectRenderer {  
  
    public String doRender(Object o) {  
        Test1 test1 = (Test1) o;  
        return "测试ObjectRenderer222 : " + test1.toString();  
    }  
}

其打印结果如下:

js 复制代码
0 [main] INFO test  - 测试ObjectRenderer : User{school='best', like='zcf', name='matio'}
0 [main] INFO test  - 测试ObjectRenderer222 : Test1{x='aaaa'}

那么在xml文件中我们该如何配置renderer呢,具体可以参考:

DOMConfigurator#parseRenderer(Element);

renderer在xml中的配置如下,支持多个:

xml 复制代码
<!-- renderer:可以存在多个 -->  
<!-- 如果打印的日志message类型是User,就调用CusObjectRenderer.doRender()二次处理生成新的message -->  
<renderer renderingClass="com.matio.log4j.objectrenderer.CusObjectRenderer"  
renderedClass="com.matio.log4j.User"/>

<renderer renderingClass="com.matio.log4j.objectrenderer.CusObjectRenderer2"  
renderedClass="com.matio.log4j.Test1"/>
类型 描述
renderer 标签 真的指定类型的日志,就调用CusObjectRenderer.doRender()二次处理生成新的日志
renderingClass 属性 ObjectRenderer的实现类
renderedClass 属性 日志类型

throwableRenderer

什么是throwableRenderer?

它可以将Throwable转成String,对应log4j中的接口定义如下:

java 复制代码
package org.apache.log4j.spi;  

public interface ThrowableRenderer {  
    public String[] doRender(Throwable t);  
}

log4j默认使用DefaultThrowableRenderer

没有注册自定义ThrowableRenderer之前,实例代码如下:

java 复制代码
package com.matio.log4j.throwablerenderer;  
  
import org.apache.log4j.BasicConfigurator;  
import org.apache.log4j.LogManager;  
import org.apache.log4j.Logger;  
import org.apache.log4j.spi.ThrowableRendererSupport;  
  
public class TestThrowableRenderer {  
  
    public static void main(String[] args) {  
        BasicConfigurator.configure(); // 日志输出到console上  
        Logger logger = Logger.getLogger("Te");  
        logger.error("错误xxx:", new IllegalArgumentException());  
        logger.error("错误:");  
    }  
}

输出结果如下:

js 复制代码
1 [main] ERROR Te  - 错误xxx:
java.lang.IllegalArgumentException
	at com.matio.log4j.throwablerenderer.TestThrowableRenderer.main(TestThrowableRenderer.java:17)
3 [main] ERROR Te  - 错误:

当注册一个自定义的ThrowableRenderer后代码如下:

java 复制代码
package com.matio.log4j.throwablerenderer;  
  
import org.apache.log4j.BasicConfigurator;  
import org.apache.log4j.LogManager;  
import org.apache.log4j.Logger;  
import org.apache.log4j.spi.ThrowableRendererSupport;  
  
public class TestThrowableRenderer {  
  
    public static void main(String[] args) {  
        BasicConfigurator.configure(); // 日志输出到console上  

        // 注册一个自定义的ThrowableRenderer  
        {  
            CusThrowableRenderer renderer = new CusThrowableRenderer();  
            ((ThrowableRendererSupport) LogManager.getLoggerRepository()).setThrowableRenderer(renderer);  
        }  
        Logger logger = Logger.getLogger("Te");  
        logger.error("错误xxx:", new IllegalArgumentException());  
        logger.error("错误:");  
    }  
}
java 复制代码
package com.matio.log4j.throwablerenderer;  
  
import org.apache.log4j.spi.OptionHandler;  
import org.apache.log4j.spi.ThrowableRenderer;  
import org.apache.log4j.xml.DOMConfigurator;  
import org.apache.log4j.xml.UnrecognizedElementHandler;  
import org.w3c.dom.Element;  
  
import java.io.PrintWriter;  
import java.io.StringWriter;  
import java.util.Properties;  
  
public class CusThrowableRenderer implements ThrowableRenderer, OptionHandler, UnrecognizedElementHandler {  
  
    private String p100;    
    private String lover;  
  
    public String[] doRender(Throwable t) {  
        StringWriter stringWriter = new StringWriter();  
        PrintWriter printWriter = new PrintWriter(stringWriter);  
        t.printStackTrace(printWriter);  
        printWriter.close();  
        String[] strings = new String[1];  
        strings[0] = "自定义ThrowableRenderer: " + stringWriter.toString();  
        return strings;  
    }  

    public void activateOptions() {  

    }  

    // 这里只在xml配置中生效  
    public boolean parseUnrecognizedElement(Element element, Properties props) throws Exception {  
        this.lover = DOMConfigurator.subst(element.getNodeValue(), props);  
        return true;  
    }  
  
    // 省略getter()和setter()...  
}

输出结果如下:

js 复制代码
0 [main] ERROR Te  - 错误xxx:
自定义ThrowableRenderer: java.lang.IllegalArgumentException
	at com.matio.log4j.throwablerenderer.TestThrowableRenderer.main(TestThrowableRenderer.java:19)

2 [main] ERROR Te  - 错误:

那么在xml中如何声明throwableRenderer呢,具体参考

DOMConfigurator#parseThrowableRenderer(Element);

xml 复制代码
<!-- throwableRenderer:class类需要实现ThrowableRenderer接口 -->  
<throwableRenderer class="com.matio.log4j.throwablerenderer.CusThrowableRenderer">  
    <!-- param属性会通过反射注入到当前class对象中 -->  
    <param name="p100" value="v100"/>  
    <!-- class类也可以实现UnrecognizedElementHandler接口然后自己去解析自定义的属性 -->  
    <lover>zcf</lover>  
</throwableRenderer>
类型 描述
throwableRenderer 标签 自定义ThrowableRenderer
class 属性 ThrowableRendere的实现类
param 标签 通过反射将value设置给class类中的属性
param.name 属性 class类中的属性名称
param.value 属性 class类中的属性值

在throwableRenderer标签被解析完成后,如果发现该throwableRenderer类实现了org.apache.log4j.spi.OptionHandler接口,就会回调其activateOptions()方法

appender

每一个handler标签都会被实例化成一个Appender对象(它其实就等同于jul中的Handler),负责将日志输出到console上、保存到文件中,保存到数据库中、或者通过socket发送给远端等

DOMConfigurator#parseAppender(Element);

xml 复制代码
<!-- appender: name和class属性必须-->  
<appender name="myConsole" class="org.apache.log4j.ConsoleAppender">  
    <layout class="org.apache.log4j.PatternLayout">  
        <param name="ConversionPattern" value="[%d{dd HH:mm:ss,SSS\} %-5p] [%t] %c{2\} - %m%n"/>  
    </layout>  

    <!--过滤器设置输出的级别-->  
    <filter class="org.apache.log4j.varia.LevelRangeFilter">  
        <param name="levelMin" value="debug"/>  
        <param name="levelMax" value="warn"/>  
        <param name="acceptOnMatch" value="true"/>  
    </filter>  
    
    <!-- class类也可以实现UnrecognizedElementHandler接口然后自己去解析自定义的属性 --> 
    <age>30</age> 
    <name value="matio"/>
</appender>
类型 描述
appender 标签 对应jul中的handler对象
class 属性 Appender实现类
name 属性 该appender的name
param 标签 通过反射将value设置给class类中的属性
param.name 属性 class类中的属性名称
param.value 属性 class类中的属性值
layout 标签 定义该appender的日志输出样式,见parseLayout()
filter 标签 定义该appender的日志过滤器,见parseFilters()
errorHandler 标签 输出日志时如果出现异常就交给该handler处理,见parseErrorHandler()
appender-ref 标签 只有该appender实现了AppenderAttachable接口才有效,就是代表真正的appender,可以有多个

在每个appender标签被解析完成后,如果发现该appender类实现了org.apache.log4j.spi.OptionHandler接口,就会回调其activateOptions()方法

最后再添加到对应的logger中

相关推荐
说书客啊9 分钟前
计算机毕业设计 | SpringBoot+vue线上家具商城 家居商品购买系统(附源码+论文)
java·spring boot·node.js·vue·毕业设计·智能家居·课程设计
小阿龙...10 分钟前
创建mapreduce项目使用maven
java·ide·hadoop·spark·big data
疯一样的码农14 分钟前
使用命令行创建 Maven 项目
java·maven
飞滕人生TYF21 分钟前
位运算实现加法 的过程中 保证最终进位为 0 详解
java·位运算
情勤坊34 分钟前
JAVA实现将PDF转换成word文档
java·pdf·word
苹果酱056735 分钟前
springcloud-网关路由gateway
java·开发语言·spring boot·mysql·中间件
武子康41 分钟前
Java-08 深入浅出 MyBatis - 多对多模型 SqlMapConfig 与 Mapper 详细讲解测试
java·开发语言·数据库·sql·mybatis
摇滚侠1 小时前
java http body的格式 ‌application/x-www-form-urlencoded‌不支持文件上传
java·开发语言·http
尘浮生1 小时前
Java项目实战II基于SpringBoot的共享单车管理系统开发文档+数据库+源码)
java·开发语言·数据库·spring boot·后端·微信小程序·小程序
huaxiaorong1 小时前
如何将旧的Android手机改造为家用服务器
后端