在static块和static方法中如何获取当前类及如何获取当前正在执行的方法

这是我自己在实际中遇到的一个问题,原问题是这样的,就是我有一个工具类 DbUtil,一看名称就知道是和数据库相关的,由于是工具类,所以里面的方法都是static的 ,但是又需要有关 jdbc 的 配置,总部至于硬编码吧,所以就想到了放在和当前类一个目录里,然后用getClass.getResourceAsStream 得到一个InputStream  再来初始化一个 Properties,用其load(InputStream) 来获得属性文件里有关jdbc的配置 ,这个时候才发现了问题,在 static中this不能用,那如何获得当前类的Class呢,在网上搜罗了一番, 没发现这个问题好的解决方案,但是却发现了另一个问题,就是如何在当前方法中获取当前方法名,这个,一听,似乎很不错哦,用过Log4j的朋友,应该知道Log4J是可以打印出来自己在哪个方法里的那行的,而且还可以知道当前类。停,到这应该是该停一下了,思考下,当前类名获取了,那么 Class.forName是做什么的,大脑里有点想法了吧,看网上介绍Log4J关于动态获取当前文件,类,方法,行号的原理,对JDK不同版本也不同了,下面就是Copy 的

一、问题的引入
我们在Java程序中使用日志功能(JDK Log或者Log4J)的时候,会发现Log系统会自动帮我们打印出丰富的信息,格式一般如下:

[运行时间] [当前类名] [方法名] 

INFO: [用户信息]

具体例子如Tomcat启动信息:

Jul 
9 2004   11 : 22 : 41  AM org.apache.coyote.http11.Http11Protocol start 
INFO: Starting Coyote HTTP
/ 1.1  on port  8080

 

看起来这毫无神奇之处,不就是打印了一条信息吗?但如果好奇心重一点,追寻后面的实现原理,会发现这确实很神奇。

上面的Log信息的[当前类名] [方法名]部分 不是用户自己添加的,而是Log系统自动添加的。这意味着Log系统能够自动判断当前执行语句是哪个类的哪个方法。这是如何做到的?

我们翻遍java.lang.reflection 
package ,幻想着找到一个Statement语句级别的Reflection类,通过这个Statement对象获得Method,然后通过这个Method获得declared Class。这不就获得对应的Class和Method信息了吗?这是一个不错的构想,但也只能是一个构想;因为没有这个Statement对象。

再想一下。对了,Java不是有一个Thread类吗?Thread.currentThread()方法获取当前线程,我们能不能通过这个当前线程获取当前运行的Method和Class呢?很遗憾,如果你还在用JDK1.4或以下版本,那么找不到这样的方法。(JDK1.5的情况后面会讲)

再想一下。对了,我们都有很深刻的印象,当系统抛出Exception的时候,总是打印出一串的信息,告诉我们Exception发生的位置,和一层一层的调用关系。我们也可以自己调用Exception的printStackTrace()方法来打印这些信息。这不就是当前线程运行栈的信息吗?找到了,就是它。

Exception的printStackTrace()方法继承自Throwable,那么我们来看一下,JDK的Throwable的printStackTrace()方法是如何实现的。

我们先来看JDK1.3的源代码,会发现Throwable.printStackTrace()方法调用了一个native printStackTrace0()方法。我们找不到任何线索,可以用在我们自己的Java代码中。

那怎么办?Throwable.printStackTrace()的输出结果字符串里面不是包含了当前线程运行栈的所有信息吗?我们可以从这个字符串中抽取自己需要的信息。JDK1.3的时代,也只能这么做了。

二、Log4J 
1 .2的相关实现
Log4J 
1 .2是JDK1.3时代的作品。我们来看相关源代码。

[code]

  
/**

     Instantiate location information based on a Throwable. We

     expect the Throwable t, to be in the format

       
        java.lang.Throwable

        ...

          at org.apache.log4j.PatternLayout.format(PatternLayout.java:413)

          at org.apache.log4j.FileAppender.doAppend(FileAppender.java:183)

        at org.apache.log4j.Category.callAppenders(Category.java:131)

        at org.apache.log4j.Category.log(Category.java:512)

        at callers.fully.qualified.className.methodName(FileName.java:74)

        ...

       

    
*/


  
public  LocationInfo(Throwable t, String fqnOfCallingClass)  {

    String s;



      t.printStackTrace(pw);

      s 
= sw.toString();

      sw.getBuffer().setLength(
0);

  …. 
// 这里的代码省略

  }


[
/ code]

 

这里我们可以看到整体的实现思路。

