热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

logger(三)log4j2简介及其实现原理

一、log4j2简介log4j2是log4j1.x和logback的改进版,据说采用了一些新技术(无锁异步、等等),使得日志

一、log4j2简介

log4j2是log4j 1.x和logback的改进版,据说采用了一些新技术(无锁异步、等等),使得日志的吞吐量、性能比log4j 1.x提高10倍,并解决了一些死锁的bug,而且配置更加简单灵活

maven配置


<dependency><groupId>org.apache.logging.log4jgroupId><artifactId>log4j-apiartifactId><version>2.9.1version>
dependency>
<dependency><groupId>org.apache.logging.log4jgroupId><artifactId>log4j-coreartifactId><version>2.9.1version>
dependency>

<dependency><groupId>org.apache.logging.log4jgroupId><artifactId>log4j-webartifactId><version>2.9.1version>
dependency>

<dependency><groupId>org.apache.logging.log4jgroupId><artifactId>log4j-slf4j-implartifactId><version>2.9.1version>
dependency>

<dependency><groupId>org.slf4jgroupId><artifactId>slf4j-apiartifactId><version>1.7.25version>

也可以配置starter

<dependency><groupId>org.springframework.bootgroupId><artifactId>spring-boot-starter-log4j2artifactId>
dependency>

二、log4j2.xml配置

实现类在log4j2.xml配置文件中的标签名。

xml version&#61;"1.0" encoding&#61;"UTF-8"?>



<configuration status&#61;"WARN" monitorInterval&#61;"30"><properties><property name&#61;"server.port">property>properties><appenders><console name&#61;"Console" target&#61;"SYSTEM_OUT"><PatternLayout pattern&#61;"%d{yyyy-MM-dd HH:mm:ss,SSS} [%thread] %p %m%n"/>console><RollingFile name&#61;"RollingFile" filePattern&#61;"/data/log/tomcat${sys:server.port}/catalina.%d{yyyy-MM-dd}.log"><ThresholdFilter level&#61;"info" onMatch&#61;"ACCEPT" onMismatch&#61;"DENY"/><PatternLayout pattern&#61;"%d{yyyy-MM-dd HH:mm:ss,SSS} [%thread] %p %m%n"/><Policies><TimeBasedTriggeringPolicy interval&#61;"1" modulate&#61;"true"/>Policies><DirectWriteRolloverStrategy/>RollingFile>appenders><loggers><logger name&#61;"org.springframework" level&#61;"INFO">logger><logger name&#61;"org.mybatis" level&#61;"INFO">logger><root level&#61;"INFO"><appender-ref ref&#61;"Console"/><appender-ref ref&#61;"RollingFile"/>root>loggers>
configuration>

简单说Appender就是一个管道&#xff0c;定义了日志内容的去向(保存位置)。

配置一个或者多个Filter进行过滤

配置Layout来控制日志信息的输出格式。

配置Policies以控制日志何时(When)进行滚动。

配置Strategy以控制日志如何(How)进行滚动。

简单说了下配置项&#xff0c;具体可参考博客&#xff1a;https://www.imooc.com/article/78966

https://www.cnblogs.com/hafiz/p/6170702.html

三、log4j2其实现原理

首先介绍下log4j2中的几个重要的概念

LoggerContext

 LoggerContext在Logging System中扮演了锚点的角色。根据情况的不同&#xff0c;一个应用可能同时存在于多个有效的LoggerContext中。在同一LoggerContext下&#xff0c;log system是互通的。如&#xff1a;Standalone Application、Web Applications、Java EE Applications、”Shared” Web Applications 和REST Service Containers&#xff0c;就是不同广度范围的log上下文环境。

Configuration

 每一个LoggerContext都有一个有效的Configuration。Configuration包含了所有的Appenders、上下文范围内的过滤器、LoggerConfigs以及StrSubstitutor.的引用。在重配置期间&#xff0c;新与旧的Configuration将同时存在。当所有的Logger对象都被重定向到新的Configuration对象后&#xff0c;旧的Configuration对象将被停用和丢弃。

 Logger

Loggers 是通过调用LogManager.getLogger方法获得的。Logger对象本身并不实行任何实际的动作。它只是拥有一个name 以及与一个LoggerConfig相关联。它继承了AbstractLogger类并实现了所需的方法。当Configuration改变时&#xff0c;Logger将会与另外的LoggerConfig相关联&#xff0c;从而改变这个Logger的行为。

