动态代理
动态代理是程序在运行期间动态构建代理对象和动态调用代理方法的一种机制。
在业务中使用动态代理,一般是为了给需要实现的方法添加预处理或者添加后续操作,但是不干预实现类的正常业务,把一些基本业务和主要的业务逻辑分离。我们一般所熟知的Spring的AOP原理就是基于动态代理实现的。
动态代理的常用实现方式是反射。反射机制是指程序在运行期间可以访问、检测和修改其本身状态或行为的一种能力,使用反射我们可以调用任意一个类对象,以及类对象中包含的属性及方法。
但动态代理不止有反射一种实现方式,例如,动态代理可以通过 CGLib 来实现,而 CGLib 是基于 ASM(一个 Java 字节码操作框架)而非反射实现的。简单来说,动态代理是一种行为方式,而反射或 ASM 只是它的一种实现手段而已。
JDK Proxy 和CGLib的区别
JDK Proxy
-
JDK Proxy 是 Java 语言自带的功能,无需通过加载第三方类实现;
-
Java 对 JDK Proxy 提供了稳定的支持,并且会持续的升级和更新 JDK Proxy,例如 Java 8 版本中的 JDK Proxy 性能相比于之前版本提升了很多;
-
JDK Proxy 是通过拦截器加反射的方式实现的;
-
JDK Proxy 只能代理继承接口的类;
-
JDK Proxy 实现和调用起来比较简单;
-
CGLib 是第三方提供的工具,基于 ASM 实现的,性能比较高;
-
CGLib 无需通过接口来实现,它是通过实现子类的方式来完成调用的。
JDK Proxy代码演练
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JDKProxyLearn {
static interface Car{
void running();
}
static class Bus implements Car{
@Override
public void running() {
System.out.println("The Bus is running");
}
}
static class Taxi implements Car{
@Override
public void running() {
System.out.println("The Taxi is running");
}
}
//使用JDK Proxy
static class JDKProxy implements InvocationHandler{
private Object target;
public Object getInstance(Object target){
this.target=target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
//target.getClass().getClassLoader() 代理类的类加载器
//target.getClass().getInterfaces()被代理类的接口,如果有多个就是数组形式传入。
// 代理类实例
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("动态代理之前的业务处理");
Object result=method.invoke(target,args);//利用反射调用类里面的实际方法
return result;//方法的返回值,没有就是null
}
}
public static void main(String[] args) {
JDKProxy jdkProxy=new JDKProxy();
Car carInstance =(Car) jdkProxy.getInstance(new Taxi());
carInstance.running();
}
}
JDK Proxy实现动态代理的核心是实现Invocation接口,内只有一个invoke接口。
InvocationHander 是代理器,而invoke()方法是触发的执行方法,通过实现Innovation接口来拥有动态代理的能力。
CGLib 代码演练
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CGLibLearn {
static class Car{
public void running()
{
System.out.println("The car is running");
}
}
/*CGLib 代理类*/
static class CGLibProxy implements MethodInterceptor{
private Object target;
public Object getInstance(Object target)
{
this.target=target;
Enhancer enhancer=new Enhancer();
enhancer.setSuperclass(this.target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("CGlib---方法调用前调用方法");
Object result = methodProxy.invokeSuper(o, objects);
return result;
}
}
public static void main(String[] args) {
CGLibProxy proxy=new CGLibProxy();
Car car = (Car) proxy.getInstance(new Car());
car.running();
}
}
CGLib 在初始化被代理类时,是通过 Enhancer 对象把代理对象设置为被代理类的子类来实现动态代理的。因此被代理类不能被关键字 final 修饰,如果被 final 修饰,再使用 Enhancer 设置父类时会报错,动态代理的构建会失败。
动态代理知识点扩充
动态代理的时候和静态代理的区别?静态代理其实就是事先写好代理类,可以手工编写也可以使用工具生成,但它的缺点是每个业务类都要对应一个代理类,特别不灵活也不方便,于是就有了动态代理。
动态代理的常见使用场景有 RPC 框架的封装、AOP(面向切面编程)的实现、JDBC 的连接等。
Spring 框架中同时使用了两种动态代理 JDK Proxy 和 CGLib,当 Bean 实现了接口时,Spring 就会使用 JDK Proxy,在没有实现接口时就会使用 CGLib,我们也可以在配置中指定强制使用 CGLib,只需要在 Spring 配置中添加 <aop:aspectj-autoproxy proxy-target-class=“true”/> 即可。
Lombok
Lombok的实现和反射没有任何关系。Lombok的实现在编译器就完成了
其实 Lombok 是基于 Java 1.6 实现的 JSR 269: Pluggable Annotation Processing API 来实现的,也就是通过编译期自定义注解处理器来实现的,它的执行步骤如下:
从流程图中可以看出,在编译期阶段,当 Java 源码被抽象成语法树(AST)之后,Lombok 会根据自己的注解处理器动态修改 AST,增加新的代码(节点),在这一切执行之后就生成了最终的字节码(.class)文件,这就是 Lombok 的执行原理。