??xml version="1.0" encoding="utf-8" standalone="yes"?>
ASM 是一?Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进?class 文gQ也可以在类被加载入 Java 虚拟Z前动态改变类行ؓ。Java class 被存储在严格格式定义?.class 文g里,q些cL件拥有够的元数据来解析cM的所有元素:cdU、方法、属性以?Java 字节码(指oQ。ASM 从类文g中读入信息后Q能够改变类行ؓQ分析类信息Q甚臌够根据用戯求生成新cR?/p>
?BCEL ?SERL 不同QASM 提供了更为现代的~程模型。对?ASM 来说QJava class 被描qCؓ一|Q?“Visitor” 模式遍历整个二进制结构;事g驱动的处理方式得用户只需要关注于对其~程有意义的部分Q而不必了?Java cL件格式的所有细节:ASM 框架提供了默认的 “response taker”处理q一切?/p>
动态生?Java cM AOP 密切相关的。AOP 的初衷在于Y件设计世界中存在q么一cM码,零散而又耦合Q零散是׃一些公有的功能Q诸如著名的 log 例子Q分散在所有模块之中;同时改变 log 功能又会影响到所有的模块。出现这L~陷Q很大程度上是由于传l的 面向对象~程注重以承关pMؓ代表?#8220;U向”关系Q而对于拥有相同功能或者说斚w QAspectQ的模块之间?#8220;横向”关系不能很好地表达。例如,目前有一个既有的银行理pȝQ包?Bank、Customer、Account、Invoice {对象,现在要加入一个安全检查模块, 对已有类的所有操作之前都必须q行一ơ安全检查?/p>
?1. ASM – AOP
然?Bank、Customer、Account、Invoice 是代表不同的事务Q派生自不同的父c,很难在高层上加入关于 Security Checker 的共有功能。对于没有多l承?Java 来说Q更是如此。传l的解决Ҏ是?Decorator 模式Q它可以在一定程度上改善耦合Q而功能仍旧是分散?—?每个需?Security Checker 的类都必要z一?DecoratorQ每个需?Security Checker 的方法都要被包装QwrapQ。下面我们以 Account
cMؓ例看一?DecoratorQ?/p>
首先Q我们有一?SecurityChecker
c,光态方?checkSecurity
执行安全查功能:
public class SecurityChecker { public static void checkSecurity() { System.out.println("SecurityChecker.checkSecurity ..."); //TODO real security check } } |
另一个是 Account
c:
public class Account { public void operation() { System.out.println("operation..."); //TODO real operation } } |
若想?operation
加入?SecurityCheck.checkSecurity()
调用Q标准的 Decorator 需要先定义一?Account
cȝ接口Q?/p>
public interface Account { void operation(); } |
然后把原来的 Account
cd义ؓ一个实现类Q?/p>
public class AccountImpl extends Account{ public void operation() { System.out.println("operation..."); //TODO real operation } } |
定义一?Account
cȝ DecoratorQƈ包装 operation
ҎQ?/p>
public class AccountWithSecurityCheck implements Account { private Account account; public AccountWithSecurityCheck (Account account) { this.account = account; } public void operation() { SecurityChecker.checkSecurity(); account.operation(); } } |
在这个简单的例子里,攚w一个类的一个方法还好,如果是变动整个模块,Decorator 很快׃演化成另一个噩梦。动态改?Java cd是要解决 AOP 的问题,提供一U得到系l支持的可编E的ҎQ自动化地生成或者增?Java 代码。这U技术已l广泛应用于最新的 Java 框架内,?HibernateQSpring {?/p>
最直接的改?Java cȝҎ莫过于直接改?class 文g。Java 规范详细说明了class 文g的格式,直接~辑字节码确实可以改?Java cȝ行ؓ。直C天,q有一?Java 高手们用最原始的工P?UltraEdit q样的编辑器?class 文g动手术。是的,q是最直接的方法,但是要求使用者对 Java class 文g的格式了熟于心:心地推出x造的函数相对文g首部的偏U量Q同旉新计?class 文g的校验码以通过 Java 虚拟机的安全机制?/p>
Java 5 中提供的 Instrument 包也可以提供cM的功能:启动时往 Java 虚拟Z挂上一个用户定义的 hook E序Q可以在装入特定cȝ时候改变特定类的字节码Q从而改变该cȝ行ؓ。但是其~点也是明显的:
ClassFileTransformer. transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer)
Q还?Instrument.redefineClasses(ClassDefinition[] definitions)
Q都必须提供?Java cȝ字节码。也是_同直接改?class 文g一P使用 Instrument 也必M解想攚w的Ҏ相对c首部的偏移量,才能在适当的位|上插入新的代码?管 Instrument 可以攚w类Q但事实上,Instrument 更适用于监控和控制虚拟机的行ؓ?/p>
一U比较理想且行的方法是使用 java.lang.ref.proxy
。我们仍旧用上面的例子Q给 Account
cd?checkSecurity 功能:
首先QProxy ~程是面向接口的。下面我们会看到QProxy q不负责实例化对象,?Decorator 模式一P要把 Account
定义成一个接口,然后?AccountImpl
里实?Account
接口Q接着实现一?InvocationHandler
Account
Ҏ被调用的时候,虚拟机都会实际调用这?InvocationHandler
?invoke
ҎQ?/p>
class SecurityProxyInvocationHandler implements InvocationHandler { private Object proxyedObject; public SecurityProxyInvocationHandler(Object o) { proxyedObject = o; } public Object invoke(Object object, Method method, Object[] arguments) throws Throwable { if (object instanceof Account && method.getName().equals("opertaion")) { SecurityChecker.checkSecurity(); } return method.invoke(proxyedObject, arguments); } } |
最后,在应用程序中指定 InvocationHandler
生成代理对象Q?/p>
public static void main(String[] args) { Account account = (Account) Proxy.newProxyInstance( Account.class.getClassLoader(), new Class[] { Account.class }, new SecurityProxyInvocationHandler(new AccountImpl()) ); account.function(); } |
其不之处在于:
Proxy.newProxyInstance
生成的是实现 Account
接口的对象而不?AccountImpl
的子cR这对于软g架构设计Q尤其对于既有Y件系l是有一定掣肘的?
ASM 能够通过攚w既有类Q直接生成需要的代码。增强的代码是硬~码在新生成的类文g内部的,没有反射带来性能上的付出。同ӞASM ?Proxy ~程不同Q不需要ؓ增强代码而新定义一个接口,生成的代码可以覆盖原来的c,或者是原始cȝ子类。它是一个普通的 Java c而不?proxy c,甚至可以在应用程序的cL架中拥有自己的位|,z自己的子cR?/p>
相比于其他流行的 Java 字节码操U工PASM 更小更快。ASM hcM?BCEL 或?SERP 的功能,而只?33k 大小Q而后者分别有 350k ?150k。同Ӟ同样c{换的负蝲Q如?ASM ?60% 的话QBCEL 需?700%Q?SERP 需?1100% 或者更多?/p>
ASM 已经被广泛应用于一pd Java 目QAspectWerkz、AspectJ、BEA WebLogic、IBM AUS、OracleBerkleyDB、Oracle TopLink、Terracotta、RIFE、EclipseME、Proactive、Speedo、Fractal、EasyBeans、BeanShell、Groovy、Jamaica、CGLIB、dynaop、Cobertura、JDBCPersistence、JiP、SonarJ、Substance L&F、Retrotranslator {。Hibernate ?Spring 也通过 cglibQ另一个更高层一些的自动代码生成工具使用?ASM?/p>
![]() ![]() |
![]()
|
所?Java cLӞ是通常?javac ~译器生的 .class 文g。这些文件具有严格定义的格式。ؓ了更好的理解 ASMQ首先对 Java cL件格式作一点简单的介绍。Java 源文件经q?javac ~译器编译之后,会生成对应的二q制文gQ如下图所C)。每个合法的 Java cL仉具备_的定义,而正是这U精的定义Q才使得 Java 虚拟机得以正读取和解释所有的 Java cL件?/p>
?2. ASM – Javac 程
Java cL件是 8 位字节的二进制流。数据项按顺序存储在 class 文g中,盔R的项之间没有间隔Q这使得 class 文g变得紧凑Q减存储空间。在 Java cL件中包含了许多大不同的,׃每一的l构都有严格规定Q这使得 class 文g能够从头到尾被顺利地解析。下面让我们来看一?Java cL件的内部l构Q以便对此有个大致的认识?/p>
例如Q一个最单的 Hello World E序Q?/p>
public class HelloWorld { public static void main(String[] args) { System.out.println("Hello world"); } } |
l过 javac ~译后,得到的类文g大致是:
从上图中可以看到Q一?Java cL件大致可以归?10 个项Q?/p>
事实上,使用 ASM 动态生成类Q不需要像早年?class hacker 一P熟知 class 文g的每一D,以及它们的功能、长度、偏U量以及~码方式。ASM 会给我们照顾好这一切的Q我们只要告?ASM 要改动什么就可以?—?当然Q我们首先得知道要改什么:对类文g格式了解的越多,我们p更好C?ASM q个利器?/p>
![]() ![]() |
![]()
|
ASM 通过树这U数据结构来表示复杂的字节码l构Qƈ利用 Push 模型来对树进行遍历,在遍历过E中对字节码q行修改。所谓的 Push 模型cM于简单的 Visitor 设计模式Q因为需要处理字节码l构是固定的Q所以不需要专门抽象出一U?Vistable 接口Q而只需要提?Visitor 接口。所?Visitor 模式?Iterator 模式有点cMQ它们都被用来遍历一些复杂的数据l构。Visitor 相当于用h出的代表Q深入到法内部Q由法安排讉K行程。Visitor 代表可以更换Q但对算法流E无法干涉,因此是被动的Q这也是它和 Iterator 模式q户主动调遣算法方式的最大的区别?/p>
?ASM 中,提供了一?ClassReader
c,q个cd以直接由字节数组或由 class 文g间接的获得字节码数据Q它能正的分析字节码,构徏出抽象的树在内存中表C字节码。它会调?accept
ҎQ这个方法接受一个实C ClassVisitor
接口的对象实例作为参敎ͼ然后依次调用 ClassVisitor
接口的各个方法。字节码I间上的偏移被{换成 visit 事g旉上调用的先后Q所?visit 事g是指对各U不?visit 函数的调用,ClassReader
知道如何调用各种 visit 函数。在q个q程中用h法对操作q行q涉Q所以遍历的法是确定的Q用户可以做的是提供不同?Visitor 来对字节码树q行不同的修攏V?code>ClassVisitor 会生一些子q程Q比?visitMethod
会返回一个实?MethordVisitor
接口的实例,visitField
会返回一个实?FieldVisitor
接口的实例,完成子过E后控制q回到父q程Ql访问下一节点。因此对?ClassReader
来说Q其内部序讉K是有一定要求的。实际上用户q可以不通过 ClassReader
c,自行手工控制q个程Q只要按照一定的序Q各?visit 事g被先后正的调用Q最后就能生成可以被正确加蝲的字节码。当然获得更大灵zL的同时也加大了调整字节码的复杂度?/p>
各个 ClassVisitor
通过职责?QChain-of-responsibilityQ?模式Q可以非常简单的装对字节码的各U修改,而无d注字节码的字节偏U,因ؓq些实现l节对于用户都被隐藏了,用户要做的只是覆写相应的 visit 函数?/p>
ClassAdaptor
cdC ClassVisitor
接口所定义的所有函敎ͼ当新Z?ClassAdaptor
对象的时候,需要传入一个实C ClassVisitor
接口的对象,作ؓ职责链中的下一个访问?QVisitorQ,q些函数的默认实现就是简单的把调用委zq个对象Q然后依ơ传递下dŞ成职责链。当用户需要对字节码进行调整时Q只需?ClassAdaptor
cL生出一个子c,覆写需要修改的ҎQ完成相应功能后再把调用传递下厅R这P用户无需考虑字节偏移Q就可以很方便的控制字节码?/p>
每个 ClassAdaptor
cȝzcd以仅装单一功能Q比如删除某函数、修改字D可见性等{,然后再加入到职责链中Q这栯合更小Q重用的概率也更大,但代h产生很多对象,而且职责铄层次太长的话也会加大pȝ调用的开销Q用户需要在低耦合和高效率之间作出权衡。用户可以通过控制职责链中 visit 事g的过E,对类文gq行如下操作Q?/p>
删除cȝ字段、方法、指令:只需在职责链传递过E中中断委派Q不讉K相应?visit Ҏ卛_Q比如删除方法时只需直接q回 null
Q而不是返回由 visitMethod
Ҏq回?MethodVisitor
对象?/p>
class DelLoginClassAdapter extends ClassAdapter { public DelLoginClassAdapter(ClassVisitor cv) { super(cv); } public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) { if (name.equals("login")) { return null; } return cv.visitMethod(access, name, desc, signature, exceptions); } } |
修改cR字Dc方法的名字或修饰符Q在职责链传递过E中替换调用参数?/p>
class AccessClassAdapter extends ClassAdapter { public AccessClassAdapter(ClassVisitor cv) { super(cv); } public FieldVisitor visitField(final int access, final String name, final String desc, final String signature, final Object value) { int privateAccess = Opcodes.ACC_PRIVATE; return cv.visitField(privateAccess, name, desc, signature, value); } } |
增加新的cR方法、字D?/p>
ASM 的最l的目的是生成可以被正常装蝲?class 文gQ因此其框架l构为客h供了一个生成字节码的工L —?ClassWriter
。它实现?ClassVisitor
接口Q而且含有一?toByteArray()
函数Q返回生成的字节码的字节,字节流写回文g卛_生调整后的 class 文g。一般它都作责链的终点,把所?visit 事g的先后调用(旉上的先后Q,最l{换成字节码的位置的调_I间上的前后Q,如下例:
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); ClassAdaptor delLoginClassAdaptor = new DelLoginClassAdapter(classWriter); ClassAdaptor accessClassAdaptor = new AccessClassAdaptor(delLoginClassAdaptor); ClassReader classReader = new ClassReader(strFileName); classReader.accept(classAdapter, ClassReader.SKIP_DEBUG); |
lg所qͼASM 的时序图如下Q?/p>
?4. ASM – 时序?/strong>
![]() ![]() |
![]()
|
我们q是用上面的例子Q给 Account
cd?security check 的功能。与 proxy ~程不同QASM 不需要将 Account
声明成接口,Account
可以仍旧是一个实现类。ASM 直接在 Account
cM动手术,l?Account
cȝ operation
Ҏ首部加上?SecurityChecker.checkSecurity
的调用?/p>
首先Q我们将?ClassAdapter
l承一个类?code>ClassAdapter ?ASM 框架提供的一个默认类Q负责沟?ClassReader
?ClassWriter
。如果想要改?ClassReader
处读入的c,然后?ClassWriter
处输出,可以重写相应?ClassAdapter
函数。这里,Z改变 Account
cȝ operation
ҎQ我们将重写 visitMethdod
Ҏ?/p>
class AddSecurityCheckClassAdapter extends ClassAdapter{ public AddSecurityCheckClassAdapter(ClassVisitor cv) { //Responsechain 的下一?ClassVisitorQ这里我们将传入 ClassWriterQ? //负责改写后代码的输出 super(cv); } //重写 visitMethodQ访问到 "operation" ҎӞ //l出自定?MethodVisitorQ实际改写方法内? public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) { MethodVisitor mv = cv.visitMethod(access, name, desc, signature,exceptions); MethodVisitor wrappedMv = mv; if (mv != null) { //对于 "operation" Ҏ if (name.equals("operation")) { //使用自定?MethodVisitorQ实际改写方法内? wrappedMv = new AddSecurityCheckMethodAdapter(mv); } } return wrappedMv; } } |
下一步就是定义一个承自 MethodAdapter
?AddSecurityCheckMethodAdapter
Q在“operation
”Ҏ首部插入?SecurityChecker.checkSecurity()
的调用?/p>
class AddSecurityCheckMethodAdapter extends MethodAdapter { public AddSecurityCheckMethodAdapter(MethodVisitor mv) { super(mv); } public void visitCode() { visitMethodInsn(Opcodes.INVOKESTATIC, "SecurityChecker", "checkSecurity", "()V"); } } |
其中Q?code>ClassReader d每个Ҏ的首部时调用 visitCode()
Q在q个重写Ҏ里,我们?code>visitMethodInsn(Opcodes.INVOKESTATIC, "SecurityChecker","checkSecurity", "()V"); 插入了安全检查功能?/p>
最后,我们集成上面定义的 ClassAdapter
Q?code>ClassReader?code>ClassWriter 产生修改后的 Account
cL?
import java.io.File; import java.io.FileOutputStream; import org.objectweb.asm.*; public class Generator{ public static void main() throws Exception { ClassReader cr = new ClassReader("Account"); ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); ClassAdapter classAdapter = new AddSecurityCheckClassAdapter(cw); cr.accept(classAdapter, ClassReader.SKIP_DEBUG); byte[] data = cw.toByteArray(); File file = new File("Account.class"); FileOutputStream fout = new FileOutputStream(file); fout.write(data); fout.close(); } } |
执行完这D늨序后Q我们会得到一个新?Account.class 文gQ如果我们用下面代码:
public class Main { public static void main(String[] args) { Account account = new Account(); account.operation(); } } |
使用q个 AccountQ我们会得到下面的输出:
SecurityChecker.checkSecurity ... operation... |
也就是说Q在 Account
原来?operation
内容执行之前Q进行了 SecurityChecker.checkSecurity()
查?/p>
动态生成类攚w成原始c?Account 的子c?/span>
上面l出的例子是直接攚w?Account
cLw的Q从?Account
cȝ operation
Ҏ必须q行 checkSecurity 查。但事实上,我们有时仍希望保留原来的 Account
c,因此把生成类定义为原始类的子cL更符?AOP 原则的做法。下面介l如何将攚w后的类定义?Account
的子c?Account$EnhancedByASM
。其中主要有两项工作:
Account$EnhancedByASM
Q将其父cL定ؓ Account
?
Account
构造函数的调用??AddSecurityCheckClassAdapter
cMQ将重写 visit
ҎQ?/p>
public void visit(final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces) { String enhancedName = name + "$EnhancedByASM"; //改变cd? enhancedSuperName = name; //改变父类Q这里是”Account” super.visit(version, access, enhancedName, signature, enhancedSuperName, interfaces); } |
改进 visitMethod
ҎQ增加对构造函数的处理Q?/p>
public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) { MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions); MethodVisitor wrappedMv = mv; if (mv != null) { if (name.equals("operation")) { wrappedMv = new AddSecurityCheckMethodAdapter(mv); } else if (name.equals("<init>")) { wrappedMv = new ChangeToChildConstructorMethodAdapter(mv, enhancedSuperName); } } return wrappedMv; } |
q里 ChangeToChildConstructorMethodAdapter
负责把 Account
的构造函数改造成其子c?Account$EnhancedByASM
的构造函敎ͼ
class ChangeToChildConstructorMethodAdapter extends MethodAdapter { private String superClassName; public ChangeToChildConstructorMethodAdapter(MethodVisitor mv, String superClassName) { super(mv); this.superClassName = superClassName; } public void visitMethodInsn(int opcode, String owner, String name, String desc) { //调用父类的构造函数时 if (opcode == Opcodes.INVOKESPECIAL && name.equals("<init>")) { owner = superClassName; } super.visitMethodInsn(opcode, owner, name, desc);//改写父类为superClassName } } |
最后演CZ下如何在q行时生ƈ装入产生?Account$EnhancedByASM
?我们定义一?Util
c,作ؓ一个类工厂负责产生有安全检查的 Account
c:
public class SecureAccountGenerator { private static AccountGeneratorClassLoader classLoader = new AccountGeneratorClassLoade(); private static Class secureAccountClass; public Account generateSecureAccount() throws ClassFormatError, InstantiationException, IllegalAccessException { if (null == secureAccountClass) { ClassReader cr = new ClassReader("Account"); ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); ClassAdapter classAdapter = new AddSecurityCheckClassAdapter(cw); cr.accept(classAdapter, ClassReader.SKIP_DEBUG); byte[] data = cw.toByteArray(); secureAccountClass = classLoader.defineClassFromClassFile( "Account$EnhancedByASM",data); } return (Account) secureAccountClass.newInstance(); } private static class AccountGeneratorClassLoader extends ClassLoader { public Class defineClassFromClassFile(String className, byte[] classFile) throws ClassFormatError { return defineClass("Account$EnhancedByASM", classFile, 0, classFile.length()); } } } |
静态方?SecureAccountGenerator.generateSecureAccount()
在运行时动态生成一个加上了安全查的 Account
子类。著名的 Hibernate ?Spring 框架Q就是用这U技术实C AOP ?#8220;无损注入”?/p>
![]() ![]() |
![]()
|
最后,我们比较一?ASM 和其他实?AOP 的底层技术:
一、LOG4Jl成
LOG4J主要׃大组件组成:
. Logger: 军_什么日志信息应该被输出、什么日志信息应该被忽略Q?br />
. Appender: 指定日志信息应该输出C么地? q些地方可以是控制台、文件、网l设备;
. Layout: 指定日志信息的输出格式;
一个Logger可以有多个AppenderQ也是说日志信息可以同时输出到多个讑֤上,每个Appender对应
一ULayout(CZ见下??/p>
?nbsp; Appender1 → Layout
Q?nbsp;
Logger
H?
?nbsp; Appender2 → Layout
二、Loggerlg
1. Loggerlg提供的方法:
Loggerlg是LOG4J的核心组Ӟ它代表了Log4J的日志记录器Q它能够Ҏ志信息进行分cȝ选。它由org.apache.log4j.Loggercd玎ͼ提供了如下方法:
2. 在配|文件中配置Loggerlg
可在Log4J配置文g中配|自qLoggerlgQ示例:
log4j.logger.myLogger=WARN
以上代码定义了一个LoggerlgQ名UCؓmyLoggerQ日志别ؓWARN?br />
3. 日志U别U类Q?/p>
一共有五种Q别由高到低依ơ是Qfatal、error、warn、info、debug。获得Logger实例后,我们可调用以下方法之一输出日志信息Q?/p>
public void debug(Object message); //输出debugU别的日志信息;
public void info(Object message); //输出infoU别的日志信息;
public void warn(Object message); //输出warnU别的日志信息;
public void error(Object message); //输出errorU别的日志信息;
public void fatal(Object message); //输出fatalU别的日志信息;
public void log(Priority p, Object message);//输出参数Priority指定U别的日志信息;
以上Ҏ只有当它的别大于或{于Loggerlg配置的日志别时才调用。以前面我们配置的myLoggerZQ它的日志别ؓWARN, 那么在程序中Q它的warn()、error()、fatal()Ҏ会被执行。对于log()ҎQ只有当它的参数Priority指定的日志别大于或{于WARNӞ它才会被执行?/p>
4. Z么需要对日志q行分Q?br />
在写E序的时候,Z调试E序Q我们会在很多出错的地方输出大量的日志信息。当E序调试完,不需要这些信息时Q将E序中这些输出日志信息代码删除吗Q这栯时费力,对于大型E序几乎不可行。通过Ҏ志分U,假如不想输出WARNU别的日志信息,则Loggerlg的别调高即可,省时省心?/p>
5. Loggerlg的承?/p>
Log4J提供了一个root LoggerQ它是所有Loggerlg?#8220;先”,它永q存在,且不能通过名字索或引用Q通过Logger.getRootLogger()Ҏ取得它。配|root Logger代码Q?/p>
log4j.rootLogger=INFO,console
可在配置文g中方便地配置存在l承关系的LoggerlgQ凡是在W号“.”后面的组仉会成为在W号“.”前面的Loggerlg的子cR例如:
log4j.apache.myLogger=WARN
log4j.apache.myLogger.mySonLogger=,file
以上代码? mySonLogger是myLogger的子cLoggerlg。Loggerlg的承关p:
. 如果子类Loggerlg没有定义日志U别Q则承父cȝ日志U别;
. 如果子类Loggerlg定义了日志别,׃会承父cȝ日志U别;
. 黙认情况下,子类Loggerlg会承父cL有的AppenderQ把它们加入到自qAppener;
. 如果把子cLoggerlg的additivity标志设ؓfalseQ那么它׃会承父cAppender。additivity标志 默认gؓfalseQ?/p>
以上配置的三个Loggerl承关系CZ如图Q?br />
root Logger: 日志U别=INFO appender清单=console
↑
myLogger: 日志U别=WARN appender清单=null
↑
mySonLogger: 日志U别=null appender清单=file
q三个Loggerlg实际日志U别和Appender如下表:
Loggerlg 日志U别 Appender清单
root Logger INFO console
myLogger WARN console(l承)
mySonLogger WARN(l承) fileQconsole(l承)
三、Appenderlg
Appenderlg军_日志信息输出到什么地斏V支持以下目的地Q?br />
. 控制?Console);
. 文g(File);
. GUIlg(GUI component);
. 套接口服务器(Remote socket server);
. NT的事件记录器(NT Event Logger);
. UNIX Syslog守护q程(Remote UNIX Syslog daemon);
一个Logger可同时对应多个AppenderQ示例:myLogger配置二个Appender: 一个file, 一个是consoleQ?/p>
log4j.logger.myAppender=WARN,file,console
log4j.appender.file=org.apache.log4j.RollingFileAppender
log4j.appender.file.File=log.txt
log4j.apender.console=org.apache.log4j.ConsoleAppender
四、Layoutlg
Layoutlg军_日志输出格式Q有以下几种cdQ?br />
. org.apache.log4j.HTMLLayout(以HTML表格形式布局);
. org.apache.log4j.PatternLayout(可以灉|地指定布局模式);
. org.apache.log4j.SimpleLayout(包含日志信息的别和信息字符?;
. org.apache.log4j.TTCCLayout(包含日志产生的时间、线E和cd{信?;
为名UCؓconsole的Appender配置SimpleLayoutQ代码如下:
log4j.appender.console.layout=org.apache.log4j.SimpleLayout
输出日志格式如下Q?/p>
WARN - This is a log message from the myLogger
为名UCؓfile的Appender配置PatternLayoutQ代码如下:
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%t %p - %m%n
输出日志格式如下Q?/p>
THREAD-1 WARN - This is a log message from the myLogger
PatternLayout让开发者依照ConversionPattern定义输出格式。ConversionPattern中一些指定日志内容和格式的预定义W号说明如下Q?/p>
W号 描述
%r 自程序开始后消耗的毫秒?br />
%t 表示日志记录h生成的线E?br />
%p 表示日专语句的优先
%r 与日志请求相关的cd名称
%c 日志信息所在的cd
%m%n 表示日志信息的内?/p>
五、Log4J的基本用?/p>
1. 定义配置文g
Log4J支持二种配置文g格式QXML和Java属性文?采用“??#8221;形式)。以下ؓJava属性文?br />
格式配置文gQ?br />
. 配置Loggerlg
配置root Logger语法为:log4j.rootLogger=[priority],appenderName,appenderName,...
配置自定义Loggerlg语法为:log4j.logger.loggerName=[priority],appenderName,appenderName,...
其中Qpriority为日志别,可选值包括FATAL、ERROR、WARN、INFO、DEBUG、ALLQ?br /> appenderName指定AppenderlgQ可指定多个;
. 配置Appenderlg
配置日志信息输出目的地Appender, 语法为:
log4j.appender.appenderName=fully.ualified.name.of.appender.class
log4j.appender.appenderName.option1=value1
...
log4j.appender.appenderName.optionN=valueN
Log4J提供的Appender有以下几U:
a. org.apache.log4j.ConsoleAppender(控制?;
b. org.apache.log4j.FileAppender(文g);
c. org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文?;
d. org.apache.log4j.RollingFileAppender(文g大小到指定尺生一个新的文?;
e. org.apache.log4j.WriteAppender(日志信息以格式发送到L指定地方);
. 配置Layoutlg
配置Layoutlg语法为:
log4j.appender.appenderName.layout=fully.ualified.name.of.appender.class
log4j.appender.appenderName.layout.option1=value1
...
log4j.appender.appenderName.layout.optionN=valueN
下面Z配置文gCZQ文件名为log4j.propertiesQ?/p>
## LOGGERS ##
#configure root logger
log4j.rootLogger=INFO,console
#define a logger named myLogger
log4j.logger.myLogger=WARN
#define a second logger that is a child to myLogger
log4j.logger.myLogger.mySonLogger=,file
## APPENDERS ##
#define an appender named console, which is set to be a ConsoleAppender
log4j.appender.console=org.apache.log4j.ConsoleAppender
# define an appender named file, which is set to be a RollingFileAppender
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=log.txt
## LAYOUTS ##
# assian a SimpleLayout to console appender
log4j.appender.console.layout=org.apache.log4j.SimpleLayout
# assian a PatternLayout to file appender
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%t%p-%m%n
2. E序中用Log4j
. 获得日志记录器:
获得rootLoggerQLogger rootLogger=Logger.getRootLogger();
获得自定义LoggerQLogger myLogger = Logger.getLogger("log4j.logger.myLogger");
. d日志记录器,配置Log4J环境;
a. BasicConfigurator.configure(): 自动快速地使用默认Log4J环境;
b. Property.configurator.configure(String configFilename): d使用Java属性格式的配置文gq|Log4J环境Q?br />
c. DOMConfigurator.configure(String filename): dXML形式的配|文件ƈ配置LOG4J环境;
. 输出日志信息;
在程序代码中需要生成日志的地方Q调用Logger的各U输出日志方法输Z同别的日志Q例如:
myLogger.debug("Thie is a log message from the " + myLogger.getName());
下面Z使用Log4J的程序,E序名ؓTest.javaQ?/p>
E序q行l果?
WARN - Thie is a log message from the myLogger
ERROR - Thie is a log message from the myLogger
FATAL - Thie is a log message from the myLogger
WARN - Thie is a log message from the myLogger.mySonLogger
ERROR - Thie is a log message from the myLogger.mySonLogger
FATAL - Thie is a log message from the myLogger.mySonLogger
另在Test.class所在的目录下看C个log.txt文gQ内容如下:
WARN - Thie is a log message from the myLogger.mySonLogger
ERROR - Thie is a log message from the myLogger.mySonLogger
FATAL - Thie is a log message from the myLogger.mySonLogger
如将配置文glog4j.properties中语?/p>
log4j.logger.myLogger.mySonLogger=,file
改ؓ
log4j.logger.myLogger.mySonLogger=,file,console
再次q行E序Q结果如下:
WARN - Thie is a log message from the myLogger
ERROR - Thie is a log message from the myLogger
FATAL - Thie is a log message from the myLogger
WARN - Thie is a log message from the myLogger.mySonLogger
WARN - Thie is a log message from the myLogger.mySonLogger
ERROR - Thie is a log message from the myLogger.mySonLogger
ERROR - Thie is a log message from the myLogger.mySonLogger
FATAL - Thie is a log message from the myLogger.mySonLogger
FATAL - Thie is a log message from the myLogger.mySonLogger
mySonLogger的日志在控制C输出了二ơ,q是因ؓmySonLoggerl承了父cconsole AppenderQ?br /> 本n又定义了一个console Appender, 因而有二个console Appender?/p>
六、在web应用中用Log4J
创徏一个ServletQ在它初始化Ҏ中读取Log4J配置文gq|Log4J环境Q这个Servlet在Web应用?br /> 动时候被加蝲和初始化Q然后就可在其它Weblg中获取Logger对象q输出日志?/p>
1. 创徏用于配置Log4J环境的Servlet
该Servlet在web.xml中的配置如下Q?br />
2. 在login.jsp中输出日?br />
<%@page import="org.apache.log4j.Logger"%>
<html>
<head>
<title>login</title>
</head>
<body>
<%
Logger myLogger = Logger.getLogger("myLogger");
Logger mySonLogger = Logger.getLogger("myLogger.mySonLogger");
myLogger.debug("Thie is a log message from the " + myLogger.getName());
myLogger.info("Thie is a log message from the " + myLogger.getName());
myLogger.warn("Thie is a log message from the " + myLogger.getName());
myLogger.error("Thie is a log message from the " + myLogger.getName());
myLogger.fatal("Thie is a log message from the " + myLogger.getName());
mySonLogger.debug("Thie is a log message from the " + mySonLogger.getName());
mySonLogger.info("Thie is a log message from the " + mySonLogger.getName());
mySonLogger.warn("Thie is a log message from the " + mySonLogger.getName());
mySonLogger.error("Thie is a log message from the " + mySonLogger.getName());
mySonLogger.fatal("Thie is a log message from the " + mySonLogger.getName());
%>
<br>
<form name="loginForm" method="post" action="dispatcher">
username: <input type="text" name="username">
<br>
password: <input type="text" name="password">
<br>
<input type="submit" name="submit" value="submit">
</form>
</body>
</html>
3. 发布q行使用Log4J的web应用
1) Log4J的JAR文g拯至目录:<WEB应用所在目?gt;/WEB-INF/lib
2) 创徏Log4J的配|文件log4j.properties, 存放目录为:<WEB应用所在目?gt;/WEB-INF。内容同前面配置文gCZ?br />
3) ~译Log4JServlet, 存放至目录: <WEB应用所在目?gt;/WEB-INF/classes
4) 修改web.xml文gQ加入以下内容:
5) 启动服务器,讉Klogin.jsp面Q在服务器控制台上看到如下日志:
WARN - Thie is a log message from the myLogger
ERROR - Thie is a log message from the myLogger
FATAL - Thie is a log message from the myLogger
WARN - Thie is a log message from the myLogger.mySonLogger
ERROR - Thie is a log message from the myLogger.mySonLogger
FATAL - Thie is a log message from the myLogger.mySonLogger
另在<WEB应用所在目?gt;/WEB-INF目录下看C个log.txt文gQ内容如下:
WARN - Thie is a log message from the myLogger.mySonLogger
ERROR - Thie is a log message from the myLogger.mySonLogger
FATAL - Thie is a log message from the myLogger.mySonLogger