总结来说,throw
是用来抛出一个具体的异常实例,而throws
是用来声明方法可能会抛出哪些类型的异常,是对调用者的一种通知和要求。
1. throw
- 作用:
throw
关键字用于在方法体内实际抛出一个异常实例。当程序运行到throw
语句时,指定的异常会被创建并抛出,立即终止当前方法的执行,并将控制权转移给包含该方法的调用者的异常处理机制。 - 使用时机: 当检测到某种错误条件或异常情况时,在方法内部使用
throw
来抛出一个异常对象,这个对象可以是系统预定义的异常类的实例,也可以是自定义异常类的实例。 - 特点:
throw
总是伴随着一个异常实例,且其后的代码不会被执行,因为一旦抛出异常,当前的代码路径就会被中断。
2. throws
- 作用:
throws
关键字用于声明方法可能抛出的异常类型,它出现在方法签名之后。这告知调用者该方法执行时可能会遇到的异常情况,要求调用者要么处理这些异常(使用try-catch
语句块),要么继续向上层方法抛出这些异常。 - 使用时机: 当一个方法无法处理或不打算处理某些异常时,可以在方法声明中使用
throws
来声明这些异常。这样做可以强制调用者意识到潜在的异常风险并做出相应的处理准备。 - 特点:
throws
后面可以跟随一个或多个异常类名,用逗号分隔,表示该方法可能抛出多种类型的异常。并且,throws
声明并不意味着异常一定会被抛出,它只是声明了一种可能性。
3. 为什么有时候异常必须用throws声明而有时候不用?
如图
(图一)
(图二)
为什么会出现这种情况呢?其实了解过Java异常的人都知道,异常分为两大类,一类是运行时异常,一类是非运行时异常。运行时异常只能在运行时识别,所以编译器无法识别运行时异常,就算不throws出异常也是能编译通过的,而非运行时异常如果不throws出来,编译器就会识别到并报错。
Java动态代理是一种在运行时创建代理对象的技术,它允许开发者在不修改目标类代码的情况下,通过代理类对目标类的实例方法进行增强或拦截。动态代理的核心价值在于能够在程序运行阶段动态地生成一个实现了预定义接口的新类,这个新类就是所谓的“代理类”。
在Java中,有两种主要的实现方式:
-
JDK动态代理:
- JDK动态代理是Java SE API内置的一种动态代理机制,它通过java.lang.reflect.Proxy类和InvocationHandler接口来实现。
- 使用JDK动态代理,目标类必须实现至少一个接口。代理类会继承Proxy类并实现与目标类相同的接口,这样代理类就能替代目标类成为接口的实现者。
- 当调用代理类的方法时,实际会调用到InvocationHandler接口中的invoke方法,在invoke方法内部可以添加额外的功能,如方法执行前后的附加逻辑、权限控制、日志记录等,然后调用目标对象的实际方法。
-
CGLIB动态代理:
- CGLIB(Code Generation Library)是一个第三方库,它通过字节码技术为没有实现接口的目标类生成子类作为代理类。
- CGLIB代理能够代理任何未实现接口的类,因为它是通过继承的方式生成一个目标类的子类,重写父类的方法并在方法中加入增强逻辑。
- 这种方式更加灵活,但要求代理的目标类不能声明为final类,并且方法也不能是final方法,否则无法被CGLIB成功继承和重写。
无论是哪种动态代理方式,其目的都是为了在目标方法执行前后增加额外的行为,或者改变原有的行为,以满足特定的需求,例如AOP(面向切面编程)框架中的事务管理、性能监控、日志记录等功能。
1.JDK动态代理
以下是一个简单的JDK动态代理示例,假设我们有一个接口Sellable
和它的实现类RealEstate
。在这个例子中,我们将创建一个动态代理来记录每次调用卖房方法时的日志信息。
首先,定义业务接口:
// 业务接口:买卖物品
public interface Sellable {
void sell(String item);
void buy(String item);
}
然后,实现这个接口的实体类:
// 接口的实现类:房地产公司
public class RealEstate implements Sellable {
@Override
public void sell(String item) {
System.out.println("实际销售房源: " + item);
}
@Override
public void buy(String item) {
System.out.println("实际购买房源: " + item);
}
}
接下来,创建一个InvocationHandler实现类,用于处理对代理对象方法的调用:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class LoggingInvocationHandler implements InvocationHandler {
// 被代理的对象引用
private final Sellable target;
public LoggingInvocationHandler(Sellable target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 方法调用前的操作:记录日志
System.out.println("开始销售房源操作...");
// 调用目标对象的方法,并返回结果
Object result = method.invoke(target, args);
// 方法调用后的操作:再次记录日志
System.out.println("完成销售房源操作.");
return result;
}
}
最后,通过Proxy类创建并使用动态代理:
public class Main {
public static void main(String[] args) {
// 实例化真实对象
Sellable realEstate = new RealEstate();
// 创建代理对象,并将真实对象传给InvocationHandler
// 这块代码是动态代理的精髓
Sellable proxy = (Sellable) Proxy.newProxyInstance(
Sellable.class.getClassLoader(),
new Class<?>[]{Sellable.class},
new LoggingInvocationHandler(realEstate)
);
// 现在调用的是代理对象的方法,但会触发InvocationHandler的逻辑
proxy.sell("豪华别墅");
proxy.buy("大平层");
// 输出:
// 开始销售房源操作...
// 实际销售房源: 豪华别墅
// 完成销售房源操作.
}
}
在这个例子中,当客户端代码通过代理对象调用sell
方法时,实际上会执行LoggingInvocationHandler
中的invoke
方法,在该方法内部先进行日志记录,然后调用实际对象的方法完成销售动作,最后再记录一次日志。这就是JDK动态代理的基本应用。
相关文章:
https://m.iqiyi.com/playlist6442695276678202.html
https://m.iqiyi.com/playlist6454727764391602.html
https://www.iqiyi.com/playlist6454727764391602.html
https://www.iqiyi.com/playlist6442695276678202.html
<br>https://m.iqiyi.com/playlist6442695276678202.html</br>
<br>https://m.iqiyi.com/playlist6454727764391602.html</br>
<br>https://www.iqiyi.com/playlist6454727764391602.html</br>
<br>https://www.iqiyi.com/playlist6442695276678202.html</br>
Q:为什么要搞这么麻烦呢, 直接写一个方法, 把实际对象调用实际方法写到这个方法里不是跟简单吗?
这种简化方式确实可以在某些场景下直接满足需求,例如在业务逻辑相对简单、需要增强的功能点不多的情况下,其实这种方式就是静态代理。但是动态代理技术的设计初衷和优势在于:
- 解耦:通过动态代理,我们可以将功能增强(如日志记录、事务管理、权限检查等)的代码与业务逻辑分离,使得业务类更专注于业务本身,而不需要关心额外的横切关注点。
- 灵活扩展:如果在系统中有很多类似的方法都需要添加同样的增强处理,使用动态代理可以避免大量重复代码的编写。只需要定义一个InvocationHandler实现类,就可以对所有实现了同一接口的对象进行统一的增强处理。
- 运行时动态决定行为:动态代理是在运行时动态生成代理对象,这意味着代理对象的行为可以根据运行时条件来决定,比如根据配置信息动态开启或关闭日志记录、性能监控等功能。
- AOP支持:在面向切面编程(Aspect Oriented Programming, AOP)框架中,动态代理是实现切面织入的重要手段。通过动态代理,可以在不侵入原有业务代码的前提下,方便地实现如事务管理、异常处理、性能统计等横切关注点的统一管理。
因此,虽然表面上看动态代理可能会显得比直接调用方法更为复杂,但在实际项目开发中,它为解决特定问题提供了强大且灵活的支持,尤其在大型、复杂的软件系统中具有很高的价值。
2.CGLIB动态代理
CGLIB(Code Generation Library)是一个强大的高性能的代码生成库,它广泛应用于Java的动态代理实现中,特别是在Spring AOP框架中作为JDK动态代理的一种补充或替代方案。CGLIB通过字节码技术(Bytecode Engineering Library, BCEL 或者 ASM 库)在运行时对目标类生成一个子类,并覆盖其中非final和非private的方法来创建代理对象。
在Spring框架中,当配置了proxy-target-class属性为true时,Spring会自动选择CGLIB作为代理机制来为目标类创建代理实例。
以下是一个使用CGLIB库进行动态代理的简单示例,假设我们有一个Calculator
类,现在希望通过CGLib创建一个代理类来增强其方法调用:
首先,定义原始的业务类:
public class Calculator {
public int add(int i, int j) {
System.out.println("Executing original add method.");
return i + j;
}
}
然后,实现CGLIB的MethodInterceptor接口以提供增强逻辑:
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class LoggingInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 在方法调用前打印日志
System.out.println("Before calling " + method.getName() + " with arguments: " + Arrays.toString(args));
// 调用实际方法并获取结果
Object result = proxy.invokeSuper(obj, args);
// 在方法调用后打印日志
System.out.println("After calling " + method.getName() + ", result is: " + result);
return result;
}
}
接下来,使用CGLIB的Enhancer类生成代理对象:
import net.sf.cglib.core.NamingPolicy;
import net.sf.cglib.core.DefaultNamingPolicy;
import net.sf.cglib.proxy.Enhancer;
public class CglibDemo {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Calculator.class); // 设置要代理的目标类
// 设置拦截器(MethodInterceptor)
enhancer.setCallback(new LoggingInterceptor());
// 可选:设置命名策略,避免代理类名称冲突
enhancer.setNamingPolicy(DefaultNamingPolicy.INSTANCE);
// 创建并获取代理对象
Calculator calculatorProxy = (Calculator) enhancer.create();
// 通过代理对象调用方法
int sum = calculatorProxy.add(3, 5);
System.out.println("Sum is: " + sum);
}
}
当运行这段代码时,CGLib会动态地为Calculator类生成一个子类作为代理,并在调用add方法前后执行LoggingInterceptor中的intercept方法。因此,输出将包含日志信息以及原方法的结果。