扩展 JDK 日志框架(jdk logger)
ccwgpt 2024-10-01 08:12 19 浏览 0 评论
自 Java 1.4 开始,JDK 包括了一个崭新的日志框架包 java.util.logging,该日志框架设计精良,和 JDK 紧密结合,控制灵活,使用简单。日志记录对于软件的维护特别是对于已部署到运行环境之后的软件调试都有着重要的意义。在实际的项目中,往往还需要比该框架所提供的更为复杂的日志功能。对于这种需求,JDK 日志框架具有足够的可扩展能力,可以自定义不同需求的日志处理、消息格式化、日志消息级别等组件。在下面的内容中,本文将介绍了如何扩展 JDK 日志框架,自定义日志处理方式。并就一个实际的例子来介绍如何结合 JDK 日志框架和 STAF(Software Testing Automation Framework,一种自动化测试框架)日志服务来对 Java 程序进行监视。
JDK 日志框架介绍
JDK 的日志框架即 java.util.logging 包。对于一个软件的日志系统而言,首先必须得有一个日志对象,该对象负责记录日志信息。同时该信息可以输出到不同的位置,例如控制台,文件甚至网络中。对于信息的格式,则可以根据不同的需求,可以输出成普通文本,XML 或者 HTML 的格式。同时还需要对日志信息进行不同级别的分类,这样的好处是可以过滤冗余信息,只保留关键的日志。对于一个日志框架而言,日志对象必须是可配置的,它可以按照配置来输出到指定的目标,同时按照配置来决定输出的格式和决定何种级别以上的日志才能输出。配置的形式还可以是多种多样的,既能是代码的形式,也能是配置文件的形式。尤其是配置文件的形式,对于一个已经部署到运行环境中的软件而言,可以非常方便的改变日志配置而无需改变其源代码。
JDK 日志框架提供了上述的所有功能。它主要包括如下几个部件:
- Logger:日志记录对象。用于记录日志信息。
- Handler:用于处理日志信息的输出。在 Handler 类中,可以决定日志是输出到文件中还是控制台中。
- Filter: 用于过滤日志。在 Filter 类中,可以根据日志级别或者某种条件来决定是否输出该日志。这样达到去除冗余信息的目的。
- Formatter:用于格式化日志信息。该类可以将日志文本格式化成 XML 或者 HTML 的格式,这完全依赖于具体的实现。
- Level:用于表示日志的级别。 JDK 日志框架默认有如下级别 : SEVERE,WARNING,INFO,CONFIG,FINE,FINER,FINEST 。
对于程序而言,它的 Logger 对象首先会判断日志的级别是否满足输出级别的要求,然后将满足级别要求的日志消息交给所配置的 Handler 对象来处理,如果日志对象配置了一个 Filter 对象,那么 Filter 对象将会对日志信息做一次过滤。 Handler 对象接受到日志消息后,根据其所配置的格式化类 Formatter 来改变日志的格式,根据所配置的 Filter 对象和 Level 对象来再次过滤日志信息,最后输出到该种 Handler 对象所指定的输出位置中,该输出位置可以是控制台,文件,网络 socket 甚至是内存缓冲区。其架构模型如 图 1 所示。
图 1 JDK 日志框架
JDK 提供了如下几种默认支持的 Handler 类:
- ConsoleHandler: 输出日志到控制台中
- FileHandler:输出日志到指定文件中
- MemoryHandler:输出日志到内存缓冲区中,当一定的条件满足的时候(如某种关键字的日志信息)再将缓冲区中的日志输出
- SocketHandler: 输出日志到网络 socket 中
- StreamHandler: 输出日志到输入输出流对象中
同时 JDK 日志框架也不失其灵活性,你可以定制自己所需要的 Handler,将日志按照自定义的需求输出到不同的位置,同时 Formatter,Level 类都可以自定义扩展,下面就详细叙述如何自定义扩展这些组件。
自定义日志 Handler
所有的 Handler 类都是继承自 java.util.logging.Handler 抽象类,该类结构图如 图 2 所示。
图 2 Handler 类图
由该类图可见,Handler 抽象类提供了抽象接口:publish, flush 和 close 。这些接口提供了日志输出的基本功能。同时 Handler 类保存了 Formatter,Filter 和 Level 对象用来控制日志输出。因此,编写自定义的 Handler 类需要如下步骤:
- 继承 Handler 抽象类
- 实现 publish,flush 和 close 方法。其中 publish 方法是用于发布一条日志记录。 flush 方法是清空内存缓冲区。 close 方法是当应用程序关闭的时候,释放该 Handler 类所申请的资源(如文件,socket 等)
- 设置默认的 Formatter,Filter 和 Level 对象。必要的时候,可以在类的初始化时候读取配置文件来设置这些参数。
一个典型的自定义 Handler 类实现如清单 1 所示。
清单 1 自定义 Handler 类
public class MyHandler extends Handler { private boolean doneHeader = false; public MyHandler() { setLevel(Level.INFO); setFilter(null); setFormatter(new SimpleFormatter()); } _cnnew1@Override public void close() throws SecurityException { if (!doneHeader) { output(getFormatter().getHead(this)); doneHeader = true; } output(getFormatter().getTail(this)); flush(); } @Override public void flush() { // 清空缓冲区 } @Override public void publish(LogRecord record) { if (!isLoggable(record)) { return; } String msg = getFormatter().format(record); try { if (!doneHeader ) { output(getFormatter().getHead(this)); doneHeader = true; } output(msg); } catch (Exception ex) { reportError(null, ex, ErrorManager.WRITE_FAILURE); } } private void output(String message) { // 实现日志输出 } }
这里 reportError 方法是将日志类中的错误信息输出到外界,这个是由 ErrorManager 类实现的,ErrorManager 类负责记录日志框架中 Handler 的错误,一般情况下是将该错误打印到控制台中。具体的每条日志消息被 JDK 日志框架封装成 LogRecord 对象,该类部分定义如 清单 2 所示。
清单 2 LogRecord 类定义
public class LogRecord implements java.io.Serializable { public String getLoggerName(); public void setLoggerName(String name); public ResourceBundle getResourceBundle(); public void setResourceBundle(ResourceBundle bundle); public Level getLevel(); public void setLevel(Level level); public String getMessage(); public void setMessage(String message); public Object[] getParameters(); public void setParameters(Object parameters[]); public int getThreadID(); public void setThreadID(int threadID); public long getMillis(); public void setMillis(long millis); public Throwable getThrown(); public void setThrown(Throwable thrown); ... }
由清单 2 可见,LogRecord 类包含了一个日志消息的级别、消息文本、时间、参数、线程等等所有的信息,这些都交给 Handler,Formatter 和 Filter 这些对象来处理。同时该类也是可序列化的,可以序列化到网络和文件中。该类还可以和一个 ResourceBundle 对象绑定,实现消息字符串的本地化处理。
本节描述了一个典型的自定义的 Handler 类的实现。在本文后面部分将会有一个实际的例子来介绍如何实现一个 STAF 日志处理类。
自定义日志 Formatter
日志可以被格式化为一定格式的文本,也可以成为 XML 或者 HTML 这样标准的格式。这取决于 Formatter 类的具体实现。 Formatter 抽象类提供了 format 成员函数用于扩展。一个典型的自定义 Formatter 类实现如清单 3 所示:
清单 3 LogRecord 类定义
public class MyFormatter extends Formatter { private final String lineSeparator = System.getProperty("line.separator"); @Override public String format(LogRecord record) { StringBuffer sb = new StringBuffer(); String message = formatMessage(record); sb.append(record.getLevel().getLocalizedName()); sb.append(message); sb.append(lineSeparator); if (record.getThrown() != null) { try { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); record.getThrown().printStackTrace(pw); pw.close(); sb.append(sw.toString()); } catch (Exception ex) { } } return sb.toString(); } }
其中 formatMessage 方法提供了默认的将日志记录本地化和格式化的方法。它还能支持 java.text 风格的文本格式化,这只需要在调用 Logger 对象的 setMessage 方法设定 java.text 风格的格式字符串,同时通过 setParameters 方法设置参数,这样 formatMessage 将会根据所设置的 java.text 风格的格式字符串来格式化日志消息。总之,formatMessage 方法方便了子类格式化字符串。使子类只需要定义输出文本的格式而无需考虑本地化等问题。
自定义日志消息级别
JDK 日志框架默认提供了 SEVERE,WARNING,INFO,CONFIG,FINE,FINER,FINEST 这几种日志级别。如果我们需要定义更多的日志级别,只需要继承 java.util.logging.Level 类,然后将自定义的级别作为静态成员变量声明即可。一个典型的自定义的消息类如清单 4 所示。
清单 4 自定义 Level 类
public class MyLevel extends Level { protected MyLevel(String name, int value) { super(name, value); } public static final Level Level1 = new MyLevel("Level1", 123); ... // 其他自定义级别 }
权重值 value 是一个整型数。在默认的 JDK 日志级别中,SEVERE 的权重是 1000,FINEST 是 300,可以根据具体的需求来定义每个自定义级别的权重。例如在 WARNING 和 INFO 级别中加入一个新的级别,该级别的权重必须介于 800 到 900 之间。
自由的日志配置
和其他日志框架一样,JDK 日志框架同样提供了强大的日志配置功能。你既可以通过代码进行动态配置,也可以通过配置文件来实现自由灵活的配置。通过代码动态配置,应用程序可以实现在运行过程中改变日志类的配置,动态地改变不同的配置组合。一个简单的动态配置代码如清单 5 所示。
清单 5 动态配置 Logger 对象
public static void main(String[] args){ Handler fh = new FileHandler("%t/wombat.log"); Logger.getLogger("logname").addHandler(fh); Logger.getLogger("com.wombat").setLevel("com.wombat",Level.FINEST); ... }
配置文件的配置方法则同样灵活多变。它主要是在应用程序启动时根据一个指定的配置文件来设置日志对象。在配置文件中,日志对象是由其名称来标识的。一个典型的日志配置文件如清单 6 所示。
清单 6 JDK Logger 配置文件
# 设置日志对象的 Handler,日志对象的名称是 com.xyz.foo com.xyz.foo.handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler # 设置日志对象的基本输出级别 com.xyz.foo.level = INFO #FileHandler 只允许输出 SEVERE 以上级别的日志 java.util.logging.ConsoleHandler.level = SEVERE #ConsoleHandler 允许输出 INFO 以上级别的日志 java.util.logging.ConsoleHandler.level = INFO
当设置好一个日志配置文件后,在 java 程序的启动参数中,我们可以通过添加 -Djava.util.logging.config.file 参数来定义配置文件路径,一个典型的 java 命令行如下:
java -Djava.util.logging.config.file=logger.properties -cp . Mainclass
我们也可以在应用程序中声明自定义的 Handler,Formatter,Level 等组件,这只需要这些自定义组件能够在 classpath 中找到即可。
实例——结合 STAF 日志服务
STAF(Software Testing Automation Framework)是一个自动化软件测试框架,它可以实现分布式的自动化软件测试管理。我们可以应用 STAF 库的 Java API 来做基于 STAF 框架的应用,同时 STAF 同时也提供了日志服务。其日志服务是用来记录自动化测试流程中的信息,方便在 24x7 的自动化测试中记录自动化测试的操作,便于发现潜在的自动化测试管理脚本的问题。
既然我们可以用 STAF 的 Java API 来做基于 STAF 的应用,我们也可以将 JDK 的日志框架同 STAF 的日志服务接口结合起来。 STAF 的日志服务的 Java 接口定义如清单 7 所示:
清单 7 STAFLog 类定义
public class STAFLog { public STAFLog(String logType, String logName, STAFHandle handle); public STAFResult log(int level, String msg) // Log type constants public static STAFResult log(STAFHandle theHandle, String logType, String logName, int level, String msg) public String getName(); public String getLogType(); public int getMonitorMask(); ... //other methods }
从清单 7 我们可以看出,STAFLog 类提供了方法可以将日志信息存储到 STAF 的日志库中, 这个日志库既可以是本地的文件,也可以是另一个 STAF 服务器上的日志库。这是通过本地 STAF 服务器的配置来决定的。而 STAFLog.log() 方法只用于记录日志信息。
将 STAF 日志服务的 Java API 同 JDK 日志框架结合起来需要做如下步骤:
创建 STAF 日志 Handler 类
该类封装了 STAF 日志服务 API 的接口。同时 STAF 的 Java API 需要一个全局的 STAFHandle 对象,用来表示本地的 STAF 服务句柄。这个可以通过建立一个静态的 STAFHandle 对象即可。其代码如下所示,我们定义了一个 STAFHandler 类如清单 8 所示。
清单 8 STAFHandler 类实现
import java.util.logging.*; import com.ibm.staf.wrapper.STAFLog; public class STAFHandler extends Handler { private String logName; private static STAFHandle stafHandle = null; public STAFHandler(String name) { configure(); logName = name; } public STAFHandler() { configure(); } @Override public void close() throws SecurityException { if (stafHandle != null){ try { stafHandle.unRegister(); } catch (STAFException e) { //ignore } } } @Override public void flush() { //nothing } @Override public void publish(LogRecord record) { if (!isLoggable(record)) { return; } String msg; try { msg = getFormatter().format(record); } catch (Exception ex) { reportError(null, ex, ErrorManager.FORMAT_FAILURE); return; } try { STAFLog.log(stafHandle, STAFLog.MACHINE, logName, record.getLevel().getName(), msg); } catch (Exception ex) { reportError(null, ex, ErrorManager.WRITE_FAILURE); } ...
在实现 STAFHandler 类时有以下几个要点:
- 由于 STAF API 的调用时需要一个 STAFHandle 的对象来代表本地的 STAF 服务,在该类中声明了一个全局变量用来存储 STAFHandle 。
- close 方法是用来清理系统资源的,上述代码的 close 方法中释放了全局变量 STAFHandle 对象。
- publish 方法就是获得格式化后的消息后,直接调用 STAF 的日志 API 将日志发送到 STAF 服务中。
但到目前为止,我们还没有给 STAFHandler 类添加一个配置的代码,使之可以支持配置文件。下面我们定义了一个函数 configure,其代码如清单 9 所示。
清单 9 配置函数实现
private void configure() { if (stafHandle == null) { try { stafHandle = new STAFHandle("my application"); } catch (STAFException e) { reportError("registe staf handle error", e, ErrorManager.OPEN_FAILURE); } } LogManager manager = LogManager.getLogManager(); String cname = getClass().getName(); //set staf log name logName = manager.getProperty(cname + ".name"); if (logName == null) logName = "demo.staflog"; //set formatter String sformatter = manager.getProperty(cname + ".formatter"); Formatter formatter = null; if (sformatter != null) { try { formatter = (Formatter)Class.forName(sformatter).newInstance(); } catch (Exception e) { //ignore } } setFormatter(formatter == null? new STAFFormatter() : formatter); //set level String sLevel = manager.getProperty(cname + ".level"); Level level = null; if (sLevel != null) { try { level = STAFLevel.parse(sLevel); } catch (Exception e) { //ignore } } setLevel(level == null? STAFLevel.DEBUG : level); }
在实现配置文件支持的代码中,有以下几个要点:
- STAF API 的初始化需要注册 STAFHandle 对象。而且该注册只能执行一次。我们根据全局变量 stafHandle 的值来决定是否注册该对象。
- JDK 的日志框架有一个全局的 singleton 管理类 STAFManager,该类用于管理日志类,并提供了读取日志配置文件的成员函数 getProperty 。在上述的代码中,我们通过 STAFManager.getProperty 方法,从日志配置文件中读取 STAFHandler 对象所设置的 Formatter 类名,然后通过反射生成一个新的 Formatter 对象,设置到 Handler 对象中。
- 对于日志级别也是通过 STAFManager.getProperty 方法。需要注意的是由于我们的日志级别是自定义的级别,所以 Level 对象是由我们自定义的 Level 类 STAFLevel 来生成的。
- 我们也能定义自己需要的属性。比如清单 9 中我们定义了一个 .name 属性,用来存储 STAF 日志名称,通过 getProperty 函数从配置文件中读取 .name 属性。
创建一个适合 STAF 日志的 Formatter 类
由于 STAF 日志服务无需特殊的格式,我们只需要定义一个普通文本格式的 Formatter 即可。其代码如清单 10 所示,注意这里考虑了如果记录了一个异常对象的情况,将异常对象的 stack 打印到字符串中添加到消息文本中。
清单 10. STAFFormatter 实现
import java.io.*; import java.util.logging.*; public class STAFFormatter extends Formatter { private final String lineSeparator = System.getProperty("line.separator"); @Override public String format(LogRecord record) { StringBuffer sb = new StringBuffer(); String message = formatMessage(record); sb.append(message); sb.append(lineSeparator); if (record.getThrown() != null) { try { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); record.getThrown().printStackTrace(pw); pw.close(); sb.append(sw.toString()); } catch (Exception ex) { } } return sb.toString(); } }
创建对应于 STAF 日志级别的 Level 对象
这是由于 STAFLog 有着不同的日志消息级别,它包括 Fatal, Error, Warning, Info, Tracer, Debug 等级别,有些是 JDK 日志框架已有的级别,有些则不是。我们需要增加新的 Level 对象来满足 STAFLog 的需求。一个新的 Level 类:STAFLevel 定义如清单 11 所示。
清单 11 自定义 STAFLevel
import java.util.logging.Level; public class STAFLevel extends Level { protected STAFLevel(String name, int value) { super(name, value); } protected STAFLevel(String name, int value, String resourceBundleName) { super(name, value, resourceBundleName); } public static final Level FATAL = new STAFLevel("FATAL",980); public static final Level ERROR = new STAFLevel("ERROR",980); public static final Level TRACE = new STAFLevel("TRACE", 790); public static final Level DEBUG = new STAFLevel("DEBUG", 690); }
清单 11 定义了 FATAL,ERROR,TRACE 和 DEBUG 级别。这就和 STAFLog 中的部分级别一一对应起来了。
将一切组合起来
清单 12 描述了如何在一段实际的代码中将 STAF 日志处理类和 JDK 日志类结合起来。 从清单 12 可以看出,该实例默认指定输出到 STAF 日志服务的日志名称为“ staflogger ”。然后通过动态配置的方法来设定 Handler,Level 和 Formatter 。最后在调用 JDK 的日志对象的 log 方法记录了 4 种自定义级别的日志。
清单 12 一个完整的例子
package demo.staflog; import java.util.logging.Logger; public class STAFLoggerTest { public static void main(String[] args) { Logger logger = Logger.getLogger(STAFLoggerTest.class.getName()); logger.setUseParentHandlers(false); logger.setLevel(STAFLevel.DEBUG); STAFHandler stafHandler = new STAFHandler("staflogger"); stafHandler.setLevel(STAFLevel.DEBUG); stafHandler.setFormatter(new STAFFormatter()); logger.addHandler(stafHandler); //log logger.log(STAFLevel.DEBUG, "debug log"); logger.log(STAFLevel.FATAL, "fatal log"); logger.log(STAFLevel.ERROR, "error log"); logger.log(STAFLevel.TRACE, "trace log"); } }
但我们也可以将这些代码改为配置文件的方式,其配置文件如清单 13 所示:
清单 13 STAFLog 类定义
# 设置日志对象的 Handler demo.staflog.STAFLoggerTest.handlers= demo.staflog.STAFHandler demo.staflog.STAFLoggerTest.level = DEBUG # 取消发送日志到父 Logger 对象 demo.staflog.STAFLoggerTest.useParentHandlers = FALSE # 设置 Handler 的名称,输出级别和格式化对象 demo.staflog.STAFHandler.name= staflogger demo.staflog.STAFHandler.level = DEBUG demo.staflog.STAFHandler.formatter = demo.staflog.STAFFormatter
这样代码可以简化为清单 14 。
清单 14 STAFLog 类定义
public class STAFLoggerTest { private static Level defaultLevel = STAFLevel.DEBUG; public static void main(String[] args) { //log logger.log(STAFLevel.DEBUG, "debug log"); logger.log(STAFLevel.FATAL, "fatal log"); logger.log(STAFLevel.ERROR, "error log"); logger.log(STAFLevel.TRACE, "trace log"); } }
配置文件的方式相对于动态配置的方式更加灵活,因为这无需改变和重新编译代码,只需要修改配置文件,就能修改日志中 Handler,Level 和 Formatter 的组合配置,这对于已经部署发布的软件而言,有着更为实际的意义。
当运行代码后,在命令行中输入 STAF 命令来显示 STAF 日志 staflogger:
mymachine:~ myname$ staf local log query machine mymachine logname staflogger Response -------- Date-Time Level Message ----------------- ----- ---------- 20081111-16:15:21 Debug debug log 20081111-16:15:21 Fatal fatal log 20081111-16:15:21 Error error log 20081111-16:15:21 Trace trace log
这显示了我们刚才在 Java 代码中记录的信息,它们已经被输出到 STAF 的日志服务中了。
结束语
JDK 日志框架简单灵活,它虽然比 log4j 出现的时期晚,但其功能并不比 log4j 少。而且 JDK 日志框架直接隶属于 JDK,被 Java 标准所支持而无需安装第三方库文件。本文介绍了 JDK 日志框架的结构,如何扩展 JDK 日志框架使之满足实际的项目需求。并以如何在 Java 程序中将日志输出到 STAF 的日志服务中为例,一步步描述了如何实现扩展 JDK 日志组件,使之和 STAF 日志服务结合到一起,同时如何创建灵活的配置文件来组合日志框架组件。希望本文可以给其他需要扩展 JDK 日志组件的开发者提供帮助。
相关推荐
- React 开发翻车现场!这 6 个救命技巧,90% 工程师居然现在才知道
-
前端圈最近都在卷React18新特性,可咱开发时踩的坑却一个比一个离谱!组件卡死、状态乱套、路由错乱...别担心!今天分享6个超实用的React实战技巧,让你轻松拿捏开发难题,代码直接...
- Web前端:React JS越来越受欢迎,它的主要优点为什么要使用它?
-
ReactJS是一个开源JavaScript库,用于为单页应用程序构建用户界面,它还为不同的移动应用程序提供视图层,并创建可重用的UI组件。 我们可以在Web应用程序的数据中创建特定的更改,而...
- 性能焦虑!前端人必看!5 个 React 组件优化神技! 颠覆你的认知!
-
在前端开发的赛道上,性能优化就像一场永不停歇的马拉松。作为前端工程师,你是否常常为React组件的性能问题头疼不已?页面加载缓慢、组件频繁重渲染,这些痛点分分钟让开发进度受阻。别担心!今天就来分享...
- React 实战必学!99% 工程师踩过的 5 大坑,3 招教你轻松破解
-
前端开发的小伙伴们,咱就是说,React现在可是前端界的“顶流明星”,热度一直居高不下!但用它开发项目的时候,是不是总有那么些瞬间,让你怀疑人生,对着屏幕疯狂抓头发?别慌!今天就给大家分享几个超实...
- 惬意!午间一道 React 题,轻松拿捏前端面试小技巧
-
忙碌了一上午,眼睛酸涩、脑子发懵?别急着刷短视频“放空”,不如花几分钟和我一起“品尝”一道React面试题小甜点!就像在阳光洒满窗台的午后,泡一杯热茶,惬意又能悄悄涨知识,何乐而不为?最近,...
- 一起深入盘点 2025 年 React 发展的 10个趋势?
-
大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!1.React服务器组件React服务...
- 前端掉坑血泪史!4 个 React 性能优化绝招让页面秒开
-
在前端圈子里摸爬滚打这么多年,我发现React开发时踩坑的经历大家都大同小异。页面加载慢、组件频繁重渲染、状态管理混乱……这些痛点,相信不少前端工程师都感同身受。别愁!今天就给大家分享4个超...
- 前端人崩溃瞬间!5 招 React 实战技巧让项目起死回生
-
有没有在写React项目时,遇到页面卡顿到怀疑人生、数据更新不及时、代码逻辑混乱到无从下手的情况?别慌!作为摸爬滚打多年的前端老炮,今天就把5个救命级的React实战技巧倾囊相授,帮你轻松...
- 8.3K star!React Bits,让你拥有全网几乎所有动画效果
-
前端开源项目101专栏:一个能让你更快接触到高质量开源项目的地方。我会探索分享精选101个高质量的开源项目。这是系列的第7篇文章,分享一套拥有计划全网所有动画效果,且创意最丰富的动画React组...
- 开始学习React - 概览和演示教程
-
#头条创作挑战赛#本文同步本人掘金平台的原创翻译:https://juejin.cn/post/6844903823085944846当我刚开始学习JavaScript的时候,我就听说了React,但...
- 阿里AI工具Web Dev上线!一句话生成React网页
-
5月11日,阿里巴巴推出全新AI工具“WebDev”,支持用户通过一句话指令生成网页应用。该工具集成HTML、CSS、JavaScript三大前端核心技术,并统一采用React框架实现,可在数秒内创...
- JS流行框架/库排名Top100,看看你熟知的Js排第几
-
权威的JavaScript趋势榜stats.js.org每15分钟根据github上的stars和forks总数实时汇总出JavaScript开源项目的流行度排名,一起来看看你所熟知的项目排名第几...
- 新手如何搭建个人网站
-
ElementUl是饿了么前端团队推出的桌面端UI框架,具有是简洁、直观、强悍和低学习成本等优势,非常适合初学者使用。因此,本次项目使用ElementUI框架来完成个人博客的主体开发,欢迎大家讨论...
- 站在巨人肩膀上的 .NET 通用权限开发框架:Admin.NET
-
站在巨人肩膀上的.NET通用权限开发框架Admin.NET是一个面向.NET程序员的低代码平台,java平台类似的框架有ruoyi,芋道,JeelowCode等。这类框架普遍采用前后端分离的开发技...
- Python+selenium自动化之判定元素是否存在
-
在测试过程中,我碰到过这类的问题,使用find_element却找不到某个元素而产生异常,这就需要在操作某个元素之前判定该元素是否存在,而selenium中没有判定元素是否存在的方法,或者判定相同的元...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- React 开发翻车现场!这 6 个救命技巧,90% 工程师居然现在才知道
- Web前端:React JS越来越受欢迎,它的主要优点为什么要使用它?
- 性能焦虑!前端人必看!5 个 React 组件优化神技! 颠覆你的认知!
- React 实战必学!99% 工程师踩过的 5 大坑,3 招教你轻松破解
- 惬意!午间一道 React 题,轻松拿捏前端面试小技巧
- 一起深入盘点 2025 年 React 发展的 10个趋势?
- 前端掉坑血泪史!4 个 React 性能优化绝招让页面秒开
- 前端人崩溃瞬间!5 招 React 实战技巧让项目起死回生
- 8.3K star!React Bits,让你拥有全网几乎所有动画效果
- 开始学习React - 概览和演示教程
- 标签列表
-
- MVC框架 (46)
- spring框架 (46)
- 框架图 (58)
- bootstrap框架 (43)
- flask框架 (53)
- quartz框架 (51)
- abp框架 (47)
- jpa框架 (47)
- laravel框架 (46)
- express框架 (43)
- springmvc框架 (49)
- 分布式事务框架 (65)
- scrapy框架 (56)
- shiro框架 (61)
- 定时任务框架 (56)
- grpc框架 (55)
- ppt框架 (48)
- 内联框架 (52)
- winform框架 (46)
- gui框架 (44)
- cad怎么画框架 (58)
- ps怎么画框架 (47)
- ssm框架实现登录注册 (49)
- oracle字符串长度 (48)
- oracle提交事务 (47)