LoggerConfig

每个LoggerConfig和logger是对应的&#xff0c;获取到一个logger&#xff0c;写日志时其实是通过LoggerConfig来记日志的

1、获取LoggerFactory

和logback一样&#xff0c;slf4j委托具体实现框架的StaticLoggerBinder来返回一个ILoggerFactory&#xff0c;从而对接到具体实现框架上&#xff0c;我们看下这个类(省略了部分代码)

public final class StaticLoggerBinder implements LoggerFactoryBinder {private static final StaticLoggerBinder SINGLETON &#61; new StaticLoggerBinder();private final ILoggerFactory loggerFactory;/*** Private constructor to prevent instantiation*/private StaticLoggerBinder() {loggerFactory &#61; new Log4jLoggerFactory();}/*** Returns the singleton of this class.** &#64;return the StaticLoggerBinder singleton*/public static StaticLoggerBinder getSingleton() {return SINGLETON;}/*** Returns the factory.* &#64;return the factor.*/&#64;Overridepublic ILoggerFactory getLoggerFactory() {return loggerFactory;}
}

可以看到

  • 1、通过getSingleton()获取该类的单例
  • 2、通过构造函数新建了Log4jLoggerFactory实例&#xff0c;
  • 3、通过getLoggerFactory()方法返回该实例

2、获取logger

进入Log4jLoggerFactory类中查看getLogger()方法&#xff0c;发现是在AbstractLoggerAdapter类中

&#64;Overridepublic L getLogger(final String name) {final LoggerContext context &#61; getContext();final ConcurrentMap loggers &#61; getLoggersInContext(context);final L logger &#61; loggers.get(name);if (logger !&#61; null) {return logger;}loggers.putIfAbsent(name, newLogger(name, context));return loggers.get(name);}

1、通过getContext()得到LoggerContext实例

2、在context中查找是否已经有该logger&#xff0c;有就返回

3、如果没有则调用newLogger(name, context)方法新建logger

Log4jLoggerFactory只有两个方法&#xff0c;就是上面说的getContext()和newLogger(name, context)。下面分两节分别讲下这两个方法

