2012年9月8日

Exception VS Control Flow

每当提到Exeption就会有人跳出来说“Exception not use for flow control”,那到底是什么意思呢?什么情况下Exception就算控制流程了,什么时候就该抛出Exception了呢?

首先什么是Exception?

Definition: 

An exception is an event, which occurs during the execution of a program, that disrupts the normal flow of the program's instructions.


再看什么是“流程”?如果流程是指程序的每一步执行,那异常就是控制流程的,它就是用来区分程序的正常流程和非正常流程的,从上面异常的定义就可以看出。因此为了明确我们应该说”不要用异常控制程序的正常流程“。如何定义正常流程和非正常流程很难,这是一个主观的决定,没有一个统一的标准,只能根据实际情况。网上找个例子:
bool isDouble(string someString) {
    
try {
        
double d = Convert.ParseInt32(someString);
    } 
catch(FormatException e) {
        
return false;
    }
    
return true;
}
这个程序其实不是想convert数字,而是想知道一个字符串是否包含一个数字,通过判断是不是有异常的方式来决定返回true还是false,这是个Smell,这种应该算”异常控制了正常流程“。我们可以通过正则表达式或其他方式来判断。

另外Clean Code上一个例子:
    try {  
        MealExpenses expenses 
= expenseReportDAO.getMeals(employee.getID());  
        m_total 
+= expenses.getTotal();  
    } 
catch(MealExpensesNotFound e) {  
        m_total 
+= getMealPerDiem();  
    } 
MealExpensesNotFound异常影响了正常的计算m_total的业务逻辑。对于这种情况可以通过一下方式改进
    public class PerDiemMealExpenses implements MealExpenses {  
        
public int getTotal() {  
            
// return the per diem default  
        }  
    } 

以上两个例子是比较明显的异常控制正常流程,Smell很明显,不会有很大争议,但是实际情况中可能有很多例子没有这么明显,因为都是主观判定的。比如一下代码,算不算异常控制正常流程?

public int doSomething()
{
    doA();
    
try {
        doB();
    } 
catch (MyException e) {
        
return ERROR;
    }
    doC();
    
return SUCCESS;
}

看到这样一段程序,如果没有上下文,我们无法判断。但是如果doSomething是想让我们回答yes or no,success or error,我们不应该通过有无异常来判断yes or no,success or error,应该有个单独的方法来判断,这个方法就只做这一件事情。如果doSometing是执行一个操作,那么在这个过程中我们假定是不会出现问题的,否则抛出异常是比较合理的。






posted @ 2012-10-30 17:03 *** 阅读(235) | 评论 (0)编辑 收藏

ClassLoader 加载机制

1. Java Class Loading Mechanism

首先当编译一个Java文件时,编译器就会在生成的字节码中内置一个public,static,final的class字段,该字段属于java.lang.Class类型,该class字段使用点来访问,所以可以有:
java.lang.Class clazz = MyClass.class

当class被JVM加载,就不再加载相同的class。class在JVM中通过(ClassLoader,Package,ClassName)来唯一决定。ClassLoader指定了一个class的scope,这意味着如果两个相同的包下面的class被不同的ClassLoader加载,它们是不一样的,并且不是type-compatible的。

JVM中所有的ClassLoader(bootstrap ClassLoader除外)都是直接或间接继承于java.lang.ClassLoader抽象类,并且人为逻辑上指定了parent-child关系,实现上child不一定继承于parent,我们也可以通过继承它来实现自己的ClassLoader。

JVM ClassLoder架构,从上到下依次为parent-child关系:
  • Bootstrap ClassLoader - 启动类加载器,主要负责加载核心Java类如java.lang.Object和其他运行时所需class,位于JRE/lib目录下或-Xbootclasspath指定的目录。我们不知道过多的关于Bootstrap ClassLoader的细节,因为它是一个native的实现,不是Java实现,所以不同JVMs的Bootstrap ClassLoader的行为也不尽相同。调用java.lang.String.getClassLoder() 返回null。
  • sun.misc.ExtClassLoader - 扩展类加载器,负责加载JRE/lib/ext目录及-Djava.ext.dirs指定目录。
  • sun.misc.AppClassLoader - 应用类加载器,负责加载java.class.path目录
  • 另外,还有一些其他的ClassLoader如:java.net.URLClassLoader,java.security.SecureClassLoader,java.rmi.server.RMIClassLoader,sun.applet.AppletClassLoader
  • 用户还可以自己继承java.lang.ClassLoader来实现自己的ClassLoader,用来动态加载class文件。
ClassLoader特性
  • 每个ClassLoader维护一份自己的命名空间,同一个ClassLoader命名空间不能加载两个同名的类。
  • 为实现Java安全沙箱模型,默认采用parent-child加载链结构,除Bootstrap ClassLoader没有parent外,每个ClassLoader都有一个逻辑上的parent,就是加载这个ClassLoader的ClassLoader,因为ClassLoader本身也是一个类,直接或间接的继承java.lang.ClassLoader抽象类。
java.lang.Thread中包含一个public的方法public ClassLoader getContextClassLoader(),它返回某一线程相关的ClassLoader,该ClassLoader是线程的创建者提供的用来加载线程中运行的classes和资源的。如果没有显式的设置其ClassLoader,默认是parent线程的Context ClassLoader。Java默认的线程上下文加载器是AppClassLoader。

