Exception底层原理

一:异常的概念

java异常是一种错误情况,是程序不希望出现的现象,但是由于程序本身的设计逻辑和运行的环境等因素,出现了异常的情况
异常的意义:不允许程序沿着其正常的路径继续走下去,并告诉我们程序发生了什么问题。

发现代异常的时机:

1、程序编译期间,由编译器对代码进行编译,遇到错误会给出提示
2、程序运行期间,运行时出现了不可预料的错误,会抛出异常

异常的执行顺序

  1. new一个异常对象

  2. 终止当前的执行程序。

  3. 弹出异常对象的引用。

  4. 异常处理机制接管被终止的执行程序。

  5. 寻找一个恰当的地点(异常处理程序)继续执行程序。 

二、异常的使用

1、捕获异常

引入一个概念“监控区域”: 一段可能产生异常的代码并且后面跟着处理异常的代码

(1)try块

一个方法内部出现问题,或者一个方法内部调用其他方法的时候出现问题,当前程序会立刻中断。如果你不希望程序中断,可以在这个块儿中增加一个“尝试”各种可能产生各种异常的方法调用,它就是try块:

try{    
    String a = null ;   
}

(2)catch异常处理程序

必须紧跟在try后面,当你希望对异常做出处理,异常处理程序就是你最好的选择,在这里你可以针对每种想要捕获的异常,准备相应的应对措施,这就是catch:

try{
    String a = null ;
}catch(Type1 e1){
    e1.printStackTrace();
}catch(Type2 e2){
    e2.printStackTrace();
}catch(Type3 e3){
    e3.printStackTrace();
}

当异常抛出后,异常处理机制会寻找与异常类型匹配的catch块儿,例如type2类型的异常就会执行e2.printStackTrace();

注意一点:当异常处理机制匹配到了一个异常处理程序时,会在当前异常程序处理完毕后结束整个异常处理环节,其他的异常处理程序不会再执行。

例如:Exception类型的异常涵盖了RuntimeException类型异常,但是并不会执行Exception异常处理程序

try{
    String a = null ;
}catch(RuntimeException e){
    System.out.println("RuntimeException");
    e.printStackTrace();
}catch(Exception e){
    System.out.println("Exception");
    e.printStackTrace();
}

异常处理两种模型:终止与恢复

终止模型

这种模型将假设错误非常关键,以至于程序无法返回到异常发生的地方继续执行,一旦异常抛出错误就意味着世界末日,意味着死亡,意味着GG

恢复模型

异常处理程序发现了错误,并且修复了错误然后重新调用出问题的方法,并且认为第二次调用该方法会成功。通常可以将try块放入while循环中,不断执行方法,直到得到满意的结果。

恢复模型会带来很多非通用性的代码,增加了维护难度,因为你需要穷举出各种可能的问题和异常的解决办法,并且一旦异常始终无法正常解决,就会陷入无限的循环中。说白了就是管的越细越会给自己造成不必要的麻烦。我们为什么不管一个大面儿,兵来将挡水来土掩,直接抛出来更容易维护。

2、创建异常对象

2.1 自定义异常

所谓的自定义异常就是java提供的异常体系无法满足你的需求,说白了就是有些异常系统无法预见,需要人为干预。所以才有自定义异常的方式,要自己定义一个异常必须从已有的异常类进行继承。

如何自定义异常类:

public class MyException extends Exception {
    public MyException(){}
    public MyException(String exceptionMassage){
        super(exceptionMassage);
         //明确调用基类的构造器,接收一个字符串为参数
    }
}
public static void main(String[] args) {
    try{
        String a = null ;
        try{
            a.equals("b");
        } catch (Exception e){
            throw new MyException("a = null");
        }
    } catch (MyException e){
        System.out.println("RuntimeException");
        e.printStackTrace();
    }
}
com.umbrellacore.privilege.controller.MyException: a = null    at com.umbrellacore.privilege.controller.aaa.main(aaa.java:10)RuntimeExceptionProcess finished with exit code 0

所有的标准异常类都有两个构造器,一个是默认构造器,一个是接受字符串作为参数,以便把相关参数放入异常对象的构造器

2.2 throw一个异常

2.1中出现了throw这个关键字,throw这个动作类似return,但是他们之间是不同的。可以简单的把异常处理看成一种不同的返回机制,此外还可以抛出任意类型的Throwable对象,他是异常类型的根类。

对于不同的错误信息,会抛出不同的异常类型,错误信息可以保存在异常对象的内部,或者用异常类的名字来暗示,通常异常对象中仅有的信息就是异常类型。

三、异常原理

上面说了那么多,对异常大概有了一个初步的了解,起码做到了会用,但是背后的原理究竟是什么,下面来完整剖析一下异常的基本原理。