public class Log4jLoggerFactory extends AbstractLoggerAdapter implements ILoggerFactory {private static final String FQCN &#61; Log4jLoggerFactory.class.getName();private static final String PACKAGE &#61; "org.slf4j";&#64;Overrideprotected Logger newLogger(final String name, final LoggerContext context) {final String key &#61; Logger.ROOT_LOGGER_NAME.equals(name) ? LogManager.ROOT_LOGGER_NAME : name;return new Log4jLogger(context.getLogger(key), name);}&#64;Overrideprotected LoggerContext getContext() {final Class anchor &#61; StackLocatorUtil.getCallerClass(FQCN, PACKAGE);return anchor &#61;&#61; null ? LogManager.getContext() : getContext(StackLocatorUtil.getCallerClass(anchor));}}

2.1  getContext()

getContext()方法就是返回合适的loggerContext&#xff0c;进入LogManager.getContext()方法

public static LoggerContext getContext() {try {return factory.getContext(FQCN, null, null, true);} catch (final IllegalStateException ex) {LOGGER.warn(ex.getMessage() &#43; " Using SimpleLogger");return new SimpleLoggerContextFactory().getContext(FQCN, null, null, true);}}

factory实在LoggerContext静态代码块中初始化的&#xff0c;继续进入factory.getContext(FQCN, null, null, true)方法中&#xff0c;进入实现类Log4jContextFactory中

&#64;Overridepublic LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext,final boolean currentContext) {final LoggerContext ctx &#61; selector.getContext(fqcn, loader, currentContext);if (externalContext !&#61; null && ctx.getExternalContext() &#61;&#61; null) {ctx.setExternalContext(externalContext);}if (ctx.getState() &#61;&#61; LifeCycle.State.INITIALIZED) {ctx.start();}return ctx;}

LoggerContext是从selector.getContext(fqcn, loader, currentContext)中获取的&#xff0c;此时判断ctx.getState()是否等于LifeCycle.State.INITIALIZED&#xff0c;第一次调用getlogger()时&#xff0c;会进入此方法&#xff0c;我们看下ctx.start();

public void start() {LOGGER.debug("Starting LoggerContext[name&#61;{}, {}]...", getName(), this);if (PropertiesUtil.getProperties().getBooleanProperty("log4j.LoggerContext.stacktrace.on.start", false)) {LOGGER.debug("Stack trace to locate invoker",new Exception("Not a real error, showing stack trace to locate invoker"));}if (configLock.tryLock()) {try {if (this.isInitialized() || this.isStopped()) {this.setStarting();reconfigure();if (this.configuration.isShutdownHookEnabled()) {setUpShutdownHook();}this.setStarted();}} finally {configLock.unlock();}}LOGGER.debug("LoggerContext[name&#61;{}, {}] started OK.", getName(), this);}

进入reconfigure()方法

private void reconfigure(final URI configURI) {final ClassLoader cl &#61; ClassLoader.class.isInstance(externalContext) ? (ClassLoader) externalContext : null;LOGGER.debug("Reconfiguration started for context[name&#61;{}] at URI {} ({}) with optional ClassLoader: {}",contextName, configURI, this, cl);final Configuration instance &#61; ConfigurationFactory.getInstance().getConfiguration(this, contextName, configURI, cl);if (instance &#61;&#61; null) {LOGGER.error("Reconfiguration failed: No configuration found for &#39;{}&#39; at &#39;{}&#39; in &#39;{}&#39;", contextName, configURI, cl);} else {setConfiguration(instance);/** instance.start(); Configuration old &#61; setConfiguration(instance); updateLoggers(); if (old !&#61; null) {* old.stop(); }*/final String location &#61; configuration &#61;&#61; null ? "?" : String.valueOf(configuration.getConfigurationSource());LOGGER.debug("Reconfiguration complete for context[name&#61;{}] at URI {} ({}) with optional ClassLoader: {}",contextName, location, this, cl);}}

我们的配置文件log4j2.xml就是该函数中实现的&#xff0c;ConfigurationFactory.getInstance().getConfiguration(this, contextName, configURI, cl)得到了配置文件&#xff0c;并解析成Configuration。进入setConfiguration(instance)方法&#xff0c;启动当前的configuration&#xff0c;并启动该配置下的所有appender&#xff0c;logger和root。

public Configuration setConfiguration(final Configuration config) {if (config &#61;&#61; null) {LOGGER.error("No configuration found for context &#39;{}&#39;.", contextName);// No change, return the current configuration.return this.configuration;}configLock.lock();try {final Configuration prev &#61; this.configuration;config.addListener(this);final ConcurrentMap map &#61; config.getComponent(Configuration.CONTEXT_PROPERTIES);try { // LOG4J2-719 network access may throw android.os.NetworkOnMainThreadExceptionmap.putIfAbsent("hostName", NetUtils.getLocalHostname());} catch (final Exception ex) {LOGGER.debug("Ignoring {}, setting hostName to &#39;unknown&#39;", ex.toString());map.putIfAbsent("hostName", "unknown");}map.putIfAbsent("contextName", contextName);config.start();this.configuration &#61; config;updateLoggers();if (prev !&#61; null) {prev.removeListener(this);prev.stop();}firePropertyChangeEvent(new PropertyChangeEvent(this, PROPERTY_CONFIG, prev, config));try {Server.reregisterMBeansAfterReconfigure();} catch (final LinkageError | Exception e) {// LOG4J2-716: Android has no java.lang.managementLOGGER.error("Could not reconfigure JMX", e);}// AsyncLoggers update their nanoClock when the configuration changes
Log4jLogEvent.setNanoClock(configuration.getNanoClock());return prev;} finally {configLock.unlock();}}

2.2 newLogger(name, context)

protected Logger newLogger(final String name, final LoggerContext context) {final String key &#61; Logger.ROOT_LOGGER_NAME.equals(name) ? LogManager.ROOT_LOGGER_NAME : name;return new Log4jLogger(context.getLogger(key), name);}

进入context.getLogger(key)方法

&#64;Overridepublic Logger getLogger(final String name) {return getLogger(name, null);}
&#64;Override
public Logger getLogger(final String name, final MessageFactory messageFactory) {// Note: This is the only method where we add entries to the &#39;loggerRegistry&#39; ivar.Logger logger &#61; loggerRegistry.getLogger(name, messageFactory);if (logger !&#61; null) {AbstractLogger.checkMessageFactory(logger, messageFactory);return logger;}logger &#61; newInstance(this, name, messageFactory);loggerRegistry.putIfAbsent(name, messageFactory, logger);return loggerRegistry.getLogger(name, messageFactory);}

进入newInstance(this, name, messageFactory)方法

protected Logger newInstance(final LoggerContext ctx, final String name, final MessageFactory messageFactory) {return new Logger(ctx, name, messageFactory);}

protected Logger(final LoggerContext context, final String name, final MessageFactory messageFactory) {super(name, messageFactory);this.context &#61; context;privateConfig &#61; new PrivateConfig(context.getConfiguration(), this);}

public PrivateConfig(final Configuration config, final Logger logger) {this.config &#61; config;this.loggerConfig &#61; config.getLoggerConfig(getName());this.loggerConfigLevel &#61; this.loggerConfig.getLevel();this.intLevel &#61; this.loggerConfigLevel.intLevel();this.logger &#61; logger;}

public LoggerConfig getLoggerConfig(final String loggerName) {LoggerConfig loggerConfig &#61; loggerConfigs.get(loggerName);if (loggerConfig !&#61; null) {return loggerConfig;}String substr &#61; loggerName;while ((substr &#61; NameUtil.getSubName(substr)) !&#61; null) {loggerConfig &#61; loggerConfigs.get(substr);if (loggerConfig !&#61; null) {return loggerConfig;}}return root;}

可以看到首先从loggerConfigs也就是配置文件中配置的logger中获取&#xff0c;如果获取不到则循环递归name中"."之前的logger&#xff0c;如果还是获取不到&#xff0c;则默认使用root的配置。

3、logger.info()

Log4jLogger.class

public void info(final String format) {logger.logIfEnabled(FQCN, Level.INFO, null, format);}

&#64;Overridepublic void logIfEnabled(final String fqcn, final Level level, final Marker marker, final String message) {if (isEnabled(level, marker, message)) {logMessage(fqcn, level, marker, message);}}public boolean isEnabled(final Level level, final Marker marker, final String message) {return privateConfig.filter(level, marker, message);}protected void logMessage(final String fqcn, final Level level, final Marker marker, final String message) {final Message msg &#61; messageFactory.newMessage(message);logMessageSafely(fqcn, level, marker, msg, msg.getThrowable());}

可以看到isEnabled()方法中用来通过配置的filter来判断是否符合&#xff0c;如果符合则进入logMessage()方法

protected void logMessage(final String fqcn, final Level level, final Marker marker, final String message) {final Message msg &#61; messageFactory.newMessage(message);logMessageSafely(fqcn, level, marker, msg, msg.getThrowable());}private void logMessageSafely(final String fqcn, final Level level, final Marker marker, final Message msg,final Throwable throwable) {try {logMessageTrackRecursion(fqcn, level, marker, msg, throwable);} finally {// LOG4J2-1583 prevent scrambled logs when logging calls are nested (logging in toString())
ReusableMessageFactory.release(msg);}}private void logMessageTrackRecursion(final String fqcn,final Level level,final Marker marker,final Message msg,final Throwable throwable) {try {incrementRecursionDepth(); // LOG4J2-1518, LOG4J2-2031
tryLogMessage(fqcn, level, marker, msg, throwable);} finally {decrementRecursionDepth();}}

private void tryLogMessage(final String fqcn,final Level level,final Marker marker,final Message msg,final Throwable throwable) {try {logMessage(fqcn, level, marker, msg, throwable);} catch (final Exception e) {// LOG4J2-1990 Log4j2 suppresses all exceptions that occur once application called the logger
handleLogMessageException(e, fqcn, msg);}}

public void logMessage(final String fqcn, final Level level, final Marker marker, final Message message,final Throwable t) {final Message msg &#61; message &#61;&#61; null ? new SimpleMessage(Strings.EMPTY) : message;final ReliabilityStrategy strategy &#61; privateConfig.loggerConfig.getReliabilityStrategy();strategy.log(this, getName(), fqcn, marker, level, msg, t);}

public void log(final Supplier reconfigured, final String loggerName, final String fqcn, final Marker marker, final Level level,final Message data, final Throwable t) {loggerConfig.log(loggerName, fqcn, marker, level, data, t);}

public void log(final String loggerName, final String fqcn, final Marker marker, final Level level,final Message data, final Throwable t) {List props &#61; null;if (!propertiesRequireLookup) {props &#61; properties;} else {if (properties !&#61; null) {props &#61; new ArrayList<>(properties.size());final LogEvent event &#61; Log4jLogEvent.newBuilder().setMessage(data).setMarker(marker).setLevel(level).setLoggerName(loggerName).setLoggerFqcn(fqcn).setThrown(t).build();for (int i &#61; 0; i ) {final Property prop &#61; properties.get(i);final String value &#61; prop.isValueNeedsLookup() // since LOG4J2-1575? config.getStrSubstitutor().replace(event, prop.getValue()) //
: prop.getValue();props.add(Property.createProperty(prop.getName(), value));}}}final LogEvent logEvent &#61; logEventFactory.createEvent(loggerName, marker, fqcn, level, data, props, t);try {log(logEvent, LoggerConfigPredicate.ALL);} finally {// LOG4J2-1583 prevent scrambled logs when logging calls are nested (logging in toString())
ReusableLogEventFactory.release(logEvent);}}

protected void log(final LogEvent event, final LoggerConfigPredicate predicate) {if (!isFiltered(event)) { processLogEvent(event, predicate);}}

private void processLogEvent(final LogEvent event, final LoggerConfigPredicate predicate) {event.setIncludeLocation(isIncludeLocation());if (predicate.allow(this)) {callAppenders(event);}logParent(event, predicate);}protected void callAppenders(final LogEvent event) {final AppenderControl[] controls &#61; appenders.get();//noinspection ForLoopReplaceableByForEachfor (int i &#61; 0; i ) {controls[i].callAppender(event);}}

这时候终于到了appender的处理了&#xff0c;直接定位到RollingFileAppender类中

public void append(final LogEvent event) {getManager().checkRollover(event);super.append(event);}

private void tryAppend(final LogEvent event) {if (Constants.ENABLE_DIRECT_ENCODERS) {directEncodeEvent(event);} else {writeByteArrayToManager(event);}}
protected void directEncodeEvent(final LogEvent event) {getLayout().encode(event, manager);if (this.immediateFlush || event.isEndOfBatch()) {manager.flush();}}

这时候可以看到layout和encode的使用了

public void encode(final StringBuilder source, final ByteBufferDestination destination) {try {final Object[] threadLocalState &#61; getThreadLocalState();final CharsetEncoder charsetEncoder &#61; (CharsetEncoder) threadLocalState[0];final CharBuffer charBuffer &#61; (CharBuffer) threadLocalState[1];final ByteBuffer byteBuffer &#61; (ByteBuffer) threadLocalState[2];TextEncoderHelper.encodeText(charsetEncoder, charBuffer, byteBuffer, source, destination);} catch (final Exception ex) {logEncodeTextException(ex, source, destination);TextEncoderHelper.encodeTextFallBack(charset, source, destination);}}

最后写日志。

 

四、通过代码动态生成logger对象

public class LoggerHolder {//加个前缀防止配置的name正好是我们某个类名&#xff0c;导致使用的日志路径使用了类名的路径private static final String PREFIX &#61; "logger_";/*** 支持生成写大数据文件的logger** &#64;param name logger name* &#64;return Logger*/public static Logger getLogger(String name) {String loggerName &#61; PREFIX &#43; name;Log4jLoggerFactory loggerFactory &#61; (Log4jLoggerFactory) LoggerFactory.getILoggerFactory();LoggerContext context &#61; (LoggerContext) LogManager.getContext();//如果未加载过该logger,则新建一个if (loggerFactory.getLoggersInContext(context).get(loggerName) &#61;&#61; null) {buildLogger(name);}//
return loggerFactory.getLogger(loggerName);}/*** 包装了Loggerfactory&#xff0c;和LoggerFactory.getLogger(T.class)功能一致** &#64;param clazz* &#64;return*/public static Logger getLogger(Class clazz) {Log4jLoggerFactory loggerFactory &#61; (Log4jLoggerFactory) LoggerFactory.getILoggerFactory();return loggerFactory.getLogger(clazz.getName());}/*** &#64;param name logger name*/private static void buildLogger(String name) {String loggerName &#61; PREFIX &#43; name;LoggerContext context &#61; (LoggerContext) LogManager.getContext();Configuration configuration &#61; context.getConfiguration();//配置PatternLayout输出格式PatternLayout layout &#61; PatternLayout.newBuilder().withCharset(UTF_8).withPattern("%msg%n").build();//配置基于时间的滚动策略TimeBasedTriggeringPolicy policy &#61; TimeBasedTriggeringPolicy.newBuilder().withInterval(24).build();//配置同类型日志策略DirectWriteRolloverStrategy strategy &#61; DirectWriteRolloverStrategy.newBuilder().withConfig(configuration).build();//配置appenderRollingFileAppender appender &#61; RollingFileAppender.newBuilder().setName(loggerName).withFilePattern("/data/bigdata/" &#43; name &#43; "/" &#43; name &#43; ".%d{yyyyMMdd}.log").setLayout(layout).withPolicy(policy).withStrategy(strategy).withAppend(true).build();//改变appender状态
appender.start();//新建loggerLoggerConfig loggerConfig &#61; new LoggerConfig(loggerName, Level.INFO, false);loggerConfig.addAppender(appender, Level.INFO, null);configuration.addLogger(loggerName, loggerConfig);
context.updateLoggers();}
}

 

 

 


转:https://www.cnblogs.com/pjfmeng/p/11277124.html



推荐阅读
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 本文介绍了使用kotlin实现动画效果的方法,包括上下移动、放大缩小、旋转等功能。通过代码示例演示了如何使用ObjectAnimator和AnimatorSet来实现动画效果,并提供了实现抖动效果的代码。同时还介绍了如何使用translationY和translationX来实现上下和左右移动的效果。最后还提供了一个anim_small.xml文件的代码示例,可以用来实现放大缩小的效果。 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • Android系统移植与调试之如何修改Android设备状态条上音量加减键在横竖屏切换的时候的显示于隐藏
    本文介绍了如何修改Android设备状态条上音量加减键在横竖屏切换时的显示与隐藏。通过修改系统文件system_bar.xml实现了该功能,并分享了解决思路和经验。 ... [详细]
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
  • MyBatis多表查询与动态SQL使用
    本文介绍了MyBatis多表查询与动态SQL的使用方法,包括一对一查询和一对多查询。同时还介绍了动态SQL的使用,包括if标签、trim标签、where标签、set标签和foreach标签的用法。文章还提供了相关的配置信息和示例代码。 ... [详细]
  • iOS超签签名服务器搭建及其优劣势
    本文介绍了搭建iOS超签签名服务器的原因和优势,包括不掉签、用户可以直接安装不需要信任、体验好等。同时也提到了超签的劣势,即一个证书只能安装100个,成本较高。文章还详细介绍了超签的实现原理,包括用户请求服务器安装mobileconfig文件、服务器调用苹果接口添加udid等步骤。最后,还提到了生成mobileconfig文件和导出AppleWorldwideDeveloperRelationsCertificationAuthority证书的方法。 ... [详细]
  • SpringMVC接收请求参数的方式总结
    本文总结了在SpringMVC开发中处理控制器参数的各种方式,包括处理使用@RequestParam注解的参数、MultipartFile类型参数和Simple类型参数的RequestParamMethodArgumentResolver,处理@RequestBody注解的参数的RequestResponseBodyMethodProcessor,以及PathVariableMapMethodArgumentResol等子类。 ... [详细]
  • 在Docker中,将主机目录挂载到容器中作为volume使用时,常常会遇到文件权限问题。这是因为容器内外的UID不同所导致的。本文介绍了解决这个问题的方法,包括使用gosu和suexec工具以及在Dockerfile中配置volume的权限。通过这些方法,可以避免在使用Docker时出现无写权限的情况。 ... [详细]
  • 本文介绍了在Python3中如何使用选择文件对话框的格式打开和保存图片的方法。通过使用tkinter库中的filedialog模块的asksaveasfilename和askopenfilename函数,可以方便地选择要打开或保存的图片文件,并进行相关操作。具体的代码示例和操作步骤也被提供。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 本文详细介绍了Spring的JdbcTemplate的使用方法,包括执行存储过程、存储函数的call()方法,执行任何SQL语句的execute()方法,单个更新和批量更新的update()和batchUpdate()方法,以及单查和列表查询的query()和queryForXXX()方法。提供了经过测试的API供使用。 ... [详细]
  • 有没有一种方法可以在不继承UIAlertController的子类或不涉及UIAlertActions的情况下 ... [详细]
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
author-avatar
恋之-风景-
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有