- 使用 Spring Initializr 创建 Spring Boot 应用程序
- 在Spring Boot中配置Cassandra
- 在 Spring Boot 上配置 Tomcat 连接池
- 将Camel消息路由到嵌入WildFly的Artemis上
首先介绍了常见的日志框架的发展历程,然后介绍了log4j、jul、jcl、slf4j、log4j2等框架的用法,接着介绍了jcl、slf4j、log4j2无缝切换底层框架的原理,最后介绍了spring中的日志框架
听过太多的日志框架了,比如log4j
、jcl
、jcl
、slf4j
、logback
……
之前对这些概念就很乱,不知道它们都是具体干什么的,而且也不知道想要使用一个日志框架时,要怎么引入相关的依赖
本文就搜集了常见的日志框架,并梳理了其用法以及相关原理。
a. 从最早期开始,大家都是使用System.out
和System.err
来打印日志;不灵活也不可以配置;要么全部打印,要么全部不打印;没有一个统一的日志级别
b. 后来Log4j
就出现了,它是Ceki Gülcü
这个大佬开发的,后来Log4j
成为了Apache
基金会项目中的一员
c. 后来Java
也推出了自己的日志框架JUL(Java Util Logging)
,在package java.util.logging
下
d. Apache
又推出了日志接口Jakarta Commons Logging
,也就是日志抽象层,你就可以很方便的在Log4j
和JUL
之间做切换
e. Ceki Gülcü
觉得觉得JCL
不好,开发了一套新的日志门面Slf4j(Simple Logging Facade for Java)
、它的实现Logback
以及一些桥接包:
jcl-over-slf4j.jar :jcl ——> slf4j
slf4j-jcl.jar :slf4j ——> jcl
log4j-over-slf4j :log4j ——> slf4j
slf4j-log4j12.jar :slf4j ——> log4j
jul-to-slf4j :jul ——> slf4j
slf4j-jdk14.jar :slf4j ——> jul
f. 后来Apache
直接推出新项目,不是Log4j1.x
升级,而是新项目Log4j2
,因为Log4j2
是完全不兼容Log4j1.x
的,它也搞了分离的设计,分化成log4j-api
和log4j-core
,这个log4j-api
也是日志接口,log4j-core
是日志实现,它也出了很多桥接包:
log4j-jcl :jcl ——> log4j2
log4j-1.2-api :log4j ——> log4j2
log4j-slf4j-impl :slf4j ——> log4j2
log4j-jul :jul ——> log4j2
log4j-to-slf4j :log4j2 ——> slf4j
依赖:
<dependencies>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
配置文件:
log4j.properties
# Set root logger level to DEBUG and its only appender to A1.
log4j.rootLogger=DEBUG, A1
# A1 is set to be a ConsoleAppender.
log4j.appender.A1=org.apache.log4j.ConsoleAppender
# A1 uses PatternLayout.
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
测试类:
import org.apache.log4j.Logger;
public class Main {
public static void main(String[] args) {
Logger logger = Logger.getLogger(Main.class);
logger.info("hello, world!");
}
}
输出:
0 [main] INFO cn.eagleli.log.log4j.Main - hello, world!
测试类:
import java.util.logging.Logger;
public class Main {
public static void main(String[] args) {
Logger logger = Logger.getLogger(Main.class.getName());
logger.info("hello, world!");
}
}
输出:
八月 11, 2021 11:06:19 下午 cn.eagleli.log.jul.Main main
信息: hello, world!
依赖:
<dependencies>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
测试类:
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class Main {
public static void main(String[] args) {
Log log = LogFactory.getLog(Main.class);
log.info("hello, world!");
}
}
输出:
八月 11, 2021 11:08:25 下午 cn.eagleli.log.jcl.Main main
信息: hello, world!
从上面输出结果可以看出,默认会使用jul
作为底层的日志框架
如果我们想换成log4j
作为底层的日志框架,怎么办呢?只需要加一个依赖即可,如下:
依赖:
<dependencies>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
输出:
0 [main] INFO cn.eagleli.log.jcl.Main - hello, world!
从结果可以看出,底层日志框架已经变了,同样发现,我们的代码没有任何改动,只是加了一个依赖,由此可以看出接口的重要性。
依赖:
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
</dependencies>
测试类:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Main {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(Main.class);
logger.info("hello, world!");
}
}
输出:
23:14:30.893 [main] INFO cn.eagleli.log.slf4j.Main - hello, world!
上面底层日志框架使用的是logback
那如果我们想切换成jcl
作为底层实现框架,怎么办呢?只需换一个依赖即可,如下:
依赖:
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jcl</artifactId>
<version>1.7.30</version>
</dependency>
</dependencies>
输出:
八月 11, 2021 11:18:27 下午 org.slf4j.impl.JCLLoggerAdapter info
信息: hello, world!
从上面结果可以看出,底层已经切到jcl
了,而jcl
默认采用的是jdk
日志框架
cl-over-slf4j
和slf4j-jcl
是不能同时使用的
因为前一个使用jcl API
桥接到slf4j
,后一个是使用slf4j API
桥接到jcl
,如果同时引用会导致循环调用,进而导致栈溢出
依赖:
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.14.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.1</version>
</dependency>
</dependencies>
测试类:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Main {
public static void main(String[] args) {
Logger logger = LogManager.getLogger(Main.class);
logger.error("hello, world!");
}
}
输出:
23:22:12.148 [main] ERROR cn.eagleli.log.log4j2.Main - hello, world!
以上我们采用的log4j2
作为底层的实现,我们想要用slf4j
作为底层的实现,怎么办呢?只需加一个依赖即可,如下:
依赖:
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.14.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
<version>2.14.1</version>
</dependency>
</dependencies>
输出:
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
从结果看出,我们底层日志框架已经切换了,因为没有任何slf4j
的实现类框架,所以没有输出日志。
jcl
无缝切换的核心代码如下:
public abstract class LogFactory {
public static Log getLog(Class clazz)
throws LogConfigurationException {
return (getFactory().getInstance(clazz));
}
}
public class LogFactoryImpl extends LogFactory {
public Log getInstance(Class clazz) throws LogConfigurationException {
return (getInstance(clazz.getName()));
}
}
首先获得一个LogFactory
,它是可以自定义的,再从LogFactory
中获得一个Log
类,Log
类也是可以自定义的
LogFactory.getFactory()
用来获取一个LogFactory
,核心逻辑如下:
a.从系统变量获取
public static final String FACTORY_PROPERTY = "org.apache.commons.logging.LogFactory";
String factoryClass = getSystemProperty(FACTORY_PROPERTY, null);
factory = newFactory(factoryClass, baseClassLoader, contextClassLoader);
b.从指定文件里获取
protected static final String SERVICE_ID =
"META-INF/services/org.apache.commons.logging.LogFactory";
final InputStream is = getResourceAsStream(contextClassLoader, SERVICE_ID);
c.从指定properties文件获取
public static final String FACTORY_PROPERTIES = "commons-logging.properties"
Properties props = getConfigurationFile(contextClassLoader, FACTORY_PROPERTIES);
public static final String FACTORY_PROPERTY = "org.apache.commons.logging.LogFactory";
String factoryClass = props.getProperty(FACTORY_PROPERTY);
factory = newFactory(factoryClass, baseClassLoader, contextClassLoader);
d.默认的LogFactory实现
public static final String FACTORY_DEFAULT = "org.apache.commons.logging.impl.LogFactoryImpl"
factory = newFactory(FACTORY_DEFAULT, thisClassLoader, contextClassLoader);
LogFactoryImpl.discoverLogImplementation()
用来获取一个Log
,核心逻辑如下:
a.从LogFactory的attributes变量中获取
public static final String LOG_PROPERTY = "org.apache.commons.logging.Log"
String specifiedClass = (String) getAttribute(LOG_PROPERTY);
public Object getAttribute(String name) {
return attributes.get(name);
}
b.从系统变量中获取
public static final String LOG_PROPERTY = "org.apache.commons.logging.Log";
specifiedClass = getSystemProperty(LOG_PROPERTY, null);
c.默认的数组列表
private static final String LOGGING_IMPL_LOG4J_LOGGER = "org.apache.commons.logging.impl.Log4JLogger";
private static final String[] classesToDiscover = {
LOGGING_IMPL_LOG4J_LOGGER,
"org.apache.commons.logging.impl.Jdk14Logger",
"org.apache.commons.logging.impl.Jdk13LumberjackLogger",
"org.apache.commons.logging.impl.SimpleLog"
};
for(int i=0; i<classesToDiscover.length && result == null; ++i) {
result = createLogFromClass(classesToDiscover[i], logCategory, true);
}
// 先类加载,然后利用反射创建实例
public class LogFactoryImpl extends LogFactory {
private Log createLogFromClass(String logAdapterClassName,
String logCategory,
boolean affectState)
throws LogConfigurationException {
Class c = null;
try {
c = Class.forName(logAdapterClassName, true, currentCL);
} catch (ClassNotFoundException originalClassNotFoundException) {
}
constructor = c.getConstructor(logConstructorSignature);
Object o = constructor.newInstance(params);
}
}
看一两个适配的Logger
:
a.
import java.util.logging.Logger;
public class Jdk14Logger implements Log, Serializable {
public Jdk14Logger(String name) {
this.name = name;
logger = getLogger();
}
public Logger getLogger() {
if (logger == null) {
logger = Logger.getLogger(name);
}
return logger;
}
public void info(Object message) {
log(Level.INFO, String.valueOf(message), null);
}
private void log( Level level, String msg, Throwable ex ) {
Logger logger = getLogger();
if (logger.isLoggable(level)) {
// ...
}
}
}
b.
import org.apache.log4j.Logger;
public class Log4JLogger implements Log, Serializable {
public Log4JLogger(String name) {
this.name = name;
this.logger = getLogger();
}
public Logger getLogger() {
Logger result = logger;
if (result == null) {
synchronized(this) {
result = logger;
if (result == null) {
logger = result = Logger.getLogger(name);
}
}
}
return result;
}
public void info(Object message) {
getLogger().log(FQCN, Priority.INFO, message, null );
}
}
从上面的代码可以看出,这是典型的适配器模式,Jdk14Logger
使用的jul
的Logger
,而Log4JLogger
使用是log4j
的Logger
。
slf4j
无缝切换的核心代码如下:
public final class LoggerFactory {
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
public static ILoggerFactory getILoggerFactory() {
if (INITIALIZATION_STATE == UNINITIALIZED) {
synchronized (LoggerFactory.class) {
if (INITIALIZATION_STATE == UNINITIALIZED) {
INITIALIZATION_STATE = ONGOING_INITIALIZATION;
performInitialization();
}
}
}
switch (INITIALIZATION_STATE) {
case SUCCESSFUL_INITIALIZATION:
return StaticLoggerBinder.getSingleton().getLoggerFactory();
case NOP_FALLBACK_INITIALIZATION:
return NOP_FALLBACK_FACTORY;
case FAILED_INITIALIZATION:
throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
case ONGOING_INITIALIZATION:
// support re-entrant behavior.
// See also http://jira.qos.ch/browse/SLF4J-97
return SUBST_FACTORY;
}
throw new IllegalStateException("Unreachable code");
}
private final static void performInitialization() {
bind();
if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
versionSanityCheck();
}
}
private final static void bind() {
try {
Set<URL> staticLoggerBinderPathSet = null;
// skip check under android, see also
// http://jira.qos.ch/browse/SLF4J-328
if (!isAndroid()) {
staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
}
// the next line does the binding
StaticLoggerBinder.getSingleton();
INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
reportActualBinding(staticLoggerBinderPathSet);
} catch (NoClassDefFoundError ncde) {
String msg = ncde.getMessage();
if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
Util.report("Defaulting to no-operation (NOP) logger implementation");
Util.report("See " + NO_STATICLOGGERBINDER_URL + " for further details.");
} else {
failedBinding(ncde);
throw ncde;
}
} catch (java.lang.NoSuchMethodError nsme) {
String msg = nsme.getMessage();
if (msg != null && msg.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()")) {
INITIALIZATION_STATE = FAILED_INITIALIZATION;
Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");
Util.report("Your binding is version 1.5.5 or earlier.");
Util.report("Upgrade your binding to version 1.6.x.");
}
throw nsme;
} catch (Exception e) {
failedBinding(e);
throw new IllegalStateException("Unexpected initialization failure", e);
} finally {
postBindCleanUp();
}
}
}
从上面的代码一路走过来,其实主要就是在LoggerFactory.bind()
方法里面,简单粗暴,直接调用StaticLoggerBinder.getSingleton();
看org.slf4j.impl.StaticLoggerBinder
这个类有没有在classpath
下
从上面的图中,我们可以看出logback
和slf4j-jcl
都有这个类,具体的StaticLoggerBinder
代码就不分析了,大家可以自己看一下。
log4j2
无缝切换的核心代码如下:
public class LogManager {
public static Logger getLogger(final Class<?> clazz) {
final Class<?> cls = callerClass(clazz);
return getContext(cls.getClassLoader(), false).getLogger(cls);
}
public static LoggerContext getContext(final ClassLoader loader, final boolean currentContext) {
try {
return factory.getContext(FQCN, loader, null, currentContext);
} catch (final IllegalStateException ex) {
LOGGER.warn(ex.getMessage() + " Using SimpleLogger");
return new SimpleLoggerContextFactory().getContext(FQCN, loader, null, currentContext);
}
}
/**
* Scans the classpath to find all logging implementation. Currently, only one will be used but this could be
* extended to allow multiple implementations to be used.
*/
static {
// Shortcut binding to force a specific logging implementation.
final PropertiesUtil managerProps = PropertiesUtil.getProperties();
final String factoryClassName = managerProps.getStringProperty(FACTORY_PROPERTY_NAME);
if (factoryClassName != null) {
try {
factory = LoaderUtil.newCheckedInstanceOf(factoryClassName, LoggerContextFactory.class);
} catch (final ClassNotFoundException cnfe) {
LOGGER.error("Unable to locate configured LoggerContextFactory {}", factoryClassName);
} catch (final Exception ex) {
LOGGER.error("Unable to create configured LoggerContextFactory {}", factoryClassName, ex);
}
}
if (factory == null) {
final SortedMap<Integer, LoggerContextFactory> factories = new TreeMap<>();
// note that the following initial call to ProviderUtil may block until a Provider has been installed when
// running in an OSGi environment
if (ProviderUtil.hasProviders()) {
for (final Provider provider : ProviderUtil.getProviders()) {
final Class<? extends LoggerContextFactory> factoryClass = provider.loadLoggerContextFactory();
if (factoryClass != null) {
try {
factories.put(provider.getPriority(), factoryClass.newInstance());
} catch (final Exception e) {
LOGGER.error("Unable to create class {} specified in provider URL {}", factoryClass.getName(), provider
.getUrl(), e);
}
}
}
if (factories.isEmpty()) {
LOGGER.error("Log4j2 could not find a logging implementation. "
+ "Please add log4j-core to the classpath. Using SimpleLogger to log to the console...");
factory = new SimpleLoggerContextFactory();
} else if (factories.size() == 1) {
factory = factories.get(factories.lastKey());
} else {
final StringBuilder sb = new StringBuilder("Multiple logging implementations found: \n");
for (final Map.Entry<Integer, LoggerContextFactory> entry : factories.entrySet()) {
sb.append("Factory: ").append(entry.getValue().getClass().getName());
sb.append(", Weighting: ").append(entry.getKey()).append('\n');
}
factory = factories.get(factories.lastKey());
sb.append("Using factory: ").append(factory.getClass().getName());
LOGGER.warn(sb.toString());
}
} else {
LOGGER.error("Log4j2 could not find a logging implementation. "
+ "Please add log4j-core to the classpath. Using SimpleLogger to log to the console...");
factory = new SimpleLoggerContextFactory();
}
LogManagerStatus.setInitialized(true);
}
}
}
public final class ProviderUtil {
public static Iterable<Provider> getProviders() {
lazyInit();
return PROVIDERS;
}
protected static void lazyInit() {
// noinspection DoubleCheckedLocking
if (instance == null) {
try {
STARTUP_LOCK.lockInterruptibly();
try {
if (instance == null) {
instance = new ProviderUtil();
}
} finally {
STARTUP_LOCK.unlock();
}
} catch (final InterruptedException e) {
LOGGER.fatal("Interrupted before Log4j Providers could be loaded.", e);
Thread.currentThread().interrupt();
}
}
}
private ProviderUtil() {
for (final ClassLoader classLoader : LoaderUtil.getClassLoaders()) {
try {
loadProviders(classLoader);
} catch (final Throwable ex) {
LOGGER.debug("Unable to retrieve provider from ClassLoader {}", classLoader, ex);
}
}
for (final LoaderUtil.UrlResource resource : LoaderUtil.findUrlResources(PROVIDER_RESOURCE)) {
loadProvider(resource.getUrl(), resource.getClassLoader());
}
}
protected static void loadProviders(final ClassLoader classLoader) {
final ServiceLoader<Provider> serviceLoader = ServiceLoader.load(Provider.class, classLoader);
for (final Provider provider : serviceLoader) {
if (validVersion(provider.getVersions()) && !PROVIDERS.contains(provider)) {
PROVIDERS.add(provider);
}
}
}
}
其实上面的核心代码就是final ServiceLoader<Provider> serviceLoader = ServiceLoader.load(Provider.class, classLoader);
,利用SPI
获取一个特定的Provider
从上图可以看出,log4j-core
和log4j-to-slf4j
都有自己的Provider
实现类,具体的代码就不分析了,大家可以自己看一下。
Spring
中的日志框架采用的是spring-jcl
,我们就来看看它,如下图:
无缝切换的核心代码如下:
public abstract class LogFactory {
public static Log getLog(Class<?> clazz) {
return getLog(clazz.getName());
}
public static Log getLog(String name) {
return LogAdapter.createLog(name);
}
}
final class LogAdapter {
public static Log createLog(String name) {
switch (logApi) {
case LOG4J:
return Log4jAdapter.createLog(name);
case SLF4J_LAL:
return Slf4jAdapter.createLocationAwareLog(name);
case SLF4J:
return Slf4jAdapter.createLog(name);
default:
// Defensively use lazy-initializing adapter class here as well since the
// java.logging module is not present by default on JDK 9. We are requiring
// its presence if neither Log4j nor SLF4J is available; however, in the
// case of Log4j or SLF4J, we are trying to prevent early initialization
// of the JavaUtilLog adapter - e.g. by a JVM in debug mode - when eagerly
// trying to parse the bytecode for all the cases of this switch clause.
return JavaUtilAdapter.createLog(name);
}
}
private static final String LOG4J_SPI = "org.apache.logging.log4j.spi.ExtendedLogger";
private static final String LOG4J_SLF4J_PROVIDER = "org.apache.logging.slf4j.SLF4JProvider";
private static final String SLF4J_SPI = "org.slf4j.spi.LocationAwareLogger";
private static final String SLF4J_API = "org.slf4j.Logger";
private static final LogApi logApi;
static {
if (isPresent(LOG4J_SPI)) {
if (isPresent(LOG4J_SLF4J_PROVIDER) && isPresent(SLF4J_SPI)) {
// log4j-to-slf4j bridge -> we'll rather go with the SLF4J SPI;
// however, we still prefer Log4j over the plain SLF4J API since
// the latter does not have location awareness support.
logApi = LogApi.SLF4J_LAL;
}
else {
// Use Log4j 2.x directly, including location awareness support
logApi = LogApi.LOG4J;
}
}
else if (isPresent(SLF4J_SPI)) {
// Full SLF4J SPI including location awareness support
logApi = LogApi.SLF4J_LAL;
}
else if (isPresent(SLF4J_API)) {
// Minimal SLF4J API without location awareness support
logApi = LogApi.SLF4J;
}
else {
// java.util.logging as default
logApi = LogApi.JUL;
}
}
private static boolean isPresent(String className) {
try {
Class.forName(className, false, LogAdapter.class.getClassLoader());
return true;
}
catch (ClassNotFoundException ex) {
return false;
}
}
}
从上面代码可以看出,spring-jcl
就是根据Class.forName()
来判断各个日志框架的核心类是否存在,然后创建对应的Log
适配类。
我们的项目一般都会依赖spring-boot-starter-web
,它又会依赖spring-boot-starter
,又会依赖spring-boot-starter-logging
我们就来看看spring-boot-starter-logging-2.5.1
,如下图:
我们发现它没有任何代码,我们再来看看它pom
文件里面的依赖:
<dependencies>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
<version>2.14.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
<version>1.7.30</version>
<scope>compile</scope>
</dependency>
</dependencies>
从这个依赖我们可以看出,它引用了logback-classic
,也就是slf4j
的实现框架
由于log4j-to-slf4j
和jul-to-slf4j
依赖的存在,不管你使用的是log4j2
还是jul
,最终都会切到slf4j
上
所以我们可以说spring boot
默认使用的日志框架就是logback
。
说了这么多,我们总结一下这些日志接口框架是怎么做到无缝切换的:jcl/spring-jcl
:通过类加载的机制:class.forname()
slf4j
:通过判断类是否在classpath
下log4j2
:通过SPI
的形式
https://segmentfault.com/a/1190000021121882
https://logging.apache.org/log4j/1.2/manual.html
https://commons.apache.org/proper/commons-logging/guide.html
https://blog.csdn.net/chengmaoning/article/details/78072670
http://www.slf4j.org/legacy.html
https://logging.apache.org/log4j/2.x/runtime-dependencies.html
新建表: create table [表名] ( [自动编号字段] int IDENTITY (1,1)&nbs
我的文件中有正在本地化的字符串。其中许多是常见的,并且已经在整个 iOS 中使用。例如。 “保存”、“加载”、“返回”、“收藏夹”、“拍照”。为了与其他应用程序和内置应用程序提供一致的用户体验,是否有
我已经学习了 Qt 的基础知识,现在对这个漂亮的库的深度感兴趣。请帮助我理解: 所有类都是从QObject派生的吗? 为什么可以在QWidget(和派生类)上绘画? return app.exec()
我在 webpack 中设置了一个自调用函数,并使用常见的 JS 来需要一些包: (function() { var $ = require("jquery"); //...my functi
我正在尝试制作一个大量使用词性标记的应用程序。但是 nltk 的 pos 标记功能对我来说似乎不符合标准 - 例如: import nltk text = "Obama delivers his fi
有没有办法处理发送到 MySQL 的常见查询以防止不必要的带宽使用? 最佳答案 选项是: 使用MySQL缓存查询 好:全自动 差:仍然需要访问数据库服务器;有一次缓存让我在一个项目中失望,花了很长时间
关闭。这个问题需要更多focused .它目前不接受答案。 想改进这个问题吗? 更新问题,使其只关注一个问题 editing this post . 关闭 4 年前。 Improve this qu
关闭。这个问题需要debugging details .它目前不接受答案。 想改善这个问题吗?更新问题,使其成为 on-topic对于堆栈溢出。 6年前关闭。 Improve this questio
我正在尝试调用返回 csv 文件的网络服务。因此,我调用的每个 URL 都有一个后缀,它是一个字符串,表示要生成哪个 csv。然后我想将此 csv 保存到文件中。有很多要生成,所以我从多个线程调用此类
流行手机型号支持的典型触摸点数量是多少?我在基础研究中看到低至 2 和高至 5,但我希望能够将其映射到实际手机和更好的限制! 最佳答案 两部手机的触控点数据: Galaxy S 5 LG
出于好奇 - 我知道有 LAMP - Linux、Apache、MySQL 和 PHP。但是还有哪些其他 Web 堆栈替代方案的缩写呢?像 LAMR - Linux、Apache、MySQL Ruby
我写了一个java代码(使用apache common vfs2)来上传文件到SFTP服务器。最近,我在我的服务器上引入了 PGP 安全性。现在,java 代码无法连接到该服务器。与 FileZill
由于 GLU 被认为对于现代 OpenGL (3.1+) 来说已经过时,那么使用 C/C++ 在 OpenGL 中绘制基本形状(例如椭圆或弧线/饼图)的方法是什么?令人难以置信的是,在 OpenGL
我想知道是否有最流行的 iOS 应用程序的自定义 URL 方案列表,例如 Keynote、Numbers、Pages、Evernote 等。我还想知道这些应用程序使用什么参数网址。 我需要这个的原因是
我正在使用 NDK r10d 移植 C++ myToll Linux 应用程序以在 Android 上运行。 (请注意,这不是带有 apk 的 Android 应用程序,而是从 shell 运行的实用
假设您想要使用 UML 2 部署图为在该领域没有太多知识的人可视化一个常见的 PHP 服务器应用程序。这样一个通用的应用程序可能有三个设备节点(数据库服务器、Web 服务器和客户端)和四个执行环境节点
我正在尝试运行以下代码,以找到两个人之间的共同 friend 。输入如下 A : B C D B : A C D E C : A B D E D : A B C E E : B C D 我无法在输出文
我在 Gitolite 的 manual 中找到的唯一东西在钩子(Hook)上,是: If you want to add your own hook, it's easy as long as it
具体来说,我有一个问题,在 AWS 环境中组织 AZ 故障转移的推荐方法是什么。此外,最好了解典型的 AWS 故障以组织应用程序 HA(高可用性)。 因此,应用程序架构(AWS 服务使用)如下: 它或
我正在尝试编写一个通用的 SecurePagingAndSorting 存储库,它将检查 CRUD 操作的安全性,以节省在所有 JPA 存储库中重复相同的 PreAuthorize(使用不同的权限)。
我是一名优秀的程序员,十分优秀!