1、继承关系

2、源码分析

Throwable : Throwable类是整个Java异常体系的超类

======顶层Throwable构造器======
public Throwable() {
    fillInStackTrace();
 //在Throwable对象中填充执行的堆栈信息。此方法在Throwable对象中记录当前线程的栈帧的状态信息
}
public Throwable(String message) {
    fillInStackTrace();
    detailMessage = message;
}
public Throwable(String message, Throwable cause) {
    fillInStackTrace();
    detailMessage = message;
    this.cause = cause;
}
public Throwable(Throwable cause) {
    fillInStackTrace();
    detailMessage = (cause==null ? null : cause.toString());
    this.cause = cause;
}

可以看到这里出镜最多的是fillInStackTrace这个方法,其他的都是detailMassage,也就是异常的语句。所以我们把关注点聚焦在fillInStackTrace这个方法上

private static final StackTraceElement[] UNASSIGNED_STACK = new StackTraceElement[0];
private StackTraceElement[] stackTrace = UNASSIGNED_STACK;
 //stackTrace 指向 UNASSIGNED_STACK 
public synchronized Throwable fillInStackTrace() {
   if (stackTrace != null ||
            backtrace != null /* Out of protocol state */ ) {
            fillInStackTrace(0);
            stackTrace = UNASSIGNED_STACK;
   }
   return this;
}
private native Throwable fillInStackTrace(int dummy);

从上述代码中来进行分析

首先定义了一个数组,数组的类型是StackTraceElement。而且采用了final类型的数组,证明其不可在引用其他类型

来看看这个Element都含哪些东西:

