《猪弟拱Java》连载番外篇:Java代理(中)

栏目: Java · 发布时间: 6年前

内容简介:《猪弟拱Java》连载番外篇:Java代理(中)

在上一篇中我用一个比较详细的案例描述了一下代理的概念,在这篇文章中,主要来看一下JDK动态代理和cblib子类代理

JDK动态代理

首先我们以一个简单的案例来说一下

案例:

现在有这样一个需求,为一个短信功能提供入参日志打印、异常打印和处理、返回结果打印、方法调用结束打印。先来看一下短信功能的代码:

首先是短信接口 ISmsSupport

package proxy;

/**
 * 短信功能支持
 *
 * @author 猪弟
 * @since v1.0.0
 */
public interface ISmsSupport {

    boolean sendMsg(String content, String phoneNumber);
}

然后是一个短信功能的实现类

package proxy;
/**
 * 短信功能实现
 *
 * @author 猪弟
 * @since v1.0.0
 */
public class SmsSupportImpl implements ISmsSupport {
    @Override
    public boolean sendMsg(String content, String phoneNumber) {
        //模拟异常
        int temp = 1 / 0;
        System.out.println(content + "," + phoneNumber);
        return true;
    }
}

在实现类中,我们没有做任何的日志和异常处理 接下来我们用动态代理实现上面的需求,以及为什么要用动态代理实现? 首先我们要建一个代理类 SmsProxy

package proxy;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 短信功能代理类
 *
 * @author 猪弟
 * @since v1.0.0
 */
public class SmsProxy implements InvocationHandler {

    /**
     * 日志实例
     */
    private final static Logger LOGGER = LoggerFactory.getLogger(SmsProxy.class);
    
    /**
     * 被代理对象
     */
    private Object realSubject;

    /**
     * 构造器
     *
     * @param realSubject 被代理对象
     */
    public SmsProxy(Object realSubject) {
        this.realSubject = realSubject;
    }

    /**
     * 获取代理对象的方法
     *
     * @return 代理对象
     */
    public Object getProxy() {
        Class<?> subjectClass = realSubject.getClass();
        return Proxy.newProxyInstance(subjectClass.getClassLoader(), subjectClass.getInterfaces(), this);
    }