ClassLoader工作原理:

了解ClassLoader工作原理,先来看一个ClassLoader类简化版的loadClass()方法源码
 1 protected Class<?> loadClass(String name, boolean resolve)
 2         throws ClassNotFoundException
 3     {
 4         synchronized (getClassLoadingLock(name)) {
 5             // First, check if the class has already been loaded
 6             Class c = findLoadedClass(name);
 7             if (c == null) {
 8                 long t0 = System.nanoTime();
 9                 try {
10                     if (parent != null) {
11                         c = parent.loadClass(name, false);
12                     } else {
13                         c = findBootstrapClassOrNull(name);
14                     }
15                 } catch (ClassNotFoundException e) {
16                     // ClassNotFoundException thrown if class not found
17                     // from the non-null parent class loader
18                 }
19 
20                 if (c == null) {
21                     // If still not found, then invoke findClass in order
22                     // to find the class.
24                     c = findClass(name);
25                 }
26             }
27             if (resolve) {
28                 resolveClass(c);
29             }
30             return c;
31         }
32     }

首先查看该class是否已被加载,如果已被加载则直接返回,否则调用parent的loadClass来加载,如果parent是null代表是Bootstrap ClassLoader,则有Bootstrap ClassLoader来加载,如果都未加载成功,最后由该ClassLoader自己加载。这种parent-child委派模型,保证了恶意的替换Java核心类不会发生,因为如果定义了一个恶意java.lang.String,它首先会被JVM的Bootstrap ClassLoader加载自己JRE/lib下的,而不会加载恶意的。另外,Java允许同一package下的类可以访问受保护成员的访问权限,如定义一个java.lang.Bad,但是因为java.lang.String由Bootstrap ClassLoader加载而java.lang.Bad由AppClassLoader加载,不是同一ClassLoader加载,仍不能访问。

2. Hotswap - 热部署

即不重启JVM,直接替换class。因为ClassLoader特性,同一个ClassLoader命名空间不能加载两个同名的类,所以在不重启JVM的情况下,只能通过新的ClassLoader来重新load新的class。

 1  public static void main(String[] args) throws InterruptedException, MalformedURLException {
 2         IExample oldExample = new Example();
 3         oldExample.plus();
 4         System.out.println(oldExample.getCount());
 5 
 6         Hotswap hotswap = new Hotswap();
 7         while (true) {
 8             IExample newExample = hotswap.swap(oldExample);
 9             String message = newExample.message();
10             int count = newExample.plus();
11             System.out.println(message.concat(" : " + count));
12             oldExample = newExample;
13             Thread.sleep(5000);
14         }
15     }
16 
利用hotswap替换就的Example,每5秒钟轮询一次,swap方法实现如下:
 1  private IExample swap(IExample old) {
 2         try {
 3             String sourceFile = srcPath().concat("Example.java");
 4             if (isChanged(sourceFile)) {
 5                 comiple(sourceFile, classPath());
 6                 MyClassLoader classLoader = new MyClassLoader(new URL[]{new URL("file:"+classPath())});
 7                 Class<?> clazz = classLoader.loadClass("Example");
 8                 System.out.println(IExample.class.getClassLoader());
 9                 IExample exampleInstance = ((IExample) clazz.newInstance()).copy(old);
10                 System.out.println(exampleInstance.getClass().getClassLoader());
11                 return exampleInstance;
12             }
13         } catch ...
24         return old;
25     }
这里必须将exampleInstance转型为IExample接口而不是Exmaple,否则会抛出ClassCastExecption,这是因为swap方法所在类Hotswap是有AppClassLoader加载的,而且加载Hotswap的同时会加载该类引用的Exmaple的symbol link,而Example是MyClassLoader加载的,不同的ClassLoader加载的类之间直接用会抛出ClassCastException, 在本例中ClassLoader实现如下:
 1 public class MyClassLoader extends URLClassLoader {
 2 
 3     public MyClassLoader(URL[] urls) {
 4         super(urls);
 5     }
 6 
 7     @Override
 8     public Class<?> loadClass(String name) throws ClassNotFoundException {
 9         if ("Example".equals(name)) {
10             return findClass(name);
11         }
12         return super.loadClass(name);
13     }
14 }
而对IExample我们还是调用super的loadClass方法,该方法实现仍是JVM的parent-child委派方式,因此最终由AppClassLoader加载,加载Hotswap时加载的symbol link也是由AppClassLoader加载的,因此能够成功。

此外再热部署时,被替换的类的所有引用及状态都要迁移到新的类上,本例中只是很简单的调用copy函数迁移了count的状态。

Tomcat的jsp热部署机制就是基于ClassLoader实现的,对于其类的热部署机制是通过修改内存中的class字节码实现的。

Resource:
Reloading Java Classes 101: Objects, Classes and ClassLoaders
Internals of Java Class Loading

posted @ 2012-09-08 17:58 *** 阅读(626) | 评论 (0)编辑 收藏

<2012年9月>
2627282930311
2345678
9101112131415
16171819202122
23242526272829
30123456

导航

统计

常用链接

留言簿

随笔档案

搜索

最新评论

阅读排行榜

评论排行榜