public final class StackTraceElement implements java.io.Serializable {
//可以看出这是一个final class,说明他是一个基础类不许被继承。
    private String declaringClass;
    // 方法的类名
    private String methodName;
    //方法名
    private String fileName;
    //文件名
    private int    lineNumber;
    // 调用的行数
// =========构造器======
public StackTraceElement(String declaringClass, String methodName,
                         String fileName, int lineNumber) {
    this.declaringClass = Objects.requireNonNull(declaringClass, "Declaring class is null");
    this.methodName     = Objects.requireNonNull(methodName, "Method name is null");
    this.fileName       = fileName;
    this.lineNumber     = lineNumber;
}

接下来回到fillInStackTrace方法,它还调用了fillInStackTrace(0) 经查看其实调用的是

private native Throwable fillInStackTrace(int dummy);

这是个native方法,也就是个底层本地方法来获取当前线程的堆栈信息。据悉这是一个非常耗时的方法。如果我们仅仅需要用到异常的传播性质,而不关心异常的堆栈信息,那么完全可以在自定义异常类的时候重写fillInStackTrace()方法。

最后我们看一下我们常用的几种打印异常的方法,他们的底层原理

printStackTrace

err是运行期异常和错误反馈的输出流方向。此方法将此对象的堆栈跟踪输出至错误输出流。
输出的第一行包含此对象的 toString() 方法的结果。剩余行表示以前由方法 fillInStackTrace() 记录的数据。

public void printStackTrace() {
    printStackTrace(System.err);
}
public void printStackTrace(PrintStream s) {
    printStackTrace(new WrappedPrintStream(s));
}
private void printStackTrace(PrintStreamOrWriter s) {
    // Guard against malicious overrides of Throwable.equals by
    // using a Set with identity equality semantics.
    Set<Throwable> dejaVu =
        Collections.newSetFromMap(new IdentityHashMap<Throwable, Boolean>());
    dejaVu.add(this);
    synchronized (s.lock()) {
        // Print our stack trace
        s.println(this);
        StackTraceElement[] trace = getOurStackTrace();
        for (StackTraceElement traceElement : trace)
            s.println("\tat " + traceElement);
        // Print suppressed exceptions, if any
        for (Throwable se : getSuppressed())
            se.printEnclosedStackTrace(s, trace, SUPPRESSED_CAPTION, "\t", dejaVu);
        // Print cause, if any
        Throwable ourCause = getCause();
        if (ourCause != null)
            ourCause.printEnclosedStackTrace(s, trace, CAUSE_CAPTION, "", dejaVu);
    }
}

toString

可以看出这里输出了类的信息+定位信息
注意这里的getLocalizedMessage 其实就是 return detailMessage,该值可以自定义字符串,也可以是cause.toString 详见上面的构造器

public String toString() {    String s = getClass().getName();    String message = getLocalizedMessage();    return (message != null) ? (s + ": " + message) : s;}

getMessage

可以看出返回的仅仅是detailMessage

public String getMessage() {
    return detailMessage;
}

getStackTrace

这个方法返回的是上面介绍的StackTraceElement数组的信息,而且会根据栈的深度去遍历,往数组里放入内容的其实是调用了一个本地方法native StackTraceElement getStackTraceElement(int index); 获得的是栈的信息

public StackTraceElement[] getStackTrace() {
    return getOurStackTrace().clone();
}
private synchronized StackTraceElement[] getOurStackTrace() {
    // Initialize stack trace field with information from
    // backtrace if this is the first call to this method
    if (stackTrace == UNASSIGNED_STACK ||
        (stackTrace == null && backtrace != null)/* Out of protocol state */) {
        int depth = getStackTraceDepth();
        stackTrace = new StackTraceElement[depth];
        for (int i=0; i < depth; i++)
            stackTrace[i] = getStackTraceElement(i);
    } else if (stackTrace == null) {
        return UNASSIGNED_STACK;
    }
    return stackTrace;}

四、Error与Exception的区别

Error

Error表示程序在运行期间出现了十分严重、不可恢复的错误

======Error======
public class Error extends Throwable {
 static final long serialVersionUID = 4980196508277280342L;
 public Error() {
     super();
 }
 public Error(String message) {
     super(message);
 }
 public Error(String message, Throwable cause) {
     super(message, cause);
 }
 public Error(Throwable cause) {
     super(cause);
 }
 protected Error(String message, Throwable cause, boolean enableSuppression,
                 boolean writableStackTrace) {
     super(message, cause, enableSuppression, writableStackTrace);
 }
}

Exception

Exception是应用层面上最顶层的异常类

======Exception======
public class Exception extends Throwable {
 static final long serialVersionUID = -3387516993124229948L;
 public Exception() {
     super();
 }
 public Exception(String message) {
     super(message);
 }
 public Exception(String message, Throwable cause) {
     super(message, cause);
 }
 public Exception(Throwable cause) {
     super(cause);
 }
 protected Exception(String message, Throwable cause, boolean enableSuppression,
                     boolean writableStackTrace) {
     super(message, cause, enableSuppression, writableStackTrace);
 }
}

可以看出error 和 exception 基本上都是大同小异,但有以下区别:

首先

Exception和Error继承Throwable类,在java中只有Throwable类型的实例才能被抛出(throw)或者捕获(catch),它是异常处理机制的基本类型。

其次

Exception和Error体现了java平台针对不同异常情况的分类。Exception是程序正常运行过程中,可以预料的意外情况,可能并且应该被捕获,并进行处理。Error正常情况下不大可能出现的情况,绝大部分的Error都会导致程序状态不正常,不可恢复,既然是非正常情况,所以不便也不需要处理,例如OutOfMemoryError之类都是Error的子类

再次

Exception分为检查型异常和非检查型异常。检查型异常必须在源码中进行捕获处理,这是编译检查的一部分。除了RuntimeExceion及其子类之外的异常都是检查型异常。非检查型异常就是所谓的RuntimeExceion,类似NullPointerException,ArrayIndexOfBoundException就是非检查型异常,通常是可以通过编码避免的逻辑错误,具体根据需要判断是否需要捕获,编译期不检查,如果抛出了非检查型异常,那就是编码逻辑有问题,要解决。

五、使用建议

try {
     // 业务代码
     Thread.sleep(100L);
} catch (Exception e) {
      // Ignore it
}

这段代码虽然很短,但是已经违反了异常处理的两个基本原则。

第一,尽量不要捕获类似Exception这样的通用异常,而是应该捕获特定异常,在这里是Thread.sleep()抛出的InterruptedException。

  • 这是因为在日常的开发和合作中,我们读代码的机会往往超过写代码,软件工程是门协作的艺术,所以我们有义务让自己的代码能够直观地体现出尽量多的信息,而泛泛的Exception之类,恰恰隐藏了我们的目的。

  • 我们也要保证程序不会捕获到我们不希望捕获的异常。比如,你可能更希望RuntimeException被扩散出来,而不是被捕获。

  • 除非深思熟虑了,否则不要捕获Throwable或者Error,这样很难保证我们能够正确程序处理OutOfMemoryError。

第二,不要生吞(swallow)异常。这是异常处理中要特别注意的事情,因为很可能会导致非常难以诊断的诡异情况。

  • 生吞异常,往往是基于假设这段代码可能不会发生,或者感觉忽略异常是无所谓的,但是千万不要在产品代码做这种假设!