    /**
     * @param proxy  代理对象
     * @param method 执行的目标方法
     * @param args   方法参数
     * @return Object 目标方法执行的结果
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //目标方法的方法名
        String methodName = method.getName();

        //打印入参(前置通知)
        LOGGER.info("{} 方法的入参:{}", methodName, args);
        try {

            //反射Reflect执行核心方法
            Object result = method.invoke(realSubject, args);

            //打印执行结果(返回后通知)
            LOGGER.info("{}方法的返回结果为:{}", methodName,result);

            return result;
        } catch (Throwable e) {

            //打印异常日志(异常通知)
            LOGGER.error("执行目标方法发生异常,异常:", e);
            return Boolean.FALSE;
        } finally {

            //方法执行完打印(后置通知)
            LOGGER.info("方法执行完成");
        }
    }
}

简单解释一下这个代理类

① 首先使用jdk动态代理要实现一个 java 为我们提供的 InvocationHandler 接口,并实现 invoke 方法,下面是接口的源码。

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

② 我们要在代理类中定义一个 Object 类型的属性用于引用被代理的对象,并通过构造器初始化。

③ 在类中定义一个函数 getProxy 用于获取代理对象,这里用到了 Proxy 类的静态方法 newInstance ,下面是方法的源码内容:

@CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        //要求传入的handler不能为空
        Objects.requireNonNull(h);
        //获取被代理对象实现的所有接口
        final Class<?>[] intfs = interfaces.clone();
        //获取安全管理器
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            //检验代理的访问安全性
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * 查找或者生成一个指定的代理类
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         * 使用指定的invocation handler 反射调用代理类的构造器
         */
        try {
            if (sm != null) {
                //检查访问权限
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
            //获取代理类的构造器对象
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            //传入的invocation handler
            final InvocationHandler ih = h;
            //如果类不是public的,先赋予权限
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            //调用构造器对象的方法生成代理类实例
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

④ 最后就是实现我们的 invoke 方法了,先看一下方法的结构和我们的实现:

/**
     * @param proxy  代理对象
     * @param method 执行的目标方法
     * @param args   方法参数
     * @return Object 目标方法执行的结果
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //目标方法的方法名
        String methodName = method.getName();

        //打印入参(前置通知)
        LOGGER.info("{} 方法的入参:{}", methodName, args);
        try {

            //反射Reflect执行核心方法
            Object result = method.invoke(realSubject, args);

            //打印执行结果(返回后通知)
            LOGGER.info("{}方法的返回结果为:{}", methodName,result);

            return result;
        } catch (Throwable e) {

            //打印异常日志(异常通知)
            LOGGER.error("执行目标方法发生异常,异常:", e);
            return Boolean.FALSE;
        } finally {

            //方法执行完打印(后置通知)
            LOGGER.info("方法执行完成");
        }
    }

是不是很简单呢,下面是入口程序:

package proxy;

/**
 * 程序入口
 *
 * @author 猪弟
 * @since v1.0.0
 */
public class Bootstrap {

    public static void main(String[] args){
        demo1();
    }

    public static void demo1(){
        ISmsSupport smsSupport = new SmsSupportImpl();
        ISmsSupport proxy = (ISmsSupport) new SmsProxy(smsSupport).getProxy();
        proxy.sendMsg("hello world", "110");
    }

}

我们先看一下运行结果 先把 SmsSupportImpl 中的 int i = 1/ 0; 注释掉正常运行:

17:12:26.976 [main] INFO proxy.SmsProxy - sendMsg 方法的入参:[hello world, 110]
hello world,110
17:12:26.980 [main] INFO proxy.SmsProxy - sendMsg方法的返回结果为:true
17:12:26.980 [main] INFO proxy.SmsProxy - 方法执行完成

再取消注释,发生异常:

17:13:31.169 [main] INFO proxy.SmsProxy - sendMsg 方法的入参:[hello world, 110]
17:13:31.175 [main] ERROR proxy.SmsProxy - 执行目标方法发生异常,异常:
java.lang.reflect.InvocationTargetException: null
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at proxy.SmsProxy.invoke(SmsProxy.java:63)
	at com.sun.proxy.$Proxy0.sendMsg(Unknown Source)
	at proxy.Bootstrap.demo1(Bootstrap.java:19)
	at proxy.Bootstrap.main(Bootstrap.java:12)
Caused by: java.lang.ArithmeticException: / by zero
	at proxy.SmsSupportImpl.sendMsg(SmsSupportImpl.java:13)
	... 8 common frames omitted
17:13:31.175 [main] INFO proxy.SmsProxy - 方法执行完成

从上面的运行结果可以看出,打印了入参和返回结果,正确拦截了异常并打印了异常信息,还有方法调用完成的日志。好啦!到这里动态代理的实现就结束了,接下来看看为什么要使用动态代理。

在这个案例中其实体现不出来动态代理的优势,为什么呢,因为要打印日志的类太少了,完全可以采用硬编码的方式去实现,那么想象一下有一万个类,每个类中有10个方法,每个方法之前都没有打日志,现在要你去实现为每个方法都实现入参和返回结果打印,你还会采用硬编码的方式吗?此时动态代理就发挥了作用,你只需要写一个日志代理类,专门用来完成这个打印功能,上代码:

package proxy;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 日志代理打印类
 *
 * @author 猪弟
 * @since v1.0.0
 */
public class LogProxy<T> implements InvocationHandler {

    /**
     * 日志实例
     */
    private final static Logger LOGGER = LoggerFactory.getLogger(LogProxy.class);

    /**
     * 被代理对象
     */
    private T realSubject;

    /**
     * 构造器
     *
     * @param realSubject
     */
    public LogProxy(T realSubject) {
        this.realSubject = realSubject;
    }

    /**
     * 获取代理对象
     *
     * @return 代理
     */
    public T getProxy() {
        Class<?> subjectClass = realSubject.getClass();
        return (T) Proxy.newProxyInstance(subjectClass.getClassLoader(), subjectClass.getInterfaces(), this);
    }

    /**
     * @param proxy  代理对象
     * @param method 执行的目标方法
     * @param args   方法参数
     * @return Object 目标方法执行结果
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //目标方法的方法名
        String methodName = method.getName();

        //打印入参(前置通知)
        LOGGER.info("{} 方法的入参:{}", methodName, args);
        try {

            //执行核心方法
            Object result = method.invoke(realSubject, args);

            //打印执行结果(返回后通知)
            LOGGER.info("{}方法的返回结果为:{}", methodName,result);

            return result;
        } catch (Throwable e) {

            //打印异常日志(异常通知)
            LOGGER.error("执行目标方法发生异常,异常:", e);
            return Boolean.FALSE;
        } finally {

            //方法执行完打印(后置通知)
            LOGGER.info("方法执行完成");
        }
    }
}

其实这个类和前面的类区别不大,只是加入了泛型和把 getProxy 的返回类型改成了泛型。 调用方式和前面的类似,结果也一样,入口程序:

package proxy;

/**
 * 程序入口
 *
 * @author 猪弟
 * @since v1.0.0
 */
public class Bootstrap {
    public static void main(String[] args){
        demo2();
    }
    public static void demo2() {
        ISmsSupport smsSupport = new SmsSupportImpl();
        LogProxy<ISmsSupport> proxy = new LogProxy<>(smsSupport);
        ISmsSupport smsProxy = proxy.getProxy();
        smsProxy.sendMsg("hello world", "110");
    }
}

其实你会发现,虽然书写方便了,但是去修改调用方法的代码也是一件工作量很大的事情,我们可以采用AOP来处理,在AOP中采用切入点表达式可以不用修改调用方法的代码就能实现使用代理打印日志。关于AOP留到下一篇中去讲述。

由于时间原因,没有把反编译代码和字节码展示、解读,有时间再回来补

cglib子类代理

在上一篇文章末尾我们介绍了三种代理的使用条件和使用限制,来回顾一下

① 静态代理可以代理某一类对象,这一类对象必须实现同一接口,所以它的使用条件就是被代理类要实现接口。上面的各种场景短信都实现了ISmsService接口,所以代理类可以代理所有场景短信实现类,并调用真正的短信发送方法去发送正确的场景短信。
① 动态代理的使用条件也是被代理类要实现接口,但是动态代理能够代理所有实现了接口的类,强大必然也会有缺点:动态代理依赖Java反射机制,反射是一个比较影响性能的功能,所以动态代理性能上会比静态代理低。
③ cglib子类代理,首先需要依赖第三方库,然后它是基于字节码来生成子类代理的,没有特定的使用条件,所以也不需要实现接口,它可以代理所有的类,所以论性能是比不上静态代理的。

从上面可以看出,前两种代理都需要实现接口才能使用,而 CGLIB 不用

此处留一个面试中遇到的问题:

jdk代理中为什么一定要实现接口呢,用抽象类不可以吗?用心思考一下,相信和我一样聪明的你一定能得到答案

好的,我们把问题留到下一篇中解答

OK,接下来我们还是同样的一个需求,使用CGLIB代理的方式实现,如果你理解了上面动态代理的内容, CGLIB 的内容就能轻松的理解了,这里我们只学习使用方式,关于原理他是根据字节码动态生成的子类,猪弟的水平还达不到就不具体解释了,待日后我猪拱神功练成之时在来讲述原理。

先看一下短信发送类,没有实现任何接口:

package xin.sun.proxy.cglib;

/**
 * 短信功能实现
 *
 * @author 猪弟
 * @since v1.0.0
 */
public class SmsSupport {

    public boolean sendMsg(String content, String phoneNumber) {
        //模拟异常
        //int temp = 1 / 0;
        System.out.println(content + "," + phoneNumber);
        return true;
    }
}

再看一下代理工厂类:

引入了 spring 的 maven 依赖:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>4.3.10.RELEASE</version>
</dependency>
package xin.sun.proxy.cglib;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

/**
 * @author 猪弟
 */
public class ProxyFactory implements MethodInterceptor {
    /**
     * 被代理对象
     */
    private Object target;
    /**
     * 构造器
     *
     * @param target
     */
    public ProxyFactory(Object target) {
        this.target = target;
    }
    /**
     * 获取代理对象实例
     *
     * @return Object 代理对象实例
     */
    public Object getProxyInstance() {
        /**
         * 工具类
         */
        Enhancer enhancer = new Enhancer();
        /**
         * 设置父类
         */
        enhancer.setSuperclass(target.getClass());
        /**
         * 设置回调对象
         */
        enhancer.setCallback(this);
        /**
         * 创建子类代理对象,并返回
         */
        return enhancer.create();
    }

    /**
     * 重写拦截方法
     *
     * @param o           代理对象
     * @param method      委托类方法
     * @param objects     方法参数
     * @param methodProxy 代理方法的MethodProxy对象
     * @return Object 目标方法执行结果
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        //目标方法的方法名
        String methodName = method.getName();
        //打印入参(前置通知)
        LOGGER.info("{} 方法的入参:{}", methodName, objects);
        try {
            //执行核心方法
            Object result = method.invoke(realSubject, objects);
            //打印执行结果(返回后通知)
            LOGGER.info("{}方法的返回结果为:{}", methodName, result);
            return result;
        } catch (Throwable e) {
            //打印异常日志(异常通知)
            LOGGER.error("执行目标方法发生异常,异常:", e);
            return Boolean.FALSE;
        } finally {
            //方法执行完打印(后置通知)
            LOGGER.info("方法执行完成");
        }
    }
}

简单解释一下这个代理类

① 首先使用cglib代理要实现一个框架为我们提供的 MethodInterceptor 接口,并实现 intercept 方法。

② 我们要在代理类中定义一个 Object 类型的属性用于引用被代理的对象,并通过构造器初始化。

③ 在类中定义一个函数 getProxyInstance 用于获取代理对象,这里和jdk动态代理有点区别,这里使用的是 Enhancer 这个类,看一下方法的实现:

public Object getProxyInstance() {
        /**
         * Enhancer工具类
         */
        Enhancer enhancer = new Enhancer();
        /**
         * 设置父类
         */
        enhancer.setSuperclass(realSubject.getClass());
        /**
         * 设置回调对象,我们实现的这个类本身就是实现了接口的,所以传本类的实例就好了
         */
        enhancer.setCallback(this);
        /**
         * 创建子类代理对象,并返回
         */
        return enhancer.create();
    }

④ 最后就是实现我们的 intercept 方法了,先看一下方法的结构和我们的实现:

/**
     * 重写拦截方法
     *
     * @param o           代理对象
     * @param method      委托类方法
     * @param objects     方法参数
     * @param methodProxy 代理方法的MethodProxy对象
     * @return Object 目标方法执行结果
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        //目标方法的方法名
        String methodName = method.getName();
        //打印入参(前置通知)
        LOGGER.info("{} 方法的入参:{}", methodName, objects);
        try {
            //执行核心方法
            Object result = method.invoke(realSubject, objects);
            //打印执行结果(返回后通知)
            LOGGER.info("{}方法的返回结果为:{}", methodName, result);
            return result;
        } catch (Throwable e) {
            //打印异常日志(异常通知)
            LOGGER.error("执行目标方法发生异常,异常:", e);
            return Boolean.FALSE;
        } finally {
            //方法执行完打印(后置通知)
            LOGGER.info("方法执行完成");
        }
    }

同样的,我们写一个入口程序来检测一下:

package xin.sun.proxy.cglib;

/**
 * @author 猪弟
 */
public class Bootstrap {

    public static void main(String[] args) {
        demo();
    }

    public static void demo(){
        ProxyFactory proxyFactory = new ProxyFactory(new SmsSupport());
        SmsSupport proxyInstance = (SmsSupport) proxyFactory.getProxyInstance();
        proxyInstance.sendMsg("CGLIB 代理","4008008820");
    }
}

看看正常运行的结果:

22:06:26.439 [main] INFO xin.sun.proxy.cglib.ProxyFactory - sendMsg 方法的入参:[CGLIB 代理, 4008008820]
CGLIB 代理,4008008820
22:06:26.445 [main] INFO xin.sun.proxy.cglib.ProxyFactory - sendMsg方法的返回结果为:true
22:06:26.445 [main] INFO xin.sun.proxy.cglib.ProxyFactory - 方法执行完成

同样的,我们人为制造一个异常 int temp = 1 / 0; ,看看异常能否被捕获:

22:10:36.454 [main] INFO xin.sun.proxy.cglib.ProxyFactory - sendMsg 方法的入参:[CGLIB 代理, 4008008820]
22:10:36.460 [main] ERROR xin.sun.proxy.cglib.ProxyFactory - 执行目标方法发生异常,异常:
java.lang.reflect.InvocationTargetException: null
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at xin.sun.proxy.cglib.ProxyFactory.intercept(ProxyFactory.java:80)
	at xin.sun.proxy.cglib.SmsSupport$$EnhancerByCGLIB$$8593c815.sendMsg(<generated>)
	at xin.sun.proxy.cglib.Bootstrap.demo(Bootstrap.java:15)
	at xin.sun.proxy.cglib.Bootstrap.main(Bootstrap.java:9)
Caused by: java.lang.ArithmeticException: / by zero
	at xin.sun.proxy.cglib.SmsSupport.sendMsg(SmsSupport.java:13)
	... 8 common frames omitted
22:10:36.461 [main] INFO xin.sun.proxy.cglib.ProxyFactory - 方法执行完成

从结果可以看到,完美的捕获到了异常

可以看到,在cglib中的短信发送类没有实现接口,同样的也实现了代理的功能,所以cglib才是真的能代理所有的类,这也是对jdk动态代理的一个补充吧,但是在Spring的AOP中默认是使用jdk动态代理的,如果被代理类没有实现接口,Spring会自动为我们切换cglib子类代理,是不是觉得Spring很人性化,其实 Spring AOP 为我们提供了更加方便的方式去做代理,下一篇文章会讲述Spring中的AOP编程。

Spring AOP 把代理做了一次封装,用起来当然更加方便了,但是要想成为java大神,还是要追究其实质的,这也是为什么我要写这两篇文章的原因,所有的框架都是基于Java基础+编程思想来实现的,其实明白了框架的设计,我们自己也能实现Spring IOC DI AOP,等有机会为大家手撸一波Spring框架,当然Spring是高度的抽象,所以源码阅读起来没那么容易,但是其思想还是很容易懂的,学会思想才是真的叼,毕竟思想语言无关。

磊叔是Spring专家,感兴趣的多看看磊叔的博客哦!!!

为了提供更加方便的阅读,小伙伴们可以关注我们的公众微信号哦...

《猪弟拱Java》连载番外篇:Java代理(中)

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

学习Web设计

学习Web设计

罗宾斯 / 靳志伟 / 机械工业出版社 / 2009-1 / 65.00元

《学习Web设计(第3版)》从说明网站和网页是如何工作开始,逐步深入。当你看完《学习Web设计(第3版)》时,你将掌握使用优化的图像文件来创建多列CSS布局的技术,而且你将知道如何创建网页。这一版经过了彻底的修订,它可以教你如何根据现代设计的实践经验和专业标准来创建网站。《学习Web设计(第3版)》包含了一些练习,可以帮助你学习各种技术,还有一些小测验可以确保你及时掌握重要的概念。如果你对网站设计......一起来看看 《学习Web设计》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具