动态代理浅入浅出

180 阅读3分钟

1.简介

动态代理在我们日常业务代码中,用到的可能性比较小,但是对于开发框架等方面,应用的就非常广泛了,常见的代理有两种,一种是静态代理,另外一种是动态代理

2.静态代理

在JVM编译期间就进行了处理,生成一个个的class文件,这种代理方式不友好,需要对每一个被代理类添加一个代理类,所以基本上没有使用场景

3.动态代理

动态代理则是在JVM运行期间进行动态增强的,对于一种代理方式,只需要编写一个代理类即可,场景的动态代理有JDK Proxy和Cglib Proxy两种

3.1 JDK Proxy

被代理类必须继承接口或者是本身就是接口,主要依赖InvocationHandler,Proxy.newProxyInstance()两个类

  • 接口
public interface ISms {
    String send(String msg);

    void call(String msg);
}
  • 接口实现类
@Slf4j
public class SmsImpl implements ISms{

    @Override
    public void call(String msg) {
        log.info(msg + "打电话成功!");
    }

    @Override
    public String send(String msg) {
        log.info(msg + "发送消息成功!");
        return "你好!";
    }
}
  • 代理类
@Slf4j
public class JdkProxy implements InvocationHandler {

    // 被代理的类
    public Object target;

    public JdkProxy(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log.info("代理方法增强前!");

        Object rs = method.invoke(target, args);

        log.info("代理方法增强后!");
        return rs;
    }


}
  • 获取代理类
public class NewJdkProxy {
    public static Object getJdkProxy(Object target) {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new JdkProxy(target)   // 代理对象对应的自定义 InvocationHandler
        );
    }
}
  • 测试JDK代理
public class TestJdkProxy {
    public static void main(String[] args) {
        // 被代理类必须实现接口,否则会报错,强转也要转为接口
        ISms proxy = (ISms) NewJdkProxy.getJdkProxy(new SmsImpl());
        String java = proxy.send("Java");
        System.out.println(java);
        proxy.call("1234");

        IMail mail = (IMail) NewJdkProxy.getJdkProxy(new MailImpl());
        mail.send("1234567");
    }
}

3.2 Cglib Proxy

该代理方式不需要被代理类实现接口,但是由于该代理方式是通过继承的方式,所以不允许被代理类有被final修饰

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>
  • 代理类
@Slf4j
public class CglibProxy implements MethodInterceptor {

    /**
     * @param o           被代理的类
     * @param method      被代理的方法
     * @param args        方法参数
     * @param methodProxy 调用原被代理类
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        log.info("cglib代理方法增强前,{}", method.getName());
        Object rs = methodProxy.invokeSuper(o, args);
        log.info("cglib代理方法增强后,{}", method.getName());
        return rs;
    }
}
  • 获取代理类
public class NewCglibProxy {
    public static Object getCglibProxy(Class<?> clazz) {
        // 创建动态代理增强类
        Enhancer enhancer = new Enhancer();
        // 设置类加载器
        enhancer.setClassLoader(clazz.getClassLoader());
        // 设置被代理类
        enhancer.setSuperclass(clazz);
        // 设置代理类
        enhancer.setCallback(new CglibProxy());
        // 创建代理类
        return enhancer.create();
    }
}
  • 测试Cgli Proxy
public class TestCglibProxy {
    public static void main(String[] args) {
        // Cglib 代理的方式是通过继承类的方式实现的,所以要求被代理类不能用final修饰
        // 被代理类可以不用实现接口,即使实现了也没有关系
        MailImpl mail = (MailImpl) NewCglibProxy.getCglibProxy(MailImpl.class);
        mail.send("8888");
    }
}
  • JDK 动态代理和 CGLIB 动态代理对比

1.JDK 动态代理只能只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。

2.就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。