  • 有较详细的日志能提高问题定位

第三,try-catch代码段会产生额外的性能开销,或者换个角度说,它往往会影响JVM对代码进行优化,所以建议仅捕获有必要的代码段,尽量不要一个大的try包住整段的代码;与此同时,利用异常控制代码流程,也不是一个好主意,远比我们通常意义上的条件语句(if/else、switch)要低效。

第四,Java每实例化一个Exception,都会对当时的栈进行快照,这是一个相对比较重的操作(前面已经分析)。如果发生的非常频繁,这个开销可就不能被忽略了。


原文:https://www.jianshu.com/p/ee76d1f4caa5

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Ioc的底层原理是基于反射和依赖注入实现的。在Spring容器启动时,会扫描所有的类,找到其中被注解为@Component、@Service、@Controller、@Repository等的类,并将其实例化为Bean对象,同时将其放入Bean工厂中进行管理。在Bean实例化的过程中,Spring使用反射机制调用构造方法或工厂方法来创建对象,并通过setter或构造方法注入依赖的其他Bean对象。这样,通过Ioc容器的管理,实现了对象之间的解耦和灵活性。 手写一套简单的Spring Ioc框架: 1. 定义注解@ComponentScan用于扫描指定包下的所有类,将被注解为@Component、@Service、@Controller、@Repository等的类实例化为Bean对象,并将其放入Bean工厂中进行管理。 ```java @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface ComponentScan { String[] basePackages(); } ``` 2. 定义注解@Component用于标注需要实例化为Bean对象的类。 ```java @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Component { String value() default ""; } ``` 3. 定义Bean工厂类,存储所有的Bean对象。 ```java public class BeanFactory { private Map<String, Object> beans = new ConcurrentHashMap<>(); public Object getBean(String name) { return beans.get(name); } public void addBean(String name, Object bean) { beans.put(name, bean); } } ``` 4. 定义Bean扫描器,扫描指定包下的所有类,并将被注解为@Component、@Service、@Controller、@Repository等的类实例化为Bean对象,并将其放入Bean工厂中进行管理。 ```java public class BeanScanner { private BeanFactory beanFactory; public BeanScanner(BeanFactory beanFactory) { this.beanFactory = beanFactory; } public void scan(String... basePackages) { for (String basePackage : basePackages) { String path = basePackage.replaceAll("\\.", "/"); File file = new File(this.getClass().getResource("/" + path).getFile()); if (!file.exists()) { continue; } if (file.isDirectory()) { for (File subFile : file.listFiles()) { scan(basePackage + "." + subFile.getName().replaceAll(".class", "")); } } else { String className = basePackage + "." + file.getName().replaceAll(".class", ""); try { Class<?> clazz = Class.forName(className); if (clazz.isAnnotationPresent(Component.class)) { Object bean = clazz.getDeclaredConstructor().newInstance(); beanFactory.addBean(clazz.getName(), bean); } } catch (Exception e) { e.printStackTrace(); } } } } } ``` 5. 定义注解@Autowired用于依赖注入。 ```java @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Autowired { } ``` 6. 定义Bean注入器,通过反射机制注入依赖的Bean对象。 ```java public class BeanInjector { private BeanFactory beanFactory; public BeanInjector(BeanFactory beanFactory) { this.beanFactory = beanFactory; } public void inject() { for (Object bean : beanFactory.beans.values()) { Field[] fields = bean.getClass().getDeclaredFields(); for (Field field : fields) { if (field.isAnnotationPresent(Autowired.class)) { try { field.setAccessible(true); Object dependencyBean = beanFactory.getBean(field.getType().getName()); field.set(bean, dependencyBean); } catch (IllegalAccessException e) { e.printStackTrace(); } } } } } } ``` 7. 定义Spring Ioc框架的入口类,启动容器,并扫描指定包下的所有类,实例化为Bean对象,并将其放入Bean工厂中进行管理,最后注入依赖的Bean对象。 ```java public class SpringIoc { public static void run(Class<?> clazz, String[] args) { BeanFactory beanFactory = new BeanFactory(); BeanScanner beanScanner = new BeanScanner(beanFactory); ComponentScan componentScan = clazz.getDeclaredAnnotation(ComponentScan.class); beanScanner.scan(componentScan.basePackages()); BeanInjector beanInjector = new BeanInjector(beanFactory); beanInjector.inject(); } } ``` 使用示例: 1. 定义两个类,一个依赖另一个。 ```java @Component public class UserService { public void sayHello() { System.out.println("Hello, World!"); } } @Component public class UserController { @Autowired private UserService userService; public void hello() { userService.sayHello(); } } ``` 2. 定义启动类,指定需要扫描的包,启动Spring Ioc容器。 ```java @ComponentScan(basePackages = "com.example") public class Application { public static void main(String[] args) { SpringIoc.run(Application.class, args); UserController userController = (UserController) BeanFactory.getBean("com.example.UserController"); userController.hello(); } } ``` 输出结果: ``` Hello, World! ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值