首先,t.printStackTrace(pw); 获得stack trace字符串。这个t是 
new  Throwable()的结果。用户程序调用Log4J方法之后,Log4J自己又进行了4次调用,然后才获得了 t  =   new  Throwable() :

          at org.apache.log4j.PatternLayout.format(PatternLayout.java:
413 )

          at org.apache.log4j.FileAppender.doAppend(FileAppender.java:
183 )

        at org.apache.log4j.Category.callAppenders(Category.java:
131 )

        at org.apache.log4j.Category.log(Category.java:
512 )

那么,往下走4行,就可以回到用户程序本身的调用信息:

        at callers.fully.qualified.className.methodName(FileName.java:
74 )

这一行里面,类名、方法名、文件名、行号等信息全有了。解析这一行,就可以获得需要的所有信息。

三、JDK1.
4  Log的相关实现
Log4J大获成功,Sun决定在JDK1.4中引入这个Log功能。

为了免去解析StackTrace字符串的麻烦,JDK1.4引入了一个新的类,StackTraceElement。

 

public   final   class  StackTraceElement  implements  java.io.Serializable  {

    
// Normally initialized by VM (public constructor added in 1.5)

    
private String declaringClass;

    
private String methodName;

    
private String fileName;

    
private int    lineNumber;

 

可以看到,恰好包括类名、方法名、文件名、行号等信息。

我们来看JDK1.
4 Log的相关实现。

LocationInfo.java 的infoCaller方法(推算调用者)

 

    
// Private method to infer the callers class and method names

    
private void inferCaller() {



            
// Get the stack trace.

            StackTraceElement stack[] 
= (new Throwable()).getStackTrace();

            
// First, search back to a method in the Logger class.

…. 
// 这里的代码省略

            
// Now search for the first frame before the "Logger" class.

            
while (ix < stack.length) {

                StackTraceElement frame 
= stack[ix];

                String cname 
= frame.getClassName();

                
if (!cname.equals("java.util.logging.Logger")) 

                        
// Weve found the relevant frame.

… 
// 这里的代码省略

            }


            
// We haven     found a suitable frame, so just punt.  This is

        
// OK as we are only committed to making a "best effort" here.

    }


 

从注释中就可以看出实现思路。过程和Log4J异曲同工。只是免去了解析字符串的麻烦。

四、Log4J 
1.3 alpha的相关实现
既然JDK1.4中引入了StackTraceElement类,Log4J也要与时俱进。LocationInfo类也有了相应的变化。

 

  
/**

     Instantiate location information based on a Throwable. We

     expect the Throwable t, to be in the format

 

       
        java.lang.Throwable

        ...

          at org.apache.log4j.PatternLayout.format(PatternLayout.java:413)

          at org.apache.log4j.FileAppender.doAppend(FileAppender.java:183)

        at org.apache.log4j.Category.callAppenders(Category.java:131)

        at org.apache.log4j.Category.log(Category.java:512)

        at callers.fully.qualified.className.methodName(FileName.java:74)

        ...

       

 

       However, we can also deal with JIT compilers that "lose" the

       location information, especially between the parentheses.

 

    
*/


  
public LocationInfo(Throwable t, String fqnOfInvokingClass) {

    
if(PlatformInfo.hasStackTraceElement()) {

      StackTraceElementExtractor.extract(
this, t, fqnOfInvokingClass);

    }
 else {

      LegacyExtractor.extract(
this, t, fqnOfInvokingClass);  

    }


  }


 

可以看到,Log4J首先判断Java平台是否支持StackTraceElement,如果是,那么用StackTraceElementExtractor,否则使用原来的LegacyExtractor。

下面来看StackTraceElementExtractor.java

 

/**

 * A faster extractor based on StackTraceElements introduced in JDK 1.4.

 *

 * The present code uses reflection. Thus, it should compile on all platforms.

 *

 * 
@author Martin Schulz

 * 
@author Ceki Gülcü

 *

 
*/


public class StackTraceElementExtractor {

  
protected static boolean haveStackTraceElement = false;

  
private static Method getStackTrace = null;

  
private static Method getClassName = null;

  
private static Method getFileName = null;

  
private static Method getMethodName = null;

  
private static Method getLineNumber = null;

…. 
// 以下代码省略

 

可以看到,Log4J 
1.3仍然兼容JDK1.3,而且为JDK1.4也做了相应的优化。

五、JDK1.5的Thread Stack Trace
JDK1.5在Thread类里面引入了getStackTrace()和getAllStackTraces()两个方法。这下子,我们不用 (
new Throwable()).getStackTrace ();可以调用

Thread.getCurrentThread().getStackTrace()来获得当前线程的运行栈信息。不仅如此,只要权限允许,还可以获得其它线程的运行栈信息。

 

    
/**

     * Returns an array of stack trace elements representing the stack dump

     * of this thread.  This method will return a zero-length array if

     * this thread has not started or has terminated. 

     * If the returned array is of non-zero length then the first element of 

     * the array represents the top of the stack, which is the most recent

     * method invocation in the sequence.  The last element of the array

     * represents the bottom of the stack, which is the least recent method

     * invocation in the sequence.

     *

     * If there is a security manager, and this thread is not 

     * the current thread, then the security managers 

     * checkPermission method is called with a 

     * RuntimePermission("getStackTrace") permission

     * to see if its ok to get the stack trace. 

     *

     * Some virtual machines may, under some circumstances, omit one

     * or more stack frames from the stack trace.  In the extreme case,

     * a virtual machine that has no stack trace information concerning

     * this thread is permitted to return a zero-length array from this

     * method.  

     *

     * 
@return an array of StackTraceElement, 

     * each represents one stack frame.

     *

     * 
@since 1.5

     
*/


    
public StackTraceElement[] getStackTrace() {

        
if (this != Thread.currentThread()) {

            
// check for getStackTrace permission

            SecurityManager security 
= System.getSecurityManager();

            
if (security != null{

                security.checkPermission(

                    SecurityConstants.GET_STACK_TRACE_PERMISSION);

            }


        }


 

        
if (!isAlive()) {

            
return EMPTY_STACK_TRACE;

        }


 

        Thread[] threads 
= new Thread[1];

        threads[
0= this;

        StackTraceElement[][] result 
= dumpThreads(threads);

        
return result[0]; 

    }


 

    
/**

     * Returns a map of stack traces for all live threads.

     *

     * 
@since 1.5

     
*/


    
public static Map getAllStackTraces() {

        
// check for getStackTrace permission

        
// Get a snapshot of the list of all threads 

    }


 

六、总结
从总的发展趋势来看,JDK不仅提供越来越多、越来越强的功能,而且暴露给用户的控制方法越来越多,越来越强大。

如此一来我 自己的 实现是这样的

 

 

     static   {
        p 
= new Properties();
        dbconfig 
= new Properties();
        
//Object obj = new Object();//构造一个对象,应为在 static 中无法获知当前类,用obj它的class来获得属性文件
        InputStream is = null;//obj.getClass().getResourceAsStream("DBConfig.properties");
        StackTraceElement ste[] = (new Throwable()).getStackTrace();
        
try {
            is 
= Class.forName(ste[0].getClassName()).getResourceAsStream("DBConfig.properties");
        }
 catch (ClassNotFoundException e2) {
            
// TODO 自动生成 catch 块
            e2.printStackTrace();
        }

//        for(int i=0;i<ste.length;i++){
//            System.out.println("" + ste[i].getFileName() + "  [" + ste[i].getClassName() + "]::" + ste[i].getMethodName() + "@" + ste[i].getLineNumber() + "行");
//        }
        try {
            dbconfig.load(is);
        }
 catch (IOException e1) {
            
// TODO 自动生成 catch 块
            e1.printStackTrace();
        }

        String driver 
= dbconfig.getProperty("JdbcDriverClass");
        
if(driver==null||driver.equals("")){
            driver 
= JDBC_DRIVER;
        }

        String url 
= dbconfig.getProperty("JdbcUrl");
        
if(driver==null||driver.equals("")){
            url 
= JDBC_URL;
        }

        String user 
= dbconfig.getProperty("JdbcUser");
        
if(driver==null||driver.equals("")){
            user 
= JDBC_USER;
        }

        String pwd 
= dbconfig.getProperty("JdbcPassword");
        
if(driver==null||driver.equals("")){
            pwd 
= JDBC_PWD;
        }


        p.setProperty(
"driverClassName", driver);
        p.setProperty(
"url", url);
        p.setProperty(
"password", pwd);
        p.setProperty(
"username", user);
        p.setProperty(
"maxActive""30");
        p.setProperty(
"maxIdle""10");
        p.setProperty(
"maxWait""1000");
        p.setProperty(
"removeAbandoned""false");
        p.setProperty(
"removeAbandonedTimeout""120");
        p.setProperty(
"testOnBorrow""true");
        p.setProperty(
"logAbandoned""true");

        
try {
            bds 
= (BasicDataSource) BasicDataSourceFactory.createDataSource(p);
        }
 catch (Exception e) {
            
// TODO 自动生成 catch 块
            e.printStackTrace();
        }

    }
这样就实现了在静态块中访问当前类信息的方法了,也许有朋友会问,为什么不直接把当前类名传递给 Class.forName呢,的确这样是可行的,但我考虑到如果我的类名改变了呢,而且,这个类名字符串写进去就成了硬编码来,这个似乎不太好。另外,我也考虑过用过 init(String path)方法,这样更灵活,问题是这个类是个静态类,每次调用都不知道以前是否在某个地方调用过这个init 方法,当然了这个类作static类似乎不太好,因为它毕竟对不同时候应该配置也不一样。这就不讨论了,主要是学习了这个 StackTraceElement类,开拓下思维!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值